<?php

// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.

/**
 * Library code used by the roles administration interfaces.
 *
 * Responds to actions:
 *   add       - add a new role
 *   duplicate - like add, only initialise the new role by using an existing one.
 *   edit      - edit the definition of a role
 *   view      - view the definition of a role
 *
 * @package    core
 * @subpackage role
 * @copyright  1999 onwards Martin Dougiamas (http://dougiamas.com)
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 */

require_once($CFG->libdir.'/adminlib.php');
require_once($CFG->dirroot.'/user/selector/lib.php');

// Classes for producing tables with one row per capability ====================

/**
 * This class represents a table with one row for each of a list of capabilities
 * where the first cell in the row contains the capability name, and there is
 * arbitrary stuff in the rest of the row. This class is used by
 * admin/roles/manage.php, override.php and check.php.
 *
 * An ajaxy search UI shown at the top, if JavaScript is on.
 */
abstract class capability_table_base {
    /** The context this table relates to. */
    protected $context;

    /** The capabilities to display. Initialised as fetch_context_capabilities($context). */
    protected $capabilities = array();

    /** Added as an id="" attribute to the table on output. */
    protected $id;

    /** Added to the class="" attribute on output. */
    protected $classes = array('rolecap');

    /** Default number of capabilities in the table for the search UI to be shown. */
    const NUM_CAPS_FOR_SEARCH = 12;

    /**
     * Constructor
     * @param object $context the context this table relates to.
     * @param string $id what to put in the id="" attribute.
     */
    public function __construct($context, $id) {
        $this->context = $context;
        $this->capabilities = fetch_context_capabilities($context);
        $this->id = $id;
    }

    /**
     * Use this to add class="" attributes to the table. You get the rolecap by
     * default.
     * @param array $classnames of class names.
     */
    public function add_classes($classnames) {
        $this->classes = array_unique(array_merge($this->classes, $classnames));
    }

    /**
     * Display the table.
     */
    public function display() {
        if (count($this->capabilities) > capability_table_base::NUM_CAPS_FOR_SEARCH) {
            global $PAGE;
            $PAGE->requires->strings_for_js(array('filter','clear'),'moodle');
            $PAGE->requires->js_init_call('M.core_role.init_cap_table_filter', array($this->id, $this->context->id));
        }
        echo '<table class="' . implode(' ', $this->classes) . '" id="' . $this->id . '">' . "\n<thead>\n";
        echo '<tr><th class="name" align="left" scope="col">' . get_string('capability','role') . '</th>';
        $this->add_header_cells();
        echo "</tr>\n</thead>\n<tbody>\n";

    /// Loop over capabilities.
        $contextlevel = 0;
        $component = '';
        foreach ($this->capabilities as $capability) {
            if ($this->skip_row($capability)) {
                continue;
            }

        /// Prints a breaker if component or name or context level has changed
            if (component_level_changed($capability, $component, $contextlevel)) {
                $this->print_heading_row($capability);
            }
            $contextlevel = $capability->contextlevel;
            $component = $capability->component;

        /// Start the row.
            echo '<tr class="' . implode(' ', array_unique(array_merge(array('rolecap'),
                    $this->get_row_classes($capability)))) . '">';

        /// Table cell for the capability name.
            echo '<th scope="row" class="name"><span class="cap-desc">' . get_capability_docs_link($capability) .
                    '<span class="cap-name">' . $capability->name . '</span></span></th>';

        /// Add the cells specific to this table.
            $this->add_row_cells($capability);

        /// End the row.
            echo "</tr>\n";
        }

    /// End of the table.
        echo "</tbody>\n</table>\n";
    }

    /**
     * Used to output a heading rows when the context level or component changes.
     * @param object $capability gives the new component and contextlevel.
     */
    protected function print_heading_row($capability) {
        echo '<tr class="rolecapheading header"><td colspan="' . (1 + $this->num_extra_columns()) . '" class="header"><strong>' .
                get_component_string($capability->component, $capability->contextlevel) .
                '</strong></td></tr>';

    }

    /** For subclasses to override, output header cells, after the initial capability one. */
    protected abstract function add_header_cells();

    /** For subclasses to override, return the number of cells that add_header_cells/add_row_cells output. */
    protected abstract function num_extra_columns();

    /**
     * For subclasses to override. Allows certain capabilties
     * to be left out of the table.
     *
     * @param object $capability the capability this row relates to.
     * @return boolean. If true, this row is omitted from the table.
     */
    protected function skip_row($capability) {
        return false;
    }

    /**
     * For subclasses to override. A change to reaturn class names that are added
     * to the class="" attribute on the &lt;tr> for this capability.
     *
     * @param object $capability the capability this row relates to.
     * @return array of class name strings.
     */
    protected function get_row_classes($capability) {
        return array();
    }

    /**
     * For subclasses to override. Output the data cells for this capability. The
     * capability name cell will already have been output.
     *
     * You can rely on get_row_classes always being called before add_row_cells.
     *
     * @param object $capability the capability this row relates to.
     */
    protected abstract function add_row_cells($capability);
}

/**
 * Subclass of capability_table_base for use on the Check permissions page.
 *
 * We have one additional column, Allowed, which contains yes/no.
 */
class check_capability_table extends capability_table_base {
    protected $user;
    protected $fullname;
    protected $contextname;
    protected $stryes;
    protected $strno;
    private $hascap;

    /**
     * Constructor
     * @param object $context the context this table relates to.
     * @param object $user the user we are generating the results for.
     * @param string $contextname print_context_name($context) - to save recomputing.
     */
    public function __construct($context, $user, $contextname) {
        global $CFG;
        parent::__construct($context, 'explaincaps');
        $this->user = $user;
        $this->fullname = fullname($user);
        $this->contextname = $contextname;
        $this->stryes = get_string('yes');
        $this->strno = get_string('no');
    }

    protected function add_header_cells() {
        echo '<th>' . get_string('allowed', 'role') . '</th>';
    }

    protected function num_extra_columns() {
        return 1;
    }

    protected function get_row_classes($capability) {
        $this->hascap = has_capability($capability->name, $this->context, $this->user->id);
        if ($this->hascap) {
            return array('yes');
        } else {
            return array('no');
        }
    }

    protected function add_row_cells($capability) {
        global $OUTPUT;
        if ($this->hascap) {
            $result = $this->stryes;
        } else {
            $result = $this->strno;
        }
        $a = new stdClass;
        $a->fullname = $this->fullname;
        $a->capability = $capability->name;
        $a->context = $this->contextname;
        echo '<td>' . $result . '</td>';
    }
}


/**
 * Subclass of capability_table_base for use on the Permissions page.
 */
class permissions_table extends capability_table_base {
    protected $contextname;
    protected $allowoverrides;
    protected $allowsafeoverrides;
    protected $overridableroles;
    protected $roles;
    protected $icons = array();

    /**
     * Constructor
     * @param object $context the context this table relates to.
     * @param string $contextname print_context_name($context) - to save recomputing.
     */
    public function __construct($context, $contextname, $allowoverrides, $allowsafeoverrides, $overridableroles) {
        parent::__construct($context, 'permissions');
        $this->contextname = $contextname;
        $this->allowoverrides = $allowoverrides;
        $this->allowsafeoverrides = $allowsafeoverrides;
        $this->overridableroles = $overridableroles;

        $roles = get_all_roles($context);
        $this->roles = role_fix_names(array_reverse($roles, true), $context, ROLENAME_ALIAS, true);

    }

