/setup/includes/modinstall.class.php
PHP | 653 lines | 586 code | 17 blank | 50 comment | 14 complexity | 81a15cf6d4518468cbab0f6eb628eca5 MD5 | raw file
Possible License(s): GPL-2.0, Apache-2.0, BSD-3-Clause, LGPL-2.1
- <?php
- /*
- * This file is part of MODX Revolution.
- *
- * Copyright (c) MODX, LLC. All Rights Reserved.
- *
- * For complete copyright and license information, see the COPYRIGHT and LICENSE
- * files found in the top-level directory of this distribution.
- */
- /**
- * Common classes for the MODX installation and provisioning services.
- *
- * @package setup
- */
- /**
- * Provides common functionality and data for installation and provisioning.
- *
- * @package setup
- */
- class modInstall {
- const MODE_NEW = 0;
- const MODE_UPGRADE_REVO = 1;
- const MODE_UPGRADE_EVO = 2;
- const MODE_UPGRADE_REVO_ADVANCED = 3;
- /** @var xPDO $xpdo */
- public $xpdo = null;
- public $options = array ();
- /** @var modInstallRequest $request */
- public $request = null;
- /** @var modInstallSettings $settings */
- public $settings = null;
- /** @var modInstallLexicon $lexicon */
- public $lexicon = null;
- /** @var modInstallTest $test */
- public $test;
- /** @var modInstallDriver $driver */
- public $driver;
- /** @var modInstallRunner $runner */
- public $runner;
- /** @var array $config */
- public $config = array ();
- public $action = '';
- public $finished = false;
- /**
- * The constructor for the modInstall object.
- *
- * @constructor
- * @param array $options An array of configuration options.
- */
- function __construct(array $options = array()) {
- if (isset ($_REQUEST['action'])) {
- $this->action = preg_replace('/[\.]{2,}/', '', htmlspecialchars($_REQUEST['action']));
- }
- if (is_array($options)) {
- $this->options = $options;
- }
- }
- /**
- * Load a class file for setup
- * @param string $class The name of the class to load
- * @param string $path The path to load the class from
- * @return array|bool
- */
- public function loadClass($class,$path = '') {
- $classFile = str_replace('.', '/', strtolower($class));
- $className = explode('.',$class);
- $className = array_reverse($className);
- $className = $className[0];
- if (empty($path)) {
- $path = strtr(realpath(MODX_SETUP_PATH.'includes'),'\\','/').'/';
- }
- $classPath = $path.$classFile.'.class.php';
- $included = require_once $classPath;
- return $included ? $className : false;
- }
- /**
- * Return a service class instance
- * @param string $name
- * @param string $class
- * @param string $path
- * @param array $config
- * @return Object|null
- */
- public function getService($name,$class,$path = '',array $config = array()) {
- if (empty($this->$name)) {
- $className = $this->loadClass($class,$path);
- if (!empty($className)) {
- $this->$name = new $className($this,$config);
- } else {
- $this->_fatalError($this->lexicon('service_err_nf',array(
- 'name' => $name,
- 'class' => $class,
- 'path' => $path,
- )));
- }
- }
- return $this->$name;
- }
- /**
- * Load settings class
- *
- * @access public
- * @param string $class The settings class to load.
- * @param string $path
- * @return modInstallSettings
- */
- public function loadSettings($class = 'modInstallSettings',$path = '') {
- if (empty($this->settings)) {
- $className = $this->loadClass($class,$path);
- if (!empty($className)) {
- $this->settings = new $className($this);
- } else {
- $this->_fatalError($this->lexicon('settings_handler_err_nf',array('path' => $className)));
- }
- }
- return $this->settings;
- }
- /**
- * Shortcut method for modInstallLexicon::get. {@see modInstallLexicon::get}
- *
- * @param string $key
- * @param array $placeholders
- * @return string
- */
- public function lexicon($key,array $placeholders = array()) {
- return $this->lexicon->get($key,$placeholders);
- }
- /**
- * Get an xPDO connection to the database.
- *
- * @param int $mode
- * @return xPDO A copy of the xpdo object.
- */
- public function getConnection($mode = 0) {
- if ($this->settings && empty($mode)) $mode = (int)$this->settings->get('installmode');
- if (empty($mode)) $mode = modInstall::MODE_NEW;
- if ($mode === modInstall::MODE_UPGRADE_REVO) {
- $errors = array ();
- $this->xpdo = $this->_modx($errors);
- } else if (!is_object($this->xpdo)) {
- $options = array();
- if ($this->settings->get('new_folder_permissions')) $options['new_folder_permissions'] = $this->settings->get('new_folder_permissions');
- if ($this->settings->get('new_file_permissions')) $options['new_file_permissions'] = $this->settings->get('new_file_permissions');
- $this->xpdo = $this->_connect(
- $this->settings->get('database_dsn')
- ,$this->settings->get('database_user')
- ,$this->settings->get('database_password')
- ,$this->settings->get('table_prefix')
- ,$options
- );
- if (!($this->xpdo instanceof xPDO)) { return $this->xpdo; }
- $this->xpdo->setOption('cache_path',MODX_CORE_PATH . 'cache/');
- $config_options = (array)$this->settings->get('config_options');
- foreach ($config_options as $config_option => $config_value) {
- $this->xpdo->setOption($config_option, $config_value);
- }
- if ($mode === modInstall::MODE_UPGRADE_REVO_ADVANCED) {
- if ($this->xpdo->connect()) {
- $errors = array ();
- $this->xpdo = $this->_modx($errors);
- } else {
- return $this->lexicon('db_err_connect_upgrade');
- }
- }
- }
- if (is_object($this->xpdo) && $this->xpdo instanceof xPDO) {
- $this->xpdo->setLogTarget(array(
- 'target' => 'FILE',
- 'options' => array(
- 'filename' => 'install.' . MODX_CONFIG_KEY . '.' . strftime('%Y-%m-%dT%H.%M.%S').'.log'
- )
- ));
- $this->xpdo->setLogLevel(xPDO::LOG_LEVEL_ERROR);
- $this->xpdo->setPackage('modx', MODX_CORE_PATH . 'model/', $this->settings->get('table_prefix'));
- }
- return $this->xpdo;
- }
- /**
- * Load distribution-specific test handlers
- *
- * @param string $class
- * @param string $path
- * @param array $config
- * @return modInstallTest|void
- */
- public function loadTestHandler($class = 'test.modInstallTest',$path = '',array $config = array()) {
- $className = $this->loadClass($class,$path);
- if (!empty($className)) {
- $this->lexicon->load('test');
- $distributionClass = 'test.'.$className.ucfirst(trim(MODX_SETUP_KEY, '@'));
- $distributionClassName = $this->loadClass($distributionClass,$path);
- if (empty($distributionClassName)) {
- $this->_fatalError($this->lexicon('test_version_class_nf',array('path' => $distributionClass)));
- }
- $this->test = new $distributionClassName($this);
- } else {
- $this->_fatalError($this->lexicon('test_class_nf',array('path' => $path)));
- }
- return $this->test;
- }
- /**
- * Perform a series of pre-installation tests.
- *
- * @param integer $mode The install mode.
- * @param string $testClass The class to run tests with
- * @param string $testClassPath
- * @return array An array of result messages collected during the process.
- */
- public function test($mode = modInstall::MODE_NEW,$testClass = 'test.modInstallTest',$testClassPath = '') {
- $this->loadTestHandler($testClass,$testClassPath);
- return $this->test->run($mode);
- }
- /**
- * Verify that the modX class can be initialized.
- *
- * @return array An array of error messages collected during the process.
- */
- public function verify() {
- $errors = array ();
- $modx = $this->_modx($errors);
- if (is_object($modx) && $modx instanceof modX) {
- if ($modx->getCacheManager()) {
- $modx->cacheManager->refresh();
- }
- }
- return $errors;
- }
- /**
- * Cleans up after install.
- *
- * TODO: implement this function to cleanup any temporary files
- * @param array $options
- * @return array
- */
- public function cleanup(array $options = array ()) {
- $errors = array();
- $modx = $this->_modx($errors);
- if (empty($modx) || !($modx instanceof modX)) {
- $errors['modx_class'] = $this->lexicon('modx_err_instantiate');
- return $errors;
- }
- /* create the directories for Package Management */
- /** @var modCacheManager $cacheManager */
- $cacheManager = $modx->getCacheManager();
- $directoryOptions = array(
- 'new_folder_permissions' => $modx->getOption('new_folder_permissions',null,0775),
- );
- /* create assets/ */
- $assetsPath = $this->settings->get('assets_path',$this->settings->get('web_path',$modx->getOption('base_path')).'assets/');
- if (!is_dir($assetsPath)) {
- $cacheManager->writeTree($assetsPath,$directoryOptions);
- }
- if (!is_dir($assetsPath) || !$this->is_writable2($assetsPath)) {
- $errors['assets_not_created'] = str_replace('[[+path]]',$assetsPath,$this->lexicon('setup_err_assets'));
- }
- unset($assetsPath);
- /* create assets/components/ */
- $assetsCompPath = $this->settings->get('assets_path',$this->settings->get('web_path',$modx->getOption('base_path')).'assets/').'components/';
- if (!is_dir($assetsCompPath)) {
- $cacheManager->writeTree($assetsCompPath,$directoryOptions);
- }
- if (!is_dir($assetsCompPath) || !$this->is_writable2($assetsCompPath)) {
- $errors['assets_comp_not_created'] = str_replace('[[+path]]',$assetsCompPath,$this->lexicon('setup_err_assets_comp'));
- }
- unset($assetsCompPath);
- /* create core/components/ */
- $coreCompPath = $this->settings->get('core_path',$modx->getOption('core_path',null,MODX_CORE_PATH)).'components/';
- if (!is_dir($coreCompPath)) {
- $cacheManager->writeTree($coreCompPath,$directoryOptions);
- }
- if (!is_dir($coreCompPath) || !$this->is_writable2($coreCompPath)) {
- $errors['core_comp_not_created'] = str_replace('[[+path]]',$coreCompPath,$this->lexicon('setup_err_core_comp'));
- }
- unset($coreCompPath);
- return $errors;
- }
- /**
- * Removes the setup directory
- *
- * @access public
- * @param array $options
- * @return array
- */
- public function removeSetupDirectory(array $options = array()) {
- $errors = array();
- $modx = $this->_modx($errors);
- if ($modx) {
- /** @var modCacheManager $cacheManager */
- $cacheManager = $modx->getCacheManager();
- if ($cacheManager) {
- $setupPath = $modx->getOption('base_path').'setup/';
- if (!$cacheManager->deleteTree($setupPath,true,false,false)) {
- $modx->log(modX::LOG_LEVEL_ERROR,$this->lexicon('setup_err_remove'));
- }
- } else {
- $modx->log(modX::LOG_LEVEL_ERROR,$this->lexicon('cache_manager_err'));
- }
- } else {
- $modx->log(modX::LOG_LEVEL_ERROR,$this->lexicon('modx_object_err'));
- }
- return $errors;
- }
- /**
- * Generates a random universal unique ID for identifying modx installs
- *
- * @return string A universally unique ID
- */
- public function generateUUID() {
- srand(intval(microtime(true) * 1000));
- $b = md5(uniqid(rand(),true),true);
- $b[6] = chr((ord($b[6]) & 0x0F) | 0x40);
- $b[8] = chr((ord($b[8]) & 0x3F) | 0x80);
- return implode('-',unpack('H8a/H4b/H4c/H4d/H12e',$b));
- }
- /**
- * Installs a transport package.
- *
- * @param string $pkg The package signature.
- * @param array $attributes An array of installation attributes.
- * @return array An array of error messages collected during the process.
- */
- public function installPackage($pkg, array $attributes = array ()) {
- $errors = array ();
- /* instantiate the modX class */
- if (@ require_once (MODX_CORE_PATH . 'model/modx/modx.class.php')) {
- $modx = new modX(MODX_CORE_PATH . 'config/');
- if (!is_object($modx) || !($modx instanceof modX)) {
- $errors[] = '<p>'.$this->lexicon('modx_err_instantiate').'</p>';
- } else {
- /* try to initialize the mgr context */
- $modx->initialize('mgr');
- if (!$modx->isInitialized()) {
- $errors[] = '<p>'.$this->lexicon('modx_err_instantiate_mgr').'</p>';
- } else {
- $loaded = $modx->loadClass('transport.xPDOTransport', XPDO_CORE_PATH, true, true);
- if (!$loaded)
- $errors[] = '<p>'.$this->lexicon('transport_class_err_load').'</p>';
- $packageDirectory = MODX_CORE_PATH . 'packages/';
- $packageState = (isset ($attributes[xPDOTransport::PACKAGE_STATE]) ? $attributes[xPDOTransport::PACKAGE_STATE] : xPDOTransport::STATE_PACKED);
- $package = xPDOTransport :: retrieve($modx, $packageDirectory . $pkg . '.transport.zip', $packageDirectory, $packageState);
- if ($package) {
- if (!$package->install($attributes)) {
- $errors[] = '<p>'.$this->lexicon('package_err_install',array('package' => $pkg)).'</p>';
- } else {
- $modx->log(xPDO::LOG_LEVEL_INFO,$this->lexicon('package_installed',array('package' => $pkg)));
- }
- } else {
- $errors[] = '<p>'.$this->lexicon('package_err_nf',array('package' => $pkg)).'</p>';
- }
- }
- }
- } else {
- $errors[] = '<p>'.$this->lexicon('modx_class_err_nf').'</p>';
- }
- return $errors;
- }
- /**
- * Gets the manager login URL.
- *
- * @return string The URL of the installed manager context.
- */
- public function getManagerLoginUrl() {
- $url = '';
- /* instantiate the modX class */
- if (@ require_once (MODX_CORE_PATH . 'model/modx/modx.class.php')) {
- $modx = new modX(MODX_CORE_PATH . 'config/');
- if (is_object($modx) && $modx instanceof modX) {
- /* try to initialize the mgr context */
- $modx->initialize('mgr');
- $url = MODX_URL_SCHEME.$modx->getOption('http_host').$modx->getOption('manager_url');
- }
- }
- return $url;
- }
- /**
- * Determines the possible install modes.
- *
- * @access public
- * @return integer One of three possible mode indicators:<ul>
- * <li>0 = new install only</li>
- * <li>1 = new OR upgrade from older versions of MODX Revolution</li>
- * <li>2 = new OR upgrade from MODX Evolution</li>
- * </ul>
- */
- public function getInstallMode() {
- $mode = modInstall::MODE_NEW;
- if (isset ($_POST['installmode'])) {
- $mode = intval($_POST['installmode']);
- } else {
- global $dbase;
- if (file_exists(MODX_CORE_PATH . 'config/' . MODX_CONFIG_KEY . '.inc.php')) {
- /* Include the file so we can test its validity */
- $included = @ include (MODX_CORE_PATH . 'config/' . MODX_CONFIG_KEY . '.inc.php');
- $mode = ($included && isset ($dbase)) ? modInstall::MODE_UPGRADE_REVO : modInstall::MODE_NEW;
- }
- if (!$mode && file_exists(MODX_INSTALL_PATH . 'manager/includes/config.inc.php')) {
- $included = @ include (MODX_INSTALL_PATH . 'manager/includes/config.inc.php');
- $mode = ($included && isset ($dbase)) ? modInstall::MODE_UPGRADE_EVO : modInstall::MODE_NEW;
- }
- }
- return $mode;
- }
- /**
- * Creates the database connection for the installation process.
- *
- * @access private
- * @return xPDO The xPDO instance to be used by the installation.
- */
- public function _connect($dsn, $user = '', $password = '', $prefix = '', array $options = array()) {
- if (include_once (MODX_CORE_PATH . 'xpdo/xpdo.class.php')) {
- $this->xpdo = new xPDO($dsn, $user, $password, array_merge(array(
- xPDO::OPT_CACHE_PATH => MODX_CORE_PATH . 'cache/',
- xPDO::OPT_TABLE_PREFIX => $prefix,
- xPDO::OPT_SETUP => true,
- ), $options),
- array(PDO::ATTR_ERRMODE => PDO::ERRMODE_SILENT)
- );
- $this->xpdo->setLogTarget(array(
- 'target' => 'FILE',
- 'options' => array(
- 'filename' => 'install.' . MODX_CONFIG_KEY . '.' . strftime('%Y%m%dT%H%M%S') . '.log'
- )
- ));
- $this->xpdo->setLogLevel(xPDO::LOG_LEVEL_ERROR);
- return $this->xpdo;
- } else {
- return $this->lexicon('xpdo_err_nf', array('path' => MODX_CORE_PATH.'xpdo/xpdo.class.php'));
- }
- }
- /**
- * Instantiate an existing modX configuration.
- *
- * @param array &$errors An array in which error messages are collected.
- * @return modX|null The modX instance, or null if it could not be instantiated.
- */
- private function _modx(array & $errors) {
- $modx = null;
- /* to validate installation, instantiate the modX class and run a few tests */
- if (include_once (MODX_CORE_PATH . 'model/modx/modx.class.php')) {
- $modx = new modX(MODX_CORE_PATH . 'config/', array(
- xPDO::OPT_SETUP => true,
- ));
- if (!is_object($modx) || !($modx instanceof modX)) {
- $errors[] = '<p>'.$this->lexicon('modx_err_instantiate').'</p>';
- } else {
- $modx->setLogTarget(array(
- 'target' => 'FILE',
- 'options' => array(
- 'filename' => 'install.' . MODX_CONFIG_KEY . '.' . strftime('%Y%m%dT%H%M%S') . '.log'
- )
- ));
- /* try to initialize the mgr context */
- $modx->initialize('mgr');
- if (!$modx->isInitialized()) {
- $errors[] = '<p>'.$this->lexicon('modx_err_instantiate_mgr').'</p>';
- }
- }
- } else {
- $errors[] = '<p>'.$this->lexicon('modx_class_err_nf').'</p>';
- }
- return $modx;
- }
- /**
- * Finds the core directory, if possible. If core cannot be found, loads the
- * findcore controller.
- *
- * @return Returns true if core directory is found.
- */
- public function findCore() {
- $exists = false;
- if (defined('MODX_CORE_PATH') && file_exists(MODX_CORE_PATH) && is_dir(MODX_CORE_PATH)) {
- if (file_exists(MODX_CORE_PATH . 'xpdo/xpdo.class.php') && file_exists(MODX_CORE_PATH . 'model/modx/modx.class.php')) {
- $exists = true;
- }
- }
- if (!$exists) {
- include(MODX_SETUP_PATH . 'templates/findcore.php');
- die();
- }
- return $exists;
- }
- /**
- * Does all the pre-load checks, before setup loads.
- *
- * @access public
- */
- public function doPreloadChecks() {
- $this->lexicon->load('preload');
- $errors= array();
- if (!extension_loaded('pdo')) {
- $errors[] = $this->lexicon('preload_err_pdo');
- }
- if (!file_exists(MODX_CORE_PATH) || !is_dir(MODX_CORE_PATH)) {
- $errors[] = $this->lexicon('preload_err_core_path');
- }
- if (!file_exists(MODX_CORE_PATH . 'cache/') || !is_dir(MODX_CORE_PATH . 'cache/') || !$this->is_writable2(MODX_CORE_PATH . 'cache/')) {
- $errors[] = $this->lexicon('preload_err_cache',array('path' => MODX_CORE_PATH));
- }
- if (!empty($errors)) {
- $this->_fatalError($errors);
- }
- }
- /**
- * Outputs a fatal error message and then dies.
- *
- * @param string|array $errors A string or array of errors
- * @return void
- */
- public function _fatalError($errors) {
- $output = '<html><head><title></title></head><body><h1>'.$this->lexicon('fatal_error').'</h1><ul>';
- if (is_array($errors)) {
- foreach ($errors as $error) {
- $output .= '<li>'.$error.'</li>';
- }
- } else {
- $output .= '<li>'.$errors.'</li>';
- }
- $output .= '</ul></body></html>';
- die($output);
- }
- /**
- * Custom is_writable function to test on problematic servers
- *
- * @param string $path
- * @return boolean True if write was successful
- */
- public function is_writable2($path) {
- $written = false;
- if (!is_string($path)) return false;
- /* if is file get parent dir */
- if (is_file($path)) { $path = dirname($path) . '/'; }
- /* ensure / at end, translate \ to / for windows */
- if (substr($path,strlen($path)-1) != '/') { $path .= '/'; }
- $path = strtr($path,'\\','/');
- /* get test file */
- $filePath = $path.uniqid().'.cache.php';
- /* attempt to create test file */
- $fp = @fopen($filePath,'w');
- if ($fp === false || !file_exists($filePath)) return false;
- /* attempt to write to test file */
- $written = @fwrite($fp,'<?php echo "test";');
- if (!$written) { /* if fails try to delete it */
- @fclose($fp);
- @unlink($filePath);
- return false;
- }
- /* attempt to delete test file */
- @fclose($fp);
- $written = @unlink($filePath);
- return $written;
- }
- /**
- * Loads the correct database driver for this environment.
- *
- * @param string $path
- * @return boolean True if successful.
- */
- public function loadDriver($path = '') {
- $this->loadSettings();
- /* db specific driver */
- $class = 'drivers.modInstallDriver_'.strtolower($this->settings->get('database_type','mysql'));
- $className = $this->loadClass($class,$path);
- if (!empty($className)) {
- $this->driver = new $className($this);
- } else {
- $this->_fatalError($this->lexicon('driver_class_err_nf',array('path' => $class)));
- }
- return !empty($className);
- }
- public function lock() {
- $errors = array();
- $modx = $this->_modx($errors);
- if ($modx) {
- /** @var modCacheManager $cacheManager */
- $cacheManager = $modx->getCacheManager();
- if ($cacheManager) {
- if (!$cacheManager->writeTree(MODX_SETUP_PATH . '.locked')) {
- $modx->log(modX::LOG_LEVEL_ERROR,$this->lexicon('setup_err_lock'));
- }
- } else {
- $modx->log(modX::LOG_LEVEL_ERROR,$this->lexicon('cache_manager_err'));
- }
- } else {
- $modx->log(modX::LOG_LEVEL_ERROR,$this->lexicon('modx_object_err'));
- }
- return $errors;
- }
- public function isLocked() {
- if (file_exists(MODX_SETUP_PATH . '.locked')) {
- return true;
- }
- return false;
- }
- }