order to indicate autoload order when necessary * * @var array * */ protected $autoloadOrders = array(); /** * Array of moduleName => condition * * Condition can be either an anonymous function or a selector string to be evaluated at ready(). * */ protected $conditionalAutoloadModules = array(); /** * Cache of module information from DB used across multiple calls temporarily by loadPath() method * */ protected $modulesTableCache = array(); /** * Module created dates indexed by module ID * */ protected $createdDates = array(); /** * Initialize all the modules that are loaded at boot * * #pw-internal * * @param null|array|Modules $modules * @param array $completed * @param int $level * */ public function triggerInit($modules = null, $completed = array(), $level = 0) { $debugKey = null; $debugKey2 = null; if($this->debug) { $debugKey = $this->modules->debugTimerStart("triggerInit$level"); $this->message("triggerInit(level=$level)"); } $queue = array(); if($modules === null) $modules = $this->modules; foreach($modules as $class => $module) { if($module instanceof ModulePlaceholder) { // skip modules that aren't autoload and those that are conditional autoload if(!$module->autoload) continue; if(isset($this->conditionalAutoloadModules[$class])) continue; } if($this->debug) $debugKey2 = $this->modules->debugTimerStart("triggerInit$level($class)"); $info = $this->modules->getModuleInfo($module); $skip = false; // module requires other modules foreach($info['requires'] as $requiresClass) { if(in_array($requiresClass, $completed)) continue; $dependencyInfo = $this->modules->getModuleInfo($requiresClass); if(empty($dependencyInfo['autoload'])) { // if dependency isn't an autoload one, there's no point in waiting for it if($this->debug) $this->warning("Autoload module '$module' requires a non-autoload module '$requiresClass'"); continue; } else if(isset($this->conditionalAutoloadModules[$requiresClass])) { // autoload module requires another autoload module that may or may not load if($this->debug) $this->warning("Autoload module '$module' requires a conditionally autoloaded module '$requiresClass'"); continue; } // dependency is autoload and required by this module, so queue this module to init later $queue[$class] = $module; $skip = true; break; } if(!$skip) { if($info['autoload'] !== false) { if($info['autoload'] === true || $this->modules->isAutoload($module)) { $this->initModule($module); } } $completed[] = $class; } if($this->debug) $this->modules->debugTimerStop($debugKey2); } // if there is a dependency queue, go recursive till the queue is completed if(count($queue) && $level < 3) { $this->triggerInit($queue, $completed, $level + 1); } $this->modules->isInitialized(true); if($this->debug) if($debugKey) $this->modules->debugTimerStop($debugKey); if(!$level && $this->modules->info->moduleInfoCacheEmpty()) { if($this->debug) $this->message("saveModuleInfoCache from triggerInit"); $this->modules->info->saveModuleInfoCache(); } } /** * Initialize a single module * * @param Module $module * @param array $options * - `clearSettings` (bool): When true, module settings will be cleared when appropriate to save space. (default=true) * - `configOnly` (bool): When true, module init() method NOT called, but config data still set (default=false) 3.0.169+ * - `configData` (array): Extra config data merge with module’s config data (default=[]) 3.0.169+ * - `throw` (bool): When true, exceptions will be allowed to pass through. (default=false) * @return bool True on success, false on fail * @throws \Exception Only if the `throw` option is true. * */ public function initModule(Module $module, array $options = array()) { $result = true; $debugKey = null; $clearSettings = isset($options['clearSettings']) ? (bool) $options['clearSettings'] : true; $throw = isset($options['throw']) ? (bool) $options['throw'] : false; if($this->debug) { static $n = 0; $this->message("initModule (" . (++$n) . "): " . wireClassName($module)); } // if the module is configurable, then load its config data // and set values for each before initializing the module $extraConfigData = isset($options['configData']) ? $options['configData'] : null; $this->modules->configs->setModuleConfigData($module, null, $extraConfigData); $moduleName = wireClassName($module, false); $moduleID = $this->modules->moduleID($moduleName); if($moduleID && $this->modules->info->modulesLastVersions($moduleID)) { $this->modules->info->checkModuleVersion($module); } if(method_exists($module, 'init') && empty($options['configOnly'])) { if($this->debug) { $debugKey = $this->modules->debugTimerStart("initModule($moduleName)"); } try { $module->init(); } catch(\Exception $e) { if($throw) throw($e); $this->error(sprintf($this->_('Failed to init module: %s'), $moduleName) . " - " . $e->getMessage()); $result = false; } if($this->debug) { $this->modules->debugTimerStop($debugKey); } } // if module is autoload (assumed here) and singular, then // we no longer need the module's config data, so remove it if($clearSettings && $this->modules->isSingular($module)) { if(!$moduleID) $moduleID = $this->modules->getModuleID($module); if($moduleID && $this->modules->configs->configData($moduleID) !== null) { $this->modules->configs->configData($moduleID, 1); } } return $result; } /** * Call ready for a single module * * @param Module $module * @return bool * */ public function readyModule(Module $module) { $result = true; if(method_exists($module, 'ready')) { $debugKey = $this->debug ? $this->modules->debugTimerStart("readyModule(" . $module->className() . ")") : null; try { $module->ready(); } catch(\Exception $e) { $this->error(sprintf($this->_('Failed to ready module: %s'), $module->className()) . " - " . $e->getMessage()); $result = false; } if($this->debug) { $this->modules->debugTimerStop($debugKey); static $n = 0; $this->message("readyModule (" . (++$n) . "): " . wireClassName($module)); } } return $result; } /** * Trigger all modules 'ready' method, if they have it. * * This is to indicate to them that the API environment is fully ready and $page is in fuel. * * This is triggered by ProcessPageView::ready * * #pw-internal * */ public function triggerReady() { $debugKey = $this->debug ? $this->modules->debugTimerStart("triggerReady") : null; $skipped = $this->triggerConditionalAutoload(); // trigger ready method on all applicable modules foreach($this->modules as $module) { /** @var Module $module */ if($module instanceof ModulePlaceholder) continue; $class = $this->modules->getModuleClass($module); if(isset($skipped[$class])) continue; $id = $this->modules->moduleID($class); $flags = $this->modules->flags->moduleFlags($id); if($flags & Modules::flagsAutoload) $this->readyModule($module); } if($this->debug) $this->modules->debugTimerStop($debugKey); } /** * Init conditional autoload modules, if conditions allow * * @return array of skipped module names * */ public function triggerConditionalAutoload() { // conditional autoload modules that are skipped (className => 1) $skipped = array(); // init conditional autoload modules, now that $page is known foreach($this->conditionalAutoloadModules as $className => $func) { if($this->debug) { $moduleID = $this->modules->getModuleID($className); $flags = $this->modules->flags->moduleFlags($moduleID); $this->message("Conditional autoload: $className (flags=$flags, condition=" . (is_string($func) ? $func : 'func') . ")"); } $load = true; if(is_string($func)) { // selector string if(!$this->wire()->page->is($func)) $load = false; } else { // anonymous function if(!is_callable($func)) $load = false; else if(!$func()) $load = false; } if($load) { $module = $this->modules->newModule($className); if($module) { $this->modules->set($className, $module); if($this->initModule($module)) { if($this->debug) $this->message("Conditional autoload: $className LOADED"); } else { if($this->debug) $this->warning("Failed conditional autoload: $className"); } } } else { $skipped[$className] = $className; if($this->debug) $this->message("Conditional autoload: $className SKIPPED"); } } // clear this out since we don't need it anymore $this->conditionalAutoloadModules = array(); return $skipped; } /** * Retrieve the installed module info as stored in the database * */ public function loadModulesTable() { $this->autoloadOrders = array(); $database = $this->wire()->database; // skip loading dymanic caches at this stage $skipCaches = array( ModulesInfo::moduleInfoCacheUninstalledName, ModulesInfo::moduleInfoCacheVerboseName ); $query = $database->query( // Currently: id, class, flags, data, with created added at sysupdate 7 "SELECT * FROM modules " . "WHERE class NOT IN('" . implode("','", $skipCaches) . "') " . "ORDER BY class", "modules.loadModulesTable()" ); /** @noinspection PhpAssignmentInConditionInspection */ while($row = $query->fetch(\PDO::FETCH_ASSOC)) { $moduleID = (int) $row['id']; $flags = (int) $row['flags']; $class = $row['class']; if($flags & Modules::flagsSystemCache) { // system cache names are prefixed with a '.' so they load first $this->modules->memcache(ltrim($class, '.'), $row['data']); continue; } $this->modules->moduleID($class, $moduleID); $this->modules->moduleName($moduleID, $class); $this->modules->flags->moduleFlags($moduleID, $flags); $autoload = $flags & Modules::flagsAutoload; $loadSettings = $autoload || ($flags & Modules::flagsDuplicate) || ($class === 'SystemUpdater'); if($loadSettings) { // preload config data for autoload modules since we'll need it again very soon $data = $row['data'] ? json_decode($row['data'], true) : array(); $this->modules->configs->configData($moduleID, $data); // populate information about duplicates, if applicable if($flags & Modules::flagsDuplicate) $this->modules->duplicates()->addFromConfigData($class, $data); } else if(!empty($row['data'])) { // indicate that it has config data, but not yet loaded $this->modules->configs->configData($moduleID, 1); } if(isset($row['created']) && $row['created'] != '0000-00-00 00:00:00') { $this->createdDates[$moduleID] = $row['created']; } if($autoload) { $value = $this->modules->info->moduleInfoCache($moduleID, 'autoload'); if(!empty($value)) { $autoload = $value; $disabled = $flags & Modules::flagsDisabled; if(is_int($autoload) && $autoload > 1 && !$disabled) { // autoload specifies an order > 1, indicating it should load before others $this->autoloadOrders[$class] = $autoload; } } } unset($row['data'], $row['created']); // info we don't want stored in modulesTableCache $this->modulesTableCache[$class] = $row; } $query->closeCursor(); } /** * Given a disk path to the modules, determine all installed modules and keep track of all uninstalled (installable) modules. * * @param string $path * */ public function loadPath($path) { $config = $this->wire()->config; $debugKey = $this->debug ? $this->modules->debugTimerStart("loadPath($path)") : null; $installed =& $this->modulesTableCache; $modulesLoaded = array(); $modulesDelayed = array(); $modulesRequired = array(); $modulesFiles = $this->modules->files; $rootPath = $config->paths->root; $basePath = substr($path, strlen($rootPath)); foreach($modulesFiles->findModuleFiles($path, true) as $pathname) { $pathname = trim($pathname); if(empty($pathname)) continue; $basename = basename($pathname); list($moduleName, $ext) = explode('.', $basename, 2); // i.e. "module.php" or "module" $modulesFiles->moduleFileExt($moduleName, $ext === 'module' ? 1 : 2); // @todo next, remove the 'file' property from verbose module info since it is redundant $requires = array(); $name = $moduleName; $moduleName = $this->loadModule($path, $pathname, $requires, $installed); if(!$config->paths->get($name)) $modulesFiles->setConfigPaths($name, dirname($basePath . $pathname)); if(!$moduleName) continue; if(count($requires)) { // module not loaded because it required other module(s) not yet loaded foreach($requires as $requiresModuleName) { if(!isset($modulesRequired[$requiresModuleName])) $modulesRequired[$requiresModuleName] = array(); if(!isset($modulesDelayed[$moduleName])) $modulesDelayed[$moduleName] = array(); // queue module for later load $modulesRequired[$requiresModuleName][$moduleName] = $pathname; $modulesDelayed[$moduleName][] = $requiresModuleName; } continue; } // module was successfully loaded $modulesLoaded[$moduleName] = 1; $loadedNames = array($moduleName); // now determine if this module had any other modules waiting on it as a dependency /** @noinspection PhpAssignmentInConditionInspection */ while($moduleName = array_shift($loadedNames)) { // iternate through delayed modules that require this one if(empty($modulesRequired[$moduleName])) continue; foreach($modulesRequired[$moduleName] as $delayedName => $delayedPathName) { $loadNow = true; if(isset($modulesDelayed[$delayedName])) { foreach($modulesDelayed[$delayedName] as $requiresModuleName) { if(!isset($modulesLoaded[$requiresModuleName])) { $loadNow = false; } } } if(!$loadNow) continue; // all conditions satisified to load delayed module unset($modulesDelayed[$delayedName], $modulesRequired[$moduleName][$delayedName]); $unused = array(); $loadedName = $this->loadModule($path, $delayedPathName, $unused, $installed); if(!$loadedName) continue; $modulesLoaded[$loadedName] = 1; $loadedNames[] = $loadedName; } } } if(count($modulesDelayed)) { foreach($modulesDelayed as $moduleName => $requiredNames) { $this->error("Module '$moduleName' dependency not fulfilled for: " . implode(', ', $requiredNames), Notice::debug); } } if($this->debug) $this->modules->debugTimerStop($debugKey); } /** * Load a module into memory (companion to load bootstrap method) * * @param string $basepath Base path of modules being processed (path provided to the load method) * @param string $pathname * @param array $requires This method will populate this array with required dependencies (class names) if present. * @param array $installed Array of installed modules info, indexed by module class name * @return string Returns module name (classname) * */ public function loadModule($basepath, $pathname, array &$requires, array &$installed) { $pathname = $basepath . $pathname; $dirname = dirname($pathname); $filename = basename($pathname); $basename = basename($filename, '.php'); $basename = basename($basename, '.module'); $requires = array(); $duplicates = $this->modules->duplicates(); // check if module has duplicate files, where one to use has already been specified to use first $currentFile = $duplicates->getCurrent($basename); // returns the current file in use, if more than one if($currentFile) { // there is a duplicate file in use $file = rtrim($this->wire()->config->paths->root, '/') . $currentFile; if(file_exists($file) && $pathname != $file) { // file in use is different from the file we are looking at // check if this is a new/yet unknown duplicate if(!$duplicates->hasDuplicate($basename, $pathname)) { // new duplicate $duplicates->recordDuplicate($basename, $pathname, $file, $installed); } return ''; } } // check if module has already been loaded, or maybe we've got duplicates if(wireClassExists($basename, false)) { $module = $this->modules->offsetGet($basename); $dir = rtrim((string) $this->wire()->config->paths($basename), '/'); if($module && $dir && $dirname != $dir) { $duplicates->recordDuplicate($basename, $pathname, "$dir/$filename", $installed); return ''; } if($module) return $basename; } // if the filename doesn't end with .module or .module.php, then stop and move onto the next if(strpos($filename, '.module') === false) return false; list(, $ext) = explode('.module', $filename, 2); if(!empty($ext) && $ext !== '.php') return false; // if the filename doesn't start with the requested path, then skip if(strpos($pathname, $basepath) !== 0) return ''; // if the file isn't there, it was probably uninstalled, so ignore it // if(!file_exists($pathname)) return ''; // if the module isn't installed, then stop and move on to next if(!isset($installed[$basename])) { // array_key_exists is used as secondary to check the null case $this->modules->installableFile($basename, $pathname); return ''; } $info = $installed[$basename]; $this->modules->files->setConfigPaths($basename, $dirname); $module = null; $autoload = false; if($info['flags'] & Modules::flagsAutoload) { // this is an Autoload module. // include the module and instantiate it but don't init() it, // because it will be done by Modules::init() // determine if module has dependencies that are not yet met $requiresClasses = $this->modules->info->getModuleInfoProperty($basename, 'requires'); if(!empty($requiresClasses)) { foreach($requiresClasses as $requiresClass) { $nsRequiresClass = $this->modules->getModuleClass($requiresClass, true); if(!wireClassExists($nsRequiresClass, false)) { $requiresInfo = $this->modules->getModuleInfo($requiresClass); if(!empty($requiresInfo['error']) || $requiresInfo['autoload'] === true || !$this->modules->isInstalled($requiresClass)) { // we only handle autoload===true since load() only instantiates other autoload===true modules $requires[] = $requiresClass; } } } if(count($requires)) { // module has unmet requirements return $basename; } } // if not defined in getModuleInfo, then we'll accept the database flag as enough proof // since the module may have defined it via an isAutoload() function /** @var bool|string|callable $autoload */ $autoload = $this->modules->info->moduleInfoCache($basename, 'autoload'); if(empty($autoload)) $autoload = true; if($autoload === 'function') { // function is stored by the moduleInfo cache to indicate we need to call a dynamic function specified with the module itself $i = $this->modules->info->getModuleInfoExternal($basename); if(empty($i)) { $this->modules->files->includeModuleFile($pathname, $basename); $namespace = $this->modules->info->getModuleNamespace($basename); $className = $namespace . $basename; if(method_exists($className, 'getModuleInfo')) { $i = $className::getModuleInfo(); } else { $i = $this->modules->getModuleInfo($className); } } $autoload = isset($i['autoload']) ? $i['autoload'] : true; unset($i); } // check for conditional autoload if(!is_bool($autoload) && (is_string($autoload) || is_callable($autoload)) && !($info['flags'] & Modules::flagsDisabled)) { // anonymous function or selector string $this->conditionalAutoloadModules[$basename] = $autoload; $this->modules->moduleID($basename, (int) $info['id']); $this->modules->moduleName((int) $info['id'], $basename); $autoload = true; } else if($autoload) { $this->modules->files->includeModuleFile($pathname, $basename); if(!($info['flags'] & Modules::flagsDisabled)) { if($this->modules->refreshing) { $module = $this->modules->offsetGet($basename); } else if(isset($this->autoloadOrders[$basename]) && $this->autoloadOrders[$basename] >= 10000) { $module = $this->modules->offsetGet($basename); // preloaded module } if(!$module) $module = $this->modules->newModule($basename); } } } if($module === null) { // placeholder for a module, which is not yet included and instantiated $ns = $this->modules->info->moduleInfoCache($basename, 'namespace'); if(empty($ns)) $ns = __NAMESPACE__ . "\\"; $singular = $info['flags'] & Modules::flagsSingular; $module = $this->newModulePlaceholder($basename, $ns, $pathname, $singular, $autoload); } $this->modules->moduleID($basename, (int) $info['id']); $this->modules->moduleName((int) $info['id'], $basename); $this->modules->set($basename, $module); return $basename; } /** * Include the file for a given module, but don't instantiate it * * #pw-internal * * @param ModulePlaceholder|Module|string Expects a ModulePlaceholder or className * @param string $file Optionally specify the module filename if you already know it * @return bool true on success, false on fail or unknown * */ public function includeModule($module, $file = '') { $className = ''; $moduleName = ''; if(is_string($module)) { $moduleName = ctype_alnum($module) ? $module : wireClassName($module); $className = wireClassName($module, true); } else if(is_object($module)) { if($module instanceof ModulePlaceholder) { $moduleName = $module->className(); $className = $module->className(true); } else if($module instanceof Module) { return true; // already included } } else { $moduleName = $this->modules->getModuleClass($module, false); $className = $this->modules->getModuleClass($module, true); } if(!$className) return false; // already included if(class_exists($className, false)) return true; // attempt to retrieve module $module = $this->modules->offsetGet($moduleName); if($module) { // module found, check to make sure it actually points to a module if(!$module instanceof Module) $module = false; } else if($moduleName) { // This is reached for any of the following: // 1. an uninstalled module // 2. an installed module that has changed locations // 3. a module outside the \ProcessWire\ namespace // 4. a module that does not exist $fast = true; if(!$file) { // determine module file, if not already provided to the method $file = $this->modules->getModuleFile($moduleName, array('fast' => true)); if(!$file) { $fast = false; $file = $this->modules->getModuleFile($moduleName, array('fast' => false)); } // still can't figure out what file is? fail if(!$file) return false; } if(!$this->modules->files->includeModuleFile($file, $moduleName)) { // module file failed to include(), try to identify and include file again if($fast) { $filePrev = $file; $file = $this->modules->getModuleFile($moduleName, array('fast' => false)); if($file && $file !== $filePrev) { if($this->modules->files->includeModuleFile($file, $moduleName)) { // module is missing a module file return false; } } } else { // we already tried this earlier, no point in doing it again } } // now check to see if included file resulted in presence of module class if(class_exists($className)) { // module in ProcessWire namespace $module = true; } else { // module in root namespace or some other namespace $namespace = (string) $this->modules->info->getModuleNamespace($moduleName, array('file' => $file)); $className = trim($namespace, "\\") . "\\$moduleName"; if(class_exists($className, false)) { // successful include module $module = true; } } } if($module === true) { // great return true; } else if(!$module) { // darn return false; } else if($module instanceof ModulePlaceholder) { // the ModulePlaceholder indicates what file to load return $this->modules->files->includeModuleFile($module->file, $moduleName); } else if($module instanceof Module) { // it's already been included, since we have a real module return true; } else { return false; } } /** * Check if user has permission for given module * * #pw-internal * * @param string|object $moduleName Module instance or module name * @param User $user Optionally specify different user to consider than current. * @param Page $page Optionally specify different page to consider than current. * @param bool $strict If module specifies no permission settings, assume no permission. * - Default (false) is to assume permission when module doesn't say anything about it. * - Process modules (for instance) generally assume no permission when it isn't specifically defined * (though this method doesn't get involved in that, leaving you to specify $strict instead). * * @return bool * */ public function hasPermission($moduleName, User $user = null, Page $page = null, $strict = false) { if(is_object($moduleName)) { $module = $moduleName; $className = $module->className(true); $moduleName = $module->className(false); } else { $module = null; $className = $this->modules->getModuleClass($moduleName, true); // ??? $moduleName = wireClassName($moduleName, false); } $info = $this->modules->getModuleInfo($module ? $module : $moduleName); if(empty($info['permission']) && empty($info['permissionMethod'])) { return ($strict ? false : true); } if(!$user instanceof User) $user = $this->wire()->user; if($user && $user->isSuperuser()) return true; if(!empty($info['permission'])) { if(!$user->hasPermission($info['permission'])) return false; } if(!empty($info['permissionMethod'])) { // module specifies a static method to call for permission if(is_null($page)) $page = $this->wire()->page; $data = array( 'wire' => $this->wire(), 'page' => $page, 'user' => $user, 'info' => $info, ); $method = $info['permissionMethod']; $this->includeModule($moduleName); return method_exists($className, $method) ? $className::$method($data) : false; } return true; } /** * Include site preload modules * * Preload modules load before all other modules, including core modules. In order * for a module to be a preload module, it must meet the following conditions: * * - Module info `autoload` value is integer of 10000 or greater, i.e. `[ 'autoload' => 10000 ]` * - Module info `singular` value must be non-empty, i.e. `[ 'singular' => true ]` * - Module file is located in: /site/modules/ModuleName/ModuleName.module.php * - Module cannot load any other modules at least until ready() method called. * - Module cannot have any `requires` dependencies to any other modules. * * Please note the above is specifically stating that the module must be in its * own “site/ModuleName/” directory and have the “.module.php” extension. Using * just the “.module” extension is not supported for preload modules. * * @param string $path * @since 3.0.173 * */ public function preloadModules($path) { if(empty($this->autoloadOrders)) return; arsort($this->autoloadOrders); foreach($this->autoloadOrders as $moduleName => $order) { if($order < 10000) break; $info = $this->modules->info->moduleInfoCache($moduleName); if(empty($info)) continue; if(empty($info['singular'])) continue; $file = $path . "$moduleName/$moduleName.module.php"; if(!file_exists($file) || !$this->modules->files->includeModuleFile($file, $moduleName)) continue; if(!isset($info['namespace'])) $info['namespace'] = ''; $className = $info['namespace'] . $moduleName; $module = $this->modules->newModule($className, $moduleName); if($module) { $this->modules->offsetSet($moduleName, $module); } } } /** * Get or set created date for given module ID * * #pw-internal * * @param int $moduleID Module ID or omit to get all * @param string $setValue Set created date value * @return string|array|null * @since 3.0.219 * */ public function createdDate($moduleID = null, $setValue = null) { if($moduleID === null) return $this->createdDates; if($setValue) { $this->createdDates[$moduleID] = $setValue; return $setValue; } return isset($this->createdDates[$moduleID]) ? $this->createdDates[$moduleID] : null; } /** * Return a new ModulePlaceholder for the given className * * #pw-internal * * @param string $className Module class this placeholder will stand in for * @param string $ns Module namespace * @param string $file Full path and filename of $className * @param bool $singular Is the module a singular module? * @param bool $autoload Is the module an autoload module? * @return ModulePlaceholder * */ public function newModulePlaceholder($className, $ns, $file, $singular, $autoload) { /** @var ModulePlaceholder $module */ $module = $this->wire(new ModulePlaceholder()); $module->setClass($className); $module->setNamespace($ns); $module->singular = $singular; $module->autoload = $autoload; $module->file = $file; return $module; } /** * Called by Modules class when init has finished * */ public function loaded() { $this->modulesTableCache = array(); } /** * Get the autoload orders * * @return array Array of [ moduleName (string => order (int) ] * */ public function getAutoloadOrders() { return $this->autoloadOrders; } public function getDebugData() { return array( 'autoloadOrders' => $this->autoloadOrders, 'conditionalAutoloadModules' => $this->conditionalAutoloadModules, 'modulesTableCache' => $this->modulesTableCache, 'createdDates' => $this->createdDates, ); } }