    protected function add_header_cells() {
        echo '<th>' . get_string('risks', 'role') . '</th>';
        echo '<th>' . get_string('neededroles', 'role') . '</th>';
        echo '<th>' . get_string('prohibitedroles', 'role') . '</th>';
    }

    protected function num_extra_columns() {
        return 3;
    }

    protected function add_row_cells($capability) {
        global $OUTPUT, $PAGE;

        $context = $this->context;
        $contextid = $this->context->id;
        $allowoverrides = $this->allowoverrides;
        $allowsafeoverrides = $this->allowsafeoverrides;
        $overridableroles = $this->overridableroles;
        $roles = $this->roles;


        list($needed, $forbidden) = get_roles_with_cap_in_context($context, $capability->name);
        $neededroles    = array();
        $forbiddenroles = array();
        $allowable      = $overridableroles;
        $forbitable     = $overridableroles;
        foreach ($neededroles as $id=>$unused) {
            unset($allowable[$id]);
        }
        foreach ($forbidden as $id=>$unused) {
            unset($allowable[$id]);
            unset($forbitable[$id]);
        }

        foreach ($roles as $id=>$name) {
            if (isset($needed[$id])) {
                $neededroles[$id] = $roles[$id];
                if (isset($overridableroles[$id]) and ($allowoverrides or ($allowsafeoverrides and is_safe_capability($capability)))) {
                    $preventurl = new moodle_url($PAGE->url, array('contextid'=>$contextid, 'roleid'=>$id, 'capability'=>$capability->name, 'prevent'=>1));
                    $neededroles[$id] .= $OUTPUT->action_icon($preventurl, new pix_icon('t/delete', get_string('prevent', 'role')));
                }
            }
        }
        $neededroles = implode(', ', $neededroles);
        foreach ($roles as $id=>$name) {
            if (isset($forbidden[$id])  and ($allowoverrides or ($allowsafeoverrides and is_safe_capability($capability)))) {
                $forbiddenroles[$id] = $roles[$id];
                if (isset($overridableroles[$id]) and prohibit_is_removable($id, $context, $capability->name)) {
                    $unprohibiturl = new moodle_url($PAGE->url, array('contextid'=>$contextid, 'roleid'=>$id, 'capability'=>$capability->name, 'unprohibit'=>1));
                    $forbiddenroles[$id] .= $OUTPUT->action_icon($unprohibiturl, new pix_icon('t/delete', get_string('delete')));
                }
            }
        }
        $forbiddenroles = implode(', ', $forbiddenroles);

        if ($allowable and ($allowoverrides or ($allowsafeoverrides and is_safe_capability($capability)))) {
            $allowurl = new moodle_url($PAGE->url, array('contextid'=>$contextid, 'capability'=>$capability->name, 'allow'=>1));
            $neededroles .= '<div class="allowmore">'.$OUTPUT->action_icon($allowurl, new pix_icon('t/add', get_string('allow', 'role'))).'</div>';
        }

        if ($forbitable and ($allowoverrides or ($allowsafeoverrides and is_safe_capability($capability)))) {
            $prohibiturl = new moodle_url($PAGE->url, array('contextid'=>$contextid, 'capability'=>$capability->name, 'prohibit'=>1));
            $forbiddenroles .= '<div class="prohibitmore">'.$OUTPUT->action_icon($prohibiturl, new pix_icon('t/add', get_string('prohibit', 'role'))).'</div>';
        }

        $risks = $this->get_risks($capability);

        echo '<td>' . $risks . '</td>';
        echo '<td>' . $neededroles . '</td>';
        echo '<td>' . $forbiddenroles . '</td>';
    }

    protected function get_risks($capability) {
        global $OUTPUT;

        $allrisks = get_all_risks();
        $risksurl = new moodle_url(get_docs_url(s(get_string('risks', 'role'))));

        $return = '';

        foreach ($allrisks as $type=>$risk) {
            if ($risk & (int)$capability->riskbitmask) {
                if (!isset($this->icons[$type])) {
                    $pixicon = new pix_icon('/i/' . str_replace('risk', 'risk_', $type), get_string($type . 'short', 'admin'));
                    $this->icons[$type] = $OUTPUT->action_icon($risksurl, $pixicon, new popup_action('click', $risksurl));
                }
                $return .= $this->icons[$type];
            }
        }

        return $return;
    }
}


/**
 * This subclass is the bases for both the define roles and override roles
 * pages. As well as adding the risks columns, this also provides generic
 * facilities for showing a certain number of permissions columns, and
 * recording the current and submitted permissions for each capability.
 */
abstract class capability_table_with_risks extends capability_table_base {
    protected $allrisks;
    protected $allpermissions; // We don't need perms ourselves, but all our subclasses do.
    protected $strperms; // Language string cache.
    protected $risksurl; // URL in moodledocs about risks.
    protected $riskicons = array(); // Cache to avoid regenerating the HTML for each risk icon.
    /** The capabilities to highlight as default/inherited. */
    protected $parentpermissions;
    protected $displaypermissions;
    protected $permissions;
    protected $changed;
    protected $roleid;

    public function __construct($context, $id, $roleid) {
        parent::__construct($context, $id);

        $this->allrisks = get_all_risks();
        $this->risksurl = get_docs_url(s(get_string('risks', 'role')));

        $this->allpermissions = array(
            CAP_INHERIT => 'inherit',
            CAP_ALLOW => 'allow',
            CAP_PREVENT => 'prevent' ,
            CAP_PROHIBIT => 'prohibit',
        );

        $this->strperms = array();
        foreach ($this->allpermissions as $permname) {
            $this->strperms[$permname] =  get_string($permname, 'role');
        }

        $this->roleid = $roleid;
        $this->load_current_permissions();

    /// Fill in any blank permissions with an explicit CAP_INHERIT, and init a locked field.
        foreach ($this->capabilities as $capid => $cap) {
            if (!isset($this->permissions[$cap->name])) {
                $this->permissions[$cap->name] = CAP_INHERIT;
            }
            $this->capabilities[$capid]->locked = false;
        }
    }

    protected function load_current_permissions() {
        global $DB;

    /// Load the overrides/definition in this context.
        if ($this->roleid) {
            $this->permissions = $DB->get_records_menu('role_capabilities', array('roleid' => $this->roleid,
                    'contextid' => $this->context->id), '', 'capability,permission');
        } else {
            $this->permissions = array();
        }
    }

    protected abstract function load_parent_permissions();

    /**
     * Update $this->permissions based on submitted data, while making a list of
     * changed capabilities in $this->changed.
     */
    public function read_submitted_permissions() {
        $this->changed = array();

        foreach ($this->capabilities as $cap) {
            if ($cap->locked || $this->skip_row($cap)) {
            /// The user is not allowed to change the permission for this capability
                continue;
            }

            $permission = optional_param($cap->name, null, PARAM_PERMISSION);
            if (is_null($permission)) {
            /// A permission was not specified in submitted data.
                continue;
            }

        /// If the permission has changed, update $this->permissions and
        /// Record the fact there is data to save.
            if ($this->permissions[$cap->name] != $permission) {
                $this->permissions[$cap->name] = $permission;
                $this->changed[] = $cap->name;
            }
        }
    }

    /**
     * Save the new values of any permissions that have been changed.
     */
    public function save_changes() {
    /// Set the permissions.
        foreach ($this->changed as $changedcap) {
            assign_capability($changedcap, $this->permissions[$changedcap],
                    $this->roleid, $this->context->id, true);
        }

    /// Force accessinfo refresh for users visiting this context.
        mark_context_dirty($this->context->path);
    }

