Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 4.3.x will end 7 October 2024 (12 months).
  • Bug fixes for security issues in 4.3.x will end 21 April 2025 (18 months).
  • PHP version: minimum PHP 8.0.0 Note: minimum PHP version has increased since Moodle 4.1. PHP 8.2.x is supported too.

Differences Between: [Versions 310 and 403] [Versions 311 and 403] [Versions 39 and 403] [Versions 400 and 403] [Versions 401 and 403] [Versions 402 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   * Class picture field for database activity
  19   *
  20   * @package    datafield_picture
  21   * @copyright  2005 Martin Dougiamas
  22   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  23   */
  24  
  25  class data_field_picture extends data_field_base {
  26      var $type = 'picture';
  27      var $previewwidth  = 50;
  28      var $previewheight = 50;
  29  
  30      public function supports_preview(): bool {
  31          return true;
  32      }
  33  
  34      public function get_data_content_preview(int $recordid): stdClass {
  35          return (object)[
  36              'id' => 0,
  37              'fieldid' => $this->field->id,
  38              'recordid' => $recordid,
  39              'content' => 'datafield_picture/preview',
  40              'content1' => get_string('sample', 'datafield_picture'),
  41              'content2' => null,
  42              'content3' => null,
  43              'content4' => null,
  44          ];
  45      }
  46  
  47      function display_add_field($recordid = 0, $formdata = null) {
  48          global $CFG, $DB, $OUTPUT, $USER, $PAGE;
  49  
  50          // Necessary for the constants used in args.
  51          require_once($CFG->dirroot . '/repository/lib.php');
  52  
  53          $file        = false;
  54          $content     = false;
  55          $alttext     = '';
  56          $itemid = null;
  57          $fs = get_file_storage();
  58  
  59          if ($formdata) {
  60              $fieldname = 'field_' . $this->field->id . '_file';
  61              $itemid = clean_param($formdata->$fieldname, PARAM_INT);
  62              $fieldname = 'field_' . $this->field->id . '_alttext';
  63              if (isset($formdata->$fieldname)) {
  64                  $alttext = $formdata->$fieldname;
  65              }
  66          } else if ($recordid) {
  67              if (!$content = $DB->get_record('data_content', array('fieldid' => $this->field->id, 'recordid' => $recordid))) {
  68                  // Quickly make one now!
  69                  $content = new stdClass();
  70                  $content->fieldid  = $this->field->id;
  71                  $content->recordid = $recordid;
  72                  $id = $DB->insert_record('data_content', $content);
  73                  $content = $DB->get_record('data_content', array('id' => $id));
  74              }
  75              file_prepare_draft_area($itemid, $this->context->id, 'mod_data', 'content', $content->id);
  76              if (!empty($content->content)) {
  77                  if ($file = $fs->get_file($this->context->id, 'mod_data', 'content', $content->id, '/', $content->content)) {
  78                      $usercontext = context_user::instance($USER->id);
  79  
  80                      if ($thumbfile = $fs->get_file($usercontext->id, 'user', 'draft', $itemid, '/', 'thumb_'.$content->content)) {
  81                          $thumbfile->delete();
  82                      }
  83                  }
  84              }
  85              $alttext = $content->content1;
  86          } else {
  87              $itemid = file_get_unused_draft_itemid();
  88          }
  89          $str = '<div title="' . s($this->field->description) . '">';
  90          $str .= '<fieldset><legend><span class="accesshide">'.$this->field->name;
  91  
  92          if ($this->field->required) {
  93              $str .= '&nbsp;' . get_string('requiredelement', 'form') . '</span></legend>';
  94              $image = $OUTPUT->pix_icon('req', get_string('requiredelement', 'form'));
  95              $str .= html_writer::div($image, 'inline-req');
  96          } else {
  97              $str .= '</span></legend>';
  98          }
  99          $str .= '<noscript>';
 100          if ($file) {
 101              $src = file_encode_url($CFG->wwwroot.'/pluginfile.php/', $this->context->id.'/mod_data/content/'.$content->id.'/'.$file->get_filename());
 102              $str .= '<img width="'.s($this->previewwidth).'" height="'.s($this->previewheight).'" src="'.$src.'" alt="" />';
 103          }
 104          $str .= '</noscript>';
 105  
 106          $options = new stdClass();
 107          $options->maxbytes  = $this->field->param3;
 108          $options->maxfiles  = 1; // Only one picture permitted.
 109          $options->itemid    = $itemid;
 110          $options->accepted_types = array('web_image');
 111          $options->return_types = FILE_INTERNAL;
 112          $options->context = $PAGE->context;
 113          if (!empty($file)) {
 114              $options->filename = $file->get_filename();
 115              $options->filepath = '/';
 116          }
 117  
 118          $fm = new form_filemanager($options);
 119          // Print out file manager.
 120  
 121          $output = $PAGE->get_renderer('core', 'files');
 122          $str .= '<div class="mod-data-input">';
 123          $str .= $output->render($fm);
 124  
 125          $str .= '<div class="mdl-left">';
 126          $str .= '<input type="hidden" name="field_' . $this->field->id . '_file" value="' . s($itemid) . '" />';
 127          $str .= '<label for="field_' . $this->field->id . '_alttext">' .
 128                  get_string('alttext', 'data') .
 129                  '</label>&nbsp;<input type="text" class="form-control" name="field_' .
 130                  $this->field->id . '_alttext" id="field_' . $this->field->id . '_alttext" value="' . s($alttext) . '" />';
 131          $str .= '</div>';
 132          $str .= '</div>';
 133  
 134          $str .= '</fieldset>';
 135          $str .= '</div>';
 136  
 137          return $str;
 138      }
 139  
 140      /**
 141       * Validate the image field type parameters.
 142       *
 143       * This will check for valid numeric values in the width and height fields.
 144       *
 145       * @param stdClass $fieldinput the field input data
 146       * @return array array of error messages if width or height parameters are not numeric
 147       * @throws coding_exception
 148       */
 149      public function validate(stdClass $fieldinput): array {
 150          $errors = [];
 151          // These are the params we have to check if they are numeric, because they represent width and height of the image
 152          // in single and list view.
 153          $widthandheightparams = ['param1', 'param2', 'param4', 'param5'];
 154  
 155          foreach ($widthandheightparams as $param) {
 156              if (!empty($fieldinput->$param) && !is_numeric($fieldinput->$param)) {
 157                  $errors[$param] = get_string('error_invalid' . $param, 'datafield_picture');
 158              }
 159          }
 160          return $errors;
 161      }
 162  
 163      // TODO delete this function and instead subclass data_field_file - see MDL-16493
 164  
 165      function get_file($recordid, $content=null) {
 166          global $DB;
 167          if (empty($content)) {
 168              if (!$content = $DB->get_record('data_content', array('fieldid'=>$this->field->id, 'recordid'=>$recordid))) {
 169                  return null;
 170              }
 171          }
 172          $fs = get_file_storage();
 173          if (!$file = $fs->get_file($this->context->id, 'mod_data', 'content', $content->id, '/', $content->content)) {
 174              return null;
 175          }
 176  
 177          return $file;
 178      }
 179  
 180      function display_search_field($value = '') {
 181          return '<label class="accesshide" for="f_' . $this->field->id . '">' . get_string('fieldname', 'data') . '</label>' .
 182                 '<input type="text" size="16" id="f_' . $this->field->id . '" name="f_' . $this->field->id . '" ' .
 183                 'value="' . s($value) . '" class="form-control"/>';
 184      }
 185  
 186      public function parse_search_field($defaults = null) {
 187          $param = 'f_'.$this->field->id;
 188          if (empty($defaults[$param])) {
 189              $defaults = array($param => '');
 190          }
 191          return optional_param($param, $defaults[$param], PARAM_NOTAGS);
 192      }
 193  
 194      function generate_sql($tablealias, $value) {
 195          global $DB;
 196  
 197          static $i=0;
 198          $i++;
 199          $name = "df_picture_$i";
 200          return array(" ({$tablealias}.fieldid = {$this->field->id} AND ".$DB->sql_like("{$tablealias}.content", ":$name", false).") ", array($name=>"%$value%"));
 201      }
 202  
 203      function display_browse_field($recordid, $template) {
 204          global $OUTPUT;
 205  
 206          $content = $this->get_data_content($recordid);
 207  
 208          if (!$content || empty($content->content)) {
 209              return '';
 210          }
 211  
 212          $alt   = $content->content1;
 213          $title = $alt;
 214  
 215          $width  = $this->field->param1 ? ' width="' . s($this->field->param1) . '" ' : ' ';
 216          $height = $this->field->param2 ? ' height="' . s($this->field->param2) . '" ' : ' ';
 217  
 218          if ($this->preview) {
 219              $imgurl = $OUTPUT->image_url('sample', 'datafield_picture');
 220              return '<img ' . $width . $height . ' src="' . $imgurl . '" alt="' . s($alt) . '" class="list_picture"/>';
 221          }
 222  
 223          if ($template == 'listtemplate') {
 224              $filename = 'thumb_' . $content->content;
 225              // Thumbnails are already converted to the correct width and height.
 226              $width = '';
 227              $height = '';
 228              $url = new moodle_url('/mod/data/view.php', ['d' => $this->field->dataid, 'rid' => $recordid]);
 229          } else {
 230              $filename = $content->content;
 231              $url = null;
 232          }
 233          $imgurl = moodle_url::make_pluginfile_url($this->context->id, 'mod_data', 'content', $content->id, '/', $filename);
 234  
 235          if (!$url) {
 236              $url = $imgurl;
 237          }
 238          $img = '<img ' . $width . $height . ' src="' . $imgurl->out() . '" alt="' . s($alt) .
 239              '" title="' . s($title) . '" class="list_picture"/>';
 240          return '<a class="data-field-link" href="' . $url->out() . '">' . $img . '</a>';
 241      }
 242  
 243      function update_field() {
 244          global $DB, $OUTPUT;
 245  
 246          // Get the old field data so that we can check whether the thumbnail dimensions have changed
 247          $oldfield = $DB->get_record('data_fields', array('id'=>$this->field->id));
 248          $DB->update_record('data_fields', $this->field);
 249  
 250          // Have the thumbnail dimensions changed?
 251          if ($oldfield && ($oldfield->param4 != $this->field->param4 || $oldfield->param5 != $this->field->param5)) {
 252              // Check through all existing records and update the thumbnail
 253              if ($contents = $DB->get_records('data_content', array('fieldid'=>$this->field->id))) {
 254                  $fs = get_file_storage();
 255                  if (count($contents) > 20) {
 256                      echo $OUTPUT->notification(get_string('resizingimages', 'data'), 'notifysuccess');
 257                      echo "\n\n";
 258                      // To make sure that ob_flush() has the desired effect
 259                      ob_flush();
 260                  }
 261                  foreach ($contents as $content) {
 262                      if (!$file = $fs->get_file($this->context->id, 'mod_data', 'content', $content->id, '/', $content->content)) {
 263                          continue;
 264                      }
 265                      if ($thumbfile = $fs->get_file($this->context->id, 'mod_data', 'content', $content->id, '/', 'thumb_'.$content->content)) {
 266                          $thumbfile->delete();
 267                      }
 268                      core_php_time_limit::raise(300);
 269                      // Might be slow!
 270                      $this->update_thumbnail($content, $file);
 271                  }
 272              }
 273          }
 274          return true;
 275      }
 276  
 277      function update_content($recordid, $value, $name='') {
 278          global $CFG, $DB, $USER;
 279  
 280          // Should always be available since it is set by display_add_field before initializing the draft area.
 281          $content = $DB->get_record('data_content', array('fieldid' => $this->field->id, 'recordid' => $recordid));
 282          if (!$content) {
 283              $content = (object)array('fieldid' => $this->field->id, 'recordid' => $recordid);
 284              $content->id = $DB->insert_record('data_content', $content);
 285          }
 286  
 287          $names = explode('_', $name);
 288          switch ($names[2]) {
 289              case 'file':
 290                  $fs = get_file_storage();
 291                  file_save_draft_area_files($value, $this->context->id, 'mod_data', 'content', $content->id);
 292                  $usercontext = context_user::instance($USER->id);
 293                  $files = $fs->get_area_files(
 294                      $this->context->id,
 295                      'mod_data', 'content',
 296                      $content->id,
 297                      'itemid, filepath, filename',
 298                      false);
 299  
 300                  // We expect no or just one file (maxfiles = 1 option is set for the form_filemanager).
 301                  if (count($files) == 0) {
 302                      $content->content = null;
 303                  } else {
 304                      $file = array_values($files)[0];
 305  
 306                      if (count($files) > 1) {
 307                          // This should not happen with a consistent database. Inform admins/developers about the inconsistency.
 308                          debugging('more then one file found in mod_data instance {$this->data->id} picture field (field id: {$this->field->id}) area during update data record {$recordid} (content id: {$content->id})', DEBUG_NORMAL);
 309                      }
 310  
 311                      if ($file->get_imageinfo() === false) {
 312                          $url = new moodle_url('/mod/data/edit.php', array('d' => $this->field->dataid));
 313                          redirect($url, get_string('invalidfiletype', 'error', $file->get_filename()));
 314                      }
 315                      $content->content = $file->get_filename();
 316                      $this->update_thumbnail($content, $file);
 317                  }
 318                  $DB->update_record('data_content', $content);
 319  
 320                  break;
 321  
 322              case 'alttext':
 323                  // only changing alt tag
 324                  $content->content1 = clean_param($value, PARAM_NOTAGS);
 325                  $DB->update_record('data_content', $content);
 326                  break;
 327  
 328              default:
 329                  break;
 330          }
 331      }
 332  
 333      function update_thumbnail($content, $file) {
 334          // (Re)generate thumbnail image according to the dimensions specified in the field settings.
 335          // If thumbnail width and height are BOTH not specified then no thumbnail is generated, and
 336          // additionally an attempted delete of the existing thumbnail takes place.
 337          $fs = get_file_storage();
 338          $filerecord = [
 339              'contextid' => $file->get_contextid(), 'component' => $file->get_component(), 'filearea' => $file->get_filearea(),
 340              'itemid' => $file->get_itemid(), 'filepath' => $file->get_filepath(),
 341              'filename' => 'thumb_' . $file->get_filename(), 'userid' => $file->get_userid()
 342          ];
 343          try {
 344              // This may fail for various reasons.
 345              $newwidth = isset($this->field->param4) ? (int) $this->field->param4 : null;
 346              $newheight = isset($this->field->param5) ? (int) $this->field->param5 : null;
 347              $fs->convert_image($filerecord, $file, $newwidth, $newheight, true);
 348              return true;
 349          } catch (Exception $e) {
 350              debugging($e->getMessage());
 351              return false;
 352          }
 353      }
 354  
 355      /**
 356       * Here we export the text value of a picture field which is the filename of the exported picture.
 357       *
 358       * @param stdClass $record the record which is being exported
 359       * @return string the value which will be stored in the exported file for this field
 360       */
 361      public function export_text_value(stdClass $record): string {
 362          return !empty($record->content) ? $record->content : '';
 363      }
 364  
 365      /**
 366       * Specifies that this field type supports the export of files.
 367       *
 368       * @return bool true which means that file export is being supported by this field type
 369       */
 370      public function file_export_supported(): bool {
 371          return true;
 372      }
 373  
 374      /**
 375       * Exports the file content for file export.
 376       *
 377       * @param stdClass $record the data content record the file belongs to
 378       * @return null|string The file content of the stored file or null if no file should be exported for this record
 379       */
 380      public function export_file_value(stdClass $record): null|string {
 381          $file = $this->get_file($record->id);
 382          return $file ? $file->get_content() : null;
 383      }
 384  
 385      /**
 386       * Specifies that this field type supports the import of files.
 387       *
 388       * @return bool true which means that file import is being supported by this field type
 389       */
 390      public function file_import_supported(): bool {
 391          return true;
 392      }
 393  
 394      /**
 395       * Provides the necessary code for importing a file when importing the content of a mod_data instance.
 396       *
 397       * @param int $contentid the id of the mod_data content record
 398       * @param string $filecontent the content of the file to import as string
 399       * @param string $filename the filename the imported file should get
 400       * @return void
 401       */
 402      public function import_file_value(int $contentid, string $filecontent, string $filename): void {
 403          $filerecord = [
 404              'contextid' => $this->context->id,
 405              'component' => 'mod_data',
 406              'filearea' => 'content',
 407              'itemid' => $contentid,
 408              'filepath' => '/',
 409              'filename' => $filename,
 410          ];
 411          $fs = get_file_storage();
 412          $file = $fs->create_file_from_string($filerecord, $filecontent);
 413          $this->update_thumbnail(null, $file);
 414      }
 415  
 416      function file_ok($path) {
 417          return true;
 418      }
 419  
 420      /**
 421       * Custom notempty function
 422       *
 423       * @param string $value
 424       * @param string $name
 425       * @return bool
 426       */
 427      function notemptyfield($value, $name) {
 428          global $USER;
 429  
 430          $names = explode('_', $name);
 431          if ($names[2] == 'file') {
 432              $usercontext = context_user::instance($USER->id);
 433              $fs = get_file_storage();
 434              $files = $fs->get_area_files($usercontext->id, 'user', 'draft', $value);
 435              return count($files) >= 2;
 436          }
 437          return false;
 438      }
 439  
 440      /**
 441       * Return the plugin configs for external functions.
 442       *
 443       * @return array the list of config parameters
 444       * @since Moodle 3.3
 445       */
 446      public function get_config_for_external() {
 447          // Return all the config parameters.
 448          $configs = [];
 449          for ($i = 1; $i <= 10; $i++) {
 450              $configs["param$i"] = $this->field->{"param$i"};
 451          }
 452          return $configs;
 453      }
 454  }