MDL-21432 backup - allow arbitrary contexts when annotating files. Record orignal...
[moodle.git] / backup / util / structure / backup_nested_element.class.php
1 <?php
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/>.
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  */
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 {
32     protected $var_array; // To be used in case we pass one in-memory structure
33     protected $table;     // Table (without prefix) to fetch records from
34     protected $sql;       // Raw SQL to fetch records from
35     protected $params;    // Unprocessed params as specified in the set_source() call
36     protected $procparams;// Processed (path resolved) params array
37     protected $aliases;   // Define DB->final element aliases
38     protected $fileannotations;   // array of file areas to be searched by file annotations
39     protected $counter;   // Number of instances of this element that have been processed
41     /**
42      * Constructor - instantiates one backup_nested_element, specifying its basic info.
43      *
44      * @param string $name name of the element
45      * @param array  $attributes attributes this element will handle (optional, defaults to null)
46      * @param array  $final_elements this element will handle (optional, defaults to null)
47      */
48     public function __construct($name, $attributes = null, $final_elements = null) {
49         parent::__construct($name, $attributes, $final_elements);
50         $this->var_array = null;
51         $this->table     = null;
52         $this->sql       = null;
53         $this->params    = null;
54         $this->procparams= null;
55         $this->aliases   = array();
56         $this->fileannotations = array();
57         $this->counter   = 0;
58     }
60     public function process($processor) {
61         if (!$processor instanceof base_processor) { // No correct processor, throw exception
62             throw new base_element_struct_exception('incorrect_processor');
63         }
65         $iterator = $this->get_iterator($processor); // Get the iterator over backup-able data
67         foreach ($iterator as $key => $values) { // Process each "ocurrrence" of the nested element (recordset or array)
69             // Fill the values of the attributes and final elements with the $values from the iterator
70             $this->fill_values($values);
72             // Perform pre-process tasks for the nested_element
73             $processor->pre_process_nested_element($this);
75             // Delegate the process of each attribute
76             foreach ($this->get_attributes() as $attribute) {
77                 $attribute->process($processor);
78             }
80             // Main process tasks for the nested element, once its attributes have been processed
81             $processor->process_nested_element($this);
83             // Delegate the process of each final_element
84             foreach ($this->get_final_elements() as $final_element) {
85                 $final_element->process($processor);
86             }
88             // Delegate the process to the optigroup
89             if ($this->get_optigroup()) {
90                 $this->get_optigroup()->process($processor);
91             }
93             // Delegate the process to each child nested_element
94             foreach ($this->get_children() as $child) {
95                 $child->process($processor);
96             }
98             // Perform post-process tasks for the nested element
99             $processor->post_process_nested_element($this);
101             // Everything processed, clean values before next iteration
102             $this->clean_values();
104             // Increment counter for this element
105             $this->counter++;
107             // For root element, check we only have 1 element
108             if ($this->get_parent() === null && $this->counter > 1) {
109                 throw new base_element_struct_exception('root_only_one_ocurrence', $this->get_name());
110             }
111         }
112         // Close the iterator (DB recordset / array iterator)
113         $iterator->close();
114     }
116     public function set_source_array($arr) {
117         // TODO: Only elements having final elements can set source
118         $this->var_array = $arr;
119     }
121     public function set_source_table($table, $params) {
122         if (!is_array($params)) { // Check we are passing array
123             throw new base_element_struct_exception('setsourcerequiresarrayofparams');
124         }
125         // TODO: Only elements having final elements can set source
126         $this->table = $table;
127         $this->procparams = $this->convert_table_params($params);
128     }
130     public function set_source_sql($sql, $params) {
131         if (!is_array($params)) { // Check we are passing array
132             throw new base_element_struct_exception('setsourcerequiresarrayofparams');
133         }
134         // TODO: Only elements having final elements can set source
135         $this->sql = $sql;
136         $this->procparams = $this->convert_sql_params($params);
137     }
139     public function set_source_alias($dbname, $finalelementname) {
140         // Get final element
141         $finalelement = $this->get_final_element($finalelementname);
142         if (!$finalelement) { // Final element incorrect, throw exception
143             throw new base_element_struct_exception('incorrectaliasfinalnamenotfound', $finalelementname);
144         } else {
145             $this->aliases[$dbname] = $finalelement;
146         }
147     }
149     public function annotate_files($component, $filearea, $elementname, $filesctxid = null) {
150         if (!array_key_exists($component, $this->fileannotations)) {
151             $this->fileannotations[$component] = array();
152         }
154         if ($elementname !== null) { // Check elementname is valid
155             $elementname = $this->find_element($elementname); //TODO: no warning here? (skodak)
156         }
158         if (array_key_exists($filearea, $this->fileannotations[$component])) {
159             throw new base_element_struct_exception('annotate_files_duplicate_annotation', "$component/$filearea/$elementname");
160         }
162         $info = new stdclass();
163         $info->element   = $elementname;
164         $info->contextid = $filesctxid;
165         $this->fileannotations[$component][$filearea] = $info;
166     }
168     public function annotate_ids($itemname, $elementname) {
169         $element = $this->find_element($elementname);
170         $element->set_annotation_item($itemname);
171     }
173     /**
174      * Returns one array containing the element in the
175      * @backup_structure and the areas to be searched
176      */
177     public function get_file_annotations() {
178         return $this->fileannotations;
179     }
181     public function get_source_array() {
182         return $this->var_array;
183     }
185     public function get_source_table() {
186         return $this->table;
187     }
189     public function get_source_sql() {
190         return $this->sql;
191     }
193     public function get_counter() {
194         return $this->counter;
195     }
197     /**
198      * Simple filler that, matching by name, will fill both attributes and final elements
199      * depending of this nested element, debugging info about non-matching elements and/or
200      * elements present in both places. Accept both arrays and objects.
201      */
202     public function fill_values($values) {
203         $values = (array)$values;
205         foreach ($values as $key => $value) {
206             $found = 0;
207             if ($attribute = $this->get_attribute($key)) { // Set value for attributes
208                 $attribute->set_value($value);
209                 $found++;
210             }
211             if ($final = $this->get_final_element($key)) { // Set value for final elements
212                 $final->set_value($value);
213                 $found++;
214             }
215             if (isset($this->aliases[$key])) { // Last chance, set value by processing final element aliases
216                 $this->aliases[$key]->set_value($value);
217                 $found++;
218             }
219             // Found more than once, notice
220                 // TODO: Route this through backup loggers
221             if ($found > 1) {
222                 debugging('Key found more than once ' . $key, DEBUG_DEVELOPER);
223             }
224         }
226     }
228 // Protected API starts here
230     protected function convert_table_params($params) {
231         return $this->convert_sql_params($params);
232     }
234     protected function convert_sql_params($params) {
235         $procparams = array(); // Reset processed params
236         foreach ($params as $key => $param) {
237             $procparams[$key] = $this->find_element($param);
238         }
239         return $procparams;
240     }
242     protected function find_element($param) {
243         if ($param == backup::VAR_PARENTID) { // Look for first parent having id attribute/final_element
244             $param = $this->find_first_parent_by_name('id');
246         // If the param is array, with key 'sqlparam', return the value without modifications
247         } else if (is_array($param) && isset($param['sqlparam'])) {
248             return $param['sqlparam'];
250         } else if (((int)$param) >= 0) {  // Search by path if param isn't a backup::XXX candidate
251             $param = $this->find_element_by_path($param);
252         }
253         return $param; // Return the param unmodified
254     }
256     /**
257      * Returns one instace of the @base_attribute class to work with
258      * when attributes are added simply by name
259      */
260     protected function get_new_attribute($name) {
261         return new backup_attribute($name);
262     }
264     /**
265      * Returns one instace of the @final_element class to work with
266      * when final_elements are added simply by name
267      */
268     protected function get_new_final_element($name) {
269         return new backup_final_element($name);
270     }
272     /**
273      * Returns one PHP iterator over each "ocurrence" of this nested
274      * element (array or DB recordset). Delegated to backup_structure_dbops class
275      */
276     protected function get_iterator($processor) {
277         return backup_structure_dbops::get_iterator($this, $this->procparams, $processor);
278     }