    public function display() {
        $this->load_parent_permissions();
        foreach ($this->capabilities as $cap) {
            if (!isset($this->parentpermissions[$cap->name])) {
                $this->parentpermissions[$cap->name] = CAP_INHERIT;
            }
        }
        parent::display();
    }

    protected function add_header_cells() {
        global $OUTPUT;
        echo '<th colspan="' . count($this->displaypermissions) . '" scope="col">' .
                get_string('permission', 'role') . ' ' . $OUTPUT->help_icon('permission', 'role') . '</th>';
        echo '<th class="risk" colspan="' . count($this->allrisks) . '" scope="col">' . get_string('risks','role') . '</th>';
    }

    protected function num_extra_columns() {
        return count($this->displaypermissions) + count($this->allrisks);
    }

    protected function get_row_classes($capability) {
        $rowclasses = array();
        foreach ($this->allrisks as $riskname => $risk) {
            if ($risk & (int)$capability->riskbitmask) {
                $rowclasses[] = $riskname;
            }
        }
        return $rowclasses;
    }

    protected abstract function add_permission_cells($capability);

    protected function add_row_cells($capability) {
        $this->add_permission_cells($capability);
    /// One cell for each possible risk.
        foreach ($this->allrisks as $riskname => $risk) {
            echo '<td class="risk ' . str_replace('risk', '', $riskname) . '">';
            if ($risk & (int)$capability->riskbitmask) {
                echo $this->get_risk_icon($riskname);
            }
            echo '</td>';
        }
    }

    /**
     * Print a risk icon, as a link to the Risks page on Moodle Docs.
     *
     * @param string $type the type of risk, will be one of the keys from the
     *      get_all_risks array. Must start with 'risk'.
     */
    function get_risk_icon($type) {
        global $OUTPUT;
        if (!isset($this->riskicons[$type])) {
            $iconurl = $OUTPUT->pix_url('i/' . str_replace('risk', 'risk_', $type));
            $text = '<img src="' . $iconurl . '" alt="' . get_string($type . 'short', 'admin') . '" />';
            $action = new popup_action('click', $this->risksurl, 'docspopup');
            $this->riskicons[$type] = $OUTPUT->action_link($this->risksurl, $text, $action, array('title'=>get_string($type, 'admin')));
        }
        return $this->riskicons[$type];
    }
}

/**
 * As well as tracking the permissions information about the role we are creating
 * or editing, we also track the other information about the role. (This class is
 * starting to be more and more like a formslib form in some respects.)
 */
class define_role_table_advanced extends capability_table_with_risks {
    /** Used to store other information (besides permissions) about the role we are creating/editing. */
    protected $role;
    /** Used to store errors found when validating the data. */
    protected $errors;
    protected $contextlevels;
    protected $allcontextlevels;
    protected $disabled = '';

    public function __construct($context, $roleid) {
        $this->roleid = $roleid;
        parent::__construct($context, 'defineroletable', $roleid);
        $this->displaypermissions = $this->allpermissions;
        $this->strperms[$this->allpermissions[CAP_INHERIT]] = get_string('notset', 'role');

        $this->allcontextlevels = array(
            CONTEXT_SYSTEM => get_string('coresystem'),
            CONTEXT_USER => get_string('user'),
            CONTEXT_COURSECAT => get_string('category'),
            CONTEXT_COURSE => get_string('course'),
            CONTEXT_MODULE => get_string('activitymodule'),
            CONTEXT_BLOCK => get_string('block')
        );
    }

    protected function load_current_permissions() {
        global $DB;
        if ($this->roleid) {
            if (!$this->role = $DB->get_record('role', array('id' => $this->roleid))) {
                throw new moodle_exception('invalidroleid');
            }
            $contextlevels = get_role_contextlevels($this->roleid);
            // Put the contextlevels in the array keys, as well as the values.
            if (!empty($contextlevels)) {
                $this->contextlevels = array_combine($contextlevels, $contextlevels);
            } else {
                $this->contextlevels = array();
            }
        } else {
            $this->role = new stdClass;
            $this->role->name = '';
            $this->role->shortname = '';
            $this->role->description = '';
            $this->role->archetype = '';
            $this->contextlevels = array();
        }
        parent::load_current_permissions();
    }

    public function read_submitted_permissions() {
        global $DB;
        $this->errors = array();

        // Role short name. We clean this in a special way. We want to end up
        // with only lowercase safe ASCII characters.
        $shortname = optional_param('shortname', null, PARAM_RAW);
        if (!is_null($shortname)) {
            $this->role->shortname = $shortname;
            $this->role->shortname = textlib::specialtoascii($this->role->shortname);
            $this->role->shortname = textlib::strtolower(clean_param($this->role->shortname, PARAM_ALPHANUMEXT));
            if (empty($this->role->shortname)) {
                $this->errors['shortname'] = get_string('errorbadroleshortname', 'role');
            }
        }
        if ($DB->record_exists_select('role', 'shortname = ? and id <> ?', array($this->role->shortname, $this->roleid))) {
            $this->errors['shortname'] = get_string('errorexistsroleshortname', 'role');
        }

        // Role name.
        $name = optional_param('name', null, PARAM_TEXT);
        if (!is_null($name)) {
            $this->role->name = $name;
            // Hack: short names of standard roles are equal to archetypes, empty name means localised via lang packs.
            $archetypes = get_role_archetypes();
            if (!isset($archetypes[$shortname]) and html_is_blank($this->role->name)) {
                $this->errors['name'] = get_string('errorbadrolename', 'role');
            }
        }
        if ($this->role->name !== '' and $DB->record_exists_select('role', 'name = ? and id <> ?', array($this->role->name, $this->roleid))) {
            $this->errors['name'] = get_string('errorexistsrolename', 'role');
        }

        // Description.
        $description = optional_param('description', null, PARAM_RAW);
        if (!is_null($description)) {
            $this->role->description = $description;
        }

        // Legacy type.
        $archetype = optional_param('archetype', null, PARAM_RAW);
        if (isset($archetype)) {
            $archetypes = get_role_archetypes();
            if (isset($archetypes[$archetype])){
                $this->role->archetype = $archetype;
            } else {
                $this->role->archetype = '';
            }
        }

        // Assignable context levels.
        foreach ($this->allcontextlevels as $cl => $notused) {
            $assignable = optional_param('contextlevel' . $cl, null, PARAM_BOOL);
            if (!is_null($assignable)) {
                if ($assignable) {
                    $this->contextlevels[$cl] = $cl;
                } else {
                    unset($this->contextlevels[$cl]);
                }
            }
        }

        // Now read the permissions for each capability.
        parent::read_submitted_permissions();
    }

    public function is_submission_valid() {
        return empty($this->errors);
    }

    /**
     * Call this after the table has been initialised, so to indicate that
     * when save is called, we want to make a duplicate role.
     */
    public function make_copy() {
        $this->roleid = 0;
        unset($this->role->id);
        $this->role->name = role_get_name($this->role, null, ROLENAME_ORIGINAL) . ' ' . get_string('copyasnoun');
        $this->role->shortname .= 'copy';
    }

    public function get_role_name() {
        return $this->role->name;
    }

    public function get_role_id() {
        return $this->role->id;
    }

    public function get_archetype() {
        return $this->role->archetype;
    }

    protected function load_parent_permissions() {
        $this->parentpermissions = get_default_capabilities($this->role->archetype);
    }

    public function save_changes() {
        global $DB;

        if (!$this->roleid) {
            // Creating role
            $this->role->id = create_role($this->role->name, $this->role->shortname, $this->role->description, $this->role->archetype);
            $this->roleid = $this->role->id; // Needed to make the parent::save_changes(); call work.
        } else {
            // Updating role
            $DB->update_record('role', $this->role);
        }

        // Assignable contexts.
        set_role_contextlevels($this->role->id, $this->contextlevels);

        // Permissions.
        parent::save_changes();
    }

