PageRenderTime 73ms CodeModel.GetById 3ms app.highlight 62ms RepoModel.GetById 1ms app.codeStats 0ms

/www/manager/setup/includes/modinstall.class.php

https://bitbucket.org/argnist/mohana
PHP | 910 lines | 632 code | 73 blank | 205 comment | 140 complexity | 1c40a98fe664decc45c418fdbdbfc508 MD5 | raw file
  1<?php
  2/*
  3 * MODX Revolution
  4 *
  5 * Copyright 2006-2011 by MODX, LLC.
  6 * All rights reserved.
  7 *
  8 * This program is free software; you can redistribute it and/or modify it under
  9 * the terms of the GNU General Public License as published by the Free Software
 10 * Foundation; either version 2 of the License, or (at your option) any later
 11 * version.
 12 *
 13 * This program is distributed in the hope that it will be useful, but WITHOUT
 14 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 15 * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
 16 * details.
 17 *
 18 * You should have received a copy of the GNU General Public License along with
 19 * this program; if not, write to the Free Software Foundation, Inc., 59 Temple
 20 * Place, Suite 330, Boston, MA 02111-1307 USA
 21 */
 22
 23/**
 24 * Common classes for the MODX installation and provisioning services.
 25 *
 26 * @package setup
 27 */
 28/**
 29 * Provides common functionality and data for installation and provisioning.
 30 *
 31 * @package setup
 32 */
 33class modInstall {
 34    const MODE_NEW = 0;
 35    const MODE_UPGRADE_REVO = 1;
 36    const MODE_UPGRADE_EVO = 2;
 37    const MODE_UPGRADE_REVO_ADVANCED = 3;
 38
 39    public $xpdo = null;
 40    public $options = array ();
 41    public $config = array ();
 42    public $action = '';
 43    public $lexicon = null;
 44    public $finished = false;
 45
 46    /**
 47     * The constructor for the modInstall object.
 48     *
 49     * @constructor
 50     * @param array $options An array of configuration options.
 51     */
 52    function __construct(array $options = array()) {
 53        if (isset ($_REQUEST['action'])) {
 54            $this->action = $_REQUEST['action'];
 55        }
 56        if (is_array($options)) {
 57            $this->options = $options;
 58        }
 59    }
 60
 61    /**
 62     * Loads the request handler for the setup.
 63     * @return boolean True if successful.
 64     */
 65    public function loadRequestHandler($class = 'modInstallRequest') {
 66        $path = dirname(__FILE__).'/'.strtolower($class).'.class.php';
 67        $included = @include $path;
 68        if ($included) {
 69            $this->request = new $class($this);
 70        } else {
 71            $this->_fatalError($this->lexicon('request_handler_err_nf',array('path' => $path)));
 72        }
 73        return $included;
 74    }
 75
 76    /**
 77     * Load settings class
 78     *
 79     * @access public
 80     * @param string $class The settings class to load.
 81     * @return boolean True if successful.
 82     */
 83    public function loadSettings($class = 'modInstallSettings') {
 84        $path = dirname(__FILE__).'/'.strtolower($class).'.class.php';
 85        $included = @include_once $path;
 86        if ($included) {
 87            $this->settings = new $class($this);
 88        } else {
 89            $this->_fatalError($this->lexicon('settings_handler_err_nf',array('path' => $path)));
 90        }
 91        return $included;
 92    }
 93
 94    /**
 95     * Loads the lexicon class for the install process.
 96     *
 97     * @param string $class The class name of the lexicon class to use.
 98     * @return boolean True if successful.
 99     */
100    public function loadLexicon($class = 'modInstallLexicon') {
101        $path = dirname(__FILE__).'/'.strtolower($class).'.class.php';
102        $included = @include $path;
103        $this->lexicon = new modInstallLexicon($this);
104        return $included;
105    }
106
107    /**
108     * Shortcut method for modInstallLexicon::get. {@see modInstallLexicon::get}
109     */
110    public function lexicon($key,array $placeholders = array()) {
111        return $this->lexicon->get($key,$placeholders);
112    }
113
114    /**
115     * Get the existing or create a new configuration.
116     *
117     * @param integer $mode The install mode.
118     * @param array $config An array of config attributes.
119     * @return array A copy of the config attributes array.
120     */
121    public function getConfig($mode = 0, $config = array ()) {
122        global $database_dsn, $database_type, $database_server, $dbase, $database_user,
123                $database_password, $database_connection_charset, $table_prefix, $config_options;
124        $database_connection_charset = 'utf8';
125        if (!is_array($config)) {
126            $config = array ();
127        }
128
129        /* get http host */
130        $https_port = isset ($_POST['httpsport']) ? $_POST['httpsport'] : '443';
131        $isSecureRequest = ((isset ($_SERVER['HTTPS']) && strtolower($_SERVER['HTTPS']) == 'on') || $_SERVER['SERVER_PORT'] == $https_port);
132        $http_host= $_SERVER['HTTP_HOST'];
133        if ($_SERVER['SERVER_PORT'] != 80) {
134            $http_host= str_replace(':' . $_SERVER['SERVER_PORT'], '', $http_host); /* remove port from HTTP_HOST */
135        }
136        $http_host .= ($_SERVER['SERVER_PORT'] == 80 || $isSecureRequest) ? '' : ':' . $_SERVER['SERVER_PORT'];
137
138        switch ($mode) {
139            case modInstall::MODE_UPGRADE_EVO :
140                $included = @ include MODX_INSTALL_PATH . 'manager/includes/config.inc.php';
141                if ($included && isset ($dbase))
142                    break;
143
144            case modInstall::MODE_UPGRADE_REVO :
145            case modInstall::MODE_UPGRADE_REVO_ADVANCED :
146                $included = @ include MODX_CORE_PATH . 'config/' . MODX_CONFIG_KEY . '.inc.php';
147                if ($included && isset ($dbase)) {
148                    $config['mgr_path'] = MODX_MANAGER_PATH;
149                    $config['connectors_path'] = MODX_CONNECTORS_PATH;
150                    $config['web_path'] = MODX_BASE_PATH;
151                    $config['context_mgr_path'] = MODX_MANAGER_PATH;
152                    $config['context_connectors_path'] = MODX_CONNECTORS_PATH;
153                    $config['context_web_path'] = MODX_BASE_PATH;
154
155                    $config['mgr_url'] = MODX_MANAGER_URL;
156                    $config['connectors_url'] = MODX_CONNECTORS_URL;
157                    $config['web_url'] = MODX_BASE_URL;
158                    $config['context_mgr_url'] = MODX_MANAGER_URL;
159                    $config['context_connectors_url'] = MODX_CONNECTORS_URL;
160                    $config['context_web_url'] = MODX_BASE_URL;
161
162                    $config['core_path'] = MODX_CORE_PATH;
163                    $config['processors_path'] = MODX_CORE_PATH.'model/modx/processors/';
164                    $config['assets_path'] = MODX_ASSETS_PATH;
165                    $config['assets_url'] = MODX_ASSETS_URL;
166
167                    $config_options = !empty($config_options) ? $config_options : array();
168                    break;
169                }
170
171            default :
172                $included = false;
173                $database_type = isset ($_POST['databasetype']) ? $_POST['databasetype'] : 'mysql';
174                $database_server = isset ($_POST['databasehost']) ? $_POST['databasehost'] : 'localhost';
175                $database_user = isset ($_POST['databaseloginname']) ? $_POST['databaseloginname'] : '';
176                $database_password = isset ($_POST['databaseloginpassword']) ? $_POST['databaseloginpassword'] : '';
177                $dbase = isset ($_POST['database_name']) ? $_POST['database_name'] : 'modx';
178                $table_prefix = isset ($_POST['tableprefix']) ? $_POST['tableprefix'] : 'modx_';
179                $https_port = isset ($_POST['httpsport']) ? $_POST['httpsport'] : '443';
180                $cache_disabled = isset ($_POST['cache_disabled']) ? $_POST['cache_disabled'] : 'false';
181                $site_sessionname = 'SN' . uniqid('');
182                $config_options = array();
183                break;
184        }
185        $config = array_merge($config,array(
186            'database_type' => $database_type,
187            'database_server' => $database_server,
188            'dbase' => trim($dbase, '`[]'),
189            'database_user' => $database_user,
190            'database_password' => $database_password,
191            'database_connection_charset' => $database_connection_charset,
192            'database_charset' => $database_connection_charset,
193            'table_prefix' => $table_prefix,
194            'https_port' => isset ($https_port) ? $https_port : '443',
195            'http_host' => defined('MODX_HTTP_HOST') ? MODX_HTTP_HOST : $http_host,
196            'site_sessionname' => isset ($site_sessionname) ? $site_sessionname : 'SN' . uniqid(''),
197            'cache_disabled' => isset ($cache_disabled) && $cache_disabled ? 'true' : 'false',
198            'inplace' => isset ($_POST['inplace']) ? 1 : 0,
199            'unpacked' => isset ($_POST['unpacked']) ? 1 : 0,
200            'config_options' => $config_options,
201        ));
202        $this->config = array_merge($this->config, $config);
203        switch ($this->config['database_type']) {
204            case 'sqlsrv':
205                $database_dsn = $this->config['database_dsn'] = "{$this->config['database_type']}:server={$this->config['database_server']};database={$this->config['dbase']}";
206                break;
207            case 'mysql':
208                $database_dsn = $this->config['database_dsn'] = "{$this->config['database_type']}:host={$this->config['database_server']};dbname={$this->config['dbase']};charset={$this->config['database_connection_charset']}";
209                break;
210            default:
211                break;
212        }
213        return $this->config;
214    }
215
216    /**
217     * Get an xPDO connection to the database.
218     *
219     * @return xPDO A copy of the xpdo object.
220     */
221    public function getConnection($mode = modInstall::MODE_NEW) {
222        if ($mode === modInstall::MODE_UPGRADE_REVO) {
223            $errors = array ();
224            $this->xpdo = $this->_modx($errors);
225        } else if (!is_object($this->xpdo)) {
226            $options = array();
227            if ($this->settings->get('new_folder_permissions')) $options['new_folder_permissions'] = $this->settings->get('new_folder_permissions');
228            if ($this->settings->get('new_file_permissions')) $options['new_file_permissions'] = $this->settings->get('new_file_permissions');
229            $this->xpdo = $this->_connect(
230                $this->settings->get('database_dsn')
231                ,$this->settings->get('database_user')
232                ,$this->settings->get('database_password')
233                ,$this->settings->get('table_prefix')
234                ,$options
235             );
236
237            if (!($this->xpdo instanceof xPDO)) { return $this->xpdo; }
238
239            $this->xpdo->setOption('cache_path',MODX_CORE_PATH . 'cache/');
240
241            if ($mode === modInstall::MODE_UPGRADE_REVO_ADVANCED) {
242                if ($this->xpdo->connect()) {
243                    $errors = array ();
244                    $this->xpdo = $this->_modx($errors);
245                } else {
246                    return $this->lexicon('db_err_connect_upgrade');
247                }
248            }
249        }
250        if (is_object($this->xpdo) && $this->xpdo instanceof xPDO) {
251            $this->xpdo->setLogTarget(array(
252                'target' => 'FILE',
253                'options' => array(
254                    'filename' => 'install.' . MODX_CONFIG_KEY . '.' . strftime('%Y-%m-%dT%H.%M.%S')
255                )
256            ));
257            $this->xpdo->setLogLevel(xPDO::LOG_LEVEL_ERROR);
258            $this->xpdo->setPackage('modx', MODX_CORE_PATH . 'model/', $this->settings->get('table_prefix'));
259        }
260        return $this->xpdo;
261    }
262
263    /**
264     * Load distribution-specific test handlers
265     */
266    public function loadTestHandler($class = 'modInstallTest') {
267        $path = dirname(__FILE__).'/'.strtolower($class).'.class.php';
268        $included = @include $path;
269        if ($included) {
270            $this->lexicon->load('test');
271
272            $class = $class.ucfirst(trim(MODX_SETUP_KEY, '@'));
273            $versionPath = dirname(__FILE__).'/checks/'.strtolower($class).'.class.php';
274            $included = @include $versionPath;
275            if (!$included) {
276                $this->_fatalError($this->lexicon('test_version_class_nf',array('path' => $versionPath)));
277            }
278            $this->test = new $class($this);
279            return $this->test;
280        } else {
281            $this->_fatalError($this->lexicon('test_class_nf',array('path' => $path)));
282        }
283    }
284
285    /**
286     * Perform a series of pre-installation tests.
287     *
288     * @param integer $mode The install mode.
289     * @param string $test_class The class to run tests with
290     * @return array An array of result messages collected during the process.
291     */
292    public function test($mode = modInstall::MODE_NEW,$test_class = 'modInstallTest') {
293        $test = $this->loadTestHandler($test_class);
294        $results = $this->test->run($mode);
295        return $results;
296    }
297
298    /**
299     * Load version-specific installer.
300     *
301     * @access public
302     * @param string $class The class to load.
303     */
304    public function loadVersionInstaller($class = 'modInstallVersion') {
305        $path = dirname(__FILE__).'/'.strtolower($class).'.class.php';
306        $included = @include $path;
307        if ($included) {
308            $this->versioner = new $class($this);
309            return $this->versioner;
310        } else {
311            $this->_fatalError($this->lexicon('versioner_err_nf',array('path' => $path)));
312        }
313    }
314
315    /**
316     * Execute the installation process.
317     *
318     * @param integer $mode The install mode.
319     * @return array An array of result messages collected during execution.
320     */
321    public function execute($mode) {
322        $results = array ();
323        /* set the time limit infinite in case it takes a bit
324         * TODO: fix this by allowing resume when it takes a long time
325         */
326        @ set_time_limit(0);
327        @ ini_set('max_execution_time', 240);
328        @ ini_set('memory_limit','128M');
329
330        /* write config file */
331        $this->writeConfig($results);
332
333        /* get connection */
334        $this->getConnection($mode);
335
336        /* run appropriate database routines */
337        switch ($mode) {
338            /* TODO: MODX Evolution to Revolution migration */
339            case modInstall::MODE_UPGRADE_EVO :
340                $results = include MODX_SETUP_PATH . 'includes/tables_migrate.php';
341                break;
342                /* revo-alpha+ upgrades */
343            case modInstall::MODE_UPGRADE_REVO :
344            case modInstall::MODE_UPGRADE_REVO_ADVANCED :
345                $this->loadVersionInstaller();
346                $results = $this->versioner->install();
347                break;
348                /* new install, create tables */
349            default :
350                $results = include MODX_SETUP_PATH . 'includes/tables_create.php';
351                break;
352        }
353
354        if ($this->xpdo) {
355            /* add required core data */
356            $this->xpdo->loadClass('transport.xPDOTransport', XPDO_CORE_PATH, true, true);
357
358            $packageDirectory = MODX_CORE_PATH . 'packages/';
359            $packageState = $this->settings->get('unpacked') == 1 ? xPDOTransport::STATE_UNPACKED : xPDOTransport::STATE_PACKED;
360            $package = xPDOTransport :: retrieve($this->xpdo, $packageDirectory . 'core.transport.zip', $packageDirectory, $packageState);
361            if (!is_object($package) || !($package instanceof xPDOTransport)) {
362                $results[] = array (
363                    'class' => 'failed',
364                    'msg' => '<p class="notok">'.$this->lexicon('package_execute_err_retrieve',array('path' => $this->settings->get('core_path'))).'</p>'
365                );
366                return $results;
367            }
368
369            if (!defined('MODX_BASE_PATH'))
370                define('MODX_BASE_PATH', $this->settings->get('context_web_path'));
371            if (!defined('MODX_ASSETS_PATH'))
372                define('MODX_ASSETS_PATH', $this->settings->get('context_assets_path'));
373            if (!defined('MODX_MANAGER_PATH'))
374                define('MODX_MANAGER_PATH', $this->settings->get('context_mgr_path'));
375            if (!defined('MODX_CONNECTORS_PATH'))
376                define('MODX_CONNECTORS_PATH', $this->settings->get('context_connectors_path'));
377
378            $package->install(array (
379                xPDOTransport::RESOLVE_FILES => ($this->settings->get('inplace') == 0 ? 1 : 0)
380                ,xPDOTransport::INSTALL_FILES => ($this->settings->get('inplace') == 0 ? 1 : 0)
381                , xPDOTransport::PREEXISTING_MODE => xPDOTransport::REMOVE_PREEXISTING
382            ));
383
384            /* set default workspace path */
385            $workspace = $this->xpdo->getObject('modWorkspace', array (
386                'active' => 1
387            ));
388            if ($workspace) {
389                $path = $workspace->get('path');
390                if (!empty($path)) {
391                    $path = trim($path);
392                }
393                if (empty ($path) || !file_exists($path)) {
394                    $workspace->set('path', MODX_CORE_PATH);
395                    if (!$workspace->save()) {
396                        $results[] = array (
397                            'class' => 'error',
398                            'msg' => '<p class="notok">'.$this->lexicon('workspace_err_path').'</p>'
399                        );
400                    } else {
401                        $results[] = array (
402                            'class' => 'success',
403                            'msg' => '<p class="ok">'.$this->lexicon('workspace_path_updated').'</p>'
404                        );
405                    }
406                }
407            } else {
408                $results[] = array (
409                    'class' => 'error',
410                    'msg' => '<p class="notok">'.$this->lexicon('workspace_err_nf').'</p>'
411                );
412            }
413            unset($workspace);
414
415            $modx =& $this->xpdo;
416
417            /* if new install */
418            if ($mode == modInstall::MODE_NEW) {
419                include MODX_SETUP_PATH.'includes/new.install.php';
420
421            /* if upgrade */
422            } else {
423                include MODX_SETUP_PATH.'includes/upgrade.install.php';
424            }
425
426            /* empty sessions table to prevent old permissions from loading */
427            $tableName = $this->xpdo->getTableName('modSession');
428            $this->xpdo->exec($this->driver->truncate($tableName));
429
430            /* clear cache */
431            $this->xpdo->cacheManager->deleteTree(MODX_CORE_PATH.'cache/',array(
432                'skipDirs' => false,
433                'extensions' => array(
434                    '.cache.php',
435                    '.tpl.php',
436                ),
437            ));
438
439            $this->settings->store(array(
440                'finished' => true,
441            ));
442        }
443
444        return $results;
445    }
446
447    /**
448     * Verify that the modX class can be initialized.
449     *
450     * @param integer $mode Indicates the installation mode.
451     * @return array An array of error messages collected during the process.
452     */
453    public function verify() {
454        $errors = array ();
455        $modx = $this->_modx($errors);
456        if (is_object($modx) && $modx instanceof modX) {
457            if ($modx->getCacheManager()) {
458                $modx->cacheManager->refresh();
459            }
460        }
461        return $errors;
462    }
463
464    /**
465     * Cleans up after install.
466     *
467     * TODO: implement this function to cleanup any temporary files
468     * @param array $options
469     */
470    public function cleanup(array $options = array ()) {
471        $errors = array();
472        $modx = $this->_modx($errors);
473        if (empty($modx) || !($modx instanceof modX)) {
474            $errors['modx_class'] = $this->lexicon('modx_err_instantiate');
475            return $errors;
476        }
477
478        /* create the directories for Package Management */
479        $cacheManager = $modx->getCacheManager();
480        $directoryOptions = array(
481            'new_folder_permissions' => $modx->getOption('new_folder_permissions',null,0775),
482        );
483
484        /* create assets/ */
485        $assetsPath = $modx->getOption('base_path').'assets/';
486        if (!is_dir($assetsPath)) {
487            $cacheManager->writeTree($assetsPath,$directoryOptions);
488        }
489        if (!is_dir($assetsPath) || !$this->is_writable2($assetsPath)) {
490            $errors['assets_not_created'] = str_replace('[[+path]]',$assetsPath,$this->lexicon('setup_err_assets'));
491        }
492        unset($assetsPath);
493
494        /* create assets/components/ */
495        $assetsCompPath = $modx->getOption('base_path').'assets/components/';
496        if (!is_dir($assetsCompPath)) {
497            $cacheManager->writeTree($assetsCompPath,$directoryOptions);
498        }
499        if (!is_dir($assetsCompPath) || !$this->is_writable2($assetsCompPath)) {
500            $errors['assets_comp_not_created'] = str_replace('[[+path]]',$assetsCompPath,$this->lexicon('setup_err_assets_comp'));
501        }
502        unset($assetsCompPath);
503
504        /* create core/components/ */
505        $coreCompPath = $modx->getOption('core_path').'components/';
506        if (!is_dir($coreCompPath)) {
507            $cacheManager->writeTree($coreCompPath,$directoryOptions);
508        }
509        if (!is_dir($coreCompPath) || !$this->is_writable2($coreCompPath)) {
510            $errors['core_comp_not_created'] = str_replace('[[+path]]',$coreCompPath,$this->lexicon('setup_err_core_comp'));
511        }
512        unset($coreCompPath);
513
514        return $errors;
515    }
516
517    /**
518     * Removes the setup directory
519     *
520     * @access publics
521     */
522    public function removeSetupDirectory(array $options = array()) {
523        $errors = array();
524
525        $modx = $this->_modx($errors);
526        if ($modx) {
527            $cacheManager = $modx->getCacheManager();
528            if ($cacheManager) {
529                $setupPath = $modx->getOption('base_path').'setup/';
530                if (!$cacheManager->deleteTree($setupPath,true,false,false)) {
531                    $modx->log(modX::LOG_LEVEL_ERROR,$this->lexicon('setup_err_remove'));
532                }
533            } else {
534                $modx->log(modX::LOG_LEVEL_ERROR,$this->lexicon('cache_manager_err'));
535            }
536        } else {
537            $modx->log(modX::LOG_LEVEL_ERROR,$this->lexicon('modx_object_err'));
538        }
539        return $errors;
540    }
541
542    /**
543     * Writes the config file.
544     *
545     * @param array $results An array of result messages.
546     * @return boolean Returns true if successful; false otherwise.
547     */
548    public function writeConfig(array &$results) {
549        $written = false;
550        $configTpl = MODX_CORE_PATH . 'docs/config.inc.tpl';
551        $configFile = MODX_CORE_PATH . 'config/' . MODX_CONFIG_KEY . '.inc.php';
552
553        $settings = $this->settings->fetch();
554        $settings['last_install_time'] = time();
555        $settings['site_id'] = uniqid('modx',true);
556
557        /* make UUID if not set */
558        if (empty($settings['uuid'])) {
559            $settings['uuid'] = $this->generateUUID();
560        }
561
562        if (file_exists($configTpl)) {
563            if ($tplHandle = @ fopen($configTpl, 'rb')) {
564                $content = @ fread($tplHandle, filesize($configTpl));
565                @ fclose($tplHandle);
566                if ($content) {
567                    $replace = array ();
568                    while (list ($key, $value) = each($settings)) {
569                        if (is_scalar($value)) {
570                            $replace['{' . $key . '}'] = "{$value}";
571                        } elseif (is_array($value)) {
572                            $replace['{' . $key . '}'] = var_export($value, true);
573                        }
574                    }
575                    $content = str_replace(array_keys($replace), array_values($replace), $content);
576                    if ($configHandle = @ fopen($configFile, 'wb')) {
577                        $written = @ fwrite($configHandle, $content);
578                        @ fclose($configHandle);
579                    }
580                }
581            }
582        }
583        $perms = $this->settings->get('new_file_permissions', sprintf("%04o", 0666 & (0666 - umask())));
584        if (is_string($perms)) $perms = octdec($perms);
585        $chmodSuccess = @ chmod($configFile, $perms);
586        if (!is_array($results)) {
587            $results = array ();
588        }
589        if ($written) {
590            $results[] = array (
591                'class' => 'success',
592                'msg' => '<p class="ok">'.$this->lexicon('config_file_written').'</p>'
593            );
594        } else {
595            $results[] = array (
596                'class' => 'failed',
597                'msg' => '<p class="notok">'.$this->lexicon('config_file_err_w').'</p>'
598            );
599        }
600        if ($chmodSuccess) {
601            $results[] = array (
602                'class' => 'success',
603                'msg' => '<p class="ok">'.$this->lexicon('config_file_perms_set').'</p>'
604            );
605        } else {
606            $results[] = array (
607                'class' => 'warning',
608                'msg' => '<p>'.$this->lexicon('config_file_perms_notset').'</p>'
609            );
610        }
611        return $results;
612    }
613
614    /**
615     * Generates a random universal unique ID for identifying modx installs
616     *
617     * @return string A universally unique ID
618     */
619    public function generateUUID() {
620        srand(intval(microtime(true) * 1000));
621        $b = md5(uniqid(rand(),true),true);
622        $b[6] = chr((ord($b[6]) & 0x0F) | 0x40);
623        $b[8] = chr((ord($b[8]) & 0x3F) | 0x80);
624        return implode('-',unpack('H8a/H4b/H4c/H4d/H12e',$b));
625    }
626
627    /**
628     * Installs a transport package.
629     *
630     * @param string The package signature.
631     * @param array $attributes An array of installation attributes.
632     * @return array An array of error messages collected during the process.
633     */
634    public function installPackage($pkg, array $attributes = array ()) {
635        $errors = array ();
636
637        /* instantiate the modX class */
638        if (@ require_once (MODX_CORE_PATH . 'model/modx/modx.class.php')) {
639            $modx = new modX(MODX_CORE_PATH . 'config/');
640            if (!is_object($modx) || !($modx instanceof modX)) {
641                $errors[] = '<p>'.$this->lexicon('modx_err_instantiate').'</p>';
642            } else {
643                /* try to initialize the mgr context */
644                $modx->initialize('mgr');
645                if (!$modx->isInitialized()) {
646                    $errors[] = '<p>'.$this->lexicon('modx_err_instantiate_mgr').'</p>';
647                } else {
648                    $loaded = $modx->loadClass('transport.xPDOTransport', XPDO_CORE_PATH, true, true);
649                    if (!$loaded)
650                        $errors[] = '<p>'.$this->lexicon('transport_class_err_load').'</p>';
651
652                    $packageDirectory = MODX_CORE_PATH . 'packages/';
653                    $packageState = (isset ($attributes[xPDOTransport::PACKAGE_STATE]) ? $attributes[xPDOTransport::PACKAGE_STATE] : xPDOTransport::STATE_PACKED);
654                    $package = xPDOTransport :: retrieve($modx, $packageDirectory . $pkg . '.transport.zip', $packageDirectory, $packageState);
655                    if ($package) {
656                        if (!$package->install($attributes)) {
657                            $errors[] = '<p>'.$this->lexicon('package_err_install',array('package' => $pkg)).'</p>';
658                        } else {
659                            $modx->log(xPDO::LOG_LEVEL_INFO,$this->lexicon('package_installed',array('package' => $pkg)));
660                        }
661                    } else {
662                        $errors[] = '<p>'.$this->lexicon('package_err_nf',array('package' => $pkg)).'</p>';
663                    }
664                }
665            }
666        } else {
667            $errors[] = '<p>'.$this->lexicon('modx_class_err_nf').'</p>';
668        }
669
670        return $errors;
671    }
672
673    /**
674     * Gets the manager login URL.
675     *
676     * @return string The URL of the installed manager context.
677     */
678    public function getManagerLoginUrl() {
679        $url = '';
680
681        /* instantiate the modX class */
682        if (@ require_once (MODX_CORE_PATH . 'model/modx/modx.class.php')) {
683            $modx = new modX(MODX_CORE_PATH . 'config/');
684            if (is_object($modx) && $modx instanceof modX) {
685                /* try to initialize the mgr context */
686                $modx->initialize('mgr');
687                $url = MODX_URL_SCHEME.$modx->getOption('http_host').$modx->getOption('manager_url');
688            }
689        }
690        return $url;
691    }
692
693    /**
694     * Determines the possible install modes.
695     *
696     * @access public
697     * @return integer One of three possible mode indicators:<ul>
698     * <li>0 = new install only</li>
699     * <li>1 = new OR upgrade from older versions of MODX Revolution</li>
700     * <li>2 = new OR upgrade from MODX Evolution</li>
701     * </ul>
702     */
703    public function getInstallMode() {
704        $mode = modInstall::MODE_NEW;
705        if (isset ($_POST['installmode'])) {
706            $mode = intval($_POST['installmode']);
707        } else {
708            global $dbase;
709            if (file_exists(MODX_CORE_PATH . 'config/' . MODX_CONFIG_KEY . '.inc.php')) {
710                /* Include the file so we can test its validity */
711                $included = @ include (MODX_CORE_PATH . 'config/' . MODX_CONFIG_KEY . '.inc.php');
712                $mode = ($included && isset ($dbase)) ? modInstall::MODE_UPGRADE_REVO : modInstall::MODE_NEW;
713            }
714            if (!$mode && file_exists(MODX_INSTALL_PATH . 'manager/includes/config.inc.php')) {
715                $included = @ include (MODX_INSTALL_PATH . 'manager/includes/config.inc.php');
716                $mode = ($included && isset ($dbase)) ? modInstall::MODE_UPGRADE_EVO : modInstall::MODE_NEW;
717            }
718        }
719        return $mode;
720    }
721
722    /**
723     * Creates the database connection for the installation process.
724     *
725     * @access private
726     * @return xPDO The xPDO instance to be used by the installation.
727     */
728    public function _connect($dsn, $user = '', $password = '', $prefix = '', array $options = array()) {
729        if (include_once (MODX_CORE_PATH . 'xpdo/xpdo.class.php')) {
730            $this->xpdo = new xPDO($dsn, $user, $password, array_merge(array(
731                    xPDO::OPT_CACHE_PATH => MODX_CORE_PATH . 'cache/',
732                    xPDO::OPT_TABLE_PREFIX => $prefix,
733                    xPDO::OPT_LOADER_CLASSES => array('modAccessibleObject'),
734                    xPDO::OPT_SETUP => true,
735                ), $options),
736                array(PDO::ATTR_ERRMODE => PDO::ERRMODE_SILENT)
737            );
738            $this->xpdo->setLogTarget(array(
739                'target' => 'FILE',
740                'options' => array(
741                    'filename' => 'install.' . MODX_CONFIG_KEY . '.' . strftime('%Y%m%dT%H%M%S') . '.log'
742                )
743            ));
744            $this->xpdo->setLogLevel(xPDO::LOG_LEVEL_ERROR);
745            return $this->xpdo;
746        } else {
747            return $this->lexicon('xpdo_err_nf', array('path' => MODX_CORE_PATH.'xpdo/xpdo.class.php'));
748        }
749    }
750
751    /**
752     * Instantiate an existing modX configuration.
753     *
754     * @param array &$errors An array in which error messages are collected.
755     * @return modX|null The modX instance, or null if it could not be instantiated.
756     */
757    private function _modx(array & $errors) {
758        $modx = null;
759
760        /* to validate installation, instantiate the modX class and run a few tests */
761        if (include_once (MODX_CORE_PATH . 'model/modx/modx.class.php')) {
762            $modx = new modX(MODX_CORE_PATH . 'config/', array(
763                xPDO::OPT_SETUP => true,
764            ));
765            if (!is_object($modx) || !($modx instanceof modX)) {
766                $errors[] = '<p>'.$this->lexicon('modx_err_instantiate').'</p>';
767            } else {
768                $modx->setLogTarget(array(
769                    'target' => 'FILE',
770                    'options' => array(
771                        'filename' => 'install.' . MODX_CONFIG_KEY . '.' . strftime('%Y%m%dT%H%M%S') . '.log'
772                    )
773                ));
774
775                /* try to initialize the mgr context */
776                $modx->initialize('mgr');
777                if (!$modx->isInitialized()) {
778                    $errors[] = '<p>'.$this->lexicon('modx_err_instantiate_mgr').'</p>';
779                }
780            }
781        } else {
782            $errors[] = '<p>'.$this->lexicon('modx_class_err_nf').'</p>';
783        }
784
785        return $modx;
786    }
787
788    /**
789     * Finds the core directory, if possible. If core cannot be found, loads the
790     * findcore controller.
791     *
792     * @return Returns true if core directory is found.
793     */
794    public function findCore() {
795        $exists = false;
796        if (defined('MODX_CORE_PATH') && file_exists(MODX_CORE_PATH) && is_dir(MODX_CORE_PATH)) {
797            if (file_exists(MODX_CORE_PATH . 'xpdo/xpdo.class.php') && file_exists(MODX_CORE_PATH . 'model/modx/modx.class.php')) {
798                $exists = true;
799            }
800        }
801        if (!$exists) {
802            include(MODX_SETUP_PATH . 'templates/findcore.php');
803            die();
804        }
805        return $exists;
806    }
807
808    /**
809     * Does all the pre-load checks, before setup loads.
810     *
811     * @access public
812     */
813    public function doPreloadChecks() {
814        $this->lexicon->load('preload');
815        $errors= array();
816
817        if (!extension_loaded('pdo')) {
818            $errors[] = $this->lexicon('preload_err_pdo');
819        }
820        if (!file_exists(MODX_CORE_PATH) || !is_dir(MODX_CORE_PATH)) {
821            $errors[] = $this->lexicon('preload_err_core_path');
822        }
823        if (!file_exists(MODX_CORE_PATH . 'cache/') || !is_dir(MODX_CORE_PATH . 'cache/') || !$this->is_writable2(MODX_CORE_PATH . 'cache/')) {
824            $errors[] = $this->lexicon('preload_err_cache',array('path' => MODX_CORE_PATH));
825        }
826
827        if (!empty($errors)) {
828            $this->_fatalError($errors);
829        }
830    }
831
832    /**
833     * Outputs a fatal error message and then dies.
834     *
835     * @access private
836     * @param string/array A string or array of errors
837     */
838    private function _fatalError($errors) {
839        $output = '<html><head><title></title></head><body><h1>'.$this->lexicon('fatal_error').'</h1><ul>';
840        if (is_array($errors)) {
841            foreach ($errors as $error) {
842                $output .= '<li>'.$error.'</li>';
843            }
844        } else {
845            $output .= '<li>'.$errors.'</li>';
846        }
847        $output .= '</ul></body></html>';
848        die($output);
849    }
850
851    /**
852     * Custom is_writable function to test on problematic servers
853     *
854     * @param string $path
855     * @return boolean True if write was successful
856     */
857    public function is_writable2($path) {
858        $written = false;
859        if (!is_string($path)) return false;
860
861        /* if is file get parent dir */
862        if (is_file($path)) { $path = dirname($path) . '/'; }
863
864        /* ensure / at end, translate \ to / for windows */
865        if (substr($path,strlen($path)-1) != '/') { $path .= '/'; }
866        $path = strtr($path,'\\','/');
867
868        /* get test file */
869        $filePath = $path.uniqid().'.cache.php';
870
871        /* attempt to create test file */
872        $fp = @fopen($filePath,'w');
873        if ($fp === false || !file_exists($filePath)) return false;
874
875        /* attempt to write to test file */
876        $written = @fwrite($fp,'<?php echo "test";');
877        if (!$written) { /* if fails try to delete it */
878            @fclose($fp);
879            @unlink($filePath);
880            return false;
881        }
882
883        /* attempt to delete test file */
884        @fclose($fp);
885        $written = @unlink($filePath);
886
887        return $written;
888    }
889
890    /**
891     * Loads the correct database driver for this environment.
892     *
893     * @return boolean True if successful.
894     */
895    public function loadDriver() {
896        $this->loadSettings();
897        $path = dirname(__FILE__).'/drivers/';
898
899        /* db specific driver */
900        $class = 'modInstallDriver_'.strtolower($this->settings->get('database_type','mysql'));
901        $driverPath = $path.strtolower($class.'.class.php');
902        $included = @include_once $driverPath;
903        if ($included) {
904            $this->driver = new $class($this);
905        } else {
906            $this->_fatalError($this->lexicon('driver_class_err_nf',array('path' => $driverPath)));
907        }
908        return $included;
909    }
910}