diff --git a/PHPCI/Command/InstallCommand.php b/PHPCI/Command/InstallCommand.php index 7f9daf6d..e6432103 100644 --- a/PHPCI/Command/InstallCommand.php +++ b/PHPCI/Command/InstallCommand.php @@ -1,28 +1,34 @@ -* @package PHPCI -* @subpackage Console -*/ + * Install console command - Installs PHPCI. + * @author Dan Cryer + * @package PHPCI + * @subpackage Console + */ class InstallCommand extends Command { protected function configure() @@ -33,137 +39,235 @@ class InstallCommand extends Command } /** - * Installs PHPCI - Can be run more than once as long as you ^C instead of entering an email address. - */ + * Installs PHPCI - Can be run more than once as long as you ^C instead of entering an email address. + */ protected function execute(InputInterface $input, OutputInterface $output) { - // Gather initial data from the user: + $this->verifyNotInstalled($output); + + $output->writeln(''); + $output->writeln('******************'); + $output->writeln(' Welcome to PHPCI'); + $output->writeln('******************'); + $output->writeln(''); + + $this->checkRequirements($output); + + $output->writeln('Please answer the following questions:'); + $output->writeln('-------------------------------------'); + $output->writeln(''); + + + /** + * @var \Symfony\Component\Console\Helper\DialogHelper + */ + $dialog = $this->getHelperSet()->get('dialog'); + + // ---- + // Get MySQL connection information and verify that it works: + // ---- + $connectionVerified = false; + + while (!$connectionVerified) { + $db = array(); + $db['servers']['read'] = $dialog->ask($output, 'Please enter your MySQL host [localhost]: ', 'localhost'); + $db['servers']['write'] = $db['servers']['read']; + $db['name'] = $dialog->ask($output, 'Please enter your database name [phpci]: ', 'phpci'); + $db['username'] = $dialog->ask($output, 'Please enter your database username [phpci]: ', 'phpci'); + $db['password'] = $dialog->askHiddenResponse($output, 'Please enter your database password: '); + + $connectionVerified = $this->verifyDatabaseDetails($db, $output); + } + + $output->writeln(''); + + // ---- + // Get basic installation details (URL, etc) + // ---- + $conf = array(); - $conf['b8']['database']['servers']['read'] = $this->ask('Enter your MySQL host: '); - $conf['b8']['database']['servers']['write'] = $conf['b8']['database']['servers']['read']; - $conf['b8']['database']['name'] = $this->ask('Enter the database name PHPCI should use: '); - $conf['b8']['database']['username'] = $this->ask('Enter your MySQL username: '); - $conf['b8']['database']['password'] = $this->ask('Enter your MySQL password: ', true); - $ask = 'Your PHPCI URL (without trailing slash): '; - $conf['phpci']['url'] = $this->ask($ask, false, array(FILTER_VALIDATE_URL,"/[^\/]$/i")); - $conf['phpci']['github']['id'] = $this->ask('(Optional) Github Application ID: ', true); - $conf['phpci']['github']['secret'] = $this->ask('(Optional) Github Application Secret: ', true); + $conf['b8']['database'] = $db; + $conf['phpci']['url'] = $dialog->askAndValidate( + $output, + 'Your PHPCI URL (without trailing slash): ', + function ($answer) { + if (!filter_var($answer, FILTER_VALIDATE_URL)) { + throw new Exception('Must be a valid URL'); + } - $conf['phpci']['email_settings']['smtp_address'] = $this->ask('(Optional) Smtp server address: ', true); - $conf['phpci']['email_settings']['smtp_port'] = $this->ask('(Optional) Smtp port: ', true); - $conf['phpci']['email_settings']['smtp_encryption'] = $this->ask('(Optional) Smtp encryption: ', true); - $conf['phpci']['email_settings']['smtp_username'] = $this->ask('(Optional) Smtp Username: ', true); - $conf['phpci']['email_settings']['smtp_password'] = $this->ask('(Optional) Smtp Password: ', true); - $conf['phpci']['email_settings']['from_address'] = $this->ask('(Optional) Email address to send from: ', true); + return $answer; + }, + false + ); - $ask = '(Optional) Default address to email notifications to: '; - $conf['phpci']['email_settings']['default_mailto_address'] = $this->ask($ask, true); + $this->writeConfigFile($conf); + $this->setupDatabase($output); + $this->createAdminUser($output, $dialog); + } - $dbUser = $conf['b8']['database']['username']; - $dbPass = $conf['b8']['database']['password']; - $dbHost = $conf['b8']['database']['servers']['write']; - $dbName = $conf['b8']['database']['name']; + /** + * Check PHP version, required modules and for disabled functions. + * @param OutputInterface $output + */ + protected function checkRequirements(OutputInterface $output) + { + $output->write('Checking requirements...'); + $errors = false; - // Create the database if it doesn't exist: - $cmd = 'mysql -u' . $dbUser . (!empty($dbPass) ? ' -p' . $dbPass : '') . ' -h' . $dbHost . - ' -e "CREATE DATABASE IF NOT EXISTS ' . $dbName . '"'; + // Check PHP version: + if (!(version_compare(PHP_VERSION, '5.3.3') >= 0)) { + $output->writeln(''); + $output->writeln('PHPCI requires at least PHP 5.3.3 to function.'); + $errors = true; + } - shell_exec($cmd); + // Check required extensions are present: + $requiredExtensions = array('PDO', 'pdo_mysql', 'mcrypt'); + foreach ($requiredExtensions as $extension) { + if (!extension_loaded($extension)) { + $output->writeln(''); + $output->writeln(''.$extension.' extension must be installed.'); + $errors = true; + } + } + + // Check required functions are callable: + $requiredFunctions = array('exec', 'shell_exec'); + + foreach ($requiredFunctions as $function) { + if (!function_exists($function)) { + $output->writeln(''); + $output->writeln('PHPCI needs to be able to call the '.$function.'() function. Is it disabled in php.ini?'); + $errors = true; + } + } + + if (!function_exists('password_hash')) { + $output->writeln(''); + $output->writeln('PHPCI requires the password_hash() function available in PHP 5.4, or the password_compat library by ircmaxell.'); + $errors = true; + } + + if ($errors) { + throw new Exception('PHPCI cannot be installed, as not all requirements are met. Please review the errors above before continuing.'); + } + + $output->writeln(' OK'); + $output->writeln(''); + } + + /** + * Try and connect to MySQL using the details provided. + * @param array $db + * @param OutputInterface $output + * @return bool + */ + protected function verifyDatabaseDetails(array $db, OutputInterface $output) + { + try { + $pdo = new PDO( + 'mysql:host='.$db['servers']['write'].';dbname='.$db['name'], + $db['username'], + $db['password'], + array( + \PDO::ATTR_PERSISTENT => false, + \PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION, + \PDO::ATTR_TIMEOUT => 2, + \PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES \'UTF8\'', + ) + ); + + return true; + + } catch (Exception $ex) { + $output->writeln('PHPCI could not connect to MySQL with the details provided. Please try again.'); + $output->writeln('' . $ex->getMessage() . ''); + } + + return false; + } + + /** + * Write the PHPCI config.yml file. + * @param array $config + */ + protected function writeConfigFile(array $config) + { $dumper = new \Symfony\Component\Yaml\Dumper(); - $yaml = $dumper->dump($conf); + $yaml = $dumper->dump($config); file_put_contents(PHPCI_DIR . 'PHPCI/config.yml', $yaml); + } + protected function setupDatabase(OutputInterface $output) + { + $output->write('Setting up your database... '); + + // Load PHPCI's bootstrap file: require(PHPCI_DIR . 'bootstrap.php'); - // Update the database: - $gen = new \b8\Database\Generator(\b8\Database::getConnection(), 'PHPCI', './PHPCI/Model/Base/'); - $gen->generate(); - - // Try to create a user account: - $adminEmail = $this->ask('Enter your email address (leave blank if updating): ', true, FILTER_VALIDATE_EMAIL); - - if (empty($adminEmail)) { - return; + try { + // Set up the database, based on table data from the models: + $gen = new Database\Generator(Database::getConnection(), 'PHPCI', './PHPCI/Model/Base/'); + $gen->generate(); + } catch (Exception $ex) { + $output->writeln(''); + $output->writeln('PHPCI failed to set up the database.'); + $output->writeln('' . $ex->getMessage() . ''); + die; } - $adminPass = $this->ask('Enter your desired admin password: '); - $adminName = $this->ask('Enter your name: '); + + $output->writeln('OK'); + } + + protected function createAdminUser(OutputInterface $output, DialogHelper $dialog) + { + // Try to create a user account: + $adminEmail = $dialog->askAndValidate( + $output, + 'Your email address: ', + function ($answer) { + if (!filter_var($answer, FILTER_VALIDATE_EMAIL)) { + throw new Exception('Must be a valid email address.'); + } + + return $answer; + }, + false + ); + + $adminPass = $dialog->askHiddenResponse($output, 'Enter your desired admin password: '); + $adminName = $dialog->ask($output, 'Enter your name: '); try { - $user = new \PHPCI\Model\User(); + $user = new User(); $user->setEmail($adminEmail); $user->setName($adminName); $user->setIsAdmin(1); $user->setHash(password_hash($adminPass, PASSWORD_DEFAULT)); - $store = \b8\Store\Factory::getStore('User'); + $store = Factory::getStore('User'); $store->save($user); - print 'User account created!' . PHP_EOL; + $output->writeln('User account created!'); } catch (\Exception $ex) { - print 'There was a problem creating your account. :(' . PHP_EOL; - print $ex->getMessage(); + $output->writeln('PHPCI failed to create your admin account.'); + $output->writeln('' . $ex->getMessage() . ''); + die; } } - protected function ask($question, $emptyOk = false, $validationFilter = null) + protected function verifyNotInstalled(OutputInterface $output) { - print $question . ' '; + if (file_exists(PHPCI_DIR . 'PHPCI/config.yml')) { + $content = file_get_contents(PHPCI_DIR . 'PHPCI/config.yml'); - $rtn = ''; - $stdin = fopen('php://stdin', 'r'); - $rtn = fgets($stdin); - fclose($stdin); - - $rtn = trim($rtn); - - if (!$emptyOk && empty($rtn)) { - $rtn = $this->ask($question, $emptyOk, $validationFilter); - } elseif ($validationFilter != null && ! empty($rtn)) { - if (! $this -> controlFormat($rtn, $validationFilter, $statusMessage)) { - print $statusMessage; - $rtn = $this->ask($question, $emptyOk, $validationFilter); + if (!empty($content)) { + $output->writeln('PHPCI/config.yml exists and is not empty.'); + $output->writeln('If you were trying to update PHPCI, please use phpci:update instead.'); + die; } } - - return $rtn; - } - protected function controlFormat($valueToInspect, $filter, &$statusMessage) - { - $filters = !(is_array($filter))? array($filter) : $filter; - $statusMessage = ''; - $status = true; - $options = array(); - - foreach ($filters as $filter) { - if (! is_int($filter)) { - $regexp = $filter; - $filter = FILTER_VALIDATE_REGEXP; - $options = array( - 'options' => array( - 'regexp' => $regexp, - ) - ); - } - if (! filter_var($valueToInspect, $filter, $options)) { - $status = false; - - switch ($filter) - { - case FILTER_VALIDATE_URL: - $statusMessage = 'Incorrect url format.' . PHP_EOL; - break; - case FILTER_VALIDATE_EMAIL: - $statusMessage = 'Incorrect e-mail format.' . PHP_EOL; - break; - case FILTER_VALIDATE_REGEXP: - $statusMessage = 'Incorrect format.' . PHP_EOL; - break; - } - } - } - - return $status; } } diff --git a/PHPCI/Command/UpdateCommand.php b/PHPCI/Command/UpdateCommand.php index 855ca893..127b272e 100644 --- a/PHPCI/Command/UpdateCommand.php +++ b/PHPCI/Command/UpdateCommand.php @@ -48,8 +48,30 @@ class UpdateCommand extends Command */ protected function execute(InputInterface $input, OutputInterface $output) { + $this->verifyInstalled($output); + + $output->writeln('Updating PHPCI database.'); + // Update the database: $gen = new \b8\Database\Generator(\b8\Database::getConnection(), 'PHPCI', './PHPCI/Model/Base/'); $gen->generate(); + + $output->writeln('Done!'); + } + + protected function verifyInstalled(OutputInterface $output) + { + if (!file_exists(PHPCI_DIR . 'PHPCI/config.yml')) { + $output->writeln('PHPCI does not appear to be installed.'); + $output->writeln('Please install PHPCI via phpci:install instead.'); + die; + } + + $content = file_get_contents(PHPCI_DIR . 'PHPCI/config.yml'); + if (empty($content)) { + $output->writeln('PHPCI does not appear to be installed.'); + $output->writeln('Please install PHPCI via phpci:install instead.'); + die; + } } } diff --git a/PHPCI/Controller/SettingsController.php b/PHPCI/Controller/SettingsController.php index 6f8f544c..c2ea099b 100644 --- a/PHPCI/Controller/SettingsController.php +++ b/PHPCI/Controller/SettingsController.php @@ -39,7 +39,15 @@ class SettingsController extends Controller public function index() { $this->view->settings = $this->settings; + + $emailSettings = array(); + + if (isset($this->settings['phpci']['email_settings'])) { + $emailSettings = $this->settings['phpci']['email_settings']; + } + $this->view->github = $this->getGithubForm(); + $this->view->emailSettings = $this->getEmailForm($emailSettings); if (!empty($this->settings['phpci']['github']['token'])) { $this->view->githubUser = $this->getGithubUser($this->settings['phpci']['github']['token']); @@ -52,17 +60,28 @@ class SettingsController extends Controller { $this->settings['phpci']['github']['id'] = $this->getParam('githubid', ''); $this->settings['phpci']['github']['secret'] = $this->getParam('githubsecret', ''); - $error = $this->storeSettings(); - if($error) - { + if($error) { header('Location: ' . PHPCI_URL . 'settings?saved=2'); - } - else - { + } else { header('Location: ' . PHPCI_URL . 'settings?saved=1'); } + + die; + } + + public function email() + { + $this->settings['phpci']['email_settings'] = $this->getParams(); + $error = $this->storeSettings(); + + if ($error) { + header('Location: ' . PHPCI_URL . 'settings?saved=2'); + } else { + header('Location: ' . PHPCI_URL . 'settings?saved=1'); + } + die; } @@ -120,18 +139,23 @@ class SettingsController extends Controller $field->setLabel('Application ID'); $field->setClass('form-control'); $field->setContainerClass('form-group'); - $field->setValue($this->settings['phpci']['github']['id']); $form->addField($field); + if (isset($this->settings['phpci']['github']['id'])) { + $field->setValue($this->settings['phpci']['github']['id']); + } + $field = new Form\Element\Text('githubsecret'); $field->setRequired(true); $field->setPattern('[a-zA-Z0-9]+'); $field->setLabel('Application Secret'); $field->setClass('form-control'); $field->setContainerClass('form-group'); - $field->setValue($this->settings['phpci']['github']['secret']); $form->addField($field); + if (isset($this->settings['phpci']['github']['secret'])) { + $field->setValue($this->settings['phpci']['github']['secret']); + } $field = new Form\Element\Submit(); $field->setValue('Save »'); @@ -141,6 +165,76 @@ class SettingsController extends Controller return $form; } + protected function getEmailForm($values = array()) + { + $form = new Form(); + $form->setMethod('POST'); + $form->setAction(PHPCI_URL . 'settings/email'); + $form->addField(new Form\Element\Csrf('csrf')); + + $field = new Form\Element\Text('smtp_address'); + $field->setRequired(false); + $field->setLabel('SMTP Server'); + $field->setClass('form-control'); + $field->setContainerClass('form-group'); + $field->setValue('localhost'); + $form->addField($field); + + $field = new Form\Element\Text('smtp_port'); + $field->setRequired(false); + $field->setPattern('[0-9]+'); + $field->setLabel('SMTP Port'); + $field->setClass('form-control'); + $field->setContainerClass('form-group'); + $field->setValue(25); + $form->addField($field); + + $field = new Form\Element\Text('smtp_username'); + $field->setRequired(false); + $field->setLabel('SMTP Username'); + $field->setClass('form-control'); + $field->setContainerClass('form-group'); + $form->addField($field); + + $field = new Form\Element\Text('smtp_password'); + $field->setRequired(false); + $field->setLabel('SMTP Password'); + $field->setClass('form-control'); + $field->setContainerClass('form-group'); + $form->addField($field); + + $field = new Form\Element\Email('from_address'); + $field->setRequired(false); + $field->setLabel('From Email Address'); + $field->setClass('form-control'); + $field->setContainerClass('form-group'); + $form->addField($field); + + $field = new Form\Element\Email('default_mailto_address'); + $field->setRequired(false); + $field->setLabel('Default Notification Address'); + $field->setClass('form-control'); + $field->setContainerClass('form-group'); + $form->addField($field); + + $field = new Form\Element\Checkbox('smtp_encryption'); + $field->setCheckedValue(1); + $field->setRequired(false); + $field->setLabel('Use SMTP encryption?'); + $field->setContainerClass('form-group'); + $field->setValue(1); + $form->addField($field); + + $field = new Form\Element\Submit(); + $field->setValue('Save »'); + $field->setClass('btn btn-success pull-right'); + $form->addField($field); + + $form->setValues($values); + + return $form; + } + protected function getGithubUser($token) { $http = new HttpClient('https://api.github.com'); diff --git a/PHPCI/View/Settings/index.phtml b/PHPCI/View/Settings/index.phtml index be0c88b2..3f0eb5ec 100644 --- a/PHPCI/View/Settings/index.phtml +++ b/PHPCI/View/Settings/index.phtml @@ -28,7 +28,12 @@

Github Application

@@ -66,3 +71,27 @@ + +
+
+
+

Email Settings

+ + + +

+ Before PHPCI can send build status emails, you need to configure your SMTP settings below. +

+ + +
+ +
+ +
+ +
+ +
+
+
\ No newline at end of file diff --git a/PHPCI/View/layout.phtml b/PHPCI/View/layout.phtml index bc731848..c455334c 100644 --- a/PHPCI/View/layout.phtml +++ b/PHPCI/View/layout.phtml @@ -64,14 +64,6 @@
- - -
- - Warning! install.php detected, for security purposes please delete it immediately. -
- -
diff --git a/bootstrap.php b/bootstrap.php index 3cfcf038..4f3e8a67 100755 --- a/bootstrap.php +++ b/bootstrap.php @@ -11,7 +11,10 @@ use PHPCI\Logging\Handler; use PHPCI\Logging\LoggerConfig; -date_default_timezone_set(@date_default_timezone_get()); +$timezone = ini_get('date.timezone'); +if (empty($timezone)) { + date_default_timezone_set('UTC'); +} // Set up a basic autoloader for PHPCI: $autoload = function ($class) { diff --git a/composer.json b/composer.json index 7ccbc600..8de9e9eb 100644 --- a/composer.json +++ b/composer.json @@ -23,6 +23,10 @@ }, "require": { + "php": ">=5.3.3", + "ext-mcrypt": "*", + "ext-pdo": "*", + "ext-pdo_mysql": "*", "block8/b8framework" : "1.*", "ircmaxell/password-compat": "1.*", "swiftmailer/swiftmailer" : "5.0.*", diff --git a/public/index.php b/public/index.php index 134e9a7b..e1754b62 100644 --- a/public/index.php +++ b/public/index.php @@ -16,3 +16,4 @@ require_once('../bootstrap.php'); $fc = new PHPCI\Application($config, new b8\Http\Request()); print $fc->handleRequest(); + diff --git a/public/install.php b/public/install.php deleted file mode 100644 index f1e61a23..00000000 --- a/public/install.php +++ /dev/null @@ -1,408 +0,0 @@ -dump($config, 5); - - file_put_contents(PHPCI_DIR . 'PHPCI/config.yml', $yaml); - - /** - * Create database: - */ - $dbhost = $config['b8']['database']['servers']['write'][0]; - $dbname = $config['b8']['database']['name'] ?: 'phpci'; - $dbuser = $config['b8']['database']['username'] ?: 'phpci'; - $dbpass = $config['b8']['database']['password']; - - $pdo = new PDO('mysql:host=' . $dbhost, $dbuser, $dbpass); - - $pdo->query('CREATE DATABASE IF NOT EXISTS `' . $dbname . '`'); - - /** - * Bootstrap PHPCI and populate database: - */ - require(PHPCI_DIR . 'bootstrap.php'); - - ob_start(); - $gen = new \b8\Database\Generator(\b8\Database::getConnection(), 'PHPCI', PHPCI_DIR . 'PHPCI/Model/Base/'); - $gen->generate(); - ob_end_clean(); - - /** - * Create our admin user: - */ - $store = \b8\Store\Factory::getStore('User'); - - try { - $user = $store->getByEmail($adminUser); - } catch (Exception $ex) { - } - - if (empty($user)) { - $user = new \PHPCI\Model\User(); - $user->setEmail($adminUser); - $user->setName($adminUser); - $user->setIsAdmin(1); - $user->setHash(password_hash($adminPass, PASSWORD_DEFAULT)); - - $store->save($user); - } - - $formAction = rtrim( $phpciUrl, '/' ) . '/session/login'; - } -} - - -switch ($installStage) { - case 'start': - $nextStage = 'database'; - break; - - case 'database': - $nextStage = 'github'; - break; - - case 'github': - $nextStage = 'email'; - break; - - case 'email': - $nextStage = 'complete'; - break; -} - -?> - - - - Install PHPCI - - - - - - - - -
-
- -
-
- - - - - -

Welcome to PHPCI!

- -

Your server has passed all of PHPCI's pre-installation checks, please press continue below to - begin installation.

- -

Please correct the problems below, then refresh this page to continue.

- - - -

- Important! - You need to run composer to install dependencies before running the installer. -

- - - - -

- Important! - ./PHPCI/config.yml needs to be writeable to continue. -

- - - -

Important! PHPCI requires PHP 5.3.3 or above.

- - - - -

Database Details

-
- -
- -
-
-
- -
- -
-
-
- -
- -
-
-
- -
- -
-
- -

PHPCI Details

-
- -
- -
-
- -
- -
- -
-
- -
- -
- -
-
- - - -

Github App Settings (Optional)

-
- -
- -
-
- -
- -
- -
-
- - - -

SMTP Settings (Optional)

-
- -
- -
-
- -
- -
- -
-
- -
- -
- -
-
- -
- -
- -
-
- -
- -
- -
-
- -
- -
- -

The address system emails should come from.

-
-
- -
- -
- -

The address to which notifications should go by default.

-
-
- - - -

Thank you for installing PHPCI. Click continue below to log in for the first time!

- - - - - - -
-
- -
-
- -
-
- - -
-
- -