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.

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

   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   * Instantiable class representing one nestable element (non final) piece of information on backup
  29   */
  30  class backup_nested_element extends base_nested_element implements processable {
  31  
  32      /** @var array To be used in case we pass one in-memory structure */
  33      protected $var_array;
  34      /** @var string */
  35      protected $table;     // Table (without prefix) to fetch records from
  36      /** @var string */
  37      protected $tablesortby; // The field to sort by when using the table methods
  38      /** @var string */
  39      protected $sql;       // Raw SQL to fetch records from
  40      /** @var mixed */
  41      protected $params;    // Unprocessed params as specified in the set_source() call
  42      /** @var array */
  43      protected $procparams;// Processed (path resolved) params array
  44      /** @var array */
  45      protected $aliases;   // Define DB->final element aliases
  46      /** @var array */
  47      protected $fileannotations;   // array of file areas to be searched by file annotations
  48      /** @var int */
  49      protected $counter;   // Number of instances of this element that have been processed
  50      /** @var array */
  51      protected $results;  // Logs the results we encounter during the process.
  52      /** @var stdClass[] */
  53      protected $logs;     // Some log messages that could be retrieved later.
  54  
  55      /**
  56       * Constructor - instantiates one backup_nested_element, specifying its basic info.
  57       *
  58       * @param string $name name of the element
  59       * @param array  $attributes attributes this element will handle (optional, defaults to null)
  60       * @param array  $final_elements this element will handle (optional, defaults to null)
  61       */
  62      public function __construct($name, $attributes = null, $final_elements = null) {
  63          parent::__construct($name, $attributes, $final_elements);
  64          $this->var_array = null;
  65          $this->table     = null;
  66          $this->tablesortby = null;
  67          $this->sql       = null;
  68          $this->params    = null;
  69          $this->procparams= null;
  70          $this->aliases   = array();
  71          $this->fileannotations = array();
  72          $this->counter   = 0;
  73          $this->results  = array();
  74          $this->logs     = array();
  75      }
  76  
  77      /**
  78       * Process the nested element
  79       *
  80       * @param object $processor the processor
  81       * @return void
  82       */
  83      public function process($processor) {
  84          if (!$processor instanceof base_processor) { // No correct processor, throw exception
  85              throw new base_element_struct_exception('incorrect_processor');
  86          }
  87  
  88          $iterator = $this->get_iterator($processor); // Get the iterator over backup-able data
  89  
  90          foreach ($iterator as $key => $values) { // Process each "ocurrrence" of the nested element (recordset or array)
  91  
  92              // Fill the values of the attributes and final elements with the $values from the iterator
  93              $this->fill_values($values);
  94  
  95              // Perform pre-process tasks for the nested_element
  96              $processor->pre_process_nested_element($this);
  97  
  98              // Delegate the process of each attribute
  99              foreach ($this->get_attributes() as $attribute) {
 100                  $attribute->process($processor);
 101              }
 102  
 103              // Main process tasks for the nested element, once its attributes have been processed
 104              $processor->process_nested_element($this);
 105  
 106              // Delegate the process of each final_element
 107              foreach ($this->get_final_elements() as $final_element) {
 108                  $final_element->process($processor);
 109              }
 110  
 111              // Delegate the process to the optigroup
 112              if ($this->get_optigroup()) {
 113                  $this->get_optigroup()->process($processor);
 114              }
 115  
 116              // Delegate the process to each child nested_element
 117              foreach ($this->get_children() as $child) {
 118                  $child->process($processor);
 119              }
 120  
 121              // Perform post-process tasks for the nested element
 122              $processor->post_process_nested_element($this);
 123  
 124              // Everything processed, clean values before next iteration
 125              $this->clean_values();
 126  
 127              // Increment counter for this element
 128              $this->counter++;
 129  
 130              // For root element, check we only have 1 element
 131              if ($this->get_parent() === null && $this->counter > 1) {
 132                  throw new base_element_struct_exception('root_only_one_ocurrence', $this->get_name());
 133              }
 134          }
 135          // Close the iterator (DB recordset / array iterator)
 136          $iterator->close();
 137      }
 138  
 139      /**
 140       * Saves a log message to an array
 141       *
 142       * @see backup_helper::log()
 143       * @param string $message to add to the logs
 144       * @param int $level level of importance {@link backup::LOG_DEBUG} and other constants
 145       * @param mixed $a to be included in $message
 146       * @param int $depth of the message
 147       * @param display $bool supporting translation via get_string() if true
 148       * @return void
 149       */
 150      protected function add_log($message, $level, $a = null, $depth = null, $display = false) {
 151          // Adding the result to the oldest parent.
 152          if ($this->get_parent()) {
 153              $parent = $this->get_grandparent();
 154              $parent->add_log($message, $level, $a, $depth, $display);
 155          } else {
 156              $log = new stdClass();
 157              $log->message = $message;
 158              $log->level = $level;
 159              $log->a = $a;
 160              $log->depth = $depth;
 161              $log->display = $display;
 162              $this->logs[] = $log;
 163          }
 164      }
 165  
 166      /**
 167       * Saves the results to an array
 168       *
 169       * @param array $result associative array
 170       * @return void
 171       */
 172      protected function add_result($result) {
 173          if (is_array($result)) {
 174              // Adding the result to the oldest parent.
 175              if ($this->get_parent()) {
 176                  $parent = $this->get_grandparent();
 177                  $parent->add_result($result);
 178              } else {
 179                  $this->results = array_merge($this->results, $result);
 180              }
 181          }
 182      }
 183  
 184      /**
 185       * Returns the logs
 186       *
 187       * @return array of log objects
 188       */
 189      public function get_logs() {
 190          return $this->logs;
 191      }
 192  
 193      /**
 194       * Returns the results
 195       *
 196       * @return associative array of results
 197       */
 198      public function get_results() {
 199          return $this->results;
 200      }
 201  
 202      public function set_source_array($arr) {
 203          // TODO: Only elements having final elements can set source
 204          $this->var_array = $arr;
 205      }
 206  
 207      public function set_source_table($table, $params, $sortby = null) {
 208          if (!is_array($params)) { // Check we are passing array
 209              throw new base_element_struct_exception('setsourcerequiresarrayofparams');
 210          }
 211          // TODO: Only elements having final elements can set source
 212          $this->table = $table;
 213          $this->procparams = $this->convert_table_params($params);
 214          if ($sortby) {
 215              $this->tablesortby = $sortby;
 216          }
 217      }
 218  
 219      public function set_source_sql($sql, $params) {
 220          if (!is_array($params)) { // Check we are passing array
 221              throw new base_element_struct_exception('setsourcerequiresarrayofparams');
 222          }
 223          // TODO: Only elements having final elements can set source
 224          $this->sql = $sql;
 225          $this->procparams = $this->convert_sql_params($params);
 226      }
 227  
 228      public function set_source_alias($dbname, $finalelementname) {
 229          // Get final element
 230          $finalelement = $this->get_final_element($finalelementname);
 231          if (!$finalelement) { // Final element incorrect, throw exception
 232              throw new base_element_struct_exception('incorrectaliasfinalnamenotfound', $finalelementname);
 233          } else {
 234              $this->aliases[$dbname] = $finalelement;
 235          }
 236      }
 237  
 238      public function annotate_files($component, $filearea, $elementname, $filesctxid = null) {
 239          if (!array_key_exists($component, $this->fileannotations)) {
 240              $this->fileannotations[$component] = array();
 241          }
 242  
 243          if ($elementname !== null) { // Check elementname is valid
 244              $elementname = $this->find_element($elementname); //TODO: no warning here? (skodak)
 245          }
 246  
 247          if (array_key_exists($filearea, $this->fileannotations[$component])) {
 248              throw new base_element_struct_exception('annotate_files_duplicate_annotation', "$component/$filearea/$elementname");
 249          }
 250  
 251          $info = new stdclass();
 252          $info->element   = $elementname;
 253          $info->contextid = $filesctxid;
 254          $this->fileannotations[$component][$filearea] = $info;
 255      }
 256  
 257      public function annotate_ids($itemname, $elementname) {
 258          $element = $this->find_element($elementname);
 259          $element->set_annotation_item($itemname);
 260      }
 261  
 262      /**
 263       * Returns one array containing the element in the
 264       * @backup_structure and the areas to be searched
 265       */
 266      public function get_file_annotations() {
 267          return $this->fileannotations;
 268      }
 269  
 270      public function get_source_array() {
 271          return $this->var_array;
 272      }
 273  
 274      public function get_source_table() {
 275          return $this->table;
 276      }
 277  
 278      public function get_source_table_sortby() {
 279          return $this->tablesortby;
 280      }
 281  
 282      public function get_source_sql() {
 283          return $this->sql;
 284      }
 285  
 286      public function get_counter() {
 287          return $this->counter;
 288      }
 289  
 290      /**
 291       * Simple filler that, matching by name, will fill both attributes and final elements
 292       * depending of this nested element, debugging info about non-matching elements and/or
 293       * elements present in both places. Accept both arrays and objects.
 294       */
 295      public function fill_values($values) {
 296          $values = (array)$values;
 297  
 298          foreach ($values as $key => $value) {
 299              $found = 0;
 300              if ($attribute = $this->get_attribute($key)) { // Set value for attributes
 301                  $attribute->set_value($value);
 302                  $found++;
 303              }
 304              if ($final = $this->get_final_element($key)) { // Set value for final elements
 305                  $final->set_value($value);
 306                  $found++;
 307              }
 308              if (isset($this->aliases[$key])) { // Last chance, set value by processing final element aliases
 309                  $this->aliases[$key]->set_value($value);
 310                  $found++;
 311              }
 312              // Found more than once, notice
 313                  // TODO: Route this through backup loggers
 314              if ($found > 1) {
 315                  debugging('Key found more than once ' . $key, DEBUG_DEVELOPER);
 316              }
 317          }
 318  
 319      }
 320  
 321  // Protected API starts here
 322  
 323      protected function convert_table_params($params) {
 324          return $this->convert_sql_params($params);
 325      }
 326  
 327      protected function convert_sql_params($params) {
 328          $procparams = array(); // Reset processed params
 329          foreach ($params as $key => $param) {
 330              $procparams[$key] = $this->find_element($param);
 331          }
 332          return $procparams;
 333      }
 334  
 335      protected function find_element($param) {
 336          if ($param == backup::VAR_PARENTID) { // Look for first parent having id attribute/final_element
 337              $param = $this->find_first_parent_by_name('id');
 338  
 339          // If the param is array, with key 'sqlparam', return the value without modifications
 340          } else if (is_array($param) && array_key_exists('sqlparam', $param)) {
 341              return $param['sqlparam'];
 342  
 343          } else if (((int)$param) >= 0) {  // Search by path if param isn't a backup::XXX candidate
 344              $param = $this->find_element_by_path($param);
 345          }
 346          return $param; // Return the param unmodified
 347      }
 348  
 349      /**
 350       * Returns one instace of the @base_attribute class to work with
 351       * when attributes are added simply by name
 352       */
 353      protected function get_new_attribute($name) {
 354          return new backup_attribute($name);
 355      }
 356  
 357      /**
 358       * Returns one instace of the @final_element class to work with
 359       * when final_elements are added simply by name
 360       */
 361      protected function get_new_final_element($name) {
 362          return new backup_final_element($name);
 363      }
 364  
 365      /**
 366       * Returns one PHP iterator over each "ocurrence" of this nested
 367       * element (array or DB recordset). Delegated to backup_structure_dbops class
 368       */
 369      protected function get_iterator($processor) {
 370          return backup_structure_dbops::get_iterator($this, $this->procparams, $processor);
 371      }
 372  }