Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 3.11.x will end 14 Nov 2022 (12 months plus 6 months extension).
  • Bug fixes for security issues in 3.11.x will end 13 Nov 2023 (18 months plus 12 months extension).
  • PHP version: minimum PHP 7.3.0 Note: minimum PHP version has increased since Moodle 3.10. PHP 7.4.x is supported too.

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