r76037 MediaWiki - Code Review archive

Repository:MediaWiki
Revision:r76036‎ | r76037 | r76038 >
Date:21:15, 4 November 2010
Author:maxsem
Status:deferred
Tags:
Comment:
Started rewriting Gadgets to use OOP and resource loader. Doesn't work yet, just committing work in progress.
Modified paths:
  • /branches/Gadgets-work/Gadgets.php (modified) (history)
  • /branches/Gadgets-work/Gadgets_body.php (added) (history)

Diff [purge]

Index: branches/Gadgets-work/Gadgets_body.php
@@ -0,0 +1,306 @@
 2+<?php
 3+/**
 4+ * Gadgets extension - lets users select custom javascript gadgets
 5+ *
 6+ *
 7+ * For more info see http://mediawiki.org/wiki/Extension:Gadgets
 8+ *
 9+ * @file
 10+ * @ingroup Extensions
 11+ * @author Daniel Kinzler, brightbyte.de
 12+ * @copyright © 2007 Daniel Kinzler
 13+ * @license GNU General Public Licence 2.0 or later
 14+ */
 15+
 16+class Gadgets {
 17+
 18+ public static function ArticleSaveComplete( $article, $user, $text ) {
 19+ //update cache if MediaWiki:Gadgets-definition was edited
 20+ $title = $article->mTitle;
 21+ if( $title->getNamespace() == NS_MEDIAWIKI && $title->getText() == 'Gadgets-definition' ) {
 22+ self::LoadStructured( $text );
 23+ }
 24+ return true;
 25+ }
 26+
 27+ private static function Load() {
 28+ static $gadgets = null;
 29+
 30+ if ( $gadgets !== null ) return $gadgets;
 31+
 32+ $struct = self::LoadStructured();
 33+ if ( !$struct ) {
 34+ $gadgets = $struct;
 35+ return $gadgets;
 36+ }
 37+
 38+ $gadgets = array();
 39+ foreach ( $struct as $section => $entries ) {
 40+ $gadgets = array_merge( $gadgets, $entries );
 41+ }
 42+
 43+ return $gadgets;
 44+ }
 45+
 46+ public static function LoadStructured( $forceNewText = null ) {
 47+ global $wgMemc;
 48+
 49+ static $gadgets = null;
 50+ if ( $gadgets !== null && $forceNewText === null ) return $gadgets;
 51+
 52+ $key = wfMemcKey( 'gadgets-definition' );
 53+
 54+ if ( $forceNewText === null ) {
 55+ //cached?
 56+ $gadgets = $wgMemc->get( $key );
 57+ // TODO: isOutdated()
 58+ if ( is_array($gadgets) && next( $gadgets ) instanceof Gadget ) return $gadgets;
 59+
 60+ $g = wfMsgForContentNoTrans( "gadgets-definition" );
 61+ if ( wfEmptyMsg( "gadgets-definition", $g ) ) {
 62+ $gadgets = false;
 63+ return $gadgets;
 64+ }
 65+ } else {
 66+ $g = $forceNewText;
 67+ }
 68+
 69+ $g = preg_replace( '/<!--.*-->/s', '', $g );
 70+ $g = preg_split( '/(\r\n|\r|\n)+/', $g );
 71+
 72+ $gadgets = array();
 73+ $section = '';
 74+
 75+ foreach ( $g as $line ) {
 76+ if ( preg_match( '/^==+ *([^*:\s|]+?)\s*==+\s*$/', $line, $m ) ) {
 77+ $section = $m[1];
 78+ }
 79+ else {
 80+ $gadget = Gadget::newFromDefinition( $line );
 81+ if ( $gadget ) {
 82+ $gadgets[$section][$gadget->getName()] = $gadget;
 83+ }
 84+ }
 85+ }
 86+
 87+ //cache for a while. gets purged automatically when MediaWiki:Gadgets-definition is edited
 88+ $wgMemc->set( $key, $gadgets, 60*60*24 );
 89+ $source = $forceNewText !== null ? 'input text' : 'MediaWiki:Gadgets-definition';
 90+ wfDebug( __METHOD__ . ": $source parsed, cache entry $key updated\n");
 91+
 92+ return $gadgets;
 93+ }
 94+
 95+ public static function GetPreferences( $user, &$preferences ) {
 96+ $gadgets = self::LoadStructured();
 97+ if (!$gadgets) return true;
 98+
 99+ $options = array();
 100+ foreach( $gadgets as $section => $thisSection ) {
 101+ if ( $section !== '' ) {
 102+ $section = wfMsgExt( "gadget-section-$section", 'parseinline' );
 103+ $options[$section] = array();
 104+ $destination = &$options[$section];
 105+ } else {
 106+ $destination = &$options;
 107+ }
 108+ foreach( $thisSection as $gadget ) {
 109+ $gname = $gadget->getName();
 110+ $destination[wfMsgExt( "gadget-$gname", 'parseinline' )] = $gname;
 111+ }
 112+ }
 113+
 114+ $preferences['gadgets-intro'] =
 115+ array(
 116+ 'type' => 'info',
 117+ 'label' => '&#160;',
 118+ 'default' => Xml::tags( 'tr', array(),
 119+ Xml::tags( 'td', array( 'colspan' => 2 ),
 120+ wfMsgExt( 'gadgets-prefstext', 'parse' ) ) ),
 121+ 'section' => 'gadgets',
 122+ 'raw' => 1,
 123+ 'rawrow' => 1,
 124+ );
 125+
 126+ $preferences['gadgets'] =
 127+ array(
 128+ 'type' => 'multiselect',
 129+ 'options' => $options,
 130+ 'section' => 'gadgets',
 131+ 'label' => '&#160;',
 132+ 'prefix' => 'gadget-',
 133+ );
 134+
 135+ return true;
 136+ }
 137+
 138+ public static function RegisterModules( &$resourceLoader ) {
 139+ $gadgets = self::Load();
 140+ if ( !gadgets ) {
 141+ return true;
 142+ }
 143+ foreach ( $gadgets as $g ) {
 144+ $module = $g->getModule();
 145+ if ( $module ) {
 146+ $resourceLoader->register( $g->getModuleName(), $module );
 147+ }
 148+ }
 149+ return true;
 150+ }
 151+
 152+ public function BeforePageDisplay( $out ) {
 153+ global $wgUser;
 154+ if ( !$wgUser->isLoggedIn() ) return true;
 155+
 156+ //disable all gadgets on critical special pages
 157+ //NOTE: $out->isUserJsAllowed() is tempting, but always fals if $wgAllowUserJs is false.
 158+ // That would disable gadgets on wikis without user JS. Introducing $out->isJsAllowed()
 159+ // may work, but should that really apply also to MediaWiki:common.js? Even on the preference page?
 160+ // See bug 22929 for discussion.
 161+ $title = $out->getTitle();
 162+ if ( $title->isSpecial( 'Preferences' )
 163+ || $title->isSpecial( 'Resetpass' )
 164+ || $title->isSpecial( 'Userlogin' ) ) {
 165+ return true;
 166+ }
 167+
 168+ $gadgets = self::Load();
 169+ if ( !$gadgets ) return true;
 170+
 171+ $lb = new LinkBatch();
 172+ $lb->setCaller( __METHOD__ );
 173+ $pages = array();
 174+
 175+ foreach ( $gadgets as $gadget ) {
 176+ $tname = 'gadget-' . $gadget->getName();
 177+ if ( $wgUser->getOption( $tname ) ) {
 178+ if ( $gadget->hasModule() ) {
 179+ $out->addModules( $gadget->getModuleName() );
 180+ }
 181+ foreach ( $gadget->getLegacyScripts() as $page ) {
 182+ $lb->add( NS_MEDIAWIKI, $page );
 183+ $pages[] = $page;
 184+ }
 185+ }
 186+ }
 187+
 188+ $lb->execute( __METHOD__ );
 189+
 190+ $pages = array();
 191+ $done = array();
 192+ foreach ( $pages as $page ) {
 193+ if ( isset( $done[$page] ) ) continue;
 194+ $done[$page] = true;
 195+ self::ApplyGadgetCode( $page, $out );
 196+ }
 197+
 198+ return true;
 199+ }
 200+
 201+ private static function ApplyGadgetCode( $page, $out ) {
 202+ global $wgJsMimeType;
 203+
 204+ //FIXME: stuff added via $out->addScript appears below usercss and userjs in the head tag.
 205+ // but we'd want it to appear above explicit user stuff, so it can be overwritten.
 206+
 207+ $t = Title::makeTitleSafe( NS_MEDIAWIKI, $page );
 208+ if ( !$t ) continue;
 209+
 210+ $u = $t->getLocalURL( 'action=raw&ctype=' . $wgJsMimeType );
 211+ //switched to addScriptFile call to support scriptLoader
 212+ $out->addScriptFile( $u, $t->getLatestRevID() );
 213+ }
 214+}
 215+
 216+class Gadget {
 217+ const GADGET_CLASS_VERSION = 1; // Increment this when changing fields
 218+
 219+ private $version = self::GADGET_CLASS_VERSION,
 220+ $scripts = array(),
 221+ $styles = array(),
 222+ $name,
 223+ $resourceLoaded = false;
 224+
 225+ public static function newFromDefinition( string $definition ) {
 226+ if ( !preg_match( '/^\*+ *([a-zA-Z](?:[-_:.\w\d ]*[a-zA-Z0-9])?)\s*((\|[^|]*)+)\s*$/', $definition, $m ) ) {
 227+ return false;
 228+ }
 229+ //NOTE: the gadget name is used as part of the name of a form field,
 230+ // and must follow the rules defined in http://www.w3.org/TR/html4/types.html#type-cdata
 231+ // Also, title-normalization applies.
 232+ $gadget = new Gadget();
 233+ $gadget->name = str_replace(' ', '_', $m[1] );
 234+ foreach( preg_split( '/\s*\|\s*/', $m[2], -1, PREG_SPLIT_NO_EMPTY ) as $page ) {
 235+ $page = "Gadget-$page";
 236+ if ( preg_match( '/\.js/', $page ) ) {
 237+ $gadget->scripts[] = $page;
 238+ } elseif ( preg_match( '/\.css/', $page ) ) {
 239+ $gadget->styles[] = $page;
 240+ }
 241+ }
 242+ return $gadget;
 243+ }
 244+
 245+ public function getName() {
 246+ return $this->name;
 247+ }
 248+
 249+ public function getModuleName() {
 250+ return "ext.gadget.{$this->name}";
 251+ }
 252+
 253+ public function isOutdated() {
 254+ return $this->version != GADGET_CLASS_VERSION;
 255+ }
 256+
 257+ public function supportsResourceLoader() {
 258+ return $this->resourceLoaded;
 259+ }
 260+
 261+ public function hasModule() {
 262+ return count( $this->styles )
 263+ + ( $this->supportsResourceLoader() ? count( $this->scripts ) : 0 )
 264+ > 0;
 265+ }
 266+
 267+ public function getScripts() {
 268+ return $this->scripts;
 269+ }
 270+
 271+ public function getStyles() {
 272+ return $this->styles;
 273+ }
 274+
 275+ public function getScriptsAndStyles() {
 276+ return $this->scripts + $this->styles;
 277+ }
 278+
 279+ public function getModule() {
 280+ $pages = $this->styles;
 281+ if ( $this->supportsResourceLoader() ) {
 282+ $pages += $this->scripts;
 283+ }
 284+ if ( count( $pages ) ) {
 285+ }
 286+ return new GadgetResourceLoaderModule( $pages );
 287+ }
 288+
 289+ public function getLegacyScripts() {
 290+ if ( $this->supportsResourceLoader() ) {
 291+ return array();
 292+ }
 293+ return $this->scripts();
 294+ }
 295+}
 296+
 297+class GadgetResourceLoaderModule extends ResourceLoaderWikiModule {
 298+ private $pages;
 299+
 300+ public function __construct( $pages ) {
 301+ $this->pages = $pages;
 302+ }
 303+
 304+ protected function getPages( ResourceLoaderContext $context ) {
 305+ return $this->pages;
 306+ }
 307+}
