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 string Unique site id for each MODX installation.
*/
public $site_id;
/**
* @var string Unique uuid for each MODX installation.
*/
public $uuid;
/**
* @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+);@',
'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;
/**
* Keeps an in-memory representation of what deprecated functions have been logged
* for this request, to avoid spamming the log too often. See the `deprecated` method.
*
* @var array
*/
private $loggedDeprecatedFunctions = array();
/**
* Harden the environment against common security flaws.
*
* @static
*/
public static function protect() {
if (@ ini_get('register_globals') && isset ($_REQUEST)) {
foreach ($_REQUEST as $key => $value) {
$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;
}
/**
* @param array|string $data The target data to sanitize.
* @param array $replaceable
* @return array|string The sanitized data
*/
public static function replaceReserved($data, array $replaceable = array ('[' => '[', ']' => ']', '`' => '`'))
{
if (\is_array($data)) {
$result = array();
foreach ($data as $key => &$value) {
$key = self::replaceReserved($key, $replaceable);
$result[$key] = self::replaceReserved($value, $replaceable);
}
} elseif (\is_scalar($data)) {
$result = \str_replace(\array_keys($replaceable), \array_values($replaceable), $data);
} else {
$result = '';
}
return $result;
}
/**
* 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= microtime(true);
}
$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');
$this->invokeEvent(
'OnMODXInit',
array(
'contextKey' => $contextKey,
'options' => $options
)
);
if (is_array ($this->config)) {
$this->setPlaceholders($this->config, '+');
}
$this->_initialized= true;
}
return $this->_initialized;
}
/**
* Loads any 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) {
$cache = $this->call('modExtensionPackage','loadCache',array(&$this));
if (!empty($cache)) {
foreach ($cache as $package) {
$package['table_prefix'] = isset($package['table_prefix']) ? $package['table_prefix'] : null;
$this->addPackage($package['namespace'],$package['path'],$package['table_prefix']);
if (!empty($package['service_name']) && !empty($package['service_class'])) {
$this->getService($package['service_name'],$package['service_class'],$package['path']);
}
}
}
$this->_loadExtensionPackagesDeprecated($options);
}
/**
* Load system-setting based extension packages. This is not recommended; use modExtensionPackage from 2.3 onward.
* The System Setting will be automatically removed in 2.4/3.0 and no longer functional.
*
* @deprecated To be removed in 2.4/3.0
* @param null $options
*/
protected function _loadExtensionPackagesDeprecated($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'] = isset($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.
*
* @info PHP errors are handle by modErrorHandler with at most LOG_LEVEL_INFO
* When called by modX $debug is a string (ie $this->getOption('debug'))
*
* (bool)true , (string)true , (string)-1 -> LOG_LEVEL_DEBUG (MODX), E_ALL | E_STRICT (PHP)
* (bool)false, (string)false, (string) 0 -> LOG_LEVEL_ERROR (MODX), 0 (PHP)
* (int)nnn -> LOG_LEVEL_INFO (MODX), nnn (PHP)
* (string)E_XXX -> LOG_LEVEL_INFO (MODX), E_XXX (PHP)
*/
public function setDebug($debug= true) {
$oldValue= $this->getDebug();
if (($debug === true) || ('true' === $debug) || ('-1' === $debug)) {
error_reporting(-1);
parent :: setDebug(true);
}
else {
if (($debug === false) || ('false' === $debug) || ('0' === $debug)) {
error_reporting(0);
parent :: setDebug(false);
}
else {
$debug = (is_int($debug) ? $debug : defined($debug) ? intval(constant($debug)) : 0);
if ($debug) {
error_reporting($debug);
parent :: setLogLevel(xPDO::LOG_LEVEL_INFO);
}
}
}
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) {
if ($c = $this->getChildIds($child, $depth - 1, $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).
* @param array $options An array of filtering options, such as 'context' to specify the context to grab from
* @return array An array containing the tree structure.
*/
public function getTree($id= null, $depth= 10, array $options = array()) {
$tree= array ();
if (!empty($options['context'])) {
$this->getContext($options['context']);
}
if ($id !== null) {
if (is_array ($id)) {
foreach ($id as $k => $v) {
$tree[$v] = $this->getTree($v, $depth - 1, $options);
}
} elseif ($branch= $this->getChildIds($id, 1, $options)) {
foreach ($branch as $key => $child) {
if ($depth > 0 && $leaf = $this->getTree($child, $depth - 1, $options)) {
$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);
}
}
}
/**
* Returns the full table name (with dynamic prefix) based on database settings.
* Legacy - Useful when dealing with migrations or prefixed database tables without an xPDO model (which xPDO.getTableName requires.)
*
* @param string $table Name of MODX table, less table prefix.
* @return string Full table name containing database and table prefix.
*/
public function getFullTableName( $table = '' ) {
return $this->getOption('dbname') .".". $this->getOption( xPDO::OPT_TABLE_PREFIX ) . $table;
}
/**
* 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;
}
/**
* Filter a string for use as a URL path segment.
*
* @param string $string The string to filter into a valid path segment.
* @param array $options Optional filter setting overrides.
*
* @return string|null A valid path segment string or null if an error occurs.
*/
public function filterPathSegment($string, array $options = array()) {
return $this->call('modResource', 'filterPathSegment', array(&$this, $string, $options));
}
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 && is_array($this->aliasMap) && array_key_exists($uri, $this->aliasMap)) {
$resourceId = (integer) $this->aliasMap[$uri];
} elseif ($ctx = $this->getContext($context)) {
$useAliasMap = $ctx->getOption('cache_alias_map', false) && is_array($ctx->aliasMap) && 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.
'); $errorHeader = $this->getOption('error_header', $options, $_SERVER['SERVER_PROTOCOL'] . ' 503 Service Unavailable'); if (file_exists(MODX_CORE_PATH . "error/{$type}.include.php")) { @include(MODX_CORE_PATH . "error/{$type}.include.php"); } header($errorHeader); echo "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, $_SERVER['SERVER_PROTOCOL'] . ' 404 Not Found') ,'error_type' => '404' ,'error_header' => $this->getOption('error_page_header', $options, $_SERVER['SERVER_PROTOCOL'] . ' 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, $this->getOption('site_start')), $options, false); } /** * 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 ,$_SERVER['SERVER_PROTOCOL'] . ' 401 Unauthorized') ,'error_type' => '401' ,'error_header' => $this->getOption('unauthorized_page_header', $options,$_SERVER['SERVER_PROTOCOL'] . ' 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, $this->getOption('site_start')), $options); } /** * Get the current authenticated User and assign 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 (!$forceLoadSettings && isset ($_SESSION["modx.{$contextKey}.user.config"])) { $this->_userConfig= $_SESSION["modx.{$contextKey}.user.config"]; } else { $this->_userConfig= array(); $settings= $this->user->getSettings(); if (is_array($settings) && !empty ($settings)) { foreach ($settings as $k => $v) { $matches= array(); if (preg_match_all('~\{(.*?)\}~', $v, $matches, PREG_SET_ORDER)) { foreach ($matches as $match) { if (isset($this->_userConfig["{$match[1]}"])) { $matchValue= $this->_userConfig["{$match[1]}"]; } elseif (isset($this->config["{$match[1]}"])) { $matchValue= $this->config["{$match[1]}"]; } else { $matchValue= ''; } $v= str_replace($match[0], $matchValue, $v); } } $this->_userConfig[$k]= $v; } } $_SESSION["modx.{$contextKey}.user.config"]= $this->_userConfig; } if (is_array($this->_userConfig) && !empty($this->_userConfig)) { $this->config= array_merge($this->config, $this->_userConfig); } } } else { $this->user = $this->newObject('modUser'); $this->user->fromArray(array( 'id' => 0, 'username' => $this->getOption('default_username','','(anonymous)',true) ), '', 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['connector_url'])) $this->config['connector_url']= MODX_CONNECTORS_URL . 'index.php'; 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'; if (!isset ($this->config['server_port'])) $this->config['server_port']= isset($_SERVER['SERVER_PORT']) ? $_SERVER['SERVER_PORT'] : ''; $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. * @param string $media all, aural, braille, embossed, handheld, print, projection, screen, tty, tv * @return void */ public function regClientCSS($src, $media = null) { if (isset ($this->loadedjscripts[$src]) && $this->loadedjscripts[$src]) { return; } $this->loadedjscripts[$src]= true; if (strpos(strtolower($src), "