/** * Migrate a single Moodle user to the Program Management system. Will * only do this for users who have an idnumber set. * * @param object $mu Moodle user object * @return boolean Whether user was synchronized or not */ function pm_moodle_user_to_pm($mu) { global $CFG, $DB; require_once $CFG->dirroot . '/lib/moodlelib.php'; require_once elis::lib('data/customfield.class.php'); require_once elispm::lib('data/user.class.php'); require_once elispm::lib('data/usermoodle.class.php'); require_once elis::lib('data/data_filter.class.php'); require_once $CFG->dirroot . '/user/profile/lib.php'; require_once elis::lib('lib.php'); if (!isset($mu->id)) { return true; } // re-fetch, in case this is from a stale event $mu = $DB->get_record('user', array('id' => $mu->id)); if (user_not_fully_set_up($mu) || !$mu->confirmed) { // Prevent the sync if a bare-bones user record is being created by create_user_record // or Moodle user has not yet been confirmed. return true; } //not going to be concerned with city or password for now if (empty($mu->idnumber) && elis::$config->local_elisprogram->auto_assign_user_idnumber) { //make sure the current user's username does not match up with some other user's //idnumber (necessary since usernames and idnumbers aren't bound to one another) if (!$DB->record_exists('user', array('idnumber' => $mu->username))) { $mu->idnumber = $mu->username; $DB->update_record('user', $mu); } } // skip user if no ID number set if (empty($mu->idnumber)) { return true; } // track whether we're syncing an idnumber change over to the PM system $idnumber_updated = false; // track whether an associated Moodle user is linked to the current PM user $moodle_user_exists = false; // determine if the user is already noted as having been associated to a PM user // this will join to Moodle user and PM user table to ensure data correctness $filters = array(); $filters[] = new join_filter('muserid', 'user', 'id'); $filters[] = new join_filter('cuserid', user::TABLE, 'id'); $filters[] = new field_filter('muserid', $mu->id); if ($um = usermoodle::find($filters)) { if ($um->valid()) { $um = $um->current(); //signal that an associated user already exists $moodle_user_exists = true; // determine if the Moodle user idnumber was updated if ($um->idnumber != $mu->idnumber) { //signal that the idnumber was synced over $idnumber_updated = true; // update the PM user with the new idnumber $cmuser = new user(); $cmuser->id = $um->cuserid; $cmuser->idnumber = $mu->idnumber; $cmuser->save(); // update the association table with the new idnumber $um->idnumber = $mu->idnumber; $um->save(); } } } // find the linked PM user //filter for the basic condition on the Moodle user id $condition_filter = new field_filter('id', $mu->id); //filter for joining the association table $association_filter = new join_filter('muserid', 'user', 'id', $condition_filter); //outermost filter $filter = new join_filter('id', usermoodle::TABLE, 'cuserid', $association_filter); $cu = user::find($filter); if ($cu->valid()) { $cu = $cu->current(); } else { // if a user with the same username but different idnumber exists, // we can't sync over because it will violate PM user uniqueness // constraints $cu = user::find(new field_filter('username', $mu->username)); if ($cu->valid()) { return true; } // if no such PM user exists, create a new one $cu = new user(); $cu->transfercredits = 0; $cu->timecreated = time(); } // synchronize standard fields $cu->username = $mu->username; $cu->password = $mu->password; // only need to update the idnumber if it wasn't handled above if (!$idnumber_updated) { $cu->idnumber = $mu->idnumber; } $cu->firstname = $mu->firstname; $cu->lastname = $mu->lastname; $cu->email = $mu->email; $cu->address = $mu->address; $cu->city = $mu->city; $cu->country = $mu->country; if (!empty($mu->phone1)) { $cu->phone = $mu->phone1; } if (!empty($mu->phone2)) { $cu->phone2 = $mu->phone2; } if (!empty($mu->lang)) { $cu->language = $mu->lang; } $cu->timemodified = time(); // synchronize custom profile fields profile_load_data($mu); fix_moodle_profile_fields($mu); $fields = field::get_for_context_level(CONTEXT_ELIS_USER); $fields = $fields ? $fields : array(); require_once elis::plugin_file('elisfields_moodleprofile', 'custom_fields.php'); foreach ($fields as $field) { $field = new field($field); if (!moodle_profile_can_sync($field->shortname)) { continue; } if (isset($field->owners['moodle_profile']) && isset($mu->{"profile_field_{$field->shortname}"})) { // check if should sync user profile field settings if ($field->owners['moodle_profile']->exclude == pm_moodle_profile::sync_from_moodle) { sync_profile_field_settings_from_moodle($field); } $fieldname = "field_{$field->shortname}"; $cu->{$fieldname} = $mu->{"profile_field_{$field->shortname}"}; } } //specifically tell the user save not to use the local_elisprogram_usr_mdl for syncing //because the record hasn't been inserted yet (see below) try { $cu->save(false); } catch (Exception $ex) { if (in_cron()) { mtrace(get_string('record_not_created_reason', 'local_elisprogram', array('message' => $ex->getMessage() . " [{$mu->id}]"))); return false; } else { throw new Exception($ex->getMessage()); } } // if no user association record exists, create one if (!$moodle_user_exists) { $um = new usermoodle(); $um->cuserid = $cu->id; $um->muserid = $mu->id; $um->idnumber = $mu->idnumber; $um->save(); } return true; }
/** * Create a user-level ELIS field from an existing Moodle user profile field. * * @param int $mfieldid The ID of a Moodle user profile field. * @param field_category $category An ELIS field_category object to add the new field to. * @param boolean $syncdir Data Sync Direction. * Possible values: * false = no syncing * pm_moodle_profile::sync_from_moodle = sync from moodle. * pm_moodle_profile::sync_to_moodle = sync to moodle. * @return field The new field object. */ public static function make_from_moodle_field($mfieldid, field_category $category, $syncdir = false) { require_once elis::file('eliscore/fields/manual/custom_fields.php'); require_once elis::file('eliscore/fields/moodleprofile/custom_fields.php'); global $DB; // Get moodle field information. $mfield = $DB->get_record('user_info_field', array('id' => $mfieldid)); if (empty($mfield)) { return null; } if (!defined('CONTEXT_ELIS_USER')) { return null; } // Initially elis field data is the same as moodle field data. $field = (array) $mfield; unset($field['id']); $field['datatype'] = 'text'; $field['categoryid'] = $category->id; // Manual field owner data. $fieldmanualowner = new field_owner(); $fieldmanualowner->plugin = 'manual'; $fieldmanualowner->param_control = $mfield->datatype; $fieldmanualowner->param_required = (bool) (int) $mfield->required; // Set data based on moodle field's datatype. switch ($mfield->datatype) { case static::CHECKBOX: $field['datatype'] = 'bool'; break; case static::DATETIME: $field['datatype'] = 'datetime'; $fieldmanualowner->param_startyear = $mfield->param1; $fieldmanualowner->param_stopyear = $mfield->param2; $fieldmanualowner->param_inctime = $mfield->param3; break; case static::MENU: $field['datatype'] = 'char'; $fieldmanualowner->param_options = $mfield->param1; break; case static::TEXTAREA: $fieldmanualowner->param_columns = !empty($mfield->param1) ? $mfield->param1 : 30; $fieldmanualowner->param_rows = !empty($mfield->param2) ? $mfield->param2 : 10; break; case static::TEXT: if ($mfield->param3) { $fieldmanualowner->param_control = 'password'; } $fieldmanualowner->param_columns = $mfield->param1; $fieldmanualowner->param_maxlength = $mfield->param2; break; } // Create field. $field = new field($field); $field->save(); // Create moodle profile owner. if ($syncdir === pm_moodle_profile::sync_from_moodle || $syncdir === pm_moodle_profile::sync_from_moodle) { $fieldmoodleprofileowner = new field_owner(array('fieldid' => $field->id, 'plugin' => 'moodle_profile', 'exclude' => $syncdir)); $fieldmoodleprofileowner->save(); } // Create manual owner. $fieldmanualowner->fieldid = $field->id; $fieldmanualowner->save(); // Update field context level. static::ensure_field_exists_for_context_level($field, CONTEXT_ELIS_USER, $category); // Reload field object. $field = new field($field->id); $field->load(); if ($syncdir === pm_moodle_profile::sync_from_moodle) { sync_profile_field_settings_from_moodle($field); } // Set default data. if (isset($mfield->defaultdata) && $mfield->defaultdata !== '') { field_data::set_for_context_and_field(null, $field, $mfield->defaultdata); } // Reload field object. $field = new field($field->id); $field->load(); return $field; }
/** * sync_profile_field_from_moodle function synchronizes ELIS custom user field from corresponding Moodle field if possible. * also syncs relevant field settings * * @param object $field the field object to sync * @return mixed void or true (may throw DB exceptions) * @uses $DB */ function sync_profile_field_from_moodle($field) { global $DB; if (!isset($field->owners['moodle_profile']) || $field->owners['moodle_profile']->exclude == pm_moodle_profile::sync_to_moodle) { // not owned by the Moodle plugin, or set to sync to Moodle return true; } // check if sync is possible with current field settings if (!moodle_profile_can_sync($field->shortname)) { return true; } // Sync field settings first, since they could prevent field data sync sync_profile_field_settings_from_moodle($field); $dest = $field->data_table(); $src = 'user_info_data'; $mfieldid = $DB->get_field('user_info_field', 'id', array('shortname' => $field->shortname)); $joins = 'JOIN {' . user::TABLE . '} cu ON usr.idnumber = cu.idnumber JOIN {context} ctx ON ctx.instanceid = cu.id AND ctx.contextlevel = ' . CONTEXT_ELIS_USER . ' JOIN {' . $src . '} src ON src.userid = usr.id AND src.fieldid = ' . $mfieldid; // insert field values that don't already exist $sql = 'INSERT INTO {' . $dest . '} (contextid, fieldid, data) SELECT ctx.id AS contextid, ' . $field->id . ' AS fieldid, src.data FROM {user} usr ' . $joins . ' LEFT JOIN {' . $dest . '} dest ON dest.contextid = ctx.id AND dest.fieldid = ? WHERE dest.id IS NULL'; $DB->execute($sql, array($field->id)); // update already-existing values $sql = 'UPDATE {' . $dest . '} dest JOIN {user} usr ' . $joins . ' SET dest.data = src.data WHERE dest.fieldid = ? AND dest.contextid = ctx.id'; $DB->execute($sql, array($field->id)); }
/** * Validate that Moodle & ELIS custom user field settings are synced. * @param array $moodlefielddata Array of moodle field params * @param array $elisfielddata Array of elis field, context & owner params * @param bool $cansync True if Moodle & ELIS field should be sync-able * @param array $moodlefieldexpect Array of expected Moodle field values * @param array $elisfieldexpect Array of expected ELIS field/owner values * @uses $DB * @dataProvider customfieldsync_dataprovider */ public function test_custom_user_field_settings_synced($moodlefielddata, $elisfielddata, $cansync, $moodlefieldexpect, $elisfieldexpect) { global $DB; // Create Moodle profile field with data. if (!empty($moodlefielddata)) { $DB->insert_record('user_info_field', (object) $moodlefielddata); } // Create ELIS field, context & owner data. $field = $this->build_elis_field_data($elisfielddata); $this->assertTrue(moodle_profile_can_sync($field->shortname) == $cansync); sync_profile_field_settings_to_moodle($field); sync_profile_field_settings_from_moodle($field); if (!empty($moodlefieldexpect)) { $mdlfield = $DB->get_record('user_info_field', array('shortname' => $field->shortname)); foreach ($moodlefieldexpect as $key => $val) { $this->assertEquals($val, $mdlfield->{$key}); } } $this->assert_elis_field_data($field, $elisfieldexpect); }