r75054 MediaWiki - Code Review archive

Repository:MediaWiki
Revision:r75053‎ | r75054 | r75055 >
Date:22:48, 19 October 2010
Author:tparscal
Status:ok (Comments)
Tags:
Comment:
Refactored ResourceLoaderFileModule, most notably removing the set* methods, which are not in use at the moment, and should only be added when we have a use case for them (API bloat = bad)
Modified paths:
  • /trunk/phase3/includes/resourceloader/ResourceLoaderFileModule.php (modified) (history)

Diff [purge]

Index: trunk/phase3/includes/resourceloader/ResourceLoaderFileModule.php
@@ -23,26 +23,33 @@
2424 defined( 'MEDIAWIKI' ) || die( 1 );
2525
2626 /**
27 - * Module based on local JS/CSS files. This is the most common type of module.
 27+ * ResourceLoader module based on local JS/CSS files
2828 */
2929 class ResourceLoaderFileModule extends ResourceLoaderModule {
3030
3131 /* Protected Members */
32 -
 32+
 33+ /** @var {array} List of paths to JavaScript files to always include */
3334 protected $scripts = array();
34 - protected $styles = array();
35 - protected $messages = array();
36 - protected $group;
37 - protected $dependencies = array();
38 - protected $debugScripts = array();
 35+ /** @var {array} List of paths to JavaScript files to include when using specific languages */
3936 protected $languageScripts = array();
 37+ /** @var {array} List of paths to JavaScript files to include when using specific skins */
4038 protected $skinScripts = array();
 39+ /** @var {array} List of paths to JavaScript files to include in debug mode */
 40+ protected $debugScripts = array();
 41+ /** @var {array} List of paths to JavaScript files to include in the startup module */
 42+ protected $loaderScripts = array();
 43+ /** @var {array} List of paths to CSS files to always include */
 44+ protected $styles = array();
 45+ /** @var {array} List of paths to CSS files to include when using specific skins */
4146 protected $skinStyles = array();
42 - protected $loaders = array();
43 -
44 - // In-object cache for file dependencies
45 - protected $fileDeps = array();
46 - // In-object cache for mtime
 47+ /** @var {array} List of modules this module depends on */
 48+ protected $dependencies = array();
 49+ /** @var {array} List of message keys used by this module */
 50+ protected $messages = array();
 51+ /** @var {array} Name of group this module should be loaded in */
 52+ protected $group;
 53+ /** @var {array} Cache for mtime */
4754 protected $modifiedTime = array();
4855
4956 /* Methods */
@@ -68,7 +75,7 @@
6976 * // Scripts to include in debug contexts
7077 * 'debugScripts' => [file path string or array of file path strings],
7178 * // Scripts to include in the startup module
72 - * 'loaders' => [file path string or array of file path strings],
 79+ * 'loaderScripts' => [file path string or array of file path strings],
7380 * // Modules which must be loaded before this module
7481 * 'dependencies' => [modile name string or array of module name strings],
7582 * // Styles to always load
@@ -89,16 +96,16 @@
9097 // Lists of file paths
9198 case 'scripts':
9299 case 'debugScripts':
93 - case 'loaders':
 100+ case 'loaderScripts':
94101 case 'styles':
95 - $this->{$member} = $this->prefixFilePathList( (array) $option, $basePath );
 102+ $this->{$member} = self::prefixFilePathList( (array) $option, $basePath );
96103 break;
97104 // Collated lists of file paths
98105 case 'languageScripts':
99106 case 'skinScripts':
100107 case 'skinStyles':
101108 foreach ( (array) $option as $key => $value ) {
102 - $this->{$member}[$key] = $this->prefixFilePathList( (array) $value, $basePath );
 109+ $this->{$member}[$key] = self::prefixFilePathList( (array) $value, $basePath );
103110 }
104111 // Lists of strings
105112 case 'dependencies':
@@ -112,213 +119,106 @@
113120 }
114121 }
115122 }
116 -
117 - /**
118 - * Add script files to this module. In order to be valid, a module
119 - * must contain at least one script file.
120 - *
121 - * @param $scripts Mixed: path to script file (string) or array of paths
122 - */
123 - public function addScripts( $scripts ) {
124 - $this->scripts = array_merge( $this->scripts, (array)$scripts );
125 - }
126123
127124 /**
128 - * Add style (CSS) files to this module.
129 - *
130 - * @param $styles Mixed: path to CSS file (string) or array of paths
 125+ * Gets all scripts for a given context concatenated together
 126+ *
 127+ * @param {ResourceLoaderContext} $context Context in which to generate script
 128+ * @return {string} JavaScript code for $context
131129 */
132 - public function addStyles( $styles ) {
133 - $this->styles = array_merge( $this->styles, (array)$styles );
 130+ public function getScript( ResourceLoaderContext $context ) {
 131+ $script = self::readScriptFiles( $this->scripts ) . "\n" .
 132+ self::readScriptFiles( self::tryForKey( $this->languageScripts, $context->getLanguage() ) ) . "\n" .
 133+ self::readScriptFiles( self::tryForKey( $this->skinScripts, $context->getSkin(), 'default' ) ) . "\n";
 134+ if ( $context->getDebug() ) {
 135+ $script .= "\n" . self::readScriptFiles( $this->debugScripts );
 136+ }
 137+ return $script;
134138 }
135139
136140 /**
137 - * Add messages to this module.
138 - *
139 - * @param $messages Mixed: message key (string) or array of message keys
 141+ * Gets loader script
 142+ *
 143+ * @return {string} JavaScript code to be added to startup module
140144 */
141 - public function addMessages( $messages ) {
142 - $this->messages = array_merge( $this->messages, (array)$messages );
 145+ public function getLoaderScript() {
 146+ if ( count( $this->loaders ) == 0 ) {
 147+ return false;
 148+ }
 149+ return self::readScriptFiles( $this->loaders );
143150 }
144 -
145 - /**
146 - * Sets the group of this module.
147 - *
148 - * @param $group string group name
149 - */
150 - public function setGroup( $group ) {
151 - $this->group = $group;
152 - }
153 -
154 - /**
155 - * Add dependencies. Dependency information is taken into account when
156 - * loading a module on the client side. When adding a module on the
157 - * server side, dependency information is NOT taken into account and
158 - * YOU are responsible for adding dependent modules as well. If you
159 - * don't do this, the client side loader will send a second request
160 - * back to the server to fetch the missing modules, which kind of
161 - * defeats the point of using the resource loader in the first place.
162 - *
163 - * To add dependencies dynamically on the client side, use a custom
164 - * loader (see addLoaders())
165 - *
166 - * @param $dependencies Mixed: module name (string) or array of module names
167 - */
168 - public function addDependencies( $dependencies ) {
169 - $this->dependencies = array_merge( $this->dependencies, (array)$dependencies );
170 - }
171151
172152 /**
173 - * Add debug scripts to the module. These scripts are only included
174 - * in debug mode.
175 - *
176 - * @param $scripts Mixed: path to script file (string) or array of paths
 153+ * Gets all styles for a given context concatenated together
 154+ *
 155+ * @param {ResourceLoaderContext} $context Context in which to generate styles
 156+ * @return {string} CSS code for $context
177157 */
178 - public function addDebugScripts( $scripts ) {
179 - $this->debugScripts = array_merge( $this->debugScripts, (array)$scripts );
180 - }
181 -
182 - /**
183 - * Add language-specific scripts. These scripts are only included for
184 - * a given language.
185 - *
186 - * @param $lang String: language code
187 - * @param $scripts Mixed: path to script file (string) or array of paths
188 - */
189 - public function addLanguageScripts( $lang, $scripts ) {
190 - $this->languageScripts = array_merge_recursive(
191 - $this->languageScripts,
192 - array( $lang => $scripts )
193 - );
194 - }
195 -
196 - /**
197 - * Add skin-specific scripts. These scripts are only included for
198 - * a given skin.
199 - *
200 - * @param $skin String: skin name, or 'default'
201 - * @param $scripts Mixed: path to script file (string) or array of paths
202 - */
203 - public function addSkinScripts( $skin, $scripts ) {
204 - $this->skinScripts = array_merge_recursive(
205 - $this->skinScripts,
206 - array( $skin => $scripts )
207 - );
208 - }
209 -
210 - /**
211 - * Add skin-specific CSS. These CSS files are only included for a
212 - * given skin. If there are no skin-specific CSS files for a skin,
213 - * the files defined for 'default' will be used, if any.
214 - *
215 - * @param $skin String: skin name, or 'default'
216 - * @param $scripts Mixed: path to CSS file (string) or array of paths
217 - */
218 - public function addSkinStyles( $skin, $scripts ) {
219 - $this->skinStyles = array_merge_recursive(
220 - $this->skinStyles,
221 - array( $skin => $scripts )
222 - );
223 - }
224 -
225 - /**
226 - * Add loader scripts. These scripts are loaded on every page and are
227 - * responsible for registering this module using
228 - * mediaWiki.loader.register(). If there are no loader scripts defined,
229 - * the resource loader will register the module itself.
230 - *
231 - * Loader scripts are used to determine a module's dependencies
232 - * dynamically on the client side (e.g. based on browser type/version).
233 - * Note that loader scripts are included on every page, so they should
234 - * be lightweight and use mediaWiki.loader.register()'s callback
235 - * feature to defer dependency calculation.
236 - *
237 - * @param $scripts Mixed: path to script file (string) or array of paths
238 - */
239 - public function addLoaders( $scripts ) {
240 - $this->loaders = array_merge( $this->loaders, (array)$scripts );
241 - }
242 -
243 - public function getScript( ResourceLoaderContext $context ) {
244 - $retval = $this->getPrimaryScript() . "\n" .
245 - $this->getLanguageScript( $context->getLanguage() ) . "\n" .
246 - $this->getSkinScript( $context->getSkin() );
247 -
248 - if ( $context->getDebug() ) {
249 - $retval .= $this->getDebugScript();
250 - }
251 -
252 - return $retval;
253 - }
254 -
255158 public function getStyles( ResourceLoaderContext $context ) {
256 - $styles = array();
257 - foreach ( $this->getPrimaryStyles() as $media => $style ) {
258 - if ( !isset( $styles[$media] ) ) {
259 - $styles[$media] = '';
 159+ // Merge general styles and skin specific styles, retaining media type collation
 160+ $styles = self::readStyleFiles( $this->styles );
 161+ $skinStyles = self::readStyleFiles( self::tryForKey( $this->skinStyles, $context->getSkin(), 'default' ) );
 162+ foreach ( $skinStyles as $media => $style ) {
 163+ if ( isset( $styles[$media] ) ) {
 164+ $styles[$media] .= $style;
 165+ } else {
 166+ $styles[$media] = $style;
260167 }
261 - $styles[$media] .= $style;
262168 }
263 - foreach ( $this->getSkinStyles( $context->getSkin() ) as $media => $style ) {
264 - if ( !isset( $styles[$media] ) ) {
265 - $styles[$media] = '';
266 - }
267 - $styles[$media] .= $style;
268 - }
269 -
270169 // Collect referenced files
271170 $files = array();
272 - foreach ( $styles as $style ) {
273 - // Extract and store the list of referenced files
 171+ foreach ( $styles as /* $media => */ $style ) {
274172 $files = array_merge( $files, CSSMin::getLocalFileReferences( $style ) );
275173 }
276 -
277 - // Only store if modified
 174+ // If the list has been modified since last time we cached it, update the cache
278175 if ( $files !== $this->getFileDependencies( $context->getSkin() ) ) {
279 - $encFiles = FormatJson::encode( $files );
280176 $dbw = wfGetDB( DB_MASTER );
281177 $dbw->replace( 'module_deps',
282178 array( array( 'md_module', 'md_skin' ) ), array(
283179 'md_module' => $this->getName(),
284180 'md_skin' => $context->getSkin(),
285 - 'md_deps' => $encFiles,
 181+ 'md_deps' => FormatJson::encode( $files ),
286182 )
287183 );
288184 }
289 -
290185 return $styles;
291186 }
292187
 188+ /**
 189+ * Gets list of message keys used by this module
 190+ *
 191+ * @return {array} List of message keys
 192+ */
293193 public function getMessages() {
294194 return $this->messages;
295195 }
296196
 197+ /**
 198+ * Gets the name of the group this module should be loaded in
 199+ *
 200+ * @return {string} Group name
 201+ */
297202 public function getGroup() {
298203 return $this->group;
299204 }
300205
 206+ /**
 207+ * Gets list of names of modules this module depends on
 208+ *
 209+ * @return {array} List of module names
 210+ */
301211 public function getDependencies() {
302212 return $this->dependencies;
303213 }
304214
305 - public function getLoaderScript() {
306 - if ( count( $this->loaders ) == 0 ) {
307 - return false;
308 - }
309 -
310 - return self::concatScripts( $this->loaders );
311 - }
312 -
313215 /**
314 - * Get the last modified timestamp of this module, which is calculated
315 - * as the highest last modified timestamp of its constituent files and
316 - * the files it depends on (see getFileDependencies()). Only files
317 - * relevant to the given language and skin are taken into account, and
318 - * files only relevant in debug mode are not taken into account when
319 - * debug mode is off.
320 - *
321 - * @param $context ResourceLoaderContext object
322 - * @return Integer: UNIX timestamp
 216+ * Get the last modified timestamp of this module, which is calculated as the highest last modified timestamp of its
 217+ * constituent files and the files it depends on. This function is context-sensitive, only performing calculations
 218+ * on files relevant to the given language, skin and debug mode.
 219+ *
 220+ * @param {ResourceLoaderContext} $context Context in which to calculate the modified time
 221+ * @return {integer} UNIX timestamp
 222+ * @see {ResourceLoaderModule::getFileDependencies}
323223 */
324224 public function getModifiedTime( ResourceLoaderContext $context ) {
325225 if ( isset( $this->modifiedTime[$context->getHash()] ) ) {
@@ -331,8 +231,8 @@
332232 foreach ( self::collateFilePathListByOption( $this->styles, 'media', 'all' ) as $styleFiles ) {
333233 $styles = array_merge( $styles, $styleFiles );
334234 }
335 - $skinFiles = (array) self::getSkinFiles(
336 - $context->getSkin(), self::collateFilePathListByOption( $this->skinStyles, 'media', 'all' )
 235+ $skinFiles = self::tryForKey(
 236+ self::collateFilePathListByOption( $this->skinStyles, 'media', 'all' ), $context->getSkin(), 'default'
337237 );
338238 foreach ( $skinFiles as $styleFiles ) {
339239 $styles = array_merge( $styles, $styleFiles );
@@ -343,15 +243,14 @@
344244 $this->scripts,
345245 $styles,
346246 $context->getDebug() ? $this->debugScripts : array(),
347 - isset( $this->languageScripts[$context->getLanguage()] ) ?
348 - (array) $this->languageScripts[$context->getLanguage()] : array(),
349 - (array) self::getSkinFiles( $context->getSkin(), $this->skinScripts ),
350 - $this->loaders,
 247+ self::tryForKey( $this->languageScripts, $context->getLanguage() ),
 248+ self::tryForKey( $this->skinScripts, $context->getSkin(), 'default' ),
 249+ $this->loaderScripts,
351250 $this->getFileDependencies( $context->getSkin() )
352251 );
353252
354253 wfProfileIn( __METHOD__.'-filemtime' );
355 - $filesMtime = max( array_map( 'filemtime', array_map( array( __CLASS__, 'remapFilename' ), $files ) ) );
 254+ $filesMtime = max( array_map( 'filemtime', array_map( array( __CLASS__, 'resolveFilePath' ), $files ) ) );
356255 wfProfileOut( __METHOD__.'-filemtime' );
357256 $this->modifiedTime[$context->getHash()] = max( $filesMtime, $this->getMsgBlobMtime( $context->getLanguage() ) );
358257 wfProfileOut( __METHOD__ );
@@ -367,7 +266,7 @@
368267 * @param {string} $prefix String to prepend to each file path in $list
369268 * @return {array} List of prefixed file paths
370269 */
371 - protected function prefixFilePathList( array $list, $prefix ) {
 270+ protected static function prefixFilePathList( array $list, $prefix ) {
372271 $prefixed = array();
373272 foreach ( $list as $key => $value ) {
374273 if ( is_array( $value ) ) {
@@ -385,6 +284,7 @@
386285 * Collates file paths by option (where provided)
387286 *
388287 * @param {array} $list List of file paths in any combination of index/path or path/options pairs
 288+ * @return {array} List of file paths, collated by $option
389289 */
390290 protected static function collateFilePathListByOption( array $list, $option, $default ) {
391291 $collatedFiles = array();
@@ -408,148 +308,96 @@
409309 }
410310
411311 /**
412 - * Get the primary JS for this module. This is pulled from the
413 - * script files added through addScripts()
 312+ * Gets a list of element that match a key, optionally using a fallback key
414313 *
415 - * @return String: JS
 314+ * @param {array} $map Map of lists to select from
 315+ * @param {string} $key Key to look for in $map
 316+ * @param {string} $fallback Key to look for in map if $key is not in $map
 317+ * @return {array} List of elements from $map which matched $key or $fallback, or an empty list in case of no match
416318 */
417 - protected function getPrimaryScript() {
418 - return self::concatScripts( $this->scripts );
 319+ protected static function tryForKey( $list, $key, $fallback = null ) {
 320+ if ( isset( $list[$key] ) && is_array( $list[$key] ) ) {
 321+ return $list[$key];
 322+ } else if ( is_string( $fallback ) && isset( $list[$fallback] ) ) {
 323+ return $list[$fallback];
 324+ }
 325+ return array();
419326 }
420327
421328 /**
422 - * Get the primary CSS for this module. This is pulled from the CSS
423 - * files added through addStyles()
424 - *
425 - * @return Array
 329+ * Get the contents of a list of JavaScript files
 330+ *
 331+ * @param {array} $scripts List of file paths to scripts to read, remap and concetenate
 332+ * @return {string} Concatenated and remapped JavaScript data from $scripts
426333 */
427 - protected function getPrimaryStyles() {
428 - return self::concatStyles( $this->styles );
429 - }
430 -
431 - /**
432 - * Get the debug JS for this module. This is pulled from the script
433 - * files added through addDebugScripts()
434 - *
435 - * @return String: JS
436 - */
437 - protected function getDebugScript() {
438 - return self::concatScripts( $this->debugScripts );
439 - }
440 -
441 - /**
442 - * Get the language-specific JS for a given language. This is pulled
443 - * from the language-specific script files added through addLanguageScripts()
444 - *
445 - * @return String: JS
446 - */
447 - protected function getLanguageScript( $lang ) {
448 - if ( !isset( $this->languageScripts[$lang] ) ) {
 334+ protected static function readScriptFiles( array $scripts ) {
 335+ if ( empty( $scripts ) ) {
449336 return '';
450337 }
451 - return self::concatScripts( $this->languageScripts[$lang] );
 338+ return implode( "\n", array_map( array( __CLASS__, 'readScriptFile' ), array_unique( $scripts ) ) );
452339 }
453340
454341 /**
455 - * Get the skin-specific JS for a given skin. This is pulled from the
456 - * skin-specific JS files added through addSkinScripts()
457 - *
458 - * @return String: JS
 342+ * Get the contents of a list of CSS files
 343+ *
 344+ * @param {array} $styles List of file paths to styles to read, remap and concetenate
 345+ * @return {array} List of concatenated and remapped CSS data from $styles, keyed by media type
459346 */
460 - protected function getSkinScript( $skin ) {
461 - return self::concatScripts( self::getSkinFiles( $skin, $this->skinScripts ) );
462 - }
463 -
464 - /**
465 - * Get the skin-specific CSS for a given skin. This is pulled from the
466 - * skin-specific CSS files added through addSkinStyles()
467 - *
468 - * @return Array: list of CSS strings keyed by media type
469 - */
470 - protected function getSkinStyles( $skin ) {
471 - return self::concatStyles( self::getSkinFiles( $skin, $this->skinStyles ) );
472 - }
473 -
474 - /**
475 - * Helper function to get skin-specific data from an array.
476 - *
477 - * @param $skin String: skin name
478 - * @param $map Array: map of skin names to arrays
479 - * @return $map[$skin] if set and non-empty, or $map['default'] if set, or an empty array
480 - */
481 - protected static function getSkinFiles( $skin, $map ) {
482 - $retval = array();
483 -
484 - if ( isset( $map[$skin] ) && $map[$skin] ) {
485 - $retval = $map[$skin];
486 - } else if ( isset( $map['default'] ) ) {
487 - $retval = $map['default'];
 347+ protected static function readStyleFiles( array $styles ) {
 348+ if ( empty( $styles ) ) {
 349+ return array();
488350 }
489 -
490 - return $retval;
491 - }
492 -
493 - /**
494 - * Get the contents of a set of files and concatenate them, with
495 - * newlines in between. Each file is used only once.
496 - *
497 - * @param $files Array of file names
498 - * @return String: concatenated contents of $files
499 - */
500 - protected static function concatScripts( $files ) {
501 - return implode( "\n",
502 - array_map(
503 - 'file_get_contents',
504 - array_map(
505 - array( __CLASS__, 'remapFilename' ),
506 - array_unique( (array) $files ) ) ) );
507 - }
508 -
509 - /**
510 - * Get the contents of a set of CSS files, remap then and concatenate
511 - * them, with newlines in between. Each file is used only once.
512 - *
513 - * @param $styles Array of file names
514 - * @return Array: list of concatenated and remapped contents of $files keyed by media type
515 - */
516 - protected static function concatStyles( $styles ) {
517351 $styles = self::collateFilePathListByOption( $styles, 'media', 'all' );
518352 foreach ( $styles as $media => $files ) {
519 - $styles[$media] =
520 - implode( "\n",
521 - array_map(
522 - array( __CLASS__, 'remapStyle' ),
523 - array_unique( (array) $files ) ) );
 353+ $styles[$media] = implode(
 354+ "\n", array_map( array( __CLASS__, 'readStyleFile' ), array_unique( $files ) )
 355+ );
524356 }
525357 return $styles;
526358 }
527359
528360 /**
529 - * Remap a relative to $IP. Used as a callback for array_map()
530 - *
531 - * @param $file String: file name
532 - * @return string $IP/$file
 361+ * Reads a script file
 362+ *
 363+ * This method can be used as a callback for array_map()
 364+ *
 365+ * @param {string} $path File path of script file to read
 366+ * @return {string} JavaScript data in script file
533367 */
534 - protected static function remapFilename( $file ) {
 368+ protected static function readScriptFile( $path ) {
535369 global $IP;
536 -
537 - return "$IP/$file";
 370+
 371+ return file_get_contents( "$IP/$path" );
538372 }
539373
540374 /**
541 - * Get the contents of a CSS file and run it through CSSMin::remap().
542 - * This wrapper is needed so we can use array_map() in concatStyles()
543 - *
544 - * @param $file String: file name
545 - * @return string Remapped CSS
 375+ * Reads a style file
 376+ *
 377+ * This method can be used as a callback for array_map()
 378+ *
 379+ * @param {string} $path File path of script file to read
 380+ * @return {string} CSS data in script file
546381 */
547 - protected static function remapStyle( $file ) {
548 - global $wgScriptPath;
 382+ protected static function readStyleFile( $path ) {
 383+ global $wgScriptPath, $IP;
 384+
549385 return CSSMin::remap(
550 - file_get_contents( self::remapFilename( $file ) ),
551 - dirname( $file ),
552 - $wgScriptPath . '/' . dirname( $file ),
553 - true
 386+ file_get_contents( "$IP/$path" ), dirname( $path ), $wgScriptPath . '/' . dirname( $path ), true
554387 );
555388 }
 389+
 390+ /**
 391+ * Resolve a file name
 392+ *
 393+ * This method can be used as a callback for array_map()
 394+ *
 395+ * @param {string} $path File path to resolve
 396+ * @return {string} Absolute file path
 397+ */
 398+ protected static function resolveFilePath( $path ) {
 399+ global $IP;
 400+
 401+ return "$IP/$path";
 402+ }
 403+
556404 }

Follow-up revisions

RevisionCommit summaryAuthorDate
r75107* Improves on r75054 and r75036 by adding comments and renaming variables...tparscal20:43, 20 October 2010

Comments

#Comment by Catrope (talk | contribs)   15:30, 20 October 2010
+	/** @var {array} List of paths to JavaScript files to include when using specific languages */
 	protected $languageScripts = array();
+	/** @var {array} List of paths to JavaScript files to include when using specific skins */
 	protected $skinScripts = array();
+	/** @var {array} List of paths to JavaScript files to include in debug mode */
+	protected $debugScripts = array();
+	/** @var {array} List of paths to JavaScript files to include in the startup module */
+	protected $loaderScripts = array();
+	/** @var {array} List of paths to CSS files to always include */
+	protected $styles = array();
+	/** @var {array} List of paths to CSS files to include when using specific skins */
 	protected $skinStyles = array();

Would be nice to also document the format of these arrays.

+	/** @var {array} Name of group this module should be loaded in */
+	protected $group;

That's not an array.

Status & tagging log