]> scripts.mit.edu Git - autoinstallsdev/mediawiki.git/blob - includes/Hooks.php
MediaWiki 1.17.1
[autoinstallsdev/mediawiki.git] / includes / Hooks.php
1 <?php
2 /**
3  * A tool for running hook functions.
4  *
5  * Copyright 2004, 2005 Evan Prodromou <evan@wikitravel.org>.
6  *
7  *  This program is free software; you can redistribute it and/or modify
8  *  it under the terms of the GNU General Public License as published by
9  *  the Free Software Foundation; either version 2 of the License, or
10  *  (at your option) any later version.
11  *
12  *  This program is distributed in the hope that it will be useful,
13  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
14  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  *  GNU General Public License for more details.
16  *
17  *  You should have received a copy of the GNU General Public License
18  *  along with this program; if not, write to the Free Software
19  *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
20  *
21  * @author Evan Prodromou <evan@wikitravel.org>
22  * @see hooks.txt
23  * @file
24  */
25
26
27 /**
28  * Call hook functions defined in $wgHooks
29  *
30  * Because programmers assign to $wgHooks, we need to be very
31  * careful about its contents. So, there's a lot more error-checking
32  * in here than would normally be necessary.
33  *
34  * @param $event String: event name
35  * @param $args Array: parameters passed to hook functions
36  * @return Boolean
37  */
38 function wfRunHooks($event, $args = array()) {
39
40         global $wgHooks;
41
42         // Return quickly in the most common case
43         if ( !isset( $wgHooks[$event] ) ) {
44                 return true;
45         }
46
47         if (!is_array($wgHooks)) {
48                 throw new MWException("Global hooks array is not an array!\n");
49         }
50
51         if (!is_array($wgHooks[$event])) {
52                 throw new MWException("Hooks array for event '$event' is not an array!\n");
53         }
54
55         foreach ($wgHooks[$event] as $index => $hook) {
56
57                 $object = null;
58                 $method = null;
59                 $func = null;
60                 $data = null;
61                 $have_data = false;
62                 $closure = false;
63                 $badhookmsg = false;
64
65                 /* $hook can be: a function, an object, an array of $function and $data,
66                  * an array of just a function, an array of object and method, or an
67                  * array of object, method, and data.
68                  */
69
70                 if ( is_array( $hook ) ) {
71                         if ( count( $hook ) < 1 ) {
72                                 throw new MWException("Empty array in hooks for " . $event . "\n");
73                         } else if ( is_object( $hook[0] ) ) {
74                                 $object = $wgHooks[$event][$index][0];
75                                 if ( $object instanceof Closure ) {
76                                         $closure = true;
77                                         if ( count( $hook ) > 1 ) {
78                                                 $data = $hook[1];
79                                                 $have_data = true;
80                                         }
81                                 } else {
82                                         if ( count( $hook ) < 2 ) {
83                                                 $method = "on" . $event;
84                                         } else {
85                                                 $method = $hook[1];
86                                                 if ( count( $hook ) > 2 ) {
87                                                         $data = $hook[2];
88                                                         $have_data = true;
89                                                 }
90                                         }
91                                 }
92                         } else if ( is_string( $hook[0] ) ) {
93                                 $func = $hook[0];
94                                 if ( count( $hook ) > 1) {
95                                         $data = $hook[1];
96                                         $have_data = true;
97                                 }
98                         } else {
99                                 throw new MWException( "Unknown datatype in hooks for " . $event . "\n" );
100                         }
101                 } else if ( is_string( $hook ) ) { # functions look like strings, too
102                         $func = $hook;
103                 } else if ( is_object( $hook ) ) {
104                         $object = $wgHooks[$event][$index];
105                         if ( $object instanceof Closure ) {
106                                 $closure = true;
107                         } else {
108                                 $method = "on" . $event;
109                         }
110                 } else {
111                         throw new MWException( "Unknown datatype in hooks for " . $event . "\n" );
112                 }
113
114                 /* We put the first data element on, if needed. */
115
116                 if ( $have_data ) {
117                         $hook_args = array_merge(array($data), $args);
118                 } else {
119                         $hook_args = $args;
120                 }
121
122                 if ( $closure ) {
123                         $callback = $object;
124                         $func = "hook-$event-closure";
125                 } elseif ( isset( $object ) ) {
126                         $func = get_class( $object ) . '::' . $method;
127                         $callback = array( $object, $method );
128                 } elseif ( false !== ( $pos = strpos( $func, '::' ) ) ) {
129                         $callback = array( substr( $func, 0, $pos ), substr( $func, $pos + 2 ) );
130                 } else {
131                         $callback = $func;
132                 }
133
134                 // Run autoloader (workaround for call_user_func_array bug)
135                 is_callable( $callback );
136
137                 /* Call the hook. The documentation of call_user_func_array clearly
138                  * states that FALSE is returned on failure. However this is not
139                  * case always. In some version of PHP if the function signature
140                  * does not match the call signature, PHP will issue an warning:
141                  * Param y in x expected to be a reference, value given.
142                  *
143                  * In that case the call will also return null. The following code
144                  * catches that warning and provides better error message. The
145                  * function documentation also says that:
146                  *     In other words, it does not depend on the function signature
147                  *     whether the parameter is passed by a value or by a reference. 
148                  * There is also PHP bug http://bugs.php.net/bug.php?id=47554 which
149                  * is unsurprisingly marked as bogus. In short handling of failures
150                  * with call_user_func_array is a failure, the documentation for that
151                  * function is wrong and misleading and PHP developers don't see any
152                  * problem here.
153                  */
154                 $retval = null;
155                 set_error_handler( 'hookErrorHandler' );
156                 wfProfileIn( $func );
157                 try {
158                         $retval = call_user_func_array( $callback, $hook_args );
159                 } catch ( MWHookException $e ) {
160                         $badhookmsg = $e->getMessage();
161                 }
162                 wfProfileOut( $func );
163                 restore_error_handler();
164
165                 /* String return is an error; false return means stop processing. */
166                 if ( is_string( $retval ) ) {
167                         global $wgOut;
168                         $wgOut->showFatalError( $retval );
169                         return false;
170                 } elseif( $retval === null ) {
171                         if ( $closure ) {
172                                 $prettyFunc = "$event closure";
173                         } elseif( is_array( $callback ) ) {
174                                 if( is_object( $callback[0] ) ) {
175                                         $prettyClass = get_class( $callback[0] );
176                                 } else {
177                                         $prettyClass = strval( $callback[0] );
178                                 }
179                                 $prettyFunc = $prettyClass . '::' . strval( $callback[1] );
180                         } else {
181                                 $prettyFunc = strval( $callback );
182                         }
183                         if ( $badhookmsg ) {
184                                 throw new MWException( "Detected bug in an extension! " .
185                                 "Hook $prettyFunc has invalid call signature; " . $badhookmsg );
186                         } else {
187                                 throw new MWException( "Detected bug in an extension! " .
188                                         "Hook $prettyFunc failed to return a value; " .
189                                         "should return true to continue hook processing or false to abort." );
190                         }
191                 } else if ( !$retval ) {
192                         return false;
193                 }
194         }
195
196         return true;
197 }
198
199 function hookErrorHandler( $errno, $errstr ) {
200         if ( strpos( $errstr, 'expected to be a reference, value given' ) !== false ) {
201                 throw new MWHookException( $errstr );
202         }
203         return false;
204 }
205
206 class MWHookException extends MWException {}