HEX
Server: LiteSpeed
System: Linux server.zepintelhosting.com 4.18.0 #1 SMP Mon Sep 30 15:36:27 MSK 2024 x86_64
User: enamadmin (1026)
PHP: 8.2.30
Disabled: exec,system,passthru,shell_exec,proc_open,popen,apache_child_terminate
Upload Files
File: /home/enamadmin/public_html/aaelearningb/enrol/attributes/lib.php
<?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/>.

/**
 * @package    enrol_attributes
 * @author     Nicolas Dunand <Nicolas.Dunand@unil.ch>
 * @copyright  2012-2018 Université de Lausanne {@link http://www.unil.ch}
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 */

defined('MOODLE_INTERNAL') || die();
require_once $CFG->dirroot . '/enrol/attributes/locallib.php';
require_once $CFG->dirroot.'/group/lib.php';

/**
 * Database enrolment plugin implementation.
 *
 * @author  Petr Skoda - based on code by Martin Dougiamas, Martin Langhoff and others
 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 */
class enrol_attributes_plugin extends enrol_plugin {

    /**
     * Handle profile field updates
     * @param \core\event\base $event
     * @return void
     */
    public static function handle_profile_update(\core\event\base $event) {
        global $DB;
        
        // Get the affected user's ID
        $userid = $event->relateduserid ?? $event->userid;
        if (!$userid) {
            return; // No user to process
        }

        // Invalidate cache to ensure fresh data
        $cache = cache::make('enrol_attributes', 'dbquerycache');
        $cache->purge();
        
        // Get all active attribute enrolment instances
        $enrol_instances = $DB->get_records('enrol', array(
            'enrol' => 'attributes',
            'status' => 0
        ));
        
        foreach ($enrol_instances as $instance) {
            // Check if the user is enrolled in this course
            $user_enrolment = $DB->get_record('user_enrolments', array(
                'enrolid' => $instance->id,
                'userid' => $userid,
                'status' => ENROL_USER_ACTIVE
            ));
            
            if (!$user_enrolment) {
                continue; // User not enrolled or already suspended/unenrolled
            }
            
            // Check if the user still meets the conditions
            $arraysyntax = self::attrsyntax_toarray($instance->customtext1);
            $arraysql = self::arraysyntax_tosql($arraysyntax);
            
            $select = 'SELECT DISTINCT u.id FROM {user} u';
            $where = ' WHERE u.id = ? AND u.deleted = 0 AND ';
            
            $sql = $select . $arraysql['select'] . $where . $arraysql['where'];
            $params = array_merge(array($userid), $arraysql['params']);
            
            $still_valid = $DB->record_exists_sql($sql, $params);
            
            if (!$still_valid) {
                // The user no longer meets the conditions
                $enrol_plugin = new self();
                
                switch ($instance->customint1) {
                    case ENROL_ATTRIBUTES_WHENEXPIREDREMOVE:
                        // Unenrol the user and log the event
                        $enrol_plugin->unenrol_user($instance, $userid);
                        
                        // Trigger event
                        $context = context_course::instance($instance->courseid);
                        $event = \core\event\user_enrolment_deleted::create(array(
                            'objectid' => $user_enrolment->id,
                            'courseid' => $instance->courseid,
                            'context' => $context,
                            'relateduserid' => $userid,
                            'other' => array('enrol' => 'attributes')
                        ));
                        $event->trigger();
                        break;
                        
                    case ENROL_ATTRIBUTES_WHENEXPIREDSUSPEND:
                        // Suspend the user
                        $enrol_plugin->update_user_enrol($instance, $userid, ENROL_USER_SUSPENDED);
                        
                        // Trigger event
                        $context = context_course::instance($instance->courseid);
                        $event = \core\event\user_enrolment_updated::create(array(
                            'objectid' => $user_enrolment->id,
                            'courseid' => $instance->courseid,
                            'context' => $context,
                            'relateduserid' => $userid,
                            'other' => array(
                                'enrol' => 'attributes',
                                'status' => ENROL_USER_SUSPENDED
                            )
                        ));
                        $event->trigger();
                        break;
                }
                
                // Remove user from groups if necessary
                if ($groups = $DB->get_records('groups', array('courseid' => $instance->courseid))) {
                    foreach ($groups as $group) {
                        groups_remove_member($group->id, $userid);
                    }
                }
            }
        }
    }