    protected function get_name_field($id) {
        return '<input type="text" id="' . $id . '" name="' . $id . '" maxlength="254" value="' . s($this->role->name) . '" />';
    }

    protected function get_shortname_field($id) {
        return '<input type="text" id="' . $id . '" name="' . $id . '" maxlength="254" value="' . s($this->role->shortname) . '" />';
    }

    protected function get_description_field($id) {
        return print_textarea(true, 10, 50, 50, 10, 'description', $this->role->description, 0, true);
    }

    protected function get_archetype_field($id) {
        $options = array();
        $options[''] = get_string('none');
        foreach(get_role_archetypes() as $type) {
            $options[$type] = get_string('archetype'.$type, 'role');
        }
        return html_writer::select($options, 'archetype', $this->role->archetype, false);
    }

    protected function get_assignable_levels_control() {
        $output = '';
        foreach ($this->allcontextlevels as $cl => $clname) {
            $extraarguments = $this->disabled;
            if (in_array($cl, $this->contextlevels)) {
                $extraarguments .= 'checked="checked" ';
            }
            if (!$this->disabled) {
                $output .= '<input type="hidden" name="contextlevel' . $cl . '" value="0" />';
            }
            $output .= '<input type="checkbox" id="cl' . $cl . '" name="contextlevel' . $cl .
                    '" value="1" ' . $extraarguments . '/> ';
            $output .= '<label for="cl' . $cl . '">' . $clname . "</label><br />\n";
        }
        return $output;
    }

    /**
     * Returns an array of roles of the allowed type.
     *
     * @param string $type Must be one of: assign, switch, or override.
     * @return array
     */
    protected function get_allow_roles_list($type) {
        global $DB;

        if ($type !== 'assign' and $type !== 'switch' and $type !== 'override') {
            debugging('Invalid role allowed type specified', DEBUG_DEVELOPER);
            return array();
        }

        if (empty($this->roleid)) {
            return array();
        }

        $sql = "SELECT r.*
                  FROM {role} r
                  JOIN {role_allow_{$type}} a ON a.allow{$type} = r.id
                 WHERE a.roleid = :roleid
              ORDER BY r.sortorder ASC";
        return $DB->get_records_sql($sql, array('roleid'=>$this->roleid));
    }

    /**
     * Returns an array of roles with the allowed type.
     *
     * @param string $type Must be one of: assign, switch, or override.
     * @return array Am array of role names with the allowed type
     */
    protected function get_allow_role_control($type) {
        if ($roles = $this->get_allow_roles_list($type)) {
            $roles = role_fix_names($roles, null, ROLENAME_ORIGINAL, true);
            return implode(', ', $roles);
        } else {
            return get_string('none');
        }
    }

    /**
     * Returns information about the risks associated with a role.
     *
     * @return string
     */
    protected function get_role_risks_info() {
        return '';
    }

    protected function print_field($name, $caption, $field) {
        global $OUTPUT;
        // Attempt to generate HTML like formslib.
        echo '<div class="fitem">';
        echo '<div class="fitemtitle">';
        if ($name) {
            echo '<label for="' . $name . '">';
        }
        echo $caption;
        if ($name) {
            echo "</label>\n";
        }
        echo '</div>';
        if (isset($this->errors[$name])) {
            $extraclass = ' error';
        } else {
            $extraclass = '';
        }
        echo '<div class="felement' . $extraclass . '">';
        if (isset($this->errors[$name])) {
            echo $OUTPUT->error_text($this->errors[$name]);
        }
        echo $field;
        echo '</div>';
        echo '</div>';
    }

    protected function print_show_hide_advanced_button() {
        echo '<p class="definenotice">' . get_string('highlightedcellsshowdefault', 'role') . ' </p>';
        echo '<div class="advancedbutton">';
        echo '<input type="submit" name="toggleadvanced" value="' . get_string('hideadvanced', 'form') . '" />';
        echo '</div>';
    }

    public function display() {
        global $OUTPUT;
        // Extra fields at the top of the page.
        echo '<div class="topfields clearfix">';
        $this->print_field('shortname', get_string('roleshortname', 'role').'&nbsp;'.$OUTPUT->help_icon('roleshortname', 'role'), $this->get_shortname_field('shortname'));
        $this->print_field('name', get_string('customrolename', 'role').'&nbsp;'.$OUTPUT->help_icon('customrolename', 'role'), $this->get_name_field('name'));
        $this->print_field('edit-description', get_string('customroledescription', 'role').'&nbsp;'.$OUTPUT->help_icon('customroledescription', 'role'), $this->get_description_field('description'));
        $this->print_field('menuarchetype', get_string('archetype', 'role').'&nbsp;'.$OUTPUT->help_icon('archetype', 'role'), $this->get_archetype_field('archetype'));
        $this->print_field('', get_string('maybeassignedin', 'role'), $this->get_assignable_levels_control());
        $this->print_field('', get_string('allowassign', 'role'), $this->get_allow_role_control('assign'));
        $this->print_field('', get_string('allowoverride', 'role'), $this->get_allow_role_control('override'));
        $this->print_field('', get_string('allowswitch', 'role'), $this->get_allow_role_control('switch'));
        if ($risks = $this->get_role_risks_info()) {
            $this->print_field('', get_string('rolerisks', 'role'), $risks);
        }
        echo "</div>";

        $this->print_show_hide_advanced_button();

        // Now the permissions table.
        parent::display();
    }

    protected function add_permission_cells($capability) {
    /// One cell for each possible permission.
        foreach ($this->displaypermissions as $perm => $permname) {
            $strperm = $this->strperms[$permname];
            $extraclass = '';
            if ($perm == $this->parentpermissions[$capability->name]) {
                $extraclass = ' capdefault';
            }
            $checked = '';
            if ($this->permissions[$capability->name] == $perm) {
                $checked = 'checked="checked" ';
            }
            echo '<td class="' . $permname . $extraclass . '">';
            echo '<label><input type="radio" name="' . $capability->name .
                    '" value="' . $perm . '" ' . $checked . '/> ';
            echo '<span class="note">' . $strperm . '</span>';
            echo '</label></td>';
        }
    }
}

class define_role_table_basic extends define_role_table_advanced {
    protected $stradvmessage;
    protected $strallow;

    public function __construct($context, $roleid) {
        parent::__construct($context, $roleid);
        $this->displaypermissions = array(CAP_ALLOW => $this->allpermissions[CAP_ALLOW]);
        $this->stradvmessage = get_string('useshowadvancedtochange', 'role');
        $this->strallow = $this->strperms[$this->allpermissions[CAP_ALLOW]];
    }

    protected function print_show_hide_advanced_button() {
        echo '<div class="advancedbutton">';
        echo '<input type="submit" name="toggleadvanced" value="' . get_string('showadvanced', 'form') . '" />';
        echo '</div>';
    }