\ No newline at end of file
Property changes on: branches/Gadgets-work/Gadgets_body.php
___________________________________________________________________
Added: svn:eol-style
1308 + native
Added: svn:keywords
2309 + LastChangedDate LastChangedRevision
Index: branches/Gadgets-work/Gadgets.php
@@ -17,6 +17,10 @@
1818 die( 1 );
1919 }
2020
 21+if ( version_compare( $wgVersion, '1.17alpha', '<' ) ) {
 22+ die( "This version of Extension:Gadgets requires MediaWiki 1.17+\n" );
 23+}
 24+
2125 $wgExtensionCredits['other'][] = array(
2226 'path' => __FILE__,
2327 'name' => 'Gadgets',
@@ -25,204 +29,19 @@
2630 'descriptionmsg' => 'gadgets-desc',
2731 );
2832
29 -$wgHooks['GetPreferences'][] = 'wfGadgetsGetPreferences';
30 -$wgHooks['BeforePageDisplay'][] = 'wfGadgetsBeforePageDisplay';
31 -$wgHooks['ArticleSaveComplete'][] = 'wfGadgetsArticleSaveComplete';
 33+$wgHooks['ArticleSaveComplete'][] = 'Gadgets::ArticleSaveComplete';
 34+$wgHooks['BeforePageDisplay'][] = 'Gadgets::BeforePageDisplay';
 35+$wgHooks['GetPreferences'][] = 'Gadgets::GetPreferences';
 36+$wgHooks['ResourceLoaderRegisterModules'][] = 'Gadgets::RegisterModules';
