PageRenderTime 8ms CodeModel.GetById 20ms app.highlight 33ms RepoModel.GetById 56ms app.codeStats 0ms

/setup/includes/modinstall.class.php

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