Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

  • Bug fixes for general core bugs in 3.9.x will end* 10 May 2021 (12 months).
  • Bug fixes for security issues in 3.9.x will end* 8 May 2023 (36 months).
  • PHP version: minimum PHP 7.2.0 Note: minimum PHP version has increased since Moodle 3.8. PHP 7.3.x and 7.4.x are supported too.

Differences Between: [Versions 39 and 311] [Versions 39 and 400] [Versions 39 and 401] [Versions 39 and 402] [Versions 39 and 403]

   1  <?php
   2  // This file is part of Moodle - http://moodle.org/
   3  //
   4  // Moodle is free software: you can redistribute it and/or modify
   5  // it under the terms of the GNU General Public License as published by
   6  // the Free Software Foundation, either version 3 of the License, or
   7  // (at your option) any later version.
   8  //
   9  // Moodle is distributed in the hope that it will be useful,
  10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
  11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  12  // GNU General Public License for more details.
  13  //
  14  // You should have received a copy of the GNU General Public License
  15  // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
  16  
  17  /**
  18   * This file contains the profile_define_base class.
  19   *
  20   * @package core_user
  21   * @copyright  2007 onwards Shane Elliot {@link http://pukunui.com}
  22   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  23   */
  24  
  25  /**
  26   * Class profile_define_base
  27   *
  28   * @copyright  2007 onwards Shane Elliot {@link http://pukunui.com}
  29   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  30   */
  31  class profile_define_base {
  32  
  33      /**
  34       * Prints out the form snippet for creating or editing a profile field
  35       * @param moodleform $form instance of the moodleform class
  36       */
  37      public function define_form(&$form) {
  38          $form->addElement('header', '_commonsettings', get_string('profilecommonsettings', 'admin'));
  39          $this->define_form_common($form);
  40  
  41          $form->addElement('header', '_specificsettings', get_string('profilespecificsettings', 'admin'));
  42          $this->define_form_specific($form);
  43      }
  44  
  45      /**
  46       * Prints out the form snippet for the part of creating or editing a profile field common to all data types.
  47       *
  48       * @param moodleform $form instance of the moodleform class
  49       */
  50      public function define_form_common(&$form) {
  51  
  52          $strrequired = get_string('required');
  53  
  54          // Accepted values for 'shortname' would follow [a-zA-Z0-9_] pattern,
  55          // but we are accepting any PARAM_TEXT value here,
  56          // and checking [a-zA-Z0-9_] pattern in define_validate_common() function to throw an error when needed.
  57          $form->addElement('text', 'shortname', get_string('profileshortname', 'admin'), 'maxlength="100" size="25"');
  58          $form->addRule('shortname', $strrequired, 'required', null, 'client');
  59          $form->setType('shortname', PARAM_TEXT);
  60  
  61          $form->addElement('text', 'name', get_string('profilename', 'admin'), 'size="50"');
  62          $form->addRule('name', $strrequired, 'required', null, 'client');
  63          $form->setType('name', PARAM_TEXT);
  64  
  65          $form->addElement('editor', 'description', get_string('profiledescription', 'admin'), null, null);
  66  
  67          $form->addElement('selectyesno', 'required', get_string('profilerequired', 'admin'));
  68  
  69          $form->addElement('selectyesno', 'locked', get_string('profilelocked', 'admin'));
  70  
  71          $form->addElement('selectyesno', 'forceunique', get_string('profileforceunique', 'admin'));
  72  
  73          $form->addElement('selectyesno', 'signup', get_string('profilesignup', 'admin'));
  74  
  75          $choices = array();
  76          $choices[PROFILE_VISIBLE_NONE]    = get_string('profilevisiblenone', 'admin');
  77          $choices[PROFILE_VISIBLE_PRIVATE] = get_string('profilevisibleprivate', 'admin');
  78          $choices[PROFILE_VISIBLE_ALL]     = get_string('profilevisibleall', 'admin');
  79          $form->addElement('select', 'visible', get_string('profilevisible', 'admin'), $choices);
  80          $form->addHelpButton('visible', 'profilevisible', 'admin');
  81          $form->setDefault('visible', PROFILE_VISIBLE_ALL);
  82  
  83          $choices = profile_list_categories();
  84          $form->addElement('select', 'categoryid', get_string('profilecategory', 'admin'), $choices);
  85      }
  86  
  87      /**
  88       * Prints out the form snippet for the part of creating or editing a profile field specific to the current data type.
  89       * @param moodleform $form instance of the moodleform class
  90       */
  91      public function define_form_specific($form) {
  92          // Do nothing - overwrite if necessary.
  93      }
  94  
  95      /**
  96       * Validate the data from the add/edit profile field form.
  97       *
  98       * Generally this method should not be overwritten by child classes.
  99       *
 100       * @param stdClass|array $data from the add/edit profile field form
 101       * @param array $files
 102       * @return array associative array of error messages
 103       */
 104      public function define_validate($data, $files) {
 105  
 106          $data = (object)$data;
 107          $err = array();
 108  
 109          $err += $this->define_validate_common($data, $files);
 110          $err += $this->define_validate_specific($data, $files);
 111  
 112          return $err;
 113      }
 114  
 115      /**
 116       * Validate the data from the add/edit profile field form that is common to all data types.
 117       *
 118       * Generally this method should not be overwritten by child classes.
 119       *
 120       * @param stdClass|array $data from the add/edit profile field form
 121       * @param array $files
 122       * @return  array    associative array of error messages
 123       */
 124      public function define_validate_common($data, $files) {
 125          global $DB;
 126  
 127          $err = array();
 128  
 129          // Check the shortname was not truncated by cleaning.
 130          if (empty($data->shortname)) {
 131              $err['shortname'] = get_string('required');
 132  
 133          } else {
 134              // Check allowed pattern (numbers, letters and underscore).
 135              if (!preg_match('/^[a-zA-Z0-9_]+$/', $data->shortname)) {
 136                  $err['shortname'] = get_string('profileshortnameinvalid', 'admin');
 137              } else {
 138                  // Fetch field-record from DB.
 139                  $field = $DB->get_record('user_info_field', array('shortname' => $data->shortname));
 140                  // Check the shortname is unique.
 141                  if ($field and $field->id <> $data->id) {
 142                      $err['shortname'] = get_string('profileshortnamenotunique', 'admin');
 143                  }
 144                  // NOTE: since 2.0 the shortname may collide with existing fields in $USER because we load these fields into
 145                  // $USER->profile array instead.
 146              }
 147          }
 148  
 149          // No further checks necessary as the form class will take care of it.
 150          return $err;
 151      }
 152  
 153      /**
 154       * Validate the data from the add/edit profile field form
 155       * that is specific to the current data type
 156       * @param array $data
 157       * @param array $files
 158       * @return  array    associative array of error messages
 159       */
 160      public function define_validate_specific($data, $files) {
 161          // Do nothing - overwrite if necessary.
 162          return array();
 163      }
 164  
 165      /**
 166       * Alter form based on submitted or existing data
 167       * @param moodleform $mform
 168       */
 169      public function define_after_data(&$mform) {
 170          // Do nothing - overwrite if necessary.
 171      }
 172  
 173      /**
 174       * Add a new profile field or save changes to current field
 175       * @param array|stdClass $data from the add/edit profile field form
 176       */
 177      public function define_save($data) {
 178          global $DB;
 179  
 180          $data = $this->define_save_preprocess($data); // Hook for child classes.
 181  
 182          $old = false;
 183          if (!empty($data->id)) {
 184              $old = $DB->get_record('user_info_field', array('id' => (int)$data->id));
 185          }
 186  
 187          // Check to see if the category has changed.
 188          if (!$old or $old->categoryid != $data->categoryid) {
 189              $data->sortorder = $DB->count_records('user_info_field', array('categoryid' => $data->categoryid)) + 1;
 190          }
 191  
 192          if (empty($data->id)) {
 193              unset($data->id);
 194              $data->id = $DB->insert_record('user_info_field', $data);
 195          } else {
 196              $DB->update_record('user_info_field', $data);
 197          }
 198  
 199          $field = $DB->get_record('user_info_field', array('id' => $data->id));
 200          if ($old) {
 201              \core\event\user_info_field_updated::create_from_field($field)->trigger();
 202          } else {
 203              \core\event\user_info_field_created::create_from_field($field)->trigger();
 204          }
 205      }
 206  
 207      /**
 208       * Preprocess data from the add/edit profile field form before it is saved.
 209       *
 210       * This method is a hook for the child classes to overwrite.
 211       *
 212       * @param array|stdClass $data from the add/edit profile field form
 213       * @return array|stdClass processed data object
 214       */
 215      public function define_save_preprocess($data) {
 216          // Do nothing - overwrite if necessary.
 217          return $data;
 218      }
 219  
 220      /**
 221       * Provides a method by which we can allow the default data in profile_define_* to use an editor
 222       *
 223       * This should return an array of editor names (which will need to be formatted/cleaned)
 224       *
 225       * @return array
 226       */
 227      public function define_editors() {
 228          return array();
 229      }
 230  }
 231  
 232  
 233  
 234  /**
 235   * Reorder the profile fields within a given category starting at the field at the given startorder.
 236   */
 237  function profile_reorder_fields() {
 238      global $DB;
 239  
 240      if ($categories = $DB->get_records('user_info_category')) {
 241          foreach ($categories as $category) {
 242              $i = 1;
 243              if ($fields = $DB->get_records('user_info_field', array('categoryid' => $category->id), 'sortorder ASC')) {
 244                  foreach ($fields as $field) {
 245                      $f = new stdClass();
 246                      $f->id = $field->id;
 247                      $f->sortorder = $i++;
 248                      $DB->update_record('user_info_field', $f);
 249                  }
 250              }
 251          }
 252      }
 253  }
 254  
 255  /**
 256   * Reorder the profile categoriess starting at the category at the given startorder.
 257   */
 258  function profile_reorder_categories() {
 259      global $DB;
 260  
 261      $i = 1;
 262      if ($categories = $DB->get_records('user_info_category', null, 'sortorder ASC')) {
 263          foreach ($categories as $cat) {
 264              $c = new stdClass();
 265              $c->id = $cat->id;
 266              $c->sortorder = $i++;
 267              $DB->update_record('user_info_category', $c);
 268          }
 269      }
 270  }
 271  
 272  /**
 273   * Delete a profile category
 274   * @param int $id of the category to be deleted
 275   * @return bool success of operation
 276   */
 277  function profile_delete_category($id) {
 278      global $DB;
 279  
 280      // Retrieve the category.
 281      if (!$category = $DB->get_record('user_info_category', array('id' => $id))) {
 282          print_error('invalidcategoryid');
 283      }
 284  
 285      if (!$categories = $DB->get_records('user_info_category', null, 'sortorder ASC')) {
 286          print_error('nocate', 'debug');
 287      }
 288  
 289      unset($categories[$category->id]);
 290  
 291      if (!count($categories)) {
 292          return false; // We can not delete the last category.
 293      }
 294  
 295      // Does the category contain any fields.
 296      if ($DB->count_records('user_info_field', array('categoryid' => $category->id))) {
 297          if (array_key_exists($category->sortorder - 1, $categories)) {
 298              $newcategory = $categories[$category->sortorder - 1];
 299          } else if (array_key_exists($category->sortorder + 1, $categories)) {
 300              $newcategory = $categories[$category->sortorder + 1];
 301          } else {
 302              $newcategory = reset($categories); // Get first category if sortorder broken.
 303          }
 304  
 305          $sortorder = $DB->count_records('user_info_field', array('categoryid' => $newcategory->id)) + 1;
 306  
 307          if ($fields = $DB->get_records('user_info_field', array('categoryid' => $category->id), 'sortorder ASC')) {
 308              foreach ($fields as $field) {
 309                  $f = new stdClass();
 310                  $f->id = $field->id;
 311                  $f->sortorder = $sortorder++;
 312                  $f->categoryid = $newcategory->id;
 313                  if ($DB->update_record('user_info_field', $f)) {
 314                      $field->sortorder = $f->sortorder;
 315                      $field->categoryid = $f->categoryid;
 316                      \core\event\user_info_field_updated::create_from_field($field)->trigger();
 317                  }
 318              }
 319          }
 320      }
 321  
 322      // Finally we get to delete the category.
 323      $DB->delete_records('user_info_category', array('id' => $category->id));
 324      profile_reorder_categories();
 325  
 326      \core\event\user_info_category_deleted::create_from_category($category)->trigger();
 327  
 328      return true;
 329  }
 330  
 331  /**
 332   * Deletes a profile field.
 333   * @param int $id
 334   */
 335  function profile_delete_field($id) {
 336      global $DB;
 337  
 338      // Remove any user data associated with this field.
 339      if (!$DB->delete_records('user_info_data', array('fieldid' => $id))) {
 340          print_error('cannotdeletecustomfield');
 341      }
 342  
 343      // Note: Any availability conditions that depend on this field will remain,
 344      // but show the field as missing until manually corrected to something else.
 345  
 346      // Need to rebuild course cache to update the info.
 347      rebuild_course_cache(0, true);
 348  
 349      // Prior to the delete, pull the record for the event.
 350      $field = $DB->get_record('user_info_field', array('id' => $id));
 351  
 352      // Try to remove the record from the database.
 353      $DB->delete_records('user_info_field', array('id' => $id));
 354  
 355      \core\event\user_info_field_deleted::create_from_field($field)->trigger();
 356  
 357      // Reorder the remaining fields in the same category.
 358      profile_reorder_fields();
 359  }
 360  
 361  /**
 362   * Change the sort order of a field
 363   *
 364   * @param int $id of the field
 365   * @param string $move direction of move
 366   * @return bool success of operation
 367   */
 368  function profile_move_field($id, $move) {
 369      global $DB;
 370  
 371      // Get the field object.
 372      if (!$field = $DB->get_record('user_info_field', array('id' => $id))) {
 373          return false;
 374      }
 375      // Count the number of fields in this category.
 376      $fieldcount = $DB->count_records('user_info_field', array('categoryid' => $field->categoryid));
 377  
 378      // Calculate the new sortorder.
 379      if ( ($move == 'up') and ($field->sortorder > 1)) {
 380          $neworder = $field->sortorder - 1;
 381      } else if (($move == 'down') and ($field->sortorder < $fieldcount)) {
 382          $neworder = $field->sortorder + 1;
 383      } else {
 384          return false;
 385      }
 386  
 387      // Retrieve the field object that is currently residing in the new position.
 388      $params = array('categoryid' => $field->categoryid, 'sortorder' => $neworder);
 389      if ($swapfield = $DB->get_record('user_info_field', $params)) {
 390  
 391          // Swap the sortorders.
 392          $swapfield->sortorder = $field->sortorder;
 393          $field->sortorder     = $neworder;
 394  
 395          // Update the field records.
 396          $DB->update_record('user_info_field', $field);
 397          $DB->update_record('user_info_field', $swapfield);
 398  
 399          \core\event\user_info_field_updated::create_from_field($field)->trigger();
 400          \core\event\user_info_field_updated::create_from_field($swapfield)->trigger();
 401      }
 402  
 403      profile_reorder_fields();
 404      return true;
 405  }
 406  
 407  /**
 408   * Change the sort order of a category.
 409   *
 410   * @param int $id of the category
 411   * @param string $move direction of move
 412   * @return bool success of operation
 413   */
 414  function profile_move_category($id, $move) {
 415      global $DB;
 416      // Get the category object.
 417      if (!($category = $DB->get_record('user_info_category', array('id' => $id)))) {
 418          return false;
 419      }
 420  
 421      // Count the number of categories.
 422      $categorycount = $DB->count_records('user_info_category');
 423  
 424      // Calculate the new sortorder.
 425      if (($move == 'up') and ($category->sortorder > 1)) {
 426          $neworder = $category->sortorder - 1;
 427      } else if (($move == 'down') and ($category->sortorder < $categorycount)) {
 428          $neworder = $category->sortorder + 1;
 429      } else {
 430          return false;
 431      }
 432  
 433      // Retrieve the category object that is currently residing in the new position.
 434      if ($swapcategory = $DB->get_record('user_info_category', array('sortorder' => $neworder))) {
 435  
 436          // Swap the sortorders.
 437          $swapcategory->sortorder = $category->sortorder;
 438          $category->sortorder     = $neworder;
 439  
 440          // Update the category records.
 441          $DB->update_record('user_info_category', $category);
 442          $DB->update_record('user_info_category', $swapcategory);
 443  
 444          \core\event\user_info_category_updated::create_from_category($category)->trigger();
 445          \core\event\user_info_category_updated::create_from_category($swapcategory)->trigger();
 446  
 447          return true;
 448      }
 449  
 450      return false;
 451  }
 452  
 453  /**
 454   * Retrieve a list of all the available data types
 455   * @return   array   a list of the datatypes suitable to use in a select statement
 456   */
 457  function profile_list_datatypes() {
 458      $datatypes = array();
 459  
 460      $plugins = core_component::get_plugin_list('profilefield');
 461      foreach ($plugins as $type => $unused) {
 462          $datatypes[$type] = get_string('pluginname', 'profilefield_'.$type);
 463      }
 464      asort($datatypes);
 465  
 466      return $datatypes;
 467  }
 468  
 469  /**
 470   * Retrieve a list of categories and ids suitable for use in a form
 471   * @return   array
 472   */
 473  function profile_list_categories() {
 474      global $DB;
 475      $categories = $DB->get_records_menu('user_info_category', null, 'sortorder ASC', 'id, name');
 476      return array_map('format_string', $categories);
 477  }
 478  
 479  
 480  /**
 481   * Edit a category
 482   *
 483   * @param int $id
 484   * @param string $redirect
 485   */
 486  function profile_edit_category($id, $redirect) {
 487      global $DB, $OUTPUT, $CFG;
 488  
 489      require_once($CFG->dirroot.'/user/profile/index_category_form.php');
 490      $categoryform = new category_form();
 491  
 492      if ($category = $DB->get_record('user_info_category', array('id' => $id))) {
 493          $categoryform->set_data($category);
 494      }
 495  
 496      if ($categoryform->is_cancelled()) {
 497          redirect($redirect);
 498      } else {
 499          if ($data = $categoryform->get_data()) {
 500              if (empty($data->id)) {
 501                  unset($data->id);
 502                  $data->sortorder = $DB->count_records('user_info_category') + 1;
 503                  $data->id = $DB->insert_record('user_info_category', $data, true);
 504  
 505                  $createdcategory = $DB->get_record('user_info_category', array('id' => $data->id));
 506                  \core\event\user_info_category_created::create_from_category($createdcategory)->trigger();
 507              } else {
 508                  $DB->update_record('user_info_category', $data);
 509  
 510                  $updatedcateogry = $DB->get_record('user_info_category', array('id' => $data->id));
 511                  \core\event\user_info_category_updated::create_from_category($updatedcateogry)->trigger();
 512              }
 513              profile_reorder_categories();
 514              redirect($redirect);
 515  
 516          }
 517  
 518          if (empty($id)) {
 519              $strheading = get_string('profilecreatenewcategory', 'admin');
 520          } else {
 521              $strheading = get_string('profileeditcategory', 'admin', format_string($category->name));
 522          }
 523  
 524          // Print the page.
 525          echo $OUTPUT->header();
 526          echo $OUTPUT->heading($strheading);
 527          $categoryform->display();
 528          echo $OUTPUT->footer();
 529          die;
 530      }
 531  
 532  }
 533  
 534  /**
 535   * Edit a profile field.
 536   *
 537   * @param int $id
 538   * @param string $datatype
 539   * @param string $redirect
 540   */
 541  function profile_edit_field($id, $datatype, $redirect) {
 542      global $CFG, $DB, $OUTPUT, $PAGE;
 543  
 544      if (!$field = $DB->get_record('user_info_field', array('id' => $id))) {
 545          $field = new stdClass();
 546          $field->datatype = $datatype;
 547          $field->description = '';
 548          $field->descriptionformat = FORMAT_HTML;
 549          $field->defaultdata = '';
 550          $field->defaultdataformat = FORMAT_HTML;
 551      }
 552  
 553      // Clean and prepare description for the editor.
 554      $field->description = clean_text($field->description, $field->descriptionformat);
 555      $field->description = array('text' => $field->description, 'format' => $field->descriptionformat, 'itemid' => 0);
 556  
 557      require_once($CFG->dirroot.'/user/profile/index_field_form.php');
 558      $fieldform = new field_form(null, $field->datatype);
 559  
 560      // Convert the data format for.
 561      if (is_array($fieldform->editors())) {
 562          foreach ($fieldform->editors() as $editor) {
 563              if (isset($field->$editor)) {
 564                  $field->$editor = clean_text($field->$editor, $field->{$editor.'format'});
 565                  $field->$editor = array('text' => $field->$editor, 'format' => $field->{$editor.'format'}, 'itemid' => 0);
 566              }
 567          }
 568      }
 569  
 570      $fieldform->set_data($field);
 571  
 572      if ($fieldform->is_cancelled()) {
 573          redirect($redirect);
 574  
 575      } else {
 576          if ($data = $fieldform->get_data()) {
 577              require_once($CFG->dirroot.'/user/profile/field/'.$datatype.'/define.class.php');
 578              $newfield = 'profile_define_'.$datatype;
 579              $formfield = new $newfield();
 580  
 581              // Collect the description and format back into the proper data structure from the editor.
 582              // Note: This field will ALWAYS be an editor.
 583              $data->descriptionformat = $data->description['format'];
 584              $data->description = $data->description['text'];
 585  
 586              // Check whether the default data is an editor, this is (currently) only the textarea field type.
 587              if (is_array($data->defaultdata) && array_key_exists('text', $data->defaultdata)) {
 588                  // Collect the default data and format back into the proper data structure from the editor.
 589                  $data->defaultdataformat = $data->defaultdata['format'];
 590                  $data->defaultdata = $data->defaultdata['text'];
 591              }
 592  
 593              // Convert the data format for.
 594              if (is_array($fieldform->editors())) {
 595                  foreach ($fieldform->editors() as $editor) {
 596                      if (isset($field->$editor)) {
 597                          $field->{$editor.'format'} = $field->{$editor}['format'];
 598                          $field->$editor = $field->{$editor}['text'];
 599                      }
 600                  }
 601              }
 602  
 603              $formfield->define_save($data);
 604              profile_reorder_fields();
 605              profile_reorder_categories();
 606              redirect($redirect);
 607          }
 608  
 609          $datatypes = profile_list_datatypes();
 610  
 611          if (empty($id)) {
 612              $strheading = get_string('profilecreatenewfield', 'admin', $datatypes[$datatype]);
 613          } else {
 614              $strheading = get_string('profileeditfield', 'admin', format_string($field->name));
 615          }
 616  
 617          // Print the page.
 618          $PAGE->navbar->add($strheading);
 619          echo $OUTPUT->header();
 620          echo $OUTPUT->heading($strheading);
 621          $fieldform->display();
 622          echo $OUTPUT->footer();
 623          die;
 624      }
 625  }
 626  
 627