    protected function add_permission_cells($capability) {
        $perm = $this->permissions[$capability->name];
        $permname = $this->allpermissions[$perm];
        $defaultperm = $this->allpermissions[$this->parentpermissions[$capability->name]];
        echo '<td class="' . $permname . '">';
        if ($perm == CAP_ALLOW || $perm == CAP_INHERIT) {
            $checked = '';
            if ($perm == CAP_ALLOW) {
                $checked = 'checked="checked" ';
            }
            echo '<input type="hidden" name="' . $capability->name . '" value="' . CAP_INHERIT . '" />';
            echo '<label><input type="checkbox" name="' . $capability->name .
                    '" value="' . CAP_ALLOW . '" ' . $checked . '/> ' . $this->strallow . '</label>';
        } else {
            echo '<input type="hidden" name="' . $capability->name . '" value="' . $perm . '" />';
            echo $this->strperms[$permname] . '<span class="note">' . $this->stradvmessage . '</span>';
        }
        echo '</td>';
    }
}
class view_role_definition_table extends define_role_table_advanced {
    public function __construct($context, $roleid) {
        parent::__construct($context, $roleid);
        $this->displaypermissions = array(CAP_ALLOW => $this->allpermissions[CAP_ALLOW]);
        $this->disabled = 'disabled="disabled" ';
    }

    public function save_changes() {
        throw new moodle_exception('invalidaccess');
    }

    protected function get_name_field($id) {
        return role_get_name($this->role);
    }

    protected function get_shortname_field($id) {
        return $this->role->shortname;
    }

    protected function get_description_field($id) {
        return role_get_description($this->role);
    }

    protected function get_archetype_field($id) {
        if (empty($this->role->archetype)) {
            return get_string('none');
        } else {
            return get_string('archetype'.$this->role->archetype, 'role');
        }
    }

    protected function print_show_hide_advanced_button() {
        // Do nothing.
    }

    /**
     * Returns HTML risk icons.
     *
     * @return string
     */
    protected function get_role_risks_info() {
        global $OUTPUT;

        if (empty($this->roleid)) {
            return '';
        }

        $risks = array();
        $allrisks = get_all_risks();
        foreach ($this->capabilities as $capability) {
            $perm = $this->permissions[$capability->name];
            if ($perm != CAP_ALLOW) {
                continue;
            }
            foreach ($allrisks as $type=>$risk) {
                if ($risk & (int)$capability->riskbitmask) {
                    $risks[$type] = $risk;
                }
            }
        }

        $risksurl = new moodle_url(get_docs_url(s(get_string('risks', 'role'))));
        foreach ($risks as $type=>$risk) {
            $pixicon = new pix_icon('/i/' . str_replace('risk', 'risk_', $type), get_string($type . 'short', 'admin'));
            $risks[$type] = $OUTPUT->action_icon($risksurl, $pixicon, new popup_action('click', $risksurl));
        }

        return implode(' ', $risks);
    }

    /**
     * Returns true if the row should be skipped.
     *
     * @param string $capability
     * @return bool
     */
    protected function skip_row($capability) {
        $perm = $this->permissions[$capability->name];
        if ($perm == CAP_INHERIT) {
            // Do not print empty rows in role overview, admins need to know quickly what is allowed and prohibited,
            // if they want to see the list of all capabilities they can go to edit role page.
            return true;
        }
        parent::skip_row($capability);
    }

    protected function add_permission_cells($capability) {
        $perm = $this->permissions[$capability->name];
        $permname = $this->allpermissions[$perm];
        $defaultperm = $this->allpermissions[$this->parentpermissions[$capability->name]];
        if ($permname != $defaultperm) {
            $default = get_string('defaultx', 'role', $this->strperms[$defaultperm]);
        } else {
            $default = "&#xa0;";
        }
        echo '<td class="' . $permname . '">' . $this->strperms[$permname] . '<span class="note">' .
                $default . '</span></td>';

    }
}

class override_permissions_table_advanced extends capability_table_with_risks {
    protected $strnotset;
    protected $haslockedcapabilities = false;

    /**
     * Constructor
     *
     * This method loads loads all the information about the current state of
     * the overrides, then updates that based on any submitted data. It also
     * works out which capabilities should be locked for this user.
     *
     * @param object $context the context this table relates to.
     * @param integer $roleid the role being overridden.
     * @param boolean $safeoverridesonly If true, the user is only allowed to override
     *      capabilities with no risks.
     */
    public function __construct($context, $roleid, $safeoverridesonly) {
        parent::__construct($context, 'overriderolestable', $roleid);
        $this->displaypermissions = $this->allpermissions;
        $this->strnotset = get_string('notset', 'role');

    /// Determine which capabilities should be locked.
        if ($safeoverridesonly) {
            foreach ($this->capabilities as $capid => $cap) {
                if (!is_safe_capability($cap)) {
                    $this->capabilities[$capid]->locked = true;
                    $this->haslockedcapabilities = true;
                }
            }
        }
    }

    protected function load_parent_permissions() {
        global $DB;

    /// Get the capabilities from the parent context, so that can be shown in the interface.
        $parentcontext = context::instance_by_id(get_parent_contextid($this->context));
        $this->parentpermissions = role_context_capabilities($this->roleid, $parentcontext);
    }

    public function has_locked_capabilities() {
        return $this->haslockedcapabilities;
    }

    protected function add_permission_cells($capability) {
        $disabled = '';
        if ($capability->locked || $this->parentpermissions[$capability->name] == CAP_PROHIBIT) {
            $disabled = ' disabled="disabled"';
        }

    /// One cell for each possible permission.
        foreach ($this->displaypermissions as $perm => $permname) {
            $strperm = $this->strperms[$permname];
            $extraclass = '';
            if ($perm != CAP_INHERIT && $perm == $this->parentpermissions[$capability->name]) {
                $extraclass = ' capcurrent';
            }
            $checked = '';
            if ($this->permissions[$capability->name] == $perm) {
                $checked = 'checked="checked" ';
            }
            echo '<td class="' . $permname . $extraclass . '">';
            echo '<label><input type="radio" name="' . $capability->name .
                    '" value="' . $perm . '" ' . $checked . $disabled . '/> ';
            if ($perm == CAP_INHERIT) {
                $inherited = $this->parentpermissions[$capability->name];
                if ($inherited == CAP_INHERIT) {
                    $inherited = $this->strnotset;
                } else {
                    $inherited = $this->strperms[$this->allpermissions[$inherited]];
                }
                $strperm .= ' (' . $inherited . ')';
            }
            echo '<span class="note">' . $strperm . '</span>';
            echo '</label></td>';
        }
    }
}

// User selectors for managing role assignments ================================

/**
 * Base class to avoid duplicating code.
 */
abstract class role_assign_user_selector_base extends user_selector_base {
    protected $roleid;
    protected $context;

    /**
     * @param string $name control name
     * @param array $options should have two elements with keys groupid and courseid.
     */
    public function __construct($name, $options) {
        global $CFG;
        if (isset($options['context'])) {
            $this->context = $options['context'];
        } else {
            $this->context = context::instance_by_id($options['contextid']);
        }
        $options['accesscontext'] = $this->context;
        parent::__construct($name, $options);
        $this->roleid = $options['roleid'];
        require_once($CFG->dirroot . '/group/lib.php');
    }

    protected function get_options() {
        global $CFG;
        $options = parent::get_options();
        $options['file'] = $CFG->admin . '/roles/lib.php';
        $options['roleid'] = $this->roleid;
        $options['contextid'] = $this->context->id;
        return $options;
    }
}

/**
 * User selector subclass for the list of potential users on the assign roles page,
 * when we are assigning in a context below the course level. (CONTEXT_MODULE and
 * some CONTEXT_BLOCK).
 *
 * This returns only enrolled users in this context.
 */
