Skip to content

Instantly share code, notes, and snippets.

@uiii
Created March 30, 2015 19:33
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save uiii/f1877686bca81fe6d668 to your computer and use it in GitHub Desktop.
Save uiii/f1877686bca81fe6d668 to your computer and use it in GitHub Desktop.
ProcessWire 2.5 CLI installer
<?php
/**
* ProcessWire Installer
*
* Because this installer runs before PW2 is installed, it is largely self contained.
* It's a quick-n-simple single purpose script that's designed to run once, and it should be deleted after installation.
* This file self-executes using code found at the bottom of the file, under the Installer class.
*
* Note that it creates this file once installation is completed: /site/assets/installed.php
* If that file exists, the installer will not run. So if you need to re-run this installer for any
* reason, then you'll want to delete that file. This was implemented just in case someone doesn't delete the installer.
*
* ProcessWire 2.x
* Copyright (C) 2014 by Ryan Cramer
* Licensed under GNU/GPL v2, see LICENSE.TXT
*
* http://processwire.com
*
*/
define("PROCESSWIRE_INSTALL", 2);
/**
* class Installer
*
* Self contained class to install ProcessWire 2.x
*
*/
class InstallerCLI {
/**
* Whether or not we force installed files to be copied.
*
* If false, we attempt a faster rename of directories instead.
*
*/
const FORCE_COPY = true;
/**
* Replace existing database tables if already present?
*
*/
const REPLACE_DB = true;
/**
* Minimum required PHP version to install ProcessWire
*
*/
const MIN_REQUIRED_PHP_VERSION = '5.3.8';
/**
* Test mode for installer development, non destructive
*
*/
const TEST_MODE = false;
/**
* File permissions, determined in the dbConfig function
*
* Below are last resort defaults
*
*/
protected $chmodDir = "0777";
protected $chmodFile = "0666";
/**
* Number of errors that occurred during the request
*
*/
protected $numErrors = 0;
/**
* Available color themes
*
*/
protected $colors = array(
'classic',
'warm',
);
protected $optionsDefinition = array(
'config' => array(
'short' => 'c',
'long' => 'config',
'param' => './install.config.php',
'description' => 'Configuration file',
),
'ignoreCompatibilityErrors' => array(
'long' => 'ignore-compatibility-errors',
'description' => 'Continue even if there are compatibility errors',
),
'removeItems' => array(
'long' => 'remove-items',
'param' => 'install-php,install-cli-php,install-dir,unused-profiles',
'description' => 'Items to be removed after installation finished'
),
'help' => array(
'short' => 'h',
'long' => 'help',
'description' => 'Display help',
)
);
/**
* Command-line options
*
*/
protected $options = array();
/**
* Installation configuration
*
*/
protected $config = array(
'profile' => null,
'dbHost' => 'localhost',
'dbName' => null,
'dbUser' => null,
'dbPass' => null,
'dbPort' => 3306,
'dbCharset' => 'utf8',
'dbEngine' => 'MyISAM',
'chmodDir' => 755,
'chmodFile' => 644,
'timezone' => 'America/New_York',
'httpHosts' => null,
'admin_name' => 'processwire',
'username' => 'admin',
'userpass' => '',
'useremail' => '',
'colors' => 'classic',
);
/**
* Execution controller
*
*/
public function execute($argv) {
if(self::TEST_MODE) {
error_reporting(E_ALL | E_STRICT);
ini_set('display_errors', 1);
}
$this->h("ProcessWire 2.5 Installation", '=');
$this->parseOptions();
if ($this->options['help']) {
$this->printHelp();
}
$configFile = $this->options['config'];
if (is_file($configFile)) {
$this->ok(sprintf("Using configuration file '%s'", $configFile));
$config = require($configFile);
foreach($config as $name => $value) {
if (array_key_exists($name, $this->config)) {
$this->config[$name] = $value;
}
}
} else {
$this->err("No configuration file found");
$this->printHelp();
$this->_exit(1);
}
$this->initProfile();
$this->compatibilityCheck();
$this->dbSaveConfig();
require("./index.php");
$this->adminAccountSave($wire);
}
/**
* Parse command-line options
*
*/
protected function parseOptions() {
$shortOptions = array();
$longOptions = array();
$optionsMap = array();
$optionValues = array();
foreach($this->optionsDefinition as $option => $definition) {
$param = array_key_exists('param', $definition) ? ':' : '';
if (isset($definition['short'])) {
$shortOptions[] = $definition['short'] . $param;
$optionsMap[$definition['short']] = $option;
}
if (isset($definition['long'])) {
$longOptions[] = $definition['long'] . $param;
$optionsMap[$definition['long']] = $option;
}
$optionValues[$option] = $param ? $definition['param'] : false;
}
$cmdOptionValues = getopt(implode('', $shortOptions), $longOptions);
foreach($cmdOptionValues as $cmdOption => $value) {
$option = $optionsMap[$cmdOption];
$optionDefinition = $this->optionsDefinition[$option];
$optionValues[$option] = isset($optionDefinition['param']) && $optionDefinition['param']
? $value
: true;
}
$this->options = $optionValues;
}
/**
* Print install script help
*
*/
protected function printHelp() {
$this->h('Usage');
$lines = array();
$firstColumnLength = 0;
foreach($this->optionsDefinition as $option => $definition) {
$line = array('', '');
if (isset($definition['short'])) {
$line[0] .= sprintf('-%s|', $definition['short']);
}
if (isset($definition['long'])) {
$line[0] .= sprintf('--%s', $definition['long']);
}
if (isset($definition['param']) && $definition['param']) {
$line[0] .= ' <ARG>';
}
$line[1] = $definition['description'];
if (array_key_exists('param', $definition)) {
$line[1] .= sprintf(' (default: %s)', $definition['param']);
}
$firstColumnLength = max(strlen($line[0]), $firstColumnLength);
$lines[] = $line;
}
$firstColumnLength += 5;
echo "\n php install-cli.php [options]\n";
foreach($lines as $line) {
echo sprintf("\n %s%s", str_pad($line[0], $firstColumnLength), $line[1]);
}
echo "\n";
$this->_exit(1);
}
/**
* Check if the given function $name exists and report OK or fail with $label
*
*/
protected function checkFunction($name, $label) {
if(function_exists($name)) $this->ok("$label");
else $this->err("Fail: $label");
}
/**
* Find all profile directories (site-*) in the current dir and return info array for each
*
* @return array
*
*/
protected function findProfiles() {
$profiles = array(
'site-beginner' => null,
'site-default' => null, // preferred starting order
'site-languages' => null,
'site-blank' => null
);
$dirTests = array(
'install',
'templates',
'assets',
);
$fileTests = array(
'config.php',
'templates/admin.php',
'install/install.sql',
);
foreach(new DirectoryIterator(dirname(__FILE__)) as $dir) {
if($dir->isDot() || !$dir->isDir()) continue;
$name = $dir->getBasename();
$path = rtrim($dir->getPathname(), '/') . '/';
if(strpos($name, 'site-') !== 0) continue;
$passed = true;
foreach($dirTests as $test) if(!is_dir($path . $test)) $passed = false;
foreach($fileTests as $test) if(!file_exists($path . $test)) $passed = false;
if(!$passed) continue;
$profile = array('name' => str_replace('site-', '', $name));
$infoFile = $path . 'install/info.php';
if(file_exists($infoFile)) {
include($infoFile);
if(isset($info) && is_array($info)) {
$profile = array_merge($profile, $info);
}
}
$profiles[$name] = $profile;
}
// remove any preferred starting order profiles that weren't present
foreach($profiles as $name => $profile) {
if(is_null($profile)) unset($profiles[$name]);
}
return $profiles;
}
protected function selectProfile() {
$options = '';
$out = '';
$profiles = $this->findProfiles();
if(!count($profiles)) $this->err("No profiles found!");
foreach($profiles as $name => $profile) {
$title = empty($profile['title']) ? ucfirst($profile['name']) : $profile['title'];
//$selected = $name == 'site-default' ? " selected='selected'" : "";
$options .= "$name\t\t$title\n";
}
$this->p("A site installation profile is a ready-to-use and modify site for ProcessWire. If you are just getting started with ProcessWire, we recommend choosing the 'Default' site profile. If you already know what you are doing, you might prefer the 'Blank' site profile.");
$this->p($options);
}
/**
* Step 1a: Determine profile
*
*/
protected function initProfile() {
$this->h('Site Installation Profile');
if(is_file("./site/install/install.sql")) {
$this->ok("Found installation profile in /site/install/");
} else if(is_dir("./site/")) {
$this->ok("Found /site/ -- already installed? ");
} else if(isset($this->config['profile'])) {
$profiles = $this->findProfiles();
$profile = preg_replace('/[^-a-zA-Z0-9_]/', '', $this->config['profile']);
if(empty($profile) || !isset($profiles[$profile]) || !is_dir(dirname(__FILE__) . "/$profile")) {
$this->err(sprintf("Profile '%s' not found", $this->config['profile']));
$this->selectProfile();
$this->_exit(1);
return;
}
// $info = $profiles[$profile];
// $this->h(empty($info['title']) ? ucfirst($info['name']) : $info['title']);
if(@rename("./$profile", "./site")) {
$this->ok("Renamed /$profile => /site");
} else {
$this->err("File system is not writable by this installer. Before continuing, please rename '/$profile' to '/site'");
$this->_exit(1);
return;
}
} else {
$this->err("No profile selected");
$this->selectProfile();
$this->_exit(1);
return;
}
}
/**
* Step 1b: Check for ProcessWire compatibility
*
*/
protected function compatibilityCheck() {
$this->h("Compatibility Check");
if(version_compare(PHP_VERSION, self::MIN_REQUIRED_PHP_VERSION) >= 0) {
$this->ok("PHP version " . PHP_VERSION);
} else {
$this->err("ProcessWire requires PHP version " . self::MIN_REQUIRED_PHP_VERSION . " or newer. You are running PHP " . PHP_VERSION);
}
if(extension_loaded('pdo_mysql')) {
$this->ok("PDO (mysql) database");
} else {
$this->err("PDO (pdo_mysql) is required (for MySQL database)");
}
if(self::TEST_MODE) {
$this->err("Example error message for test mode");
$this->warn("Example warning message for test mode");
}
$this->checkFunction("filter_var", "Filter functions (filter_var)");
$this->checkFunction("mysqli_connect", "MySQLi (not required by core, but may be required by some 3rd party modules)");
$this->checkFunction("imagecreatetruecolor", "GD 2.0 or newer");
$this->checkFunction("json_encode", "JSON support");
$this->checkFunction("preg_match", "PCRE support");
$this->checkFunction("ctype_digit", "CTYPE support");
$this->checkFunction("iconv", "ICONV support");
$this->checkFunction("session_save_path", "SESSION support");
$this->checkFunction("hash", "HASH support");
$this->checkFunction("spl_autoload_register", "SPL support");
if(function_exists('apache_get_modules')) {
if(in_array('mod_rewrite', apache_get_modules())) $this->ok("Found Apache module: mod_rewrite");
else $this->err("Apache mod_rewrite does not appear to be installed and is required by ProcessWire.");
} else {
// apache_get_modules doesn't work on a cgi installation.
// check for environment var set in htaccess file, as submitted by jmarjie.
$mod_rewrite = getenv('HTTP_MOD_REWRITE') == 'On' || getenv('REDIRECT_HTTP_MOD_REWRITE') == 'On' ? true : false;
if($mod_rewrite) {
$this->ok("Found Apache module (cgi): mod_rewrite");
} else {
$this->warn("Unable to determine if Apache mod_rewrite (required by ProcessWire) is installed.");
--$this->numErrors;
}
}
if(class_exists('ZipArchive')) {
$this->ok("ZipArchive support");
} else {
$this->warn("ZipArchive support was not found. This is recommended, but not required to complete installation.");
}
$dirs = array(
// directory => required?
'./site/assets/' => true,
'./site/modules/' => false,
);
foreach($dirs as $dir => $required) {
$d = ltrim($dir, '.');
if(!file_exists($dir)) {
$this->err("Directory $d does not exist! Please create this and make it writable before continuing.");
} else if(is_writable($dir)) {
$this->ok("$d is writable");
} else if($required) {
$this->err("Directory $d must be writable. Please adjust the server permissions before continuing.");
} else {
$this->warn("We recommend that directory $d be made writable before continuing.");
}
}
if(is_writable("./site/config.php")) $this->ok("/site/config.php is writable");
else $this->err("/site/config.php must be writable. Please adjust the server permissions before continuing.");
if(!is_file("./.htaccess") || !is_readable("./.htaccess")) {
if(@rename("./htaccess.txt", "./.htaccess")) $this->ok("Installed .htaccess");
else $this->err("/.htaccess doesn't exist. Before continuing, you should rename the included htaccess.txt file to be .htaccess (with the period in front of it, and no '.txt' at the end).");
} else if(!strpos(file_get_contents("./.htaccess"), "PROCESSWIRE")) {
$this->err("/.htaccess file exists, but is not for ProcessWire. Please overwrite or combine it with the provided /htaccess.txt file (i.e. rename /htaccess.txt to /.htaccess, with the period in front).");
} else {
$this->ok(".htaccess looks good");
}
if($this->numErrors) {
if ($this->options['ignoreCompatibilityErrors']) {
$this->info("Ignoring compatibility errors");
} else {
$this->p("One or more errors were found above. We recommend you correct these issues before proceeding or contact ProcessWire support (http://processwire.com/talk/) if you have questions or think the error is incorrect. But if you want to proceed anyway run the installation with --ignore-compatibility-errors option");
$this->_exit(1);
}
}
}
/**
* Step 3: Save database configuration, then begin profile import
*
*/
protected function dbSaveConfig() {
$this->h('Configuration');
$values = array();
// file permissions
$fields = array('chmodDir', 'chmodFile');
foreach($fields as $field) {
$value = (int) $this->config[$field];
if(strlen("$value") !== 3) $this->err("Value for '$field' is invalid");
else $this->$field = "0$value";
$values[$field] = $value;
}
$timezone = (int) $this->config['timezone'];
$timezones = $this->timezones();
if(isset($timezones[$timezone])) {
$value = $timezones[$timezone];
if(strpos($value, '|')) list($label, $value) = explode('|', $value);
$values['timezone'] = $value;
} else {
$values['timezone'] = 'America/New_York';
}
$values['httpHosts'] = array();
$httpHosts = trim($this->config['httpHosts']);
if(strlen($httpHosts)) {
$httpHosts = str_replace(array("'", '"'), '', $httpHosts);
$httpHosts = explode("\n", $httpHosts);
foreach($httpHosts as $key => $host) {
$host = strtolower(trim(filter_var($host, FILTER_SANITIZE_URL)));
$httpHosts[$key] = $host;
}
$values['httpHosts'] = $httpHosts;
}
// db configuration
$fields = array('dbUser', 'dbName', 'dbPass', 'dbHost', 'dbPort', 'dbEngine', 'dbCharset');
foreach($fields as $field) {
$value = get_magic_quotes_gpc() ? stripslashes($this->config[$field]) : $this->config[$field];
$value = substr($value, 0, 255);
if(strpos($value, "'") !== false) $value = str_replace("'", "\\" . "'", $value); // allow for single quotes (i.e. dbPass)
$values[$field] = trim($value);
}
$values['dbCharset'] = strtolower($values['dbCharset']);
$values['dbEngine'] = ($values['dbEngine'] === 'InnoDB' ? 'InnoDB' : 'MyISAM');
if(!ctype_alnum($values['dbCharset'])) $values['dbCharset'] = 'utf8';
if(!$values['dbUser'] || !$values['dbName'] || !$values['dbPort']) {
$this->err("Missing database configuration fields");
} else {
error_reporting(0);
$dsn = "mysql:dbname=$values[dbName];host=$values[dbHost];port=$values[dbPort]";
$driver_options = array(
PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES 'UTF8'",
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION
);
try {
$database = new PDO($dsn, $values['dbUser'], $values['dbPass'], $driver_options);
} catch(Exception $e) {
$this->err("Database connection information did not work.");
$this->err($e->getMessage());
}
}
if($this->numErrors) {
$this->_exit(1);
return;
}
$this->ok("Database connection successful to " . htmlspecialchars($values['dbName']));
$options = array('dbCharset' => strtolower($values['dbCharset']), 'dbEngine' => $values['dbEngine']);
if($this->dbSaveConfigFile($values)) $this->profileImport($database, $options);
else $this->_exit(1);
}
/**
* Save configuration to /site/config.php
*
*/
protected function dbSaveConfigFile(array $values) {
if(self::TEST_MODE) return true;
$salt = md5(mt_rand() . microtime(true));
$cfg = "\n/**" .
"\n * Installer: Database Configuration" .
"\n * " .
"\n */" .
"\n\$config->dbHost = '$values[dbHost]';" .
"\n\$config->dbName = '$values[dbName]';" .
"\n\$config->dbUser = '$values[dbUser]';" .
"\n\$config->dbPass = '$values[dbPass]';" .
"\n\$config->dbPort = '$values[dbPort]';";
if(!empty($values['dbCharset']) && strtolower($values['dbCharset']) != 'utf8') $cfg .= "\n\$config->dbCharset = '$values[dbCharset]';";
if(!empty($values['dbEngine']) && $values['dbEngine'] == 'InnoDB') $cfg .= "\n\$config->dbEngine = 'InnoDB';";
$cfg .=
"\n" .
"\n/**" .
"\n * Installer: User Authentication Salt " .
"\n * " .
"\n * Must be retained if you migrate your site from one server to another" .
"\n * " .
"\n */" .
"\n\$config->userAuthSalt = '$salt'; " .
"\n" .
"\n/**" .
"\n * Installer: File Permission Configuration" .
"\n * " .
"\n */" .
"\n\$config->chmodDir = '0$values[chmodDir]'; // permission for directories created by ProcessWire" .
"\n\$config->chmodFile = '0$values[chmodFile]'; // permission for files created by ProcessWire " .
"\n" .
"\n/**" .
"\n * Installer: Time zone setting" .
"\n * " .
"\n */" .
"\n\$config->timezone = '$values[timezone]';" .
"\n\n";
if(!empty($values['httpHosts'])) {
$cfg .= "" .
"\n/**" .
"\n * Installer: HTTP Hosts Whitelist" .
"\n * " .
"\n */" .
"\n\$config->httpHosts = array(";
foreach($values['httpHosts'] as $host) $cfg .= "'$host', ";
$cfg = rtrim($cfg, ", ") . ");\n\n";
}
if(($fp = fopen("./site/config.php", "a")) && fwrite($fp, $cfg)) {
fclose($fp);
$this->ok("Saved configuration to ./site/config.php");
return true;
} else {
$this->err("Error saving configuration to ./site/config.php. Please make sure it is writable.");
return false;
}
}
/**
* Step 3b: Import profile
*
*/
protected function profileImport($database) {
$this->h('Profile import');
if(self::TEST_MODE) {
$this->ok("TEST MODE: Skipping profile import");
return;
}
$profile = "./site/install/";
if(!is_file("{$profile}install.sql")) {
$this->err("No installation profile found in {$profile}");
$this->_exit(1);
}
// checks to see if the database exists using an arbitrary query (could just as easily be something else)
try {
$query = $database->prepare("SHOW COLUMNS FROM pages");
$result = $query->execute();
} catch(Exception $e) {
$result = false;
}
if(self::REPLACE_DB || !$result || $query->rowCount() == 0) {
$this->profileImportSQL($database, "./wire/core/install.sql", $profile . "install.sql");
if(is_dir($profile . "files")) $this->profileImportFiles($profile);
else $this->mkdir("./site/assets/files/");
$this->mkdir("./site/assets/cache/");
$this->mkdir("./site/assets/logs/");
$this->mkdir("./site/assets/sessions/");
} else {
$this->ok("A profile is already imported, skipping...");
}
// copy default site modules /site-default/modules/ to /site/modules/
$dir = "./site/modules/";
$defaultDir = "./site-default/modules/";
if(!is_dir($dir)) $this->mkdir($dir);
if(is_dir($defaultDir)) {
if(is_writable($dir)) {
$result = $this->copyRecursive($defaultDir, $dir, false);
if($result) {
$this->ok("Imported: $defaultDir => $dir");
} else {
$this->warn("Error Importing: $defaultDir => $dir");
}
} else {
$this->warn("$dir is not writable, unable to install default site modules (recommended, but not required)");
}
} else {
// they are installing site-default already
}
}
/**
* Import files to profile
*
*/
protected function profileImportFiles($fromPath) {
if(self::TEST_MODE) {
$this->ok("TEST MODE: Skipping file import - $fromPath");
return;
}
$dir = new DirectoryIterator($fromPath);
foreach($dir as $file) {
if($file->isDot()) continue;
if(!$file->isDir()) continue;
$dirname = $file->getFilename();
$pathname = $file->getPathname();
if(is_writable($pathname) && self::FORCE_COPY == false) {
// if it's writable, then we know all the files are likely writable too, so we can just rename it
$result = rename($pathname, "./site/assets/$dirname/");
} else {
// if it's not writable, then we will make a copy instead, and that copy should be writable by the server
$result = $this->copyRecursive($pathname, "./site/assets/$dirname/");
}
if($result) $this->ok("Imported: $pathname => ./site/assets/$dirname/");
else $this->err("Error Importing: $pathname => ./site/assets/$dirname/");
}
}
/**
* Import profile SQL dump
*
*/
protected function profileImportSQL($database, $file1, $file2, array $options = array()) {
$defaults = array(
'dbEngine' => 'MyISAM',
'dbCharset' => 'utf8',
);
$options = array_merge($defaults, $options);
if(self::TEST_MODE) return;
$restoreOptions = array();
$replace = array();
if($options['dbEngine'] != 'MyISAM') {
$replace['ENGINE=MyISAM'] = "ENGINE=$options[dbEngine]";
$this->warn("Engine changed to '$options[dbEngine]', please keep an eye out for issues.");
}
if($options['dbCharset'] != 'utf8') {
$replace['CHARSET=utf8'] = "CHARSET=$options[dbCharset]";
$this->warn("Character set has been changed to '$options[dbCharset]', please keep an eye out for issues.");
}
if(count($replace)) $restoreOptions['findReplaceCreateTable'] = $replace;
require("./wire/core/WireDatabaseBackup.php");
$backup = new WireDatabaseBackup();
$backup->setDatabase($database);
if($backup->restoreMerge($file1, $file2, $restoreOptions)) {
$this->ok("Imported database file: $file1");
$this->ok("Imported database file: $file2");
} else {
foreach($backup->errors() as $error) $this->err($error);
}
}
protected function getRemoveableItems($wire, $removeNow = false) {
$root = dirname(__FILE__) . '/';
$itemsToRemove = explode(',', $this->options['removeItems']);
$items = array(
'install-php' => array(
'label' => 'Remove installer (install.php) when finished',
'file' => "/install.php",
'path' => $root . "install.php",
),
/*'install-cli-php' => array(
'label' => 'Remove CLI installer (install-cli.php) when finished',
'file' => "/install-cli.php",
'path' => $root . "install-cli.php",
),*/
'install-dir' => array(
'label' => 'Remove installer site profile assets (/site/install/)',
'path' => $root . "site/install/",
'file' => '/site/install/',
),
);
foreach($this->findProfiles() as $name => $profile) {
$title = empty($profile['title']) ? $name : $profile['title'];
$items[$name] = array(
'label' => "Remove unused $title site profile (/$name/)",
'path' => $root . "$name/",
'file' => "/$name/",
);
if (in_array('unused-profiles', $itemsToRemove)) {
$itemsToRemove[] = $name;
}
}
if ($removeNow) {
foreach($items as $name => $item) {
if(! file_exists($item['path'])) continue;
if(! in_array($name, $itemsToRemove)) continue;
if(is_dir($item['path'])) {
$success = wireRmdir($item['path'], true);
} else if(is_file($item['path'])) {
$success = @unlink($item['path']);
} else {
$success = true;
}
if($success) {
$this->ok("Completed: " . $item['label']);
} else {
$this->err("Unable to remove $item[file] - please remove manually, as it is no longer needed");
}
}
}
return $items;
}
/**
* Save submitted admin account form
*
*/
protected function adminAccountSave($wire) {
$sanitizer = $wire->sanitizer;
if(! $this->config['username'] || ! $this->config['userpass']) $this->err("Missing account information");
if(strlen($this->config['userpass']) < 6) $this->err("Password must be at least 6 characters long");
$username = $sanitizer->pageName($this->config['username']);
if($username != $this->config['username']) $this->err("Username must be only a-z 0-9");
if(strlen($username) < 2) $this->err("Username must be at least 2 characters long");
$adminName = $sanitizer->pageName($this->config['admin_name']);
if($adminName != $this->config['admin_name']) $this->err("Admin login URL must be only a-z 0-9");
if($adminName == 'wire' || $adminName == 'site') $this->err("Admin name may not be 'wire' or 'site'");
if(strlen($adminName) < 2) $this->err("Admin login URL must be at least 2 characters long");
$email = strtolower($sanitizer->email($this->config['useremail']));
if($email != strtolower($this->config['useremail'])) $this->err("Email address did not validate");
if($this->numErrors) $this->_exit(1);
$superuserRole = $wire->roles->get("name=superuser");
$user = $wire->users->get($wire->config->superUserPageID);
if(!$user->id) {
$user = new User();
$user->id = $wire->config->superUserPageID;
}
$user->name = $username;
$user->pass = $this->config['userpass'];
$user->email = $email;
if(!$user->roles->has("superuser")) $user->roles->add($superuserRole);
$admin = $wire->pages->get($wire->config->adminRootPageID);
$admin->of(false);
$admin->name = $adminName;
try {
if(self::TEST_MODE) {
$this->ok("TEST MODE: skipped user creation");
} else {
$wire->users->save($user);
$wire->pages->save($admin);
}
} catch(Exception $e) {
$this->err($e->getMessage());
return $this->adminAccount($wire);
}
$adminName = htmlentities($adminName, ENT_QUOTES, "UTF-8");
$this->h("Admin Account Saved");
$this->ok("User account saved: {$user->name}");
$colors = $wire->sanitizer->pageName($this->config['colors']);
if(!in_array($colors, $this->colors)) $colors = reset($this->colors);
$theme = $wire->modules->getInstall('AdminThemeDefault');
$configData = $wire->modules->getModuleConfigData('AdminThemeDefault');
$configData['colors'] = $colors;
$wire->modules->saveModuleConfigData('AdminThemeDefault', $configData);
$this->ok("Saved admin color set '$colors' - you will see this when you login.");
$this->h("Complete & Secure Your Installation");
$this->getRemoveableItems($wire, true);
$this->warn("Depending on the environment, you may want to make /site/config.php non-writable, for security.");
$this->ok("Note that future runtime errors are logged to /site/assets/logs/errors.txt (not web accessible).");
$this->ok("For more configuration options see /wire/config.php.");
if(is_writable("./site/modules/")) wireChmod("./site/modules/", true);
$this->h("Use The Site!");
$this->ok("Your admin URL is /$adminName/");
$this->p("If you'd like, you may change this later by editing the admin page and changing the name.");
// set a define that indicates installation is completed so that this script no longer runs
if(!self::TEST_MODE) {
file_put_contents("./site/assets/installed.php", "<?php // The existence of this file prevents the installer from running. Don't delete it unless you want to re-run the install or you have deleted ./install.php.");
}
}
/******************************************************************************************************************
* OUTPUT FUNCTIONS
*
*/
/**
* Report and log an error
*
*/
protected function err($str) {
$this->report('error', $str);
$this->numErrors++;
return false;
}
/**
* Action/warning
*
*/
protected function warn($str) {
$this->report('warn', $str);
$this->numErrors++;
return false;
}
/**
* Print info
*
*/
protected function info($str) {
$this->report('info', $str);
return true;
}
/**
* Report success
*
*/
protected function ok($str) {
$this->report('ok', $str);
return true;
}
protected function report($type, $str) {
if ($this->firstReport) {
echo "\n";
}
echo sprintf("[%s] %s\n", strtoupper($type), $str);
$this->firstReport = false;
}
/**
* Output a headline
*
*/
protected function h($label, $underline = '-') {
echo "\n$label\n" . str_repeat($underline, strlen($label)) . "\n";
$this->firstReport = true;
}
/**
* Output a paragraph
*
*/
protected function p($text, $class = '') {
echo "\n$text\n";
$this->firstReport = true;
}
protected function _exit($code) {
echo "\n";
exit(1);
}
/******************************************************************************************************************
* FILE FUNCTIONS
*
*/
/**
* Create a directory and assign permission
*
*/
protected function mkdir($path, $showNote = true) {
if(self::TEST_MODE) return;
if(is_dir($path) || mkdir($path)) {
chmod($path, octdec($this->chmodDir));
if($showNote) $this->ok("Created directory: $path");
return true;
} else {
if($showNote) $this->err("Error creating directory: $path");
return false;
}
}
/**
* Copy directories recursively
*
*/
protected function copyRecursive($src, $dst, $overwrite = true) {
if(self::TEST_MODE) return;
if(substr($src, -1) != '/') $src .= '/';
if(substr($dst, -1) != '/') $dst .= '/';
$dir = opendir($src);
$this->mkdir($dst, false);
while(false !== ($file = readdir($dir))) {
if($file == '.' || $file == '..') continue;
if(is_dir($src . $file)) {
$this->copyRecursive($src . $file, $dst . $file);
} else {
if(!$overwrite && file_exists($dst . $file)) {
// don't replace existing files when $overwrite == false;
} else {
copy($src . $file, $dst . $file);
chmod($dst . $file, octdec($this->chmodFile));
}
}
}
closedir($dir);
return true;
}
protected function timezones() {
$timezones = timezone_identifiers_list();
$extras = array(
'US Eastern|America/New_York',
'US Central|America/Chicago',
'US Mountain|America/Denver',
'US Mountain (no DST)|America/Phoenix',
'US Pacific|America/Los_Angeles',
'US Alaska|America/Anchorage',
'US Hawaii|America/Adak',
'US Hawaii (no DST)|Pacific/Honolulu',
);
foreach($extras as $t) $timezones[] = $t;
return $timezones;
}
}
/****************************************************************************************************/
if(!InstallerCLI::TEST_MODE && is_file("./site/assets/installed.php")) die("This installer has already run. Please delete it.");
error_reporting(E_ALL | E_STRICT);
$installer = new InstallerCLI();
$installer->execute($argv);
<?php
return array(
'profile' => 'site-blank',
'dbHost' => 'localhost',
'dbName' => '',
'dbUser' => '',
'dbPass' => '',
'dbPort' => 3306,
//'dbCharset' => 'utf8',
//'dbEngine' => 'MyISAM',
//'chmodDir' => 777,
//'chmodFile' => 666,
'timezone' => 'Europe/Prague',
'httpHosts' => 'localhost',
'admin_name' => 'processwire',
'username' => '',
'userpass' => '',
'useremail' => '',
);
@uiii
Copy link
Author

uiii commented Mar 30, 2015

Copy the files to the PW root directory and rename the install.config.php.example to install.config.php and fill the options. Then run php install-cli.php. For help run php install-cli.php --help

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment