Initial commit of website to git. Has the "old" ob3 website as the main site, and...
[dana/openbox-web.git] / mediawiki / extensions / ExtensionClass.php
1 <?php
2 /*
3  * ExtensionClass.php
4  * 
5  * MediaWiki extension
6  * @author: Jean-Lou Dupont (http://www.bluecortex.com)
7  *
8  * Purpose:  Provides a toolkit for easier Mediawiki
9  *           extension development.
10  *
11  * FEATURES:
12  * - 'singleton' implementation suited for extensions that require single instance
13  * - 'magic word' helper functionality
14  * - limited pollution of global namespace
15  *
16  * Tested Compatibility: MW 1.8.2 (PHP5), 1.9.3, 1.10
17  *
18  * History:
19  * v1.0         Initial availability
20  * v1.01    Small enhancement in processArgList
21  * v1.02    Corrected minor bug
22  * v1.1     Added function 'checkPageEditRestriction'
23  * v1.2     Added 'getArticle' function
24  * ----     Moved to SVN management
25  * v1.3     Added wgExtensionCredits updating upon Special:Version viewing
26  * v1.4     Fixed broken singleton functionality
27  * v1.5         Added automatic registration of hook functions based
28  *          on the definition of an handler in the derived class
29  *          (e.g. if handler 'hArticleSave' exists, then the appropriate
30  *           'ArticleSave' hook is registered)
31  * v1.51    Fixed '$passingStyle' bug (thanks to Joshua C. Lerner)
32  * v1.6     Added 'updateCreditsDescription' helper method.
33  * v1.7         Added 'depth' parameter support: more than 1 class depth can be created.
34  *          Added 'setupTags' method (support for parser tags)
35  *          Enhancement to 'getParam' method
36  *          Added 'formatParams' method
37  * v1.8     Added 'initFirst' parameter
38  * v1.9     Added support for including 'head' scripts and stylesheeets
39  *          in a manner compatible with parser caching functionality.
40  *          (Original idea from [user:Jimbojw]
41  * v1.91    Added check for screening script duplicates in 'addHeadScript'
42  * v1.92    Added optional removal of parameters not listed in template.
43  *
44  */
45 $wgExtensionCredits['other'][] = array( 
46         'name'    => 'ExtensionClass',
47         'version' => 'v1.92 $LastChangedRevision: 174 $',
48         'author'  => 'Jean-Lou Dupont', 
49         'url'     => 'http://www.bluecortex.com',
50 );
51  
52 class ExtensionClass
53 {
54         static $gObj; // singleton instance
55  
56 // List up-to-date with MW 1.10 SVN 21828
57 static $hookList = array(
58 'ArticlePageDataBefore', 
59 'ArticlePageDataAfter', 
60 'ArticleAfterFetchContent',
61 'ArticleViewRedirect', 
62 'ArticleViewHeader',
63 'ArticlePurge',
64 'ArticleSave', 
65 'ArticleInsertComplete',
66 'ArticleSaveComplete',
67 'MarkPatrolled', 
68 'MarkPatrolledComplete', 
69 'WatchArticle', 
70 'WatchArticleComplete',
71 'UnwatchArticle', 
72 'UnwatchArticleComplete', 
73 'ArticleProtect', 
74 'ArticleProtectComplete',
75 'ArticleDelete', 
76 'ArticleDeleteComplete', 
77 'ArticleEditUpdatesDeleteFromRecentchanges',
78 'ArticleEditUpdateNewTalk',
79 'DisplayOldSubtitle',
80 'IsFileCacheable',
81 'CategoryPageView',
82 'FetchChangesList',
83 'DiffViewHeader',
84 'AlternateEdit', 
85 'EditFormPreloadText', 
86 'EditPage::attemptSave', 
87 'EditFilter', 
88 'EditPage::showEditForm:initial',
89 'EditPage::showEditForm:fields',
90 'SiteNoticeBefore',
91 'SiteNoticeAfter',
92 'FileUpload',
93 'BadImage', 
94 'MagicWordMagicWords', 
95 'MagicWordwgVariableIDs',
96 'MathAfterTexvc',
97 'MessagesPreLoad',
98 'LoadAllMessages',
99 'OutputPageParserOutput',
100 'OutputPageBeforeHTML',
101 'AjaxAddScript', 
102 'PageHistoryBeforeList',
103 'PageHistoryLineEnding',
104 'ParserClearState', 
105 'ParserBeforeStrip',
106 'ParserAfterStrip',
107 'ParserBeforeTidy',
108 'ParserAfterTidy',
109 'ParserBeforeStrip',
110 'ParserAfterStrip', 
111 'ParserBeforeStrip',
112 'ParserAfterStrip', 
113 'ParserBeforeInternalParse',
114 'InternalParseBeforeLinks', 
115 'ParserGetVariableValueVarCache',
116 'ParserGetVariableValueTs', 
117 'ParserGetVariableValueSwitch',
118 'IsTrustedProxy',
119 'wgQueryPages', 
120 'RawPageViewBeforeOutput', 
121 'RecentChange_save',
122 'SearchUpdate', 
123 'AuthPluginSetup', 
124 'LogPageValidTypes',
125 'LogPageLogName', 
126 'LogPageLogHeader', 
127 'LogPageActionText',
128 'SkinTemplateTabs', 
129 'BeforePageDisplay', 
130 'SkinTemplateOutputPageBeforeExec', 
131 'PersonalUrls', 
132 'SkinTemplatePreventOtherActiveTabs',
133 'SkinTemplateTabs', 
134 'SkinTemplateBuildContentActionUrlsAfterSpecialPage',
135 'SkinTemplateContentActions', 
136 'SkinTemplateBuildNavUrlsNav_urlsAfterPermalink',
137 'SkinTemplateSetupPageCss',
138 'BlockIp', 
139 'BlockIpComplete', 
140 'BookInformation', 
141 'SpecialContributionsBeforeMainOutput',
142 'EmailUser', 
143 'EmailUserComplete',
144 'SpecialMovepageAfterMove',
145 'SpecialMovepageAfterMove',
146 'SpecialPage_initList',
147 'SpecialPageExecuteBeforeHeader',
148 'SpecialPageExecuteBeforePage',
149 'SpecialPageExecuteAfterPage',
150 'PreferencesUserInformationPanel',
151 'SpecialSearchNogomatch',
152 'ArticleUndelete',
153 'UndeleteShowRevision',
154 'UploadForm:BeforeProcessing',
155 'UploadVerification',
156 'UploadComplete',
157 'UploadForm:initial',
158 'AddNewAccount',
159 'AbortNewAccount',
160 'UserLoginComplete',
161 'UserCreateForm',
162 'UserLoginForm',
163 'UserLogout',
164 'UserLogoutComplete',
165 'UserRights',
166 /*'SpecialVersionExtensionTypes',*/ // reserved special treatment
167 'UnwatchArticle',
168 'AutoAuthenticate', 
169 'GetFullURL',
170 'GetLocalURL',
171 'GetInternalURL',
172 'userCan',
173 'TitleMoveComplete',
174 'isValidPassword',
175 'UserToggles',
176 'GetBlockedStatus',
177 'PingLimiter',
178 'UserRetrieveNewTalks',
179 'UserClearNewTalkNotification',
180 'PageRenderingHash',
181 'EmailConfirmed',
182 'ArticleFromTitle',
183 'CustomEditor',
184 'UnknownAction',
185 /*'LanguageGetMagic', */ // reserved a special treatment in this class 
186 'LangugeGetSpecialPageAliases',
187 'MonoBookTemplateToolboxEnd',
188 'SkinTemplateSetupPageCss',
189 'SkinTemplatePreventOtherActiveTabs'
190 );
191  
192         var $className;
193         
194         var $paramPassingStyle;
195         var $ext_mgwords;       
196         
197         // Parameter passing style.
198         const mw_style = 1;
199         const tk_style = 2;
200         
201         public static function &singleton( $mwlist=null ,$globalObjName=null, 
202                                                                                 $passingStyle = self::mw_style, $depth = 1,
203                                                                                 $initFirst = false )
204         {
205                 // Let's first extract the callee's classname
206                 $trace = debug_backtrace();
207                 $cname = $trace[$depth]['class'];
208  
209                 // If no globalObjName was given, create a unique one.
210                 if ($globalObjName === null)
211                         $globalObjName = substr(create_function('',''), 1 );
212                 
213                 // Since there can only be one extension with a given child class name,
214                 // Let's store the $globalObjName in a static array.
215                 if (!isset(self::$gObj[$cname]) )
216                         self::$gObj[$cname] = $globalObjName; 
217                                 
218                 if ( !isset( $GLOBALS[self::$gObj[$cname]] ) )
219                         $GLOBALS[self::$gObj[$cname]] = new $cname( $mwlist, $passingStyle, $depth, $initFirst );
220                         
221                 return $GLOBALS[self::$gObj[$cname]];
222         }
223         public function ExtensionClass( $mgwords=null, $passingStyle = self::mw_style, 
224                                                                         $depth = 1, $initFirst = false )
225         /*
226          *  $mgwords: array of 'magic words' to subscribe to *if* required.
227          */
228         {
229                 global $wgHooks;
230                         
231                 if ($passingStyle == null) $passingStyle = self::mw_style; // prevention...
232                 $this->paramPassingStyle = $passingStyle;
233                 
234                 // Let's first extract the callee's classname
235                 $trace = debug_backtrace();
236                 $this->className= $cname = $trace[$depth]['class'];
237                 // And let's retrieve the global object's name
238                 $n = self::$gObj[$cname];
239                 
240                 global $wgExtensionFunctions;
241                 
242                 // v1.8 feature
243                 $initFnc = create_function('',"global $".$n."; $".$n."->setup();");
244                 if ($initFirst)
245                          array_unshift( $wgExtensionFunctions, $initFnc );
246                 else $wgExtensionFunctions[] = $initFnc;
247                 
248                 $this->ext_mgwords = $mgwords;          
249                 if (is_array($this->ext_mgwords) )
250                         $wgHooks['LanguageGetMagic'][] = array($this, 'getMagic');
251  
252                 // v1.3 feature
253                 if ( in_array( 'hUpdateExtensionCredits', get_class_methods($this->className) ) )
254                         $wgHooks['SpecialVersionExtensionTypes'][] = array( &$this, 'hUpdateExtensionCredits' );                                
255  
256                 // v1.5 feature
257                 foreach (self::$hookList as $index => $hookName)
258                         if ( in_array( 'h'.$hookName, get_class_methods($this->className) ) )
259                                 $wgHooks[$hookName][] = array( &$this, 'h'.$hookName );
260         }
261         public function getParamPassingStyle() { return $this->passingStyle; }
262         public function setup()
263         {
264                 if (is_array($this->ext_mgwords))
265                         $this->setupMagic();
266         }
267         // ================== MAGIC WORD HELPER FUNCTIONS ===========================
268         public function getMagic( &$magicwords, $langCode )
269         {
270                 foreach($this->ext_mgwords as $index => $key)
271                         $magicwords [$key] = array( 0, $key );
272                 return true;
273         }
274         public function setupMagic( )
275         {
276                 global $wgParser;
277                 foreach($this->ext_mgwords as $index => $key)
278                         $wgParser->setFunctionHook( "$key", array( $this, "mg_$key" ) );
279         }
280         public function setupTags( $tagList )
281         {
282                 global $wgParser;
283                 foreach($tagList as $index => $key)
284                         $wgParser->setHook( "$key", array( $this, "tag_$key" ) );
285         }
286         // ================== GENERAL PURPOSE HELPER FUNCTIONS ===========================
287         public function processArgList( $list, $getridoffirstparam=false )
288         /*
289          * The resulting list contains:
290          * - The parameters extracted by 'key=value' whereby (key => value) entries in the list
291          * - The parameters extracted by 'index' whereby ( index = > value) entries in the list
292          */
293         {
294                 if ($getridoffirstparam)   
295                         array_shift( $list );
296                         
297                 // the parser sometimes includes a boggie
298                 // null parameter. get rid of it.
299                 if (count($list) >0 )
300                         if (empty( $list[count($list)-1] ))
301                                 unset( $list[count($list)-1] );
302                 
303                 $result = array();
304                 foreach ($list as $index => $el )
305                 {
306                         $t = explode("=", $el);
307                         if (!isset($t[1])) 
308                                 continue;
309                         $result[ "{$t[0]}" ] = $t[1];
310                         unset( $list[$index] );
311                 }
312                 if (empty($result)) 
313                         return $list;
314                 return array_merge( $result, $list );   
315         }
316         public function getParam( &$alist, $key, $index, $default )
317         /*
318          *  Gets a parameter by 'key' if present
319          *  or fallback on getting the value by 'index' and
320          *  ultimately fallback on default if both previous attempts fail.
321          */
322         {
323                 if (array_key_exists($key, $alist) )
324                         return $alist[$key];
325                 elseif (array_key_exists($index, $alist) && $index!==null )
326                         return $alist[$index];
327                 else
328                         return $default;
329         }
330         public function initParams( &$alist, &$templateElements, $removeNotInTemplate = true )
331         {
332                         #var_dump( $alist );
333                 // v1.92 feature.
334                 if ($removeNotInTemplate)
335                         foreach( $templateElements as $index => &$el )
336                                 if ( !isset($alist[ $el['key'] ]) )
337                                         unset( $alist[$el['key']] );
338                 
339                 foreach( $templateElements as $index => &$el )
340                         $alist[$el['key']] = $this->getParam( $alist, $el['key'], $el['index'], $el['default'] );
341         }
342         public function formatParams( &$alist , &$template )
343         // look at yuiPanel extension for usage example.
344         // $alist = { 'key' => 'value' ... }
345         {
346                 foreach ( $alist as $key => $value )
347                         // format the entry.
348                         $this->formatParam( $key, $value, $template );
349         }
350         private function formatParam( &$key, &$value, &$template )
351         {
352                 $format = $this->getFormat( $key, $template );
353                 if ($format !==null )
354                 {
355                         switch ($format)
356                         {
357                                 case 'bool':   $value = (bool) $value; break; 
358                                 case 'int':    $value = (int) $value; break;
359                                 default:
360                                 case 'string': $value = (string) $value; break;                                 
361                         }                       
362                 }
363         }
364         public function getFormat( &$key, &$template )
365         {
366                 $format = null;
367                 foreach( $template as $index => &$el )
368                         if ( $el['key'] == $key )
369                                 $format  = $el['format'];
370                         
371                 return $format;
372         }
373         public function checkPageEditRestriction( &$title )
374         // v1.1 feature
375         // where $title is a Mediawiki Title class object instance
376         {
377                 $proceed = false;
378   
379                 $state = $title->getRestrictions('edit');
380                 foreach ($state as $index => $group )
381                         if ( $group == 'sysop' )
382                                 $proceed = true;
383  
384                 return $proceed;                
385         } 
386         public function getArticle( $article_title )
387         {
388                 $title = Title::newFromText( $article_title );
389                   
390                 // Can't load page if title is invalid.
391                 if ($title == null)     return null;
392                 $article = new Article($title);
393  
394                 return $article;        
395         }
396         
397         function isSysop( $user = null ) // v1.5 feature
398         {
399                 if ($user == null)
400                 {
401                         global $wgUser;
402                         $user = $wgUser;
403                 }       
404                 return in_array( 'sysop', $user->getGroups() );
405         }
406         
407         function updateCreditsDescription( &$text ) // v1.6 feature.
408         {
409                 global $wgExtensionCredits;
410         
411                 foreach ( $wgExtensionCredits[self::thisType] as $index => &$el )
412                         if ($el['name']==self::thisName)
413                                 $el['description'].=$text;      
414         }
415  
416 /*  Add scripts & stylesheets functionality.
417 This process must be done in two phases:
418 phase 1- encode information related to the required
419          scripts & stylesheets in a 'meta form' in
420                  the parser cache text.
421 phase 2- when the page is rendered, extract the meta information
422          and include the information appropriately in the 'head' of the page.             
423 ************************************************************************************/
424         static $scriptList;
425         static $scriptsAdded;
426         static $scriptsListed;
427  
428         function addHeadScript( $st )
429         {
430                 if ( !isset($st) ) return;
431                 
432                 if ( !isset(self::$scriptList) )
433                         self::$scriptList[] = $st;
434                 elseif  (!in_array($st, self::$scriptList)) 
435                         self::$scriptList[] = $st;
436                          
437                 self::$scriptsAdded = false;
438                 self::$scriptsListed = false;
439         }
440         
441         function hParserAfterTidy( &$parser, &$text )
442         // set the meta information in the parsed 'wikitext'.
443         {
444                 if (self::$scriptsListed) return true;
445                 self::$scriptsListed = true;
446  
447                 if (!empty(self::$scriptList))
448                         foreach(self::$scriptList as $sc)
449                                 $text .= '<!-- META_KEYWORDS '.base64_encode($sc).' -->'; 
450  
451                 return true;
452         }       
453         function hOutputPageBeforeHTML( &$op, &$text )
454         // This function sifts through 'meta tags' embedded in html comments
455         // and picks out scripts & stylesheet references that need to be put
456         // in the page's HEAD.
457         {
458                 // some hooks get called more than once...
459                 // In this case, since ExtensionClass provides a 
460                 // base class for numerous extensions, then it is very
461                 // likely this method will be called more than once;
462                 // so, we want to make sure we include the head scripts just once.
463                 if (self::$scriptsAdded) return true;
464                 self::$scriptsAdded = true;
465                 
466                 if (preg_match_all(
467                 '/<!-- META_KEYWORDS ([0-9a-zA-Z\\+\\/]+=*) -->/m', 
468                 $text, 
469                 $matches)===false) return true;
470                         
471         $data = $matches[1];
472  
473             foreach ($data AS $item) 
474                 {
475                 $content = @base64_decode($item);
476                 if ($content) $op->addScript( $content );
477             }
478             return true;
479         }
480  
481 } // end class definition.
482 ?>