class potential_assignees_below_course extends role_assign_user_selector_base {
    public function find_users($search) {
        global $DB;

        list($enrolsql, $eparams) = get_enrolled_sql($this->context);

        // Now we have to go to the database.
        list($wherecondition, $params) = $this->search_sql($search, 'u');
        $params = array_merge($params, $eparams);

        if ($wherecondition) {
            $wherecondition = ' AND ' . $wherecondition;
        }

        $fields      = 'SELECT ' . $this->required_fields_sql('u');
        $countfields = 'SELECT COUNT(u.id)';

        $sql   = " FROM {user} u
              LEFT JOIN {role_assignments} ra ON (ra.userid = u.id AND ra.roleid = :roleid AND ra.contextid = :contextid)
                  WHERE u.id IN ($enrolsql)
                        $wherecondition
                        AND ra.id IS NULL";
        $params['contextid'] = $this->context->id;
        $params['roleid'] = $this->roleid;

        list($sort, $sortparams) = users_order_by_sql('u', $search, $this->accesscontext);
        $order = ' ORDER BY ' . $sort;

        // Check to see if there are too many to show sensibly.
        if (!$this->is_validating()) {
            $potentialmemberscount = $DB->count_records_sql($countfields . $sql, $params);
            if ($potentialmemberscount > $this->maxusersperpage) {
                return $this->too_many_results($search, $potentialmemberscount);
            }
        }

        // If not, show them.
        $availableusers = $DB->get_records_sql($fields . $sql . $order, array_merge($params, $sortparams));

        if (empty($availableusers)) {
            return array();
        }

        if ($search) {
            $groupname = get_string('potusersmatching', 'role', $search);
        } else {
            $groupname = get_string('potusers', 'role');
        }

        return array($groupname => $availableusers);
    }
}

/**
 * User selector subclass for the selection of users in the check permissions page.
 *
 * @copyright 2012 Petr Skoda {@link http://skodak.org}
 */
class role_check_users_selector extends user_selector_base {
    /** @var bool limit listing of users to enrolled only */
    var $onlyenrolled;

    /**
     * Constructor.
     *
     * @param string $name the control name/id for use in the HTML.
     * @param array $options other options needed to construct this selector.
     * You must be able to clone a userselector by doing new get_class($us)($us->get_name(), $us->get_options());
     */
    public function __construct($name, $options) {
        if (!isset($options['multiselect'])) {
            $options['multiselect'] = false;
        }
        parent::__construct($name, $options);

        $coursecontext = $this->accesscontext->get_course_context(false);
        if ($coursecontext and $coursecontext->id != SITEID and !has_capability('moodle/role:manage', $coursecontext)) {
            // Prevent normal teachers from looking up all users.
            $this->onlyenrolled = true;
        } else {
            $this->onlyenrolled = false;
        }
    }

    public function find_users($search) {
        global $DB;

        list($wherecondition, $params) = $this->search_sql($search, 'u');

        $fields      = 'SELECT ' . $this->required_fields_sql('u');
        $countfields = 'SELECT COUNT(1)';

        $coursecontext = $this->accesscontext->get_course_context(false);

        if ($coursecontext and $coursecontext != SITEID) {
            $sql1 = " FROM {user} u
                      JOIN {user_enrolments} ue ON (ue.userid = u.id)
                      JOIN {enrol} e ON (e.id = ue.enrolid AND e.courseid = :courseid1)
                     WHERE $wherecondition";
            $params['courseid1'] = $coursecontext->instanceid;

            if ($this->onlyenrolled) {
                $sql2 = null;
            } else {
                $sql2 = " FROM {user} u
                     LEFT JOIN ({user_enrolments} ue
                                JOIN {enrol} e ON (e.id = ue.enrolid AND e.courseid = :courseid2)) ON (ue.userid = u.id)
                         WHERE $wherecondition
                               AND ue.id IS NULL";
                $params['courseid2'] = $coursecontext->instanceid;
            }

        } else {
            if ($this->onlyenrolled) {
                // Bad luck, current user may not view only enrolled users.
                return array();
            }
            $sql1 = null;
            $sql2 = " FROM {user} u
                     WHERE $wherecondition";
        }

        $params['contextid'] = $this->accesscontext->id;

        list($sort, $sortparams) = users_order_by_sql('u', $search, $this->accesscontext);
        $order = ' ORDER BY ' . $sort;

        $result = array();

        if ($search) {
            $groupname1 = get_string('enrolledusersmatching', 'enrol', $search);
            $groupname2 = get_string('potusersmatching', 'role', $search);
        } else {
            $groupname1 = get_string('enrolledusers', 'enrol');
            $groupname2 = get_string('potusers', 'role');
        }

        if ($sql1) {
            $enrolleduserscount = $DB->count_records_sql($countfields . $sql1, $params);
            if (!$this->is_validating() and $enrolleduserscount > $this->maxusersperpage) {
                $result[$groupname1] = array();
                $toomany = $this->too_many_results($search, $enrolleduserscount);
                $result[implode(' - ', array_keys($toomany))] = array();

            } else {
                $enrolledusers = $DB->get_records_sql($fields . $sql1 . $order, array_merge($params, $sortparams));
                if ($enrolledusers) {
                    $result[$groupname1] = $enrolledusers;
                }
            }
            if ($sql2) {
                $result[''] = array();
            }
        }
        if ($sql2) {
            $otheruserscount = $DB->count_records_sql($countfields . $sql2, $params);
            if (!$this->is_validating() and $otheruserscount > $this->maxusersperpage) {
                $result[$groupname2] = array();
                $toomany = $this->too_many_results($search, $otheruserscount);
                $result[implode(' - ', array_keys($toomany))] = array();
            } else {
                $otherusers = $DB->get_records_sql($fields . $sql2 . $order, array_merge($params, $sortparams));
                if ($otherusers) {
                    $result[$groupname2] = $otherusers;
                }
            }
        }

        return $result;
    }

    protected function get_options() {
        global $CFG;
        $options = parent::get_options();
        $options['file'] = $CFG->admin . '/roles/lib.php';
        return $options;
    }
}

/**
 * User selector subclass for the list of potential users on the assign roles page,
 * when we are assigning in a context at or above the course level. In this case we
 * show all the users in the system who do not already have the role.
 */
class potential_assignees_course_and_above extends role_assign_user_selector_base {
    public function find_users($search) {
        global $DB;

        list($wherecondition, $params) = $this->search_sql($search, '');

        $fields      = 'SELECT ' . $this->required_fields_sql('');
        $countfields = 'SELECT COUNT(1)';

        $sql = " FROM {user}
                WHERE $wherecondition
                      AND id NOT IN (
                         SELECT r.userid
                           FROM {role_assignments} r
                          WHERE r.contextid = :contextid
                                AND r.roleid = :roleid)";

        list($sort, $sortparams) = users_order_by_sql('', $search, $this->accesscontext);
        $order = ' ORDER BY ' . $sort;

        $params['contextid'] = $this->context->id;
        $params['roleid'] = $this->roleid;

        if (!$this->is_validating()) {
            $potentialmemberscount = $DB->count_records_sql($countfields . $sql, $params);
            if ($potentialmemberscount > $this->maxusersperpage) {
                return $this->too_many_results($search, $potentialmemberscount);
            }
        }

        $availableusers = $DB->get_records_sql($fields . $sql . $order, array_merge($params, $sortparams));

        if (empty($availableusers)) {
            return array();
        }

        if ($search) {
            $groupname = get_string('potusersmatching', 'role', $search);
        } else {
            $groupname = get_string('potusers', 'role');
        }

        return array($groupname => $availableusers);
    }
}

/**
 * User selector subclass for the list of users who already have the role in
 * question on the assign roles page.
 */
class existing_role_holders extends role_assign_user_selector_base {

    public function __construct($name, $options) {
        parent::__construct($name, $options);
    }

