Issues (48)

Security Analysis    not enabled

This project does not seem to handle request data directly as such no vulnerable execution paths were found.

  Cross-Site Scripting
Cross-Site Scripting enables an attacker to inject code into the response of a web-request that is viewed by other users. It can for example be used to bypass access controls, or even to take over other users' accounts.
  File Exposure
File Exposure allows an attacker to gain access to local files that he should not be able to access. These files can for example include database credentials, or other configuration files.
  File Manipulation
File Manipulation enables an attacker to write custom data to files. This potentially leads to injection of arbitrary code on the server.
  Object Injection
Object Injection enables an attacker to inject an object into PHP code, and can lead to arbitrary code execution, file exposure, or file manipulation attacks.
  Code Injection
Code Injection enables an attacker to execute arbitrary code on the server.
  Response Splitting
Response Splitting can be used to send arbitrary responses.
  File Inclusion
File Inclusion enables an attacker to inject custom files into PHP's file loading mechanism, either explicitly passed to include, or for example via PHP's auto-loading mechanism.
  Command Injection
Command Injection enables an attacker to inject a shell command that is execute with the privileges of the web-server. This can be used to expose sensitive data, or gain access of your server.
  SQL Injection
SQL Injection enables an attacker to execute arbitrary SQL code on your database server gaining access to user data, or manipulating user data.
  XPath Injection
XPath Injection enables an attacker to modify the parts of XML document that are read. If that XML document is for example used for authentication, this can lead to further vulnerabilities similar to SQL Injection.
  LDAP Injection
LDAP Injection enables an attacker to inject LDAP statements potentially granting permission to run unauthorized queries, or modify content inside the LDAP tree.
  Header Injection
  Other Vulnerability
This category comprises other attack vectors such as manipulating the PHP runtime, loading custom extensions, freezing the runtime, or similar.
  Regex Injection
Regex Injection enables an attacker to execute arbitrary code in your PHP process.
  XML Injection
XML Injection enables an attacker to read files on your local filesystem including configuration files, or can be abused to freeze your web-server process.
  Variable Injection
Variable Injection enables an attacker to overwrite program variables with custom data, and can lead to further vulnerabilities.
Unfortunately, the security analysis is currently not available for your project. If you are a non-commercial open-source project, please contact support to gain access.