    /**
     * Process login event
     * @param \core\event\user_loggedin $event
     * @return true|void
     */
    public static function process_login(\core\event\user_loggedin $event) {
        global $CFG, $DB;
        // we just received the event from the authentication system; check if well-formed:
        if (!$event->userid) {
            // didn't get an user ID, return as there is nothing we can do
            return true;
        }
        if (in_array('shibboleth', get_enabled_auth_plugins())
            && $_SERVER['SCRIPT_FILENAME'] == $CFG->dirroot . '/auth/shibboleth/index.php') {
            // we did get this event from the Shibboleth authentication plugin,
            // so let's try to make the relevant mappings, ensuring that necessary profile fields exist and Shibboleth attributes are provided:
            $customfieldrecords = $DB->get_records('user_info_field');
            $customfields = array();
            foreach ($customfieldrecords as $customfieldrecord) {
                $customfields[] = $customfieldrecord->shortname;
            }
            $mapping = array();
            $mappings_str = explode("\n", str_replace("\r", '', get_config('enrol_attributes', 'mappings')));
            foreach ($mappings_str as $mapping_str) {
                if (preg_match('/^\s*([^: ]+)\s*:\s*([^: ]+)\s*$/', $mapping_str, $matches)
                    && array_key_exists($matches[1], $_SERVER)
                    && in_array($matches[2], $customfields)) {
                    $mapping[$matches[1]] = $matches[2];
                }
            }
            if (count($mapping)) {
                // now update user profile data from Shibboleth params received as part of the event:
                $user = $DB->get_record('user', ['id' => $event->userid], '*', MUST_EXIST);
                foreach ($mapping as $shibattr => $fieldname) {
                    if (isset($_SERVER[$shibattr])) {
                        $propertyname = 'profile_field_' . $fieldname;
                        $user->$propertyname = $_SERVER[$shibattr];
                    }
                }
                require_once($CFG->dirroot . '/user/profile/lib.php');
                profile_save_data($user);
            }
        }
        // last, process the actual enrolments, whether we're using Shibboleth authentication or not:
        self::process_enrolments($event);
    }

