* @link https://TheTempusProject.com * @license https://opensource.org/licenses/MIT [MIT LICENSE] */ namespace TheTempusProject\Controllers; require_once 'bin/autoload.php'; use TheTempusProject\TheTempusProject; use TheTempusProject\Classes\Controller; use TheTempusProject\Classes\Plugin; use TheTempusProject\Classes\Installer; use TheTempusProject\Classes\Forms; use TheTempusProject\Models\User; use TheTempusProject\Classes\Email; use TheTempusProject\Bedrock\Functions\Code; use TheTempusProject\Bedrock\Functions\Check; use TheTempusProject\Bedrock\Functions\Cookie; use TheTempusProject\Bedrock\Functions\Input; use TheTempusProject\Bedrock\Functions\Upload; use TheTempusProject\Bedrock\Functions\Hash; use TheTempusProject\Bedrock\Functions\Session; use TheTempusProject\Houdini\Classes\Issues; use TheTempusProject\Houdini\Classes\Views; use TheTempusProject\Houdini\Classes\Components; use TheTempusProject\Houdini\Classes\Template; use TheTempusProject\Hermes\Functions\Redirect; use TheTempusProject\Hermes\Functions\Route; use TheTempusProject\Canary\Bin\Canary as Debug; class Install extends Controller { private $installer; private $location = false; private $steps = [ 'Welcome', 'Terms', 'Verify', 'Configure', 'Routing', 'Models', 'Plugins', 'Install', 'Resources', 'User', 'Complete', ]; /** * This is the main builder for the rest of the controller. It mostly handle template variables used on all pages. */ public function __construct() { parent::__construct(); self::$title = 'TP Installer'; self::$pageDescription = 'This is the install script for The Tempus Project.'; $this->installer = new Installer(); Template::noIndex(); Template::noFollow(); foreach ( $this->steps as $step ) { Components::set( 'menu-' . $step, 'disabled' ); } if ( $this->checkSession() !== false ) { $this->location = $this->getStep(); Components::set( 'menu-' . ucfirst( $this->location ), 'active' ); } else { Components::set( 'menu-Welcome', 'active' ); } Components::set( 'installer-nav', Views::simpleView( 'install.nav' ) ); } /** * This method will reset the install hash, set the saved install step, update the session and cookie, and refresh if required. * * @param string $page * @param boolean $redirect * @return void */ public function nextStep( $page, $redirect = false ) { $newHash = Code::genInstall(); $this->installer->setNode( 'installHash', $newHash, true ); $this->installer->setNode( 'installStep', $page, true ); Session::put( 'installHash', $newHash ); Cookie::put( 'installHash', $newHash ); if ( $redirect === true ) { Redirect::reload(); } return true; } public function checkSession() { if ( empty( $this->installer->getNode('installHash') ) ) { Debug::error( 'install hash not found on file.' ); return false; } $session = Session::get( 'installHash' ); $cookie = Cookie::get( 'installHash' ); $file = $this->installer->getNode('installHash'); if ( ! $session && ! $cookie ) { Debug::error( 'install hash not found in session or cookie.' ); return false; } if ( $cookie && ! $session ) { if ( $cookie !== $file ) { Debug::error( 'install cookie did not match install file.' ); Cookie::delete( 'installHash' ); return false; } Debug::error( 'cookie matches file, using as session' ); Session::put( 'installHash', $cookie ); return true; } if ( $session !== $file ) { Debug::error( 'session did not match file, deleting session' ); Session::delete( 'installHash' ); return false; } return true; } public function getStep() { if ( !empty( $this->installer->getNode('installStep') ) ) { return $this->installer->getNode('installStep'); } Debug::error( 'install status not found.' ); return false; } /** * The index method is called on the first request and all requests thereafter and is responsible for routing * the current request to the appropriate installer step/method. */ public function index() { if ( false === $this->location ) { return $this->welcome(); } // this seems dumb, i could probably do this better $location = $this->location; return $this->$location(); } /** * The welcome method is is just a page to submit a form and save the install.json for the first time. */ public function welcome() { if ( Forms::Check( 'installStart' ) ) { return $this->nextStep( 'terms', true ); } if ( Input::exists( 'submit' ) ) { Issues::add( 'error', ['There was an error with the Installation.' => Check::userErrors()] ); } Views::view( 'install.start' ); } /** * The terms step is pretty straight forward. You simply need to continue to the next step, understanding * that you agree to these terms when you continue the installation. */ public function terms() { if ( Forms::Check( 'installAgreement' ) ) { return $this->nextStep( 'verify', true ); } if ( Input::exists( 'submit' ) ) { Issues::add( 'error', [ 'There was an error with the Installation.' => Check::userErrors() ] ); } Components::set( 'TERMS', Views::simpleView( 'terms' ) ); Views::view( 'install.agreement' ); } /** * There is a small list a of requirements for the application to run properly. These are things like sessions, emails, cookies, etc. * This step verifies that all of these features are working as expected. */ public function verify() { if ( Forms::Check( 'installCheck' ) ) { return $this->nextStep( 'configure', true ); } if ( Input::exists( 'submit' ) ) { Issues::add( 'error', ['There was an error with the Installation.' => array_merge( Check::userErrors(), Check::systemErrors() )] ); } Views::view( 'install.check' ); } /** * One of the most important steps for installation, is the configuration. In this step, we will define some very core settings * for the app including the app's name and database credentials. */ public function configure() { if ( Forms::Check( 'installConfigure' ) ) { $logo = 'images/logo.png'; if ( Input::exists( 'logo' ) && Upload::image( 'logo', 'System' ) ) { $logo = 'Uploads/Images/System/' . Upload::last(); } TheTempusProject::$activeConfig->load( BEDROCK_CONFIG_JSON ); $baseConfig = TheTempusProject::$configMatrix; $baseConfig['main']['logo']['value'] = $logo; $baseConfig['main']['name']['value'] = Input::postNull( 'siteName' ); $baseConfig['main']['template']['value'] = $baseConfig['main']['template']['default']; $baseConfig['main']['tokenEnabled']['value'] = $baseConfig['main']['tokenEnabled']['default']; $baseConfig['main']['loginLimit']['value'] = $baseConfig['main']['loginLimit']['default']; $baseConfig['database']['dbEnabled']['value'] = $baseConfig['database']['dbEnabled']['default']; $baseConfig['database']['dbHost']['value'] = Input::postNull( 'dbHost' ); $baseConfig['database']['dbMaxQuery']['value'] = $baseConfig['database']['dbMaxQuery']['default']; $baseConfig['database']['dbName']['value'] = Input::postNull( 'dbName' ); $baseConfig['database']['dbPassword']['value'] = Input::postNull( 'dbPassword' ); $baseConfig['database']['dbPrefix']['value'] = Input::postNull( 'dbPrefix' ); $baseConfig['database']['dbUsername']['value'] = Input::postNull( 'dbUsername' ); if ( ! TheTempusProject::$activeConfig->generate( CONFIG_JSON, $baseConfig ) ) { return Issues::add( 'error', 'Config file already exists so the installer has been halted. If there was an error with installation, please delete app/config/config.json manually and try again. The installer should automatically bring you back to this step.' ); } Session::flash( 'success', 'Config saved successfully.' ); return $this->nextStep( 'routing', true ); } if ( Input::exists( 'submit' ) ) { Issues::add( 'error', ['There was an error with your form.' => Check::userErrors()] ); } Views::view( 'install.configure' ); } /** * For the application to function properly on nginx or apache, the web servers must be configured correctly. * Depending on which server you use, this step will help you set up and test the routing required for the * application to function as expected. */ public function routing() { if ( Input::exists( 'submit' ) && Forms::Check( 'installRouting' ) ) { // if its Apache, attempt to generate the htaccess file before testing if ( Check::isApache() ) { if ( !$this->installer->checkHtaccess() ) { if ( !$this->installer->saveHtaccess() ) { Issues::add( 'error', 'There was an unexpected error when generating your htaccess file. Please see the error logs for more information.' ); } } } // Apache should have the htaccess now, and Nginx should have been configured this way out of the box if ( Route::testRouting() ) { Session::flash( 'success', 'Routing is working as expected.' ); return $this->nextStep( 'models', true ); } else { Issues::add( 'error', 'Could not verify url routing' ); } // routing is busted, if its Apache, we already have the error from htaccess generation // so Nginx is the only one that needs more info if ( Check::isNginx() ) { Issues::add( 'error', 'There appears to be an issue with your configuration. Certain urls are not being routed as expected.' ); } } elseif ( Input::exists( 'submit' ) ) { Issues::add( 'error', ['There was an error with your form.' => Check::userErrors()] ); } Views::view( 'install.routing' ); } /** * Since models are required for the proper function of the app, this step is required and has no selection to make. * This step will install all the required models excluding resources. */ public function models() { $errors = []; $options = [ 'installResources' => false ]; $models = $this->installer->getModelList( MODEL_DIRECTORY ); if ( Input::exists( 'submit' ) && Forms::Check( 'installModels' ) ) { $error = false; foreach ( $models as $model ) { $result = $this->installer->installModel( $model, $options ); if ( $result === false ) { $error = true; continue; } } if ( $error ) { Issues::add( 'error', [ 'There was an error with the Installation.' => $this->installer->getErrors() ] ); } else { Session::flash( 'success', [ 'Models Have been installed successfully.' => $this->installer->getErrors() ] ); return $this->nextStep( 'plugins', true ); } } elseif ( Input::exists( 'submit' ) ) { Issues::add( 'error', [ 'There was an error with your form.' => Check::userErrors() ] ); } Views::view( 'install.models', $models ); } /** * This step will allow the user to install any plugins currently available for installing excluding resources. */ public function plugins() { $errors = []; $options = [ 'resources_installed' => false ]; $plugins = $this->installer->getAvailablePlugins(); $selected_plugins = Input::post( 'P_' ); if ( Input::exists( 'submit' ) && Forms::Check( 'installPlugins' ) ) { $error = false; foreach ( $plugins as $plugin ) { if ( ! in_array( $plugin->name, $selected_plugins ) ) { continue; } $result = $this->installer->installPlugin( $plugin, $options ); if ( !$result ) { $error = true; continue; } Plugin::enable( $plugin->name, true ); } if ( $error ) { Issues::add( 'error', ['There was an error with the Installation.' => $this->installer->getErrors()] ); } else { Session::flash( 'success', [ 'Plugins Have been installed successfully.' => $this->installer->getErrors() ] ); return $this->nextStep( 'resources', true ); } } elseif ( Input::exists( 'submit' ) ) { Issues::add( 'error', ['There was an error with your form.' => Check::userErrors()] ); } Views::view( 'install.plugins', $plugins ); } /** * The resource step will cycle through the partially installed models and install any missing resources. */ public function resources() { $errors = []; if ( Input::exists( 'submit' ) && Forms::Check( 'installResources' ) ) { $error = false; $allModules = $this->installer->getModules(true); foreach ( $allModules as $name => $module ) { if ( empty( $module ) || 'unknown' === $name || empty( $name ) || empty( $module['installedVersion'] ) ) { continue; } if ( 'plugin' == $module['type'] ) { $installResult = $this->installer->installPlugin( (object) $module, [ 'resources_installed' => true ], false ); } else { $installResult = $this->installer->installModel( (object) $module, [ 'installResources' => true ], false ); } if ( !$installResult ) { $error = true; } } if ( $error ) { Issues::add( 'error', ['There was an error with the Installation.' => $this->installer->getErrors()] ); } else { Session::flash( 'success', ['Resources have been installed successfully.' => $this->installer->getErrors()] ); return $this->nextStep( 'user', true ); } } elseif ( Input::exists( 'submit' ) ) { Issues::add( 'error', ['There was an error with your form.' => Check::userErrors()] ); } Views::view( 'install.resources' ); } /** * This is the registration step; allowing the installer to create the super admin account. */ public function user() { if ( Input::exists( 'submit' ) && Forms::Check( 'installAdminUser' ) ) { $user = new User(); if ( !$user->create( [ 'username' => Input::post( 'newUsername' ), 'password' => Hash::make( Input::post( 'userPassword' ) ), 'email' => Input::post( 'userEmail' ), 'lastLogin' => time(), 'registered' => time(), 'confirmed' => 1, 'terms' => 1, 'userGroup' => 1, ] ) ) { Issues::add( 'error', 'There was an error creating the admin user.' ); return; } $this->nextStep( 'complete' ); return $this->complete( true ); } elseif ( Input::exists( 'submit' ) ) { Issues::add( 'error', ['There was an error with your form.' => Check::userErrors()] ); } Views::view( 'install.adminUser' ); } /** * This is the final step of installation. On first load it will send an email and show the final view. * It will then redirect to the index controller and prompt the user to delete this file on any subsequent loads. * * @param bool $sendEmail */ public function complete( $sendEmail = false ) { if ( $sendEmail ) { Issues::add( 'success', 'The Tempus Project has been installed successfully.' ); Email::send( Input::post( 'email' ), 'install', null, [ 'template' => true ] ); return Views::view( 'install.complete' ); } Session::flash( 'notice', 'Installation has been completed. Updates and installation can be managed in the admin panel. Please delete the install.php file.' ); Redirect::to( 'home/index' ); } } $app = new TheTempusProject(); if ( CANARY_ENABLED ) { // ini_set( 'display_errors', '1' ); // ini_set( 'display_startup_errors', '1' ); // error_reporting( E_ALL ); // $app->printDebug(); } $app->setUrl( 'install/index' ); $app->load(); if ( CANARY_DEBUG_TO_CONSOLE ) { Components::set( 'DEBUGGING_LOG', Debug::dump() ); register_shutdown_function( [ $app::class, 'handle_shutdown' ] ); } exit;