ÿØÿà JFIF    ÿÛ „  ( %!1"%)+...383-7(-.+  -%%--------+------------/----------------------------ÿÀ  ¶" ÿÄ     ÿÄ @    !1AQaq‘¡"±ÁÑð2’BRráS‚ñ3b¢CT²Ò #cÿÄ    ÿÄ '   1!AQ2a"‘±#ÿÚ   ? ò(/p‘—–¢·,M|¯G»R£üešT. ôŸEoCFñ‡ 4ÔkåóxR|µÁ¨ÓDà!Ç´µ˜‡p ÍQ‹§!þž^õWsŒy  åÁ§$á«%ºq˜™JyyË0y«^Ϗ -3ÃÏËÈ£Æ\P’ä„s&•‹Üs üô'àWt¹pñCp$bÚF`·7.^)¾!)ÒÙ˜–ÅÏš^h¾ø³·ìe±ŒaeÖ—Í,e=B}Jéߦ$$½—šÙÜCC¯¶è8β–Åkèwx LÉngT±æ¹£Ócœ¿Í/©Éÿ ‰ÚCÒ­mq;Tt¶–}0"zÖPcd1š­¤tˆ„ÐÓâqú[™öUCŠ2޿د­É(¸ßúçšmW,Ò ºf‹Ån™ÄLêÁ8üÁkÁ€L¥ÍuJ1œôrÂSŒÓöt!7EUíïb)AËi¬â-ÙHk8òÁiXYwÅ)O^"ÇØriÛô{»wRUKÙÏÚlÓ¬€‚’שT ] ªb—ÊîKC^×M“xùäñz¬zJ× {4[¹M'IÆeH1LCWÕ]œûº WDæà°OÅ‚¹c²ÞuA–rL`HšpCez‡V’— Ž‚Ë„–ñ­'µR0ÕÖ=À:±G»C\nÆÉ5*¢ †3šhQ4ýÒi$Õ1"Ü]¢p-q3zcQ$ªö¨¥æf›‘»´ÝÚHá„e²JÇ–iÊ:·à¬Øc5Åk»Laª*wi [0Ówk ¦a¦0Õ²ÅÅ‚T¸’³q:Æ<¹ú Ò˜Æ}2VE•Ѐp¡Ô3Úº …h³^LJºçì¥Ánó|˜ÖˆÎˆ+« ¦dø‘Mº–d\ªDðÛ„Ì×LÝ——Ð9Ç|•FèçVm™¨€™3õS–9Äå¢<̉Ìm¢±`·º˜&Z²ž Ë®D«¶»6™‰©­HÈ Àá«h­i³04æ’hxœi3ï¨(ÓL·†‹¶ËpŒ ƒÜÒ? :õdsÀ –3æÓ·Üjâ¬9—]8n¼&]I F£¼…ì–—Œ0v°LǐÅfÛ2TÖ’aµ˜N@ÖBSÏ^à´4-±°b×nÈÈœÆD=KdŒø…ÄÈK,›ÖR§fjü$^ÔÎ!„6^ s:Üun\ôkCÞò÷<—Lúl Í›G¹ÀPá¼Nc¡šÒ…ÙWͳœ±vÊáЄ×9‹P‰cEØ‘™›µŸP<×g1 º©Ø¬wiÁ^l5× I/';Ë+´Z‡ÓÄFÕp[Èð’ HÄ ó 9¬Dl4“é±Ï”V=^Hð;žLädÉa‚°Ë,#/Ž~²BÔÛ QãñQtIeórVB,ÏÂf‹4#Μc‹É3ZIÖ Œ¢¥·üxa‡Ãçè„jŠ[5 Ä!U?,3žÎ×…ðdÓ÷háŠA‰ìÄ4÷ÃÜZÃE{‰®+W\ZÍE[‰»µk»MÝ­`¢©†˜ÃV‹-f¢¡†¢a«eŠ%«Y¨©q%jâd,Ôq`) ¤¦…šˆ ¦E>0Ùb²t–Ÿ  ÷jsςҒKÈcø(é›;k(’#ôŒQ ™C> õ³M9óºÆµ¤çâ;uÀ)5>RÔ¸fÓ~Ȧ—’-$fe«"FâUˆÊwµPÏu w+"åÉó¨6s™Û9(À¨ù©hE°½†n`m~“@©›p!Vkhµ Á1šbEqqÊ@ºèl›2 –Yã%‘‚hÛ{a¶‡ÅÞM²¬çL34®Çl]®‹ŽØŒm'RÜe35æöˆ­Á­ cP9šeÁköo´L³Ì=p8]”‡B¶,šº|ɏdzXˆØk*ÇÚ{#ñ‰pã(€¶‡9ý'šÜ³½¯kƒ†Â’ëSOƒ•Å®H6+a¢µˆ‹Y¨b˜b+XˆÖ!°u!¢"µˆˆn2ˆ Å0Ä`Å Ô®c(ÓÜGº•Ä»Œ âWî%q.áÐÄ÷î%umÍ£+ÜQ,Vn¦-GphU¸¢X­&,Gt ·K¢ÅÅ·¥RÄÊÉbKn N­R2g$VµÕó Qr#–ÓZUÏð0ÎtÙÀœz ëöo“Îk¯n„T°"SÞ+³/…€½"ižãO%Í%&Δâ‘ËØ´IˆÓ°Ë‰ÞM3.™yçÁzit›`4C`k¢¼á!álÄï sÂ[×!¤l¾æo"ƒIÔ'@ªu)3›ô®“@Z à¾] üœ'‡4¸PÞkÁ-qÄH‰ÓÍBd´ÿ —¹°ÄA&ÊUC„öz‚‚RæŠtf¬>˜ÑidšEçÑL÷áý¡V±h§­c¿¥¨ƒ@g¼…Ýö^ÞËc.ž„æ“I[ ª+·zÐÒÚ,½Ð»¶€C›7jk$๖€Ž¾ÅÙpp¶ÍþéΤ ½ØÎŒq ñpæžÅ¥#Øâ49ÃÂK^MYV“,¶Éz_òæ:…;Õ«¦I3âJ£¤û5$7Èxün\â]Êe¯(Ÿ&Ž‹¶2<6Ćf× æ¢ƱqWı[(ÙÂŽç‚T7¨@ÈÉÍß‚ïÙ"&3TYlGށ†)†#5ŠA©^T2ÆÁ†)©†©†¤yGXÁ†©"†)©¼ÅV WS†#©]R–r‹q5ÕbâWþBh¯u+ªÅÄ×ü„nÑ^êkªÁj‰jeœWˆÕÕ`µDµ2Ì+Ä µDµµ5Ô{¤Þ0RF’H÷AÛ<ÊÁ§ ƈæ0Ñ£ê4 öÚµ[¬/1²€â<-¬šö²r×0o×w ´xÐÒË„ g01¬„åEM蛇Ã\6µE›‘fÙO k÷CºnÓ1`iîÒ² áÂ!ÑLÄÿ ³¬\õ*:M=Ç»c‹2[‰ØsÕLò\ŒK L^™üÄN\¶#» Ç\……ßïIœÉ$¸ÎñÍ®ÔHÇ Ûí/´>Li”„†g^$ 7NÍÑÑ2M2ªNg=k½Ð}Ÿl6‡ —JWˆ”ÞéT·+µ¦üÂFè¢Vs6ýÆ4»2+2IÝ9ô9®ÇGÙÇ1+Ä8;*Õ§ž½D*îÑWÚçá3Ma¤·ˆ¥t}š²Ü…Q+Æüµ8HíS”üxGéÁˆ¦Ël„éÈ 5 µÄƒ6äÜ(kIš¯S†fÖ&¸®×v|Äyˆ*禌Éù@7-®Ä[]ÎÖ¿ê`º…=:…žJVnݳvðÖKE¼‚ »mà ÷f®:Ë ™–4rð¡4` ܹçž~‹C=œ…¾Ïj|VDtK.ƒ"9 Ï ­–éXá³6WÒïIL-Ї Ç lib.php000064400000103731152266356300006040 0ustar00. /** * Profile field API library file. * * @package core_user * @copyright 2007 onwards Shane Elliot {@link http://pukunui.com} * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ /** * Visible to anyone who has the moodle/site:viewuseridentity permission. * Editable by the profile owner if they have the moodle/user:editownprofile capability * or any user with the moodle/user:update capability. */ define('PROFILE_VISIBLE_TEACHERS', '3'); /** * Visible to anyone who can view the user. * Editable by the profile owner if they have the moodle/user:editownprofile capability * or any user with the moodle/user:update capability. */ define('PROFILE_VISIBLE_ALL', '2'); /** * Visible to the profile owner or anyone with the moodle/user:viewalldetails capability. * Editable by the profile owner if they have the moodle/user:editownprofile capability * or any user with moodle/user:viewalldetails and moodle/user:update capabilities. */ define('PROFILE_VISIBLE_PRIVATE', '1'); /** * Only visible to users with the moodle/user:viewalldetails capability. * Only editable by users with the moodle/user:viewalldetails and moodle/user:update capabilities. */ define('PROFILE_VISIBLE_NONE', '0'); /** * Base class for the customisable profile fields. * * @package core_user * @copyright 2007 onwards Shane Elliot {@link http://pukunui.com} * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class profile_field_base { // These 2 variables are really what we're interested in. // Everything else can be extracted from them. /** @var int */ public $fieldid; /** @var int */ public $userid; /** @var stdClass */ public $field; /** @var string */ public $inputname; /** @var mixed */ public $data; /** @var string */ public $dataformat; /** @var string name of the user profile category */ protected $categoryname; /** * Constructor method. * @param int $fieldid id of the profile from the user_info_field table * @param int $userid id of the user for whom we are displaying data * @param stdClass $fielddata optional data for the field object plus additional fields 'hasuserdata', 'data' and 'dataformat' * with user data. (If $fielddata->hasuserdata is empty, user data is not available and we should use default data). * If this parameter is passed, constructor will not call load_data() at all. */ public function __construct($fieldid=0, $userid=0, $fielddata=null) { global $CFG; if ($CFG->debugdeveloper) { // In Moodle 3.4 the new argument $fielddata was added to the constructor. Make sure that // plugin constructor properly passes this argument. $backtrace = debug_backtrace(); if (isset($backtrace[1]['class']) && $backtrace[1]['function'] === '__construct' && in_array(self::class, class_parents($backtrace[1]['class']))) { // If this constructor is called from the constructor of the plugin make sure that the third argument was passed through. if (count($backtrace[1]['args']) >= 3 && count($backtrace[0]['args']) < 3) { debugging($backtrace[1]['class'].'::__construct() must support $fielddata as the third argument ' . 'and pass it to the parent constructor', DEBUG_DEVELOPER); } } } $this->set_fieldid($fieldid); $this->set_userid($userid); if ($fielddata) { $this->set_field($fielddata); if ($userid > 0 && !empty($fielddata->hasuserdata)) { $this->set_user_data($fielddata->data, $fielddata->dataformat); } } else { $this->load_data(); } } /** * Old syntax of class constructor. Deprecated in PHP7. * * @deprecated since Moodle 3.1 */ public function profile_field_base($fieldid=0, $userid=0) { debugging('Use of class name as constructor is deprecated', DEBUG_DEVELOPER); self::__construct($fieldid, $userid); } /** * Abstract method: Adds the profile field to the moodle form class * @abstract The following methods must be overwritten by child classes * @param MoodleQuickForm $mform instance of the moodleform class */ public function edit_field_add($mform) { throw new \moodle_exception('mustbeoveride', 'debug', '', 'edit_field_add'); } /** * Display the data for this field * @return string */ public function display_data() { $options = new stdClass(); $options->para = false; return format_text($this->data, FORMAT_MOODLE, $options); } /** * Display the name of the profile field. * * @param bool $escape * @return string */ public function display_name(bool $escape = true): string { return format_string($this->field->name, true, [ 'context' => context_system::instance(), 'escape' => $escape, ]); } /** * Print out the form field in the edit profile page * @param MoodleQuickForm $mform instance of the moodleform class * @return bool */ public function edit_field($mform) { if (!$this->is_editable()) { return false; } $this->edit_field_add($mform); $this->edit_field_set_default($mform); $this->edit_field_set_required($mform); return true; } /** * Tweaks the edit form * @param MoodleQuickForm $mform instance of the moodleform class * @return bool */ public function edit_after_data($mform) { if (!$this->is_editable()) { return false; } $this->edit_field_set_locked($mform); return true; } /** * Saves the data coming from form * @param stdClass $usernew data coming from the form */ public function edit_save_data($usernew) { global $DB; if (!isset($usernew->{$this->inputname})) { // Field not present in form, probably locked and invisible - skip it. return; } $data = new stdClass(); $usernew->{$this->inputname} = $this->edit_save_data_preprocess($usernew->{$this->inputname}, $data); if (!isset($usernew->{$this->inputname})) { // Field cannot be set to null, set the default value. $usernew->{$this->inputname} = $this->field->defaultdata; } $data->userid = $usernew->id; $data->fieldid = $this->field->id; $data->data = $usernew->{$this->inputname}; if ($dataid = $DB->get_field('user_info_data', 'id', array('userid' => $data->userid, 'fieldid' => $data->fieldid))) { $data->id = $dataid; $DB->update_record('user_info_data', $data); } else { $DB->insert_record('user_info_data', $data); } } /** * Validate the form field from profile page * * @param stdClass $usernew * @return array error messages for the form validation */ public function edit_validate_field($usernew) { global $DB; $errors = array(); // Get input value. if (isset($usernew->{$this->inputname})) { if (is_array($usernew->{$this->inputname}) && isset($usernew->{$this->inputname}['text'])) { $value = $usernew->{$this->inputname}['text']; } else { $value = $usernew->{$this->inputname}; } } else { $value = ''; } // Check for uniqueness of data if required. if ($this->is_unique() && (($value !== '') || $this->is_required())) { $data = $DB->get_records_sql(' SELECT id, userid FROM {user_info_data} WHERE fieldid = ? AND ' . $DB->sql_compare_text('data', 255) . ' = ' . $DB->sql_compare_text('?', 255), array($this->field->id, $value)); if ($data) { $existing = false; foreach ($data as $v) { if ($v->userid == $usernew->id) { $existing = true; break; } } if (!$existing) { $errors[$this->inputname] = get_string('valuealreadyused'); } } } return $errors; } /** * Sets the default data for the field in the form object * @param MoodleQuickForm $mform instance of the moodleform class */ public function edit_field_set_default($mform) { if (isset($this->field->defaultdata)) { $mform->setDefault($this->inputname, $this->field->defaultdata); } } /** * Sets the required flag for the field in the form object * * @param MoodleQuickForm $mform instance of the moodleform class */ public function edit_field_set_required($mform) { global $USER; if ($this->is_required() && ($this->userid == $USER->id || isguestuser())) { $mform->addRule($this->inputname, get_string('required'), 'required', null, 'client'); } } /** * HardFreeze the field if locked. * @param MoodleQuickForm $mform instance of the moodleform class */ public function edit_field_set_locked($mform) { if (!$mform->elementExists($this->inputname)) { return; } if ($this->is_locked() and !has_capability('moodle/user:update', context_system::instance())) { $mform->hardFreeze($this->inputname); $mform->setConstant($this->inputname, $this->data); } } /** * Hook for child classess to process the data before it gets saved in database * @param stdClass $data * @param stdClass $datarecord The object that will be used to save the record * @return mixed */ public function edit_save_data_preprocess($data, $datarecord) { return $data; } /** * Loads a user object with data for this field ready for the edit profile * form * @param stdClass $user a user object */ public function edit_load_user_data($user) { if ($this->data !== null) { $user->{$this->inputname} = $this->data; } } /** * Check if the field data should be loaded into the user object * By default it is, but for field types where the data may be potentially * large, the child class should override this and return false * @return bool */ public function is_user_object_data() { return true; } /** * Accessor method: set the userid for this instance * @internal This method should not generally be overwritten by child classes. * @param integer $userid id from the user table */ public function set_userid($userid) { $this->userid = $userid; } /** * Accessor method: set the fieldid for this instance * @internal This method should not generally be overwritten by child classes. * @param integer $fieldid id from the user_info_field table */ public function set_fieldid($fieldid) { $this->fieldid = $fieldid; } /** * Sets the field object and default data and format into $this->data and $this->dataformat * * This method should be called before {@link self::set_user_data} * * @param stdClass $field * @throws coding_exception */ public function set_field($field) { global $CFG; if ($CFG->debugdeveloper) { $properties = ['id', 'shortname', 'name', 'datatype', 'description', 'descriptionformat', 'categoryid', 'sortorder', 'required', 'locked', 'visible', 'forceunique', 'signup', 'defaultdata', 'defaultdataformat', 'param1', 'param2', 'param3', 'param4', 'param5']; foreach ($properties as $property) { if (!property_exists($field, $property)) { debugging('The \'' . $property . '\' property must be set.', DEBUG_DEVELOPER); } } } if ($this->fieldid && $this->fieldid != $field->id) { throw new coding_exception('Can not set field object after a different field id was set'); } $this->fieldid = $field->id; $this->field = $field; $this->inputname = 'profile_field_' . $this->field->shortname; $this->data = $this->field->defaultdata; $this->dataformat = FORMAT_HTML; } /** * Sets user id and user data for the field * * @param mixed $data * @param int $dataformat */ public function set_user_data($data, $dataformat) { $this->data = $data; $this->dataformat = $dataformat; } /** * Set the name for the profile category where this field is * * @param string $categoryname */ public function set_category_name($categoryname) { $this->categoryname = $categoryname; } /** * Return field short name * * @return string */ public function get_shortname(): string { return $this->field->shortname; } /** * Returns the name of the profile category where this field is * * @return string */ public function get_category_name() { global $DB; if ($this->categoryname === null) { $this->categoryname = $DB->get_field('user_info_category', 'name', ['id' => $this->field->categoryid]); } return $this->categoryname; } /** * Accessor method: Load the field record and user data associated with the * object's fieldid and userid * * @internal This method should not generally be overwritten by child classes. */ public function load_data() { global $DB; // Load the field object. if (($this->fieldid == 0) or (!($field = $DB->get_record('user_info_field', array('id' => $this->fieldid))))) { $this->field = null; $this->inputname = ''; } else { $this->set_field($field); } if (!empty($this->field) && $this->userid > 0) { $params = array('userid' => $this->userid, 'fieldid' => $this->fieldid); if ($data = $DB->get_record('user_info_data', $params, 'data, dataformat')) { $this->set_user_data($data->data, $data->dataformat); } } else { $this->data = null; } } /** * Check if the field data is visible to the current user * @internal This method should not generally be overwritten by child classes. * * @param context|null $context * @return bool */ public function is_visible(?context $context = null): bool { global $USER, $COURSE; if ($context === null) { $context = ($this->userid > 0) ? context_user::instance($this->userid) : context_system::instance(); } switch ($this->field->visible) { case PROFILE_VISIBLE_TEACHERS: if ($this->is_signup_field() && (empty($this->userid) || isguestuser($this->userid))) { return true; } else if ($this->userid == $USER->id) { return true; } else if ($this->userid > 0) { return has_capability('moodle/user:viewalldetails', $context); } else if ($context instanceof context_course) { return has_capability('moodle/site:viewuseridentity', $context); } else { // Fall back to the global course object. $coursecontext = context_course::instance($COURSE->id); return has_capability('moodle/site:viewuseridentity', $coursecontext); } case PROFILE_VISIBLE_ALL: return true; case PROFILE_VISIBLE_PRIVATE: if ($this->is_signup_field() && (empty($this->userid) || isguestuser($this->userid))) { return true; } else if ($this->userid == $USER->id) { return true; } else { return has_capability('moodle/user:viewalldetails', $context); } default: // PROFILE_VISIBLE_NONE, so let's check capabilities at system level. if ($this->userid > 0) { $context = context_system::instance(); } return has_capability('moodle/user:viewalldetails', $context); } } /** * Check if the field data is editable for the current user * This method should not generally be overwritten by child classes. * @return bool */ public function is_editable() { global $USER; if (!$this->is_visible()) { return false; } if ($this->is_signup_field() && (empty($this->userid) || isguestuser($this->userid))) { // Allow editing the field on the signup page. return true; } $systemcontext = context_system::instance(); if ($this->userid == $USER->id && has_capability('moodle/user:editownprofile', $systemcontext)) { return true; } if (has_capability('moodle/user:update', $systemcontext)) { return true; } // Checking for mentors have capability to edit user's profile. if ($this->userid > 0) { $usercontext = context_user::instance($this->userid); if ($this->userid != $USER->id && has_capability('moodle/user:editprofile', $usercontext, $USER->id)) { return true; } } return false; } /** * Check if the field data is considered empty * @internal This method should not generally be overwritten by child classes. * @return boolean */ public function is_empty() { return ( ($this->data != '0') and empty($this->data)); } /** * Check if the field is required on the edit profile page * @internal This method should not generally be overwritten by child classes. * @return bool */ public function is_required() { return (boolean)$this->field->required; } /** * Check if the field is locked on the edit profile page * @internal This method should not generally be overwritten by child classes. * @return bool */ public function is_locked() { return (boolean)$this->field->locked; } /** * Check if the field data should be unique * @internal This method should not generally be overwritten by child classes. * @return bool */ public function is_unique() { return (boolean)$this->field->forceunique; } /** * Check if the field should appear on the signup page * @internal This method should not generally be overwritten by child classes. * @return bool */ public function is_signup_field() { return (boolean)$this->field->signup; } /** * Return the field settings suitable to be exported via an external function. * By default it return all the field settings. * * @return array all the settings * @since Moodle 3.2 */ public function get_field_config_for_external() { return (array) $this->field; } /** * Return the field type and null properties. * This will be used for validating the data submitted by a user. * * @return array the param type and null property * @since Moodle 3.2 */ public function get_field_properties() { return array(PARAM_RAW, NULL_NOT_ALLOWED); } /** * Whether to display the field and content to the user * * @param context|null $context * @return bool */ public function show_field_content(?context $context = null): bool { return $this->is_visible($context) && !$this->is_empty(); } /** * Check if the field should convert the raw data into user-friendly data when exporting * * @return bool */ public function is_transform_supported(): bool { return false; } } /** * Return profile field instance for given type * * @param string $type * @param int $fieldid * @param int $userid * @param stdClass|null $fielddata * @return profile_field_base */ function profile_get_user_field(string $type, int $fieldid = 0, int $userid = 0, ?stdClass $fielddata = null): profile_field_base { global $CFG; require_once("{$CFG->dirroot}/user/profile/field/{$type}/field.class.php"); // Return instance of profile field type. $profilefieldtype = "profile_field_{$type}"; return new $profilefieldtype($fieldid, $userid, $fielddata); } /** * Returns an array of all custom field records with any defined data (or empty data), for the specified user id. * @param int $userid * @return profile_field_base[] */ function profile_get_user_fields_with_data(int $userid): array { global $DB; // Join any user info data present with each user info field for the user object. $sql = 'SELECT uif.*, uic.name AS categoryname '; if ($userid > 0) { $sql .= ', uind.id AS hasuserdata, uind.data, uind.dataformat '; } $sql .= 'FROM {user_info_field} uif '; $sql .= 'LEFT JOIN {user_info_category} uic ON uif.categoryid = uic.id '; if ($userid > 0) { $sql .= 'LEFT JOIN {user_info_data} uind ON uif.id = uind.fieldid AND uind.userid = :userid '; } $sql .= 'ORDER BY uic.sortorder ASC, uif.sortorder ASC '; $fields = $DB->get_records_sql($sql, ['userid' => $userid]); $data = []; foreach ($fields as $field) { $field->hasuserdata = !empty($field->hasuserdata); $fieldobject = profile_get_user_field($field->datatype, $field->id, $userid, $field); $fieldobject->set_category_name($field->categoryname); unset($field->categoryname); $data[] = $fieldobject; } return $data; } /** * Returns an array of all custom field records with any defined data (or empty data), for the specified user id, by category. * @param int $userid * @return profile_field_base[][] */ function profile_get_user_fields_with_data_by_category(int $userid): array { $fields = profile_get_user_fields_with_data($userid); $data = []; foreach ($fields as $field) { $data[$field->field->categoryid][] = $field; } return $data; } /** * Loads user profile field data into the user object. * @param stdClass $user */ function profile_load_data(stdClass $user): void { $fields = profile_get_user_fields_with_data($user->id); foreach ($fields as $formfield) { $formfield->edit_load_user_data($user); } } /** * Print out the customisable categories and fields for a users profile * * @param MoodleQuickForm $mform instance of the moodleform class * @param int $userid id of user whose profile is being edited or 0 for the new user */ function profile_definition(MoodleQuickForm $mform, int $userid = 0): void { $categories = profile_get_user_fields_with_data_by_category($userid); foreach ($categories as $categoryid => $fields) { // Check first if *any* fields will be displayed. $fieldstodisplay = []; foreach ($fields as $formfield) { if ($formfield->is_editable()) { $fieldstodisplay[] = $formfield; } } if (empty($fieldstodisplay)) { continue; } // Display the header and the fields. $mform->addElement('header', 'category_'.$categoryid, format_string($fields[0]->get_category_name())); foreach ($fieldstodisplay as $formfield) { $formfield->edit_field($mform); } } } /** * Adds profile fields to user edit forms. * @param MoodleQuickForm $mform * @param int $userid */ function profile_definition_after_data(MoodleQuickForm $mform, int $userid): void { $userid = ($userid < 0) ? 0 : (int)$userid; $fields = profile_get_user_fields_with_data($userid); foreach ($fields as $formfield) { $formfield->edit_after_data($mform); } } /** * Validates profile data. * @param stdClass $usernew * @param array $files * @return array array of errors, same as in {@see moodleform::validation()} */ function profile_validation(stdClass $usernew, array $files): array { $err = array(); $fields = profile_get_user_fields_with_data($usernew->id); foreach ($fields as $formfield) { $err += $formfield->edit_validate_field($usernew, $files); } return $err; } /** * Saves profile data for a user. * @param stdClass $usernew */ function profile_save_data(stdClass $usernew): void { global $CFG; $fields = profile_get_user_fields_with_data($usernew->id); foreach ($fields as $formfield) { $formfield->edit_save_data($usernew); } } /** * Retrieves a list of profile fields that must be displayed in the sign-up form. * * @return array list of profile fields info * @since Moodle 3.2 */ function profile_get_signup_fields(): array { $profilefields = array(); $fieldobjects = profile_get_user_fields_with_data(0); foreach ($fieldobjects as $fieldobject) { $field = (object)$fieldobject->get_field_config_for_external(); if ($fieldobject->get_category_name() !== null && $fieldobject->is_signup_field() && $field->visible <> 0) { $profilefields[] = (object) array( 'categoryid' => $field->categoryid, 'categoryname' => $fieldobject->get_category_name(), 'fieldid' => $field->id, 'datatype' => $field->datatype, 'object' => $fieldobject ); } } return $profilefields; } /** * Adds code snippet to a moodle form object for custom profile fields that * should appear on the signup page * @param MoodleQuickForm $mform moodle form object */ function profile_signup_fields(MoodleQuickForm $mform): void { if ($fields = profile_get_signup_fields()) { foreach ($fields as $field) { // Check if we change the categories. if (!isset($currentcat) || $currentcat != $field->categoryid) { $currentcat = $field->categoryid; $mform->addElement('header', 'category_'.$field->categoryid, format_string($field->categoryname)); }; $field->object->edit_field($mform); } } } /** * Returns an object with the custom profile fields set for the given user * @param int $userid * @param bool $onlyinuserobject True if you only want the ones in $USER. * @return stdClass object where properties names are shortnames of custom profile fields */ function profile_user_record(int $userid, bool $onlyinuserobject = true): stdClass { $usercustomfields = new stdClass(); $fields = profile_get_user_fields_with_data($userid); foreach ($fields as $formfield) { if (!$onlyinuserobject || $formfield->is_user_object_data()) { $usercustomfields->{$formfield->field->shortname} = $formfield->data; } } return $usercustomfields; } /** * Obtains a list of all available custom profile fields, indexed by id. * * Some profile fields are not included in the user object data (see * profile_user_record function above). Optionally, you can obtain only those * fields that are included in the user object. * * To be clear, this function returns the available fields, and does not * return the field values for a particular user. * * @param bool $onlyinuserobject True if you only want the ones in $USER * @return array Array of field objects from database (indexed by id) * @since Moodle 2.7.1 */ function profile_get_custom_fields(bool $onlyinuserobject = false): array { $fieldobjects = profile_get_user_fields_with_data(0); $fields = []; foreach ($fieldobjects as $fieldobject) { if (!$onlyinuserobject || $fieldobject->is_user_object_data()) { $fields[$fieldobject->fieldid] = (object)$fieldobject->get_field_config_for_external(); } } ksort($fields); return $fields; } /** * Load custom profile fields into user object * * @param stdClass $user user object */ function profile_load_custom_fields($user) { $user->profile = (array)profile_user_record($user->id); } /** * Save custom profile fields for a user. * * @param int $userid The user id * @param array $profilefields The fields to save */ function profile_save_custom_fields($userid, $profilefields) { global $DB; $fields = profile_get_user_fields_with_data(0); if ($fields) { foreach ($fields as $fieldobject) { $field = (object)$fieldobject->get_field_config_for_external(); if (isset($profilefields[$field->shortname])) { $conditions = array('fieldid' => $field->id, 'userid' => $userid); $id = $DB->get_field('user_info_data', 'id', $conditions); $data = $profilefields[$field->shortname]; if ($id) { $DB->set_field('user_info_data', 'data', $data, array('id' => $id)); } else { $record = array('fieldid' => $field->id, 'userid' => $userid, 'data' => $data); $DB->insert_record('user_info_data', $record); } } } } } /** * Gets basic data about custom profile fields. This is minimal data that is cached within the * current request for all fields so that it can be used quickly. * * @param string $shortname Shortname of custom profile field * @param bool $casesensitive Whether to perform case-sensitive matching of shortname. Note current limitations of custom profile * fields allow the same shortname to exist differing only by it's case * @return stdClass|null Object with properties id, shortname, name, visible, datatype, categoryid, etc */ function profile_get_custom_field_data_by_shortname(string $shortname, bool $casesensitive = true): ?stdClass { $cache = \cache::make_from_params(cache_store::MODE_REQUEST, 'core_profile', 'customfields', [], ['simplekeys' => true, 'simpledata' => true]); $data = $cache->get($shortname); if ($data === false) { // If we don't have data, we get and cache it for all fields to avoid multiple DB requests. $fields = profile_get_custom_fields(); $data = null; foreach ($fields as $field) { $cache->set($field->shortname, $field); // Perform comparison according to case sensitivity parameter. $shortnamematch = $casesensitive ? strcmp($field->shortname, $shortname) === 0 : strcasecmp($field->shortname, $shortname) === 0; if ($shortnamematch) { $data = $field; } } } return $data; } /** * Trigger a user profile viewed event. * * @param stdClass $user user object * @param stdClass $context context object (course or user) * @param stdClass $course course object * @since Moodle 2.9 */ function profile_view($user, $context, $course = null) { $eventdata = array( 'objectid' => $user->id, 'relateduserid' => $user->id, 'context' => $context ); if (!empty($course)) { $eventdata['courseid'] = $course->id; $eventdata['other'] = array( 'courseid' => $course->id, 'courseshortname' => $course->shortname, 'coursefullname' => $course->fullname ); } $event = \core\event\user_profile_viewed::create($eventdata); $event->add_record_snapshot('user', $user); $event->trigger(); } /** * Does the user have all required custom fields set? * * Internal, to be exclusively used by {@link user_not_fully_set_up()} only. * * Note that if users have no way to fill a required field via editing their * profiles (e.g. the field is not visible or it is locked), we still return true. * So this is actually checking if we should redirect the user to edit their * profile, rather than whether there is a value in the database. * * @param int $userid * @return bool */ function profile_has_required_custom_fields_set($userid) { $profilefields = profile_get_user_fields_with_data($userid); foreach ($profilefields as $profilefield) { if ($profilefield->is_required() && !$profilefield->is_locked() && $profilefield->is_empty() && $profilefield->get_field_config_for_external()['visible']) { return false; } } return true; } /** * Return the list of valid custom profile user fields. * * @return array array of profile field names */ function get_profile_field_names(): array { $profilefields = profile_get_user_fields_with_data(0); $profilefieldnames = []; foreach ($profilefields as $field) { $profilefieldnames[] = $field->inputname; } return $profilefieldnames; } /** * Return the list of profile fields * in a format they can be used for choices in a group select menu. * * @return array array of category name with its profile fields */ function get_profile_field_list(): array { $customfields = profile_get_user_fields_with_data_by_category(0); $data = []; foreach ($customfields as $category) { foreach ($category as $field) { $categoryname = $field->get_category_name(); if (!isset($data[$categoryname])) { $data[$categoryname] = []; } $data[$categoryname][$field->inputname] = $field->display_name(); } } return $data; } definelib.php000064400000047057152266356300007223 0ustar00. /** * This file contains the profile_define_base class. * * @package core_user * @copyright 2007 onwards Shane Elliot {@link http://pukunui.com} * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ /** * Class profile_define_base * * @copyright 2007 onwards Shane Elliot {@link http://pukunui.com} * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class profile_define_base { /** * Prints out the form snippet for creating or editing a profile field * @param MoodleQuickForm $form instance of the moodleform class */ public function define_form(&$form) { $form->addElement('header', '_commonsettings', get_string('profilecommonsettings', 'admin')); $this->define_form_common($form); $form->addElement('header', '_specificsettings', get_string('profilespecificsettings', 'admin')); $this->define_form_specific($form); } /** * Prints out the form snippet for the part of creating or editing a profile field common to all data types. * * @param MoodleQuickForm $form instance of the moodleform class */ public function define_form_common(&$form) { $strrequired = get_string('required'); // Accepted values for 'shortname' would follow [a-zA-Z0-9_] pattern, // but we are accepting any PARAM_TEXT value here, // and checking [a-zA-Z0-9_] pattern in define_validate_common() function to throw an error when needed. $form->addElement('text', 'shortname', get_string('profileshortname', 'admin'), 'maxlength="100" size="25"'); $form->addRule('shortname', $strrequired, 'required', null, 'client'); $form->setType('shortname', PARAM_TEXT); $form->addElement('text', 'name', get_string('profilename', 'admin'), 'size="50"'); $form->addRule('name', $strrequired, 'required', null, 'client'); $form->setType('name', PARAM_TEXT); $form->addElement('editor', 'description', get_string('profiledescription', 'admin'), null, null); $form->addElement('selectyesno', 'required', get_string('profilerequired', 'admin')); $form->addElement('selectyesno', 'locked', get_string('profilelocked', 'admin')); $form->addElement('selectyesno', 'forceunique', get_string('profileforceunique', 'admin')); $form->addElement('selectyesno', 'signup', get_string('profilesignup', 'admin')); $choices = array(); $choices[PROFILE_VISIBLE_NONE] = get_string('profilevisiblenone', 'admin'); $choices[PROFILE_VISIBLE_PRIVATE] = get_string('profilevisibleprivate', 'admin'); $choices[PROFILE_VISIBLE_TEACHERS] = get_string('profilevisibleteachers', 'admin'); $choices[PROFILE_VISIBLE_ALL] = get_string('profilevisibleall', 'admin'); $form->addElement('select', 'visible', get_string('profilevisible', 'admin'), $choices); $form->addHelpButton('visible', 'profilevisible', 'admin'); $form->setDefault('visible', PROFILE_VISIBLE_ALL); $choices = profile_list_categories(); $form->addElement('select', 'categoryid', get_string('profilecategory', 'admin'), $choices); } /** * Prints out the form snippet for the part of creating or editing a profile field specific to the current data type. * @param MoodleQuickForm $form instance of the moodleform class */ public function define_form_specific($form) { // Do nothing - overwrite if necessary. } /** * Validate the data from the add/edit profile field form. * * Generally this method should not be overwritten by child classes. * * @param stdClass|array $data from the add/edit profile field form * @param array $files * @return array associative array of error messages */ public function define_validate($data, $files) { $data = (object)$data; $err = array(); $err += $this->define_validate_common($data, $files); $err += $this->define_validate_specific($data, $files); return $err; } /** * Validate the data from the add/edit profile field form that is common to all data types. * * Generally this method should not be overwritten by child classes. * * @param stdClass|array $data from the add/edit profile field form * @param array $files * @return array associative array of error messages */ public function define_validate_common($data, $files) { global $DB; $err = array(); // Check the shortname was not truncated by cleaning. if (empty($data->shortname)) { $err['shortname'] = get_string('required'); } else { // Check allowed pattern (numbers, letters and underscore). if (!preg_match('/^[a-zA-Z0-9_]+$/', $data->shortname)) { $err['shortname'] = get_string('profileshortnameinvalid', 'admin'); } else { // Fetch field-record from DB. $field = profile_get_custom_field_data_by_shortname($data->shortname); // Check the shortname is unique. if ($field and $field->id <> $data->id) { $err['shortname'] = get_string('profileshortnamenotunique', 'admin'); } // NOTE: since 2.0 the shortname may collide with existing fields in $USER because we load these fields into // $USER->profile array instead. } } // No further checks necessary as the form class will take care of it. return $err; } /** * Validate the data from the add/edit profile field form * that is specific to the current data type * @param array $data * @param array $files * @return array associative array of error messages */ public function define_validate_specific($data, $files) { // Do nothing - overwrite if necessary. return array(); } /** * Alter form based on submitted or existing data * @param MoodleQuickForm $mform */ public function define_after_data(&$mform) { // Do nothing - overwrite if necessary. } /** * Add a new profile field or save changes to current field * @param array|stdClass $data from the add/edit profile field form */ public function define_save($data) { global $DB; $data = $this->define_save_preprocess($data); // Hook for child classes. $old = false; if (!empty($data->id)) { $old = $DB->get_record('user_info_field', array('id' => (int)$data->id)); } // Check to see if the category has changed. if (!$old or $old->categoryid != $data->categoryid) { $data->sortorder = $DB->count_records('user_info_field', array('categoryid' => $data->categoryid)) + 1; } if (empty($data->id)) { unset($data->id); $data->id = $DB->insert_record('user_info_field', $data); } else { $DB->update_record('user_info_field', $data); } $field = $DB->get_record('user_info_field', array('id' => $data->id)); if ($old) { \core\event\user_info_field_updated::create_from_field($field)->trigger(); } else { \core\event\user_info_field_created::create_from_field($field)->trigger(); } profile_purge_user_fields_cache(); } /** * Preprocess data from the add/edit profile field form before it is saved. * * This method is a hook for the child classes to overwrite. * * @param array|stdClass $data from the add/edit profile field form * @return array|stdClass processed data object */ public function define_save_preprocess($data) { // Do nothing - overwrite if necessary. return $data; } /** * Provides a method by which we can allow the default data in profile_define_* to use an editor * * This should return an array of editor names (which will need to be formatted/cleaned) * * @return array */ public function define_editors() { return array(); } } /** * Reorder the profile fields within a given category starting at the field at the given startorder. */ function profile_reorder_fields() { global $DB; if ($categories = $DB->get_records('user_info_category')) { foreach ($categories as $category) { $i = 1; if ($fields = $DB->get_records('user_info_field', array('categoryid' => $category->id), 'sortorder ASC')) { foreach ($fields as $field) { $f = new stdClass(); $f->id = $field->id; $f->sortorder = $i++; $DB->update_record('user_info_field', $f); } } } profile_purge_user_fields_cache(); } } /** * Reorder the profile categoriess starting at the category at the given startorder. */ function profile_reorder_categories() { global $DB; $i = 1; if ($categories = $DB->get_records('user_info_category', null, 'sortorder ASC')) { foreach ($categories as $cat) { $c = new stdClass(); $c->id = $cat->id; $c->sortorder = $i++; $DB->update_record('user_info_category', $c); } profile_purge_user_fields_cache(); } } /** * Delete a profile category * @param int $id of the category to be deleted * @return bool success of operation */ function profile_delete_category($id) { global $DB; // Retrieve the category. if (!$category = $DB->get_record('user_info_category', array('id' => $id))) { throw new \moodle_exception('invalidcategoryid'); } if (!$categories = $DB->get_records('user_info_category', null, 'sortorder ASC')) { throw new \moodle_exception('nocate', 'debug'); } unset($categories[$category->id]); if (!count($categories)) { return false; // We can not delete the last category. } // Does the category contain any fields. if ($DB->count_records('user_info_field', array('categoryid' => $category->id))) { if (array_key_exists($category->sortorder - 1, $categories)) { $newcategory = $categories[$category->sortorder - 1]; } else if (array_key_exists($category->sortorder + 1, $categories)) { $newcategory = $categories[$category->sortorder + 1]; } else { $newcategory = reset($categories); // Get first category if sortorder broken. } $sortorder = $DB->count_records('user_info_field', array('categoryid' => $newcategory->id)) + 1; if ($fields = $DB->get_records('user_info_field', array('categoryid' => $category->id), 'sortorder ASC')) { foreach ($fields as $field) { $f = new stdClass(); $f->id = $field->id; $f->sortorder = $sortorder++; $f->categoryid = $newcategory->id; if ($DB->update_record('user_info_field', $f)) { $field->sortorder = $f->sortorder; $field->categoryid = $f->categoryid; \core\event\user_info_field_updated::create_from_field($field)->trigger(); } } } } // Finally we get to delete the category. $DB->delete_records('user_info_category', array('id' => $category->id)); profile_reorder_categories(); \core\event\user_info_category_deleted::create_from_category($category)->trigger(); profile_purge_user_fields_cache(); return true; } /** * Deletes a profile field. * @param int $id */ function profile_delete_field($id) { global $DB; // Remove any user data associated with this field. if (!$DB->delete_records('user_info_data', array('fieldid' => $id))) { throw new \moodle_exception('cannotdeletecustomfield'); } // Note: Any availability conditions that depend on this field will remain, // but show the field as missing until manually corrected to something else. // Need to rebuild course cache to update the info. rebuild_course_cache(0, true); // Prior to the delete, pull the record for the event. $field = $DB->get_record('user_info_field', array('id' => $id)); // Try to remove the record from the database. $DB->delete_records('user_info_field', array('id' => $id)); \core\event\user_info_field_deleted::create_from_field($field)->trigger(); profile_purge_user_fields_cache(); // Reorder the remaining fields in the same category. profile_reorder_fields(); } /** * Change the sort order of a field * * @param int $id of the field * @param string $move direction of move * @return bool success of operation */ function profile_move_field($id, $move) { global $DB; // Get the field object. if (!$field = $DB->get_record('user_info_field', array('id' => $id))) { return false; } // Count the number of fields in this category. $fieldcount = $DB->count_records('user_info_field', array('categoryid' => $field->categoryid)); // Calculate the new sortorder. if ( ($move == 'up') and ($field->sortorder > 1)) { $neworder = $field->sortorder - 1; } else if (($move == 'down') and ($field->sortorder < $fieldcount)) { $neworder = $field->sortorder + 1; } else { return false; } // Retrieve the field object that is currently residing in the new position. $params = array('categoryid' => $field->categoryid, 'sortorder' => $neworder); if ($swapfield = $DB->get_record('user_info_field', $params)) { // Swap the sortorders. $swapfield->sortorder = $field->sortorder; $field->sortorder = $neworder; // Update the field records. $DB->update_record('user_info_field', $field); $DB->update_record('user_info_field', $swapfield); \core\event\user_info_field_updated::create_from_field($field)->trigger(); \core\event\user_info_field_updated::create_from_field($swapfield)->trigger(); } profile_reorder_fields(); return true; } /** * Change the sort order of a category. * * @param int $id of the category * @param string $move direction of move * @return bool success of operation */ function profile_move_category($id, $move) { global $DB; // Get the category object. if (!($category = $DB->get_record('user_info_category', array('id' => $id)))) { return false; } // Count the number of categories. $categorycount = $DB->count_records('user_info_category'); // Calculate the new sortorder. if (($move == 'up') and ($category->sortorder > 1)) { $neworder = $category->sortorder - 1; } else if (($move == 'down') and ($category->sortorder < $categorycount)) { $neworder = $category->sortorder + 1; } else { return false; } // Retrieve the category object that is currently residing in the new position. if ($swapcategory = $DB->get_record('user_info_category', array('sortorder' => $neworder))) { // Swap the sortorders. $swapcategory->sortorder = $category->sortorder; $category->sortorder = $neworder; // Update the category records. $DB->update_record('user_info_category', $category); $DB->update_record('user_info_category', $swapcategory); \core\event\user_info_category_updated::create_from_category($category)->trigger(); \core\event\user_info_category_updated::create_from_category($swapcategory)->trigger(); profile_purge_user_fields_cache(); return true; } return false; } /** * Retrieve a list of all the available data types * @return array a list of the datatypes suitable to use in a select statement */ function profile_list_datatypes() { $datatypes = array(); $plugins = core_component::get_plugin_list('profilefield'); foreach ($plugins as $type => $unused) { $datatypes[$type] = get_string('pluginname', 'profilefield_'.$type); } asort($datatypes); return $datatypes; } /** * Retrieve a list of categories and ids suitable for use in a form * @return array */ function profile_list_categories() { global $DB; $categories = $DB->get_records_menu('user_info_category', null, 'sortorder ASC', 'id, name'); return array_map('format_string', $categories); } /** * Create or update a profile category * * @param stdClass $data */ function profile_save_category(stdClass $data): void { global $DB; if (empty($data->id)) { unset($data->id); $data->sortorder = $DB->count_records('user_info_category') + 1; $data->id = $DB->insert_record('user_info_category', $data, true); $createdcategory = $DB->get_record('user_info_category', array('id' => $data->id)); \core\event\user_info_category_created::create_from_category($createdcategory)->trigger(); } else { $DB->update_record('user_info_category', $data); $updatedcateogry = $DB->get_record('user_info_category', array('id' => $data->id)); \core\event\user_info_category_updated::create_from_category($updatedcateogry)->trigger(); } profile_reorder_categories(); profile_purge_user_fields_cache(); } /** * Save updated field definition or create a new field * * @param stdClass $data data from the form profile_field_form * @param array $editors editors for this form field type */ function profile_save_field(stdClass $data, array $editors): void { global $CFG; require_once($CFG->dirroot.'/user/profile/field/'.$data->datatype.'/define.class.php'); $newfield = 'profile_define_'.$data->datatype; /** @var profile_define_base $formfield */ $formfield = new $newfield(); // Collect the description and format back into the proper data structure from the editor. // Note: This field will ALWAYS be an editor. $data->descriptionformat = $data->description['format']; $data->description = $data->description['text']; // Check whether the default data is an editor, this is (currently) only the textarea field type. if (is_array($data->defaultdata) && array_key_exists('text', $data->defaultdata)) { // Collect the default data and format back into the proper data structure from the editor. $data->defaultdataformat = $data->defaultdata['format']; $data->defaultdata = $data->defaultdata['text']; } // Convert the data format for. if (is_array($editors)) { foreach ($editors as $editor) { if (isset($field->$editor)) { $field->{$editor.'format'} = $field->{$editor}['format']; $field->$editor = $field->{$editor}['text']; } } } $formfield->define_save($data); profile_reorder_fields(); profile_reorder_categories(); } /** * Purge the cache for the user profile fields */ function profile_purge_user_fields_cache() { $cache = \cache::make_from_params(cache_store::MODE_REQUEST, 'core_profile', 'customfields', [], ['simplekeys' => true, 'simpledata' => true]); $cache->purge(); } field/menu/lang/en/profilefield_menu.php000064400000003013152266356300014324 0ustar00. /** * Strings for component 'profilefield_menu', language 'en', branch 'MOODLE_20_STABLE' * * @package profilefield_menu * @copyright 1999 onwards Martin Dougiamas {@link http://moodle.com} * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ $string['pluginname'] = 'Drop-down menu'; $string['privacy:metadata:profilefield_menu:userid'] = 'The ID of the user whose data is stored by the drop-down menu user profile field'; $string['privacy:metadata:profilefield_menu:fieldid'] = 'The ID of the profile field'; $string['privacy:metadata:profilefield_menu:data'] = 'Drop-down menu user profile field user data'; $string['privacy:metadata:profilefield_menu:dataformat'] = 'The format of the drop-down menu user profile field user data'; $string['privacy:metadata:profilefield_menu:tableexplanation'] = 'Additional profile data'; field/menu/version.php000064400000002273152266356300011005 0ustar00. /** * Menu profile field version information. * * @package profilefield_menu * @copyright 2007 onwards Shane Elliot {@link http://pukunui.com} * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); $plugin->version = 2025041400; // The current plugin version (Date: YYYYMMDDXX). $plugin->requires = 2025040800; // Requires this Moodle version. $plugin->component = 'profilefield_menu'; // Full name of the plugin (used for diagnostics) field/menu/classes/privacy/provider.php000064400000017211152266356300014262 0ustar00. /** * Privacy class for requesting user data. * * @package profilefield_menu * @copyright 2018 Mihail Geshoski * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace profilefield_menu\privacy; defined('MOODLE_INTERNAL') || die(); use \core_privacy\local\metadata\collection; use \core_privacy\local\request\contextlist; use \core_privacy\local\request\approved_contextlist; use core_privacy\local\request\userlist; use core_privacy\local\request\approved_userlist; /** * Privacy class for requesting user data. * * @copyright 2018 Mihail Geshoski * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class provider implements \core_privacy\local\metadata\provider, \core_privacy\local\request\core_userlist_provider, \core_privacy\local\request\plugin\provider { /** * Returns meta data about this system. * * @param collection $collection The initialised collection to add items to. * @return collection A listing of user data stored through this system. */ public static function get_metadata(collection $collection): collection { return $collection->add_database_table('user_info_data', [ 'userid' => 'privacy:metadata:profilefield_menu:userid', 'fieldid' => 'privacy:metadata:profilefield_menu:fieldid', 'data' => 'privacy:metadata:profilefield_menu:data', 'dataformat' => 'privacy:metadata:profilefield_menu:dataformat' ], 'privacy:metadata:profilefield_menu:tableexplanation'); } /** * Get the list of contexts that contain user information for the specified user. * * @param int $userid The user to search. * @return contextlist $contextlist The contextlist containing the list of contexts used in this plugin. */ public static function get_contexts_for_userid(int $userid): contextlist { $sql = "SELECT ctx.id FROM {user_info_data} uda JOIN {user_info_field} uif ON uda.fieldid = uif.id JOIN {context} ctx ON ctx.instanceid = uda.userid AND ctx.contextlevel = :contextlevel WHERE uda.userid = :userid AND uif.datatype = :datatype"; $params = [ 'userid' => $userid, 'contextlevel' => CONTEXT_USER, 'datatype' => 'menu' ]; $contextlist = new contextlist(); $contextlist->add_from_sql($sql, $params); return $contextlist; } /** * Get the list of users within a specific context. * * @param userlist $userlist The userlist containing the list of users who have data in this context/plugin combination. */ public static function get_users_in_context(userlist $userlist) { $context = $userlist->get_context(); if (!$context instanceof \context_user) { return; } $sql = "SELECT uda.userid FROM {user_info_data} uda JOIN {user_info_field} uif ON uda.fieldid = uif.id WHERE uda.userid = :userid AND uif.datatype = :datatype"; $params = [ 'userid' => $context->instanceid, 'datatype' => 'menu' ]; $userlist->add_from_sql('userid', $sql, $params); } /** * Export all user data for the specified user, in the specified contexts. * * @param approved_contextlist $contextlist The approved contexts to export information for. */ public static function export_user_data(approved_contextlist $contextlist) { $user = $contextlist->get_user(); foreach ($contextlist->get_contexts() as $context) { // Check if the context is a user context. if ($context->contextlevel == CONTEXT_USER && $context->instanceid == $user->id) { $results = static::get_records($user->id); foreach ($results as $result) { $data = (object) [ 'name' => $result->name, 'description' => $result->description, 'data' => $result->data ]; \core_privacy\local\request\writer::with_context($context)->export_data([ get_string('pluginname', 'profilefield_menu')], $data); } } } } /** * Delete all user data which matches the specified context. * * @param context $context A user context. */ public static function delete_data_for_all_users_in_context(\context $context) { // Delete data only for user context. if ($context->contextlevel == CONTEXT_USER) { static::delete_data($context->instanceid); } } /** * Delete multiple users within a single context. * * @param approved_userlist $userlist The approved context and user information to delete information for. */ public static function delete_data_for_users(approved_userlist $userlist) { $context = $userlist->get_context(); if ($context instanceof \context_user) { static::delete_data($context->instanceid); } } /** * Delete all user data for the specified user, in the specified contexts. * * @param approved_contextlist $contextlist The approved contexts and user information to delete information for. */ public static function delete_data_for_user(approved_contextlist $contextlist) { $user = $contextlist->get_user(); foreach ($contextlist->get_contexts() as $context) { // Check if the context is a user context. if ($context->contextlevel == CONTEXT_USER && $context->instanceid == $user->id) { static::delete_data($context->instanceid); } } } /** * Delete data related to a userid. * * @param int $userid The user ID */ protected static function delete_data($userid) { global $DB; $params = [ 'userid' => $userid, 'datatype' => 'menu' ]; $DB->delete_records_select('user_info_data', "fieldid IN ( SELECT id FROM {user_info_field} WHERE datatype = :datatype) AND userid = :userid", $params); } /** * Get records related to this plugin and user. * * @param int $userid The user ID * @return array An array of records. */ protected static function get_records($userid) { global $DB; $sql = "SELECT * FROM {user_info_data} uda JOIN {user_info_field} uif ON uda.fieldid = uif.id WHERE uda.userid = :userid AND uif.datatype = :datatype"; $params = [ 'userid' => $userid, 'datatype' => 'menu' ]; return $DB->get_records_sql($sql, $params); } } field/menu/define.class.php000064400000005521152266356300011655 0ustar00. /** * Menu profile field definition. * * @package profilefield_menu * @copyright 2007 onwards Shane Elliot {@link http://pukunui.com} * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ /** * Class profile_define_menu * * @copyright 2007 onwards Shane Elliot {@link http://pukunui.com} * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class profile_define_menu extends profile_define_base { /** * Adds elements to the form for creating/editing this type of profile field. * @param moodleform $form */ public function define_form_specific($form) { // Param 1 for menu type contains the options. $form->addElement('textarea', 'param1', get_string('profilemenuoptions', 'admin'), array('rows' => 6, 'cols' => 40)); $form->setType('param1', PARAM_TEXT); // Default data. $form->addElement('text', 'defaultdata', get_string('profiledefaultdata', 'admin'), 'size="50"'); $form->setType('defaultdata', PARAM_TEXT); } /** * Validates data for the profile field. * * @param array $data * @param array $files * @return array */ public function define_validate_specific($data, $files) { $err = array(); $data->param1 = str_replace("\r", '', $data->param1); // Check that we have at least 2 options. if (($options = explode("\n", $data->param1)) === false) { $err['param1'] = get_string('profilemenunooptions', 'admin'); } else if (count($options) < 2) { $err['param1'] = get_string('profilemenutoofewoptions', 'admin'); } else if (!empty($data->defaultdata) and !in_array($data->defaultdata, $options)) { // Check the default data exists in the options. $err['defaultdata'] = get_string('profilemenudefaultnotinoptions', 'admin'); } return $err; } /** * Processes data before it is saved. * @param array|stdClass $data * @return array|stdClass */ public function define_save_preprocess($data) { $data->param1 = str_replace("\r", '', $data->param1); return $data; } } field/menu/tests/privacy/provider_test.php000064400000031053152266356300015026 0ustar00. /** * Base class for unit tests for profilefield_menu. * * @package profilefield_menu * @copyright 2018 Mihail Geshoski * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace profilefield_menu\privacy; defined('MOODLE_INTERNAL') || die(); use core_privacy\tests\provider_testcase; use profilefield_menu\privacy\provider; use core_privacy\local\request\approved_userlist; /** * Unit tests for user\profile\field\menu\classes\privacy\provider.php * * @copyright 2018 Mihail Geshoski * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ final class provider_test extends provider_testcase { /** * Basic setup for these tests. */ public function setUp(): void { parent::setUp(); $this->resetAfterTest(true); } /** * Test getting the context for the user ID related to this plugin. */ public function test_get_contexts_for_userid(): void { global $DB; // Create profile category. $categoryid = $this->add_profile_category(); // Create profile field. $profilefieldid = $this->add_profile_field($categoryid, 'menu'); // Create a user. $user = $this->getDataGenerator()->create_user(); $this->add_user_info_data($user->id, $profilefieldid, 'test data'); // Get the field that was created. $userfielddata = $DB->get_records('user_info_data', array('userid' => $user->id)); // Confirm we got the right number of user field data. $this->assertCount(1, $userfielddata); $context = \context_user::instance($user->id); $contextlist = provider::get_contexts_for_userid($user->id); $this->assertEquals($context, $contextlist->current()); } /** * Test that data is exported correctly for this plugin. */ public function test_export_user_data(): void { // Create profile category. $categoryid = $this->add_profile_category(); // Create menu profile field. $menuprofilefieldid = $this->add_profile_field($categoryid, 'menu'); // Create checkbox profile field. $checkboxprofilefieldid = $this->add_profile_field($categoryid, 'checkbox'); // Create a user. $user = $this->getDataGenerator()->create_user(); $context = \context_user::instance($user->id); // Add menu user info data. $this->add_user_info_data($user->id, $menuprofilefieldid, 'test menu'); // Add checkbox user info data. $this->add_user_info_data($user->id, $checkboxprofilefieldid, 'test data'); $writer = \core_privacy\local\request\writer::with_context($context); $this->assertFalse($writer->has_any_data()); $this->export_context_data_for_user($user->id, $context, 'profilefield_menu'); $data = $writer->get_data([get_string('pluginname', 'profilefield_menu')]); $this->assertCount(3, (array) $data); $this->assertEquals('Test field', $data->name); $this->assertEquals('This is a test.', $data->description); $this->assertEquals('test menu', $data->data); } /** * Test that user data is deleted using the context. */ public function test_delete_data_for_all_users_in_context(): void { global $DB; // Create profile category. $categoryid = $this->add_profile_category(); // Create menu profile field. $menuprofilefieldid = $this->add_profile_field($categoryid, 'menu'); // Create checkbox profile field. $checkboxprofilefieldid = $this->add_profile_field($categoryid, 'checkbox'); // Create a user. $user = $this->getDataGenerator()->create_user(); $context = \context_user::instance($user->id); // Add menu user info data. $this->add_user_info_data($user->id, $menuprofilefieldid, 'test menu'); // Add checkbox user info data. $this->add_user_info_data($user->id, $checkboxprofilefieldid, 'test data'); // Check that we have two entries. $userinfodata = $DB->get_records('user_info_data', ['userid' => $user->id]); $this->assertCount(2, $userinfodata); provider::delete_data_for_all_users_in_context($context); // Check that the correct profile field has been deleted. $userinfodata = $DB->get_records('user_info_data', ['userid' => $user->id]); $this->assertCount(1, $userinfodata); $this->assertNotEquals('test menu', reset($userinfodata)->data); } /** * Test that user data is deleted for this user. */ public function test_delete_data_for_user(): void { global $DB; // Create profile category. $categoryid = $this->add_profile_category(); // Create menu profile field. $menuprofilefieldid = $this->add_profile_field($categoryid, 'menu'); // Create checkbox profile field. $checkboxprofilefieldid = $this->add_profile_field($categoryid, 'checkbox'); // Create a user. $user = $this->getDataGenerator()->create_user(); $context = \context_user::instance($user->id); // Add menu user info data. $this->add_user_info_data($user->id, $menuprofilefieldid, 'test menu'); // Add checkbox user info data. $this->add_user_info_data($user->id, $checkboxprofilefieldid, 'test data'); // Check that we have two entries. $userinfodata = $DB->get_records('user_info_data', ['userid' => $user->id]); $this->assertCount(2, $userinfodata); $approvedlist = new \core_privacy\local\request\approved_contextlist($user, 'profilefield_menu', [$context->id]); provider::delete_data_for_user($approvedlist); // Check that the correct profile field has been deleted. $userinfodata = $DB->get_records('user_info_data', ['userid' => $user->id]); $this->assertCount(1, $userinfodata); $this->assertNotEquals('test menu', reset($userinfodata)->data); } /** * Test that only users with a user context are fetched. */ public function test_get_users_in_context(): void { $this->resetAfterTest(); $component = 'profilefield_menu'; // Create profile category. $categoryid = $this->add_profile_category(); // Create menu profile field. $profilefieldid = $this->add_profile_field($categoryid, 'menu'); // Create a user. $user = $this->getDataGenerator()->create_user(); $usercontext = \context_user::instance($user->id); // The list of users should not return anything yet (related data still haven't been created). $userlist = new \core_privacy\local\request\userlist($usercontext, $component); provider::get_users_in_context($userlist); $this->assertCount(0, $userlist); $this->add_user_info_data($user->id, $profilefieldid, 'test data'); // The list of users for user context should return the user. provider::get_users_in_context($userlist); $this->assertCount(1, $userlist); $expected = [$user->id]; $actual = $userlist->get_userids(); $this->assertEquals($expected, $actual); // The list of users for system context should not return any users. $systemcontext = \context_system::instance(); $userlist = new \core_privacy\local\request\userlist($systemcontext, $component); provider::get_users_in_context($userlist); $this->assertCount(0, $userlist); } /** * Test that data for users in approved userlist is deleted. */ public function test_delete_data_for_users(): void { $this->resetAfterTest(); $component = 'profilefield_menu'; // Create profile category. $categoryid = $this->add_profile_category(); // Create menu profile field. $profilefieldid = $this->add_profile_field($categoryid, 'menu'); // Create user1. $user1 = $this->getDataGenerator()->create_user(); $usercontext1 = \context_user::instance($user1->id); // Create user2. $user2 = $this->getDataGenerator()->create_user(); $usercontext2 = \context_user::instance($user2->id); $this->add_user_info_data($user1->id, $profilefieldid, 'test data'); $this->add_user_info_data($user2->id, $profilefieldid, 'test data'); // The list of users for usercontext1 should return user1. $userlist1 = new \core_privacy\local\request\userlist($usercontext1, $component); provider::get_users_in_context($userlist1); $this->assertCount(1, $userlist1); $expected = [$user1->id]; $actual = $userlist1->get_userids(); $this->assertEquals($expected, $actual); // The list of users for usercontext2 should return user2. $userlist2 = new \core_privacy\local\request\userlist($usercontext2, $component); provider::get_users_in_context($userlist2); $this->assertCount(1, $userlist2); $expected = [$user2->id]; $actual = $userlist2->get_userids(); $this->assertEquals($expected, $actual); // Add userlist1 to the approved user list. $approvedlist = new approved_userlist($usercontext1, $component, $userlist1->get_userids()); // Delete user data using delete_data_for_user for usercontext1. provider::delete_data_for_users($approvedlist); // Re-fetch users in usercontext1 - The user list should now be empty. $userlist1 = new \core_privacy\local\request\userlist($usercontext1, $component); provider::get_users_in_context($userlist1); $this->assertCount(0, $userlist1); // Re-fetch users in usercontext2 - The user list should not be empty (user2). $userlist2 = new \core_privacy\local\request\userlist($usercontext2, $component); provider::get_users_in_context($userlist2); $this->assertCount(1, $userlist2); // User data should be only removed in the user context. $systemcontext = \context_system::instance(); // Add userlist2 to the approved user list in the system context. $approvedlist = new approved_userlist($systemcontext, $component, $userlist2->get_userids()); // Delete user1 data using delete_data_for_user. provider::delete_data_for_users($approvedlist); // Re-fetch users in usercontext2 - The user list should not be empty (user2). $userlist1 = new \core_privacy\local\request\userlist($usercontext2, $component); provider::get_users_in_context($userlist1); $this->assertCount(1, $userlist1); } /** * Add dummy user info data. * * @param int $userid The ID of the user * @param int $fieldid The ID of the field * @param string $data The data */ private function add_user_info_data($userid, $fieldid, $data) { global $DB; $userinfodata = array( 'userid' => $userid, 'fieldid' => $fieldid, 'data' => $data, 'dataformat' => 0 ); $DB->insert_record('user_info_data', $userinfodata); } /** * Add dummy profile category. * * @return int The ID of the profile category */ private function add_profile_category() { $cat = $this->getDataGenerator()->create_custom_profile_field_category(['name' => 'Test category']); return $cat->id; } /** * Add dummy profile field. * * @param int $categoryid The ID of the profile category * @param string $datatype The datatype of the profile field * @return int The ID of the profile field */ private function add_profile_field($categoryid, $datatype) { $data = $this->getDataGenerator()->create_custom_profile_field([ 'datatype' => $datatype, 'shortname' => 'tstField', 'name' => 'Test field', 'description' => 'This is a test.', 'categoryid' => $categoryid, ]); return $data->id; } } field/menu/field.class.php000064400000014407152266356300011511 0ustar00. /** * Menu profile field. * * @package profilefield_menu * @copyright 2007 onwards Shane Elliot {@link http://pukunui.com} * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ /** * Class profile_field_menu * * @copyright 2007 onwards Shane Elliot {@link http://pukunui.com} * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class profile_field_menu extends profile_field_base { /** @var array $options */ public $options; /** @var int $datakey */ public $datakey; /** * Constructor method. * * Pulls out the options for the menu from the database and sets the the corresponding key for the data if it exists. * * @param int $fieldid * @param int $userid * @param object $fielddata */ public function __construct($fieldid = 0, $userid = 0, $fielddata = null) { // First call parent constructor. parent::__construct($fieldid, $userid, $fielddata); // Param 1 for menu type is the options. if (isset($this->field->param1)) { $options = explode("\n", $this->field->param1); } else { $options = array(); } $this->options = array(); if (!empty($this->field->required)) { $this->options[''] = get_string('choose').'...'; } foreach ($options as $key => $option) { // Multilang formatting with filters. $this->options[$option] = format_string($option, true, ['context' => context_system::instance()]); } // Set the data key. if ($this->data !== null) { $key = $this->data; if (isset($this->options[$key]) || ($key = array_search($key, $this->options)) !== false) { $this->data = $key; $this->datakey = $key; } } } /** * Create the code snippet for this field instance * Overwrites the base class method * @param moodleform $mform Moodle form instance */ public function edit_field_add($mform) { $mform->addElement('select', $this->inputname, format_string($this->field->name), $this->options); } /** * Set the default value for this field instance * Overwrites the base class method. * @param moodleform $mform Moodle form instance */ public function edit_field_set_default($mform) { $key = $this->field->defaultdata; if (isset($this->options[$key]) || ($key = array_search($key, $this->options)) !== false){ $defaultkey = $key; } else { $defaultkey = ''; } $mform->setDefault($this->inputname, $defaultkey); } /** * The data from the form returns the key. * * This should be converted to the respective option string to be saved in database * Overwrites base class accessor method. * * @param mixed $data The key returned from the select input in the form * @param stdClass $datarecord The object that will be used to save the record * @return mixed Data or null */ public function edit_save_data_preprocess($data, $datarecord) { return isset($this->options[$data]) ? $data : null; } /** * When passing the user object to the form class for the edit profile page * we should load the key for the saved data * * Overwrites the base class method. * * @param stdClass $user User object. */ public function edit_load_user_data($user) { $user->{$this->inputname} = $this->datakey; } /** * HardFreeze the field if locked. * @param moodleform $mform instance of the moodleform class */ public function edit_field_set_locked($mform) { if (!$mform->elementExists($this->inputname)) { return; } if ($this->is_locked() and !has_capability('moodle/user:update', context_system::instance())) { $mform->hardFreeze($this->inputname); $mform->setConstant($this->inputname, $this->datakey); } } /** * Convert external data (csv file) from value to key for processing later by edit_save_data_preprocess * * @param string $value one of the values in menu options. * @return int options key for the menu */ public function convert_external_data($value) { if (isset($this->options[$value])) { $retval = $value; } else { $retval = array_search($value, $this->options); } // If value is not found in options then return null, so that it can be handled // later by edit_save_data_preprocess. if ($retval === false) { $retval = null; } return $retval; } /** * Return the field type and null properties. * This will be used for validating the data submitted by a user. * * @return array the param type and null property * @since Moodle 3.2 */ public function get_field_properties() { return array(PARAM_TEXT, NULL_NOT_ALLOWED); } /** * Return the field settings suitable to be exported via an external function. * * @return array all the settings */ public function get_field_config_for_external() { if (isset($this->field->param1) && !empty($this->options)) { // Remove "Choose" option to make it consisten with the rest of the data. if (!empty($this->field->required)) { unset($this->options['']); } $this->field->param2 = implode("\n", array_values($this->options)); } return (array) $this->field; } } field/textarea/lang/en/profilefield_textarea.php000064400000003024152266356300016050 0ustar00. /** * Strings for component 'profilefield_textarea', language 'en', branch 'MOODLE_20_STABLE' * * @package profilefield_textarea * @copyright 1999 onwards Martin Dougiamas {@link http://moodle.com} * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ $string['pluginname'] = 'Text area'; $string['privacy:metadata:profile_field_textarea:userid'] = 'The ID of the user whose data is stored by the Text area user profile field'; $string['privacy:metadata:profile_field_textarea:fieldid'] = 'The ID of the profile field'; $string['privacy:metadata:profile_field_textarea:data'] = 'Text area user profile field user data'; $string['privacy:metadata:profile_field_textarea:dataformat'] = 'The format of Text area user profile field user data'; $string['privacy:metadata:profile_field_textarea:tableexplanation'] = 'Additional profile data'; field/textarea/version.php000064400000002317152266356300011655 0ustar00. /** * Version information for the textarea profile field. * * @package profilefield_textarea * @copyright 2007 onwards Shane Elliot {@link http://pukunui.com} * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); $plugin->version = 2025041400; // The current plugin version (Date: YYYYMMDDXX). $plugin->requires = 2025040800; // Requires this Moodle version. $plugin->component = 'profilefield_textarea'; // Full name of the plugin (used for diagnostics) field/textarea/classes/privacy/provider.php000064400000017274152266356300015144 0ustar00. /** * Privacy class for requesting user data. * * @package profilefield_textarea * @copyright 2018 Mihail Geshoski * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace profilefield_textarea\privacy; defined('MOODLE_INTERNAL') || die(); use \core_privacy\local\metadata\collection; use \core_privacy\local\request\contextlist; use \core_privacy\local\request\approved_contextlist; use core_privacy\local\request\userlist; use core_privacy\local\request\approved_userlist; /** * Privacy class for requesting user data. * * @copyright 2018 Mihail Geshoski * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class provider implements \core_privacy\local\metadata\provider, \core_privacy\local\request\core_userlist_provider, \core_privacy\local\request\plugin\provider { /** * Returns meta data about this system. * * @param collection $collection The initialised collection to add items to. * @return collection A listing of user data stored through this system. */ public static function get_metadata(collection $collection): collection { return $collection->add_database_table('user_info_data', [ 'userid' => 'privacy:metadata:profile_field_textarea:userid', 'fieldid' => 'privacy:metadata:profile_field_textarea:fieldid', 'data' => 'privacy:metadata:profile_field_textarea:data', 'dataformat' => 'privacy:metadata:profile_field_textarea:dataformat' ], 'privacy:metadata:profile_field_textarea:tableexplanation'); } /** * Get the list of contexts that contain user information for the specified user. * * @param int $userid The user to search. * @return contextlist $contextlist The contextlist containing the list of contexts used in this plugin. */ public static function get_contexts_for_userid(int $userid): contextlist { $sql = "SELECT ctx.id FROM {user_info_data} uda JOIN {user_info_field} uif ON uda.fieldid = uif.id JOIN {context} ctx ON ctx.instanceid = uda.userid AND ctx.contextlevel = :contextlevel WHERE uda.userid = :userid AND uif.datatype = :datatype"; $params = [ 'userid' => $userid, 'contextlevel' => CONTEXT_USER, 'datatype' => 'textarea' ]; $contextlist = new contextlist(); $contextlist->add_from_sql($sql, $params); return $contextlist; } /** * Get the list of users within a specific context. * * @param userlist $userlist The userlist containing the list of users who have data in this context/plugin combination. */ public static function get_users_in_context(userlist $userlist) { $context = $userlist->get_context(); if (!$context instanceof \context_user) { return; } $sql = "SELECT uda.userid FROM {user_info_data} uda JOIN {user_info_field} uif ON uda.fieldid = uif.id WHERE uda.userid = :userid AND uif.datatype = :datatype"; $params = [ 'userid' => $context->instanceid, 'datatype' => 'textarea' ]; $userlist->add_from_sql('userid', $sql, $params); } /** * Export all user data for the specified user, in the specified contexts. * * @param approved_contextlist $contextlist The approved contexts to export information for. */ public static function export_user_data(approved_contextlist $contextlist) { $user = $contextlist->get_user(); foreach ($contextlist->get_contexts() as $context) { // Check if the context is a user context. if ($context->contextlevel == CONTEXT_USER && $context->instanceid == $user->id) { $results = static::get_records($user->id); foreach ($results as $result) { $data = (object) [ 'name' => $result->name, 'description' => $result->description, 'data' => $result->data ]; \core_privacy\local\request\writer::with_context($context)->export_data([ get_string('pluginname', 'profilefield_textarea')], $data); } } } } /** * Delete all user data which matches the specified context. * * @param context $context A user context. */ public static function delete_data_for_all_users_in_context(\context $context) { // Delete data only for user context. if ($context->contextlevel == CONTEXT_USER) { static::delete_data($context->instanceid); } } /** * Delete multiple users within a single context. * * @param approved_userlist $userlist The approved context and user information to delete information for. */ public static function delete_data_for_users(approved_userlist $userlist) { $context = $userlist->get_context(); if ($context instanceof \context_user) { static::delete_data($context->instanceid); } } /** * Delete all user data for the specified user, in the specified contexts. * * @param approved_contextlist $contextlist The approved contexts and user information to delete information for. */ public static function delete_data_for_user(approved_contextlist $contextlist) { $user = $contextlist->get_user(); foreach ($contextlist->get_contexts() as $context) { // Check if the context is a user context. if ($context->contextlevel == CONTEXT_USER && $context->instanceid == $user->id) { static::delete_data($context->instanceid); } } } /** * Delete data related to a userid. * * @param int $userid The user ID */ protected static function delete_data($userid) { global $DB; $params = [ 'userid' => $userid, 'datatype' => 'textarea' ]; $DB->delete_records_select('user_info_data', "fieldid IN ( SELECT id FROM {user_info_field} WHERE datatype = :datatype) AND userid = :userid", $params); } /** * Get records related to this plugin and user. * * @param int $userid The user ID * @return array An array of records. */ protected static function get_records($userid) { global $DB; $sql = "SELECT * FROM {user_info_data} uda JOIN {user_info_field} uif ON uda.fieldid = uif.id WHERE uda.userid = :userid AND uif.datatype = :datatype"; $params = [ 'userid' => $userid, 'datatype' => 'textarea' ]; return $DB->get_records_sql($sql, $params); } } field/textarea/define.class.php000064400000003360152266356300012525 0ustar00. /** * Textarea profile field define. * * @package profilefield_textarea * @copyright 2007 onwards Shane Elliot {@link http://pukunui.com} * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ /** * Class profile_define_textarea. * * @copyright 2007 onwards Shane Elliot {@link http://pukunui.com} * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class profile_define_textarea extends profile_define_base { /** * Add elements for creating/editing a textarea profile field. * @param moodleform $form */ public function define_form_specific($form) { // Default data. $form->addElement('editor', 'defaultdata', get_string('profiledefaultdata', 'admin')); $form->setType('defaultdata', PARAM_RAW); // We have to trust person with capability to edit this default description. } /** * Returns an array of editors used when defining this type of profile field. * @return array */ public function define_editors() { return array('defaultdata'); } }field/textarea/tests/privacy/provider_test.php000064400000031250152266356300015676 0ustar00. /** * Base class for unit tests for profilefield_textarea. * * @package profilefield_textarea * @copyright 2018 Mihail Geshoski * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace profilefield_textarea\privacy; defined('MOODLE_INTERNAL') || die(); use core_privacy\tests\provider_testcase; use profilefield_textarea\privacy\provider; use core_privacy\local\request\approved_userlist; /** * Unit tests for user\profile\field\textarea\classes\privacy\provider.php * * @copyright 2018 Mihail Geshoski * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ final class provider_test extends provider_testcase { /** * Basic setup for these tests. */ public function setUp(): void { parent::setUp(); $this->resetAfterTest(true); } /** * Test getting the context for the user ID related to this plugin. */ public function test_get_contexts_for_userid(): void { global $DB; // Create profile category. $categoryid = $this->add_profile_category(); // Create profile field. $profilefieldid = $this->add_profile_field($categoryid, 'textarea'); // Create a user. $user = $this->getDataGenerator()->create_user(); $this->add_user_info_data($user->id, $profilefieldid, 'test data'); // Get the field that was created. $userfielddata = $DB->get_records('user_info_data', array('userid' => $user->id)); // Confirm we got the right number of user field data. $this->assertCount(1, $userfielddata); $context = \context_user::instance($user->id); $contextlist = provider::get_contexts_for_userid($user->id); $this->assertEquals($context, $contextlist->current()); } /** * Test that data is exported correctly for this plugin. */ public function test_export_user_data(): void { // Create profile category. $categoryid = $this->add_profile_category(); // Create textarea profile field. $textareaprofilefieldid = $this->add_profile_field($categoryid, 'textarea'); // Create checkbox profile field. $checkboxprofilefieldid = $this->add_profile_field($categoryid, 'checkbox'); // Create a user. $user = $this->getDataGenerator()->create_user(); $context = \context_user::instance($user->id); // Add textarea user info data. $this->add_user_info_data($user->id, $textareaprofilefieldid, 'test textarea'); // Add checkbox user info data. $this->add_user_info_data($user->id, $checkboxprofilefieldid, 'test data'); $writer = \core_privacy\local\request\writer::with_context($context); $this->assertFalse($writer->has_any_data()); $this->export_context_data_for_user($user->id, $context, 'profilefield_textarea'); $data = $writer->get_data([get_string('pluginname', 'profilefield_textarea')]); $this->assertCount(3, (array) $data); $this->assertEquals('Test field', $data->name); $this->assertEquals('This is a test.', $data->description); $this->assertEquals('test textarea', $data->data); } /** * Test that user data is deleted using the context. */ public function test_delete_data_for_all_users_in_context(): void { global $DB; // Create profile category. $categoryid = $this->add_profile_category(); // Create textarea profile field. $textareaprofilefieldid = $this->add_profile_field($categoryid, 'textarea'); // Create checkbox profile field. $checkboxprofilefieldid = $this->add_profile_field($categoryid, 'checkbox'); // Create a user. $user = $this->getDataGenerator()->create_user(); $context = \context_user::instance($user->id); // Add textarea user info data. $this->add_user_info_data($user->id, $textareaprofilefieldid, 'test textarea'); // Add checkbox user info data. $this->add_user_info_data($user->id, $checkboxprofilefieldid, 'test data'); // Check that we have two entries. $userinfodata = $DB->get_records('user_info_data', ['userid' => $user->id]); $this->assertCount(2, $userinfodata); provider::delete_data_for_all_users_in_context($context); // Check that the correct profile field has been deleted. $userinfodata = $DB->get_records('user_info_data', ['userid' => $user->id]); $this->assertCount(1, $userinfodata); $this->assertNotEquals('test textarea', reset($userinfodata)->data); } /** * Test that user data is deleted for this user. */ public function test_delete_data_for_user(): void { global $DB; // Create profile category. $categoryid = $this->add_profile_category(); // Create textarea profile field. $textareaprofilefieldid = $this->add_profile_field($categoryid, 'textarea'); // Create checkbox profile field. $checkboxprofilefieldid = $this->add_profile_field($categoryid, 'checkbox'); // Create a user. $user = $this->getDataGenerator()->create_user(); $context = \context_user::instance($user->id); // Add textarea user info data. $this->add_user_info_data($user->id, $textareaprofilefieldid, 'test textarea'); // Add checkbox user info data. $this->add_user_info_data($user->id, $checkboxprofilefieldid, 'test data'); // Check that we have two entries. $userinfodata = $DB->get_records('user_info_data', ['userid' => $user->id]); $this->assertCount(2, $userinfodata); $approvedlist = new \core_privacy\local\request\approved_contextlist($user, 'profilefield_textarea', [$context->id]); provider::delete_data_for_user($approvedlist); // Check that the correct profile field has been deleted. $userinfodata = $DB->get_records('user_info_data', ['userid' => $user->id]); $this->assertCount(1, $userinfodata); $this->assertNotEquals('test textarea', reset($userinfodata)->data); } /** * Test that only users with a user context are fetched. */ public function test_get_users_in_context(): void { $this->resetAfterTest(); $component = 'profilefield_textarea'; // Create profile category. $categoryid = $this->add_profile_category(); // Create profile field. $profilefieldid = $this->add_profile_field($categoryid, 'textarea'); // Create a user. $user = $this->getDataGenerator()->create_user(); $usercontext = \context_user::instance($user->id); // The list of users should not return anything yet (related data still haven't been created). $userlist = new \core_privacy\local\request\userlist($usercontext, $component); provider::get_users_in_context($userlist); $this->assertCount(0, $userlist); $this->add_user_info_data($user->id, $profilefieldid, 'test data'); // The list of users for user context should return the user. provider::get_users_in_context($userlist); $this->assertCount(1, $userlist); $expected = [$user->id]; $actual = $userlist->get_userids(); $this->assertEquals($expected, $actual); // The list of users for system context should not return any users. $systemcontext = \context_system::instance(); $userlist = new \core_privacy\local\request\userlist($systemcontext, $component); provider::get_users_in_context($userlist); $this->assertCount(0, $userlist); } /** * Test that data for users in approved userlist is deleted. */ public function test_delete_data_for_users(): void { $this->resetAfterTest(); $component = 'profilefield_textarea'; // Create profile category. $categoryid = $this->add_profile_category(); // Create profile field. $profilefieldid = $this->add_profile_field($categoryid, 'textarea'); // Create user1. $user1 = $this->getDataGenerator()->create_user(); $usercontext1 = \context_user::instance($user1->id); // Create user2. $user2 = $this->getDataGenerator()->create_user(); $usercontext2 = \context_user::instance($user2->id); $this->add_user_info_data($user1->id, $profilefieldid, 'test data'); $this->add_user_info_data($user2->id, $profilefieldid, 'test data'); // The list of users for usercontext1 should return user1. $userlist1 = new \core_privacy\local\request\userlist($usercontext1, $component); provider::get_users_in_context($userlist1); $this->assertCount(1, $userlist1); $expected = [$user1->id]; $actual = $userlist1->get_userids(); $this->assertEquals($expected, $actual); // The list of users for usercontext2 should return user2. $userlist2 = new \core_privacy\local\request\userlist($usercontext2, $component); provider::get_users_in_context($userlist2); $this->assertCount(1, $userlist2); $expected = [$user2->id]; $actual = $userlist2->get_userids(); $this->assertEquals($expected, $actual); // Add userlist1 to the approved user list. $approvedlist = new approved_userlist($usercontext1, $component, $userlist1->get_userids()); // Delete user data using delete_data_for_user for usercontext1. provider::delete_data_for_users($approvedlist); // Re-fetch users in usercontext1 - The user list should now be empty. $userlist1 = new \core_privacy\local\request\userlist($usercontext1, $component); provider::get_users_in_context($userlist1); $this->assertCount(0, $userlist1); // Re-fetch users in usercontext2 - The user list should not be empty (user2). $userlist2 = new \core_privacy\local\request\userlist($usercontext2, $component); provider::get_users_in_context($userlist2); $this->assertCount(1, $userlist2); // User data should be only removed in the user context. $systemcontext = \context_system::instance(); // Add userlist2 to the approved user list in the system context. $approvedlist = new approved_userlist($systemcontext, $component, $userlist2->get_userids()); // Delete user1 data using delete_data_for_user. provider::delete_data_for_users($approvedlist); // Re-fetch users in usercontext2 - The user list should not be empty (user2). $userlist1 = new \core_privacy\local\request\userlist($usercontext2, $component); provider::get_users_in_context($userlist1); $this->assertCount(1, $userlist1); } /** * Add dummy user info data. * * @param int $userid The ID of the user * @param int $fieldid The ID of the field * @param string $data The data */ private function add_user_info_data($userid, $fieldid, $data) { global $DB; $userinfodata = array( 'userid' => $userid, 'fieldid' => $fieldid, 'data' => $data, 'dataformat' => 0 ); $DB->insert_record('user_info_data', $userinfodata); } /** * Add dummy profile category. * * @return int The ID of the profile category */ private function add_profile_category() { $cat = $this->getDataGenerator()->create_custom_profile_field_category(['name' => 'Test category']); return $cat->id; } /** * Add dummy profile field. * * @param int $categoryid The ID of the profile category * @param string $datatype The datatype of the profile field * @return int The ID of the profile field */ private function add_profile_field($categoryid, $datatype) { $data = $this->getDataGenerator()->create_custom_profile_field([ 'datatype' => $datatype, 'shortname' => 'tstField', 'name' => 'Test field', 'description' => 'This is a test.', 'categoryid' => $categoryid, ]); return $data->id; } } field/textarea/field.class.php000064400000006002152266356300012352 0ustar00. /** * Textarea profile field define. * * @package profilefield_textarea * @copyright 2007 onwards Shane Elliot {@link http://pukunui.com} * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ /** * Class profile_field_textarea. * * @copyright 2007 onwards Shane Elliot {@link http://pukunui.com} * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class profile_field_textarea extends profile_field_base { /** * Adds elements for this field type to the edit form. * @param moodleform $mform */ public function edit_field_add($mform) { // Create the form field. $mform->addElement('editor', $this->inputname, format_string($this->field->name), null, null); $mform->setType($this->inputname, PARAM_RAW); // We MUST clean this before display! } /** * Overwrite base class method, data in this field type is potentially too large to be included in the user object. * @return bool */ public function is_user_object_data() { return false; } /** * Process incoming data for the field. * @param stdClass $data * @param stdClass $datarecord * @return mixed|stdClass */ public function edit_save_data_preprocess($data, $datarecord) { if (is_array($data)) { $datarecord->dataformat = $data['format']; $data = $data['text']; } return $data; } /** * Load user data for this profile field, ready for editing. * @param stdClass $user */ public function edit_load_user_data($user) { if ($this->data !== null) { $this->data = clean_text($this->data, $this->dataformat); $user->{$this->inputname} = array('text' => $this->data, 'format' => $this->dataformat); } } /** * Display the data for this field * @return string */ public function display_data() { return format_text($this->data, $this->dataformat, array('overflowdiv' => true)); } /** * Return the field type and null properties. * This will be used for validating the data submitted by a user. * * @return array the param type and null property * @since Moodle 3.2 */ public function get_field_properties() { return array(PARAM_RAW, NULL_NOT_ALLOWED); } } field/datetime/lang/en/profilefield_datetime.php000064400000003533152266356300016013 0ustar00. /** * The english language pack used in this profile field type. * * @package profilefield_datetime * @copyright 2010 Mark Nelson * @license http://www.gnu.org/copyleft/gpl.html GNU Public License */ $string['currentdatedefault'] = 'Check to use current date as default'; $string['defaultdate'] = 'Default date'; $string['endyear'] = 'End year'; $string['notset'] = 'Not set'; $string['pluginname'] = 'Date/Time'; $string['privacy:metadata:profilefield_datetime:userid'] = 'The ID of the user whose data is stored by the Date/time user profile field'; $string['privacy:metadata:profilefield_datetime:fieldid'] = 'The ID of the profile field'; $string['privacy:metadata:profilefield_datetime:data'] = 'Date/time user profile field user data'; $string['privacy:metadata:profilefield_datetime:dataformat'] = 'The format of Date/time user profile field user data'; $string['privacy:metadata:profilefield_datetime:tableexplanation'] = 'Additional profile data'; $string['specifydatedefault'] = 'or specify a date'; $string['startyearafterend'] = 'The start year can\'t occur after the end year'; $string['startyear'] = 'Start year'; $string['wanttime'] = 'Include time?'; field/datetime/version.php000064400000002256152266356300011636 0ustar00. /** * Version information for the datetime field. * * @package profilefield_datetime * @copyright 2010 Mark Nelson * @license http://www.gnu.org/copyleft/gpl.html GNU Public License */ defined('MOODLE_INTERNAL') || die(); $plugin->version = 2025041400; // The current plugin version (Date: YYYYMMDDXX). $plugin->requires = 2025040800; // Requires this Moodle version. $plugin->component = 'profilefield_datetime'; // Full name of the plugin (used for diagnostics) field/datetime/classes/privacy/provider.php000064400000017401152266356300015113 0ustar00. /** * Privacy class for requesting user data. * * @package profilefield_datetime * @copyright 2018 Mihail Geshoski * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace profilefield_datetime\privacy; defined('MOODLE_INTERNAL') || die(); use \core_privacy\local\metadata\collection; use \core_privacy\local\request\contextlist; use \core_privacy\local\request\approved_contextlist; use \core_privacy\local\request\transform; use core_privacy\local\request\userlist; use core_privacy\local\request\approved_userlist; /** * Privacy class for requesting user data. * * @copyright 2018 Mihail Geshoski * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class provider implements \core_privacy\local\metadata\provider, \core_privacy\local\request\core_userlist_provider, \core_privacy\local\request\plugin\provider { /** * Returns meta data about this system. * * @param collection $collection The initialised collection to add items to. * @return collection A listing of user data stored through this system. */ public static function get_metadata(collection $collection): collection { return $collection->add_database_table('user_info_data', [ 'userid' => 'privacy:metadata:profilefield_datetime:userid', 'fieldid' => 'privacy:metadata:profilefield_datetime:fieldid', 'data' => 'privacy:metadata:profilefield_datetime:data', 'dataformat' => 'privacy:metadata:profilefield_datetime:dataformat' ], 'privacy:metadata:profilefield_datetime:tableexplanation'); } /** * Get the list of contexts that contain user information for the specified user. * * @param int $userid The user to search. * @return contextlist $contextlist The contextlist containing the list of contexts used in this plugin. */ public static function get_contexts_for_userid(int $userid): contextlist { $sql = "SELECT ctx.id FROM {user_info_data} uda JOIN {user_info_field} uif ON uda.fieldid = uif.id JOIN {context} ctx ON ctx.instanceid = uda.userid AND ctx.contextlevel = :contextlevel WHERE uda.userid = :userid AND uif.datatype = :datatype"; $params = [ 'userid' => $userid, 'contextlevel' => CONTEXT_USER, 'datatype' => 'datetime' ]; $contextlist = new contextlist(); $contextlist->add_from_sql($sql, $params); return $contextlist; } /** * Get the list of users within a specific context. * * @param userlist $userlist The userlist containing the list of users who have data in this context/plugin combination. */ public static function get_users_in_context(userlist $userlist) { $context = $userlist->get_context(); if (!$context instanceof \context_user) { return; } $sql = "SELECT uda.userid FROM {user_info_data} uda JOIN {user_info_field} uif ON uda.fieldid = uif.id WHERE uda.userid = :userid AND uif.datatype = :datatype"; $params = [ 'userid' => $context->instanceid, 'datatype' => 'datetime' ]; $userlist->add_from_sql('userid', $sql, $params); } /** * Export all user data for the specified user, in the specified contexts. * * @param approved_contextlist $contextlist The approved contexts to export information for. */ public static function export_user_data(approved_contextlist $contextlist) { $user = $contextlist->get_user(); foreach ($contextlist->get_contexts() as $context) { // Check if the context is a user context. if ($context->contextlevel == CONTEXT_USER && $context->instanceid == $user->id) { $results = static::get_records($user->id); foreach ($results as $result) { $data = (object) [ 'name' => $result->name, 'description' => $result->description, 'data' => transform::date($result->data) ]; \core_privacy\local\request\writer::with_context($context)->export_data([ get_string('pluginname', 'profilefield_datetime')], $data); } } } } /** * Delete all user data which matches the specified context. * * @param context $context A user context. */ public static function delete_data_for_all_users_in_context(\context $context) { // Delete data only for user context. if ($context->contextlevel == CONTEXT_USER) { static::delete_data($context->instanceid); } } /** * Delete multiple users within a single context. * * @param approved_userlist $userlist The approved context and user information to delete information for. */ public static function delete_data_for_users(approved_userlist $userlist) { $context = $userlist->get_context(); if ($context instanceof \context_user) { static::delete_data($context->instanceid); } } /** * Delete all user data for the specified user, in the specified contexts. * * @param approved_contextlist $contextlist The approved contexts and user information to delete information for. */ public static function delete_data_for_user(approved_contextlist $contextlist) { $user = $contextlist->get_user(); foreach ($contextlist->get_contexts() as $context) { // Check if the context is a user context. if ($context->contextlevel == CONTEXT_USER && $context->instanceid == $user->id) { static::delete_data($context->instanceid); } } } /** * Delete data related to a userid. * * @param int $userid The user ID */ protected static function delete_data($userid) { global $DB; $params = [ 'userid' => $userid, 'datatype' => 'datetime' ]; $DB->delete_records_select('user_info_data', "fieldid IN ( SELECT id FROM {user_info_field} WHERE datatype = :datatype) AND userid = :userid", $params); } /** * Get records related to this plugin and user. * * @param int $userid The user ID * @return array An array of records. */ protected static function get_records($userid) { global $DB; $sql = "SELECT * FROM {user_info_data} uda JOIN {user_info_field} uif ON uda.fieldid = uif.id WHERE uda.userid = :userid AND uif.datatype = :datatype"; $params = [ 'userid' => $userid, 'datatype' => 'datetime' ]; return $DB->get_records_sql($sql, $params); } } field/datetime/define.class.php000064400000015404152266356300012506 0ustar00. /** * This file contains the datetime profile field definition class. * * @package profilefield_datetime * @copyright 2010 Mark Nelson * @license http://www.gnu.org/copyleft/gpl.html GNU Public License */ /** * Define datetime fields. * * @copyright 2010 Mark Nelson * @license http://www.gnu.org/copyleft/gpl.html GNU Public License */ class profile_define_datetime extends profile_define_base { /** * Define the setting for a datetime custom field. * * @param moodleform $form the user form */ public function define_form_specific($form) { // Get the current calendar in use - see MDL-18375. $calendartype = \core_calendar\type_factory::get_calendar_instance(); // Create variables to store start and end. list($year, $month, $day) = explode('_', date('Y_m_d')); $currentdate = $calendartype->convert_from_gregorian($year, $month, $day); $currentyear = $currentdate['year']; $arryears = $calendartype->get_years(); // Add elements. $form->addElement('select', 'param1', get_string('startyear', 'profilefield_datetime'), $arryears); $form->setType('param1', PARAM_INT); $form->setDefault('param1', $currentyear); $form->addElement('select', 'param2', get_string('endyear', 'profilefield_datetime'), $arryears); $form->setType('param2', PARAM_INT); $form->setDefault('param2', $currentyear); $form->addElement('checkbox', 'param3', get_string('wanttime', 'profilefield_datetime')); $form->setType('param3', PARAM_INT); $form->addElement('hidden', 'startday', '1'); $form->setType('startday', PARAM_INT); $form->addElement('hidden', 'startmonth', '1'); $form->setType('startmonth', PARAM_INT); $form->addElement('hidden', 'startyear', '1'); $form->setType('startyear', PARAM_INT); $form->addElement('hidden', 'endday', '1'); $form->setType('endday', PARAM_INT); $form->addElement('hidden', 'endmonth', '1'); $form->setType('endmonth', PARAM_INT); $form->addElement('hidden', 'endyear', '1'); $form->setType('endyear', PARAM_INT); $form->addElement('hidden', 'defaultdata', '0'); $form->setType('defaultdata', PARAM_INT); } /** * Validate the data from the profile field form. * * @param stdClass $data from the add/edit profile field form * @param array $files * @return array associative array of error messages */ public function define_validate_specific($data, $files) { $errors = array(); // Make sure the start year is not greater than the end year. if ($data->param1 > $data->param2) { $errors['param1'] = get_string('startyearafterend', 'profilefield_datetime'); } return $errors; } /** * Alter form based on submitted or existing data. * * @param moodleform $mform */ public function define_after_data(&$mform) { global $DB; // If we are adding a new profile field then the dates have already been set // by setDefault to the correct dates in the used calendar system. We only want // to execute the rest of the code when we have the years in the DB saved in // Gregorian that need converting to the date for this user. $id = optional_param('id', 0, PARAM_INT); if ($id === 0) { return; } // Get the field data from the DB. $field = $DB->get_record('user_info_field', array('id' => $id), 'param1, param2', MUST_EXIST); // Get the current calendar in use - see MDL-18375. $calendartype = \core_calendar\type_factory::get_calendar_instance(); // An array to store form values. $values = array(); // The start and end year will be set as a Gregorian year in the DB. We want // convert these to the equivalent year in the current calendar type being used. $startdate = $calendartype->convert_from_gregorian($field->param1, 1, 1); $values['startday'] = $startdate['day']; $values['startmonth'] = $startdate['month']; $values['startyear'] = $startdate['year']; $values['param1'] = $startdate['year']; $stopdate = $calendartype->convert_from_gregorian($field->param2, 1, 1); $values['endday'] = $stopdate['day']; $values['endmonth'] = $stopdate['month']; $values['endyear'] = $stopdate['year']; $values['param2'] = $stopdate['year']; // Set the values. foreach ($values as $key => $value) { $param = $mform->getElement($key); $param->setValue($value); } } /** * Preprocess data from the profile field form before * it is saved. * * @param stdClass $data from the add/edit profile field form * @return stdClass processed data object */ public function define_save_preprocess($data) { // Get the current calendar in use - see MDL-18375. $calendartype = \core_calendar\type_factory::get_calendar_instance(); // Check if the start year was changed, if it was then convert from the start of that year. if ($data->param1 != $data->startyear) { $startdate = $calendartype->convert_to_gregorian($data->param1, 1, 1); } else { $startdate = $calendartype->convert_to_gregorian($data->param1, $data->startmonth, $data->startday); } // Check if the end year was changed, if it was then convert from the start of that year. if ($data->param2 != $data->endyear) { $stopdate = $calendartype->convert_to_gregorian($data->param2, 1, 1); } else { $stopdate = $calendartype->convert_to_gregorian($data->param2, $data->endmonth, $data->endday); } $data->param1 = $startdate['year']; $data->param2 = $stopdate['year']; if (empty($data->param3)) { $data->param3 = null; } // No valid value in the default data column needed. $data->defaultdata = '0'; return $data; } } field/datetime/tests/privacy/provider_test.php000064400000031235152266356300015660 0ustar00. /** * Base class for unit tests for profilefield_datetime. * * @package profilefield_datetime * @copyright 2018 Mihail Geshoski * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace profilefield_datetime\privacy; defined('MOODLE_INTERNAL') || die(); use core_privacy\tests\provider_testcase; use profilefield_datetime\privacy\provider; use core_privacy\local\request\approved_userlist; /** * Unit tests for user\profile\field\datetime\classes\privacy\provider.php * * @copyright 2018 Mihail Geshoski * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ final class provider_test extends provider_testcase { /** * Basic setup for these tests. */ public function setUp(): void { parent::setUp(); $this->resetAfterTest(true); } /** * Test getting the context for the user ID related to this plugin. */ public function test_get_contexts_for_userid(): void { global $DB; // Create profile category. $categoryid = $this->add_profile_category(); // Create profile field. $profilefieldid = $this->add_profile_field($categoryid, 'datetime'); // Create a user. $user = $this->getDataGenerator()->create_user(); $this->add_user_info_data($user->id, $profilefieldid, 'test data'); // Get the field that was created. $userfielddata = $DB->get_records('user_info_data', array('userid' => $user->id)); // Confirm we got the right number of user field data. $this->assertCount(1, $userfielddata); $context = \context_user::instance($user->id); $contextlist = provider::get_contexts_for_userid($user->id); $this->assertEquals($context, $contextlist->current()); } /** * Test that data is exported correctly for this plugin. */ public function test_export_user_data(): void { // Create profile category. $categoryid = $this->add_profile_category(); // Create datetime profile field. $datetimeprofilefieldid = $this->add_profile_field($categoryid, 'datetime'); // Create checkbox profile field. $checkboxprofilefieldid = $this->add_profile_field($categoryid, 'checkbox'); // Create a user. $user = $this->getDataGenerator()->create_user(); $context = \context_user::instance($user->id); // Add datetime user info data. $this->add_user_info_data($user->id, $datetimeprofilefieldid, '1524067200'); // Add checkbox user info data. $this->add_user_info_data($user->id, $checkboxprofilefieldid, 'test data'); $writer = \core_privacy\local\request\writer::with_context($context); $this->assertFalse($writer->has_any_data()); $this->export_context_data_for_user($user->id, $context, 'profilefield_datetime'); $data = $writer->get_data([get_string('pluginname', 'profilefield_datetime')]); $this->assertCount(3, (array) $data); $this->assertEquals('Test field', $data->name); $this->assertEquals('This is a test.', $data->description); $this->assertEquals('19 April 2018', $data->data); } /** * Test that user data is deleted using the context. */ public function test_delete_data_for_all_users_in_context(): void { global $DB; // Create profile category. $categoryid = $this->add_profile_category(); // Create datetime profile field. $datetimeprofilefieldid = $this->add_profile_field($categoryid, 'datetime'); // Create checkbox profile field. $checkboxprofilefieldid = $this->add_profile_field($categoryid, 'checkbox'); // Create a user. $user = $this->getDataGenerator()->create_user(); $context = \context_user::instance($user->id); // Add datetime user info data. $this->add_user_info_data($user->id, $datetimeprofilefieldid, '1524067200'); // Add checkbox user info data. $this->add_user_info_data($user->id, $checkboxprofilefieldid, 'test data'); // Check that we have two entries. $userinfodata = $DB->get_records('user_info_data', ['userid' => $user->id]); $this->assertCount(2, $userinfodata); provider::delete_data_for_all_users_in_context($context); // Check that the correct profile field has been deleted. $userinfodata = $DB->get_records('user_info_data', ['userid' => $user->id]); $this->assertCount(1, $userinfodata); $this->assertNotEquals('1524067200', reset($userinfodata)->data); } /** * Test that user data is deleted for this user. */ public function test_delete_data_for_user(): void { global $DB; // Create profile category. $categoryid = $this->add_profile_category(); // Create datetime profile field. $datetimeprofilefieldid = $this->add_profile_field($categoryid, 'datetime'); // Create checkbox profile field. $checkboxprofilefieldid = $this->add_profile_field($categoryid, 'checkbox'); // Create a user. $user = $this->getDataGenerator()->create_user(); $context = \context_user::instance($user->id); // Add datetime user info data. $this->add_user_info_data($user->id, $datetimeprofilefieldid, '1524067200'); // Add checkbox user info data. $this->add_user_info_data($user->id, $checkboxprofilefieldid, 'test data'); // Check that we have two entries. $userinfodata = $DB->get_records('user_info_data', ['userid' => $user->id]); $this->assertCount(2, $userinfodata); $approvedlist = new \core_privacy\local\request\approved_contextlist($user, 'profilefield_datetime', [$context->id]); provider::delete_data_for_user($approvedlist); // Check that the correct profile field has been deleted. $userinfodata = $DB->get_records('user_info_data', ['userid' => $user->id]); $this->assertCount(1, $userinfodata); $this->assertNotEquals('1524067200', reset($userinfodata)->data); } /** * Test that only users with a user context are fetched. */ public function test_get_users_in_context(): void { $this->resetAfterTest(); $component = 'profilefield_datetime'; // Create profile category. $categoryid = $this->add_profile_category(); // Create profile field. $profilefieldid = $this->add_profile_field($categoryid, 'datetime'); // Create a user. $user = $this->getDataGenerator()->create_user(); $usercontext = \context_user::instance($user->id); // The list of users should not return anything yet (related data still haven't been created). $userlist = new \core_privacy\local\request\userlist($usercontext, $component); provider::get_users_in_context($userlist); $this->assertCount(0, $userlist); $this->add_user_info_data($user->id, $profilefieldid, 'test data'); // The list of users for user context should return the user. provider::get_users_in_context($userlist); $this->assertCount(1, $userlist); $expected = [$user->id]; $actual = $userlist->get_userids(); $this->assertEquals($expected, $actual); // The list of users for system context should not return any users. $systemcontext = \context_system::instance(); $userlist = new \core_privacy\local\request\userlist($systemcontext, $component); provider::get_users_in_context($userlist); $this->assertCount(0, $userlist); } /** * Test that data for users in approved userlist is deleted. */ public function test_delete_data_for_users(): void { $this->resetAfterTest(); $component = 'profilefield_datetime'; // Create profile category. $categoryid = $this->add_profile_category(); // Create profile field. $profilefieldid = $this->add_profile_field($categoryid, 'datetime'); // Create user1. $user1 = $this->getDataGenerator()->create_user(); $usercontext1 = \context_user::instance($user1->id); // Create user2. $user2 = $this->getDataGenerator()->create_user(); $usercontext2 = \context_user::instance($user2->id); $this->add_user_info_data($user1->id, $profilefieldid, 'test data'); $this->add_user_info_data($user2->id, $profilefieldid, 'test data'); // The list of users for usercontext1 should return user1. $userlist1 = new \core_privacy\local\request\userlist($usercontext1, $component); provider::get_users_in_context($userlist1); $this->assertCount(1, $userlist1); $expected = [$user1->id]; $actual = $userlist1->get_userids(); $this->assertEquals($expected, $actual); // The list of users for usercontext2 should return user2. $userlist2 = new \core_privacy\local\request\userlist($usercontext2, $component); provider::get_users_in_context($userlist2); $this->assertCount(1, $userlist2); $expected = [$user2->id]; $actual = $userlist2->get_userids(); $this->assertEquals($expected, $actual); // Add userlist1 to the approved user list. $approvedlist = new approved_userlist($usercontext1, $component, $userlist1->get_userids()); // Delete user data using delete_data_for_user for usercontext1. provider::delete_data_for_users($approvedlist); // Re-fetch users in usercontext1 - The user list should now be empty. $userlist1 = new \core_privacy\local\request\userlist($usercontext1, $component); provider::get_users_in_context($userlist1); $this->assertCount(0, $userlist1); // Re-fetch users in usercontext2 - The user list should not be empty (user2). $userlist2 = new \core_privacy\local\request\userlist($usercontext2, $component); provider::get_users_in_context($userlist2); $this->assertCount(1, $userlist2); // User data should be only removed in the user context. $systemcontext = \context_system::instance(); // Add userlist2 to the approved user list in the system context. $approvedlist = new approved_userlist($systemcontext, $component, $userlist2->get_userids()); // Delete user1 data using delete_data_for_user. provider::delete_data_for_users($approvedlist); // Re-fetch users in usercontext2 - The user list should not be empty (user2). $userlist1 = new \core_privacy\local\request\userlist($usercontext2, $component); provider::get_users_in_context($userlist1); $this->assertCount(1, $userlist1); } /** * Add dummy user info data. * * @param int $userid The ID of the user * @param int $fieldid The ID of the field * @param string $data The data */ private function add_user_info_data($userid, $fieldid, $data) { global $DB; $userinfodata = array( 'userid' => $userid, 'fieldid' => $fieldid, 'data' => $data, 'dataformat' => 0 ); $DB->insert_record('user_info_data', $userinfodata); } /** * Add dummy profile category. * * @return int The ID of the profile category */ private function add_profile_category() { $cat = $this->getDataGenerator()->create_custom_profile_field_category(['name' => 'Test category']); return $cat->id; } /** * Add dummy profile field. * * @param int $categoryid The ID of the profile category * @param string $datatype The datatype of the profile field * @return int The ID of the profile field */ private function add_profile_field($categoryid, $datatype) { $data = $this->getDataGenerator()->create_custom_profile_field([ 'datatype' => $datatype, 'shortname' => 'tstField', 'name' => 'Test field', 'description' => 'This is a test.', 'categoryid' => $categoryid, ]); return $data->id; } } field/datetime/field.class.php000064400000011614152266356300012336 0ustar00. /** * This file contains the datetime profile field class. * * @package profilefield_datetime * @copyright 2010 Mark Nelson * @license http://www.gnu.org/copyleft/gpl.html GNU Public License */ /** * Handles displaying and editing the datetime field. * * @copyright 2010 Mark Nelson * @license http://www.gnu.org/copyleft/gpl.html GNU Public License */ class profile_field_datetime extends profile_field_base { /** * Handles editing datetime fields. * * @param moodleform $mform */ public function edit_field_add($mform) { // Get the current calendar in use - see MDL-18375. $calendartype = \core_calendar\type_factory::get_calendar_instance(); // Check if the field is required. if ($this->field->required) { $optional = false; } else { $optional = true; } // Convert the year stored in the DB as gregorian to that used by the calendar type. $startdate = $calendartype->convert_from_gregorian($this->field->param1, 1, 1); $stopdate = $calendartype->convert_from_gregorian($this->field->param2, 1, 1); $attributes = array( 'startyear' => $startdate['year'], 'stopyear' => $stopdate['year'], 'optional' => $optional ); // Check if they wanted to include time as well. if (!empty($this->field->param3)) { $mform->addElement('date_time_selector', $this->inputname, format_string($this->field->name), $attributes); } else { $mform->addElement('date_selector', $this->inputname, format_string($this->field->name), $attributes); } $mform->setType($this->inputname, PARAM_INT); $mform->setDefault($this->inputname, time()); } /** * If timestamp is in YYYY-MM-DD or YYYY-MM-DD-HH-MM-SS format, then convert it to timestamp. * * @param string|int $datetime datetime to be converted. * @param stdClass $datarecord The object that will be used to save the record * @return int timestamp * @since Moodle 2.5 */ public function edit_save_data_preprocess($datetime, $datarecord) { if (!$datetime) { return 0; } if (is_numeric($datetime)) { $gregoriancalendar = \core_calendar\type_factory::get_calendar_instance('gregorian'); $datetime = $gregoriancalendar->timestamp_to_date_string($datetime, '%Y-%m-%d-%H-%M-%S', 99, true, true); } $datetime = explode('-', $datetime); // Bound year with start and end year. $datetime[0] = min(max($datetime[0], $this->field->param1), $this->field->param2); if (!empty($this->field->param3) && count($datetime) == 6) { return make_timestamp($datetime[0], $datetime[1], $datetime[2], $datetime[3], $datetime[4], $datetime[5]); } else { return make_timestamp($datetime[0], $datetime[1], $datetime[2]); } } /** * Display the data for this field. */ public function display_data() { // Check if time was specified. if (!empty($this->field->param3)) { $format = get_string('strftimedaydatetime', 'langconfig'); } else { $format = get_string('strftimedate', 'langconfig'); } // Check if a date has been specified. if (empty($this->data)) { return get_string('notset', 'profilefield_datetime'); } else { return userdate($this->data, $format); } } /** * Check if the field data is considered empty * * @return boolean */ public function is_empty() { return empty($this->data); } /** * Return the field type and null properties. * This will be used for validating the data submitted by a user. * * @return array the param type and null property * @since Moodle 3.2 */ public function get_field_properties() { return array(PARAM_INT, NULL_NOT_ALLOWED); } /** * Check if the field should convert the raw data into user-friendly data when exporting * * @return bool */ public function is_transform_supported(): bool { return true; } } field/upgrade.txt000064400000000575152266356300010036 0ustar00=== 4.5 Onwards === This file has been replaced by UPGRADING.md. See MDL-81125 for further information. === This files describes API changes in /user/profile/field/* - user profile fields, information provided here is intended especially for developers. === 3.4 === * profile_field_base::__construct() now takes three arguments instead of two. Update your plugins if required. field/checkbox/lang/en/profilefield_checkbox.php000064400000003017152266356300015774 0ustar00. /** * Strings for component 'profilefield_checkbox', language 'en', branch 'MOODLE_20_STABLE' * * @package profilefield_checkbox * @copyright 1999 onwards Martin Dougiamas {@link http://moodle.com} * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ $string['pluginname'] = 'Checkbox'; $string['privacy:metadata:profilefield_checkbox:userid'] = 'The ID of the user whose data is stored by the Checkbox user profile field'; $string['privacy:metadata:profilefield_checkbox:fieldid'] = 'The ID of the profile field'; $string['privacy:metadata:profilefield_checkbox:data'] = 'The checkbox user profile field user data'; $string['privacy:metadata:profilefield_checkbox:dataformat'] = 'The format of Checkbox user profile field user data'; $string['privacy:metadata:profilefield_checkbox:tableexplanation'] = 'Additional profile data'; field/checkbox/version.php000064400000002324152266356300011624 0ustar00. /** * Version information for the checkbox profile field type. * * @package profilefield_checkbox * @copyright 2008 onwards Shane Elliot {@link http://pukunui.com} * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); $plugin->version = 2025041400; // The current plugin version (Date: YYYYMMDDXX). $plugin->requires = 2025040800; // Requires this Moodle version. $plugin->component = 'profilefield_checkbox'; // Full name of the plugin (used for diagnostics) field/checkbox/classes/privacy/provider.php000064400000017305152266356300015110 0ustar00. /** * Privacy class for requesting user data. * * @package profilefield_checkbox * @copyright 2018 Mihail Geshoski * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace profilefield_checkbox\privacy; defined('MOODLE_INTERNAL') || die(); use \core_privacy\local\metadata\collection; use \core_privacy\local\request\contextlist; use \core_privacy\local\request\approved_contextlist; use core_privacy\local\request\userlist; use core_privacy\local\request\approved_userlist; /** * Privacy class for requesting user data. * * @copyright 2018 Mihail Geshoski * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class provider implements \core_privacy\local\metadata\provider, \core_privacy\local\request\core_userlist_provider, \core_privacy\local\request\plugin\provider { /** * Returns meta data about this system. * * @param collection $collection The initialised collection to add items to. * @return collection A listing of user data stored through this system. */ public static function get_metadata(collection $collection): collection { return $collection->add_database_table('user_info_data', [ 'userid' => 'privacy:metadata:profilefield_checkbox:userid', 'fieldid' => 'privacy:metadata:profilefield_checkbox:fieldid', 'data' => 'privacy:metadata:profilefield_checkbox:data', 'dataformat' => 'privacy:metadata:profilefield_checkbox:dataformat' ], 'privacy:metadata:profilefield_checkbox:tableexplanation'); } /** * Get the list of contexts that contain user information for the specified user. * * @param int $userid The user to search. * @return contextlist $contextlist The contextlist containing the list of contexts used in this plugin. */ public static function get_contexts_for_userid(int $userid): contextlist { $sql = "SELECT ctx.id FROM {user_info_data} uda JOIN {user_info_field} uif ON uda.fieldid = uif.id JOIN {context} ctx ON ctx.instanceid = uda.userid AND ctx.contextlevel = :contextlevel WHERE uda.userid = :userid AND uif.datatype = :datatype"; $params = [ 'userid' => $userid, 'contextlevel' => CONTEXT_USER, 'datatype' => 'checkbox' ]; $contextlist = new contextlist(); $contextlist->add_from_sql($sql, $params); return $contextlist; } /** * Get the list of users within a specific context. * * @param userlist $userlist The userlist containing the list of users who have data in this context/plugin combination. */ public static function get_users_in_context(userlist $userlist) { $context = $userlist->get_context(); if (!$context instanceof \context_user) { return; } $sql = "SELECT uda.userid FROM {user_info_data} uda JOIN {user_info_field} uif ON uda.fieldid = uif.id WHERE uda.userid = :userid AND uif.datatype = :datatype"; $params = [ 'userid' => $context->instanceid, 'datatype' => 'checkbox' ]; $userlist->add_from_sql('userid', $sql, $params); } /** * Export all user data for the specified user, in the specified contexts. * * @param approved_contextlist $contextlist The approved contexts to export information for. */ public static function export_user_data(approved_contextlist $contextlist) { $user = $contextlist->get_user(); foreach ($contextlist->get_contexts() as $context) { // Check if the context is a user context. if ($context->contextlevel == CONTEXT_USER && $context->instanceid == $user->id) { $results = static::get_records($user->id); foreach ($results as $result) { $data = (object) [ 'name' => $result->name, 'description' => $result->description, 'data' => $result->data ]; \core_privacy\local\request\writer::with_context($context)->export_data([ get_string('pluginname', 'profilefield_checkbox')], $data); } } } } /** * Delete all user data which matches the specified context. * * @param context $context A user context. */ public static function delete_data_for_all_users_in_context(\context $context) { // Delete data only for user context. if ($context->contextlevel == CONTEXT_USER) { static::delete_data($context->instanceid); } } /** * Delete multiple users within a single context. * * @param approved_userlist $userlist The approved context and user information to delete information for. */ public static function delete_data_for_users(approved_userlist $userlist) { $context = $userlist->get_context(); if ($context instanceof \context_user) { static::delete_data($context->instanceid); } } /** * Delete all user data for the specified user, in the specified contexts. * * @param approved_contextlist $contextlist The approved contexts and user information to delete information for. */ public static function delete_data_for_user(approved_contextlist $contextlist) { $user = $contextlist->get_user(); foreach ($contextlist->get_contexts() as $context) { // Check if the context is a user context. if ($context->contextlevel == CONTEXT_USER && $context->instanceid == $user->id) { static::delete_data($context->instanceid); } } } /** * Delete data related to a userid. * * @param int $userid The user ID */ protected static function delete_data($userid) { global $DB; $params = [ 'userid' => $userid, 'datatype' => 'checkbox' ]; $DB->delete_records_select('user_info_data', "fieldid IN ( SELECT id FROM {user_info_field} WHERE datatype = :datatype) AND userid = :userid", $params); } /** * Get records related to this plugin and user. * * @param int $userid The user ID * @return array An array of records. */ protected static function get_records($userid) { global $DB; $sql = "SELECT * FROM {user_info_data} uda JOIN {user_info_field} uif ON uda.fieldid = uif.id WHERE uda.userid = :userid AND uif.datatype = :datatype"; $params = [ 'userid' => $userid, 'datatype' => 'checkbox' ]; return $DB->get_records_sql($sql, $params); } } field/checkbox/define.class.php000064400000003117152266356300012476 0ustar00. /** * Checkbox profile field * * @package profilefield_checkbox * @copyright 2008 onwards Shane Elliot {@link http://pukunui.com} * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ /** * Class profile_define_checkbox * @copyright 2008 onwards Shane Elliot {@link http://pukunui.com} * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class profile_define_checkbox extends profile_define_base { /** * Add elements for creating/editing a checkbox profile field. * * @param moodleform $form */ public function define_form_specific($form) { // Select whether or not this should be checked by default. $form->addElement('selectyesno', 'defaultdata', get_string('profiledefaultchecked', 'admin')); $form->setDefault('defaultdata', 0); // Defaults to 'no'. $form->setType('defaultdata', PARAM_BOOL); } } field/checkbox/tests/privacy/provider_test.php000064400000031223152266356300015647 0ustar00. /** * Base class for unit tests for profilefield_checkbox. * * @package profilefield_checkbox * @copyright 2018 Mihail Geshoski * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace profilefield_checkbox\privacy; defined('MOODLE_INTERNAL') || die(); use core_privacy\tests\provider_testcase; use profilefield_checkbox\privacy\provider; use core_privacy\local\request\approved_userlist; /** * Unit tests for user\profile\field\checkbox\classes\privacy\provider.php * * @copyright 2018 Mihail Geshoski * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ final class provider_test extends provider_testcase { /** * Basic setup for these tests. */ public function setUp(): void { parent::setUp(); $this->resetAfterTest(true); } /** * Test getting the context for the user ID related to this plugin. */ public function test_get_contexts_for_userid(): void { global $DB; // Create profile category. $categoryid = $this->add_profile_category(); // Create profile field. $profilefieldid = $this->add_profile_field($categoryid, 'checkbox'); // Create a user. $user = $this->getDataGenerator()->create_user(); $this->add_user_info_data($user->id, $profilefieldid, 'test data'); // Get the field that was created. $userfielddata = $DB->get_records('user_info_data', array('userid' => $user->id)); // Confirm we got the right number of user field data. $this->assertCount(1, $userfielddata); $context = \context_user::instance($user->id); $contextlist = provider::get_contexts_for_userid($user->id); $this->assertEquals($context, $contextlist->current()); } /** * Test that data is exported correctly for this plugin. */ public function test_export_user_data(): void { // Create profile category. $categoryid = $this->add_profile_category(); // Create checkbox profile field. $checkboxprofilefieldid = $this->add_profile_field($categoryid, 'checkbox'); // Create datetime profile field. $datetimeprofilefieldid = $this->add_profile_field($categoryid, 'datetime'); // Create a user. $user = $this->getDataGenerator()->create_user(); $context = \context_user::instance($user->id); // Add checkbox user info data. $this->add_user_info_data($user->id, $checkboxprofilefieldid, 'test data'); // Add datetime user info data. $this->add_user_info_data($user->id, $datetimeprofilefieldid, '1524067200'); $writer = \core_privacy\local\request\writer::with_context($context); $this->assertFalse($writer->has_any_data()); $this->export_context_data_for_user($user->id, $context, 'profilefield_checkbox'); $data = $writer->get_data([get_string('pluginname', 'profilefield_checkbox')]); $this->assertCount(3, (array) $data); $this->assertEquals('Test field', $data->name); $this->assertEquals('This is a test.', $data->description); $this->assertEquals('test data', $data->data); } /** * Test that user data is deleted using the context. */ public function test_delete_data_for_all_users_in_context(): void { global $DB; // Create profile category. $categoryid = $this->add_profile_category(); // Create checkbox profile field. $checkboxprofilefieldid = $this->add_profile_field($categoryid, 'checkbox'); // Create datetime profile field. $datetimeprofilefieldid = $this->add_profile_field($categoryid, 'datetime'); // Create a user. $user = $this->getDataGenerator()->create_user(); $context = \context_user::instance($user->id); // Add checkbox user info data. $this->add_user_info_data($user->id, $checkboxprofilefieldid, 'test data'); // Add datetime user info data. $this->add_user_info_data($user->id, $datetimeprofilefieldid, '1524067200'); // Check that we have two entries. $userinfodata = $DB->get_records('user_info_data', ['userid' => $user->id]); $this->assertCount(2, $userinfodata); provider::delete_data_for_all_users_in_context($context); // Check that the correct profile field has been deleted. $userinfodata = $DB->get_records('user_info_data', ['userid' => $user->id]); $this->assertCount(1, $userinfodata); $this->assertNotEquals('test data', reset($userinfodata)->data); } /** * Test that user data is deleted for this user. */ public function test_delete_data_for_user(): void { global $DB; // Create profile category. $categoryid = $this->add_profile_category(); // Create checkbox profile field. $checkboxprofilefieldid = $this->add_profile_field($categoryid, 'checkbox'); // Create datetime profile field. $datetimeprofilefieldid = $this->add_profile_field($categoryid, 'datetime'); // Create a user. $user = $this->getDataGenerator()->create_user(); $context = \context_user::instance($user->id); // Add checkbox user info data. $this->add_user_info_data($user->id, $checkboxprofilefieldid, 'test data'); // Add datetime user info data. $this->add_user_info_data($user->id, $datetimeprofilefieldid, '1524067200'); // Check that we have two entries. $userinfodata = $DB->get_records('user_info_data', ['userid' => $user->id]); $this->assertCount(2, $userinfodata); $approvedlist = new \core_privacy\local\request\approved_contextlist($user, 'profilefield_checkbox', [$context->id]); provider::delete_data_for_user($approvedlist); // Check that the correct profile field has been deleted. $userinfodata = $DB->get_records('user_info_data', ['userid' => $user->id]); $this->assertCount(1, $userinfodata); $this->assertNotEquals('test data', reset($userinfodata)->data); } /** * Test that only users with a user context are fetched. */ public function test_get_users_in_context(): void { $this->resetAfterTest(); $component = 'profilefield_checkbox'; // Create profile category. $categoryid = $this->add_profile_category(); // Create profile field. $profilefieldid = $this->add_profile_field($categoryid, 'checkbox'); // Create a user. $user = $this->getDataGenerator()->create_user(); $usercontext = \context_user::instance($user->id); // The list of users should not return anything yet (related data still haven't been created). $userlist = new \core_privacy\local\request\userlist($usercontext, $component); provider::get_users_in_context($userlist); $this->assertCount(0, $userlist); $this->add_user_info_data($user->id, $profilefieldid, 'test data'); // The list of users for user context should return the user. provider::get_users_in_context($userlist); $this->assertCount(1, $userlist); $expected = [$user->id]; $actual = $userlist->get_userids(); $this->assertEquals($expected, $actual); // The list of users for system context should not return any users. $systemcontext = \context_system::instance(); $userlist = new \core_privacy\local\request\userlist($systemcontext, $component); provider::get_users_in_context($userlist); $this->assertCount(0, $userlist); } /** * Test that data for users in approved userlist is deleted. */ public function test_delete_data_for_users(): void { $this->resetAfterTest(); $component = 'profilefield_checkbox'; // Create profile category. $categoryid = $this->add_profile_category(); // Create profile field. $profilefieldid = $this->add_profile_field($categoryid, 'checkbox'); // Create user1. $user1 = $this->getDataGenerator()->create_user(); $usercontext1 = \context_user::instance($user1->id); // Create user2. $user2 = $this->getDataGenerator()->create_user(); $usercontext2 = \context_user::instance($user2->id); $this->add_user_info_data($user1->id, $profilefieldid, 'test data'); $this->add_user_info_data($user2->id, $profilefieldid, 'test data'); // The list of users for usercontext1 should return user1. $userlist1 = new \core_privacy\local\request\userlist($usercontext1, $component); provider::get_users_in_context($userlist1); $this->assertCount(1, $userlist1); $expected = [$user1->id]; $actual = $userlist1->get_userids(); $this->assertEquals($expected, $actual); // The list of users for usercontext2 should return user2. $userlist2 = new \core_privacy\local\request\userlist($usercontext2, $component); provider::get_users_in_context($userlist2); $this->assertCount(1, $userlist2); $expected = [$user2->id]; $actual = $userlist2->get_userids(); $this->assertEquals($expected, $actual); // Add userlist1 to the approved user list. $approvedlist = new approved_userlist($usercontext1, $component, $userlist1->get_userids()); // Delete user data using delete_data_for_user for usercontext1. provider::delete_data_for_users($approvedlist); // Re-fetch users in usercontext1 - The user list should now be empty. $userlist1 = new \core_privacy\local\request\userlist($usercontext1, $component); provider::get_users_in_context($userlist1); $this->assertCount(0, $userlist1); // Re-fetch users in usercontext2 - The user list should not be empty (user2). $userlist2 = new \core_privacy\local\request\userlist($usercontext2, $component); provider::get_users_in_context($userlist2); $this->assertCount(1, $userlist2); // User data should be only removed in the user context. $systemcontext = \context_system::instance(); // Add userlist2 to the approved user list in the system context. $approvedlist = new approved_userlist($systemcontext, $component, $userlist2->get_userids()); // Delete user1 data using delete_data_for_user. provider::delete_data_for_users($approvedlist); // Re-fetch users in usercontext2 - The user list should not be empty (user2). $userlist1 = new \core_privacy\local\request\userlist($usercontext2, $component); provider::get_users_in_context($userlist1); $this->assertCount(1, $userlist1); } /** * Add dummy user info data. * * @param int $userid The ID of the user * @param int $fieldid The ID of the field * @param string $data The data */ private function add_user_info_data($userid, $fieldid, $data) { global $DB; $userinfodata = array( 'userid' => $userid, 'fieldid' => $fieldid, 'data' => $data, 'dataformat' => 0 ); $DB->insert_record('user_info_data', $userinfodata); } /** * Add dummy profile category. * * @return int The ID of the profile category */ private function add_profile_category() { $cat = $this->getDataGenerator()->create_custom_profile_field_category(['name' => 'Test category']); return $cat->id; } /** * Add dummy profile field. * * @param int $categoryid The ID of the profile category * @param string $datatype The datatype of the profile field * @return int The ID of the profile field */ private function add_profile_field($categoryid, $datatype) { $data = $this->getDataGenerator()->create_custom_profile_field([ 'datatype' => $datatype, 'shortname' => 'tstField', 'name' => 'Test field', 'description' => 'This is a test.', 'categoryid' => $categoryid, ]); return $data->id; } } field/checkbox/tests/profile_field_checkbox_test.php000064400000007252152266356300017016 0ustar00. namespace profilefield_checkbox; use advanced_testcase; use profile_field_checkbox; /** * Unit tests for the field class * * @package profilefield_checkbox * @covers \profile_field_checkbox * @copyright 2024 Paul Holden * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ final class profile_field_checkbox_test extends advanced_testcase { /** * Load required test libraries */ public static function setUpBeforeClass(): void { global $CFG; require_once("{$CFG->dirroot}/user/profile/lib.php"); parent::setUpBeforeClass(); } /** * Data provider for {@see test_is_empty} * * @return array[] */ public static function is_empty_provider(): array { return [ 'No value' => [ [], true, ], 'Value equals 0' => [ ['profile_field_check' => 0], false, ], 'Value equals 1' => [ ['profile_field_check' => 1], false, ], ]; } /** * Test field empty state * * @param array $userrecord * @param bool $expected * * @dataProvider is_empty_provider */ public function test_is_empty(array $userrecord, bool $expected): void { $this->resetAfterTest(); $this->getDataGenerator()->create_custom_profile_field([ 'datatype' => 'checkbox', 'name' => 'My check', 'shortname' => 'check', ]); $user = $this->getDataGenerator()->create_user($userrecord); /** @var profile_field_checkbox[] $fields */ $fields = profile_get_user_fields_with_data($user->id); $fieldinstance = reset($fields); $this->assertEquals($expected, $fieldinstance->is_empty()); } /** * Test whether to show field content */ public function test_show_field_content(): void { $this->resetAfterTest(); $this->getDataGenerator()->create_custom_profile_field([ 'datatype' => 'checkbox', 'name' => 'My check', 'shortname' => 'check', 'visible' => PROFILE_VISIBLE_PRIVATE, ]); // User can view their own value. $userwith = $this->getDataGenerator()->create_user(['profile_field_check' => 1]); $this->setUser($userwith); /** @var profile_field_checkbox[] $fields */ $fields = profile_get_user_fields_with_data($userwith->id); $fieldinstance = reset($fields); $this->assertTrue($fieldinstance->show_field_content()); // Another user cannot view the value. $userview = $this->getDataGenerator()->create_user(); $this->setUser($userview); $this->assertFalse($fieldinstance->show_field_content()); // Another user with appropriate access can view the value. $this->setAdminUser(); $this->assertTrue($fieldinstance->show_field_content()); } } field/checkbox/field.class.php000064400000006007152266356300012330 0ustar00. /** * Class profile_field_checkbox * * @package profilefield_checkbox * @copyright 2008 onwards Shane Elliot {@link http://pukunui.com} * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class profile_field_checkbox extends profile_field_base { /** * Add elements for editing the profile field value. * @param moodleform $mform */ public function edit_field_add($mform) { // Create the form field. $checkbox = $mform->addElement('advcheckbox', $this->inputname, format_string($this->field->name)); if ($this->data == '1') { $checkbox->setChecked(true); } $mform->setType($this->inputname, PARAM_BOOL); if ($this->is_required() and !has_capability('moodle/user:update', context_system::instance())) { $mform->addRule($this->inputname, get_string('required'), 'nonzero', null, 'client'); } } /** * Override parent {@see profile_field_base::is_empty} check * * We can't check the "data" property, because if not set by the user then it's populated by "defaultdata" of the field, * which can also be 0 (false) therefore ensuring the parent class check could never return true for this comparison * * @return bool */ public function is_empty() { return ($this->userid && !$this->field->hasuserdata); } /** * Override parent {@see profile_field_base::show_field_content} check * * We only need to determine whether the field is visible, because we also want to show the "defaultdata" of the field, * even if the user hasn't explicitly filled it in * * @param context|null $context * @return bool */ public function show_field_content(?context $context = null): bool { return $this->is_visible($context); } /** * Display the data for this field * * @return string HTML. */ public function display_data() { return $this->data ? get_string('yes') : get_string('no'); } /** * Return the field type and null properties. * This will be used for validating the data submitted by a user. * * @return array the param type and null property * @since Moodle 3.2 */ public function get_field_properties() { return array(PARAM_BOOL, NULL_NOT_ALLOWED); } } field/text/lang/en/profilefield_text.php000064400000002767152266356300014403 0ustar00. /** * Strings for component 'profilefield_text', language 'en', branch 'MOODLE_20_STABLE' * * @package profilefield_text * @copyright 1999 onwards Martin Dougiamas {@link http://moodle.com} * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ $string['pluginname'] = 'Text input'; $string['privacy:metadata:profilefield_text:userid'] = 'The ID of the user whose data is stored by the Text input user profile field'; $string['privacy:metadata:profilefield_text:fieldid'] = 'The ID of the profile field'; $string['privacy:metadata:profilefield_text:data'] = 'Text input user profile field user data'; $string['privacy:metadata:profilefield_text:dataformat'] = 'The format of Text input user profile field user data'; $string['privacy:metadata:profilefield_text:tableexplanation'] = 'Additional profile data'; field/text/version.php000064400000002274152266356300011026 0ustar00. /** * Text profile field version information. * * @package profilefield_text * @copyright 2007 onwards Shane Elliot {@link http://pukunui.com} * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); $plugin->version = 2025041400; // The current plugin version (Date: YYYYMMDDXX). $plugin->requires = 2025040800; // Requires this Moodle version. $plugin->component = 'profilefield_text'; // Full name of the plugin (used for diagnostics). field/text/classes/privacy/provider.php000064400000017143152266356300014306 0ustar00. /** * Privacy class for requesting user data. * * @package profilefield_text * @copyright 2018 Mihail Geshoski * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace profilefield_text\privacy; use \core_privacy\local\metadata\collection; use \core_privacy\local\request\contextlist; use \core_privacy\local\request\approved_contextlist; use core_privacy\local\request\userlist; use core_privacy\local\request\approved_userlist; /** * Privacy class for requesting user data. * * @copyright 2018 Mihail Geshoski * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class provider implements \core_privacy\local\metadata\provider, \core_privacy\local\request\core_userlist_provider, \core_privacy\local\request\plugin\provider { /** * Returns meta data about this system. * * @param collection $collection The initialised collection to add items to. * @return collection A listing of user data stored through this system. */ public static function get_metadata(collection $collection): collection { return $collection->add_database_table('user_info_data', [ 'userid' => 'privacy:metadata:profilefield_text:userid', 'fieldid' => 'privacy:metadata:profilefield_text:fieldid', 'data' => 'privacy:metadata:profilefield_text:data', 'dataformat' => 'privacy:metadata:profilefield_text:dataformat' ], 'privacy:metadata:profilefield_text:tableexplanation'); } /** * Get the list of contexts that contain user information for the specified user. * * @param int $userid The user to search. * @return contextlist $contextlist The contextlist containing the list of contexts used in this plugin. */ public static function get_contexts_for_userid(int $userid): contextlist { $sql = "SELECT ctx.id FROM {user_info_data} uda JOIN {user_info_field} uif ON uda.fieldid = uif.id JOIN {context} ctx ON ctx.instanceid = uda.userid AND ctx.contextlevel = :contextlevel WHERE uda.userid = :userid AND uif.datatype = :datatype"; $params = [ 'userid' => $userid, 'contextlevel' => CONTEXT_USER, 'datatype' => 'text' ]; $contextlist = new contextlist(); $contextlist->add_from_sql($sql, $params); return $contextlist; } /** * Get the list of users within a specific context. * * @param userlist $userlist The userlist containing the list of users who have data in this context/plugin combination. */ public static function get_users_in_context(userlist $userlist) { $context = $userlist->get_context(); if (!$context instanceof \context_user) { return; } $sql = "SELECT uda.userid FROM {user_info_data} uda JOIN {user_info_field} uif ON uda.fieldid = uif.id WHERE uda.userid = :userid AND uif.datatype = :datatype"; $params = [ 'userid' => $context->instanceid, 'datatype' => 'text' ]; $userlist->add_from_sql('userid', $sql, $params); } /** * Export all user data for the specified user, in the specified contexts. * * @param approved_contextlist $contextlist The approved contexts to export information for. */ public static function export_user_data(approved_contextlist $contextlist) { $user = $contextlist->get_user(); foreach ($contextlist->get_contexts() as $context) { // Check if the context is a user context. if ($context->contextlevel == CONTEXT_USER && $context->instanceid == $user->id) { $results = static::get_records($user->id); foreach ($results as $result) { $data = (object) [ 'name' => $result->name, 'description' => $result->description, 'data' => $result->data ]; \core_privacy\local\request\writer::with_context($context)->export_data([ get_string('pluginname', 'profilefield_text')], $data); } } } } /** * Delete all user data which matches the specified context. * * @param context $context A user context. */ public static function delete_data_for_all_users_in_context(\context $context) { // Delete data only for user context. if ($context->contextlevel == CONTEXT_USER) { static::delete_data($context->instanceid); } } /** * Delete multiple users within a single context. * * @param approved_userlist $userlist The approved context and user information to delete information for. */ public static function delete_data_for_users(approved_userlist $userlist) { $context = $userlist->get_context(); if ($context instanceof \context_user) { static::delete_data($context->instanceid); } } /** * Delete all user data for the specified user, in the specified contexts. * * @param approved_contextlist $contextlist The approved contexts and user information to delete information for. */ public static function delete_data_for_user(approved_contextlist $contextlist) { $user = $contextlist->get_user(); foreach ($contextlist->get_contexts() as $context) { // Check if the context is a user context. if ($context->contextlevel == CONTEXT_USER && $context->instanceid == $user->id) { static::delete_data($context->instanceid); } } } /** * Delete data related to a userid. * * @param int $userid The user ID */ protected static function delete_data($userid) { global $DB; $params = [ 'userid' => $userid, 'datatype' => 'text' ]; $DB->delete_records_select('user_info_data', "fieldid IN ( SELECT id FROM {user_info_field} WHERE datatype = :datatype) AND userid = :userid", $params); } /** * Get records related to this plugin and user. * * @param int $userid The user ID * @return array An array of records. */ protected static function get_records($userid) { global $DB; $sql = "SELECT * FROM {user_info_data} uda JOIN {user_info_field} uif ON uda.fieldid = uif.id WHERE uda.userid = :userid AND uif.datatype = :datatype"; $params = [ 'userid' => $userid, 'datatype' => 'text' ]; return $DB->get_records_sql($sql, $params); } } field/text/define.class.php000064400000006175152266356300011703 0ustar00. /** * Text profile field definition. * * @package profilefield_text * @copyright 2007 onwards Shane Elliot {@link http://pukunui.com} * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ /** * Class profile_define_text * * @copyright 2007 onwards Shane Elliot {@link http://pukunui.com} * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class profile_define_text extends profile_define_base { /** * Add elements for creating/editing a text profile field. * * @param MoodleQuickForm $form */ public function define_form_specific($form) { // Default data. $form->addElement('text', 'defaultdata', get_string('profiledefaultdata', 'admin'), 'size="50"'); $form->setType('defaultdata', PARAM_TEXT); // Param 1 for text type is the size of the field. $form->addElement('text', 'param1', get_string('profilefieldsize', 'admin'), 'size="6"'); $form->setDefault('param1', 30); $form->setType('param1', PARAM_INT); // Param 2 for text type is the maxlength of the field. $form->addElement('text', 'param2', get_string('profilefieldmaxlength', 'admin'), 'size="6"'); $form->setDefault('param2', 2048); $form->setType('param2', PARAM_INT); $form->addHelpButton('param2', 'profilefieldmaxlength', 'admin'); // Param 3 for text type detemines if this is a password field or not. $form->addElement('selectyesno', 'param3', get_string('profilefieldispassword', 'admin')); $form->setDefault('param3', 0); // Defaults to 'no'. $form->setType('param3', PARAM_INT); // Param 4 for text type contains a link. $form->addElement('text', 'param4', get_string('profilefieldlink', 'admin')); $form->setType('param4', PARAM_URL); $form->addHelpButton('param4', 'profilefieldlink', 'admin'); // Param 5 for text type contains link target. $targetoptions = array( '' => get_string('linktargetnone', 'editor'), '_blank' => get_string('linktargetblank', 'editor'), '_self' => get_string('linktargetself', 'editor'), '_top' => get_string('linktargettop', 'editor') ); $form->addElement('select', 'param5', get_string('profilefieldlinktarget', 'admin'), $targetoptions); $form->setType('param5', PARAM_RAW); } } field/text/tests/privacy/provider_test.php000064400000031005152266356300015043 0ustar00. /** * Base class for unit tests for profilefield_text. * * @package profilefield_text * @copyright 2018 Mihail Geshoski * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace profilefield_text\privacy; use core_privacy\tests\provider_testcase; use profilefield_text\privacy\provider; use core_privacy\local\request\approved_userlist; /** * Unit tests for user\profile\field\text\classes\privacy\provider.php * * @copyright 2018 Mihail Geshoski * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ final class provider_test extends provider_testcase { /** * Basic setup for these tests. */ public function setUp(): void { parent::setUp(); $this->resetAfterTest(true); } /** * Test getting the context for the user ID related to this plugin. */ public function test_get_contexts_for_userid(): void { global $DB; // Create profile category. $categoryid = $this->add_profile_category(); // Create profile field. $profilefieldid = $this->add_profile_field($categoryid, 'text'); // Create a user. $user = $this->getDataGenerator()->create_user(); $this->add_user_info_data($user->id, $profilefieldid, 'test data'); // Get the field that was created. $userfielddata = $DB->get_records('user_info_data', array('userid' => $user->id)); // Confirm we got the right number of user field data. $this->assertCount(1, $userfielddata); $context = \context_user::instance($user->id); $contextlist = provider::get_contexts_for_userid($user->id); $this->assertEquals($context, $contextlist->current()); } /** * Test that data is exported correctly for this plugin. */ public function test_export_user_data(): void { // Create profile category. $categoryid = $this->add_profile_category(); // Create text profile field. $textprofilefieldid = $this->add_profile_field($categoryid, 'text'); // Create checkbox profile field. $checkboxprofilefieldid = $this->add_profile_field($categoryid, 'checkbox'); // Create a user. $user = $this->getDataGenerator()->create_user(); $context = \context_user::instance($user->id); // Add text user info data. $this->add_user_info_data($user->id, $textprofilefieldid, 'test text'); // Add checkbox user info data. $this->add_user_info_data($user->id, $checkboxprofilefieldid, 'test data'); $writer = \core_privacy\local\request\writer::with_context($context); $this->assertFalse($writer->has_any_data()); $this->export_context_data_for_user($user->id, $context, 'profilefield_text'); $data = $writer->get_data([get_string('pluginname', 'profilefield_text')]); $this->assertCount(3, (array) $data); $this->assertEquals('Test field', $data->name); $this->assertEquals('This is a test.', $data->description); $this->assertEquals('test text', $data->data); } /** * Test that user data is deleted using the context. */ public function test_delete_data_for_all_users_in_context(): void { global $DB; // Create profile category. $categoryid = $this->add_profile_category(); // Create text profile field. $textprofilefieldid = $this->add_profile_field($categoryid, 'text'); // Create checkbox profile field. $checkboxprofilefieldid = $this->add_profile_field($categoryid, 'checkbox'); // Create a user. $user = $this->getDataGenerator()->create_user(); $context = \context_user::instance($user->id); // Add text user info data. $this->add_user_info_data($user->id, $textprofilefieldid, 'test text'); // Add checkbox user info data. $this->add_user_info_data($user->id, $checkboxprofilefieldid, 'test data'); // Check that we have two entries. $userinfodata = $DB->get_records('user_info_data', ['userid' => $user->id]); $this->assertCount(2, $userinfodata); provider::delete_data_for_all_users_in_context($context); // Check that the correct profile field has been deleted. $userinfodata = $DB->get_records('user_info_data', ['userid' => $user->id]); $this->assertCount(1, $userinfodata); $this->assertNotEquals('test text', reset($userinfodata)->data); } /** * Test that user data is deleted for this user. */ public function test_delete_data_for_user(): void { global $DB; // Create profile category. $categoryid = $this->add_profile_category(); // Create text profile field. $textprofilefieldid = $this->add_profile_field($categoryid, 'text'); // Create checkbox profile field. $checkboxprofilefieldid = $this->add_profile_field($categoryid, 'checkbox'); // Create a user. $user = $this->getDataGenerator()->create_user(); $context = \context_user::instance($user->id); // Add text user info data. $this->add_user_info_data($user->id, $textprofilefieldid, 'test text'); // Add checkbox user info data. $this->add_user_info_data($user->id, $checkboxprofilefieldid, 'test data'); // Check that we have two entries. $userinfodata = $DB->get_records('user_info_data', ['userid' => $user->id]); $this->assertCount(2, $userinfodata); $approvedlist = new \core_privacy\local\request\approved_contextlist($user, 'profilefield_text', [$context->id]); provider::delete_data_for_user($approvedlist); // Check that the correct profile field has been deleted. $userinfodata = $DB->get_records('user_info_data', ['userid' => $user->id]); $this->assertCount(1, $userinfodata); $this->assertNotEquals('test text', reset($userinfodata)->data); } /** * Test that only users with a user context are fetched. */ public function test_get_users_in_context(): void { $this->resetAfterTest(); $component = 'profilefield_text'; // Create profile category. $categoryid = $this->add_profile_category(); // Create text profile field. $profilefieldid = $this->add_profile_field($categoryid, 'text'); // Create a user. $user = $this->getDataGenerator()->create_user(); $usercontext = \context_user::instance($user->id); // The list of users should not return anything yet (related data still haven't been created). $userlist = new \core_privacy\local\request\userlist($usercontext, $component); provider::get_users_in_context($userlist); $this->assertCount(0, $userlist); $this->add_user_info_data($user->id, $profilefieldid, 'test data'); // The list of users for user context should return the user. provider::get_users_in_context($userlist); $this->assertCount(1, $userlist); $expected = [$user->id]; $actual = $userlist->get_userids(); $this->assertEquals($expected, $actual); // The list of users for system context should not return any users. $systemcontext = \context_system::instance(); $userlist = new \core_privacy\local\request\userlist($systemcontext, $component); provider::get_users_in_context($userlist); $this->assertCount(0, $userlist); } /** * Test that data for users in approved userlist is deleted. */ public function test_delete_data_for_users(): void { $this->resetAfterTest(); $component = 'profilefield_text'; // Create profile category. $categoryid = $this->add_profile_category(); // Create text profile field. $profilefieldid = $this->add_profile_field($categoryid, 'text'); // Create user1. $user1 = $this->getDataGenerator()->create_user(); $usercontext1 = \context_user::instance($user1->id); // Create user2. $user2 = $this->getDataGenerator()->create_user(); $usercontext2 = \context_user::instance($user2->id); $this->add_user_info_data($user1->id, $profilefieldid, 'test data'); $this->add_user_info_data($user2->id, $profilefieldid, 'test data'); // The list of users for usercontext1 should return user1. $userlist1 = new \core_privacy\local\request\userlist($usercontext1, $component); provider::get_users_in_context($userlist1); $this->assertCount(1, $userlist1); $expected = [$user1->id]; $actual = $userlist1->get_userids(); $this->assertEquals($expected, $actual); // The list of users for usercontext2 should return user2. $userlist2 = new \core_privacy\local\request\userlist($usercontext2, $component); provider::get_users_in_context($userlist2); $this->assertCount(1, $userlist2); $expected = [$user2->id]; $actual = $userlist2->get_userids(); $this->assertEquals($expected, $actual); // Add userlist1 to the approved user list. $approvedlist = new approved_userlist($usercontext1, $component, $userlist1->get_userids()); // Delete user data using delete_data_for_user for usercontext1. provider::delete_data_for_users($approvedlist); // Re-fetch users in usercontext1 - The user list should now be empty. $userlist1 = new \core_privacy\local\request\userlist($usercontext1, $component); provider::get_users_in_context($userlist1); $this->assertCount(0, $userlist1); // Re-fetch users in usercontext2 - The user list should not be empty (user2). $userlist2 = new \core_privacy\local\request\userlist($usercontext2, $component); provider::get_users_in_context($userlist2); $this->assertCount(1, $userlist2); // User data should be only removed in the user context. $systemcontext = \context_system::instance(); // Add userlist2 to the approved user list in the system context. $approvedlist = new approved_userlist($systemcontext, $component, $userlist2->get_userids()); // Delete user1 data using delete_data_for_user. provider::delete_data_for_users($approvedlist); // Re-fetch users in usercontext2 - The user list should not be empty (user2). $userlist1 = new \core_privacy\local\request\userlist($usercontext2, $component); provider::get_users_in_context($userlist1); $this->assertCount(1, $userlist1); } /** * Add dummy user info data. * * @param int $userid The ID of the user * @param int $fieldid The ID of the field * @param string $data The data */ private function add_user_info_data($userid, $fieldid, $data) { global $DB; $userinfodata = array( 'userid' => $userid, 'fieldid' => $fieldid, 'data' => $data, 'dataformat' => 0 ); $DB->insert_record('user_info_data', $userinfodata); } /** * Add dummy profile category. * * @return int The ID of the profile category */ private function add_profile_category() { $cat = $this->getDataGenerator()->create_custom_profile_field_category(['name' => 'Test category']); return $cat->id; } /** * Add dummy profile field. * * @param int $categoryid The ID of the profile category * @param string $datatype The datatype of the profile field * @return int The ID of the profile field */ private function add_profile_field($categoryid, $datatype) { $data = $this->getDataGenerator()->create_custom_profile_field([ 'datatype' => $datatype, 'shortname' => 'tstField', 'name' => 'Test field', 'description' => 'This is a test.', 'categoryid' => $categoryid, ]); return $data->id; } } field/text/tests/field_class_test.php000064400000007637152266356300014022 0ustar00. namespace profilefield_text; defined('MOODLE_INTERNAL') || die(); global $CFG; require_once($CFG->dirroot.'/user/profile/lib.php'); require_once($CFG->dirroot.'/user/profile/field/text/field.class.php'); use profile_field_text; /** * Unit tests for the profilefield_text. * * @package profilefield_text * @copyright 2022 The Open University * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later * @covers \profilefield_text\profile_field_text */ final class field_class_test extends \advanced_testcase { /** * Test that the profile text data is formatted and required filters applied * * @covers \profile_field_text::display_data * @dataProvider filter_profile_field_text_provider * @param string $input * @param string $expected */ public function test_filter_display_data(string $input, string $expected): void { $this->resetAfterTest(); $field = new profile_field_text(); $field->data = $input; filter_set_global_state('multilang', TEXTFILTER_ON); filter_set_global_state('emoticon', TEXTFILTER_ON); filter_set_applies_to_strings('multilang', true); $actual = $field->display_data(); $this->assertEquals($expected, $actual); } /** * Data provider for {@see test_filter_display_data} * * @return string[] */ public static function filter_profile_field_text_provider(): array { return [ 'simple_string' => ['Simple string', 'Simple string'], 'format_string' => ['HTML & is escaped', 'HTML & is escaped'], 'multilang_filter' => ['EnglishFrench', 'English'], 'emoticons_filter' => ['No emoticons filter :-(', 'No emoticons filter :-('] ]; } /** * Test preprocess data validation */ public function test_edit_save_data_preprocess(): void { $this->resetAfterTest(); $fielddata = $this->getDataGenerator()->create_custom_profile_field([ 'datatype' => 'text', 'name' => 'Test', 'shortname' => 'test', 'param2' => 5, // Max length. ]); $field = new profile_field_text(0, 0, $fielddata); $value = $field->edit_save_data_preprocess('ABCDE', new \stdClass()); $this->assertEquals('ABCDE', $value); // Exceed max length. $value = $field->edit_save_data_preprocess('ABCDEF', new \stdClass()); $this->assertEquals('ABCDE', $value); } /** * Test external data validation */ public function test_convert_external_data(): void { $this->resetAfterTest(); $fielddata = $this->getDataGenerator()->create_custom_profile_field([ 'datatype' => 'text', 'name' => 'Test', 'shortname' => 'test', 'param2' => 5, // Max length. ]); $field = new profile_field_text(0, 0, $fielddata); $value = $field->convert_external_data('ABCDE'); $this->assertEquals('ABCDE', $value); // Exceed max length. $value = $field->convert_external_data('ABCDEF'); $this->assertNull($value); } } field/text/field.class.php000064400000006665152266356300011540 0ustar00. /** * Text profile field. * * @package profilefield_text * @copyright 2007 onwards Shane Elliot {@link http://pukunui.com} * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ /** * Class profile_field_text * * @copyright 2007 onwards Shane Elliot {@link http://pukunui.com} * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class profile_field_text extends profile_field_base { /** * Overwrite the base class to display the data for this field */ public function display_data() { // Default formatting. $data = format_string($this->data); // Are we creating a link? if (!empty($this->field->param4) && !empty($data)) { // Define the target. if (! empty($this->field->param5)) { $target = 'target="'.$this->field->param5.'"'; } else { $target = ''; } // Create the link. $data = ''.htmlspecialchars($data, ENT_COMPAT).''; } return $data; } /** * Add fields for editing a text profile field. * @param moodleform $mform */ public function edit_field_add($mform) { $size = $this->field->param1; $maxlength = $this->field->param2; $fieldtype = ($this->field->param3 == 1 ? 'password' : 'text'); // Create the form field. $mform->addElement($fieldtype, $this->inputname, format_string($this->field->name), 'maxlength="'.$maxlength.'" size="'.$size.'" '); $mform->setType($this->inputname, PARAM_TEXT); } /** * Process the data before it gets saved in database * * @param string|null $data * @param stdClass $datarecord * @return string|null */ public function edit_save_data_preprocess($data, $datarecord) { if ($data === null) { return null; } return core_text::substr($data, 0, $this->field->param2); } /** * Convert external data (csv file) from value to key for processing later by edit_save_data_preprocess * * @param string $data * @return string|null */ public function convert_external_data($data) { if (core_text::strlen($data) > $this->field->param2) { return null; } return $data; } /** * Return the field type and null properties. * This will be used for validating the data submitted by a user. * * @return array the param type and null property * @since Moodle 3.2 */ public function get_field_properties() { return array(PARAM_TEXT, NULL_NOT_ALLOWED); } } field/social/lang/en/profilefield_social.php000064400000003441152266356300015145 0ustar00. /** * Strings for component 'profilefield_social' * * @package profilefield_social * @copyright 2020 Bas Brands * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ $string['invalidnetwork'] = 'Invalid social network'; $string['networkinuse'] = 'Social network has already been added'; $string['pluginname'] = 'Social'; $string['networktype'] = 'Network type'; $string['privacy:metadata:profile_field_social:data'] = 'Text area user profile field user data'; $string['privacy:metadata:profile_field_social:dataformat'] = 'The format of Text area user profile field user data'; $string['privacy:metadata:profile_field_social:fieldid'] = 'The ID of the profile field'; $string['privacy:metadata:profile_field_social:tableexplanation'] = 'Additional profile data'; $string['privacy:metadata:profile_field_social:userid'] = 'The ID of the user whose data is stored by the social user profile field'; $string['aimid'] = 'AIM ID'; $string['yahooid'] = 'Yahoo ID'; $string['skypeid'] = 'Skype ID'; $string['icqnumber'] = 'ICQ number'; $string['msnid'] = 'MSN ID'; $string['webpage'] = 'Website'; field/social/version.php000064400000002042152266356300011305 0ustar00. /** * Version information for the social profile field. * * @package profilefield_social * @copyright 2020 Bas Brands * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); $plugin->version = 2025041400; $plugin->requires = 2025040800; $plugin->component = 'profilefield_social'; field/social/classes/privacy/provider.php000064400000017213152266356300014572 0ustar00. /** * Privacy class for requesting user data. * * @package profilefield_social * @copyright 2020 Bas Brands * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace profilefield_social\privacy; defined('MOODLE_INTERNAL') || die(); use \core_privacy\local\metadata\collection; use \core_privacy\local\request\contextlist; use \core_privacy\local\request\approved_contextlist; use core_privacy\local\request\userlist; use core_privacy\local\request\approved_userlist; /** * Privacy class for requesting user data. * * @copyright 2018 Mihail Geshoski * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class provider implements \core_privacy\local\metadata\provider, \core_privacy\local\request\core_userlist_provider, \core_privacy\local\request\plugin\provider { /** * Returns meta data about this system. * * @param collection $collection The initialised collection to add items to. * @return collection A listing of user data stored through this system. */ public static function get_metadata(collection $collection): collection { return $collection->add_database_table('user_info_data', [ 'userid' => 'privacy:metadata:profile_field_social:userid', 'fieldid' => 'privacy:metadata:profile_field_social:fieldid', 'data' => 'privacy:metadata:profile_field_social:data', 'dataformat' => 'privacy:metadata:profile_field_social:dataformat' ], 'privacy:metadata:profile_field_social:tableexplanation'); } /** * Get the list of contexts that contain user information for the specified user. * * @param int $userid The user to search. * @return contextlist $contextlist The contextlist containing the list of contexts used in this plugin. */ public static function get_contexts_for_userid(int $userid): contextlist { $sql = "SELECT ctx.id FROM {user_info_data} uda JOIN {user_info_field} uif ON uda.fieldid = uif.id JOIN {context} ctx ON ctx.instanceid = uda.userid AND ctx.contextlevel = :contextlevel WHERE uda.userid = :userid AND uif.datatype = :datatype"; $params = [ 'userid' => $userid, 'contextlevel' => CONTEXT_USER, 'datatype' => 'social' ]; $contextlist = new contextlist(); $contextlist->add_from_sql($sql, $params); return $contextlist; } /** * Get the list of users within a specific context. * * @param userlist $userlist The userlist containing the list of users who have data in this context/plugin combination. */ public static function get_users_in_context(userlist $userlist) { $context = $userlist->get_context(); if (!$context instanceof \context_user) { return; } $sql = "SELECT uda.userid FROM {user_info_data} uda JOIN {user_info_field} uif ON uda.fieldid = uif.id WHERE uda.userid = :userid AND uif.datatype = :datatype"; $params = [ 'userid' => $context->instanceid, 'datatype' => 'social' ]; $userlist->add_from_sql('userid', $sql, $params); } /** * Export all user data for the specified user, in the specified contexts. * * @param approved_contextlist $contextlist The approved contexts to export information for. */ public static function export_user_data(approved_contextlist $contextlist) { $user = $contextlist->get_user(); foreach ($contextlist->get_contexts() as $context) { // Check if the context is a user context. if ($context->contextlevel == CONTEXT_USER && $context->instanceid == $user->id) { $results = static::get_records($user->id); foreach ($results as $result) { $data = (object) [ 'name' => $result->name, 'description' => $result->description, 'data' => $result->data ]; \core_privacy\local\request\writer::with_context($context)->export_data([ get_string('pluginname', 'profilefield_social')], $data); } } } } /** * Delete all user data which matches the specified context. * * @param context $context A user context. */ public static function delete_data_for_all_users_in_context(\context $context) { // Delete data only for user context. if ($context->contextlevel == CONTEXT_USER) { static::delete_data($context->instanceid); } } /** * Delete multiple users within a single context. * * @param approved_userlist $userlist The approved context and user information to delete information for. */ public static function delete_data_for_users(approved_userlist $userlist) { $context = $userlist->get_context(); if ($context instanceof \context_user) { static::delete_data($context->instanceid); } } /** * Delete all user data for the specified user, in the specified contexts. * * @param approved_contextlist $contextlist The approved contexts and user information to delete information for. */ public static function delete_data_for_user(approved_contextlist $contextlist) { $user = $contextlist->get_user(); foreach ($contextlist->get_contexts() as $context) { // Check if the context is a user context. if ($context->contextlevel == CONTEXT_USER && $context->instanceid == $user->id) { static::delete_data($context->instanceid); } } } /** * Delete data related to a userid. * * @param int $userid The user ID */ protected static function delete_data($userid) { global $DB; $params = [ 'userid' => $userid, 'datatype' => 'social' ]; $DB->delete_records_select('user_info_data', "fieldid IN ( SELECT id FROM {user_info_field} WHERE datatype = :datatype) AND userid = :userid", $params); } /** * Get records related to this plugin and user. * * @param int $userid The user ID * @return array An array of records. */ protected static function get_records($userid) { global $DB; $sql = "SELECT * FROM {user_info_data} uda JOIN {user_info_field} uif ON uda.fieldid = uif.id WHERE uda.userid = :userid AND uif.datatype = :datatype"; $params = [ 'userid' => $userid, 'datatype' => 'social' ]; return $DB->get_records_sql($sql, $params); } } field/social/classes/helper.php000064400000004672152266356300012547 0ustar00. /** * Contains class profilefield_social\networks * * @package profilefield_social * @copyright 2020 Bas Brands * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace profilefield_social; /** * helper class for social profile fields. * * @copyright 2020 Bas Brands * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class helper { /** * Get the available social networks * * @return array list of social networks. */ public static function get_networks(): array { return [ 'icq' => get_string('icqnumber', 'profilefield_social'), 'msn' => get_string('msnid', 'profilefield_social'), 'aim' => get_string('aimid', 'profilefield_social'), 'yahoo' => get_string('yahooid', 'profilefield_social'), 'skype' => get_string('skypeid', 'profilefield_social'), 'url' => get_string('webpage', 'profilefield_social'), ]; } /** * Get the translated fieldname string for a network. * * @param string $fieldname Network short name. * @return string network name. */ public static function get_fieldname(string $fieldname): string { $networks = self::get_networks(); return $networks[$fieldname]; } /** * Get the available network url formats. * * @return array list network url strings. */ public static function get_network_urls(): array { return [ 'skype' => '%%PLAIN%%', 'icq' => '%%PLAIN%%', 'url' => '%%PLAIN%%' ]; } } field/social/define.class.php000064400000006544152266356300012171 0ustar00. /** * Textarea profile field define. * * @package profilefield_social * @copyright 2020 Bas Brands * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ /** * Class profile_define_social. * * @copyright 2020 Bas Brands * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class profile_define_social extends profile_define_base { /** * Prints out the form snippet for the part of creating or editing a profile field common to all data types. * * @param MoodleQuickForm $form instance of the moodleform class */ public function define_form_common(&$form) { $availablenetworks = profilefield_social\helper::get_networks(); $networks = array_merge(['0' => get_string('select')], $availablenetworks); $form->addElement('hidden', 'defaultdata', ''); $form->setType('defaultdata', PARAM_TEXT); $form->addElement('select', 'param1', get_string('networktype', 'profilefield_social'), $networks); $form->addRule('param1', get_string('required'), 'required', null, 'client'); $form->setType('param1', PARAM_TEXT); parent::define_form_common($form); $form->removeElement('name'); } /** * Alter form based on submitted or existing data. * * @param MoodleQuickForm $form */ public function define_after_data(&$form) { if (isset($form->_defaultValues['name'])) { $form->setDefault('param1', $form->_defaultValues['name']); } } /** * Validate the data from the add/edit profile field form that is common to all data types. * * Generally this method should not be overwritten by child classes. * * @param stdClass|array $data from the add/edit profile field form * @param array $files * @return array associative array of error messages */ public function define_validate_common($data, $files) { $err = parent::define_validate_common($data, $files); $networks = profilefield_social\helper::get_networks(); if (empty($data->param1) || !array_key_exists($data->param1, $networks)) { $err['param1'] = get_string('invalidnetwork', 'profilefield_social'); } return $err; } /** * Preprocess data from the add/edit profile field form before it is saved. * * This method is a hook for the child classes to overwrite. * * @param array|stdClass $data from the add/edit profile field form * @return array|stdClass processed data object */ public function define_save_preprocess($data) { $data->name = $data->param1; return $data; } } field/social/tests/privacy/provider_test.php000064400000031046152266356300015336 0ustar00. /** * Base class for unit tests for profilefield_social. * * @package profilefield_social * @copyright 2020 Bas Brands * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace profilefield_social\privacy; defined('MOODLE_INTERNAL') || die(); use core_privacy\tests\provider_testcase; use profilefield_social\privacy\provider; use core_privacy\local\request\approved_userlist; /** * Unit tests for user\profile\field\social\classes\privacy\provider.php * * @copyright 2020 Bas Brands * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ final class provider_test extends provider_testcase { /** * Basic setup for these tests. */ public function setUp(): void { parent::setUp(); $this->resetAfterTest(true); } /** * Test getting the context for the user ID related to this plugin. */ public function test_get_contexts_for_userid(): void { global $DB; // Create profile category. $categoryid = $this->add_profile_category(); // Create profile field. $profilefieldid = $this->add_profile_field($categoryid, 'social'); // Create a user. $user = $this->getDataGenerator()->create_user(); $this->add_user_info_data($user->id, $profilefieldid, 'test data'); // Get the field that was created. $userfielddata = $DB->get_records('user_info_data', array('userid' => $user->id)); // Confirm we got the right number of user field data. $this->assertCount(1, $userfielddata); $context = \context_user::instance($user->id); $contextlist = provider::get_contexts_for_userid($user->id); $this->assertEquals($context, $contextlist->current()); } /** * Test that data is exported correctly for this plugin. */ public function test_export_user_data(): void { // Create profile category. $categoryid = $this->add_profile_category(); // Create social profile field. $socialprofilefieldid = $this->add_profile_field($categoryid, 'social'); // Create checkbox profile field. $checkboxprofilefieldid = $this->add_profile_field($categoryid, 'checkbox'); // Create a user. $user = $this->getDataGenerator()->create_user(); $context = \context_user::instance($user->id); // Add social user info data. $this->add_user_info_data($user->id, $socialprofilefieldid, '#icq-1294923'); // Add checkbox user info data. $this->add_user_info_data($user->id, $checkboxprofilefieldid, 'test data'); $writer = \core_privacy\local\request\writer::with_context($context); $this->assertFalse($writer->has_any_data()); $this->export_context_data_for_user($user->id, $context, 'profilefield_social'); $data = $writer->get_data([get_string('pluginname', 'profilefield_social')]); $this->assertCount(3, (array) $data); $this->assertEquals('icq', $data->name); $this->assertEquals('', $data->description); $this->assertEquals('#icq-1294923', $data->data); } /** * Test that user data is deleted using the context. */ public function test_delete_data_for_all_users_in_context(): void { global $DB; // Create profile category. $categoryid = $this->add_profile_category(); // Create social profile field. $socialprofilefieldid = $this->add_profile_field($categoryid, 'social'); // Create checkbox profile field. $checkboxprofilefieldid = $this->add_profile_field($categoryid, 'checkbox'); // Create a user. $user = $this->getDataGenerator()->create_user(); $context = \context_user::instance($user->id); // Add social user info data. $this->add_user_info_data($user->id, $socialprofilefieldid, '#icq-1294923'); // Add checkbox user info data. $this->add_user_info_data($user->id, $checkboxprofilefieldid, 'test data'); // Check that we have two entries. $userinfodata = $DB->get_records('user_info_data', ['userid' => $user->id]); $this->assertCount(2, $userinfodata); provider::delete_data_for_all_users_in_context($context); // Check that the correct profile field has been deleted. $userinfodata = $DB->get_records('user_info_data', ['userid' => $user->id]); $this->assertCount(1, $userinfodata); $this->assertNotEquals('#icq-1294923', reset($userinfodata)->data); } /** * Test that user data is deleted for this user. */ public function test_delete_data_for_user(): void { global $DB; // Create profile category. $categoryid = $this->add_profile_category(); // Create social profile field. $socialprofilefieldid = $this->add_profile_field($categoryid, 'social'); // Create checkbox profile field. $checkboxprofilefieldid = $this->add_profile_field($categoryid, 'checkbox'); // Create a user. $user = $this->getDataGenerator()->create_user(); $context = \context_user::instance($user->id); // Add social user info data. $this->add_user_info_data($user->id, $socialprofilefieldid, '#icq-1294923'); // Add checkbox user info data. $this->add_user_info_data($user->id, $checkboxprofilefieldid, 'test data'); // Check that we have two entries. $userinfodata = $DB->get_records('user_info_data', ['userid' => $user->id]); $this->assertCount(2, $userinfodata); $approvedlist = new \core_privacy\local\request\approved_contextlist($user, 'profilefield_social', [$context->id]); provider::delete_data_for_user($approvedlist); // Check that the correct profile field has been deleted. $userinfodata = $DB->get_records('user_info_data', ['userid' => $user->id]); $this->assertCount(1, $userinfodata); $this->assertNotEquals('#icq-1294923', reset($userinfodata)->data); } /** * Test that only users with a user context are fetched. */ public function test_get_users_in_context(): void { $this->resetAfterTest(); $component = 'profilefield_social'; // Create profile category. $categoryid = $this->add_profile_category(); // Create profile field. $profilefieldid = $this->add_profile_field($categoryid, 'social'); // Create a user. $user = $this->getDataGenerator()->create_user(); $usercontext = \context_user::instance($user->id); // The list of users should not return anything yet (related data still haven't been created). $userlist = new \core_privacy\local\request\userlist($usercontext, $component); provider::get_users_in_context($userlist); $this->assertCount(0, $userlist); $this->add_user_info_data($user->id, $profilefieldid, 'test data'); // The list of users for user context should return the user. provider::get_users_in_context($userlist); $this->assertCount(1, $userlist); $expected = [$user->id]; $actual = $userlist->get_userids(); $this->assertEquals($expected, $actual); // The list of users for system context should not return any users. $systemcontext = \context_system::instance(); $userlist = new \core_privacy\local\request\userlist($systemcontext, $component); provider::get_users_in_context($userlist); $this->assertCount(0, $userlist); } /** * Test that data for users in approved userlist is deleted. */ public function test_delete_data_for_users(): void { $this->resetAfterTest(); $component = 'profilefield_social'; // Create profile category. $categoryid = $this->add_profile_category(); // Create profile field. $profilefieldid = $this->add_profile_field($categoryid, 'social'); // Create user1. $user1 = $this->getDataGenerator()->create_user(); $usercontext1 = \context_user::instance($user1->id); // Create user2. $user2 = $this->getDataGenerator()->create_user(); $usercontext2 = \context_user::instance($user2->id); $this->add_user_info_data($user1->id, $profilefieldid, 'test data'); $this->add_user_info_data($user2->id, $profilefieldid, 'test data'); // The list of users for usercontext1 should return user1. $userlist1 = new \core_privacy\local\request\userlist($usercontext1, $component); provider::get_users_in_context($userlist1); $this->assertCount(1, $userlist1); $expected = [$user1->id]; $actual = $userlist1->get_userids(); $this->assertEquals($expected, $actual); // The list of users for usercontext2 should return user2. $userlist2 = new \core_privacy\local\request\userlist($usercontext2, $component); provider::get_users_in_context($userlist2); $this->assertCount(1, $userlist2); $expected = [$user2->id]; $actual = $userlist2->get_userids(); $this->assertEquals($expected, $actual); // Add userlist1 to the approved user list. $approvedlist = new approved_userlist($usercontext1, $component, $userlist1->get_userids()); // Delete user data using delete_data_for_user for usercontext1. provider::delete_data_for_users($approvedlist); // Re-fetch users in usercontext1 - The user list should now be empty. $userlist1 = new \core_privacy\local\request\userlist($usercontext1, $component); provider::get_users_in_context($userlist1); $this->assertCount(0, $userlist1); // Re-fetch users in usercontext2 - The user list should not be empty (user2). $userlist2 = new \core_privacy\local\request\userlist($usercontext2, $component); provider::get_users_in_context($userlist2); $this->assertCount(1, $userlist2); // User data should be only removed in the user context. $systemcontext = \context_system::instance(); // Add userlist2 to the approved user list in the system context. $approvedlist = new approved_userlist($systemcontext, $component, $userlist2->get_userids()); // Delete user1 data using delete_data_for_user. provider::delete_data_for_users($approvedlist); // Re-fetch users in usercontext2 - The user list should not be empty (user2). $userlist1 = new \core_privacy\local\request\userlist($usercontext2, $component); provider::get_users_in_context($userlist1); $this->assertCount(1, $userlist1); } /** * Add dummy user info data. * * @param int $userid The ID of the user * @param int $fieldid The ID of the field * @param string $data The data */ private function add_user_info_data($userid, $fieldid, $data) { global $DB; $userinfodata = array( 'userid' => $userid, 'fieldid' => $fieldid, 'data' => $data, 'dataformat' => 0 ); $DB->insert_record('user_info_data', $userinfodata); } /** * Add dummy profile category. * * @return int The ID of the profile category */ private function add_profile_category() { $cat = $this->getDataGenerator()->create_custom_profile_field_category(['name' => 'Test category']); return $cat->id; } /** * Add dummy profile field. * * @param int $categoryid The ID of the profile category * @param string $datatype The datatype of the profile field * @return int The ID of the profile field */ private function add_profile_field($categoryid, $datatype) { $data = $this->getDataGenerator()->create_custom_profile_field([ 'datatype' => $datatype, 'shortname' => 'icq', 'name' => 'icq', 'description' => '', 'categoryid' => $categoryid, ]); return $data->id; } } field/social/tests/behat/social_profile_field.feature000064400000002065152266356300017053 0ustar00@core @core_user Feature: Social profile fields can not have a duplicate shortname. In order edit social profile fields properly As an admin I should not be able to create duplicate shortnames for social profile fields. @javascript Scenario: Verify you can edit social profile fields. Given I log in as "admin" When I navigate to "Users > Accounts > User profile fields" in site administration And I click on "Create a new profile field" "link" And I click on "Social" "link" And I set the following fields to these values: | Network type | Yahoo ID | | Short name | yahoo | And I click on "Save changes" "button" And I click on "Create a new profile field" "link" And I click on "Social" "link" And I set the following fields to these values: | Network type | Yahoo ID | | Short name | yahoo | And I click on "Save changes" "button" Then I should see "This short name is already in use" field/social/upgradelib.php000064400000015441152266356300011745 0ustar00. /** * Contains upgrade and install functions for the social profile fields. * * @package profilefield_social * @copyright 2020 Bas Brands * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); require_once("$CFG->dirroot/user/profile/definelib.php"); /** * Create the default category for custom profile fields if it does not exist yet. * * @return int Category ID for social user profile category. */ function user_profile_social_create_info_category(): int { global $DB; $categories = $DB->get_records('user_info_category', null, 'sortorder ASC'); // Check that we have at least one category defined. if (empty($categories)) { $defaultcategory = (object) [ 'name' => get_string('profiledefaultcategory', 'admin'), 'sortorder' => 1 ]; return $DB->insert_record('user_info_category', $defaultcategory); } else { return (int)$DB->get_field_sql('SELECT min(id) from {user_info_category}'); } } /** * Called on upgrade to create new profile fields based on the old user table columns * for icq, msn, aim, skype and url. * * @param string $social Social profile field. */ function user_profile_social_moveto_profilefield($social) { global $DB; $users = $DB->get_records_select('user', "$social IS NOT NULL AND $social != ''"); if (count($users)) { $profilefield = user_profile_social_create_profilefield($social); foreach ($users as $user) { $userinfodata = [ 'userid' => $user->id, 'fieldid' => $profilefield->id, 'data' => $user->$social, 'dataformat' => 0 ]; $user->$social = ''; $DB->update_record('user', $user); $DB->insert_record('user_info_data', $userinfodata); } } } /** * Create an new custom social profile field if it does not exist * * @param string $social Social profile field. * @return object DB record or social profield field. */ function user_profile_social_create_profilefield($social) { global $DB, $CFG; $hiddenfields = explode(',', $CFG->hiddenuserfields); $confignames = [ 'url' => 'webpage', 'icq' => 'icqnumber', 'skype' => 'skypeid', 'yahoo' => 'yahooid', 'aim' => 'aimid', 'msn' => 'msnid', ]; $visible = (in_array($confignames[$social], $hiddenfields)) ? 3 : 2; $categoryid = user_profile_social_create_info_category(); $newfield = (object)[ 'shortname' => $social, 'name' => $social, 'datatype' => 'social', 'description' => '', 'descriptionformat' => 1, 'categoryid' => $categoryid, 'required' => 0, 'locked' => 0, 'visible' => $visible, 'forceunique' => 0, 'signup' => 0, 'defaultdata' => '', 'defaultdataformat' => 0, 'param1' => $social ]; $profilefield = $DB->get_record_sql( 'SELECT * FROM {user_info_field} WHERE datatype = :datatype AND ' . $DB->sql_compare_text('param1') . ' = ' . $DB->sql_compare_text(':social', 40), ['datatype' => 'social', 'social' => $social]); if (!$profilefield) { // Find a new unique shortname. $count = 0; $shortname = $newfield->shortname; while ($field = $DB->get_record('user_info_field', array('shortname' => $shortname))) { $count++; $shortname = $newfield->shortname . '_' . $count; } $newfield->shortname = $shortname; $profileclass = new profile_define_base(); $profileclass->define_save($newfield); profile_reorder_fields(); $profilefield = $DB->get_record_sql( 'SELECT * FROM {user_info_field} WHERE datatype = :datatype AND ' . $DB->sql_compare_text('param1') . ' = ' . $DB->sql_compare_text(':social', 40), ['datatype' => 'social', 'social' => $social]); } if (!$profilefield) { throw new moodle_exception('upgradeerror', 'admin', 'could not create new social profile field'); } return $profilefield; } /** * Update the module availability configuration for all course modules * */ function user_profile_social_update_module_availability() { global $DB; // Use transaction to improve performance if there are many individual database updates. $transaction = $DB->start_delegated_transaction(); // Query all the course_modules entries that have availability set. $rs = $DB->get_recordset_select('course_modules', 'availability IS NOT NULL', [], '', 'id, availability'); foreach ($rs as $mod) { if (isset($mod->availability)) { $availability = json_decode($mod->availability); if (!is_null($availability)) { user_profile_social_update_availability_structure($availability); $newavailability = json_encode($availability); if ($newavailability !== $mod->availability) { $mod->availability = json_encode($availability); $DB->update_record('course_modules', $mod); } } } } $rs->close(); $transaction->allow_commit(); } /** * Loop through the availability info and change all move standard profile * fields for icq, skype, aim, yahoo, msn and url to be custom profile fields. * @param mixed $structure The availability object. */ function user_profile_social_update_availability_structure(&$structure) { if (is_array($structure)) { foreach ($structure as $st) { user_profile_social_update_availability_structure($st); } } foreach ($structure as $key => $value) { if ($key === 'c' && is_array($value)) { user_profile_social_update_availability_structure($value); } if ($key === 'type' && $value === 'profile') { if (isset($structure->sf) && in_array($structure->sf, ['icq', 'skype', 'aim', 'yahoo', 'msn', 'url'])) { $structure->cf = $structure->sf; unset($structure->sf); } } } } field/social/field.class.php000064400000005216152266356300012015 0ustar00. /** * Social profile field define. * * @package profilefield_social * @copyright 2020 Bas Brands * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ /** * Class profile_field_social. * * @copyright 2020 Bas Brands * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class profile_field_social extends profile_field_base { /** * Adds elements for this field type to the edit form. * @param moodleform $mform */ public function edit_field_add($mform) { $mform->addElement('text', $this->inputname, $this->field->name, null, null); if ($this->field->param1 === 'url') { $mform->setType($this->inputname, PARAM_URL); } else { $mform->setType($this->inputname, PARAM_NOTAGS); } } /** * alter the fieldname to be fetched from the language file. * * @param stdClass $field */ public function set_field($field) { $networks = profilefield_social\helper::get_networks(); $field->name = $networks[$field->name]; parent::set_field($field); } /** * Display the data for this field * @return string */ public function display_data() { $network = $this->field->param1; $networkurls = profilefield_social\helper::get_network_urls(); if (array_key_exists($network, $networkurls)) { $pattern = ['%%ENCODED%%', '%%PLAIN%%']; $data = [rawurlencode($this->data), $this->data]; return str_replace($pattern, $data, $networkurls[$network]); } return $this->data; } /** * Return the field type and null properties. * This will be used for validating the data submitted by a user. * * @return array the param type and null property * @since Moodle 3.2 */ public function get_field_properties() { return array(PARAM_TEXT, NULL_NOT_ALLOWED); } } index.php000064400000014231152266356300006375 0ustar00. /** * Manage user profile fields. * @package core_user * @copyright 2007 onwards Shane Elliot {@link http://pukunui.com} * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ require('../../config.php'); require_once($CFG->libdir.'/adminlib.php'); require_once($CFG->dirroot.'/user/profile/lib.php'); require_once($CFG->dirroot.'/user/profile/definelib.php'); admin_externalpage_setup('profilefields'); $action = optional_param('action', '', PARAM_ALPHA); $redirect = $CFG->wwwroot.'/user/profile/index.php'; $strdefaultcategory = get_string('profiledefaultcategory', 'admin'); $strcreatefield = get_string('profilecreatefield', 'admin'); // Do we have any actions to perform before printing the header. switch ($action) { case 'movecategory': $id = required_param('id', PARAM_INT); $dir = required_param('dir', PARAM_ALPHA); if (confirm_sesskey()) { profile_move_category($id, $dir); } redirect($redirect); break; case 'movefield': $id = required_param('id', PARAM_INT); $dir = required_param('dir', PARAM_ALPHA); if (confirm_sesskey()) { profile_move_field($id, $dir); } redirect($redirect); break; case 'deletecategory': $id = required_param('id', PARAM_INT); if (confirm_sesskey()) { profile_delete_category($id); } redirect($redirect, get_string('deleted')); break; case 'deletefield': $id = required_param('id', PARAM_INT); $confirm = optional_param('confirm', 0, PARAM_BOOL); // If no userdata for profile than don't show confirmation. $datacount = $DB->count_records('user_info_data', array('fieldid' => $id)); if (((data_submitted() and $confirm) or ($datacount === 0)) and confirm_sesskey()) { profile_delete_field($id); redirect($redirect, get_string('deleted')); } // Ask for confirmation, as there is user data available for field. $fieldname = $DB->get_field('user_info_field', 'name', array('id' => $id)); $optionsyes = array ('id' => $id, 'confirm' => 1, 'action' => 'deletefield', 'sesskey' => sesskey()); $strheading = get_string('profiledeletefield', 'admin', format_string($fieldname)); $PAGE->navbar->add($strheading); echo $OUTPUT->header(); echo $OUTPUT->heading($strheading); $formcontinue = new single_button(new moodle_url($redirect, $optionsyes), get_string('yes'), 'post'); $formcancel = new single_button(new moodle_url($redirect), get_string('no'), 'get'); echo $OUTPUT->confirm(get_string('profileconfirmfielddeletion', 'admin', $datacount), $formcontinue, $formcancel); echo $OUTPUT->footer(); die; break; default: // Normal form. } // Show all categories. $categories = $DB->get_records('user_info_category', null, 'sortorder ASC'); // Check that we have at least one category defined. if (empty($categories)) { $defaultcategory = new stdClass(); $defaultcategory->name = $strdefaultcategory; $defaultcategory->sortorder = 1; $DB->insert_record('user_info_category', $defaultcategory); redirect($redirect); } // Print the header. echo $OUTPUT->header(); echo $OUTPUT->heading(get_string('profilefields', 'admin')); $outputcategories = []; $options = profile_list_datatypes(); foreach ($categories as $category) { // Category fields. $outputfields = []; if ($fields = $DB->get_records('user_info_field', array('categoryid' => $category->id), 'sortorder ASC')) { foreach ($fields as $field) { $fieldname = format_string($field->name); $component = 'profilefield_' . $field->datatype; $classname = "\\$component\\helper"; if (class_exists($classname) && method_exists($classname, 'get_fieldname')) { $fieldname = $classname::get_fieldname($field->name); } $outputfields[] = [ 'id' => $field->id, 'shortname' => $field->shortname, 'datatype' => $field->datatype, 'name' => $fieldname, 'isfirst' => !count($outputfields), 'islast' => count($outputfields) == count($fields) - 1, ]; } } // Add new field menu. $menu = new \action_menu(); $menu->set_menu_trigger($strcreatefield); foreach ($options as $type => $fieldname) { $action = new \action_menu_link_secondary(new \moodle_url('#'), null, $fieldname, ['data-action' => 'createfield', 'data-categoryid' => $category->id, 'data-datatype' => $type, 'data-datatypename' => $fieldname]); $menu->add($action); } $menu->attributes['class'] .= ' float-start me-1'; // Add category information to the template. $outputcategories[] = [ 'id' => $category->id, 'name' => format_string($category->name), 'fields' => $outputfields, 'hasfields' => count($outputfields), 'isfirst' => !count($outputcategories), 'islast' => count($outputcategories) == count($categories) - 1, 'candelete' => count($categories) > 1, 'addfieldmenu' => $menu->export_for_template($OUTPUT), ]; } echo $OUTPUT->render_from_template('core_user/edit_profile_fields', [ 'categories' => $outputcategories, 'sesskey' => sesskey(), 'baseurl' => (new moodle_url('/user/profile/index.php'))->out(false) ]); echo $OUTPUT->footer(); lang/en/ltiservice_profile.php000064400000002166152267106750012511 0ustar00. /** * Strings for component 'ltiservice_profile', language 'en' * * @package ltiservice_profile * @copyright 2014 Vital Source Technologies http://vitalsource.com * @author Stephen Vickers * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ $string['pluginname'] = 'Tool Consumer Profile LTI Service'; $string['privacy:metadata'] = 'The Tool Consumer Profile LTI Service plugin does not store any personal data.'; version.php000064400000002134152267106750006755 0ustar00. /** * Version information for the ltiservice_profile service. * * @package ltiservice_profile * @copyright 2014 Vital Source Technologies http://vitalsource.com * @author Stephen Vickers * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); $plugin->version = 2025041400; $plugin->requires = 2025040800; $plugin->component = 'ltiservice_profile'; classes/privacy/provider.php000064400000003003152267106750012230 0ustar00. /** * Privacy Subsystem implementation for ltiservice_profile. * * @package ltiservice_profile * @copyright 2018 Mark Nelson * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace ltiservice_profile\privacy; defined('MOODLE_INTERNAL') || die(); /** * Privacy Subsystem for ltiservice_profile implementing null_provider. * * @copyright 2018 Mark Nelson * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class provider implements \core_privacy\local\metadata\null_provider { /** * Get the language string identifier with the component's language * file to explain why this plugin stores no data. * * @return string */ public static function get_reason(): string { return 'privacy:metadata'; } } classes/local/service/profile.php000064400000003670152267106750013125 0ustar00. /** * This file contains a class definition for the Tool Consumer Profile service * * @package ltiservice_profile * @copyright 2014 Vital Source Technologies http://vitalsource.com * @author Stephen Vickers * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace ltiservice_profile\local\service; defined('MOODLE_INTERNAL') || die(); /** * A service implementing the Tool Consumer Profile. * * @package ltiservice_profile * @since Moodle 2.8 * @copyright 2014 Vital Source Technologies http://vitalsource.com * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class profile extends \mod_lti\local\ltiservice\service_base { /** * Class constructor. */ public function __construct() { parent::__construct(); $this->id = 'profile'; $this->name = 'Tool Consumer Profile'; $this->unsigned = true; } /** * Get the resources for this service. * * @return array */ public function get_resources() { if (empty($this->resources)) { $this->resources = array(); $this->resources[] = new \ltiservice_profile\local\resources\profile($this); } return $this->resources; } } classes/local/resources/profile.php000064400000016315152267106750013477 0ustar00. /** * This file contains a class definition for the Tool Consumer Profile resource * * @package ltiservice_profile * @copyright 2014 Vital Source Technologies http://vitalsource.com * @author Stephen Vickers * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace ltiservice_profile\local\resources; use \mod_lti\local\ltiservice\service_base; defined('MOODLE_INTERNAL') || die(); /** * A resource implementing the Tool Consumer Profile. * * @package ltiservice_profile * @since Moodle 2.8 * @copyright 2014 Vital Source Technologies http://vitalsource.com * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class profile extends \mod_lti\local\ltiservice\resource_base { /** * Class constructor. * * @param service_base $service Service instance */ public function __construct($service) { parent::__construct($service); $this->id = 'ToolConsumerProfile'; $this->template = '/profile/{tool_proxy_id}'; $this->variables[] = 'ToolConsumerProfile.url'; $this->formats[] = 'application/vnd.ims.lti.v2.toolconsumerprofile+json'; $this->methods[] = 'GET'; } /** * Get the path for this resource. * * @return string */ public function get_path() { $path = $this->template; $toolproxy = $this->get_service()->get_tool_proxy(); if (!empty($toolproxy)) { $path = str_replace('{tool_proxy_id}', $toolproxy->guid, $path); } return $path; } /** * Execute the request for this resource. * * @param \mod_lti\local\ltiservice\response $response Response object for this request. */ public function execute($response) { global $CFG; $version = service_base::LTI_VERSION2P0; $params = $this->parse_template(); if (optional_param('lti_version', service_base::LTI_VERSION2P0, PARAM_ALPHANUMEXT) != $version) { $ok = false; $response->set_code(400); } else { $toolproxy = lti_get_tool_proxy_from_guid($params['tool_proxy_id']); $ok = $toolproxy !== false; } if ($ok) { $this->get_service()->set_tool_proxy($toolproxy); $response->set_content_type($this->formats[0]); $servicepath = $this->get_service()->get_service_path(); $id = $servicepath . $this->get_path(); $now = date('Y-m-d\TH:iO'); $capabilityofferedarr = explode("\n", $toolproxy->capabilityoffered); $serviceofferedarr = explode("\n", $toolproxy->serviceoffered); $serviceoffered = ''; $sep = ''; $services = \core_component::get_plugin_list('ltiservice'); foreach ($services as $name => $location) { if (in_array($name, $serviceofferedarr)) { $classname = "\\ltiservice_{$name}\\local\\service\\{$name}"; /** @var service_base $service */ $service = new $classname(); $service->set_tool_proxy($toolproxy); $resources = $service->get_resources(); foreach ($resources as $resource) { $formats = implode("\", \"", $resource->get_formats()); $methods = implode("\", \"", $resource->get_methods()); $capabilityofferedarr = array_merge($capabilityofferedarr, $resource->get_variables()); $template = $resource->get_path(); if (!empty($template)) { $path = $servicepath . preg_replace('/[\(\)]/', '', $template); } else { $path = $resource->get_endpoint(); } $serviceoffered .= <<< EOD {$sep} { "@type":"{$resource->get_type()}", "@id":"tcp:{$resource->get_id()}", "endpoint":"{$path}", "format":["{$formats}"], "action":["{$methods}"] } EOD; $sep = ','; } } } $capabilityoffered = implode("\",\n \"", $capabilityofferedarr); if (strlen($capabilityoffered) > 0) { $capabilityoffered = "\n \"{$capabilityoffered}\""; } $urlparts = parse_url($CFG->wwwroot); $orgid = $urlparts['host']; $name = 'Moodle'; $code = 'moodle'; $vendorname = 'Moodle.org'; $vendorcode = 'mdl'; $prodversion = strval($CFG->version); if (!empty($CFG->mod_lti_institution_name)) { $consumername = $CFG->mod_lti_institution_name; $consumerdesc = ''; } else { $consumername = get_site()->fullname; $consumerdesc = strip_tags(get_site()->summary); } $profile = <<< EOD { "@context":[ "http://purl.imsglobal.org/ctx/lti/v2/ToolConsumerProfile", { "tcp":"{$id}#" } ], "@type":"ToolConsumerProfile", "@id":"{$id}", "lti_version":"{$version}", "guid":"{$toolproxy->guid}", "product_instance":{ "guid":"{$orgid}", "product_info":{ "product_name":{ "default_value":"{$name}", "key":"product.name" }, "product_version":"{$prodversion}", "product_family":{ "code":"{$code}", "vendor":{ "code":"{$vendorcode}", "vendor_name":{ "default_value":"{$vendorname}", "key":"product.vendor.name" }, "timestamp":"{$now}" } } }, "service_owner":{ "@id":"ServiceOwner", "service_owner_name":{ "default_value":"{$consumername}", "key":"service_owner.name" }, "description":{ "default_value":"{$consumerdesc}", "key":"service_owner.description" } } }, "capability_offered":[{$capabilityoffered} ], "service_offered":[{$serviceoffered} ] } EOD; $response->set_body($profile); } } /** * Parse a value for custom parameter substitution variables. * * @param string $value String to be parsed * * @return string */ public function parse_value($value) { if (!empty($this->get_service()->get_tool_proxy()) && (strpos($value, '$ToolConsumerProfile.url') !== false)) { $value = str_replace('$ToolConsumerProfile.url', $this->get_endpoint(), $value); } return $value; } }