    /**
     * @param \core\event\user_loggedin|null $event
     * @param int|null $instanceid
     * @return int|void
     */
    public static function process_enrolments($event = null, $instanceid = null) {
        global $DB;
        $nbenrolled = 0;
        $nbdbqueries = 0;
        $nbcachequeries = 0;
        $nbpossunenrol = 0;
        $nbpossenrol = 0;
        $nbunenrolled = 0;
        $possible_unenrolments = array();

        $cache = cache::make('enrol_attributes', 'dbquerycache');

        if ($instanceid) {
            // We're processing one particular instance, making sure it's active
            $enrol_attributes_records = $DB->get_records('enrol', array(
                    'enrol'  => 'attributes',
                    'status' => 0,
                    'id'     => $instanceid
            ));
        }
        else {
            // We're processing all active instances,
            // because a user just logged in
            // OR we're running the scheduled task
            $enrol_attributes_records = $DB->get_records('enrol', array(
                    'enrol'  => 'attributes',
                    'status' => 0
            ));
            if (!is_null($event)) {
                // This is a login triggering
                if (!get_config('enrol_attributes', 'observelogins')) {
                    // Admin has decided not to process logins to save performance.
                    return;
                }
                // Let's check if there are any potential unenroling instances
                $userid = (int)$event->userid;
                $possible_unenrolments =
                        $DB->get_records_sql("SELECT id, enrolid, userid FROM {user_enrolments} WHERE userid = ? AND enrolid IN ( SELECT id FROM {enrol} WHERE enrol = 'attributes' AND customint1 > 0 ) ",
                                array($userid));
            } else {
                $possible_unenrolments =
                        $DB->get_records_sql("SELECT id, enrolid, userid FROM {user_enrolments} WHERE enrolid IN ( SELECT id FROM {enrol} WHERE enrol = 'attributes' AND customint1 > 0 ) ",
                                array());
            }
        }

        // are we to unenrol/suspend from anywhere?
        foreach ($possible_unenrolments as $id => $user_enrolment) {
            $nbpossunenrol++;
            // we only want output if runnning within the scheduled task
            if (!$event && !$instanceid && $nbpossunenrol % 1000 === 0 && isset($_SERVER['argv'][0]) && is_string($_SERVER['argv'][0]) && strpos($_SERVER['argv'][0], 'phpunit') === FALSE) {
                mtrace('-', '');
            }

            $unenrol_attributes_record = $DB->get_record('enrol', array(
                    'enrol'  => 'attributes',
                    'status' => 0,
                    'id'     => $user_enrolment->enrolid
            ));

            if (!$unenrol_attributes_record) {
                continue;
            }

            if ($unenrol_attributes_record->customint1 == ENROL_ATTRIBUTES_WHENEXPIREDDONOTHING) {
                continue;
            }

            $select = 'SELECT DISTINCT u.id FROM {user} u';
            $where = ' WHERE u.id=' . $user_enrolment->userid . ' AND u.deleted=0 AND ';
            $arraysyntax = self::attrsyntax_toarray($unenrol_attributes_record->customtext1);
            $arraysql = self::arraysyntax_tosql($arraysyntax);
            $dbquerycachekey = md5($select . serialize($arraysql) . $where);
            $users_cache = $cache->get($dbquerycachekey);
            if ($users_cache) {
                $users = unserialize($users_cache);
                $nbcachequeries++;
            }
            else {
                $users = $DB->get_records_sql($select . $arraysql['select'] . $where . $arraysql['where'],
                        $arraysql['params']);
                $nbdbqueries++;
                $cache->set($dbquerycachekey, serialize($users));
            }

            if (!array_key_exists($user_enrolment->userid, $users)) {
                // User is to be either unenrolled or suspended
                $enrol_attributes_instance = new enrol_attributes_plugin();
                if ($unenrol_attributes_record->customint1 == ENROL_ATTRIBUTES_WHENEXPIREDREMOVE) {
                    $enrol_attributes_instance->unenrol_user($unenrol_attributes_record, (int)$user_enrolment->userid);
                } elseif ($unenrol_attributes_record->customint1 == ENROL_ATTRIBUTES_WHENEXPIREDSUSPEND) {
                    $enrol_attributes_instance->update_user_enrol($unenrol_attributes_record, (int)$user_enrolment->userid,
                            ENROL_USER_SUSPENDED);
                }
                $nbunenrolled++;
            }
        }

        // are we to enrol anywhere?
        foreach ($enrol_attributes_records as $enrol_attributes_record) {
            $nbpossenrol++;
            if (!$event && !$instanceid && isset($_SERVER['argv'][0]) && is_string($_SERVER['argv'][0]) && strpos($_SERVER['argv'][0], 'phpunit') === FALSE) {
                // we only want output if runnning within the scheduled task
                mtrace('+', '');
            }

            $enroldetails = json_decode($enrol_attributes_record->customtext1 ?? '');
            if (isset($enroldetails->rules)) {
                $rules = $enroldetails->rules;
            } else {
                // skip this record, as it is malformed
                continue;
            }
            $configured_profilefields = explode(',', get_config('enrol_attributes', 'profilefields'));
            foreach ($rules as $rule) {
                if (!isset($rule->param) && !isset($rule->rules)) {
                    continue 2; // Rule malformed.
                }
                if (isset($rule->param) && !in_array($rule->param, $configured_profilefields)) {
                    continue 2; // Rule uses a param that's not allowed in the plugin settings.
                }
            }
            $enrol_attributes_instance = new enrol_attributes_plugin();
            $enrol_attributes_instance->name = $enrol_attributes_record->name;

            $select = 'SELECT DISTINCT u.id FROM {user} u';
            if ($event) { // called by an event, i.e. user login
                $userid = (int)$event->userid;
                $where = ' WHERE u.id=' . $userid;
            }
            else { // called by scheduled task or by construct
                $where = ' WHERE 1=1';
            }
            $where .= ' AND u.deleted=0 AND ';
            $arraysyntax = self::attrsyntax_toarray($enrol_attributes_record->customtext1);
            $arraysql = self::arraysyntax_tosql($arraysyntax);
            $dbquerycachekey = md5($select . serialize($arraysql) . $where);

            //TODO fix bug related to cache : users are not unenrolled
            $users_cache = $cache->get($dbquerycachekey);
            if ($users_cache) {
                $users = unserialize($users_cache);
                $nbcachequeries++;
            } else {
                $users = $DB->get_records_sql($select . $arraysql['select'] . $where . $arraysql['where'],
                        $arraysql['params']);
                $nbdbqueries++;
                $cache->set($dbquerycachekey, serialize($users));
            }
            foreach ($users ?? [] as $user) {
                $recovergrades = null;
                if (is_enrolled(context_course::instance($enrol_attributes_record->courseid), $user)) {
                    $recovergrades = false; // do not try to recover grades if user is already enrolled
                }
                $enrol_attributes_instance->enrol_user($enrol_attributes_record, $user->id,
                        $enrol_attributes_record->roleid, 0, 0, null, $recovergrades);
                $nbenrolled++;
                
                // Start modification
                $groups = json_decode($enrol_attributes_record->customtext1, true)['groups'] ?? [];
                foreach ($groups as $groupid) {
                    groups_add_member($groupid, $user->id);
                }
                // End modification
            }
        }

        if (!$event && !$instanceid && isset($_SERVER['argv'][0]) && is_string($_SERVER['argv'][0]) && strpos($_SERVER['argv'][0], 'phpunit') === FALSE) {
            // we only want output if runnning within the scheduled task
            mtrace("\n" . 'enrol_attributes : ' . $nbdbqueries . ' DB queries.');
            mtrace('enrol_attributes : ' . $nbcachequeries . ' cache queries.');
            mtrace('enrol_attributes : ' . $nbpossenrol . ' enrolment instances.');
            mtrace('enrol_attributes : ' . $nbpossunenrol . ' possible unenrolments.');
            mtrace('enrol_attributes : ' . $nbunenrolled . ' unenrolments.');
            mtrace('enrol_attributes : enrolled ' . $nbenrolled . ' users.');
        }

        return $nbenrolled;
    }