src/Event.php (3 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
/**
3
 * @author Todd Burry <[email protected]>
4
 * @copyright 2009-2014 Vanilla Forums Inc.
5
 * @license MIT
6
 */
7
8
namespace Garden;
9
10
/**
11
 * Contains methods for binding and firing to events.
12
 *
13
 * Addons can create callbacks that bind to events which are calle througout the code to allow
14
 * extension of the application and framework.
15
 */
16
class Event {
17
    /// Constants ///
18
19
    const PRIORITY_LOW = 1000;
20
    const PRIORITY_NORMAL = 100;
21
    const PRIORITY_HIGH = 10;
22
23
    /// Properties ///
24
25
    /**
26
     * All of the event handlers that have been registered.
27
     * @var array An array of event handlers.
28
     */
29
    protected static $handlers = array();
30
31
    /**
32
     * All of the event handlers that still need to be sorted by priority.
33
     * @var array An array of event handler names that need to be sorted.
34
     */
35
    protected static $toSort = array();
36
37
    /// Methods ///
38
39
    /**
40
     * Call a callback with an array of parameters, checking for events to be called with the callback.
41
     *
42
     * This method is similar to {@link call_user_func_array()} but it will fire events that can happen
43
     * before and/or after the callback and can call an event instead of the callback itself to override it.
44
     *
45
     * In order to use events with this method they must be bound with a particular naming convention.
46
     *
47
     * **Modify a function**
48
     *
49
     * - functionname_before
50
     * - functionname
51
     * - functionname_after
52
     *
53
     * **Modify a method call**
54
     *
55
     * - classname_methodname_before
56
     * - classname_methodname
57
     * - classname_methodname_after
58
     *
59
     * @param callable $callback The {@link callable} to be called.
60
     * @param array $args The arguments to be passed to the callback, as an indexed array.
61
     * @return mixed Returns the return value of the callback.
62
     */
63 18
    public static function callUserFuncArray($callback, $args = []) {
64
        // Figure out the event name from the callback.
65 18
        $event_name = static::getEventname($callback);
66 18
        if (!$event_name) {
67 1
            return call_user_func_array($callback, $args);
68
        }
69
70
        // The events could have different args because the event handler can take the object as the first parameter.
71 17
        $event_args = $args;
72
        // If the callback is an object then it gets passed as the first argument.
73 17
        if (is_array($callback) && is_object($callback[0])) {
74 16
            array_unshift($event_args, $callback[0]);
75 16
        }
76
77
        // Fire before events.
78 17
        self::fireArray($event_name.'_before', $event_args);
79
80
        // Call the function.
81 17
        if (static::hasHandler($event_name)) {
82
            // The callback was overridden so fire it.
83 1
            $result = static::fireArray($event_name, $event_args);
84 1
        } else {
85
            // The callback was not overridden so just call the passed callback.
86 16
            $result = call_user_func_array($callback, $args);
87
        }
88
89
        // Fire after events.
90 17
        self::fireArray($event_name.'_after', $event_args);
91
92 17
        return $result;
93
    }
94
95
    /**
96
     * Bind an event handler to an event.
97
     *
98
     * @param string $event The naame of the event to bind to.
99
     * @param callback $callback The callback of the event.
100
     * @param int $priority The priority of the event.
101
     */
102 41
    public static function bind($event, $callback, $priority = Event::PRIORITY_NORMAL) {
103 41
        $event = strtolower($event);
104 41
        self::$handlers[$event][$priority][] = $callback;
105 41
        self::$toSort[$event] = true;
106 41
    }
107
108
    /**
109
     * Bind a class' declared event handlers.
110
     *
111
     * Plugin classes declare event handlers in the following way:
112
     *
113
     * ```
114
     * // Bind to a normal event.
115
     * public function eventname_handler($arg1, $arg2, ...) { ... }
116
     *
117
     * // Add/override a method called with Event::callUserFuncArray().
118
     * public function ClassName_methodName($sender, $arg1, $arg2) { ... }
119
     * public function ClassName_methodName_create($sender, $arg1, $arg2) { ... } // deprecated
120
     *
121
     * // Call the handler before or after a method called with Event::callUserFuncArray().
122
     * public function ClassName_methodName_before($sender, $arg1, $arg2) { ... }
123
     * public function ClassName_methodName_after($sender, $arg1, $arg2) { ... }
124
     * ```
125
     *
126
     * @param mixed $class The class name or an object instance.
127
     * @param int $priority The priority of the event.
128
     * @throws \InvalidArgumentException Throws an exception when binding to a class name with no `instance()` method.
129
     */
130 2
    public static function bindClass($class, $priority = Event::PRIORITY_NORMAL) {
131 2
        $method_names = get_class_methods($class);
132
133
        // Grab an instance of the class so there is something to bind to.
134 2
        if (is_string($class)) {
135 1
            if (method_exists($class, 'instance')) {
136
                // TODO: Make the instance lazy load.
137 1
                $instance = call_user_func(array($class, 'instance'));
138 1
            } else {
139
                throw new \InvalidArgumentException('Event::bindClass(): The class for argument #1 must have an instance() method or be passed as an object.', 422);
140
            }
141 1
        } else {
142 1
            $instance = $class;
143
        }
144
145 2
        foreach ($method_names as $method_name) {
146 2
            if (strpos($method_name, '_') === false) {
147 2
                continue;
148
            }
149
150 2
            $parts = explode('_', strtolower($method_name));
151 2
            switch (end($parts)) {
152 2
                case 'handler':
153 2
                case 'create':
154 2
                case 'override':
155 2
                    array_pop($parts);
156 2
                    $event_name = implode('_', $parts);
157 2
                    break;
158 2
                case 'before':
159 2
                case 'after':
160 2
                default:
161 2
                    $event_name = implode('_', $parts);
162 2
                    break;
163 2
            }
164
            // Bind the event if we have one.
165 2
            if ($event_name) {
166 2
                static::bind($event_name, array($instance, $method_name), $priority);
167 2
            }
168 2
        }
169 2
    }
170
171
    /**
172
     * Dumps all of the bound handlers.
173
     *
174
     * This method is meant for debugging.
175
     *
176
     * @return array Returns an array of all handlers indexed by event name.
177
     */
178 1
    public static function dumpHandlers() {
179 1
        $result = [];
180
181 1
        foreach (self::$handlers as $event_name => $nested) {
182 1
            $handlers = call_user_func_array('array_merge', static::getHandlers($event_name));
183 1
            $result[$event_name] = array_map('format_callback', $handlers);
184 1
        }
185
186 1
        return $result;
187
    }
188
189
    /**
190
     * Fire an event.
191
     *
192
     * @param string $event The name of the event.
193
     * @return mixed Returns the result of the last event handler.
194
     */
195 38
    public static function fire($event) {
196 38
        $handlers = self::getHandlers($event);
197 38
        if (!$handlers) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $handlers of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
198 1
            return null;
199
        }
200
201
        // Grab the handlers and call them.
202 37
        $args = array_slice(func_get_args(), 1);
203 37
        $result = null;
204 37
        foreach ($handlers as $callbacks) {
205 37
            foreach ($callbacks as $callback) {
206 37
                $result = call_user_func_array($callback, $args);
207 37
            }
208 37
        }
209 37
        return $result;
210
    }
211
212
    /**
213
     * Fire an event with an array of arguments.
214
     *
215
     * This method is to {@link Event::fire()} as {@link call_user_func_array()} is to {@link call_user_funct()}.
216
     * The main purpose though is to allow you to have event handlers that can take references.
217
     *
218
     * @param string $event The name of the event.
219
     * @param array $args The arguments for the event handlers.
220
     * @return mixed Returns the result of the last event handler.
221
     */
222 18
    public static function fireArray($event, $args = []) {
223 18
        $handlers = self::getHandlers($event);
224 18
        if (!$handlers) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $handlers of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
225 18
            return null;
226
        }
227
228
        // Grab the handlers and call them.
229 2
        $result = null;
230 2
        foreach ($handlers as $callbacks) {
231 2
            foreach ($callbacks as $callback) {
232 2
                $result = call_user_func_array($callback, $args);
233 2
            }
234 2
        }
235 2
        return $result;
236
    }
237
238
    /**
239
     * Chain several event handlers together.
240
     *
241
     * This method will fire the first handler and pass its result as the first argument
242
     * to the next event handler and so on. A chained event handler can have more than one parameter,
243
     * but must have at least one parameter.
244
     *
245
     * @param string $event The name of the event to fire.
246
     * @param mixed $value The value to pass into the filter.
247
     * @return mixed The result of the chained event or `$value` if there were no handlers.
248
     */
249 2
    public static function fireFilter($event, $value) {
250 2
        $handlers = self::getHandlers($event);
251 2
        if (!$handlers) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $handlers of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
252 2
            return $value;
253
        }
254
255 1
        $args = array_slice(func_get_args(), 1);
256 1
        foreach ($handlers as $callbacks) {
257 1
            foreach ($callbacks as $callback) {
258 1
                $value = call_user_func_array($callback, $args);
259 1
                $args[0] = $value;
260 1
            }
261 1
        }
262 1
        return $value;
263
    }
264
265
    /**
266
     * Fire an event meant to override another function or method.
267
     * The event handler should return true if it wants to say it is overridding the method.
268
     *
269
     * @param string $event The name of the event to fire.
270
     * @return bool Whether or not the event has been overridden.
271
     */
272
//   public static function fireOverride($event) {
273
//      $handlers = self::getHandlers($event);
274
//      if ($handlers === false)
275
//         return $value;
276
//
277
//      $args = array_slice(func_get_args(), 1);
278
//      foreach ($handlers as $callbacks) {
279
//         foreach ($callbacks as $callback) {
280
//            $overridden = call_user_func_array($callback, $args);
281
//            if ($overridden)
282
//               return true;
283
//         }
284
//      }
285
//      return false;
286
//   }
287
288
    /**
289
     * Checks if a function exists or there is a replacement event for it.
290
     *
291
     * @param string $function_name The function name.
292
     * @param bool $only_events Whether or not to only check events.
293
     * @return boolean Returns `true` if the function given by `function_name` has been defined, `false` otherwise.
294
     * @see http://ca1.php.net/manual/en/function.function-exists.php
295
     */
