Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 4.0.x will end 8 May 2023 (12 months).
  • Bug fixes for security issues in 4.0.x will end 13 November 2023 (18 months).
  • PHP version: minimum PHP 7.3.0 Note: the minimum PHP version has increased since Moodle 3.10. PHP 7.4.x is also supported.

Differences Between: [Versions 310 and 400] [Versions 311 and 400] [Versions 39 and 400] [Versions 400 and 402] [Versions 400 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   * Contains class \core\output\inplace_editable
  19   *
  20   * @package    core
  21   * @category   output
  22   * @copyright  2016 Marina Glancy
  23   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  24   */
  25  
  26  namespace core\output;
  27  
  28  use templatable;
  29  use renderable;
  30  use lang_string;
  31  use pix_icon;
  32  
  33  /**
  34   * Class allowing to quick edit a title inline
  35   *
  36   * This class is used for displaying an element that can be in-place edited by the user. To display call:
  37   * echo $OUTPUT->render($element);
  38   * or
  39   * echo $OUTPUT->render_from_template('core/inplace_editable', $element->export_for_template($OUTPUT));
  40   *
  41   * Template core/inplace_editable will automatically load javascript module with the same name
  42   * core/inplace_editable. Javascript module registers a click-listener on edit link and
  43   * then replaces the displayed value with an input field. On "Enter" it sends a request
  44   * to web service core_update_inplace_editable, which invokes the callback from the component.
  45   * Any exception thrown by the web service (or callback) is displayed as an error popup.
  46   *
  47   * Callback {$component}_inplace_editable($itemtype, $itemid, $newvalue) must be present in the lib.php file of
  48   * the component or plugin. It must return instance of this class.
  49   *
  50   * @package    core
  51   * @category   output
  52   * @copyright  2016 Marina Glancy
  53   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  54   */
  55  class inplace_editable implements templatable, renderable {
  56  
  57      /**
  58       * @var string component responsible for diplsying/updating
  59       */
  60      protected $component = null;
  61  
  62      /**
  63       * @var string itemtype inside the component
  64       */
  65      protected $itemtype = null;
  66  
  67      /**
  68       * @var int identifier of the editable element (usually database id)
  69       */
  70      protected $itemid = null;
  71  
  72      /**
  73       * @var string value of the editable element as it is present in the database
  74       */
  75      protected $value = null;
  76  
  77      /**
  78       * @var string value of the editable element as it should be displayed,
  79       * must be formatted and may contain links or other html tags
  80       */
  81      protected $displayvalue = null;
  82  
  83      /**
  84       * @var string label for the input element (for screenreaders)
  85       */
  86      protected $editlabel = null;
  87  
  88      /**
  89       * @var string hint for the input element (for screenreaders)
  90       */
  91      protected $edithint = null;
  92  
  93      /**
  94       * @var pix_icon icon to use to toggle editing
  95       */
  96      protected $editicon = null;
  97  
  98      /**
  99       * @var bool indicates if the current user is allowed to edit this element - set in constructor after permissions are checked
 100       */
 101      protected $editable = false;
 102  
 103      /**
 104       * @var string type of the element - text, toggle or select
 105       */
 106      protected $type = 'text';
 107  
 108      /**
 109       * @var string options for the element, for example new value for the toggle or json-encoded list of options for select
 110       */
 111      protected $options = '';
 112  
 113      /**
 114       * Constructor.
 115       *
 116       * @param string $component name of the component or plugin responsible for the updating of the value (must declare callback)
 117       * @param string $itemtype type of the item inside the component - each component/plugin may implement multiple inplace-editable elements
 118       * @param int $itemid identifier of the item that can be edited in-place
 119       * @param bool $editable whether this value is editable (check capabilities and editing mode), if false, only "displayvalue"
 120       *              will be displayed without anything else
 121       * @param string $displayvalue what needs to be displayed to the user, it must be cleaned, with applied filters (call
 122       *              {@link format_string()}). It may be wrapped in an html link, contain icons or other decorations
 123       * @param string $value what needs to be edited - usually raw value from the database, it may contain multilang tags
 124       * @param lang_string|string $edithint hint (title) that will be displayed under the edit link
 125       * @param lang_string|string $editlabel label for the input element in the editing mode (for screenreaders)
 126       * @param pix_icon|null $editicon icon to use to toggle editing
 127       */
 128      public function __construct($component, $itemtype, $itemid, $editable,
 129              $displayvalue, $value = null, $edithint = null, $editlabel = null, ?pix_icon $editicon = null) {
 130          $this->component = $component;
 131          $this->itemtype = $itemtype;
 132          $this->itemid = $itemid;
 133          $this->editable = $editable;
 134          $this->displayvalue = $displayvalue;
 135          $this->value = $value;
 136          $this->edithint = $edithint;
 137          $this->editlabel = $editlabel;
 138          $this->editicon = $editicon;
 139      }
 140  
 141      /**
 142       * Sets the element type to be a toggle
 143       *
 144       * For toggle element $editlabel is not used.
 145       * $displayvalue must be specified, it can have text or icons but can not contain html links.
 146       *
 147       * Toggle element can have two or more options.
 148       *
 149       * @param array $options toggle options as simple, non-associative array; defaults to array(0,1)
 150       * @return self
 151       */
 152      public function set_type_toggle($options = null) {
 153          if ($options === null) {
 154              $options = array(0, 1);
 155          }
 156          $options = array_values($options);
 157          $idx = array_search($this->value, $options, true);
 158          if ($idx === false) {
 159              throw new \coding_exception('Specified value must be one of the toggle options');
 160          }
 161          $nextvalue = ($idx < count($options) - 1) ? $idx + 1 : 0;
 162  
 163          $this->type = 'toggle';
 164          $this->options = (string)$nextvalue;
 165          return $this;
 166      }
 167  
 168      /**
 169       * Sets the element type to be a dropdown
 170       *
 171       * For select element specifying $displayvalue is optional, if null it will
 172       * be assumed that $displayvalue = $options[$value].
 173       * However displayvalue can still be specified if it needs icons and/or
 174       * html links.
 175       *
 176       * If only one option specified, the element will not be editable.
 177       *
 178       * @param array $options associative array with dropdown options
 179       * @return self
 180       */
 181      public function set_type_select($options) {
 182          if (!array_key_exists($this->value, $options)) {
 183              throw new \coding_exception('Options for select element must contain an option for the specified value');
 184          }
 185          if (count($options) < 2) {
 186              $this->editable = false;
 187          }
 188          $this->type = 'select';
 189  
 190          $pairedoptions = [];
 191          foreach ($options as $key => $value) {
 192              $pairedoptions[] = [
 193                  'key' => $key,
 194                  'value' => $value,
 195              ];
 196          }
 197          $this->options = json_encode($pairedoptions);
 198          if ($this->displayvalue === null) {
 199              $this->displayvalue = $options[$this->value];
 200          }
 201          if ($this->editicon === null) {
 202              $this->editicon = new pix_icon('t/expanded', (string) $this->edithint);
 203          }
 204          return $this;
 205      }
 206  
 207      /**
 208       * Sets the element type to be an autocomplete field
 209       *
 210       * @param array $options associative array with dropdown options
 211       * @param array $attributes associative array with attributes for autoselect field. See AMD module core/form-autocomplete.
 212       * @return self
 213       */
 214      public function set_type_autocomplete($options, $attributes) {
 215          $this->type = 'autocomplete';
 216  
 217          $pairedoptions = [];
 218          foreach ($options as $key => $value) {
 219              $pairedoptions[] = [
 220                  'key' => $key,
 221                  'value' => $value,
 222              ];
 223          }
 224          $this->options = json_encode(['options' => $pairedoptions, 'attributes' => $attributes]);
 225          return $this;
 226      }
 227  
 228      /**
 229       * Whether the link should contain all of the content or not.
 230       */
 231      protected function get_linkeverything() {
 232          if ($this->type === 'toggle') {
 233              return true;
 234          }
 235  
 236          if (preg_match('#<a .*>.*</a>#', $this->displayvalue) === 1) {
 237              return false;
 238          }
 239  
 240          return true;
 241      }
 242  
 243      /**
 244       * Export this data so it can be used as the context for a mustache template (core/inplace_editable).
 245       *
 246       * @param renderer_base $output typically, the renderer that's calling this function
 247       * @return array data context for a mustache template
 248       */
 249      public function export_for_template(\renderer_base $output) {
 250          if (!$this->editable) {
 251              return array(
 252                  'displayvalue' => (string)$this->displayvalue
 253              );
 254          }
 255  
 256          if ($this->editicon === null) {
 257              $this->editicon = new pix_icon('t/editstring', (string) $this->edithint);
 258          }
 259  
 260          return array(
 261              'component' => $this->component,
 262              'itemtype' => $this->itemtype,
 263              'itemid' => $this->itemid,
 264              'displayvalue' => (string)$this->displayvalue,
 265              'value' => (string)$this->value,
 266              'edithint' => (string)$this->edithint,
 267              'editlabel' => (string)$this->editlabel,
 268              'editicon' => $this->editicon->export_for_pix(),
 269              'type' => $this->type,
 270              'options' => $this->options,
 271              'linkeverything' => $this->get_linkeverything() ? 1 : 0,
 272          );
 273      }
 274  
 275      /**
 276       * Renders this element
 277       *
 278       * @param renderer_base $output typically, the renderer that's calling this function
 279       * @return string
 280       */
 281      public function render(\renderer_base $output) {
 282          return $output->render_from_template('core/inplace_editable', $this->export_for_template($output));
 283      }
 284  }