    /**
     * @param stdClass $instance
     * @param int      $groupid
     * @param int      $userid
     *
     * @throws coding_exception
     */
    public function restore_group_member($instance, $groupid, $userid) {
        global $CFG;
        require_once("$CFG->dirroot/group/lib.php");
        groups_add_member($groupid, $userid);
    }

    public static function attrsyntax_toarray($attrsyntax) { // TODO : protected
        global $DB;

        $attrsyntax_object = json_decode($attrsyntax);
        $rules = $attrsyntax_object->rules;

        $customuserfields = array();
        foreach ($DB->get_records('user_info_field') as $customfieldrecord) {
            $customuserfields[$customfieldrecord->id] = $customfieldrecord->shortname;
        }

        return array(
                'customuserfields' => $customuserfields,
                'rules'            => $rules
        );
    }

    public static function arraysyntax_tosql($arraysyntax, &$join_id = 0) {
        global $DB;
        $select = '';
        $where = '1=1';
        $params = array();
        $customuserfields = $arraysyntax['customuserfields'];
        foreach ($arraysyntax['rules'] as $rule) {
            if (isset($rule->cond_op)) {
                $where .= ' ' . strtoupper($rule->cond_op) . ' ';
            }
            else {
                $where .= ' AND ';
            }
            // first just check if we have a value 'ANY' to enroll all people :
            if (isset($rule->value) && $rule->value === 'ANY') {
                $where .= '1=1';
                continue;
            }
            if (isset($rule->rules)) {
                $sub_arraysyntax = array(
                        'customuserfields' => $customuserfields,
                        'rules'            => $rule->rules
                );
                $sub_sql = self::arraysyntax_tosql($sub_arraysyntax, $join_id);
                $select .= ' ' . $sub_sql['select'] . ' ';
                $where .= ' ( ' . $sub_sql['where'] . ' ) ';
                $params = array_merge($params, $sub_sql['params']);
            } elseif ($customkey = array_search($rule->param, $customuserfields, true)) {
                // custom user field actually exists
                $join_id++;
                $data = 'd' . $join_id . '.data';
                $select .= ' INNER JOIN {user_info_data} d' . $join_id . ' ON d' . $join_id . '.userid = u.id AND d' . $join_id . '.fieldid = ' . $customkey;

                if (isset($rule->comp_op) && $rule->comp_op === 'contains') {
                    $where .= ' (' . $DB->sql_like($DB->sql_compare_text($data), '?') . ')';
                    $params[] = '%' . $rule->value . '%';
                } else if (isset($rule->comp_op) && $rule->comp_op !== 'listitem') {
                        $where .= ' (' . $DB->sql_compare_text($data) . ' ' . strtoupper($rule->comp_op) . ' ' . $DB->sql_compare_text('?') . ')';
                        $params[] = $rule->value;
                } else {
                    $where .= ' (' . $DB->sql_compare_text($data) . ' = ' . $DB->sql_compare_text(
                            '?'
                        ) . ' OR ' . $DB->sql_like(
                            $DB->sql_compare_text($data),
                            '?'
                        ) . ' OR ' . $DB->sql_like(
                            $DB->sql_compare_text($data),
                            '?'
                        ) . ' OR ' . $DB->sql_like(
                            $DB->sql_compare_text($data),
                            '?')
                        . ')';

                    array_push(
                        $params,
                        $rule->value,
                        '%;' . $rule->value,
                        $rule->value . ';%',
                        '%;' . $rule->value . ';%'
                    );
                }
            }
        }
        $where = preg_replace('/^1=1 AND ?/', '', $where);
        $where = preg_replace('/^1=1 OR/', '', $where);
        $where = preg_replace('/^1=1/', '', $where);

        if($where === '') {
            // Must be FALSE in any database without causing syntax error
            $where = '1=0';
        } else {
            $where = " ( $where ) ";
        }

        return array(
                'select' => $select,
                'where'  => $where,
                'params' => $params
        );
    }

