Overview

Namespaces

  • None
  • PHP

Classes

  • Breadcrumb
  • Breadcrumbs
  • CacheFile
  • Comment
  • CommentArray
  • CommentFilter
  • CommentForm
  • CommentList
  • Config
  • Database
  • DatabaseQuery
  • DatabaseQuerySelect
  • DatabaseQuerySelectFulltext
  • DatabaseStopwords
  • Debug
  • Field
  • Fieldgroup
  • Fieldgroups
  • FieldgroupsArray
  • Fields
  • FieldsArray
  • Fieldtype
  • FieldtypeMulti
  • Fieldtypes
  • FileLog
  • FilenameArray
  • Fuel
  • HookEvent
  • ImageSizer
  • Inputfield
  • InputfieldsArray
  • InputfieldWrapper
  • Language
  • LanguageParser
  • Languages
  • LanguagesPageFieldValue
  • LanguageSupportInstall
  • LanguageTranslator
  • Markdown_Parser
  • MarkdownExtra_Parser
  • ModuleJS
  • ModulePlaceholder
  • Modules
  • Notice
  • NoticeError
  • NoticeMessage
  • Notices
  • NullPage
  • Page
  • PageArray
  • Pagefile
  • Pagefiles
  • PagefilesManager
  • PageFinder
  • Pageimage
  • Pageimages
  • PagerNav
  • PagerNavItem
  • Pages
  • PagesAccess
  • PagesSortfields
  • PagesType
  • Paths
  • Permission
  • Permissions
  • Process
  • ProcessController
  • ProcessWire
  • Role
  • Roles
  • Sanitizer
  • Selector
  • SelectorBitwiseAnd
  • SelectorContains
  • SelectorContainsLike
  • SelectorContainsWords
  • SelectorEnds
  • SelectorEqual
  • SelectorGreaterThan
  • SelectorGreaterThanEqual
  • SelectorLessThan
  • SelectorLessThanEqual
  • SelectorNotEqual
  • Selectors
  • SelectorStarts
  • Session
  • SessionCSRF
  • SmartyPants_Parser
  • SmartyPantsTypographer_Parser
  • SystemUpdate1
  • Template
  • TemplateFile
  • Templates
  • TemplatesArray
  • Textformatter
  • Textile
  • User
  • Users
  • Wire
  • WireArray
  • WireData
  • WireInput
  • WireInputData
  • WireSaveableItems
  • WireSaveableItemsLookup
  • WireUpload

Interfaces

  • CommentFormInterface
  • CommentListInterface
  • ConfigurableModule
  • FieldtypeLanguageInterface
  • FieldtypePageTitleCompatible
  • HasLookupItems
  • HasRoles
  • InputfieldHasArrayValue
  • Module
  • Saveable
  • TrackChanges

Exceptions

  • ProcessController404Exception
  • ProcessControllerPermissionException
  • Wire404Exception
  • WireDatabaseException
  • WireException
  • WirePermissionException

