value pairs that can be used by any Resource
* or Element.
*/
public $placeholders= array ();
/**
* @var modResource An instance of the current modResource controlling the
* request.
*/
public $resource= null;
/**
* @var string The preferred Culture key for the current request.
*/
public $cultureKey= '';
/**
* @var modLexicon Represents a localized dictionary of common words and phrases.
*/
public $lexicon= null;
/**
* @var modUser The current user object, if one is authenticated for the
* current request and context.
*/
public $user= null;
/**
* @var array Represents the modContentType instances that can be delivered
* by this modX deployment.
*/
public $contentTypes= null;
/**
* @var mixed The resource id or alias being requested.
*/
public $resourceIdentifier= null;
/**
* @var string The method to use to locate the Resource, 'id' or 'alias'.
*/
public $resourceMethod= null;
/**
* @var boolean Indicates if the resource was generated during this request.
*/
public $resourceGenerated= false;
/**
* @var array Version information for this MODX deployment.
*/
public $version= null;
/**
* @var boolean Indicates if modX has been successfully initialized for a
* modContext.
*/
protected $_initialized= false;
/**
* @var array An array of javascript content to be inserted into the HEAD
* of an HTML resource.
*/
public $sjscripts= array ();
/**
* @var array An array of javascript content to be inserted into the BODY
* of an HTML resource.
*/
public $jscripts= array ();
/**
* @var array An array of already loaded javascript/css code
*/
public $loadedjscripts= array ();
/**
* @var string Stores the virtual path for a request to MODX if the
* friendly_alias_paths option is enabled.
*/
public $virtualDir;
/**
* @var modErrorHandler|object An error_handler for the modX instance.
*/
public $errorHandler= null;
/**
* @var modError An error response class for the request
*/
public $error = null;
/**
* @var modManagerController A controller object that represents a page in the manager
*/
public $controller = null;
/**
* @var modRegistry $registry
*/
public $registry;
/**
* @var modMail $mail
*/
public $mail;
/**
* @var modRestClient $rest
*/
public $rest;
/**
* @var array $processors An array of loaded processors and their class name
*/
public $processors = array();
/**
* @var array An array of regex patterns regulary cleansed from content.
*/
public $sanitizePatterns = array(
'scripts' => '@@si',
'entities' => '@(\d+);@e',
'tags1' => '@\[\[(.*?)\]\]@si',
'tags2' => '@(\[\[|\]\])@si',
);
/**
* @var integer An integer representing the session state of modX.
*/
protected $_sessionState= modX::SESSION_STATE_UNINITIALIZED;
/**
* @var array A config array that stores the bootstrap settings.
*/
protected $_config= null;
/**
* @var array A config array that stores the system-wide settings.
*/
public $_systemConfig= array();
/**
* @var array A config array that stores the user settings.
*/
public $_userConfig= array();
/**
* @var int The current log sequence
*/
protected $_logSequence= 0;
/**
* @var array An array of plugins that have been cached for execution
*/
public $pluginCache= array();
/**
* @var array The elemnt source cache used for caching and preparing Element data
*/
public $sourceCache= array(
'modChunk' => array()
,'modSnippet' => array()
,'modTemplateVar' => array()
);
/** @var modCacheManager $cacheManager */
public $cacheManager;
/**
* @deprecated
* @var modSystemEvent $Event
*/
public $Event= null;
/**
* @deprecated
* @var string $documentOutput
*/
public $documentOutput= null;
/**
* Harden the environment against common security flaws.
*
* @static
*/
public static function protect() {
if (isset ($_SERVER['QUERY_STRING']) && strpos(urldecode($_SERVER['QUERY_STRING']), chr(0)) !== false) die();
if (@ ini_get('register_globals') && isset ($_REQUEST)) {
while (list($key, $value)= each($_REQUEST)) {
$GLOBALS[$key] = null;
unset ($GLOBALS[$key]);
}
}
$targets= array ('PHP_SELF', 'HTTP_USER_AGENT', 'HTTP_REFERER', 'QUERY_STRING');
foreach ($targets as $target) {
$_SERVER[$target] = isset ($_SERVER[$target]) ? htmlspecialchars($_SERVER[$target], ENT_QUOTES) : null;
}
}
/**
* Sanitize values of an array using regular expression patterns.
*
* @static
* @param array $target The target array to sanitize.
* @param array|string $patterns A regular expression pattern, or array of
* regular expression patterns to apply to all values of the target.
* @param integer $depth The maximum recursive depth to sanitize if the
* target contains values that are arrays.
* @param integer $nesting The maximum nesting level in which to dive
* @return array The sanitized array.
*/
public static function sanitize(array &$target, array $patterns= array(), $depth= 99, $nesting= 10) {
foreach ($target as $key => &$value) {
if (is_array($value) && $depth > 0) {
modX :: sanitize($value, $patterns, $depth-1);
} elseif (is_string($value)) {
if (!empty($patterns)) {
$iteration = 1;
$nesting = ((integer) $nesting ? (integer) $nesting : 10);
while ($iteration <= $nesting) {
$matched = false;
foreach ($patterns as $pattern) {
$patternIterator = 1;
$patternMatches = preg_match($pattern, $value);
if ($patternMatches > 0) {
$matched = true;
while ($patternMatches > 0 && $patternIterator <= $nesting) {
$value= preg_replace($pattern, '', $value);
$patternMatches = preg_match($pattern, $value);
}
}
}
if (!$matched) {
break;
}
$iteration++;
}
}
if (get_magic_quotes_gpc()) {
$target[$key]= stripslashes($value);
} else {
$target[$key]= $value;
}
}
}
return $target;
}
/**
* Sanitizes a string
*
* @param string $str The string to sanitize
* @param array $chars An array of chars to remove
* @param string $allowedTags A list of tags to allow.
* @return string The sanitized string.
*/
public function sanitizeString($str,$chars = array('/',"'",'"','(',')',';','>','<'),$allowedTags = '') {
$str = str_replace($chars,'',strip_tags($str,$allowedTags));
return preg_replace("/[^A-Za-z0-9_\-\.\/\\p{L}[\p{L} _.-]/u",'',$str);
}
/**
* Turn an associative or numeric array into a valid query string.
*
* @static
* @param array $parameters An associative or numeric-indexed array of parameters.
* @param string $numPrefix A string prefix added to the numeric-indexed array keys.
* Ignored if associative array is used.
* @param string $argSeparator The string used to separate arguments in the resulting query string.
* @return string A valid query string representing the parameters.
*/
public static function toQueryString(array $parameters = array(), $numPrefix = '', $argSeparator = '&') {
return http_build_query($parameters, $numPrefix, $argSeparator);
}
/**
* Create, retrieve, or update specific modX instances.
*
* @static
* @param string|int|null $id An optional identifier for the instance. If not set
* a uniqid will be generated and used as the key for the instance.
* @param array|null $config An optional array of config data for the instance.
* @param bool $forceNew If true a new instance will be created even if an instance
* with the provided $id already exists in modX::$instances.
* @return modX An instance of modX.
* @throws xPDOException
*/
public static function getInstance($id = null, $config = null, $forceNew = false) {
$class = __CLASS__;
if (is_null($id)) {
if (!is_null($config) || $forceNew || empty(self::$instances)) {
$id = uniqid($class);
} else {
$instances =& self::$instances;
$id = key($instances);
}
}
if ($forceNew || !array_key_exists($id, self::$instances) || !(self::$instances[$id] instanceof $class)) {
self::$instances[$id] = new $class('', $config);
} elseif (self::$instances[$id] instanceof $class && is_array($config)) {
self::$instances[$id]->config = array_merge(self::$instances[$id]->config, $config);
}
if (!(self::$instances[$id] instanceof $class)) {
throw new xPDOException("Error getting {$class} instance, id = {$id}");
}
return self::$instances[$id];
}
/**
* Construct a new modX instance.
*
* @param string $configPath An absolute filesystem path to look for the config file.
* @param array $options xPDO options that can be passed to the instance.
* @param array $driverOptions PDO driver options that can be passed to the instance.
* @return modX A new modX instance.
*/
public function __construct($configPath= '', $options = null, $driverOptions = null) {
try {
$options = $this->loadConfig($configPath, $options, $driverOptions);
parent :: __construct(
null,
null,
null,
$options,
null
);
$this->setLogLevel($this->getOption('log_level', null, xPDO::LOG_LEVEL_ERROR));
$this->setLogTarget($this->getOption('log_target', null, 'FILE'));
$debug = $this->getOption('debug');
if (!is_null($debug) && $debug !== '') {
$this->setDebug($debug);
}
$this->setPackage('modx', MODX_CORE_PATH . 'model/');
$this->loadClass('modAccess');
$this->loadClass('modAccessibleObject');
$this->loadClass('modAccessibleSimpleObject');
$this->loadClass('modResource');
$this->loadClass('modElement');
$this->loadClass('modScript');
$this->loadClass('modPrincipal');
$this->loadClass('modUser');
$this->loadClass('sources.modMediaSource');
} catch (xPDOException $xe) {
$this->sendError('unavailable', array('error_message' => $xe->getMessage()));
} catch (Exception $e) {
$this->sendError('unavailable', array('error_message' => $e->getMessage()));
}
}
/**
* Load the modX configuration when creating an instance of modX.
*
* @param string $configPath An absolute path location to search for the modX config file.
* @param array $data Data provided to initialize the instance with, overriding config file entries.
* @param null $driverOptions Driver options for the primary connection.
* @return array The merged config data ready for use by the modX::__construct() method.
*/
protected function loadConfig($configPath = '', $data = array(), $driverOptions = null) {
if (!is_array($data)) $data = array();
modX :: protect();
if (!defined('MODX_CONFIG_KEY')) {
define('MODX_CONFIG_KEY', 'config');
}
if (empty ($configPath)) {
$configPath= MODX_CORE_PATH . 'config/';
}
global $database_dsn, $database_user, $database_password, $config_options, $driver_options, $table_prefix, $site_id, $uuid;
if (file_exists($configPath . MODX_CONFIG_KEY . '.inc.php') && include ($configPath . MODX_CONFIG_KEY . '.inc.php')) {
$cachePath= MODX_CORE_PATH . 'cache/';
if (MODX_CONFIG_KEY !== 'config') $cachePath .= MODX_CONFIG_KEY . '/';
if (!is_array($config_options)) $config_options = array();
if (!is_array($driver_options)) $driver_options = array();
$data = array_merge(
array (
xPDO::OPT_CACHE_KEY => 'default',
xPDO::OPT_CACHE_HANDLER => 'xPDOFileCache',
xPDO::OPT_CACHE_PATH => $cachePath,
xPDO::OPT_TABLE_PREFIX => $table_prefix,
xPDO::OPT_HYDRATE_FIELDS => true,
xPDO::OPT_HYDRATE_RELATED_OBJECTS => true,
xPDO::OPT_HYDRATE_ADHOC_FIELDS => true,
xPDO::OPT_VALIDATOR_CLASS => 'validation.modValidator',
xPDO::OPT_VALIDATE_ON_SAVE => true,
'cache_system_settings' => true,
'cache_system_settings_key' => 'system_settings'
),
$config_options,
$data
);
$primaryConnection = array(
'dsn' => $database_dsn,
'username' => $database_user,
'password' => $database_password,
'options' => array(
xPDO::OPT_CONN_MUTABLE => isset($data[xPDO::OPT_CONN_MUTABLE]) ? (boolean) $data[xPDO::OPT_CONN_MUTABLE] : true,
),
'driverOptions' => $driver_options
);
if (!array_key_exists(xPDO::OPT_CONNECTIONS, $data) || !is_array($data[xPDO::OPT_CONNECTIONS])) {
$data[xPDO::OPT_CONNECTIONS] = array();
}
array_unshift($data[xPDO::OPT_CONNECTIONS], $primaryConnection);
if (!empty($site_id)) $this->site_id = $site_id;
if (!empty($uuid)) $this->uuid = $uuid;
} else {
throw new xPDOException("Could not load MODX config file.");
}
return $data;
}
/**
* Initializes the modX engine.
*
* This includes preparing the session, pre-loading some common
* classes and objects, the current site and context settings, extension
* packages used to override session handling, error handling, or other
* initialization classes
*
* @param string $contextKey Indicates the context to initialize.
* @param array|null $options An array of options for the initialization.
* @return bool True if initialized successfully, or already initialized.
*/
public function initialize($contextKey= 'web', $options = null) {
if (!$this->_initialized) {
if (!$this->startTime) {
$this->startTime= $this->getMicroTime();
}
$this->getCacheManager();
$this->getConfig();
$this->_initContext($contextKey, false, $options);
$this->_loadExtensionPackages($options);
$this->_initSession($options);
$this->_initErrorHandler($options);
$this->_initCulture($options);
$this->getService('registry', 'registry.modRegistry');
if (is_array ($this->config)) {
$this->setPlaceholders($this->config, '+');
}
$this->_initialized= true;
}
return $this->_initialized;
}
/**
* Loads any specified extension packages.
*
* @param array|null An optional array of options that can contain additional
* extension packages which will be merged with those specified via config.
*/
protected function _loadExtensionPackages($options = null) {
$extPackages = $this->getOption('extension_packages');
$extPackages = $this->fromJSON($extPackages);
if (!is_array($extPackages)) $extPackages = array();
if (is_array($options) && array_key_exists('extension_packages', $options)) {
$optPackages = $this->fromJSON($options['extension_packages']);
if (is_array($optPackages)) {
$extPackages = array_merge($extPackages, $optPackages);
}
}
if (!empty($extPackages)) {
foreach ($extPackages as $extPackage) {
if (!is_array($extPackage)) continue;
foreach ($extPackage as $packageName => $package) {
if (!empty($package) && !empty($package['path'])) {
$package['tablePrefix'] = !empty($package['tablePrefix']) ? $package['tablePrefix'] : null;
$package['path'] = str_replace(array(
'[[++core_path]]',
'[[++base_path]]',
'[[++assets_path]]',
'[[++manager_path]]',
),array(
$this->config['core_path'],
$this->config['base_path'],
$this->config['assets_path'],
$this->config['manager_path'],
),$package['path']);
$this->addPackage($packageName,$package['path'],$package['tablePrefix']);
if (!empty($package['serviceName']) && !empty($package['serviceClass'])) {
$packagePath = str_replace('//','/',$package['path'].$packageName.'/');
$this->getService($package['serviceName'],$package['serviceClass'],$packagePath);
}
}
}
}
}
}
/**
* Sets the debugging features of the modX instance.
*
* @param boolean|int $debug Boolean or bitwise integer describing the
* debug state and/or PHP error reporting level.
* @param boolean $stopOnNotice Indicates if processing should stop when
* encountering PHP errors of type E_NOTICE.
* @return boolean|int The previous value.
*/
public function setDebug($debug= true) {
$oldValue= $this->getDebug();
if ($debug === true) {
error_reporting(-1);
parent :: setDebug(true);
} elseif ($debug === false) {
error_reporting(0);
parent :: setDebug(false);
} else {
error_reporting(intval($debug));
parent :: setDebug(intval($debug));
}
return $oldValue;
}
/**
* Get an extended xPDOCacheManager instance responsible for MODX caching.
*
* @param string $class The class name of the cache manager to load
* @param array $options An array of options to send to the cache manager instance
* @return modCacheManager A modCacheManager instance registered for this modX instance.
*/
public function getCacheManager($class= 'cache.xPDOCacheManager', $options = array('path' => XPDO_CORE_PATH, 'ignorePkg' => true)) {
if ($this->cacheManager === null) {
if ($this->loadClass($class, $options['path'], $options['ignorePkg'], true)) {
$cacheManagerClass= $this->getOption('modCacheManager.class', null, 'modCacheManager');
if ($className= $this->loadClass($cacheManagerClass, '', false, true)) {
if ($this->cacheManager= new $className ($this)) {
$this->_cacheEnabled= true;
}
}
}
}
return $this->cacheManager;
}
/**
* Gets the MODX parser.
*
* Returns an instance of modParser responsible for parsing tags in element
* content, performing actions, returning content and/or sending other responses
* in the process.
*
* @return modParser The modParser for this modX instance.
*/
public function getParser() {
return $this->getService('parser', $this->getOption('parser_class', null, 'modParser'), $this->getOption('parser_class_path', null, ''));
}
/**
* Gets all of the parent resource ids for a given resource.
*
* @param integer $id The resource id for the starting node.
* @param integer $height How many levels max to search for parents (default 10).
* @param array $options An array of filtering options, such as 'context' to specify the context to grab from
* @return array An array of all the parent resource ids for the specified resource.
*/
public function getParentIds($id= null, $height= 10,array $options = array()) {
$parentId= 0;
$parents= array ();
if ($id && $height > 0) {
$context = '';
if (!empty($options['context'])) {
$this->getContext($options['context']);
$context = $options['context'];
}
$resourceMap = !empty($context) && !empty($this->contexts[$context]->resourceMap) ? $this->contexts[$context]->resourceMap : $this->resourceMap;
foreach ($resourceMap as $parentId => $mapNode) {
if (array_search($id, $mapNode) !== false) {
$parents[]= $parentId;
break;
}
}
if ($parentId && !empty($parents)) {
$height--;
$parents= array_merge($parents, $this->getParentIds($parentId,$height,$options));
}
}
return $parents;
}
/**
* Gets all of the child resource ids for a given resource.
*
* @see getTree for hierarchical node results
* @param integer $id The resource id for the starting node.
* @param integer $depth How many levels max to search for children (default 10).
* @param array $options An array of filtering options, such as 'context' to specify the context to grab from
* @return array An array of all the child resource ids for the specified resource.
*/
public function getChildIds($id= null, $depth= 10,array $options = array()) {
$children= array ();
if ($id !== null && intval($depth) >= 1) {
$id= is_int($id) ? $id : intval($id);
$context = '';
if (!empty($options['context'])) {
$this->getContext($options['context']);
$context = $options['context'];
}
$resourceMap = !empty($context) && !empty($this->contexts[$context]->resourceMap) ? $this->contexts[$context]->resourceMap : $this->resourceMap;
if (isset ($resourceMap["{$id}"])) {
if ($children= $resourceMap["{$id}"]) {
foreach ($children as $child) {
$processDepth = $depth - 1;
if ($c= $this->getChildIds($child,$processDepth,$options)) {
$children= array_merge($children, $c);
}
}
}
}
}
return $children;
}
/**
* Get a site tree from a single or multiple modResource instances.
*
* @see getChildIds for linear results
* @param int|array $id A single or multiple modResource ids to build the
* tree from.
* @param int $depth The maximum depth to build the tree (default 10).
* @return array An array containing the tree structure.
*/
public function getTree($id= null, $depth= 10) {
$tree= array ();
if ($id !== null) {
if (is_array ($id)) {
foreach ($id as $k => $v) {
$tree[$v]= $this->getTree($v, $depth);
}
}
elseif ($branch= $this->getChildIds($id, 1)) {
foreach ($branch as $key => $child) {
if ($depth > 0 && $leaf= $this->getTree($child, $depth--)) {
$tree[$child]= $leaf;
} else {
$tree[$child]= $child;
}
}
}
}
return $tree;
}
/**
* Sets a placeholder value.
*
* @param string $key The unique string key which identifies the
* placeholder.
* @param mixed $value The value to set the placeholder to.
*/
public function setPlaceholder($key, $value) {
if (is_string($key)) {
$this->placeholders["{$key}"]= $value;
}
}
/**
* Sets a collection of placeholders stored in an array or as object vars.
*
* An optional namespace parameter can be prepended to each placeholder key in the collection,
* to isolate the collection of placeholders.
*
* Note that unlike toPlaceholders(), this function does not add separators between the
* namespace and the placeholder key. Use toPlaceholders() when working with multi-dimensional
* arrays or objects with variables other than scalars so each level gets delimited by a
* separator.
*
* @param array|object $placeholders An array of values or object to set as placeholders.
* @param string $namespace A namespace prefix to prepend to each placeholder key.
*/
public function setPlaceholders($placeholders, $namespace= '') {
$this->toPlaceholders($placeholders, $namespace, '');
}
/**
* Sets placeholders from values stored in arrays and objects.
*
* Each recursive level adds to the prefix, building an access path using an optional separator.
*
* @param array|object $subject An array or object to process.
* @param string $prefix An optional prefix to be prepended to the placeholder keys. Recursive
* calls prepend the parent keys.
* @param string $separator A separator to place in between the prefixes and keys. Default is a
* dot or period: '.'.
* @param boolean $restore Set to true if you want overwritten placeholder values returned.
* @return array A multi-dimensional array containing up to two elements: 'keys' which always
* contains an array of placeholder keys that were set, and optionally, if the restore parameter
* is true, 'restore' containing an array of placeholder values that were overwritten by the method.
*/
public function toPlaceholders($subject, $prefix= '', $separator= '.', $restore= false) {
$keys = array();
$restored = array();
if (is_object($subject)) {
if ($subject instanceof xPDOObject) {
$subject= $subject->toArray();
} else {
$subject= get_object_vars($subject);
}
}
if (is_array($subject)) {
foreach ($subject as $key => $value) {
$rv = $this->toPlaceholder($key, $value, $prefix, $separator, $restore);
if (isset($rv['keys'])) {
foreach ($rv['keys'] as $rvKey) $keys[] = $rvKey;
}
if ($restore === true && isset($rv['restore'])) {
$restored = array_merge($restored, $rv['restore']);
}
}
}
$return = array('keys' => $keys);
if ($restore === true) $return['restore'] = $restored;
return $return;
}
/**
* Recursively validates and sets placeholders appropriate to the value type passed.
*
* @param string $key The key identifying the value.
* @param mixed $value The value to set.
* @param string $prefix A string prefix to prepend to the key. Recursive calls prepend the
* parent keys as well.
* @param string $separator A separator placed in between the prefix and the key. Default is a
* dot or period: '.'.
* @param boolean $restore Set to true if you want overwritten placeholder values returned.
* @return array A multi-dimensional array containing up to two elements: 'keys' which always
* contains an array of placeholder keys that were set, and optionally, if the restore parameter
* is true, 'restore' containing an array of placeholder values that were overwritten by the method.
*/
public function toPlaceholder($key, $value, $prefix= '', $separator= '.', $restore= false) {
$return = array('keys' => array());
if ($restore === true) $return['restore'] = array();
if (!empty($prefix) && !empty($separator)) {
$prefix .= $separator;
}
if (is_array($value) || is_object($value)) {
$return = $this->toPlaceholders($value, "{$prefix}{$key}", $separator, $restore);
} elseif (is_scalar($value)) {
$return['keys'][] = "{$prefix}{$key}";
if ($restore === true && array_key_exists("{$prefix}{$key}", $this->placeholders)) {
$return['restore']["{$prefix}{$key}"] = $this->getPlaceholder("{$prefix}{$key}");
}
$this->setPlaceholder("{$prefix}{$key}", $value);
}
return $return;
}
/**
* Get a placeholder value by key.
*
* @param string $key The key of the placeholder to a return a value from.
* @return mixed The value of the requested placeholder, or an empty string if not located.
*/
public function getPlaceholder($key) {
$placeholder= null;
if (is_string($key) && array_key_exists($key, $this->placeholders)) {
$placeholder= & $this->placeholders["{$key}"];
}
return $placeholder;
}
/**
* Unset a placeholder value by key.
*
* @param string $key The key of the placeholder to unset.
*/
public function unsetPlaceholder($key) {
if (is_string($key) && array_key_exists($key, $this->placeholders)) {
unset($this->placeholders[$key]);
}
}
/**
* Unset multiple placeholders, either by prefix or an array of keys.
*
* @param string|array $keys A string prefix or an array of keys indicating
* the placeholders to unset.
*/
public function unsetPlaceholders($keys) {
if (is_array($keys)) {
foreach ($keys as $key) {
if (is_string($key)) $this->unsetPlaceholder($key);
if (is_array($key)) $this->unsetPlaceholders($key);
}
} elseif (is_string($keys)) {
$placeholderKeys = array_keys($this->placeholders);
foreach ($placeholderKeys as $key) {
if (strpos($key, $keys) === 0) $this->unsetPlaceholder($key);
}
}
}
/**
* Generates a URL representing a specified resource.
*
* @param integer $id The id of a resource.
* @param string $context Specifies a context to limit URL generation to.
* @param string $args A query string to append to the generated URL.
* @param mixed $scheme The scheme indicates in what format the URL is generated.
*
* -1 : (default value) URL is relative to site_url * 0 : see http * 1 : see https * full : URL is absolute, prepended with site_url from config * abs : URL is absolute, prepended with base_url from config * http : URL is absolute, forced to http scheme * https : URL is absolute, forced to https scheme ** @param array $options An array of options for generating the Resource URL. * @return string The URL for the resource. */ public function makeUrl($id, $context= '', $args= '', $scheme= -1, array $options= array()) { $url= ''; if ($validid = intval($id)) { $id = $validid; if ($context == '' || $this->context->get('key') == $context) { $url= $this->context->makeUrl($id, $args, $scheme, $options); } if (empty($url) && ($context !== $this->context->get('key'))) { $ctx= null; if ($context == '') { /** @var PDOStatement $stmt */ if ($stmt = $this->prepare("SELECT context_key FROM " . $this->getTableName('modResource') . " WHERE id = :id")) { $stmt->bindValue(':id', $id); if ($contextKey = $this->getValue($stmt)) { $ctx = $this->getContext($contextKey); } } } else { $ctx = $this->getContext($context); } if ($ctx) { $url= $ctx->makeUrl($id, $args, 'full', $options); } } if (!empty($url) && $this->getOption('xhtml_urls', $options, false)) { $url= preg_replace("/&(?!amp;)/","&", $url); } } else { $this->log(modX::LOG_LEVEL_ERROR, '`' . $id . '` is not a valid integer and may not be passed to makeUrl()'); } return $url; } public function findResource($uri, $context = '') { $resourceId = false; if (empty($context) && isset($this->context)) $context = $this->context->get('key'); if (!empty($context) && (!empty($uri) || $uri === '0')) { $useAliasMap = (boolean) $this->getOption('cache_alias_map', null, false); if ($useAliasMap) { if (isset($this->context) && $this->context->get('key') === $context && array_key_exists($uri, $this->aliasMap)) { $resourceId = (integer) $this->aliasMap[$uri]; } elseif ($ctx = $this->getContext($context)) { $useAliasMap = $ctx->getOption('cache_alias_map', false) && array_key_exists($uri, $ctx->aliasMap); if ($useAliasMap && array_key_exists($uri, $ctx->aliasMap)) { $resourceId = (integer) $ctx->aliasMap[$uri]; } } } if (!$resourceId && !$useAliasMap) { $query = $this->newQuery('modResource', array('context_key' => $context, 'uri' => $uri, 'deleted' => false)); $query->select($this->getSelectColumns('modResource', '', '', array('id'))); $stmt = $query->prepare(); if ($stmt) { $value = $this->getValue($stmt); if ($value) { $resourceId = $value; } } } } return $resourceId; } /** * Send the user to a type-specific core error page and halt PHP execution. * * @param string $type The type of error to present. * @param array $options An array of options to provide for the error file. */ public function sendError($type = '', $options = array()) { if (!is_string($type) || empty($type)) $type = $this->getOption('error_type', $options, 'unavailable'); while (ob_get_level() && @ob_end_clean()) {} if (!XPDO_CLI_MODE) { $errorPageTitle = $this->getOption('error_pagetitle', $options, 'Error 503: Service temporarily unavailable'); $errorMessage = $this->getOption('error_message', $options, '
Site temporarily unavailable.
'); if (file_exists(MODX_CORE_PATH . "error/{$type}.include.php")) { @include(MODX_CORE_PATH . "error/{$type}.include.php"); } header($this->getOption('error_header', $options, 'HTTP/1.1 503 Service Unavailable')); echo "{$errorMessage}
"; @session_write_close(); } else { echo ucfirst($type) . "\n"; echo $this->getOption('error_message', $options, 'Service temporarily unavailable') . "\n"; } exit(); } /** * Sends a redirect to the specified URL using the specified options. * * Valid 'type' option values include: * REDIRECT_REFRESH Uses the header refresh method * REDIRECT_META Sends a a META HTTP-EQUIV="Refresh" tag to the output * REDIRECT_HEADER Uses the header location method * * REDIRECT_HEADER is the default. * * @param string $url The URL to redirect the client browser to. * @param array|boolean $options An array of options for the redirect OR * indicates if redirect attempts should be counted and limited to 3 (latter is deprecated * usage; use count_attempts in options array). * @param string $type The type of redirection to attempt (deprecated, use type in * options array). * @param string $responseCode The type of HTTP response code HEADER to send for the * redirect (deprecated, use responseCode in options array) */ public function sendRedirect($url, $options= false, $type= '', $responseCode = '') { if (!$this->getResponse()) { $this->log(modX::LOG_LEVEL_FATAL, "Could not load response class."); } $this->response->sendRedirect($url, $options, $type, $responseCode); } /** * Forwards the request to another resource without changing the URL. * * @param integer $id The resource identifier. * @param string $options An array of options for the process. */ public function sendForward($id, $options = null) { if (!$this->getRequest()) { $this->log(modX::LOG_LEVEL_FATAL, "Could not load request class."); } $idInt = intval($id); if (is_string($options) && !empty($options)) { $options = array('response_code' => $options); } elseif (!is_array($options)) { $options = array(); } $this->elementCache = array(); if ($idInt > 0) { $merge = array_key_exists('merge', $options) && !empty($options['merge']); $currentResource = array(); if ($merge) { $excludes = array_merge( explode(',', $this->getOption('forward_merge_excludes', $options, 'type,published,class_key')), array( 'content' ,'pub_date' ,'unpub_date' ,'richtext' ,'_content' ,'_processed' ) ); reset($this->resource->_fields); while (list($fkey, $fval) = each($this->resource->_fields)) { if (!in_array($fkey, $excludes)) { if (is_scalar($fval) && $fval !== '') { $currentResource[$fkey] = $fval; } elseif (is_array($fval) && count($fval) === 5 && $fval[1] !== '') { $currentResource[$fkey] = $fval; } } } } $this->resource= $this->request->getResource('id', $idInt, array('forward' => true)); if ($this->resource) { if ($merge && !empty($currentResource)) { $this->resource->_fields = array_merge($this->resource->_fields, $currentResource); $this->elementCache = array(); unset($currentResource); } $this->resourceIdentifier= $this->resource->get('id'); $this->resourceMethod= 'id'; if (isset($options['response_code']) && !empty($options['response_code'])) { header($options['response_code']); } $this->request->prepareResponse(); exit(); } $options= array_merge( array( 'error_type' => '404' ,'error_header' => $this->getOption('error_page_header', $options,'HTTP/1.1 404 Not Found') ,'error_pagetitle' => $this->getOption('error_page_pagetitle', $options,'Error 404: Page not found') ,'error_message' => $this->getOption('error_page_message', $options,'The page you requested was not found.
') ), $options ); } $this->sendError($id, $options); } /** * Send the user to a MODX virtual error page. * * @uses invokeEvent() The OnPageNotFound event is invoked before the error page is forwarded * to. * @param array $options An array of options to provide for the OnPageNotFound event and error * page. */ public function sendErrorPage($options = null) { if (!is_array($options)) $options = array(); $options= array_merge( array( 'response_code' => $this->getOption('error_page_header', $options, 'HTTP/1.1 404 Not Found') ,'error_type' => '404' ,'error_header' => $this->getOption('error_page_header', $options, 'HTTP/1.1 404 Not Found') ,'error_pagetitle' => $this->getOption('error_page_pagetitle', $options, 'Error 404: Page not found') ,'error_message' => $this->getOption('error_page_message', $options, 'The page you requested was not found.
') ), $options ); $this->invokeEvent('OnPageNotFound', $options); $this->sendForward($this->getOption('error_page', $options, '404'), $options); } /** * Send the user to the MODX unauthorized page. * * @uses invokeEvent() The OnPageUnauthorized event is invoked before the unauthorized page is * forwarded to. * @param array $options An array of options to provide for the OnPageUnauthorized * event and unauthorized page. */ public function sendUnauthorizedPage($options = null) { if (!is_array($options)) $options = array(); $options= array_merge( array( 'response_code' => $this->getOption('unauthorized_page_header' ,$options ,'HTTP/1.1 401 Unauthorized') ,'error_type' => '401' ,'error_header' => $this->getOption('unauthorized_page_header', $options,'HTTP/1.1 401 Unauthorized') ,'error_pagetitle' => $this->getOption('unauthorized_page_pagetitle',$options, 'Error 401: Unauthorized') ,'error_message' => $this->getOption('unauthorized_page_message', $options,'You are not authorized to view the requested content.
') ), $options ); $this->invokeEvent('OnPageUnauthorized', $options); $this->sendForward($this->getOption('unauthorized_page', $options, '401'), $options); } /** * Get the current authenticated User and assigns it to the modX instance. * * @param string $contextKey An optional context to get the user from. * @param boolean $forceLoadSettings If set to true, will load settings * regardless of whether the user has an authenticated context or not. * @return modUser The user object authenticated for the request. */ public function getUser($contextKey= '',$forceLoadSettings = false) { if ($contextKey == '') { if ($this->context !== null) { $contextKey= $this->context->get('key'); } } if ($this->user === null || !is_object($this->user)) { $this->user= $this->getAuthenticatedUser($contextKey); if ($contextKey !== 'mgr' && !$this->user) { $this->user= $this->getAuthenticatedUser('mgr'); } } if ($this->user !== null && is_object($this->user)) { if ($this->user->hasSessionContext($contextKey) || $forceLoadSettings) { if (isset ($_SESSION["modx.{$contextKey}.user.config"])) { $this->_userConfig= $_SESSION["modx.{$contextKey}.user.config"]; } else { $settings= $this->user->getMany('UserSettings'); if (is_array($settings) && !empty ($settings)) { foreach ($settings as $setting) { $v= $setting->get('value'); $matches= array(); if (preg_match_all('~\{(.*?)\}~', $v, $matches, PREG_SET_ORDER)) { $matchValue= ''; foreach ($matches as $match) { if (isset ($this->config["{$match[1]}"])) { $matchValue= $this->config["{$match[1]}"]; } else { $matchValue= ''; } $v= str_replace($match[0], $matchValue, $v); } } $this->_userConfig[$setting->get('key')]= $v; } } } if (is_array ($this->_userConfig) && !empty ($this->_userConfig)) { $_SESSION["modx.{$contextKey}.user.config"]= $this->_userConfig; $this->config= array_merge($this->config, $this->_userConfig); } } } else { $this->user = $this->newObject('modUser'); $this->user->fromArray(array( 'id' => 0, 'username' => '(anonymous)' ), '', true); } ksort($this->config); $this->toPlaceholders($this->user->get(array('id','username')),'modx.user'); return $this->user; } /** * Gets the user authenticated in the specified context. * * @param string $contextKey Optional context key; uses current context by default. * @return modUser|null The user object that is authenticated in the specified context, * or null if no user is authenticated. */ public function getAuthenticatedUser($contextKey= '') { $user= null; if ($contextKey == '') { if ($this->context !== null) { $contextKey= $this->context->get('key'); } } if ($contextKey && isset ($_SESSION['modx.user.contextTokens'][$contextKey])) { $user= $this->getObject('modUser', intval($_SESSION['modx.user.contextTokens'][$contextKey]), true); if ($user) { $user->getSessionContexts(); } } return $user; } /** * Checks to see if the user has a session in the specified context. * * @param string $sessionContext The context to test for a session key in. * @return boolean True if the user is valid in the context specified. */ public function checkSession($sessionContext= 'web') { $hasSession = false; if ($this->user !== null) { $hasSession = $this->user->hasSessionContext($sessionContext); } return $hasSession; } /** * Gets the modX core version data. * * @return array The version data loaded from the config version file. */ public function getVersionData() { if ($this->version === null) { $this->version= @ include_once MODX_CORE_PATH . "docs/version.inc.php"; } return $this->version; } /** * Reload the config settings. * * @return array An associative array of configuration key/values */ public function reloadConfig() { $this->getCacheManager(); $this->cacheManager->refresh(); if (!$this->_loadConfig()) { $this->log(modX::LOG_LEVEL_ERROR, 'Could not reload core MODX configuration!'); } return $this->config; } /** * Get the configuration for the site. * * @return array An associate array of configuration key/values */ public function getConfig() { if (!$this->_initialized || !is_array($this->config) || empty ($this->config)) { if (!isset ($this->config['base_url'])) $this->config['base_url']= MODX_BASE_URL; if (!isset ($this->config['base_path'])) $this->config['base_path']= MODX_BASE_PATH; if (!isset ($this->config['core_path'])) $this->config['core_path']= MODX_CORE_PATH; if (!isset ($this->config['url_scheme'])) $this->config['url_scheme']= MODX_URL_SCHEME; if (!isset ($this->config['http_host'])) $this->config['http_host']= MODX_HTTP_HOST; if (!isset ($this->config['site_url'])) $this->config['site_url']= MODX_SITE_URL; if (!isset ($this->config['manager_path'])) $this->config['manager_path']= MODX_MANAGER_PATH; if (!isset ($this->config['manager_url'])) $this->config['manager_url']= MODX_MANAGER_URL; if (!isset ($this->config['assets_path'])) $this->config['assets_path']= MODX_ASSETS_PATH; if (!isset ($this->config['assets_url'])) $this->config['assets_url']= MODX_ASSETS_URL; if (!isset ($this->config['connectors_path'])) $this->config['connectors_path']= MODX_CONNECTORS_PATH; if (!isset ($this->config['connectors_url'])) $this->config['connectors_url']= MODX_CONNECTORS_URL; if (!isset ($this->config['processors_path'])) $this->config['processors_path']= MODX_PROCESSORS_PATH; if (!isset ($this->config['request_param_id'])) $this->config['request_param_id']= 'id'; if (!isset ($this->config['request_param_alias'])) $this->config['request_param_alias']= 'q'; if (!isset ($this->config['https_port'])) $this->config['https_port']= isset($GLOBALS['https_port']) ? $GLOBALS['https_port'] : 443; if (!isset ($this->config['error_handler_class'])) $this->config['error_handler_class']= 'error.modErrorHandler'; $this->_config= $this->config; if (!$this->_loadConfig()) { $this->log(modX::LOG_LEVEL_FATAL, "Could not load core MODX configuration!"); return null; } } return $this->config; } /** * Initialize, cleanse, and process a request made to a modX site. * * @return mixed The result of the request handler. */ public function handleRequest() { if ($this->getRequest()) { return $this->request->handleRequest(); } return ''; } /** * Attempt to load the request handler class, if not already loaded. * * @access public * @param string $class The class name of the response class to load. Defaults to * modRequest; is ignored if the Setting "modRequest.class" is set. * @param string $path The absolute path by which to load the response class from. * Defaults to the current MODX model path. * @return boolean Returns true if a valid request handler object was * loaded on this or any previous call to the function, false otherwise. */ public function getRequest($class= 'modRequest', $path= '') { if ($this->request === null || !($this->request instanceof modRequest)) { $requestClass = $this->getOption('modRequest.class',$this->config,$class); if ($requestClass !== $class) { $this->loadClass('modRequest', '', false, true); } if ($className= $this->loadClass($requestClass, $path, !empty($path), true)) $this->request= new $className ($this); } return is_object($this->request) && $this->request instanceof modRequest; } /** * Attempt to load the response handler class, if not already loaded. * * @access public * @param string $class The class name of the response class to load. Defaults to * modResponse; is ignored if the Setting "modResponse.class" is set. * @param string $path The absolute path by which to load the response class from. * Defaults to the current MODX model path. * @return boolean Returns true if a valid response handler object was * loaded on this or any previous call to the function, false otherwise. */ public function getResponse($class= 'modResponse', $path= '') { $responseClass= $this->getOption('modResponse.class',$this->config,$class); $className= $this->loadClass($responseClass, $path, !empty($path), true); if ($this->response === null || !($this->response instanceof $className)) { if ($className) $this->response= new $className ($this); } return $this->response instanceof $className; } /** * Register CSS to be injected inside the HEAD tag of a resource. * * @param string $src The CSS to be injected before the closing HEAD tag in * an HTML response. * @return void */ public function regClientCSS($src) { if (isset ($this->loadedjscripts[$src]) && $this->loadedjscripts[$src]) { return; } $this->loadedjscripts[$src]= true; if (strpos(strtolower($src), "