    public static function purge_instance($instanceid) {
        global $DB;
        $enrolplugininstance = new self();

        if($instanceid) {
            $enrol_attributes_record = $DB->get_record('enrol', ['id' => $instanceid]);
            $enrolment_records = $DB->get_records('user_enrolments', ['enrolid'  => $enrol_attributes_record->id]);
            foreach ($enrolment_records as $record) {
                $enrolplugininstance->unenrol_user($enrol_attributes_record, $record->userid);
            }

            return true;
        }

        return false;
    }

    /**
     * Is it possible to delete enrol instance via standard UI?
     *
     * @param object $instance
     *
     * @return bool
     */
    public function instance_deleteable($instance) {
        return true;
    }

    /**
     * Returns link to page which may be used to add new instance of enrolment plugin in course.
     *
     * @param int $courseid
     *
     * @return moodle_url page url
     */
    public function get_newinstance_link($courseid) {
        $context = context_course::instance($courseid);

        if (!has_capability('moodle/course:enrolconfig', $context) or !has_capability('enrol/attributes:config',
                        $context)) {
            return null;
        }
        $configured_profilefields = explode(',', get_config('enrol_attributes', 'profilefields'));
        if (!strlen(array_shift($configured_profilefields))) {
            // no profile fields are configured for this plugin
            return null;
        }

        // multiple instances supported - different roles with different password
        return new moodle_url('/enrol/attributes/edit.php', array('courseid' => $courseid));
    }