    public function find_users($search) {
        global $DB;

        list($wherecondition, $params) = $this->search_sql($search, 'u');
        list($ctxcondition, $ctxparams) = $DB->get_in_or_equal(get_parent_contexts($this->context, true), SQL_PARAMS_NAMED, 'ctx');
        $params = array_merge($params, $ctxparams);
        $params['roleid'] = $this->roleid;

        list($sort, $sortparams) = users_order_by_sql('u', $search, $this->accesscontext);
        $params = array_merge($params, $sortparams);

        $sql = "SELECT ra.id as raid," . $this->required_fields_sql('u') . ",ra.contextid,ra.component
                FROM {role_assignments} ra
                JOIN {user} u ON u.id = ra.userid
                JOIN {context} ctx ON ra.contextid = ctx.id
                WHERE
                    $wherecondition AND
                    ctx.id $ctxcondition AND
                    ra.roleid = :roleid
                ORDER BY ctx.depth DESC, ra.component, $sort";
        $contextusers = $DB->get_records_sql($sql, $params);

        // No users at all.
        if (empty($contextusers)) {
            return array();
        }

        // We have users. Out put them in groups by context depth.
        // To help the loop below, tack a dummy user on the end of the results
        // array, to trigger output of the last group.
        $dummyuser = new stdClass;
        $dummyuser->contextid = 0;
        $dummyuser->id = 0;
        $dummyuser->component = '';
        $contextusers[] = $dummyuser;
        $results = array(); // The results array we are building up.
        $doneusers = array(); // Ensures we only list each user at most once.
        $currentcontextid = $this->context->id;
        $currentgroup = array();
        foreach ($contextusers as $user) {
            if (isset($doneusers[$user->id])) {
                continue;
            }
            $doneusers[$user->id] = 1;
            if ($user->contextid != $currentcontextid) {
                // We have got to the end of the previous group. Add it to the results array.
                if ($currentcontextid == $this->context->id) {
                    $groupname = $this->this_con_group_name($search, count($currentgroup));
                } else {
                    $groupname = $this->parent_con_group_name($search, $currentcontextid);
                }
                $results[$groupname] = $currentgroup;
                // Get ready for the next group.
                $currentcontextid = $user->contextid;
                $currentgroup = array();
            }
            // Add this user to the group we are building up.
            unset($user->contextid);
            if ($currentcontextid != $this->context->id) {
                $user->disabled = true;
            }
            if ($user->component !== '') {
                // bad luck, you can tweak only manual role assignments
                $user->disabled = true;
            }
            unset($user->component);
            $currentgroup[$user->id] = $user;
        }

        return $results;
    }

    protected function this_con_group_name($search, $numusers) {
        if ($this->context->contextlevel == CONTEXT_SYSTEM) {
            // Special case in the System context.
            if ($search) {
                return get_string('extusersmatching', 'role', $search);
            } else {
                return get_string('extusers', 'role');
            }
        }
        $contexttype = get_contextlevel_name($this->context->contextlevel);
        if ($search) {
            $a = new stdClass;
            $a->search = $search;
            $a->contexttype = $contexttype;
            if ($numusers) {
                return get_string('usersinthisxmatching', 'role', $a);
            } else {
                return get_string('noneinthisxmatching', 'role', $a);
            }
        } else {
            if ($numusers) {
                return get_string('usersinthisx', 'role', $contexttype);
            } else {
                return get_string('noneinthisx', 'role', $contexttype);
            }
        }
    }

    protected function parent_con_group_name($search, $contextid) {
        $context = context::instance_by_id($contextid);
        $contextname = print_context_name($context, true, true);
        if ($search) {
            $a = new stdClass;
            $a->contextname = $contextname;
            $a->search = $search;
            return get_string('usersfrommatching', 'role', $a);
        } else {
            return get_string('usersfrom', 'role', $contextname);
        }
    }
}

/**
 * Base class for managing the data in the grid of checkboxes on the role allow
 * allow/overrides/switch editing pages (allow.php).
 */
abstract class role_allow_role_page {
    protected $tablename;
    protected $targetcolname;
    protected $roles;
    protected $allowed = null;

    /**
     * @param string $tablename the table where our data is stored.
     * @param string $targetcolname the name of the target role id column.
     */
    public function __construct($tablename, $targetcolname) {
        $this->tablename = $tablename;
        $this->targetcolname = $targetcolname;
        $this->load_required_roles();
    }

    /**
     * Load information about all the roles we will need information about.
     */
    protected function load_required_roles() {
    /// Get all roles
        $this->roles = role_fix_names(get_all_roles(), context_system::instance(), ROLENAME_ORIGINAL);
    }

    /**
     * Update the data with the new settings submitted by the user.
     */
    public function process_submission() {
        global $DB;
    /// Delete all records, then add back the ones that should be allowed.
        $DB->delete_records($this->tablename);
        foreach ($this->roles as $fromroleid => $notused) {
            foreach ($this->roles as $targetroleid => $alsonotused) {
                if (optional_param('s_' . $fromroleid . '_' . $targetroleid, false, PARAM_BOOL)) {
                    $this->set_allow($fromroleid, $targetroleid);
                }
            }
        }
    }

    /**
     * Set one allow in the database.
     * @param integer $fromroleid
     * @param integer $targetroleid
     */
    protected abstract function set_allow($fromroleid, $targetroleid);

    /**
     * Load the current allows from the database.
     */
    public function load_current_settings() {
        global $DB;
    /// Load the current settings
        $this->allowed = array();
        foreach ($this->roles as $role) {
            // Make an array $role->id => false. This is probably too clever for its own good.
            $this->allowed[$role->id] = array_combine(array_keys($this->roles), array_fill(0, count($this->roles), false));
        }
        $rs = $DB->get_recordset($this->tablename);
        foreach ($rs as $allow) {
            $this->allowed[$allow->roleid][$allow->{$this->targetcolname}] = true;
        }
        $rs->close();
    }

    /**
     * @param integer $targetroleid a role id.
     * @return boolean whether the user should be allowed to select this role as a
     * target role.
     */
    protected function is_allowed_target($targetroleid) {
        return true;
    }

    /**
     * @return object a $table structure that can be passed to print_table, containing
     * one cell for each checkbox.
     */
    public function get_table() {
        $table = new html_table();
        $table->tablealign = 'center';
        $table->cellpadding = 5;
        $table->cellspacing = 0;
        $table->width = '90%';
        $table->align = array('left');
        $table->rotateheaders = true;
        $table->head = array('&#xa0;');
        $table->colclasses = array('');

    /// Add role name headers.
        foreach ($this->roles as $targetrole) {
            $table->head[] = $targetrole->localname;
            $table->align[] = 'left';
            if ($this->is_allowed_target($targetrole->id)) {
                $table->colclasses[] = '';
            } else {
                $table->colclasses[] = 'dimmed_text';
            }
        }

    /// Now the rest of the table.
        foreach ($this->roles as $fromrole) {
            $row = array($fromrole->localname);
            foreach ($this->roles as $targetrole) {
                $checked = '';
                $disabled = '';
                if ($this->allowed[$fromrole->id][$targetrole->id]) {
                    $checked = 'checked="checked" ';
                }
                if (!$this->is_allowed_target($targetrole->id)) {
                    $disabled = 'disabled="disabled" ';
                }
                $name = 's_' . $fromrole->id . '_' . $targetrole->id;
                $tooltip = $this->get_cell_tooltip($fromrole, $targetrole);
                $row[] = '<input type="checkbox" name="' . $name . '" id="' . $name .
                        '" title="' . $tooltip . '" value="1" ' . $checked . $disabled . '/>' .
                        '<label for="' . $name . '" class="accesshide">' . $tooltip . '</label>';
            }
            $table->data[] = $row;
        }

        return $table;
    }