3237
3338 $dir = dirname(__FILE__) . '/';
3439 $wgExtensionMessagesFiles['Gadgets'] = $dir . 'Gadgets.i18n.php';
3540 $wgExtensionAliasesFiles['Gadgets'] = $dir . 'Gadgets.alias.php';
 41+
 42+$wgAutoloadClasses['Gadget'] = $dir . 'Gadgets_body.php';
 43+$wgAutoloadClasses['Gadgets'] = $dir . 'Gadgets_body.php';
 44+$wgAutoloadClasses['GadgetsResourceLoaderModule'] = $dir . 'Gadgets_body.php';
3645 $wgAutoloadClasses['SpecialGadgets'] = $dir . 'SpecialGadgets.php';
 46+
3747 $wgSpecialPages['Gadgets'] = 'SpecialGadgets';
3848 $wgSpecialPageGroups['Gadgets'] = 'wiki';
39 -
40 -function wfGadgetsArticleSaveComplete( $article, $user, $text ) {
41 - //update cache if MediaWiki:Gadgets-definition was edited
42 - $title = $article->mTitle;
43 - if( $title->getNamespace() == NS_MEDIAWIKI && $title->getText() == 'Gadgets-definition' ) {
44 - wfLoadGadgetsStructured( $text );
45 - }
46 - return true;
47 -}
48 -
49 -function wfLoadGadgets() {
50 - static $gadgets = null;
51 -
52 - if ( $gadgets !== null ) return $gadgets;
53 -
54 - $struct = wfLoadGadgetsStructured();
55 - if ( !$struct ) {
56 - $gadgets = $struct;
57 - return $gadgets;
58 - }
59 -
60 - $gadgets = array();
61 - foreach ( $struct as $section => $entries ) {
62 - $gadgets = array_merge( $gadgets, $entries );
63 - }
64 -
65 - return $gadgets;
66 -}
67 -
68 -function wfLoadGadgetsStructured( $forceNewText = null ) {
69 - global $wgMemc;
70 -
71 - static $gadgets = null;
72 - if ( $gadgets !== null && $forceNewText === null ) return $gadgets;
73 -
74 - $key = wfMemcKey( 'gadgets-definition' );
75 -
76 - if ( $forceNewText === null ) {
77 - //cached?
78 - $gadgets = $wgMemc->get( $key );
79 - if ( is_array($gadgets) ) return $gadgets;
80 -
81 - $g = wfMsgForContentNoTrans( "gadgets-definition" );
82 - if ( wfEmptyMsg( "gadgets-definition", $g ) ) {
83 - $gadgets = false;
84 - return $gadgets;
85 - }
86 - } else {
87 - $g = $forceNewText;
88 - }
89 -
90 - $g = preg_replace( '/<!--.*-->/s', '', $g );
91 - $g = preg_split( '/(\r\n|\r|\n)+/', $g );
92 -
93 - $gadgets = array();
94 - $section = '';
95 -
96 - foreach ( $g as $line ) {
97 - if ( preg_match( '/^==+ *([^*:\s|]+?)\s*==+\s*$/', $line, $m ) ) {
98 - $section = $m[1];
99 - }
100 - else if ( preg_match( '/^\*+ *([a-zA-Z](?:[-_:.\w\d ]*[a-zA-Z0-9])?)\s*((\|[^|]*)+)\s*$/', $line, $m ) ) {
101 - //NOTE: the gadget name is used as part of the name of a form field,
102 - // and must follow the rules defined in http://www.w3.org/TR/html4/types.html#type-cdata
103 - // Also, title-normalization applies.
104 - $name = str_replace(' ', '_', $m[1] );
105 -
106 - $code = preg_split( '/\s*\|\s*/', $m[2], -1, PREG_SPLIT_NO_EMPTY );
107 -
108 - if ( $code ) {
109 - $gadgets[$section][$name] = $code;
110 - }
111 - }
112 - }
113 -
114 - //cache for a while. gets purged automatically when MediaWiki:Gadgets-definition is edited
115 - $wgMemc->set( $key, $gadgets, 60*60*24 );
116 - $source = $forceNewText !== null ? 'input text' : 'MediaWiki:Gadgets-definition';
117 - wfDebug( __METHOD__ . ": $source parsed, cache entry $key updated\n");
118 -
119 - return $gadgets;
120 -}
121 -
122 -function wfGadgetsGetPreferences( $user, &$preferences ) {
123 - $gadgets = wfLoadGadgetsStructured();
124 - if (!$gadgets) return true;
125 -
126 - wfLoadExtensionMessages( 'Gadgets' );
127 -
128 - $options = array();
129 - foreach( $gadgets as $section => $thisSection ) {
130 - if ( $section !== '' ) {
131 - $section = wfMsgExt( "gadget-section-$section", 'parseinline' );
132 - $options[$section] = array();
133 - $destination = &$options[$section];
134 - } else {
135 - $destination = &$options;
136 - }
137 - foreach( $thisSection as $gname => $code ) {
138 - $destination[wfMsgExt( "gadget-$gname", 'parseinline' )] = $gname;
139 - }
140 - }
141 -
142 - $preferences['gadgets-intro'] =
143 - array(
144 - 'type' => 'info',
145 - 'label' => '&#160;',
146 - 'default' => Xml::tags( 'tr', array(),
147 - Xml::tags( 'td', array( 'colspan' => 2 ),
148 - wfMsgExt( 'gadgets-prefstext', 'parse' ) ) ),
149 - 'section' => 'gadgets',
150 - 'raw' => 1,
151 - 'rawrow' => 1,
152 - );
153 -
154 - $preferences['gadgets'] =
155 - array(
156 - 'type' => 'multiselect',
157 - 'options' => $options,
158 - 'section' => 'gadgets',
159 - 'label' => '&#160;',
160 - 'prefix' => 'gadget-',
161 - );
162 -
163 - return true;
164 -}
165 -
166 -function wfGadgetsBeforePageDisplay( $out ) {
167 - global $wgUser;
168 - if ( !$wgUser->isLoggedIn() ) return true;
169 -
170 - //disable all gadgets on critical special pages
171 - //NOTE: $out->isUserJsAllowed() is tempting, but always fals if $wgAllowUserJs is false.
172 - // That would disable gadgets on wikis without user JS. Introducing $out->isJsAllowed()
173 - // may work, but should that really apply also to MediaWiki:common.js? Even on the preference page?
174 - // See bug 22929 for discussion.
175 - $title = $out->getTitle();
176 - if ( $title->isSpecial( 'Preferences' )
177 - || $title->isSpecial( 'Resetpass' )
178 - || $title->isSpecial( 'Userlogin' ) ) {
179 - return true;
180 - }
181 -
182 - $gadgets = wfLoadGadgets();
183 - if ( !$gadgets ) return true;
184 -
185 - $lb = new LinkBatch();
186 - $lb->setCaller( __METHOD__ );
187 - $pages = array();
188 -
189 - foreach ( $gadgets as $gname => $id ) {
190 - $tname = "gadget-$gname";
191 - if ( $wgUser->getOption( $tname ) ) {
192 - foreach ( $id as $page ) {
193 - $lb->add( NS_MEDIAWIKI, "Gadget-$page" );
194 - $pages[] = $page;
195 - }
196 - }
197 - }
198 -
199 - $lb->execute( __METHOD__ );
200 -
201 - $done = array();
202 - foreach ( $pages as $page ) {
203 - if ( isset( $done[$page] ) ) continue;
204 - $done[$page] = true;
205 - wfApplyGadgetCode( $page, $out );
206 - }
207 -
208 - return true;
209 -}
210 -
211 -function wfApplyGadgetCode( $page, $out ) {
212 - global $wgJsMimeType;
213 -
214 - //FIXME: stuff added via $out->addScript appears below usercss and userjs in the head tag.
215 - // but we'd want it to appear above explicit user stuff, so it can be overwritten.
216 -
217 - $t = Title::makeTitleSafe( NS_MEDIAWIKI, "Gadget-$page" );
218 - if ( !$t ) continue;
219 -
220 - if ( preg_match( '/\.js/', $page ) ) {
221 - $u = $t->getLocalURL( 'action=raw&ctype=' . $wgJsMimeType );
222 - //switched to addScriptFile call to support scriptLoader
223 - $out->addScriptFile( $u, $t->getLatestRevID() );
224 - } elseif ( preg_match( '/\.css/', $page ) ) {
225 - $u = $t->getLocalURL( 'action=raw&ctype=text/css&' . $t->getLatestRevID() );
226 - $out->addScript( Html::linkedStyle( $u ) );
227 - }
228 -}
229 -

Status & tagging log