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-plan
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
25/**
26 * Abstract class defining the needed stuff to backup one @backup_structure
27 *
28 * TODO: Finish phpdocs
29 */
30abstract class backup_structure_step extends backup_step {
31
32    protected $filename; // Name of the file to be generated
33    protected $contenttransformer; // xml content transformer being used
34                                   // (need it here, apart from xml_writer,
35                                   // thanks to serialized data to process -
36                                   // say thanks to blocks!)
37
38    /**
39     * Constructor - instantiates one object of this class
40     */
41    public function __construct($name, $filename, $task = null) {
42        if (!is_null($task) && !($task instanceof backup_task)) {
43            throw new backup_step_exception('wrong_backup_task_specified');
44        }
45        $this->filename = $filename;
46        $this->contenttransformer = null;
47        parent::__construct($name, $task);
48    }
49
50    public function execute() {
51
52        if (!$this->execute_condition()) { // Check any condition to execute this
53            return;
54        }
55
56        $fullpath = $this->task->get_taskbasepath();
57
58        // We MUST have one fullpath here, else, error
59        if (empty($fullpath)) {
60            throw new backup_step_exception('backup_structure_step_undefined_fullpath');
61        }
62
63        // Append the filename to the fullpath
64        $fullpath = rtrim($fullpath, '/') . '/' . $this->filename;
65
66        // Create output, transformer, writer, processor
67        $xo = new file_xml_output($fullpath);
68        $xt = null;
69        if (class_exists('backup_xml_transformer')) {
70            $xt = new backup_xml_transformer($this->get_courseid());
71            $this->contenttransformer = $xt; // Save the reference to the transformer
72                                             // as far as we are going to need it out
73                                             // from xml_writer (blame serialized data!)
74        }
75        $xw = new xml_writer($xo, $xt);
76        $progress = $this->task->get_progress();
77        $progress->start_progress($this->get_name());
78        $pr = new backup_structure_processor($xw, $progress);
79
80        // Set processor variables from settings
81        foreach ($this->get_settings() as $setting) {
82            $pr->set_var($setting->get_name(), $setting->get_value());
83        }
84        // Add backupid as one more var for processor
85        $pr->set_var(backup::VAR_BACKUPID, $this->get_backupid());
86
87        // Get structure definition
88        $structure = $this->define_structure();
89        if (! $structure instanceof backup_nested_element) {
90            throw new backup_step_exception('backup_structure_step_wrong_structure');
91        }
92
93        // Start writer
94        $xw->start();
95
96        // Process structure definition
97        $structure->process($pr);
98
99        // Get the results from the nested elements
100        $results = $structure->get_results();
101
102        // Get the log messages to append to the log
103        $logs = $structure->get_logs();
104        foreach ($logs as $log) {
105            $this->log($log->message, $log->level, $log->a, $log->depth, $log->display);
106        }
107
108        // Close everything
109        $xw->stop();
110        $progress->end_progress();
111
112        // Destroy the structure. It helps PHP 5.2 memory a lot!
113        $structure->destroy();
114
115        return $results;
116    }
117
118    /**
119     * As far as backup structure steps are implementing backup_plugin stuff, they need to
120     * have the parent task available for wrapping purposes (get course/context....)
121     */
122    public function get_task() {
123        return $this->task;
124    }
125
126// Protected API starts here
127
128    /**
129     * Add plugin structure to any element in the structure backup tree
130     *
131     * @param string $plugintype type of plugin as defined by core_component::get_plugin_types()
132     * @param backup_nested_element $element element in the structure backup tree that
133     *                                       we are going to add plugin information to
134     * @param bool $multiple to define if multiple plugins can produce information
135     *                       for each instance of $element (true) or no (false)
136     */
137    protected function add_plugin_structure($plugintype, $element, $multiple) {
138
139        global $CFG;
140
141        // Check the requested plugintype is a valid one
142        if (!array_key_exists($plugintype, core_component::get_plugin_types($plugintype))) {
143             throw new backup_step_exception('incorrect_plugin_type', $plugintype);
144        }
145
146        // Arrived here, plugin is correct, let's create the optigroup
147        $optigroupname = $plugintype . '_' . $element->get_name() . '_plugin';
148        $optigroup = new backup_optigroup($optigroupname, null, $multiple);
149        $element->add_child($optigroup); // Add optigroup to stay connected since beginning
150
151        // Get all the optigroup_elements, looking across all the plugin dirs
152        $pluginsdirs = core_component::get_plugin_list($plugintype);
153        foreach ($pluginsdirs as $name => $plugindir) {
154            $classname = 'backup_' . $plugintype . '_' . $name . '_plugin';
155            $backupfile = $plugindir . '/backup/moodle2/' . $classname . '.class.php';
156            if (file_exists($backupfile)) {
157                require_once($backupfile);
158                $backupplugin = new $classname($plugintype, $name, $optigroup, $this);
159                // Add plugin returned structure to optigroup
160                $backupplugin->define_plugin_structure($element->get_name());
161            }
162        }
163    }
164
165    /**
166     * Add subplugin structure for a given plugin to any element in the structure backup tree.
167     *
168     * This method allows the injection of subplugins (of a specified plugin) data to any
169     * element in any backup structure.
170     *
171     * NOTE: Initially subplugins were only available for activities (mod), so only the
172     * {@link backup_activity_structure_step} class had support for them, always
173     * looking for /mod/modulenanme subplugins. This new method is a generalization of the
174     * existing one for activities, supporting all subplugins injecting information everywhere.
175     *
176     * @param string $subplugintype type of subplugin as defined in plugin's db/subplugins.json.
177     * @param backup_nested_element $element element in the backup tree (anywhere) that
178     *                                       we are going to add subplugin information to.
179     * @param bool $multiple to define if multiple subplugins can produce information
180     *                       for each instance of $element (true) or no (false).
181     * @param string $plugintype type of the plugin.
182     * @param string $pluginname name of the plugin.
183     * @return void
184     */
185    protected function add_subplugin_structure($subplugintype, $element, $multiple, $plugintype = null, $pluginname = null) {
186        global $CFG;
187        // This global declaration is required, because where we do require_once($backupfile);
188        // That file may in turn try to do require_once($CFG->dirroot ...).
189        // That worked in the past, we should keep it working.
190
191        // Verify if this is a BC call for an activity backup. See NOTE above for this special case.
192        if ($plugintype === null and $pluginname === null) {
193            $plugintype = 'mod';
194            $pluginname = $this->task->get_modulename();
195            // TODO: Once all the calls have been changed to add both not null plugintype and pluginname, add a debugging here.
196        }
197
198        // Check the requested plugintype is a valid one.
199        if (!array_key_exists($plugintype, core_component::get_plugin_types())) {
200             throw new backup_step_exception('incorrect_plugin_type', $plugintype);
201        }
202
203        // Check the requested pluginname, for the specified plugintype, is a valid one.
204        if (!array_key_exists($pluginname, core_component::get_plugin_list($plugintype))) {
205             throw new backup_step_exception('incorrect_plugin_name', array($plugintype, $pluginname));
206        }
207
208        // Check the requested subplugintype is a valid one.
209        $subplugins = core_component::get_subplugins("{$plugintype}_{$pluginname}");
210        if (null === $subplugins) {
211            throw new backup_step_exception('plugin_missing_subplugins_configuration', [$plugintype, $pluginname]);
212        }
213        if (!array_key_exists($subplugintype, $subplugins)) {
214             throw new backup_step_exception('incorrect_subplugin_type', $subplugintype);
215        }
216
217        // Arrived here, subplugin is correct, let's create the optigroup.
218        $optigroupname = $subplugintype . '_' . $element->get_name() . '_subplugin';
219        $optigroup = new backup_optigroup($optigroupname, null, $multiple);
220        $element->add_child($optigroup); // Add optigroup to stay connected since beginning.
221
222        // Every subplugin optionally can have a common/parent subplugin
223        // class for shared stuff.
224        $parentclass = 'backup_' . $plugintype . '_' . $pluginname . '_' . $subplugintype . '_subplugin';
225        $parentfile = core_component::get_component_directory($plugintype . '_' . $pluginname) .
226            '/backup/moodle2/' . $parentclass . '.class.php';
227        if (file_exists($parentfile)) {
228            require_once($parentfile);
229        }
230
231        // Get all the optigroup_elements, looking over all the subplugin dirs.
232        $subpluginsdirs = core_component::get_plugin_list($subplugintype);
233        foreach ($subpluginsdirs as $name => $subpluginsdir) {
234            $classname = 'backup_' . $subplugintype . '_' . $name . '_subplugin';
235            $backupfile = $subpluginsdir . '/backup/moodle2/' . $classname . '.class.php';
236            if (file_exists($backupfile)) {
237                require_once($backupfile);
238                $backupsubplugin = new $classname($subplugintype, $name, $optigroup, $this);
239                // Add subplugin returned structure to optigroup.
240                $backupsubplugin->define_subplugin_structure($element->get_name());
241            }
242        }
243    }
244
245    /**
246     * To conditionally decide if one step will be executed or no
247     *
248     * For steps needing to be executed conditionally, based in dynamic
249     * conditions (at execution time vs at declaration time) you must
250     * override this function. It will return true if the step must be
251     * executed and false if not
252     */
253    protected function execute_condition() {
254        return true;
255    }
256
257    /**
258     * Define the structure to be processed by this backup step.
259     *
260     * @return backup_nested_element
261     */
262    abstract protected function define_structure();
263}
264