    /**
     * Snippet of text displayed above the table, telling the admin what to do.
     * @return unknown_type
     */
    public abstract function get_intro_text();
}

/**
 * Subclass of role_allow_role_page for the Allow assigns tab.
 */
class role_allow_assign_page extends role_allow_role_page {
    public function __construct() {
        parent::__construct('role_allow_assign', 'allowassign');
    }

    protected function set_allow($fromroleid, $targetroleid) {
        allow_assign($fromroleid, $targetroleid);
    }

    protected function get_cell_tooltip($fromrole, $targetrole) {
        $a = new stdClass;
        $a->fromrole = $fromrole->localname;
        $a->targetrole = $targetrole->localname;
        return get_string('allowroletoassign', 'role', $a);
    }

    public function get_intro_text() {
        return get_string('configallowassign', 'admin');
    }
}

/**
 * Subclass of role_allow_role_page for the Allow overrides tab.
 */
class role_allow_override_page extends role_allow_role_page {
    public function __construct() {
        parent::__construct('role_allow_override', 'allowoverride');
    }

    protected function set_allow($fromroleid, $targetroleid) {
        allow_override($fromroleid, $targetroleid);
    }

    protected function get_cell_tooltip($fromrole, $targetrole) {
        $a = new stdClass;
        $a->fromrole = $fromrole->localname;
        $a->targetrole = $targetrole->localname;
        return get_string('allowroletooverride', 'role', $a);
    }

    public function get_intro_text() {
        return get_string('configallowoverride2', 'admin');
    }
}

/**
 * Subclass of role_allow_role_page for the Allow switches tab.
 */
class role_allow_switch_page extends role_allow_role_page {
    protected $allowedtargetroles;

    public function __construct() {
        parent::__construct('role_allow_switch', 'allowswitch');
    }

    protected function load_required_roles() {
        global $DB;
        parent::load_required_roles();
        $this->allowedtargetroles = $DB->get_records_menu('role', NULL, 'id');
    }

    protected function set_allow($fromroleid, $targetroleid) {
        allow_switch($fromroleid, $targetroleid);
    }

    protected function is_allowed_target($targetroleid) {
        return isset($this->allowedtargetroles[$targetroleid]);
    }

    protected function get_cell_tooltip($fromrole, $targetrole) {
        $a = new stdClass;
        $a->fromrole = $fromrole->localname;
        $a->targetrole = $targetrole->localname;
        return get_string('allowroletoswitch', 'role', $a);
    }

    public function get_intro_text() {
        return get_string('configallowswitch', 'admin');
    }
}

/**
 * Get the potential assignees selector for a given context.
 *
 * If this context is a course context, or inside a course context (module or
 * some blocks) then return a potential_assignees_below_course object. Otherwise
 * return a potential_assignees_course_and_above.
 *
 * @param stdClass $context a context.
 * @param string $name passed to user selector constructor.
 * @param array $options to user selector constructor.
 * @return user_selector_base an appropriate user selector.
 */
function roles_get_potential_user_selector($context, $name, $options) {
        $blockinsidecourse = false;
        if ($context->contextlevel == CONTEXT_BLOCK) {
            $parentcontext = context::instance_by_id(get_parent_contextid($context));
            $blockinsidecourse = in_array($parentcontext->contextlevel, array(CONTEXT_MODULE, CONTEXT_COURSE));
        }

        if (($context->contextlevel == CONTEXT_MODULE || $blockinsidecourse) &&
                !is_inside_frontpage($context)) {
            $potentialuserselector = new potential_assignees_below_course('addselect', $options);
        } else {
            $potentialuserselector = new potential_assignees_course_and_above('addselect', $options);
        }
    return $potentialuserselector;
}

class admins_potential_selector extends user_selector_base {
    /**
     * @param string $name control name
     * @param array $options should have two elements with keys groupid and courseid.
     */
    public function __construct($name = null, $options = array()) {
        global $CFG;
        if (is_null($name)) {
            $name = 'addselect';
        }
        $options['multiselect'] = false;
        $options['exclude'] = explode(',', $CFG->siteadmins);
        parent::__construct($name, $options);
    }

    public function find_users($search) {
        global $CFG, $DB;
        list($wherecondition, $params) = $this->search_sql($search, '');

        $fields      = 'SELECT ' . $this->required_fields_sql('');
        $countfields = 'SELECT COUNT(1)';

        $sql = " FROM {user}
                WHERE $wherecondition AND mnethostid = :localmnet";

        $params['localmnet'] = $CFG->mnet_localhost_id; // it could be dangerous to make remote users admins and also this could lead to other problems

        list($sort, $sortparams) = users_order_by_sql('', $search, $this->accesscontext);
        $order = ' ORDER BY ' . $sort;

        // Check to see if there are too many to show sensibly.
        if (!$this->is_validating()) {
            $potentialcount = $DB->count_records_sql($countfields . $sql, $params);
            if ($potentialcount > $this->maxusersperpage) {
                return $this->too_many_results($search, $potentialcount);
            }
        }

        $availableusers = $DB->get_records_sql($fields . $sql . $order, array_merge($params, $sortparams));

        if (empty($availableusers)) {
            return array();
        }

        if ($search) {
            $groupname = get_string('potusersmatching', 'role', $search);
        } else {
            $groupname = get_string('potusers', 'role');
        }

        return array($groupname => $availableusers);
    }

    protected function get_options() {
        global $CFG;
        $options = parent::get_options();
        $options['file'] = $CFG->admin . '/roles/lib.php';
        return $options;
    }
}

class admins_existing_selector extends user_selector_base {
    /**
     * @param string $name control name
     * @param array $options should have two elements with keys groupid and courseid.
     */
    public function __construct($name = null, $options = array()) {
        if (is_null($name)) {
            $name = 'removeselect';
        }
        $options['multiselect'] = false;
        parent::__construct($name, $options);
    }

    public function find_users($search) {
        global $DB, $CFG;
        list($wherecondition, $params) = $this->search_sql($search, '');

        $fields      = 'SELECT ' . $this->required_fields_sql('');
        $countfields = 'SELECT COUNT(1)';

        if ($wherecondition) {
            $wherecondition = "$wherecondition AND id IN ($CFG->siteadmins)";
        } else {
            $wherecondition = "id IN ($CFG->siteadmins)";
        }
        $sql = " FROM {user}
                WHERE $wherecondition";

        list($sort, $sortparams) = users_order_by_sql('', $search, $this->accesscontext);
        $params = array_merge($params, $sortparams);
        $order = ' ORDER BY ' . $sort;

        $availableusers = $DB->get_records_sql($fields . $sql . $order, $params);

        if (empty($availableusers)) {
            return array();
        }

        $mainadmin = array();
        $mainadminuser = get_admin();
        if ($mainadminuser && isset($availableusers[$mainadminuser->id])) {
            $mainadmin = array($mainadminuser->id => $availableusers[$mainadminuser->id]);
            unset($availableusers[$mainadminuser->id]);
        }

        $result = array();
        if ($mainadmin) {
            $result[get_string('mainadmin', 'role')] = $mainadmin;
        }

        if ($availableusers) {
            if ($search) {
                $groupname = get_string('extusersmatching', 'role', $search);
            } else {
                $groupname = get_string('extusers', 'role');
            }
            $result[$groupname] = $availableusers;
        }

        return $result;
    }

    protected function get_options() {
        global $CFG;
        $options = parent::get_options();
        $options['file'] = $CFG->admin . '/roles/lib.php';
        return $options;
    }
}