Functions

  • __
  • _n
  • _x
  • fuel
  • identify_modifier_markdown
  • Markdown
  • mdwp_add_p
  • mdwp_hide_tags
  • mdwp_show_tags
  • mdwp_strip_p
  • ProcessWireClassLoader
  • ProcessWireHostSiteConfig
  • ProcessWireShutdown
  • removeNewlines
  • SmartDashes
  • SmartEllipsis
  • SmartQuotes
  • smarty_modifier_markdown
  • smarty_modifier_smartypants
  • SmartyPants
  • tabIndent
  • unregisterGLOBALS
  • wire
  • wireDecodeJSON
  • wireEncodeJSON
  • wireMkdir
  • Overview
  • Namespace
  • Class
  • Tree
  • Download
  1: <?php
  2: 
  3: /**
  4:  * ProcessWire Modules
  5:  *
  6:  * Loads and manages all runtime modules for ProcessWire
  7:  *
  8:  * Note that when iterating, find(), or calling any other method that returns module(s), excepting get(), a ModulePlaceholder may be
  9:  * returned rather than a real Module. ModulePlaceholders are used in instances when the module may or may not be needed at runtime
 10:  * in order to save resources. As a result, anything iterating through these Modules should check to make sure it's not a ModulePlaceholder
 11:  * before using it. If it's a ModulePlaceholder, then the real Module can be instantiated/retrieved by $modules->get($className).
 12:  * 
 13:  * ProcessWire 2.x 
 14:  * Copyright (C) 2010 by Ryan Cramer 
 15:  * Licensed under GNU/GPL v2, see LICENSE.TXT
 16:  * 
 17:  * http://www.processwire.com
 18:  * http://www.ryancramer.com
 19:  *
 20:  */
 21: 
 22: class Modules extends WireArray {
 23: 
 24:     /**
 25:      * Flag indicating the module may have only one instance at runtime. 
 26:      *
 27:      */
 28:     const flagsSingular = 1; 
 29: 
 30:     /**
 31:      * Flag indicating that the module should be instantiated at runtime, rather than when called upon. 
 32:      *
 33:      */
 34:     const flagsAutoload = 2; 
 35: 
 36:     /**
 37:      * Array of modules that are not currently installed, indexed by className => filename
 38:      *
 39:      */
 40:     protected $installable = array(); 
 41: 
 42:     /**
 43:      * An array of module database IDs indexed by: class => id 
 44:      *
 45:      * Used internally for database operations
 46:      *
 47:      */
 48:     protected $moduleIDs = array();
 49: 
 50:     /**
 51:      * Path where system modules are stored
 52:      *
 53:      */
 54:     protected $modulePath = '';
 55: 
 56:     /**
 57:      * Path where site-specific modules are stored
 58:      *
 59:      */
 60:     protected $modulePath2  = '';
 61: 
 62:     /**
 63:      * Cached module configuration data
 64:      *
 65:      */
 66:     protected $configData = array();
 67: 
 68:     /**
 69:      * Have the modules been init'd() ?
 70:      *
 71:      */
 72:     protected $initialized = false;
 73: 
 74:     /**
 75:      * Construct the Modules
 76:      *
 77:      * @param string $path Path to modules
 78:      * @param string $path2 Optional path to siteModules
 79:      * @see load()
 80:      *
 81:      */
 82:     public function __construct($path, $path2 = null) {
 83:         $this->setTrackChanges(false); 
 84:         $this->modulePath = $path; 
 85:         $this->load($path); 
 86:         if($path2 && is_dir($path2)) {
 87:             $this->modulePath2 = $path2; 
 88:             $this->load($path2);
 89:         }
 90:     }
 91: 
 92:     /**
 93:      * Modules class accepts only Module instances, per the WireArray interface
 94:      *
 95:      */
 96:     public function isValidItem($item) {
 97:         return $item instanceof Module;
 98:     }
 99: 
100:     /**
101:      * The key/index used for each module in the array is it's class name, per the WireArray interface
102:      *
103:      */
104:     public function getItemKey($item) {
105:         return $this->getModuleClass($item); 
106:     }
107: 
108:     /**
109:      * There is no blank/generic module type, so makeBlankItem returns null
110:      *
111:      */
112:     public function makeBlankItem() {
113:         return null; 
114:     }
115: 
116:     /**
117:      * Initialize all the modules that are loaded at boot
118:      *
119:      */
120:     public function triggerInit() {
121: 
122:         foreach($this as $module) {
123:             // if the module is configurable, then load it's config data
124:             // and set values for each before initializing themodule
125:             $this->setModuleConfigData($module); 
126:             $module->init();
127: 
128:             // if module is autoload (assumed here) and singular, then
129:             // we no longer need the module's config data, so remove it
130:             if($this->isSingular($module)) {
131:                 $id = $this->getModuleID($module); 
132:                 unset($this->configData[$id]); 
133:             }
134:         }
135:         $this->initialized = true; 
136:     }
137: 
138:     /**
139:      * Trigger all modules 'ready' method, if they have it.
140:      *
141:      * This is to indicate to them that the API environment is fully ready and $page is in fuel.
142:      *
143:      * This is triggered by ProcessPageView::ready
144:      *
145:      */
146:     public function triggerReady() {
147: 
148:         foreach($this as $module) {
149:             if($module instanceof ModulePlaceholder) continue;
150:             if(!method_exists($module, 'ready')) continue;
151:             if(!$this->isAutoload($module)) continue; 
152:             $module->ready();
153:         }
154:     }
155: 
156:     /**
157:      * Given a disk path to the modules, instantiate all installed modules and keep track of all uninstalled (installable) modules. 
158:      *
159:      * @param string $path 
160:      *
161:      */
162:     protected function load($path) {
163: 
164:         static $installed = array();
165: 
166:         if(!count($installed)) {
167:             $result = $this->fuel('db')->query("SELECT id, class, flags, data FROM modules ORDER BY class");
168:             while($row = $result->fetch_assoc()) {
169:                 if($row['flags'] & self::flagsAutoload) {
170:                     // preload config data for autoload modules since we'll need it again very soon
171:                     $this->configData[$row['id']] = wireDecodeJSON($row['data']); 
172:                 }
173:                 unset($row['data']);
174:                 $installed[$row['class']] = $row;
175:             }
176:             $result->free();
177:         }
178: 
179:         $files = $this->findModuleFiles($path, true); 
180: 
181:         foreach($files as $pathname) {
182: 
183:             $pathname = $path . $pathname;
184:             $dirname = dirname($pathname);
185:             $filename = basename($pathname); 
186:             $basename = basename($filename, '.module'); 
187: 
188:             // if the filename doesn't end with .module, then stop and move onto the next
189:             if(!strpos($filename, '.module') || substr($filename, -7) !== '.module') continue; 
190: 
191:             //  if the filename doesn't start with the requested path, then continue
192:             if(strpos($pathname, $path) !== 0) continue; 
193: 
194:             // if the file isn't there, it was probably uninstalled, so ignore it
195:             if(!is_file($pathname)) continue; 
196: 
197:             // if the module isn't installed, then stop and move on to next
198:             if(!array_key_exists($basename, $installed)) {  
199:                 $this->installable[$basename] = $pathname; 
200:                 continue; 
201:             }
202: 
203:             $info = $installed[$basename]; 
204:             $this->setConfigPaths($basename, $dirname); 
205: 
206:             if($info['flags'] & self::flagsAutoload) { 
207:                 // this is an Autoload mdoule. 
208:                 // include the module and instantiate it but don't init() it,
209:                 // because it will be done by Modules::init()
210:                 include_once($pathname); 
211:                 $module = new $basename(); 
212: 
213:             } else {
214:                 // placeholder for a module, which is not yet included and instantiated
215:                 $module = new ModulePlaceholder(); 
216:                 $module->setClass($basename); 
217:                 $module->singular = $info['flags'] & self::flagsSingular; 
218:                 $module->file = $pathname; 
219:             }
220: 
221:             $this->moduleIDs[$basename] = $info['id']; 
222:             $this->set($basename, $module); 
223:         }
224: 
225:     }
226: 
227:     /**
228:      * Find new module files in the given $path
229:      *
230:      * If $readCache is true, this will perform the find from the cache
231:      *
232:      * @param string $path Path to the modules
233:      * @param bool $readCache Optional. If set to true, then this method will attempt to read modules from the cache. 
234:      * @param int $level For internal recursive use.
235:      * @return array Array of module files
236:      *
237:      */
238:     protected function findModuleFiles($path, $readCache = false, $level = 0) {
239: 
240:         static $startPath;
241: 
242:         $config = $this->fuel('config');
243: 
244:         if($level == 0) {
245:             $startPath = $path;
246:             $cacheFilename = $config->paths->cache . "Modules." . md5($path) . ".cache";
247:             if($readCache && is_file($cacheFilename)) return explode("\n", file_get_contents($cacheFilename)); 
248:         }
249: 
250:         $files = array();
251:         $dir = new DirectoryIterator($path); 
252:         
253:         foreach($dir as $file) {
254: 
255:             if($file->isDot()) continue; 
256: 
257:             $filename = $file->getFilename();
258:             $pathname = $file->getPathname();
259: 
260:             if(DIRECTORY_SEPARATOR != '/') {
261:                 $pathname = str_replace(DIRECTORY_SEPARATOR, '/', $pathname); 
262:                 $filename = str_replace(DIRECTORY_SEPARATOR, '/', $filename); 
263:             }
264: 
265:             // if it's a directory with a .module file in it named the same as the dir, then descend into it
266:             if($file->isDir() && ($level < 1 || is_file("$pathname/$filename.module"))) {
267:                 $files = array_merge($files, $this->findModuleFiles($pathname, false, $level + 1));
268:             }
269: 
270:             // if the filename doesn't end with .module, then stop and move onto the next
271:             if(!strpos($filename, '.module') || substr($filename, -7) !== '.module') continue; 
272: 
273:             $files[] = str_replace($startPath, '', $pathname); 
274:         }
275: 
276:         if($level == 0) @file_put_contents($cacheFilename, implode("\n", $files), LOCK_EX); 
277:         if($config->chmodFile) @chmod($cacheFilename, octdec($config->chmodFile));
278: 
279:         return $files;
280:     }
281: 
282: 
283:     /**
284:      * Setup entries in config->urls and config->paths for the given module
285:      *
286:      * @param string $moduleName
287:      * @param string $path
288:      *
289:      */
290:     protected function setConfigPaths($moduleName, $path) {
291:         $config = $this->fuel('config'); 
292:         $path = rtrim($path, '/'); 
293:         $path = substr($path, strlen($config->paths->root)) . '/';
294:         $config->paths->set($moduleName, $path);
295:         $config->urls->set($moduleName, $path); 
296:     }
297: 
298:     /**
299:      * Get the requsted Module or NULL if it doesn't exist. 
300:      *
301:      * If the module is a ModulePlaceholder, then it will be converted to the real module (included, instantiated, init'd) .
302:      * If the module is not installed, but is installable, it will be installed, instantiated, and init'd. 
303:      * This method is the only one guaranteed to return a real [non-placeholder] module. 
304:      *
305:      * @param string|int $key Module className or database ID
306:      * @return Module|null
307:      *
308:      */
309:     public function get($key) {
310: 
311:         $module = null; 
312:         $justInstalled = false;
313: 
314:         // check for optional module ID and convert to classname if found
315:         if(ctype_digit("$key")) {
316:             if(!$key = array_search($key, $this->moduleIDs)) return null;
317:         }
318: 
319:         if($module = parent::get($key)) {
320: 
321:             // check if it's a placeholder, and if it is then include/instantiate/init the real module 
322:             // OR check if it's non-singular, so that a new instance is created
323:             if($module instanceof ModulePlaceholder || !$this->isSingular($module)) {
324:                 $placeholder = $module; 
325:                 $class = $this->getModuleClass($placeholder); 
326:                 if($module instanceof ModulePlaceholder) $this->includeModule($module); 
327:                 $module = new $class(); 
328:                 if($this->isSingular($placeholder)) $this->set($key, $module); 
329:             }
330: 
331:         } else if(array_key_exists($key, $this->getInstallable())) {
332:             // check if the request is for an uninstalled module 
333:             // if so, install it and return it 
334:             $module = $this->install($key); 
335:             $justInstalled = true; 
336:         }
337: 
338:         // skip autoload modules because they have already been initialized in the load() method
339:         // unless they were just installed, in which case we need do init now
340:         if($module && (!$this->isAutoload($module) || $justInstalled)) { 
341:             // if the module is configurable, then load it's config data
342:             // and set values for each before initializing the module
343:             $this->setModuleConfigData($module); 
344:             $module->init();
345:         }
346: 
347:         return $module; 
348:     }
349: 
350:     /**
351:      * Include the file for a given module, but don't instantiate it 
352:      *
353:      * @param ModulePlaceholder|Module|string Expects a ModulePlaceholder or className
354:      * @return bool true on success
355:      *
356:      */
357:     public function includeModule($module) {
358: 
359:         if(is_string($module)) $module = parent::get($module); 
360:         if(!$module) return false; 
361: 
362:         if($module instanceof ModulePlaceholder) {
363:             include_once($module->file);            
364:         } else {
365:             // it's already been included, no doubt
366:         }
367:         return true; 
368:     }
369: 
370:     /**
371:      * Find modules based on a selector string and ensure any ModulePlaceholders are loaded in the returned result
372:      *
373:      * @param string $selector
374:      * @return Modules
375:      *  
376:      */
377:     public function find($selector) {
378:         $a = parent::find($selector); 
379:         if($a) {
380:             foreach($a as $key => $value) {
381:                 $a[$key] = $this->get($value->className()); 
382:             }
383:         }
384:         return $a; 
385:     }
386: 
387:     /**
388:      * Get an array of all modules that aren't currently installed
389:      *
390:      * @return array Array of elements with $className => $pathname
391:      *
392:      */
393:     public function getInstallable() {
394:         return $this->installable; 
395:     }
396: 
397:     /**
398:      * Is the given class name installed?
399:      *
400:      * @param string $class
401:      * @return bool
402:      *
403:      */
404:     public function isInstalled($class) {
405:         return (parent::get($class) !== null);
406:     }
407: 
408: 
409:     /**
410:      * Is the given class name not installed?
411:      *
412:      * @param string $class
413:      * @return bool
414:      *
415:      */
416:     public function isInstallable($class) {
417:         return array_key_exists($class, $this->installable); 
418:     }
419: 
420:     /**
421:      * Install the given class name
422:      *
423:      * @param string $class
424:      * @return null|Module Returns null if unable to install, or instantiated Module object if successfully installed. 
425:      *
426:      */
427:     public function ___install($class) {
428: 
429:         if(!$this->isInstallable($class)) return null; 
430:         $pathname = $this->installable[$class];     
431:         require_once($pathname); 
432:         $this->setConfigPaths($class, dirname($pathname)); 
433: 
434:         $requires = $this->getRequiresForInstall($class); 
435:         if(count($requires)) throw new WireException("Module $class requires: " . implode(", ", $requires)); 
436: 
437:         $module = new $class();
438: 
439:         $flags = 0; 
440:         if($this->isSingular($module)) $flags = $flags | self::flagsSingular; 
441:         if($this->isAutoload($module)) $flags = $flags | self::flagsAutoload; 
442: 
443:         $sql =  "INSERT INTO modules SET " . 
444:             "class='" . $this->fuel('db')->escape_string($class) . "', " . 
445:             "flags=$flags, " . 
446:             "data='' ";
447: 
448:         $result = $this->fuel('db')->query($sql); 
449:         $moduleID = $this->fuel('db')->insert_id;
450:         $this->moduleIDs[$class] = $moduleID;
451: 
452:         $this->add($module); 
453:         unset($this->installable[$class]); 
454: 
455:         // note: the module's install is called here because it may need to know it's module ID for installation of permissions, etc. 
456:         if(method_exists($module, '___install') || method_exists($module, 'install')) {
457:             try {
458:                 $module->install();
459: 
460:             } catch(Exception $e) {
461:                 // remove the module from the modules table if the install failed
462:                 $this->fuel('db')->query("DELETE FROM modules WHERE id='$moduleID' LIMIT 1");               
463:                 throw new WireException("Unable to install module '$class': " . $e->getMessage()); 
464:             }
465:         }
466: 
467:         $info = $this->getModuleInfo($class); 
468: 
469:         // check if there are any modules in 'installs' that this module didn't handle installation of, and install them
470:         $label = "Module Auto Install:";
471:         foreach($info['installs'] as $name) {
472:             if(!$this->isInstalled($name)) {
473:                 try { 
474:                     $this->install($name); 
475:                     $this->message("$label $name"); 
476:                 } catch(Exception $e) {
477:                     $this->error("$label $name - " . $e->getMessage());     
478:                 }
479:             }
480:         }
481: 
482:         return $module; 
483:     }
484: 
485:     /**
486:      * Returns whether the module can be uninstalled
487:      *
488:      * @param string|Module $class
489:      * @param bool $returnReason If true, the reason why it can't be uninstalled with be returned rather than boolean false.
490:      * @return bool|string 
491:      *
492:      */
493:     public function isUninstallable($class, $returnReason = false) {
494: 
495:         $reason = '';
496:         $reason1 = "Module is not already installed";
497:         $class = $this->getModuleClass($class); 
498: 
499:         if(!$this->isInstalled($class)) {
500:             $reason = $reason1;
501: 
502:         } else {
503:             $this->includeModule($class); 
504:             if(!class_exists($class)) $reason = $reason1; 
505:         }
506: 
507:         if(!$reason) {
508: 
509:             // if the moduleInfo contains a non-empty 'permanent' property, then it's not uninstallable
510:             $info = $this->getModuleInfo($class); 
511:             if(!empty($info['permanent'])) {
512:                 $reason = "Module is permanent"; 
513:             } else {
514:                 $dependents = $this->getRequiresForUninstall($class);   
515:                 if(count($dependents)) $reason = "Module is required by other modules that must be uninstalled first"; 
516:             }
517:         }
518: 
519:         if(!$reason && in_array('Fieldtype', class_parents($class))) {
520:             foreach(wire('fields') as $field) {
521:                 $fieldtype = get_class($field->type);
522:                 if($fieldtype == $class) { 
523:                     $reason = "This module is a Fieldtype currently in use by one or more fields";
524:                     break;
525:                 }
526:             }
527:         }
528: 
529:         if($returnReason && $reason) return $reason;
530:     
531:         return $reason ? false : true;  
532:     }
533: 
534:     /**
535:      * Uninstall the given class name
536:      *
537:      * @param string $class
538:      * @return bool
539:      *
540:      */
541:     public function ___uninstall($class) {
542: 
543:         $class = $this->getModuleClass($class); 
544:         $reason = $this->isUninstallable($class, true); 
545:         if($reason !== true) throw new WireException("$class - Can't Uninstall - $reason"); 
546: 
547:         $info = $this->getModuleInfo($class); 
548:         $module = $this->get($class); 
549:         
550:         if(method_exists($module, '___uninstall') || method_exists($module, 'uninstall')) {
551:             // note module's uninstall method may throw an exception to abort the uninstall
552:             $module->uninstall();
553:         }
554: 
555:         $result = $this->fuel('db')->query("DELETE FROM modules WHERE class='" . $this->fuel('db')->escape_string($class) . "' LIMIT 1"); 
556:         if(!$result) return false; 
557: 
558:         // check if there are any modules still installed that this one says it is responsible for installing
559:         foreach($info['installs'] as $name) {
560: 
561:             // if module isn't installed, then great
562:             if(!$this->isInstalled($name)) continue; 
563: 
564:             // if an 'installs' module doesn't indicate that it requires this one, then leave it installed
565:             $i = $this->getModuleInfo($name); 
566:             if(!in_array($class, $i['requires'])) continue; 
567: 
568:             // catch uninstall exceptions at this point since original module has already been uninstalled
569:             $label = "Module Auto Uninstall";
570:             try { 
571:                 $this->uninstall($name); 
572:                 $this->message("$label - $name"); 
573: 
574:             } catch(Exception $e) {
575:                 $this->error("$label - $name - " . $e->getMessage()); 
576:             }
577:         }
578: 
579:         unset($this->moduleIDs[$class]);
580:         $this->remove($module); 
581: 
582:         return true; 
583:     }
584: 
585:     /**
586:      * Returns the database ID of a given module class, or 0 if not found
587:      *
588:      * @param string|Module $class
589:      * @return int
590:      *
591:      */
592:     public function getModuleID($class) {
593: 
594:         if(is_object($class)) {
595:             if($class instanceof Module) $class = $this->getModuleClass($class); 
596:                 else throw new WireException("Unknown module type"); 
597:         }
598: 
599:         return isset($this->moduleIDs[$class]) ? (int) $this->moduleIDs[$class] : 0; 
600:     }
601: 
602:     /**
603:      * Returns the module's class name. 
604:      *
605:      * Given a numeric database ID, returns the associated module class name or false if it doesn't exist
606:      *
607:      * Given a Module or ModulePlaceholder instance, returns the Module's class name. 
608:      *
609:      * If the module has a className() method then it uses that rather than PHP's get_class().
610:      * This is important because of placeholder modules. For example, get_class would return 
611:      * 'ModulePlaceholder' rather than the correct className for a Module.
612:      *
613:      * @param string|int|Module
614:      * @return string|false The Module's class name or false if not found. 
615:      *  Note that 'false' is only possible if you give this method a non-Module, or an integer ID 
616:      *  that doesn't correspond to a module ID. 
617:      *
618:      */
619:     public function getModuleClass($module) {
620: 
621:         if($module instanceof Module) {
622:             if(method_exists($module, 'className')) return $module->className();    
623:             return get_class($module); 
624: 
625:         } else if(is_int($module) || ctype_digit("$module")) {
626:             return array_search((int) $module, $this->moduleIDs); 
627: 
628:         }  else if(is_string($module)) { 
629:             if(array_key_exists($module, $this->moduleIDs)) return $module; 
630:             if(array_key_exists($module, $this->installable)) return $module; 
631:         }
632: 
633:         return false; 
634:     }
635: 
636: 
637:     /**
638:      * Returns the standard array of information for a Module
639:      *
640:      * @param string|Module|int $module May be class name, module instance, or module ID
641:      * @return array
642:      *  
643:      */
644:     public function getModuleInfo($module) {
645: 
646:         $info = array(
647:             'title' => '',
648:             'version' => 0,
649:             'author' => '',
650:             'summary' => '',
651:             'href' => '',
652:             'requires' => array(),
653:             'installs' => array(),
654:             );
655: 
656:         if($module instanceof Module || ctype_digit("$module")) {
657:             $module = $this->getModuleClass($module); 
658:         }
659: 
660:         if(!class_exists($module)) {
661: 
662:             if(isset($this->installable[$module])) {
663:                 $filename = $this->installable[$module]; 
664:                 include_once($filename); 
665:             }
666: 
667:             if(!class_exists($module)) {
668:                 $info['title'] = $module; 
669:                 $info['summary'] = 'Inactive';
670:                 return $info;
671:             }
672:         }
673: 
674:         //$func = $module . "::getModuleInfo"; // requires PHP 5.2.3+
675:         //return call_user_func($func);
676:         $info = array_merge($info, call_user_func(array($module, 'getModuleInfo')));
677: 
678:         // if $info[requires] or $info[installs] isn't already an array, make it one
679:         if(!is_array($info['requires'])) $info['requires'] = array($info['requires']); 
680:         if(!is_array($info['installs'])) $info['installs'] = array($info['installs']); 
681: 
682:         return $info;
683:     }
684: 
685:     /**
686:      * Given a class name, return an array of configuration data specified for the Module
687:      *
688:      * Corresponds to the modules.data table in the database
689:      *
690:      * Applicable only for modules that implement the ConfigurableModule interface
691:      *
692:      * @param string|Module $className
693:      * @return array
694:      *
695:      */
696:     public function getModuleConfigData($className) {
697: 
698:         if(is_object($className)) $className = $className->className();
699:         if(!$id = $this->moduleIDs[$className]) return array();
700:         if(isset($this->configData[$id])) return $this->configData[$id]; 
701: 
702:         // if the class doesn't implement ConfigurableModule, then it's not going to have any configData
703:         if(!in_array('ConfigurableModule', class_implements($className))) return array();
704: 
705:         $result = $this->fuel('db')->query("SELECT data FROM modules WHERE id=$id"); 
706:         list($data) = $result->fetch_array(); 
707:         if(empty($data)) $data = array();
708:             else $data = wireDecodeJSON($data); 
709:         $this->configData[$id] = $data; 
710:         $result->free();
711: 
712:         return $data;   
713:     }
714: 
715:     /**
716:      * Populate configuration data to a ConfigurableModule
717:      *
718:      * If the Module has a 'setConfigData' method, it will send the array of data to that. 
719:      * Otherwise it will populate the properties individually. 
720:      *
721:      * @param Module $module
722:      * @param array $data Configuration data (key = value), or omit if you want it to retrieve the config data for you.
723:      * 
724:      */
725:     protected function setModuleConfigData(Module $module, $data = null) {
726: 
727:         if(!$module instanceof ConfigurableModule) return; 
728:         if(!is_array($data)) $data = $this->getModuleConfigData($module); 
729: 
730:         if(method_exists($module, 'setConfigData') || method_exists($module, '___setConfigData')) {
731:             $module->setConfigData($data); 
732:             return;
733:         }
734: 
735:         foreach($data as $key => $value) {
736:             $module->$key = $value; 
737:         }
738:     }
739: 
740:     /**
741:      * Given a module class name and an array of configuration data, save it for the module
742:      *
743:      * @param string|Module $className
744:      * @param array $configData
745:      * @return bool True on success
746:      *
747:      */
748:     public function ___saveModuleConfigData($className, array $configData) {
749:         if(is_object($className)) $className = $className->className();
750:         if(!$id = $this->moduleIDs[$className]) throw new WireException("Unable to find ID for Module '$className'"); 
751:         $this->configData[$id] = $configData; 
752:         $json = count($configData) ? wireEncodeJSON($configData, true) : '';
753:         return $this->fuel('db')->query("UPDATE modules SET data='" . $this->fuel('db')->escape_string($json) . "' WHERE id=$id"); 
754:     }
755: 
756:     /**
757:      * Is the given module Singular (single instance)?
758:      *
759:      * isSingular and isAutoload Module methods have been deprecated. So this method, and isAutoload() 
760:      * exist in part to enable singular and autoload properties to be set in getModuleInfo, rather than 
761:      * with methods. 
762:      *
763:      * Note that isSingular() and isAutoload() are not deprecated for ModulePlaceholder, so the Modules
764:      * class isn't going to stop looking for them. 
765:      *
766:      * @param Module $module
767:      * @return bool 
768:      *
769:      */
770:     public function isSingular(Module $module) {
771:         $info = $module->getModuleInfo();
772:         if(isset($info['singular'])) return $info['singular'];
773:         if(method_exists($module, 'isSingular')) return $module->isSingular();
774:         // $info = call_user_func(array($module, 'getModuleInfo'));
775:         return false;
776:     }
777: 
778:     /**
779:      * Is the given module Autoload (automatically loaded at runtime)?
780:      *
781:      * @param Module $module
782:      * @return bool 
783:      *
784:      */
785:     public function isAutoload(Module $module) {
786:         $info = $module->getModuleInfo();
787:         if(isset($info['autoload'])) return $info['autoload'];
788:         if(method_exists($module, 'isAutoload')) return $module->isAutoload();
789:         return false; 
790:     }
791: 
792:     /**
793:      * Returns whether the modules have been initialized yet
794:      *
795:      * @return bool
796:      *
797:      */
798:     public function isInitialized() {
799:         return $this->initialized; 
800:     }
801: 
802:     /**
803:      * Reset the cache that stores module files by recreating it
804:      *
805:      */
806:     public function resetCache() {
807:         $this->findModuleFiles($this->modulePath); 
808:         if($this->modulePath2) $this->findModuleFiles($this->modulePath2); 
809:     }
810: 
811:     /**
812:      * Return an array of module class names that require the given one
813:      * 
814:      * @param string $class
815:      * @param bool $uninstalled Set to true to include modules dependent upon this one, even if they aren't installed.
816:      * @param bool $installs Set to true to exclude modules that indicate their install/uninstall is controlled by $class.
817:      * @return array()
818:      *
819:      */
820:     public function getRequiredBy($class, $uninstalled = false, $installs = false) {
821: 
822:         $class = $this->getModuleClass($class); 
823:         $info = $this->getModuleInfo($class); 
824:         $dependents = array();
825: 
826:         foreach($this as $module) {
827:             $c = $this->getModuleClass($module);    
828:             if(!$uninstalled && !$this->isInstalled($c)) continue; 
829:             $i = $this->getModuleInfo($c); 
830:             if(!count($i['requires'])) continue; 
831:             if($installs && in_array($c, $info['installs'])) continue; 
832:             if(in_array($class, $i['requires'])) $dependents[] = $c; 
833:         }
834: 
835:         return $dependents; 
836:     }
837: 
838:     /**
839:      * Return an array of module class names required by the given one
840:      * 
841:      * @param string $class
842:      * @param bool $uninstalled Set to true to return only required modules that aren't yet installed or those that $class says it will install (via 'installs' property of getModuleInfo)
843:      * @return array()
844:      *
845:      */
846:     public function getRequires($class, $uninstalled = false) {
847: 
848:         $class = $this->getModuleClass($class); 
849:         $info = $this->getModuleInfo($class); 
850:         $requires = $info['requires']; 
851: 
852:         // quick exit if arguments permit it 
853:         if(!$uninstalled) return $requires; 
854: 
855:         foreach($requires as $key => $module) {
856:             $c = $this->getModuleClass($module); 
857:             if($this->isInstalled($c) || in_array($c, $info['installs'])) {
858:                 unset($requires[$key]);         
859:             }
860:         }
861: 
862:         return $requires; 
863:     }
864: 
865:     /**
866:      * Return an array of module class names required by the given one to be installed before this one.
867:      *
868:      * Excludes modules that are required but already installed. 
869:      * Excludes uninstalled modules that $class indicates it handles via it's 'installs' getModuleInfo property.
870:      * 
871:      * @param string $class
872:      * @return array()
873:      *
874:      */
875:     public function getRequiresForInstall($class) {
876:         return $this->getRequires($class, true); 
877:     }
878: 
879:     /**
880:      * Return an array of module class names required by the given one to be uninstalled before this one.
881:      *
882:      * Excludes modules that the given one says it handles via it's 'installs' getModuleInfo property.
883:      * 
884:      * @param string $class
885:      * @return array()
886:      *
887:      */
888:     public function getRequiresForUninstall($class) {
889:         return $this->getRequiredBy($class, false, true); 
890:     }
891: 
892: }
893: 
894: 
ProcessWire API documentation generated by ApiGen 2.6.0