296 2
    public static function functionExists($function_name, $only_events = false) {
297 2
        if (!$only_events && function_exists($function_name)) {
298 1
            return true;
299
        } else {
300 1
            return static::hasHandler($function_name);
301
        }
302
    }
303
304
    /**
305
     * Get the event name for a callback.
306
     *
307
     * @param string|array|callable $callback The callback or an array in the form of a callback.
308
     * @return string The name of the callback.
309
     */
310 29
    protected static function getEventname($callback) {
311 29
        if (is_string($callback)) {
312 1
            return strtolower($callback);
313 29
        } elseif (is_array($callback)) {
314 28
            if (is_string($callback[0])) {
315 1
                $classname = $callback[0];
316 1
            } else {
317 27
                $classname = get_class($callback[0]);
318
            }
319 28
            $eventclass = trim(strrchr($classname, '\\'), '\\');
320 28
            if (!$eventclass) {
321 26
                $eventclass = $classname;
322 26
            }
323 28
            return strtolower($eventclass.'_'.$callback[1]);
324
        }
325 1
        return '';
326
    }
327
328
    /**
329
     * Get all of the handlers bound to an event.
330
     *
331
     * @param string $name The name of the event.
332
     * @return array Returns the handlers that are watching {@link $name}.
333
     */
334 42
    public static function getHandlers($name) {
335 42
        $name = strtolower($name);
336
337 42
        if (!isset(self::$handlers[$name])) {
338 19
            return [];
339
        }
340
341
        // See if the handlers need to be sorted.
342 41
        if (isset(self::$toSort[$name])) {
343 41
            ksort(self::$handlers[$name]);
344 41
            unset(self::$toSort[$name]);
345 41
        }
346
347 41
        return self::$handlers[$name];
348
    }
349
350
    /**
351
     * Checks if an event has a handler.
352
     *
353
     * @param string $event The name of the event.
354
     * @return bool Returns `true` if the event has at least one handler, `false` otherwise.
355
     */
356 29
    public static function hasHandler($event) {
357 29
        $event = strtolower($event);
358 29
        return array_key_exists($event, self::$handlers) && !empty(self::$handlers[$event]);
359
    }
360
361
    /**
362
     * Checks if a class method exists or there is a replacement event for it.
363
     *
364
     * @param mixed $object An object instance or a class name.
365
     * @param string $method_name The method name.
366
     * @param bool $only_events Whether or not to only check events.
367
     * @return boolean Returns `true` if the method given by method_name has been defined for the given object,
368
     * `false` otherwise.
369
     * @see http://ca1.php.net/manual/en/function.method-exists.php
370
     */
371 29
    public static function methodExists($object, $method_name, $only_events = false) {
372 29
        if (!$only_events && method_exists($object, $method_name)) {
373 27
            return true;
374
        } else {
375
            // Check to see if there is an event bound to the method.
376 13
            $event_name = self::getEventname([$object, $method_name]);
377 13
            return static::hasHandler($event_name);
378
        }
379
    }
380
381
    /**
382
     * Clear all of the event handlers.
383
     *
384
     * This method resets the event object to its original state.
385
     */
386 10
    public static function reset() {
387 10
        self::$handlers = [];
388 10
        self::$toSort = [];
389 10
    }
390
}
391