Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 4.2.x will end 22 April 2024 (12 months).
  • Bug fixes for security issues in 4.2.x will end 7 October 2024 (18 months).
  • PHP version: minimum PHP 8.0.0 Note: minimum PHP version has increased since Moodle 4.1. PHP 8.1.x is supported too.
   1  <?php
   2  
   3  // This file is part of Moodle - http://moodle.org/
   4  //
   5  // Moodle is free software: you can redistribute it and/or modify
   6  // it under the terms of the GNU General Public License as published by
   7  // the Free Software Foundation, either version 3 of the License, or
   8  // (at your option) any later version.
   9  //
  10  // Moodle is distributed in the hope that it will be useful,
  11  // but WITHOUT ANY WARRANTY; without even the implied warranty of
  12  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  13  // GNU General Public License for more details.
  14  //
  15  // You should have received a copy of the GNU General Public License
  16  // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
  17  
  18  /**
  19   * @package    moodlecore
  20   * @subpackage backup-structure
  21   * @copyright  2010 onwards Eloy Lafuente (stronk7) {@link http://stronk7.com}
  22   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  23   *
  24   * TODO: Finish phpdocs
  25   */
  26  
  27  /**
  28   * Abstract class representing one nestable element (non final) piece of information
  29   */
  30  abstract class base_nested_element extends base_final_element {
  31  
  32      /** @var array final elements of the element (maps to XML final elements of the tag) */
  33      private $final_elements;
  34  
  35      /** @var array children base_elements of this element (describes structure of the XML file) */
  36      private $children;
  37  
  38      /** @var base_optigroup optional group of this element (branches to be processed conditionally) */
  39      private $optigroup;
  40  
  41      /** @var array elements already used by the base_element, to avoid circular references */
  42      private $used;
  43  
  44      /**
  45       * Constructor - instantiates one base_nested_element, specifying its basic info.
  46       *
  47       * @param string $name name of the element
  48       * @param array  $attributes attributes this element will handle (optional, defaults to null)
  49       * @param array  $final_elements this element will handle (optional, defaults to null)
  50       */
  51      public function __construct($name, $attributes = null, $final_elements = null) {
  52          parent::__construct($name, $attributes);
  53          $this->final_elements = array();
  54          if (!empty($final_elements)) {
  55              $this->add_final_elements($final_elements);
  56          }
  57          $this->children = array();
  58          $this->optigroup = null;
  59          $this->used[] = $name;
  60      }
  61  
  62      /**
  63       * Destroy all circular references. It helps PHP 5.2 a lot!
  64       */
  65      public function destroy() {
  66          // Before reseting anything, call destroy recursively
  67          foreach ($this->children as $child) {
  68              $child->destroy();
  69          }
  70          foreach ($this->final_elements as $element) {
  71              $element->destroy();
  72          }
  73          if ($this->optigroup) {
  74              $this->optigroup->destroy();
  75          }
  76          // Everything has been destroyed recursively, now we can reset safely
  77          $this->children = array();
  78          $this->final_elements = array();
  79          $this->optigroup = null;
  80          // Delegate to parent to destroy other bits
  81          parent::destroy();
  82      }
  83  
  84      protected function get_used() {
  85          return $this->used;
  86      }
  87  
  88      protected function set_used($used) {
  89          $this->used = $used;
  90      }
  91  
  92      protected function add_used($element) {
  93          $this->used = array_merge($this->used, $element->get_used());
  94      }
  95  
  96      protected function check_and_set_used($element) {
  97          // First of all, check the element being added doesn't conflict with own final elements
  98          if (array_key_exists($element->get_name(), $this->final_elements)) {
  99              throw new base_element_struct_exception('baseelementchildnameconflict', $element->get_name());
 100          }
 101          $grandparent = $this->get_grandoptigroupelement_or_grandparent();
 102          if ($existing = array_intersect($grandparent->get_used(), $element->get_used())) { // Check the element isn't being used already
 103              throw new base_element_struct_exception('baseelementexisting', implode($existing));
 104          }
 105          $grandparent->add_used($element);
 106          // If the parent is one optigroup, add the element useds to it too
 107          if ($grandparent->get_parent() instanceof base_optigroup) {
 108              $grandparent->get_parent()->add_used($element);
 109          }
 110  
 111      }
 112  
 113  /// Public API starts here
 114  
 115      public function get_final_elements() {
 116          return $this->final_elements;
 117      }
 118  
 119      public function get_final_element($name) {
 120          if (array_key_exists($name, $this->final_elements)) {
 121              return $this->final_elements[$name];
 122          } else {
 123              return null;
 124          }
 125      }
 126  
 127      public function get_children() {
 128          return $this->children;
 129      }
 130  
 131      public function get_child($name) {
 132          if (array_key_exists($name, $this->children)) {
 133              return $this->children[$name];
 134          } else {
 135              return null;
 136          }
 137      }
 138  
 139      public function get_optigroup() {
 140          return $this->optigroup;
 141      }
 142  
 143      public function add_final_elements($final_elements) {
 144          if ($final_elements instanceof base_final_element || is_string($final_elements)) { // Accept 1 final_element, object or string
 145              $final_elements = array($final_elements);
 146          }
 147          if (is_array($final_elements)) {
 148              foreach ($final_elements as $final_element) {
 149                  if (is_string($final_element)) { // Accept string final_elements
 150                      $final_element = $this->get_new_final_element($final_element);
 151                  }
 152                  if (!($final_element instanceof base_final_element)) {
 153                      throw new base_element_struct_exception('baseelementnofinalelement', get_class($final_element));
 154                  }
 155                  if (array_key_exists($final_element->get_name(), $this->final_elements)) {
 156                      throw new base_element_struct_exception('baseelementexists', $final_element->get_name());
 157                  }
 158                  $this->final_elements[$final_element->get_name()] = $final_element;
 159                  $final_element->set_parent($this);
 160              }
 161          } else {
 162              throw new base_element_struct_exception('baseelementincorrect');
 163          }
 164      }
 165  
 166      public function add_child($element) {
 167          if (!is_object($element) || !($element instanceof base_nested_element)) { // parameter must be a base_nested_element
 168              if (!is_object($element) || !($found = get_class($element))) {
 169                  $found = 'non object';
 170              }
 171              throw new base_element_struct_exception('nestedelementincorrect', $found);
 172          }
 173          $this->check_and_set_used($element);
 174          $this->children[$element->get_name()] = $element;
 175          $element->set_parent($this);
 176      }
 177  
 178      public function add_optigroup($optigroup) {
 179          if (!($optigroup instanceof base_optigroup)) { // parameter must be a base_optigroup
 180              if (!$found = get_class($optigroup)) {
 181                  $found = 'non object';
 182              }
 183              throw new base_element_struct_exception('optigroupincorrect', $found);
 184          }
 185          if ($this->optigroup !== null) {
 186              throw new base_element_struct_exception('optigroupalreadyset', $found);
 187          }
 188          $this->check_and_set_used($optigroup);
 189          $this->optigroup = $optigroup;
 190          $optigroup->set_parent($this);
 191      }
 192  
 193      public function get_value() {
 194          throw new base_element_struct_exception('nestedelementnotvalue');
 195      }
 196  
 197      public function set_value($value) {
 198          throw new base_element_struct_exception('nestedelementnotvalue');
 199      }
 200  
 201      public function clean_value() {
 202          throw new base_element_struct_exception('nestedelementnotvalue');
 203      }
 204  
 205      public function clean_values() {
 206          parent::clean_values();
 207          if (!empty($this->final_elements)) {
 208              foreach ($this->final_elements as $final_element) {
 209                  $final_element->clean_values();
 210              }
 211          }
 212          if (!empty($this->children)) {
 213              foreach ($this->children as $child) {
 214                  $child->clean_values();
 215              }
 216          }
 217          if (!empty($this->optigroup)) {
 218              $this->optigroup->clean_values();
 219          }
 220      }
 221  
 222      public function to_string($showvalue = false) {
 223          $output = parent::to_string($showvalue);
 224          if (!empty($this->final_elements)) {
 225              foreach ($this->final_elements as $final_element) {
 226                  $output .= PHP_EOL . $final_element->to_string($showvalue);
 227              }
 228          }
 229          if (!empty($this->children)) {
 230              foreach ($this->children as $child) {
 231                  $output .= PHP_EOL . $child->to_string($showvalue);
 232              }
 233          }
 234          if (!empty($this->optigroup)) {
 235              $output .= PHP_EOL . $this->optigroup->to_string($showvalue);
 236          }
 237          return $output;
 238      }
 239  
 240  // Implementable API
 241  
 242      /**
 243       * Returns one instace of the @final_element class to work with
 244       * when final_elements are added simply by name
 245       */
 246      abstract protected function get_new_final_element($name);
 247  }
 248  
 249  /**
 250   * base_element exception to control all the errors while building the nested tree
 251   *
 252   * This exception will be thrown each time the base_element class detects some
 253   * inconsistency related with the building of the nested tree representing one base part
 254   * (invalid objects, circular references, double parents...)
 255   */
 256  class base_element_struct_exception extends base_atom_exception {
 257  
 258      /**
 259       * Constructor - instantiates one base_element_struct_exception
 260       *
 261       * @param string $errorcode key for the corresponding error string
 262       * @param object $a extra words and phrases that might be required in the error string
 263       * @param string $debuginfo optional debugging information
 264       */
 265      public function __construct($errorcode, $a = null, $debuginfo = null) {
 266          parent::__construct($errorcode, $a, $debuginfo);
 267      }
 268  }