    /**
     * Restore instance and map settings.
     *
     * @param restore_enrolments_structure_step $step
     * @param stdClass                          $data
     * @param stdClass                          $course
     * @param int                               $oldid
     */
    public function restore_instance(restore_enrolments_structure_step $step, stdClass $data, $course, $oldid) {
        if ($step->get_task()->get_target() !== backup::TARGET_NEW_COURSE) {
            return false;
        }
        $instanceid = $this->add_instance($course, (array)$data);
        $step->set_mapping('enrol', $oldid, $instanceid);
    }

    /**
     * Is it possible to delete enrol instance via standard UI?
     *
     * @param object $instance
     *
     * @return bool
     */
    public function can_delete_instance($instance) {
        $context = context_course::instance($instance->courseid);

        return has_capability('enrol/attributes:config', $context);
    }

    /**
     * Is it possible to hide/show enrol instance via standard UI?
     *
     * @param stdClass $instance
     *
     * @return bool
     */
    public function can_hide_show_instance($instance) {
        $context = context_course::instance($instance->courseid);

        return has_capability('enrol/attributes:config', $context);
    }

    /**
     * Returns edit icons for the page with list of instances
     *
     * @param stdClass $instance
     *
     * @return array
     */
    public function get_action_icons(stdClass $instance) {
        global $OUTPUT;

        if ($instance->enrol !== 'attributes') {
            throw new coding_exception('invalid enrol instance!');
        }
        $context = context_course::instance($instance->courseid);

        $icons = array();

        if (has_capability('enrol/attributes:config', $context)) {
            $editlink = new moodle_url("/enrol/attributes/edit.php", array(
                    'courseid' => $instance->courseid,
                    'id'       => $instance->id
            ));
            $icons[] = $OUTPUT->action_icon($editlink,
                    new pix_icon('i/edit', get_string('edit'), 'core', array('class' => 'icon')));
        }

        return $icons;
    }

   /**
    * Does this plugin allow manual changes in user_enrolments table?
    *
    * All plugins allowing this must implement 'enrol/xxx:manage' capability
    *
    * @param stdClass $instance course enrol instance
    * @return bool - true means it is possible to change enrol period and status in user_enrolments table
    */
    public function allow_manage(stdClass $instance) {
        return true;
    }

    /**
     * Returns enrolment instance manage link.
     *
     * By defaults looks for manage.php file and tests for manage capability.
     *
     * @param navigation_node $instancesnode
     * @param stdClass $instance
     *
     * @return moodle_url;
     * @throws \coding_exception|\moodle_exception
     */
    public function add_course_navigation($instancesnode, stdClass $instance) {
        if ($instance->enrol !== 'attributes') {
            throw new coding_exception('Invalid enrol instance type!');
        }

        $context = context_course::instance($instance->courseid);
        if (has_capability('enrol/attributes:config', $context)) {
            $managelink = new moodle_url('/enrol/attributes/edit.php', array(
                    'courseid' => $instance->courseid,
                    'id'       => $instance->id
            ));
            $instancesnode->add($this->get_instance_name($instance), $managelink, navigation_node::TYPE_SETTING);
        }
    }
}