/** * Load all the components in the system, replacement for the Core. * @throws CoreException */ private function _loadComponents() { // cannot reload components. if ($this->_components) return null; $this->_components = array(); $this->_libraries = array(); $tempcomponents = false; Core\Utilities\Logger\write_debug('Starting loading of component metadata'); // If the site is in DEVELOPMENT mode, component caching would probably be a bad idea; ie: the developer probably wants // those component files loaded everytime. if(DEVELOPMENT_MODE){ $enablecache = false; } else{ $enablecache = true; } // Is there a cache of elements available? This is a primary system cache that greatly increases performance, // since it will no longer have to run through each component.xml file to register each one. if($enablecache){ Core\Utilities\Logger\write_debug('Checking core-components cache'); // Try to load up the cached components and check them first. $tempcomponents = \Core\Cache::Get('core-components', (3600 * 24)); if($tempcomponents !== false){ // Cached components only need to be loaded. foreach ($tempcomponents as $c) { try { $c->load(); } catch (Exception $e) { // Don't completely bail out here, just invalidate the cache and continue on. \Core\Cache::Delete('core-components'); $tempcomponents = false; } } } } if(!$enablecache || $tempcomponents == false){ \Core\Utilities\Profiler\Profiler::GetDefaultProfiler()->record('Scanning for component.xml files manually'); Core\Utilities\Logger\write_debug('Scanning for component.xml files manually'); // Core is first, (obviously) $tempcomponents['core'] = ComponentFactory::Load(ROOT_PDIR . 'core/component.xml'); Core\Utilities\Logger\write_debug('Core component loaded'); // First, build my cache of components, regardless if the component is installed or not. $dh = opendir(ROOT_PDIR . 'components'); if (!$dh) throw new CoreException('Unable to open directory [' . ROOT_PDIR . 'components/] for reading.'); // This will read through every directory in 'components', which is // where all components in the system are installed to. while (($file = readdir($dh)) !== false) { // skip hidden directories. if ($file{0} == '.') continue; // skip non-directories if (!is_dir(ROOT_PDIR . 'components/' . $file)) continue; // Skip directories that do not have a readable component.xml file. if (!is_readable(ROOT_PDIR . 'components/' . $file . '/component.xml')) continue; //Core\Utilities\Logger\write_debug(' * Loading component ' . $file); $c = ComponentFactory::Load(ROOT_PDIR . 'components/' . $file . '/component.xml'); Core\Utilities\Logger\write_debug('Opened component ' . $file); // All further operations are case insensitive. // The original call to Component needs to be case sensitive because it sets the filename to pull. $file = strtolower($file); // If the component was flagged as invalid.. just skip to the next one. if (!$c->isValid()) { if (DEVELOPMENT_MODE) { \Core\set_message('Component ' . $c->getName() . ' appears to be invalid.'); } continue; } $tempcomponents[$file] = $c; unset($c); } closedir($dh); \Core\Utilities\Profiler\Profiler::GetDefaultProfiler()->record('Component XML files scanned'); // Now I probably could actually load the components! foreach ($tempcomponents as $c) { /** @var Component_2_1 $c */ try { // Load some of the data in the class so that it's available in the cached version. // This is because the component 2.1 has built-in caching for many of the XML requests. // by calling them once, that lookup data is cached in that component, which in turn gets // copied to the cache version here! $c->load(); $c->getClassList(); $c->getViewSearchDir(); $c->getSmartyPluginDirectory(); $c->getWidgetList(); } catch (Exception $e) { var_dump($e); die(); } } // Cache this list! if($enablecache){ Core\Utilities\Logger\write_debug(' * Caching core-components for next pass'); \Core\Cache::Set('core-components', $tempcomponents, (3600 * 24)); } } $list = $tempcomponents; \Core\Utilities\Profiler\Profiler::GetDefaultProfiler()->record('Component metadata loaded, starting registration'); Core\Utilities\Logger\write_debug(' * Component metadata loaded, starting registration'); // The core component at a minimum needs to be loaded and registered. // $this->_registerComponent($list['core']); // $this->_components['core']->loadFiles(); // unset($list['core']); // Now that I have a list of components available, copy them into a list of // components that are installed. do { $size = sizeof($list); foreach ($list as $n => $c) { /** @var $c Component_2_1 */ // Disabled components don't get recognized. if($c->isInstalled() && !$c->isEnabled()){ // But they do get sent to the disabled list! $this->_componentsDisabled[$n] = $c; unset($list[$n]); continue; } // Clear out the temporary class list $this->_tmpclasses = []; // If it's loaded, register it and remove it from the list! if ($c->isInstalled() && $c->isLoadable() && $c->loadFiles()) { try{ // Allow for on-the-fly package upgrading regardless of DEV mode or not. if ($c->needsUpdated()) { // Load this component's classes in case an upgrade operation requires one. // This allows a component to be loaded partially without completely being loaded. $this->_tmpclasses = $c->getClassList(); // Lock the site first! // This is because some upgrade procedures take a long time to upgrade. file_put_contents(TMP_DIR . 'lock.message', 'Core Plus is being upgraded, please try again in a minute. '); $c->upgrade(); unlink(TMP_DIR . 'lock.message'); } } catch(Exception $e){ SystemLogModel::LogErrorEvent('/core/component/failedupgrade', 'Ignoring component [' . $n . '] due to an error during upgrading!', $e->getMessage()); unlink(TMP_DIR . 'lock.message'); //$c->disable(); $this->_componentsDisabled[$n] = $c; unset($list[$n]); continue; } try{ $this->_components[$n] = $c; $this->_registerComponent($c); $c->loadSupplementalModels(); } catch(Exception $e){ SystemLogModel::LogErrorEvent('/core/component/failedregister', 'Ignoring component [' . $n . '] due to an error during registration!', $e->getMessage()); //$c->disable(); $this->_componentsDisabled[$n] = $c; unset($list[$n]); continue; } unset($list[$n]); continue; } // Allow for on-the-fly package upgrading regardless of DEV mode or not. // Guess this is needed for the loadFiles part... if ($c->isInstalled() && $c->needsUpdated() && $c->isLoadable()) { // Lock the site first! // This is because some upgrade procedures take a long time to upgrade. file_put_contents(TMP_DIR . 'lock.message', 'Core Plus is being upgraded, please try again in a minute. '); $c->upgrade(); $c->loadFiles(); $this->_components[$n] = $c; $this->_registerComponent($c); unlink(TMP_DIR . 'lock.message'); unset($list[$n]); continue; } // Allow packages to be auto-installed if in DEV mode. // If DEV mode is not enabled, just install the new component, do not enable it. if (!$c->isInstalled() && $c->isLoadable()) { // Load this component's classes in case an install operation requires one. // This allows a component to be loaded partially without completely being loaded. $this->_tmpclasses = $c->getClassList(); // w00t $c->install(); // BLAH, until I fix the disabled-packages-not-viewable bug... $c->enable(); $c->loadFiles(); $this->_components[$n] = $c; $this->_registerComponent($c); /* if(!DEVELOPMENT_MODE){ $c->disable(); } else{ $c->enable(); $c->loadFiles(); $this->_components[$n] = $c; $this->_registerComponent($c); } */ unset($list[$n]); continue; } } } while ($size > 0 && ($size != sizeof($list))); // If dev mode is enabled, display a list of components installed but not loadable. foreach ($list as $n => $c) { //$this->_components[$n] = $c; $this->_componentsDisabled[$n] = $c; // Ignore anything with the execmode different, those should be minor notices for debugging if anything. if ($c->error & Component_2_1::ERROR_WRONGEXECMODE) continue; if (DEVELOPMENT_MODE) { SystemLogModel::LogErrorEvent('/core/component/missingrequirement', 'Could not load installed component ' . $n . ' due to requirement failed.', $c->getErrors()); } } // Don't forget to load the themes too! if(class_exists('ThemeHandler')){ foreach(ThemeHandler::GetAllThemes() as $theme){ /** @var $theme Theme */ $theme->load(); } } // Lastly, make sure that the template path cache is updated! if(class_exists('\\Core\\Templates\\Template')){ \Core\Templates\Template::RequeryPaths(); } }
/** * Copy in all the assets for this theme into the assets location. * * Returns false if nothing changed, else will return an array of all the changes that occured. * * @param bool $install Set to false to uninstall the assets instead of installing. * @param int $verbosity 0 for standard output, 1 for real-time, 2 for real-time verbose output. * * @return false | array * @throws \InstallerException */ public function _parseAssets($install = true, $verbosity = 0) { $assetbase = \Core\Filestore\get_asset_path(); $coretheme = \ConfigHandler::Get('/theme/selected'); // WHY is core theme set to blank?!? // Damn installer... // this happens in the installer. if ($coretheme === null) { $coretheme = 'default'; } $theme = $this->getKeyName(); $changes = []; foreach ($this->_xmlloader->getElements('/assets/file') as $node) { // Cannot install assets if the directory is not setup! if (!$this->getAssetDir()) { continue; } $b = $this->getBaseDir(); // The base filename with the directory. $filename = $node->getAttribute('filename'); // The new theme asset will be installed into the same directory as its theme. // This differs from usual components because they just follow whatever theme is currently running. //$nf = Core::File($assetbase . $theme . '/' . $filename); $trimmedfilename = substr($b . $node->getAttribute('filename'), strlen($this->getAssetDir())); $themespecificfilename = $assetbase . $theme . '/' . $trimmedfilename; $newfilename = 'assets/' . $trimmedfilename; // Before anything, check and see if this file has a custom override file present. if (file_exists(ROOT_PDIR . 'themes/custom/' . $newfilename)) { // If so, then copy that asset to the custom directory too! $f = \Core\Filestore\Factory::File(ROOT_PDIR . 'themes/custom/' . $newfilename); $srcname = '!CUSTOM!'; } else { // Otherwise, the local file is guaranteed to be a local file. $f = new \Core\Filestore\Backends\FileLocal($b . $filename); $srcname = '-theme- '; } if ($verbosity == 2) { CLI::PrintActionStart('Installing ' . $srcname . ' asset ' . $f->getBasename()); } $nf = \Core\Filestore\Factory::File($newfilename); /* // The various replacement possibilities for this file. // The new destination must be in the theme-specific directory, this is a // bit of a hack from the usual behaviour of the filestore system. // Since that's designed to return the default if the theme-specific doesn't exist. $replacements = array( // The theme is not default, but the system translated the path to the default directory. // This is because the file doesn't exist in any theme. // This is actually expected behaviour, except unwanted here. 'default/' . $trimmedfilename => $theme . '/' . $trimmedfilename, // The theme is not the currently installed, but the system translated the path to the that directory. // This is because the filename is the same as the installed theme, so the system just translated there. // We don't want that. $coretheme . '/' . $trimmedfilename => $theme . '/' . $trimmedfilename, ); foreach($replacements as $k => $v){ if($k == $v) continue; if(strpos($nf->getFilename(), $k) !== false){ $nf->setFilename( str_replace($k, $v, $nf->getFilename()) ); } } */ // Check if this file even needs updated. (this is primarily used for reporting reasons) if ($nf->exists() && $nf->identicalTo($f)) { //echo "Skipping file, it's identical.<br/>"; if ($verbosity == 2) { CLI::PrintActionStatus('skip'); } continue; } elseif ($nf->exists()) { $action = 'Replaced'; } else { $action = 'Installed'; } if (!$f->isReadable()) { throw new \InstallerException('Source file [' . $f->getFilename() . '] is not readable.'); } try { $f->copyTo($nf, true); } catch (\Exception $e) { throw new \InstallerException('Unable to copy [' . $f->getFilename() . '] to [' . $nf->getFilename() . ']'); } $change = $action . ' ' . $nf->getFilename(); $changes[] = $change; if ($verbosity == 1) { CLI::PrintLine($change); } elseif ($verbosity == 2) { CLI::PrintActionStatus('ok'); } } // If there are custom assets not registered by any application, install them too! // This will allow an admin to upload additional css resources and images easily. $directory = \Core\Filestore\Factory::Directory('themes/custom/assets'); $ls = $directory->ls(null, true); $baseStrLen = strlen(ROOT_PDIR . '/themes/custom/assets'); foreach ($ls as $fileOrDir) { if ($fileOrDir instanceof File) { $newfilename = substr($fileOrDir->getFilename(), $baseStrLen); if ($verbosity == 2) { CLI::PrintActionStart('Installing CUSTOM asset ' . $newfilename); } $nf = \Core\Filestore\Factory::File('asset/' . $newfilename); if ($nf->exists() && $nf->identicalTo($fileOrDir)) { //echo "Skipping file, it's identical.<br/>"; if ($verbosity == 2) { CLI::PrintActionStatus('skip'); } continue; } elseif ($nf->exists()) { $action = 'Replaced'; } else { $action = 'Installed'; } try { $fileOrDir->copyTo($nf, true); } catch (\Exception $e) { throw new \InstallerException('Unable to copy [' . $fileOrDir->getFilename() . '] to [' . $nf->getFilename() . ']'); } $change = $action . ' ' . $nf->getFilename(); $changes[] = $change; if ($verbosity == 1) { CLI::PrintLine($change); } elseif ($verbosity == 2) { CLI::PrintActionStatus('ok'); } } } if (!sizeof($changes)) { if ($verbosity > 0) { CLI::PrintLine('No changes required'); } return false; } // Make sure the asset cache is purged! \Core\Cache::Delete('asset-resolveurl'); return $changes; }
/** * Component installation operations all share common actions, (mostly). * * Returns false if nothing changed, else will return an array containing all changes. * * @param int $verbosity 0 for standard output, 1 for real-time, 2 for real-time verbose output. * * @return false | array * @throws InstallerException */ private function _performInstall($verbosity = 0) { // make sure that some of the installer elements are available! require_once(ROOT_PDIR . 'core/libs/core/InstallerException.php'); #SKIPCOMPILER $changed = array(); $change = $this->_parseDBSchema(true, $verbosity); if ($change !== false){ $changed = array_merge($changed, $change); } $change = $this->_parseConfigs(true, $verbosity); if ($change !== false){ $changed = array_merge($changed, $change); } $change = $this->_parseUserConfigs(true, $verbosity); if ($change !== false){ $changed = array_merge($changed, $change); } $change = $this->_parsePages(true, $verbosity); if ($change !== false){ $changed = array_merge($changed, $change); } $change = $this->_parseWidgets(true, $verbosity); if ($change !== false){ $changed = array_merge($changed, $change); } $change = $this->_parseAssets(true, $verbosity); if ($change !== false){ $changed = array_merge($changed, $change); } // Core has some additional things that need to ran through. if($this->getKeyName() == 'core'){ // Make sure that files/private has a restrictive .htaccess file installed. $f = \Core\Filestore\Factory::File('private/.htaccess'); if(!$f->exists() && $f->isWritable()){ $src = \Core\Filestore\Factory::File('core/htaccess.private'); if($src->copyTo($f)){ $changed[] = 'Installed private htaccess file into ' . $f->getFilename(); } } // Make sure that files/public has the appropriate .htaccess file installed. $f = \Core\Filestore\Factory::File('public/.htaccess'); if(!$f->exists() && $f->isWritable()){ $src = \Core\Filestore\Factory::File('core/htaccess.public'); if($src->copyTo($f)){ $changed[] = 'Installed public htaccess file into ' . $f->getFilename(); } } // Make sure that files/public has the appropriate .htaccess file installed. $f = \Core\Filestore\Factory::File('asset/.htaccess'); // This is a bit of a hack because I need the parent directory for assets, not the theme-specific version. $f->setFilename(dirname(dirname($f->getFilename())) . '/.htaccess'); if(!$f->exists() && $f->isWritable()){ $src = \Core\Filestore\Factory::File('core/htaccess.assets'); if($src->copyTo($f)){ $changed[] = 'Installed assets htaccess file into ' . $f->getFilename(); } } } // Ensure that the core component cache is purged too! \Core\Cache::Delete('core-components'); return (sizeof($changed)) ? $changed : false; }