Proyectos de Subversion Moodle

Rev

| Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1 efrain 1
<?php
2
// This file is part of Moodle - http://moodle.org/
3
//
4
// Moodle is free software: you can redistribute it and/or modify
5
// it under the terms of the GNU General Public License as published by
6
// the Free Software Foundation, either version 3 of the License, or
7
// (at your option) any later version.
8
//
9
// Moodle is distributed in the hope that it will be useful,
10
// but WITHOUT ANY WARRANTY; without even the implied warranty of
11
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
// GNU General Public License for more details.
13
//
14
// You should have received a copy of the GNU General Public License
15
// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
16
 
17
namespace core\event;
18
 
19
defined('MOODLE_INTERNAL') || die();
20
 
21
/**
22
 * New event manager class.
23
 *
24
 * @package    core
25
 * @since      Moodle 2.6
26
 * @copyright  2013 Petr Skoda {@link http://skodak.org}
27
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
28
 */
29
 
30
/**
31
 * Class used for event dispatching.
32
 *
33
 * Note: Do NOT use directly in your code, it is intended to be used from
34
 *       base event class only.
35
 */
36
class manager {
37
    /** @var array buffer of event for dispatching */
38
    protected static $buffer = array();
39
 
40
    /** @var array buffer for events that were not sent to external observers when DB transaction in progress */
41
    protected static $extbuffer = array();
42
 
43
    /** @var bool evert dispatching already in progress - prevents nesting */
44
    protected static $dispatching = false;
45
 
46
    /** @var array cache of all observers */
47
    protected static $allobservers = null;
48
 
49
    /** @var bool should we reload observers after the test? */
50
    protected static $reloadaftertest = false;
51
 
52
    /**
53
     * Trigger new event.
54
     *
55
     * @internal to be used only from \core\event\base::trigger() method.
56
     * @param \core\event\base $event
57
     *
58
     * @throws \coding_Exception if used directly.
59
     */
60
    public static function dispatch(\core\event\base $event) {
61
        if (during_initial_install()) {
62
            return;
63
        }
64
        if (!$event->is_triggered() or $event->is_dispatched()) {
65
            throw new \coding_exception('Illegal event dispatching attempted.');
66
        }
67
 
68
        self::$buffer[] = $event;
69
 
70
        if (self::$dispatching) {
71
            return;
72
        }
73
 
74
        self::$dispatching = true;
75
        self::process_buffers();
76
        self::$dispatching = false;
77
    }
78
 
79
    /**
80
     * Notification from DML layer.
81
     * @internal to be used from DML layer only.
82
     */
83
    public static function database_transaction_commited() {
84
        if (self::$dispatching or empty(self::$extbuffer)) {
85
            return;
86
        }
87
 
88
        self::$dispatching = true;
89
        self::process_buffers();
90
        self::$dispatching = false;
91
    }
92
 
93
    /**
94
     * Notification from DML layer.
95
     * @internal to be used from DML layer only.
96
     */
97
    public static function database_transaction_rolledback() {
98
        self::$extbuffer = array();
99
    }
100
 
101
    protected static function process_buffers() {
102
        global $DB, $CFG;
103
        self::init_all_observers();
104
 
105
        while (self::$buffer or self::$extbuffer) {
106
 
107
            $fromextbuffer = false;
108
            $addedtoextbuffer = false;
109
 
110
            if (self::$extbuffer and !$DB->is_transaction_started()) {
111
                $fromextbuffer = true;
112
                $event = reset(self::$extbuffer);
113
                unset(self::$extbuffer[key(self::$extbuffer)]);
114
 
115
            } else if (self::$buffer) {
116
                $event = reset(self::$buffer);
117
                unset(self::$buffer[key(self::$buffer)]);
118
 
119
            } else {
120
                return;
121
            }
122
 
123
            $observingclasses = self::get_observing_classes($event);
124
            foreach ($observingclasses as $observingclass) {
125
                if (!isset(self::$allobservers[$observingclass])) {
126
                    continue;
127
                }
128
                foreach (self::$allobservers[$observingclass] as $observer) {
129
                    if ($observer->internal) {
130
                        if ($fromextbuffer) {
131
                            // Do not send buffered external events to internal handlers,
132
                            // they processed them already.
133
                            continue;
134
                        }
135
                    } else {
136
                        if ($DB->is_transaction_started()) {
137
                            if ($fromextbuffer) {
138
                                // Weird!
139
                                continue;
140
                            }
141
                            // Do not notify external observers while in DB transaction.
142
                            if (!$addedtoextbuffer) {
143
                                self::$extbuffer[] = $event;
144
                                $addedtoextbuffer = true;
145
                            }
146
                            continue;
147
                        }
148
                    }
149
 
150
                    if (isset($observer->includefile) and file_exists($observer->includefile)) {
151
                        include_once($observer->includefile);
152
                    }
153
                    if (is_callable($observer->callable)) {
154
                        try {
155
                            call_user_func($observer->callable, $event);
156
                        } catch (\Exception $e) {
157
                            // Observers are notified before installation and upgrade, this may throw errors.
158
                            if (empty($CFG->upgraderunning)) {
159
                                // Ignore errors during upgrade, otherwise warn developers.
160
                                debugging("Exception encountered in event observer '$observer->callable': ".$e->getMessage(), DEBUG_DEVELOPER, $e->getTrace());
161
                            }
162
                        }
163
                    } else {
164
                        debugging("Can not execute event observer '$observer->callable'");
165
                    }
166
                }
167
            }
168
 
169
            // TODO: Invent some infinite loop protection in case events cross-trigger one another.
170
        }
171
    }
172
 
173
    /**
174
     * Returns list of classes related to this event.
175
     * @param \core\event\base $event
176
     * @return array
177
     */
178
    protected static function get_observing_classes(\core\event\base $event) {
179
        $classname = get_class($event);
180
        $observers = array('\\'.$classname);
181
        while ($classname = get_parent_class($classname)) {
182
            $observers[] = '\\'.$classname;
183
        }
184
        $observers = array_reverse($observers, false);
185
 
186
        return $observers;
187
    }
188
 
189
    /**
190
     * Initialise the list of observers.
191
     */
192
    protected static function init_all_observers() {
193
        global $CFG;
194
 
195
        if (is_array(self::$allobservers)) {
196
            return;
197
        }
198
 
199
        if (!PHPUNIT_TEST and !during_initial_install()) {
200
            $cache = \cache::make('core', 'observers');
201
            $cached = $cache->get('all');
202
            $dirroot = $cache->get('dirroot');
203
            if ($dirroot === $CFG->dirroot and is_array($cached)) {
204
                self::$allobservers = $cached;
205
                return;
206
            }
207
        }
208
 
209
        self::$allobservers = array();
210
 
211
        $plugintypes = \core_component::get_plugin_types();
212
        $plugintypes = array_merge(array('core' => 'not used'), $plugintypes);
213
        $systemdone = false;
214
        foreach ($plugintypes as $plugintype => $ignored) {
215
            if ($plugintype === 'core') {
216
                $plugins['core'] = "$CFG->dirroot/lib";
217
            } else {
218
                $plugins = \core_component::get_plugin_list($plugintype);
219
            }
220
 
221
            foreach ($plugins as $plugin => $fulldir) {
222
                if (!file_exists("$fulldir/db/events.php")) {
223
                    continue;
224
                }
225
                $observers = null;
226
                include("$fulldir/db/events.php");
227
                if (!is_array($observers)) {
228
                    continue;
229
                }
230
                self::add_observers($observers, "$fulldir/db/events.php", $plugintype, $plugin);
231
            }
232
        }
233
 
234
        self::order_all_observers();
235
 
236
        if (!PHPUNIT_TEST and !during_initial_install()) {
237
            $cache->set('all', self::$allobservers);
238
            $cache->set('dirroot', $CFG->dirroot);
239
        }
240
    }
241
 
242
    /**
243
     * Add observers.
244
     * @param array $observers
245
     * @param string $file
246
     * @param string $plugintype Plugin type of the observer.
247
     * @param string $plugin Plugin of the observer.
248
     */
249
    protected static function add_observers(array $observers, $file, $plugintype = null, $plugin = null) {
250
        global $CFG;
251
 
252
        foreach ($observers as $observer) {
253
            if (empty($observer['eventname']) or !is_string($observer['eventname'])) {
254
                debugging("Invalid 'eventname' detected in $file observer definition", DEBUG_DEVELOPER);
255
                continue;
256
            }
257
            if ($observer['eventname'] === '*') {
258
                $observer['eventname'] = '\core\event\base';
259
            }
260
            if (strpos($observer['eventname'], '\\') !== 0) {
261
                $observer['eventname'] = '\\'.$observer['eventname'];
262
            }
263
            if (empty($observer['callback'])) {
264
                debugging("Invalid 'callback' detected in $file observer definition", DEBUG_DEVELOPER);
265
                continue;
266
            }
267
            $o = new \stdClass();
268
            $o->callable = $observer['callback'];
269
            if (!isset($observer['priority'])) {
270
                $o->priority = 0;
271
            } else {
272
                $o->priority = (int)$observer['priority'];
273
            }
274
            if (!isset($observer['internal'])) {
275
                $o->internal = true;
276
            } else {
277
                $o->internal = (bool)$observer['internal'];
278
            }
279
            if (empty($observer['includefile'])) {
280
                $o->includefile = null;
281
            } else {
282
                if ($CFG->admin !== 'admin' and strpos($observer['includefile'], '/admin/') === 0) {
283
                    $observer['includefile'] = preg_replace('|^/admin/|', '/'.$CFG->admin.'/', $observer['includefile']);
284
                }
285
                $observer['includefile'] = $CFG->dirroot . '/' . ltrim($observer['includefile'], '/');
286
                if (!file_exists($observer['includefile'])) {
287
                    debugging("Invalid 'includefile' detected in $file observer definition", DEBUG_DEVELOPER);
288
                    continue;
289
                }
290
                $o->includefile = $observer['includefile'];
291
            }
292
            $o->plugintype = $plugintype;
293
            $o->plugin = $plugin;
294
            self::$allobservers[$observer['eventname']][] = $o;
295
        }
296
    }
297
 
298
    /**
299
     * Reorder observers to allow quick lookup of observer for each event.
300
     */
301
    protected static function order_all_observers() {
302
        foreach (self::$allobservers as $classname => $observers) {
303
            \core_collator::asort_objects_by_property($observers, 'priority', \core_collator::SORT_NUMERIC);
304
            self::$allobservers[$classname] = array_reverse($observers);
305
        }
306
    }
307
 
308
    /**
309
     * Returns all observers in the system. This is only for use for reporting on the list of observers in the system.
310
     *
311
     * @access private
312
     * @return array An array of stdClass with all core observer details.
313
     */
314
    public static function get_all_observers() {
315
        self::init_all_observers();
316
        return self::$allobservers;
317
    }
318
 
319
    /**
320
     * Replace all standard observers.
321
     * @param array $observers
322
     * @return array
323
     *
324
     * @throws \coding_Exception if used outside of unit tests.
325
     */
326
    public static function phpunit_replace_observers(array $observers) {
327
        if (!PHPUNIT_TEST) {
328
            throw new \coding_exception('Cannot override event observers outside of phpunit tests!');
329
        }
330
 
331
        self::phpunit_reset();
332
        self::$allobservers = array();
333
        self::$reloadaftertest = true;
334
 
335
        self::add_observers($observers, 'phpunit');
336
        self::order_all_observers();
337
 
338
        return self::$allobservers;
339
    }
340
 
341
    /**
342
     * Reset everything if necessary.
343
     * @private
344
     *
345
     * @throws \coding_Exception if used outside of unit tests.
346
     */
347
    public static function phpunit_reset() {
348
        if (!PHPUNIT_TEST) {
349
            throw new \coding_exception('Cannot reset event manager outside of phpunit tests!');
350
        }
351
        self::$buffer = array();
352
        self::$extbuffer = array();
353
        self::$dispatching = false;
354
        if (!self::$reloadaftertest) {
355
            self::$allobservers = null;
356
        }
357
        self::$reloadaftertest = false;
358
    }
359
}