/ */ class Setup_IndexController extends Zend_Controller_Action { const MIN_PHP_VERSION = '5.3'; const MIN_P4_VERSION = '2012.1'; const P4D_BINARY = 'p4d'; const P4D_FLAGS = '-ir'; const P4D_PORT = 'rsh:'; const P4D_FOLDER = 'perforce'; const P4D_USER = 'chronicle'; public $contexts = array( 'requirements' => array('partial'), 'create' => array('json') ); protected $_adminP4 = null; protected $_session = null; /** * Use the setup layout and disable toolbar for all setup actions. */ public function init() { $this->_helper->layout->setLayout('setup-layout'); // never cache setup requests; they can be particularly problematic // on the first run of setup caching the root page. if (P4Cms_Cache::canCache('page')) { P4Cms_Cache::getCache('page')->cancel(); } // list of actions that will be skipped from the permissions check $skipActions = array('rewrite', 'summary'); // don't enforce permissions if setup is needed. // as there is only one privilege, we can do the permissions check for all // actions here - with the exception of actions listed in skipActions. if (in_array($this->getRequest()->getActionName(), $skipActions) || $this->getInvokeArg('bootstrap')->isSetupNeeded() ) { return; } // enforce permissions. $this->_helper->acl->check('site', 'add'); } /** * Clear out completed setup data from session. */ protected function _cleanupSession() { $session = $this->_getSession(); if ($session->setupComplete) { $session->site = null; $session->storage = null; $session->administrator = null; $session->setupComplete = false; } } /** * Show setup splash page. */ public function indexAction() { $this->_cleanupSession(); // display splash page unless start is set. $request = $this->getRequest(); if ($request->getParam('start')) { $this->_forward('requirements'); } else { // build start url. $startUrl = $request->getBaseUrl(); if ($this->_isRewriteWorking()) { $startUrl .= '/setup/start/yes'; } else { $startUrl .= '?start=yes'; } $this->view->startUrl = $startUrl; } $this->view->headTitle()->set('Setup'); } /** * Start setup process by checking requirements. */ public function requirementsAction() { $this->_cleanupSession(); $this->view->headTitle()->set('Setup: Requirements'); // check overall sanity. $this->view->isValidEnvironment = $this->_isValidEnvironment(); // check php requirement. $this->view->isPhpValid = $this->_isPhpValid(); $this->view->isPhpVersionValid = $this->_isPhpVersionValid(); $this->view->phpVersion = PHP_VERSION; $this->view->minPhpVersion = self::MIN_PHP_VERSION; $this->view->isMagicQuotesOn = $this->_isMagicQuotesOn(); // check mod-rewrite requirement. $this->view->isRewriteWorking = $this->_isRewriteWorking(); // check p4 requirement. $this->view->isP4Valid = $this->_isP4Valid(); $this->view->p4Version = $this->_getP4Version(); $this->view->minP4Version = self::MIN_P4_VERSION; $this->view->isP4Installed = $this->_isP4Installed(); $this->view->p4ClientType = $this->_p4ClientType(); // check data directory. $this->view->isDataPathValid = $this->_isDataPathValid(); $this->view->isDataPathPresent = $this->_isDataPathPresent(); $this->view->isDataPathWritable = $this->_isDataPathWritable(); $this->view->dataPath = DATA_PATH; // check the Perforce extension $this->view->isP4PHPInstalled = extension_loaded('perforce'); // check the Opcode Cache. $this->view->isWinCacheInstalled = extension_loaded('wincache'); $this->view->isApcInstalled = extension_loaded('apc'); if (P4_Environment::isWindows() && isset($_SERVER['SERVER_SOFTWARE'])) { $this->view->isWebServerIis = stripos($_SERVER['SERVER_SOFTWARE'], "Microsoft-IIS") !== false; } // check for image manipulation availability $this->view->imageExtensions = array(); $this->view->imageExtensionsEnabled = array(); foreach (P4Cms_Image_Driver_Factory::getDriverClasses() as $driverClass) { $extension = $driverClass::getRequiredExtension(); if (!$extension) { continue; } $this->view->imageExtensions[] = $extension; if (extension_loaded($extension)) { $this->view->imageExtensionsEnabled[] = $extension; } } // check for common image types support for the default driver try { $defaultDriver = P4Cms_Image_Driver_Factory::create(); } catch (P4Cms_Image_Exception $e) { // no driver available $defaultDriver = null; } $commonTypes = array('jpeg', 'png', 'gif'); $this->view->defaultImageDriver = $defaultDriver; $this->view->missingCommonImageTypes = $defaultDriver ? array_diff($commonTypes, array_filter($commonTypes, array($defaultDriver, 'isSupportedType'))) : array(); // save the username/group for the web server if available $webServerDetails = ''; if (function_exists("posix_geteuid") && function_exists("posix_getpwuid") && function_exists("posix_getgrgid") ) { $userInfo = posix_getpwuid(posix_geteuid()); $userName = $userInfo['name']; $groupInfo = posix_getgrgid($userInfo['gid']); $groupName = $groupInfo['name']; $webServerDetails = " (username \"$userName\", group \"$groupName\")"; } $this->view->webServerDetails = $webServerDetails; } /** * Simple action exists only to be requested to test if rewrite is working. * Responds with a checksum of this file. */ public function rewriteAction() { print(md5_file(__FILE__)); $this->_helper->layout()->disableLayout(); $this->_helper->viewRenderer->setNoRender(); } /** * Obtain Perforce server information. */ public function storageAction() { // if requirements not met, return to requirements step. if (!$this->_isValidEnvironment()) { $this->redirector->gotoSimple('requirements'); return; } // setup view. $form = new Setup_Form_Storage; $this->view->form = $form; $this->view->isP4dInstalled = $form->isP4dInstalled(); $this->view->isP4dValid = $form->isP4dValid(); $this->view->minP4Version = self::MIN_P4_VERSION; $this->view->headTitle()->set('Setup: Site Storage'); // if we have a previously configured perforce connection, setup // the form to always use it (regardless of request paramaters) $request = $this->getRequest(); $perforce = $this->getInvokeArg('bootstrap')->getResource('perforce'); if ($perforce) { $form->getElement('serverType') ->setAttrib('disabled', true) ->setValue($form::SERVER_TYPE_EXISTING); $form->getElement('port') ->setAttrib('disabled', true) ->setValue($perforce->getPort()) ->setDescription('You have already configured a Perforce Server.'); $request->setPost('serverType', $form::SERVER_TYPE_EXISTING) ->setPost('port', $perforce->getPort()); } // if form has been posted and is valid, save values // to session and proceed to administrator form. if ($request->isPost() && $form->isValid($request->getPost())) { $session = $this->_getSession(); $session->storage = $form->getValues(); if ($request->getParam('goback')) { $this->redirector->gotoSimple('requirements'); return; } $this->redirector->gotoSimple('administrator'); return; } elseif ($request->isPost()) { if ($request->getParam('goback')) { $this->redirector->gotoSimple('requirements'); return; } $count = count($form->getMessages()); $s = ($count == 1) ? '' : 's'; P4Cms_Notifications::add("$count field$s failed validation.", P4Cms_Notifications::SEVERITY_ERROR); } // if serverType=new, disable port/address field if ($form->getValue('serverType') == $form::SERVER_TYPE_NEW) { $group = $form->getDisplayGroup('existingServer'); $group->setAttrib('class', $group->getAttrib('class') . ' disabled'); } // if we have a previously configured 'rsh' perforce server, // pretty-up the port value to hide 'rsh' details. if ($perforce && $this->_isRshServer($perforce)) { $form->getElement('port') ->setValue($this->_getFriendlyPort($perforce)) ->setLabel('Local Server'); } } /** * Obtain server administrator information. */ public function administratorAction() { // if requirements not met, return to requirements step. if (!$this->_isValidEnvironment()) { $this->redirector->gotoSimple('requirements'); return; } // If perforce server is invalid - return to storage action. $storageForm = new Setup_Form_Storage; $storageForm->setCsrfProtection(false); // trusted source, disable CRSF so isValid works $session = $this->_getSession(); if (!is_array($session->storage) || !$storageForm->isValid($session->storage)) { $this->redirector->gotoSimple('storage'); return; } $this->_cleanupSession(); // setup view. $perforce = $this->getInvokeArg('bootstrap')->getResource('perforce'); $view = $this->view; $view->port = $perforce ? $this->_getFriendlyPort($perforce) : $session->storage['port']; $view->isRsh = $perforce ? $this->_isRshServer($perforce) : false; if (!$perforce && $storageForm->getValue('serverType') !== $storageForm::SERVER_TYPE_NEW) { // note: auto user creation does not appear to be triggered for the info command (which // P4_Connection::hasExternalAuth() uses.) but should that change, we create a // highly-unlikely username for the connection test. $username = md5(mt_rand()); $p4 = P4_Connection::factory($session->storage['port'], $username); $session->storage['hasExternalAuth'] = $p4->hasExternalAuth(); } // setup form. $form = new Setup_Form_Administrator( array( 'serverType' => $storageForm->getValue('serverType'), 'p4Port' => $session->storage['port'], 'hasExternalAuth' => isset($session->storage['hasExternalAuth']) ? $session->storage['hasExternalAuth'] : false ) ); $view->form = $form; $view->headTitle()->set('Setup: Administrator'); // if form has been posted and is valid, save values // to session and proceed to site form. $request = $this->getRequest(); if ($request->isPost() && $form->isValid($request->getPost())) { $session = $this->_getSession(); $session->administrator = $form->getValues(); if ($request->getParam('goback')) { $this->redirector->gotoSimple('storage'); return; } $this->redirector->gotoSimple('site'); return; } elseif ($request->isPost()) { if ($request->getParam('goback')) { $this->redirector->gotoSimple('storage'); return; } $count = count($form->getMessages()); $s = ($count == 1) ? '' : 's'; P4Cms_Notifications::add("$count field$s failed validation.", P4Cms_Notifications::SEVERITY_ERROR); } } /** * Setup a site definition. * * Both of the optional paramaters are intended for use by the 'create' action. * * @param bool $skipRedirect optional - if true skips trying to redirect backward * for failed requirements and simply returns. * @param bool $optionalUrls optional - if true the 'urls' field of the site form * isn't required. */ public function siteAction($skipRedirect = false, $optionalUrls = false) { // if requirements not met, return to requirements step. if (!$this->_isValidEnvironment()) { $this->view->step = 'environment'; $this->view->isValid = false; $this->view->errors = array('form' => array('One or more requirements are not met.')); $skipRedirect ?: $this->redirector->gotoSimple('requirements'); return; } // if perforce server is invalid - return to storage action. $storageForm = new Setup_Form_Storage; $storageForm->setCsrfProtection(false); // trusted source, disable CRSF so isValid works $session = $this->_getSession(); if (!$storageForm->isValid((array) $session->storage)) { $this->view->step = 'storage'; $this->view->isValid = false; $this->view->form = $storageForm; $skipRedirect ?: $this->redirector->gotoSimple('storage'); return; } // if administrator credentials are invalid, return to administrator action $options = array( 'p4Port' => $session->storage['port'], 'serverType' => $session->storage['serverType'], 'hasExternalAuth' => isset($session->storage['hasExternalAuth']) ? $session->storage['hasExternalAuth'] : false ); $adminForm = new Setup_Form_Administrator($options); $adminForm->setCsrfProtection(false); // trusted source, disable CRSF so isValid works if (!$adminForm->isValid((array) $session->administrator)) { $this->view->step = 'administrator'; $this->view->isValid = false; $this->view->form = $adminForm; $skipRedirect ?: $this->redirector->gotoSimple('administrator'); return; } // set the page title $view = $this->view; $view->headTitle()->set('Setup: Site'); // prepare the site form - if we are adding a site to an existing // perforce server, we need a connection to the server so that the // form can check if the site title is taken. $form = new Setup_Form_Site; $view->form = $form; $bootstrap = $this->getInvokeArg('bootstrap'); if ($bootstrap->hasResource('perforce') || $storageForm->getValue('serverType') !== $storageForm::SERVER_TYPE_NEW ) { $form->setConnection( $this->_getAdminConnection($storageForm, $adminForm) ); } // make urls optional if requested by caller // this is intended for API driven create if ($optionalUrls) { $form->getElement('urls')->setRequired(false); } // if form has been posted and is valid, create site. $request = $this->getRequest(); if ($request->isPost() && $form->isValid($request->getPost())) { if ($request->getParam('goback')) { $this->redirector->gotoSimple('administrator'); return; } $site = $this->_createSite($form, $storageForm, $adminForm); // as we have created a new site, we need to clear the site cache. P4Cms_Cache::remove(P4Cms_Site::CACHE_KEY, 'global'); $session->site = $site; $session->setupComplete = true; $this->view->step = 'completed'; $this->view->isValid = true; $skipRedirect ?: $this->redirector->gotoSimple('summary'); } elseif ($request->isPost()) { if ($request->getParam('goback')) { $this->redirector->gotoSimple('administrator'); return; } $this->view->step = 'site'; $this->view->isValid = false; $this->view->form = $form; $count = count($form->getMessages()); $s = ($count == 1) ? '' : 's'; P4Cms_Notifications::add("$count field$s failed validation.", P4Cms_Notifications::SEVERITY_ERROR); } } /** * This action allows api's to make a single post to create a new site. * * You should be able to succesfully add a site with the following post: * storage[serverType]=new * administrator[user]= * administrator[email]= * administrator[password]= * administrator[passwordConfirm]= * site[title]= * * To use an existing perforce server replace the storage line with: * storage[serverType]=existing * storage[port]=perforce:1666 * * A json response will be returned with the following data: * step = environment, storage, administrator, site or completed * isValid = true or false * errors = may contain a form key with an array of strings and/or * an elements key which contains error arrays indexed by * element id. value(s) can be ignored if isValid is true. * * Note: the site[urls] field is only optional for the first site * if any subsequent sites are added this field must be included. */ public function createAction() { // force a json context $this->contextSwitch->initContext('json'); // administrator (username, email, password, confirm) // site-storage (new or old radio and address) // site (title/address/description) $request = $this->getRequest(); $session = $this->_getSession(); $session->administrator = $request->getPost('administrator'); $session->storage = $request->getPost('storage'); // ensure the request only contains the values of site $site = (array) $request->getPost('site'); $request->setPost($site + array('administrator' => '', 'storage' => '', 'site' => '')); // call through to the site action passing true to // ensure it won't redirect and our initial setup // status to determine if urls are optional or not. $this->siteAction(true, $this->_isInitalSetup()); } /** * Summarize site setup - clear site from session. */ public function summaryAction() { // if requirements not met, return to requirements step. if (!$this->_isValidEnvironment()) { $this->redirector->gotoSimple('requirements'); return; } // if no site data in session, redirect to site creation. $session = $this->_getSession(); if (!isset($session->site)) { $this->redirector->gotoSimple('site'); return; } // setup view data. $view = $this->view; $perforce = $this->getInvokeArg('bootstrap')->getResource('perforce'); $view->port = $perforce ? $this->_getFriendlyPort($perforce) : $session->storage['port']; $view->isRsh = $perforce ? $this->_isRshServer($perforce) : false; $view->site = $session->site; $view->storage = $session->storage; $view->admin = $session->administrator; $view->headTitle()->set('Setup: Summary'); } /** * Get a connection to the target Perforce Server as an administrator. * This method will not return a connection to a new local server; that * is the responsibility of _createLocalServer(). * * @param Zend_Form $storageForm the form with the target server port. * @param Zend_Form $adminForm the form containing admin credentials. * @return P4_Connection_Interface an admin connection to the target server */ protected function _getAdminConnection($storageForm, $adminForm) { // if we already have prepared an admin connection, re-use it. if ($this->_adminP4) { return $this->_adminP4; } // if we have a known perforce server use it, // otherwise use the port passed in via storageForm $bootstrap = $this->getInvokeArg('bootstrap'); $port = $bootstrap->hasResource('perforce') ? $bootstrap->getResource('perforce')->getPort() : $storageForm->getValue('port'); $adminP4 = P4_Connection::factory( $port, $adminForm->getValue('user'), null, $adminForm->getValue('password') ); $adminP4->login(); return $adminP4; } /** * Checks if the current environment meets our requirements. * * @return boolean true if the environment meets requirements, false otherwise. */ private function _isValidEnvironment() { if (!$this->_isPhpValid()) { return false; } if (!$this->_isRewriteWorking()) { return false; } if (!$this->_isP4Valid()) { return false; } if (!$this->_isDataPathValid()) { return false; } return true; } /** * Checks if the current version of PHP meets the minimum requirement and * magic quotes are disabled. * * @return boolean true if PHP meets the requirements, false otherwise. */ private function _isPhpValid() { // check the version of php. if (!$this->_isPHPVersionValid()) { return false; } // check magic quotes. if ($this->_isMagicQuotesOn()) { return false; } return true; } /** * Checks if the current version of PHP meets the minimum requirement. * * @return boolean true if PHP meets the requirement, false otherwise. */ private function _isPhpVersionValid() { if (version_compare(PHP_VERSION, self::MIN_PHP_VERSION) >= 0) { return true; } else { return false; } } /** * Checks if magic quotes gpc or runtime are enabled. * * @return boolean true if magic_quotes_gpc or magic_quotes_runtime are on. */ private function _isMagicQuotesOn() { if (get_magic_quotes_gpc() || get_magic_quotes_runtime()) { return true; } else { return false; } } /** * Checks if mod rewrite is enabled and configured correctly. * * @return boolean true if mod-rewrite is enabled and configured. */ private function _isRewriteWorking() { // make http request that requires rewrite. $request = $this->getRequest(); $address = $request->getScheme() . "://" . $request->getHttpHost() . $request->getBaseUrl() . "/setup/index/rewrite"; $result = @file_get_contents($address); // verify that response matches md5 of this file. if (trim($result) == md5_file(__FILE__)) { return true; } else { return false; } } /** * Checks if the minimum version of p4 is installed in the web server's path. * * @return boolean true if a 'p4' is installed, false otherwise. */ private function _isP4Valid() { $p4Version = strtolower($this->_getP4Version()); $minVersion = strtolower(self::MIN_P4_VERSION); if (version_compare($p4Version, $minVersion) >= 0) { return true; } else { return false; } } /** * Get the version of the Perforce client library that is installed. * * @return string the version component of the client identity. */ private function _getP4Version() { try { $identity = P4_Connection::getConnectionIdentity(); return $identity['version']; } catch (P4_Exception $e) { return false; } } /** * Check if the Perforce client library is installed. * * @return boolean true if p4 is installed. */ private function _isP4Installed() { try { $identity = P4_Connection::getConnectionIdentity(); return true; } catch (P4_Exception $e) { return false; } } /** * Determine what Perforce client type is in use. * * @return string description of the client type */ public function _p4ClientType() { switch (get_class(P4_Connection::getDefaultConnection())) { case "P4_Connection_CommandLine": $type = 'Perforce command-line client, P4'; break; case "P4_Connection_Extension": $type = 'Perforce PHP extension, P4PHP'; break; default: $type = '(unknown client)'; break; } return $type; } /** * Checks if the data path exists and is writable. * * @return boolean true if the data path exists and writable, false otherwise. */ private function _isDataPathValid() { if ($this->_isDataPathPresent() && $this->_isDataPathWritable()) { return true; } else { return false; } } /** * Checks if the data directory exists. * * @return boolean true if data directory exists, false otherwise. */ private function _isDataPathPresent() { if (is_dir(DATA_PATH)) { return true; } else { return false; } } /** * Checks if the data directory is writable. * * @return boolean true if data directory is writable, false otherwise. */ private function _isDataPathWritable() { if (!is_writable(DATA_PATH)) { if (!@chmod(DATA_PATH, 0755)) { return false; } } // if we need to configure perforce, we must also // verify that the application config file is writable $bootstrap = $this->getInvokeArg('bootstrap'); if (!$bootstrap->hasResource('perforce')) { $file = $bootstrap->getApplication()->getConfigFile(); if (file_exists($file) && !is_writable($file) && !@chmod($file, 0755)) { return false; } } return true; } /** * Create a site in Perforce and save it to the local sites list. * * Site creation entails several changes in Perforce. A new depot, user, client, * several groups and the configuration of protections to grant read/write access * to the depot for the site groups and their members. * * @param Setup_Form_Site $siteForm site title/urls collected from user. * @param Setup_Form_Storage $storageForm target perforce server information. * @param Setup_Form_Administrator $adminForm target administrator information. * * @publishes p4cms.site.created * Perform operations when a site is created by the Site Module. * P4Cms_Site $site The site that has been created. * P4Cms_User $admin The administrator account used for creation. */ private function _createSite($siteForm, $storageForm, $adminForm) { // if the application has not yet been configured to use a specific // perforce server, we will need to write to the application config file // if there is already an application config file (unlikely) we want // to make sure it is valid before we get any further, so we set it up now. $bootstrap = $this->getInvokeArg('bootstrap'); if (!$bootstrap->hasResource('perforce')) { $configFile = $bootstrap->getApplication()->getConfigFile(); $config = file_exists($configFile) ? new Zend_Config_Ini($configFile, null, array('allowModifications' => true)) : new Zend_Config(array(), true); } // create a writable sites folder if we don't already have one. P4Cms_FileUtility::createWritablePath(DATA_PATH . '/sites'); // connect to the target perforce server. // a new (local) server will be created if we don't already have one if ($bootstrap->hasResource('perforce') || $storageForm->getValue('serverType') === $storageForm::SERVER_TYPE_EXISTING ) { $adminP4 = $this->_getAdminConnection($storageForm, $adminForm); } else { $root = DATA_PATH . '/' . self::P4D_FOLDER; $adminP4 = $this->_createLocalServer($root); // add new server info to session. $session = $this->_getSession(); $session->storage = array_merge( $session->storage, array( 'root' => $root, 'port' => $adminP4->getPort(), ) ); $session->administrator = array_merge( $session->administrator, array( 'user' => $adminP4->getUser(), 'password' => $adminP4->getPassword() ) ); } // if the application has not yet been configured to use a specific // perforce server, we need to create a dedicated 'chronicle' user // and save the connection information to the application config file. $session = $this->_getSession(); if (!$bootstrap->hasResource('perforce')) { $user = new P4_User($adminP4); $user->setId(static::P4D_USER) ->setFullName(static::P4D_USER) ->setEmail(static::P4D_USER); // do not set the password if we are connected to a P4 server // using external authentication if (!isset($session->storage['hasExternalAuth']) || !$session->storage['hasExternalAuth']) { $password = P4Cms_User::generatePassword(10, 3); $user->setPassword($password); } else if (isset($session->administrator['systemPassword'])) { $session = $this->_getSession(); $password = $session->administrator['systemPassword']; } $user->save(); // perforce config should be shared by all environments by default. // prime config object to contain the default sections and inheritance. $config->all = $config->get('all', array()); $config->all->resources = $config->all->resources ?: array(); $config->production = $config->get('production', array()); $config->development = $config->get('development', array()); $config->setExtend('production', 'all'); $config->setExtend('development', 'all'); // create a system connection for later use $systemP4 = P4_Connection::factory($adminP4->getPort(), $user->getId(), null, $password); $systemP4->login(); // add perforce connection information to the config file. $config->all->resources->perforce = array( 'port' => $adminP4->getPort(), 'user' => $user->getId(), 'password' => $password ); // write it out. $writer = new Zend_Config_Writer_Ini; $writer->write($configFile, $config); } else { // get a copy of the system's connection for later use $systemP4 = $bootstrap->getResource('perforce'); } // create site-specific depot (each site relates 1:1 with a depot) $depot = new P4_Depot($adminP4); $depot->setId($siteForm->getValue('id')) ->setOwner($systemP4->getUser()) ->setType('stream') ->setMap($siteForm->getValue('id') . '/...') ->setDescription('Chronicle depot for ' . $siteForm->getValue('title') . ' site.') ->save(); // disconnect and reconnect to avoid a p4 bug where // its not possible to make a new depot and map it // into a client spec on the same connection. $adminP4->disconnect()->connect(); $systemP4->disconnect()->connect(); // create a new site branch object setting just the id. // we don't set anything that is stored on the config record // as it is too early to read/write the config at this time. $site = new P4Cms_Site; $site->setId('//' . $depot->getId() . '/' . P4Cms_Site::DEFAULT_BRANCH); // create local writable folders for site data. P4Cms_FileUtility::createWritablePath($site->getDataPath()); P4Cms_FileUtility::createWritablePath($site->getWorkspacesPath()); // create the site stream (each site branch relates 1:1 with a stream) $stream = new P4_Stream($adminP4); $stream->setId($site->getId()) ->setName(ucfirst($site->getBranchBasename())) ->setParent('none') ->setType('mainline') ->setOwner($systemP4->getUser()) ->setPaths('share ...') ->save(); // ensure the site is using the system connection; this has // to happen afer we create the site's stream above because // set connection will use the stream to create a temp client $site->setConnection($systemP4); // fetch system administrator user and load personal adapter // we will use the administrator's adapter to setup the roles. $admin = P4Cms_User::fetch($adminP4->getUser(), null, $site->getStorageAdapter()); $admin->setPersonalAdapter( $admin->createPersonalAdapter($adminP4->getTicket(), $site) ); // create default site roles $this->_createSiteRoles($site, $admin); // now that the perforce connection and permissions are established // for the site branch, we can access records and configure it. $site->getConfig() ->setTitle($siteForm->getValue('title')) ->setDescription($siteForm->getValue('description')) ->setUrls($siteForm->getValue('urls')) ->setTheme(P4Cms_Theme::DEFAULT_THEME) ->save(); // temporarily swap out the active site (if there is one) and load // the new site. loading the site configures the package system to // look in the correct file-system paths for modules/themes and sets // the default adapter/connection so that naive reads/writes hit // the correct storage location. $activeSite = P4Cms_Site::hasActive() ? P4Cms_Site::fetchActive() : null; $site->load(); // find optional modules that should be enabled by default. $modules = P4Cms_Module::fetchAllDisabled(); foreach ($modules as $module) { if ($module->getPackageInfo('enableByDefault')) { $module->enable(); } } // notify subscribers of site creation event. P4Cms_PubSub::publish('p4cms.site.created', $site, $admin); // restore the active site. if ($activeSite) { $activeSite->load(); } return $site; } /** * Create user roles for the given site and alter protections table. * * By default, following roles are created: * * member gather all members of this site - having this role * is required for ability to log into the cms * administrator gather all site administrators (having this role * automatically implies super user privileges in Perforce) * * Additionally, a site group (not a role) is created to act as a parent * for all site roles so that they inherit its permissions. The site group * is given read/write access to all of the files in the site depot (with * the exception of the acl file which is read-only). The system user is * configured as the sole user in the site group so that it can have these * permissions, but not actually appear in any roles. The system user is * made the owner of the 'member' group so that the system can add new * users as members. * * @param P4Cms_Site $site site object * @param P4Cms_User $admin site administrator user */ private function _createSiteRoles($site, $admin) { $adapter = $admin->getPersonalAdapter(); $systemUser = P4Cms_User::fetch($site->getConnection()->getUser(), null, $adapter); // create the base site group and add system user to it. $siteGroup = new P4_Group($adapter->getConnection()); $siteGroup->setId($adapter->getProperty(P4Cms_Acl_Role::PARENT_GROUP)) ->setUsers(array($systemUser->getId())) ->save(); // create the administrator role $admins = new P4Cms_Acl_Role; $admins->setAdapter($adapter) ->setId(P4Cms_Acl_Role::ROLE_ADMINISTRATOR) ->setUsers(array($admin)) ->save(); // create the member role $members = new P4Cms_Acl_Role; $members->setAdapter($adapter) ->setId(P4Cms_Acl_Role::ROLE_MEMBER) ->addOwner($systemUser) ->save(); // determine the group id prefix used for this site. $prefix = $siteGroup->getId() . P4Cms_Acl_Role::PREFIX_DELIMITER; // alter protections to grant write access for members // and super access for administrators (for site depot) $depotMap = dirname($site->getId()) . '/...'; P4_Protections::fetch($adapter->getConnection()) ->addProtection('write', 'group', $siteGroup->getId(), '*', $depotMap) ->addProtection('review', 'group', $siteGroup->getId(), '*', $depotMap) ->addProtection('super', 'group', $prefix . $admins->getId(), '*', $depotMap) ->save(); } /** * Get the session namespace object for this setup session. * * @return Zend_Session_Namespace persisted data for this setup session. */ private function _getSession() { if (!isset($this->_session)) { $this->_session = new Zend_Session_Namespace('setup'); // our setup session data shouldn't influence page caching if (P4Cms_Cache::canCache('page')) { P4Cms_Cache::getCache('page')->addIgnoredSessionVariable('setup'); } } return $this->_session; } /** * Setup a local perforce depot in the given path. * Create an administrator user with supplied password. * * @param string $path the p4 root folder. * @return P4_Connection_Interface a connection to the new depot. */ private function _createLocalServer($path) { // make target p4 root folder. P4Cms_FileUtility::createWritablePath($path); // generate p4 port for inetd/rsh mode (escape spaces in path). $p4Port = self::P4D_PORT . self::P4D_BINARY . ' ' . self::P4D_FLAGS . ' ' . str_replace(' ', '\ ', $path); // connect to p4d as 'admin' user. $session = $this->_getSession(); $username = $session->administrator['user']; $email = $session->administrator['email']; $p4 = P4_Connection::factory($p4Port, $username); // generate password for admin user. This is only used prior to setting the // security level; afterwards we'll use the user-supplied password. $password = P4Cms_User::generatePassword(10, 3); // create admin user. $user = new P4_User($p4); $user->setId($username) ->setFullName($username) ->setEmail($email) ->setPassword($password) ->save(); // set server security level to 2 $counter = new P4_Counter($p4); $counter->setId('security'); $counter->setValue(2, true); // update the password for this connection. $p4->run( 'password', null, array( $password, $session->administrator['password'], $session->administrator['password'] ) ); // authenticate $p4->setPassword($session->administrator['password']) ->login(); // disable server-locks - server locks are not needed for our // workflow and the locks directory will grow out of control $p4->run('configure', array('set', 'server.locks.dir=disabled')); return $p4; } /** * Check if setup is running for the first time. * We consider it the 'maiden voyage' if perforce is not yet configured. * * @return bool true if it is the first setup */ protected function _isInitalSetup() { return !$this->getInvokeArg('bootstrap')->hasResource('perforce'); } /** * Determines if given connection is to a local 'rsh' server. * * @param P4_Connection_Interface $connection connection to examine. * @return bool true if connection uses rsh; false otherwise. */ protected function _isRshServer(P4_Connection_Interface $connection) { return strpos($connection->getPort(), 'rsh:') === 0; } /** * Present rsh ports as 'Local Server: /path/to/server/root'. * Remote server ports are returned as-is. * * @param P4_Connection_Interface $connection connection to pretty-up port of. * @return string the friendly port */ protected function _getFriendlyPort(P4_Connection_Interface $connection) { if ($this->_isRshServer($connection)) { $info = $connection->getInfo(); return $info['serverRoot']; } return $connection->getPort(); } }