'', self::statusBoot => 'boot', self::statusInit => 'init', self::statusReady => 'ready', self::statusRender => 'render', self::statusDownload => 'download', self::statusFinished => 'finished', self::statusExited => 'exited', self::statusFailed => 'failed', ); /** * Whether debug mode is on or off * * @var bool * */ protected $debug = false; /** * Fuel manages ProcessWire API variables * * @var Fuel|null * */ protected $fuel = null; /** * Saved path, for includeFile() method * * @var string * */ protected $pathSave = ''; /** * Saved file, for includeFile() method * * @var string * */ protected $fileSave = ''; /** * @var SystemUpdater|null * */ protected $updater = null; /** * ID for this instance of ProcessWire * * @var int * */ protected $instanceID = 0; /** * @var WireShutdown * */ protected $shutdown = null; /** * Create a new ProcessWire instance * * ~~~~~ * // A. Current directory assumed to be root of installation * $wire = new ProcessWire(); * * // B: Specify a Config object as returned by ProcessWire::buildConfig() * $wire = new ProcessWire($config); * * // C: Specify where installation root is * $wire = new ProcessWire('/server/path/'); * * // D: Specify installation root path and URL * $wire = new ProcessWire('/server/path/', '/url/'); * * // E: Specify installation root path, scheme, hostname, URL * $wire = new ProcessWire('/server/path/', 'https://hostname/url/'); * ~~~~~ * * @param Config|string|null $config May be any of the following: * - A Config object as returned from ProcessWire::buildConfig(). * - A string path to PW installation. * - You may optionally omit this argument if current dir is root of PW installation. * @param string $rootURL URL or scheme+host to installation. * - This is only used if $config is omitted or a path string. * - May also include scheme & hostname, i.e. "http://hostname.com/url" to force use of scheme+host. * - If omitted, it is determined automatically. * @throws WireException if given invalid arguments * */ public function __construct($config = null, $rootURL = '/') { parent::__construct(); if(empty($config)) $config = getcwd(); if(is_string($config)) $config = self::buildConfig($config, $rootURL); if(!$config instanceof Config) throw new WireException("No configuration information available"); // this is reset in the $this->setConfig() method based on current debug mode ini_set('display_errors', true); error_reporting(E_ALL | E_STRICT); $config->setWire($this); $this->debug = $config->debug; if($this->debug) Debug::timer('all'); $this->instanceID = self::addInstance($this); $this->setWire($this); $this->fuel = new Fuel(); $this->fuel->set('wire', $this, true); /** @var WireClassLoader $classLoader */ $classLoader = $this->wire('classLoader', new WireClassLoader($this), true); $classLoader->addNamespace((strlen(__NAMESPACE__) ? __NAMESPACE__ : "\\"), PROCESSWIRE_CORE_PATH); if($config->usePageClasses) { $classLoader->addSuffix('Page', $config->paths->classes); } $this->wire('hooks', new WireHooks($this, $config), true); $this->setConfig($config); $this->shutdown = $this->wire(new WireShutdown($config)); $this->setStatus(self::statusBoot); $this->load($config); if(self::getNumInstances() > 1) { // this instance is not handling the request and needs a mock $page API var and pageview /** @var ProcessPageView $view */ $view = $this->fuel->get('modules')->get('ProcessPageView'); $view->execute(false); } } /** * Destruct * */ public function __destruct() { if($this->status < self::statusFinished) { // call finished hook if it wasn’t already $this->status = self::statusExited; $this->finished(array( 'prevStatus' => $this->status, 'exited' => true, )); } } public function __toString() { $str = $this->className() . " "; $str .= self::versionMajor . "." . self::versionMinor . "." . self::versionRevision; if(self::versionSuffix) $str .= " " . self::versionSuffix; if(self::getNumInstances() > 1) $str .= " #$this->instanceID"; return $str; } /** * Populate ProcessWire's configuration with runtime and optional variables * * @param Config $config * */ protected function setConfig(Config $config) { $this->wire('config', $config, true); $this->wire($config->paths); $this->wire($config->urls); // If debug mode is on then echo all errors, if not then disable all error reporting if($config->debug) { error_reporting(E_ALL | E_STRICT); ini_set('display_errors', 1); } else { error_reporting(0); ini_set('display_errors', 0); } ini_set('date.timezone', $config->timezone); ini_set('default_charset','utf-8'); if(!$config->templateExtension) $config->templateExtension = 'php'; if(!$config->httpHost) $config->httpHost = $this->getHttpHost($config); if($config->https === null) { $config->https = (!empty($_SERVER['HTTPS']) && strtolower($_SERVER['HTTPS']) == 'on') || (isset($_SERVER['SERVER_PORT']) && $_SERVER['SERVER_PORT'] == 443) || (isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && strtolower($_SERVER['HTTP_X_FORWARDED_PROTO']) == 'https'); // AWS LOAD BALANCER } $config->ajax = (isset($_SERVER['HTTP_X_REQUESTED_WITH']) && $_SERVER['HTTP_X_REQUESTED_WITH'] == 'XMLHttpRequest'); $config->cli = (!isset($_SERVER['SERVER_SOFTWARE']) && (php_sapi_name() == 'cli' || (isset($_SERVER['argc']) && $_SERVER['argc'] > 0 && is_numeric($_SERVER['argc'])))); $config->modal = empty($_GET['modal']) ? false : abs((int) $_GET['modal']); $config->admin = 0; // 0=not known, determined during ready state $version = self::versionMajor . "." . self::versionMinor . "." . self::versionRevision; $config->version = $version; $config->versionName = trim($version . " " . self::versionSuffix); $config->moduleServiceKey .= str_replace('.', '', $version); // $config->debugIf: optional setting to determine if debug mode should be on or off if($config->debugIf && is_string($config->debugIf)) { $debugIf = trim($config->debugIf); $ip = $config->sessionForceIP; if(empty($ip)) $ip = isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : null; if(strpos($debugIf, '/') === 0 && !empty($ip)) { $debugIf = (bool) @preg_match($debugIf, $ip); // regex IPs } else if(is_callable($debugIf)) { $debugIf = $debugIf(); // callable function to determine debug mode for us } else if(!empty($ip)) { $debugIf = $debugIf === $ip; // exact IP match } else { $debugIf = false; } unset($ip); $config->debug = $debugIf; } if($config->useFunctionsAPI && !function_exists("\\ProcessWire\\pages")) { $file = $config->paths->core . 'FunctionsAPI.php'; /** @noinspection PhpIncludeInspection */ include_once($file); } if($config->installed >= 1547136020) { // installations Jan 10, 2019 and onwards: // make the __('text') translation function return entity encoded text, whether translated or not __(true, 'entityEncode', true); } // check if noHTTPS option is using conditional hostname if($config->noHTTPS && $config->noHTTPS !== true) { $noHTTPS = $config->noHTTPS; $httpHost = $config->httpHost; if(is_string($noHTTPS)) $noHTTPS = array($noHTTPS); if(is_array($noHTTPS)) { $config->noHTTPS = false; foreach($noHTTPS as $host) { if($host === $httpHost) { $config->noHTTPS = true; break; } } } } } /** * Safely determine the HTTP host * * @param Config $config * @return string * */ protected function getHttpHost(Config $config) { $httpHosts = $config->httpHosts; $port = isset($_SERVER['SERVER_PORT']) ? (int) $_SERVER['SERVER_PORT'] : 80; $port = ($port === 80 || $port === 443 ? "" : ":$port"); $host = ''; if(is_array($httpHosts) && count($httpHosts)) { // validate from an allowed whitelist of http hosts $key = false; if(isset($_SERVER['SERVER_NAME'])) { $key = array_search(strtolower($_SERVER['SERVER_NAME']) . $port, $httpHosts, true); } if($key === false && isset($_SERVER['HTTP_HOST'])) { $key = array_search(strtolower($_SERVER['HTTP_HOST']), $httpHosts, true); } if($key === false) { // no valid host found, default to first in whitelist $host = reset($httpHosts); } else { // found a valid host $host = $httpHosts[$key]; } } else { // pull from server_name or http_host and sanitize if(isset($_SERVER['SERVER_NAME']) && $host = $_SERVER['SERVER_NAME']) { // no whitelist available, so defer to server_name $host .= $port; } else if(isset($_SERVER['HTTP_HOST'])) { // fallback to sanitized http_host if server_name not available // note that http_host already includes port if not 80 $host = $_SERVER['HTTP_HOST']; } // sanitize since it did not come from a whitelist if(!preg_match('/^[-a-zA-Z0-9.:]+$/D', $host)) $host = ''; } return $host; } /** * Load’s ProcessWire using the supplied Config and populates all API fuel * * #pw-internal * * @param Config $config * @throws WireDatabaseException|WireException on fatal error * */ public function load(Config $config) { if($this->debug) { Debug::timer('boot'); Debug::timer('boot.load'); } $notices = new Notices(); $this->wire('notices', $notices, true); // first so any API var can send notices $this->wire('urls', $config->urls); // shortcut API var $this->wire('log', new WireLog(), true); $this->wire('sanitizer', new Sanitizer()); $this->wire('datetime', new WireDateTime()); $this->wire('files', new WireFileTools()); $this->wire('mail', new WireMailTools()); try { /** @noinspection PhpUnusedLocalVariableInspection */ $database = $this->wire('database', WireDatabasePDO::getInstance($config), true); /** @noinspection PhpUnusedLocalVariableInspection */ $db = $this->wire('db', new DatabaseMysqli($config), true); } catch(\Exception $e) { // catch and re-throw to prevent DB connect info from ever appearing in debug backtrace $this->trackException($e, true, 'Unable to load WireDatabasePDO'); throw new WireDatabaseException($e->getMessage()); } /** @var WireCache $cache */ $cache = $this->wire('cache', new WireCache(), true); $cacheNames = $config->preloadCacheNames; if($database->getEngine() === 'innodb') $cacheNames[] = 'InnoDB.stopwords'; $cache->preload($cacheNames); $modules = null; try { if($this->debug) Debug::timer('boot.load.modules'); $modules = $this->wire('modules', new Modules($config->paths->modules), true); $modules->addPath($config->paths->siteModules); $modules->setSubstitutes($config->substituteModules); $modules->init(); if($this->debug) Debug::saveTimer('boot.load.modules'); } catch(\Exception $e) { $this->trackException($e, true, 'Unable to load Modules'); if(!$modules) throw new WireException($e->getMessage()); $this->error($e->getMessage()); } $this->updater = $modules->get('SystemUpdater'); if(!$this->updater) { $modules->resetCache(); $this->updater = $modules->get('SystemUpdater'); } $fieldtypes = $this->wire('fieldtypes', new Fieldtypes(), true); $fields = $this->wire('fields', new Fields(), true); $fieldgroups = $this->wire('fieldgroups', new Fieldgroups(), true); $templates = $this->wire('templates', new Templates($fieldgroups), true); $pages = $this->wire('pages', new Pages($this), true); $this->initVar('fieldtypes', $fieldtypes); if($this->debug) Debug::timer('init.fields.templates.fieldgroups'); $this->initVar('fields', $fields); $this->initVar('fieldgroups', $fieldgroups); $this->initVar('templates', $templates); if($this->debug) Debug::saveTimer('init.fields.templates.fieldgroups'); $this->initVar('pages', $pages); if($this->debug) Debug::timer('boot.load.permissions'); if(!$t = $templates->get('permission')) throw new WireException("Missing system template: 'permission'"); /** @noinspection PhpUnusedLocalVariableInspection */ $permissions = $this->wire('permissions', new Permissions($this, $t, $config->permissionsPageID), true); if($this->debug) Debug::saveTimer('boot.load.permissions'); if($this->debug) Debug::timer('boot.load.roles'); if(!$t = $templates->get('role')) throw new WireException("Missing system template: 'role'"); /** @noinspection PhpUnusedLocalVariableInspection */ $roles = $this->wire('roles', new Roles($this, $t, $config->rolesPageID), true); if($this->debug) Debug::saveTimer('boot.load.roles'); if($this->debug) Debug::timer('boot.load.users'); $users = $this->wire('users', new Users($this, $config->userTemplateIDs, $config->usersPageIDs), true); if($this->debug) Debug::saveTimer('boot.load.users'); // the current user can only be determined after the session has been initiated $session = $this->wire('session', new Session($this), true); $this->initVar('session', $session); $this->wire('user', $users->getCurrentUser()); $input = $this->wire('input', new WireInput(), true); if($config->wireInputLazy) $input->setLazy(true); // populate admin URL before modules init() $config->urls->admin = $config->urls->root . ltrim($pages->getPath($config->adminRootPageID), '/'); $notices->init(); if($this->debug) Debug::saveTimer('boot.load', 'includes all boot.load timers'); $this->setStatus(self::statusInit); } /** * Initialize the given API var * * @param string $name * @param Fieldtypes|Fields|Fieldgroups|Templates|Pages|Session $value * */ protected function initVar($name, $value) { if($this->debug) Debug::timer("boot.load.$name"); if($name != 'session') $value->init(); if($this->debug) Debug::saveTimer("boot.load.$name"); } /** * Set the system status to one of the ProcessWire::status* constants * * This also triggers init/ready functions for modules, when applicable. * * @param int $status * @param array $data Associative array of any extra data to pass along to include files as locally scoped vars (3.0.142+) * */ public function setStatus($status, array $data = array()) { /** @var Config $config */ $config = $this->fuel->get('config'); // don’t re-trigger if this state has already been triggered // except that a failed status can be backtracked if($this->status >= $status && $this->status != self::statusFailed) return; $name = isset($this->statusNames[$status]) ? $this->statusNames[$status] : 'unknown'; $path = $config->paths->site; $files = $config->statusFiles; if($status == self::statusReady || $status == self::statusInit) { // before status include file, i.e. "readyBefore" or "initBefore" $nameBefore = $name . 'Before'; $file = empty($files[$nameBefore]) ? null : $path . basename($files[$nameBefore]); if($file !== null) $this->includeFile($file, $data); } // set status to config $prevStatus = $this->status; $this->status = $status; $config->status = $status; // call any relevant internal methods if($status == self::statusInit) { $this->init(); } else if($status == self::statusReady) { $config->admin = $this->isAdmin(); $this->ready(); if($this->debug) Debug::saveTimer('boot', 'includes all boot timers'); } // after status include file, names like 'init', 'ready', etc. $file = empty($files[$name]) ? null : $path . basename($files[$name]); if($file !== null) $this->includeFile($file, $data); if($status == self::statusFinished) { // internal finished always runs after any included finished file $data['prevStatus'] = $prevStatus; $data['maintenance'] = true; $data['exited'] = false; $this->finished($data); } else if($status == self::statusReady) { // additional 'admin' or 'site' options for ready status if(!empty($files['readyAdmin']) && $config->admin === true) { $this->includeFile($path . basename($files['readyAdmin']), $data); } else if(!empty($files['readySite']) && $config->admin === false) { $this->includeFile($path . basename($files['readySite']), $data); } } } /** * Set internal runtime status to failed, with additional info * * #pw-internal * * @param \Throwable $e Exception or Error * @param string $reason * @param null $page * @param string $url * @since 3.0.142 * */ public function setStatusFailed($e, $reason = '', $page = null, $url = '') { static $lastThrowable = null; if($lastThrowable === $e) return; $isException = $e instanceof \Exception; if(!$page instanceof Page) $page = new NullPage(); $this->setStatus(ProcessWire::statusFailed, array( 'throwable' => $e, 'exception' => $isException ? $e : null, 'error' => $isException ? null : $e, 'failPage' => $page, 'reason' => $reason, 'url' => $url, )); $lastThrowable = $e; } /** * Is the current request for a logged-in user within the admin control panel? * * #pw-internal * * @return bool|int Returns boolean true or false, or 0 if not yet able to tell * @since 3.0.142 * */ protected function isAdmin() { /** @var Config $config */ $config = $this->fuel->get('config'); $admin = $config->admin; if(is_bool($admin)) return $admin; $admin = 0; /** @var Page $page */ $page = $this->fuel->get('page'); if(!$page || !$page->id) return 0; if(in_array($page->template->name, $config->adminTemplates)) { /** @var User $user */ $user = $this->fuel->get('user'); if($user) $admin = $user->isLoggedin() ? true : false; } else { $admin = false; } return $admin; } /** * Get the current runtime status/state * * #pw-internal * * @param bool $getName Get the name of the status rather than the integer value? (default=false) * @return int|string * @since 3.0.142 * */ public function getStatus($getName = false) { if(!$getName) return $this->status; return isset($this->statusNames[$this->status]) ? $this->statusNames[$this->status] : 'unknown'; } /** * Hookable init for anyone that wants to hook immediately before any autoload modules initialized or after all modules initialized * * #pw-hooker * */ protected function ___init() { if($this->debug) Debug::timer('boot.modules.autoload.init'); $this->fuel->get('modules')->triggerInit(); if($this->debug) Debug::saveTimer('boot.modules.autoload.init'); } /** * Hookable ready for anyone that wants to hook immediately before any autoload modules ready or after all modules ready * * #pw-hooker * */ protected function ___ready() { if($this->debug) Debug::timer('boot.modules.autoload.ready'); $this->fuel->get('modules')->triggerReady(); $this->updater->ready(); unset($this->updater); if($this->debug) Debug::saveTimer('boot.modules.autoload.ready'); } /** * Hookable ready for anyone that wants to hook when the request is finished * * @param array $data Additional data for hooks (3.0.147+ only): * - `maintenance` (bool): Allow maintenance to run? (default=true) * - `prevStatus` (int): Previous status before finished status (render, download or failed). * - `exited` (bool): True if request was exited before finished (ProcessWire instance destructed before expected). 3.0.180+ * - `redirectUrl` (string): Contains redirect URL only if request ending with redirect (not present otherwise). * - `redirectType` (int): Contains redirect type 301 or 302, only if requestUrl property is also present. * * #pw-hooker * */ protected function ___finished(array $data = array()) { $config = $this->fuel->get('config'); /** @var Config $config */ $session = $this->fuel->get('session'); /** @var Session $session */ $cache = $this->fuel->get('cache'); /** @var WireCache $cache */ $profiler = $this->fuel->get('profiler'); /** @var WireProfilerInterface $profiler */ $exited = !empty($data['exited']); if($data) {} // data for hooks // if a hook cancelled maintenance then exit early if(isset($data['maintenance']) && $data['maintenance'] === false) return; if($session && !$exited) $session->maintenance(); if($cache && !$exited) $cache->maintenance(); if($profiler) $profiler->maintenance(); if($config && !$exited) { if($config->templateCompile) { $compiler = new FileCompiler($config->paths->templates); $this->wire($compiler); $compiler->maintenance(); } if($config->moduleCompile) { $compiler = new FileCompiler($config->paths->siteModules); $this->wire($compiler); $compiler->maintenance(); } } } /** * Set a new API variable * * Alias of $this->wire(), but for setting only, for syntactic convenience. * i.e. $this->wire()->set($key, $value); * * @param string $key API variable name to set * @param Wire|mixed $value Value of API variable * @param bool $lock Whether to lock the value from being overwritten * @return $this */ public function set($key, $value, $lock = false) { $this->wire($key, $value, $lock); return $this; } /** * Get API var directly * * @param string $key * @return mixed * */ public function __get($key) { if($key === 'fuel') return $this->fuel; if($key === 'shutdown') return $this->shutdown; if($key === 'instanceID') return $this->instanceID; $value = $this->fuel->get($key); if($value !== null) return $value; return parent::__get($key); } /** * Include a PHP file, giving it all PW API varibles in scope * * File is executed in the directory where it exists. * * @param string $file Full path and filename * @param array $data Associative array of any extra data to pass along to include file as locally scoped vars * @return bool True if file existed and was included, false if not. * */ protected function includeFile($file, array $data = array()) { if(!file_exists($file)) return false; $this->fileSave = $file; // to prevent any possibility of extract() vars from overwriting $config = $this->fuel->get('config'); /** @var Config $config */ if($this->status > self::statusBoot && $config->templateCompile) { $files = $this->fuel->get('files'); /** @var WireFileTools $files */ if($files) $this->fileSave = $files->compile($file, array('skipIfNamespace' => true)); } $this->pathSave = getcwd(); chdir(dirname($this->fileSave)); if(count($data)) extract($data); $fuel = $this->fuel->getArray(); extract($fuel); /** @noinspection PhpIncludeInspection */ include($this->fileSave); chdir($this->pathSave); $this->fileSave = ''; return true; } /** * Call method * * @param string $method * @param array $arguments * @return mixed * @throws WireException * */ public function __call($method, $arguments) { if(method_exists($this, "___$method")) return parent::__call($method, $arguments); $value = $this->__get($method); if(is_object($value)) return call_user_func_array(array($value, '__invoke'), $arguments); return parent::__call($method, $arguments); } /** * Get an API variable * * #pw-internal * * @param string $name Optional API variable name * @return mixed|null|Fuel * */ public function fuel($name = '') { if(empty($name)) return $this->fuel; return $this->fuel->$name; } /** * Called if any Wire-derived object makes API calls before being wired * * This is for debugging purposes only and is not called unless `ProcessWire::objectNotWired` is hooked. * It is called only once per non-wired object. Uncomment code within to use. * * #pw-internal * * @param Wire $obj Object that accessed API var without being assigned ProcessWire instance * @param string|Wire The $name argument that was passed to $obj->wire($name, $value) * @param mixed $value The $value argument passed to $object->wire($name, $value) * @since 3.0.158 * */ public function _objectNotWired(Wire $obj, $name, $value) { // Uncomment code below to enable (use in admin) /* if(is_string($name) && $this->wire($name)) { $msg = $obj->className() . " accessed API var \$$name before being wired"; $this->warning("$msg\n" . Debug::backtrace(array( 'limit' => 2, 'getString' => true, 'getCnt' => false, 'getFile' => 'basename', ))); } */ } /*** MULTI-INSTANCE *************************************************************************************/ /** * Instances of ProcessWire * * @var array * */ static protected $instances = array(); /** * Current ProcessWire instance * * @var null * */ static protected $currentInstance = null; /** * Instance ID of this ProcessWire instance * * #pw-group-instances * * @return int * */ public function getProcessWireInstanceID() { return $this->instanceID; } /** * Add a ProcessWire instance and return the instance ID * * #pw-group-instances * * @param ProcessWire $wire * @return int * */ protected static function addInstance(ProcessWire $wire) { $id = 0; while(isset(self::$instances[$id])) $id++; self::$instances[$id] = $wire; return $id; } /** * Get all ProcessWire instances * * #pw-group-instances * * @return array * */ public static function getInstances() { return self::$instances; } /** * Return number of instances * * #pw-group-instances * * @return int * */ public static function getNumInstances() { return count(self::$instances); } /** * Get a ProcessWire instance by ID * * #pw-group-instances * * @param int|null $instanceID Omit this argument to return the current instance * @return null|ProcessWire * */ public static function getInstance($instanceID = null) { if(is_null($instanceID)) return self::getCurrentInstance(); return isset(self::$instances[$instanceID]) ? self::$instances[$instanceID] : null; } /** * Get the current ProcessWire instance * * #pw-group-instances * * @return ProcessWire|null * */ public static function getCurrentInstance() { if(is_null(self::$currentInstance)) { $wire = reset(self::$instances); if($wire) self::setCurrentInstance($wire); } return self::$currentInstance; } /** * Set the current ProcessWire instance * * #pw-group-instances * * @param ProcessWire $wire * */ public static function setCurrentInstance(ProcessWire $wire) { self::$currentInstance = $wire; } /** * Remove a ProcessWire instance * * #pw-group-instances * * @param ProcessWire $wire * */ public static function removeInstance(ProcessWire $wire) { foreach(self::$instances as $key => $instance) { if($instance === $wire) { unset(self::$instances[$key]); if(self::$currentInstance === $wire) self::$currentInstance = null; break; } } } /** * Get root path, check it, and optionally auto-detect it if not provided * * @param bool|string $rootPath Root path if already known, in which case we’ll just modify as needed * …or specify boolean true to get absolute root path, which disregards any symbolic links to core. * @return string * */ public static function getRootPath($rootPath = '') { if($rootPath !== true && strpos($rootPath, '..') !== false) { $rootPath = realpath($rootPath); } if(empty($rootPath) && !empty($_SERVER['SCRIPT_FILENAME'])) { // first try to determine from the script filename $parts = explode(DIRECTORY_SEPARATOR, $_SERVER['SCRIPT_FILENAME']); array_pop($parts); // most likely: index.php $rootPath = implode('/', $parts) . '/'; if(!file_exists($rootPath . 'wire/core/ProcessWire.php')) $rootPath = ''; } if(empty($rootPath) || $rootPath === true) { // if unable to determine from script filename, attempt to determine from current file $parts = explode(DIRECTORY_SEPARATOR, __FILE__); $parts = array_slice($parts, 0, -3); // removes "ProcessWire.php", "core" and "wire" $rootPath = implode('/', $parts) . '/'; } if(DIRECTORY_SEPARATOR != '/') { $rootPath = str_replace(DIRECTORY_SEPARATOR, '/', $rootPath); } return $rootPath; } /** * Static method to build a Config object for booting ProcessWire * * @param string $rootPath Path to root of installation where ProcessWire's index.php file is located. * @param string $rootURL Should be specified only for secondary ProcessWire instances. * May also include scheme & hostname, i.e. "http://hostname.com/url" to force use of scheme+host. * @param array $options Options to modify default behaviors (experimental): * - `siteDir` (string): Name of "site" directory in $rootPath that contains site's config.php, no slashes (default="site"). * @return Config * */ public static function buildConfig($rootPath = '', $rootURL = null, array $options = array()) { $rootPath = self::getRootPath($rootPath); $httpHost = ''; $scheme = ''; $siteDir = isset($options['siteDir']) ? $options['siteDir'] : 'site'; $cfg = array('dbName' => ''); if($rootURL && strpos($rootURL, '://')) { // rootURL is specifying scheme and hostname list($scheme, $httpHost) = explode('://', $rootURL); if(strpos($httpHost, '/')) { list($httpHost, $rootURL) = explode('/', $httpHost, 2); $rootURL = "/$rootURL"; } else { $rootURL = '/'; } $scheme = strtolower($scheme); $httpHost = strtolower($httpHost); } $rootPath = rtrim($rootPath, '/'); $_rootURL = $rootURL; if(is_null($rootURL)) $rootURL = '/'; // check what rootPath is referring to if(strpos($rootPath, "/$siteDir")) { $parts = explode('/', $rootPath); $testDir = array_pop($parts); if(($testDir === $siteDir || strpos($testDir, 'site-') === 0) && is_file("$rootPath/config.php")) { // rootPath was given as a /site/ directory rather than root directory $rootPath = implode('/', $parts); // remove siteDir from rootPath $siteDir = $testDir; // set proper siteDir } } if(isset($_SERVER['HTTP_HOST'])) { $host = $httpHost ? $httpHost : strtolower($_SERVER['HTTP_HOST']); // when serving pages from a web server if(is_null($_rootURL)) $rootURL = rtrim(dirname($_SERVER['SCRIPT_NAME']), "/\\") . '/'; $realScriptFile = empty($_SERVER['SCRIPT_FILENAME']) ? '' : realpath($_SERVER['SCRIPT_FILENAME']); $realIndexFile = realpath($rootPath . "/index.php"); // check if we're being included from another script and adjust the rootPath accordingly $sf = empty($realScriptFile) ? '' : dirname($realScriptFile); $f = dirname($realIndexFile); if($sf && $sf != $f && strpos($sf, $f) === 0) { $x = rtrim(substr($sf, strlen($f)), '/'); if(is_null($_rootURL)) $rootURL = substr($rootURL, 0, strlen($rootURL) - strlen($x)); } unset($sf, $f, $x); // when internal is true, we are not being called by an external script $cfg['internal'] = strtolower($realIndexFile) == strtolower($realScriptFile); } else { // when included from another app or command line script $cfg['internal'] = false; $host = ''; } // Allow for an optional /index.config.php file that can point to a different site configuration per domain/host. $indexConfigFile = $rootPath . "/index.config.php"; if(is_file($indexConfigFile) && !function_exists("\\ProcessWire\\ProcessWireHostSiteConfig") && !function_exists("\\ProcessWireHostSiteConfig")) { // optional config file is present in root $hostConfig = array(); /** @noinspection PhpIncludeInspection */ @include($indexConfigFile); if(function_exists("\\ProcessWire\\ProcessWireHostSiteConfig")) { $hostConfig = ProcessWireHostSiteConfig(); } else if(function_exists("\\ProcessWireHostSiteConfig")) { $hostConfig = \ProcessWireHostSiteConfig(); } if($host && isset($hostConfig[$host])) { $siteDir = $hostConfig[$host]; } else if(isset($hostConfig['*'])) { $siteDir = $hostConfig['*']; // default override } } // other default directories $sitePath = $rootPath . "/$siteDir/"; $wireDir = "wire"; $coreDir = "$wireDir/core"; $assetsDir = "$siteDir/assets"; $adminTplDir = 'templates-admin'; // create new Config instance $cfg['urls'] = new Paths($rootURL); $cfg['urls']->data(array( 'wire' => "$wireDir/", 'site' => "$siteDir/", 'modules' => "$wireDir/modules/", 'siteModules' => "$siteDir/modules/", 'core' => "$coreDir/", 'assets' => "$assetsDir/", 'cache' => "$assetsDir/cache/", 'logs' => "$assetsDir/logs/", 'files' => "$assetsDir/files/", 'tmp' => "$assetsDir/tmp/", 'templates' => "$siteDir/templates/", 'fieldTemplates' => "$siteDir/templates/fields/", 'adminTemplates' => "$wireDir/$adminTplDir/", ), true); $cfg['paths'] = clone $cfg['urls']; $cfg['paths']->set('root', $rootPath . '/'); $cfg['paths']->data('sessions', $cfg['paths']->assets . "sessions/"); $cfg['paths']->data('classes', $cfg['paths']->site . "classes/"); // Styles and scripts are CSS and JS files, as used by the admin application. // But reserved here if needed by other apps and templates. $cfg['styles'] = new FilenameArray(); $cfg['scripts'] = new FilenameArray(); $config = new Config(); $config->setTrackChanges(false); $config->data($cfg, true); // Include system config defaults /** @noinspection PhpIncludeInspection */ require("$rootPath/$wireDir/config.php"); // Include site-specific config settings $configFile = $sitePath . "config.php"; $configFileDev = $sitePath . "config-dev.php"; if(is_file($configFileDev)) { /** @noinspection PhpIncludeInspection */ @require($configFileDev); } else if(is_file($configFile)) { /** @noinspection PhpIncludeInspection */ @require($configFile); } if($httpHost) { $config->httpHost = $httpHost; if(!in_array($httpHost, $config->httpHosts)) $config->httpHosts[] = $httpHost; } if($scheme) $config->https = ($scheme === 'https'); return $config; } }