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 basis for one execution (backup/restore) task
27 *
28 * TODO: Finish phpdocs
29 */
30abstract class base_task implements checksumable, executable, loggable {
31
32    protected $name;      // One simple name for identification purposes
33    protected $plan;      // Plan this is part of
34    protected $settings;  // One array of base_setting elements to define this task
35    protected $steps;     // One array of base_step elements
36
37    protected $built;     // Flag to know if one task has been built
38    protected $executed;  // Flag to know if one task has been executed
39
40    /**
41     * Constructor - instantiates one object of this class
42     */
43    public function __construct($name, $plan = null) {
44        if (!is_null($plan) && !($plan instanceof base_plan)) {
45            throw new base_task_exception('wrong_base_plan_specified');
46        }
47        $this->name = $name;
48        $this->plan = $plan;
49        $this->settings = array();
50        $this->steps    = array();
51        $this->built    = false;
52        $this->executed = false;
53        if (!is_null($plan)) { // Add the task to the plan if specified
54            $plan->add_task($this);
55        }
56    }
57
58    public function get_name() {
59        return $this->name;
60    }
61
62    public function get_steps() {
63        return $this->steps;
64    }
65
66    public function get_settings() {
67        return $this->settings;
68    }
69
70    /**
71     * Returns the weight of this task, an approximation of the amount of time
72     * it will take. By default this value is 1. It can be increased for longer
73     * tasks.
74     *
75     * @return int Weight
76     */
77    public function get_weight() {
78        return 1;
79    }
80
81    public function get_setting($name) {
82        // First look in task settings
83        $result = null;
84        foreach ($this->settings as $key => $setting) {
85            if ($setting->get_name() == $name) {
86                if ($result != null) {
87                    throw new base_task_exception('multiple_settings_by_name_found', $name);
88                } else {
89                    $result = $setting;
90                }
91            }
92        }
93        if ($result) {
94            return $result;
95        } else {
96            // Fallback to plan settings
97            return $this->plan->get_setting($name);
98        }
99    }
100
101    public function setting_exists($name) {
102        return $this->plan->setting_exists($name);
103    }
104
105    public function get_setting_value($name) {
106        return $this->get_setting($name)->get_value();
107    }
108
109    public function get_courseid() {
110        return $this->plan->get_courseid();
111    }
112
113    public function get_basepath() {
114        return $this->plan->get_basepath();
115    }
116
117    public function get_taskbasepath() {
118        return $this->get_basepath();
119    }
120
121    public function get_logger() {
122        return $this->plan->get_logger();
123    }
124
125    /**
126     * Gets the progress reporter, which can be used to report progress within
127     * the backup or restore process.
128     *
129     * @return \core\progress\base Progress reporting object
130     */
131    public function get_progress() {
132        return $this->plan->get_progress();
133    }
134
135    public function log($message, $level, $a = null, $depth = null, $display = false) {
136        backup_helper::log($message, $level, $a, $depth, $display, $this->get_logger());
137    }
138
139    public function add_step($step) {
140        if (! $step instanceof base_step) {
141            throw new base_task_exception('wrong_base_step_specified');
142        }
143        // link the step with the task
144        $step->set_task($this);
145        $this->steps[] = $step;
146    }
147
148    public function set_plan($plan) {
149        if (! $plan instanceof base_plan) {
150            throw new base_task_exception('wrong_base_plan_specified');
151        }
152        $this->plan = $plan;
153        $this->define_settings(); // Settings are defined when plan & task are linked
154    }
155
156    /**
157     * Function responsible for building the steps of any task
158     * (must set the $built property to true)
159     */
160    public abstract function build();
161
162    /**
163     * Function responsible for executing the steps of any task
164     * (setting the $executed property to  true)
165     */
166    public function execute() {
167        if (!$this->built) {
168            throw new base_task_exception('base_task_not_built', $this->name);
169        }
170        if ($this->executed) {
171            throw new base_task_exception('base_task_already_executed', $this->name);
172        }
173
174        // Starts progress based on the weight of this task and number of steps.
175        $progress = $this->get_progress();
176        $progress->start_progress($this->get_name(), count($this->steps), $this->get_weight());
177        $done = 0;
178
179        // Execute all steps.
180        foreach ($this->steps as $step) {
181            $result = $step->execute();
182            // If step returns array, it will be forwarded to plan
183            // (TODO: shouldn't be array but proper result object)
184            if (is_array($result) and !empty($result)) {
185                $this->add_result($result);
186            }
187            $done++;
188            $progress->progress($done);
189        }
190        // Mark as executed if any step has been executed
191        if (!empty($this->steps)) {
192            $this->executed = true;
193        }
194
195        // Finish progress for this task.
196        $progress->end_progress();
197    }
198
199    /**
200     * Destroy all circular references. It helps PHP 5.2 a lot!
201     */
202    public function destroy() {
203        // Before reseting anything, call destroy recursively
204        foreach ($this->steps as $step) {
205            $step->destroy();
206        }
207        foreach ($this->settings as $setting) {
208            $setting->destroy();
209        }
210        // Everything has been destroyed recursively, now we can reset safely
211        $this->steps = array();
212        $this->settings = array();
213        $this->plan = null;
214    }
215
216    public function is_checksum_correct($checksum) {
217        return $this->calculate_checksum() === $checksum;
218    }
219
220    public function calculate_checksum() {
221        // Let's do it using name and settings and steps
222        return md5($this->name . '-' .
223                   backup_general_helper::array_checksum_recursive($this->settings) .
224                   backup_general_helper::array_checksum_recursive($this->steps));
225    }
226
227    /**
228     * Add the given info to the current plan's results.
229     *
230     * @see base_plan::add_result()
231     * @param array $result associative array describing a result of a task/step
232     */
233    public function add_result($result) {
234        if (!is_null($this->plan)) {
235            $this->plan->add_result($result);
236        } else {
237            debugging('Attempting to add a result of a task not binded with a plan', DEBUG_DEVELOPER);
238        }
239    }
240
241    /**
242     * Return the current plan's results
243     *
244     * @return array|null
245     */
246    public function get_results() {
247        if (!is_null($this->plan)) {
248            return $this->plan->get_results();
249        } else {
250            debugging('Attempting to get results of a task not binded with a plan', DEBUG_DEVELOPER);
251            return null;
252        }
253    }
254
255// Protected API starts here
256
257    /**
258     * This function is invoked on activity creation in order to add all the settings
259     * that are associated with one task. The function will, directly, inject the settings
260     * in the task.
261     */
262    protected abstract function define_settings();
263
264    protected function add_setting($setting) {
265        if (! $setting instanceof base_setting) {
266            throw new base_setting_exception('wrong_base_setting_specified');
267        }
268        $this->settings[] = $setting;
269    }
270}
271
272/*
273 * Exception class used by all the @base_task stuff
274 */
275class base_task_exception extends moodle_exception {
276
277    public function __construct($errorcode, $a=NULL, $debuginfo=null) {
278        parent::__construct($errorcode, '', '', $a, $debuginfo);
279    }
280}
281