Index: branches/5.3.x/core/kernel/utility/unit_config.php =================================================================== --- branches/5.3.x/core/kernel/utility/unit_config.php (revision 15731) +++ branches/5.3.x/core/kernel/utility/unit_config.php (revision 15732) @@ -1,1029 +1,1380 @@ <?php /** * @version $Id$ * @package In-Portal * @copyright Copyright (C) 1997 - 2012 Intechnic. All rights reserved. * @license GNU/GPL * In-Portal is Open Source software. * This means that this software may have been modified pursuant * the GNU General Public License, and as distributed it includes * or is derivative of works licensed under the GNU General Public License * or other free or open source software licenses. * See http://www.in-portal.org/license for copyright notices and details. */ defined('FULL_PATH') or die('restricted access!'); /** * TODO: Rename following settings * * ConfigMapping -> ConfigMappings * StatusField -> StatusFields * PermSection -> PermSections */ /** * @method Array getFilterMenu(mixed $default = false) * @method kUnitConfig setFilterMenu(Array $value) * * @method Array getConfigMapping(mixed $default = false) * @method kUnitConfig setConfigMapping(Array $value) * * * @method string getModuleFolder(mixed $default = false) * @method kUnitConfig setModuleFolder(string $value) * * @method string getBasePath(mixed $default = false) * @method kUnitConfig setBasePath(string $value) * * @method Array getEventHandlerClass(mixed $default = false) * @method kUnitConfig setEventHandlerClass(Array $value) * * @method Array getTagProcessorClass(mixed $default = false) * @method kUnitConfig setTagProcessorClass(Array $value) * * @method Array getItemClass(mixed $default = false) * @method kUnitConfig setItemClass(Array $value) * * @method Array getListClass(mixed $default = false) * @method kUnitConfig setListClass(Array $value) * * @method Array getValidatorClass(mixed $default = false) * @method kUnitConfig setValidatorClass(Array $value) * * @method Array getQueryString(mixed $default = false) * @method kUnitConfig setQueryString(Array $value) * * @method string getPermItemPrefix(mixed $default = false) * @method kUnitConfig setPermItemPrefix(string $value) * * @method string getPermTabText(mixed $default = false) * @method kUnitConfig setPermTabText(string $value) * * @method Array getPermSection(mixed $default = false) * @method kUnitConfig setPermSection(Array $value) * * @method bool getAutoLoad(mixed $default = false) * @method kUnitConfig setAutoLoad(bool $value) * * @method string getIDField(mixed $default = false) * @method kUnitConfig setIDField(string $value) * * @method string getTableName(mixed $default = false) * @method kUnitConfig setTableName(string $value) * * @method string getCustomDataTableName(mixed $default = false) * @method kUnitConfig setCustomDataTableName(string $value) * * @method kUnitConfig setStatusField(Array $value) * * @method string getTitleField(mixed $default = false) * @method kUnitConfig setTitleField(string $value) * * @method string getOrderField(mixed $default = false) * @method kUnitConfig setOrderField(string $value) * * @method string getOwnerField(mixed $default = false) * @method kUnitConfig setOwnerField(string $value) * * @method int getConfigPriority(mixed $default = false) * @method kUnitConfig setConfigPriority(int $value) * * @method bool getCatalogItem(mixed $default = false) * @method kUnitConfig setCatalogItem(bool $value) * * @method string getCatalogSelectorName(mixed $default = false) * @method kUnitConfig setCatalogSelectorName(string $value) * * @method string getAdminTemplatePath(mixed $default = false) * @method kUnitConfig setAdminTemplatePath(string $value) * * @method string getAdminTemplatePrefix(mixed $default = false) * @method kUnitConfig setAdminTemplatePrefix(string $value) * * @method string getSearchConfigPostfix(mixed $default = false) * @method kUnitConfig setSearchConfigPostfix(string $value) * * @method string getTitlePhrase(mixed $default = false) * @method kUnitConfig setTitlePhrase(string $value) * * @method int getItemType(mixed $default = false) * @method kUnitConfig setItemType(int $value) * * @method Array getStatisticsInfo(mixed $default = false) * @method kUnitConfig setStatisticsInfo(Array $value) * * @method string getViewMenuPhrase(mixed $default = false) * @method kUnitConfig setViewMenuPhrase(string $value) * * @method string getCatalogTabIcon(mixed $default = false) * @method kUnitConfig setCatalogTabIcon(string $value) * * @method bool getCacheModRewrite(mixed $default = false) * @method kUnitConfig setCacheModRewrite(bool $value) * * @method kUnitConfig setParentTableKey(mixed $value) * * @method kUnitConfig setForeignKey(mixed $value) * * @method string getConstrain(mixed $default = false) * @method kUnitConfig setConstrain(string $value) * * @method bool getAutoDelete(mixed $default = false) * @method kUnitConfig setAutoDelete(bool $value) * * @method bool getAutoClone(mixed $default = false) * @method kUnitConfig setAutoClone(bool $value) * * @method string getParentPrefix(mixed $default = false) * @method kUnitConfig setParentPrefix(string $value) * * @method bool getCheckSimulatniousEdit(mixed $default = false) * @method kUnitConfig setCheckSimulatniousEdit(bool $value) * * @method bool getPortalStyleEnv(mixed $default = false) * @method kUnitConfig setPortalStyleEnv(bool $value) * * @method int getRewritePriority(mixed $default = false) * @method kUnitConfig setRewritePriority(int $value) * * @method Array getRewriteListener(mixed $default = false) * @method kUnitConfig setRewriteListener(Array $value) * * @method bool getForceDontLogChanges(mixed $default = false) * @method kUnitConfig setForceDontLogChanges(bool $value) * * @method Array getUserProfileMapping(mixed $default = false) * @method kUnitConfig setUserProfileMapping(Array $value) * * @method bool getUsePendingEditing(mixed $default = false) * @method kUnitConfig setUsePendingEditing(bool $value) * * @method string getNavigationSelectClause(mixed $default = false) * @method kUnitConfig setNavigationSelectClause(string $value) * * @method bool getPopulateMlFields(bool $default = false) * @method kUnitConfig setPopulateMlFields(bool $value) * * @method bool getLogChanges(bool $default = false) * @method kUnitConfig setLogChanges(bool $value) * * @method int getFileCount(bool $default = false) * @method kUnitConfig setFileCount(int $value) * * @method int getImageCount(bool $default = false) * @method kUnitConfig setImageCount(int $value) * * @method string getDownloadHelperClass(bool $default = false) * @method kUnitConfig setDownloadHelperClass(string $value) * * @method string getSectionPrefix(bool $default = false) * @method kUnitConfig setSectionPrefix(string $value) * * @method bool getSiteConfigProcessed(bool $default = false) * @method kUnitConfig setSiteConfigProcessed(bool $value) * * * @method Array getRegisterClasses(mixed $default = false) * @method kUnitConfig setRegisterClasses(Array $value) * @method kUnitConfig addRegisterClasses(Array $value, string $name = null) * @method kUnitConfig removeRegisterClasses(string $name = null) * * @method Array getScheduledTasks(mixed $default = false) * @method Array getScheduledTaskByName(string $name, mixed $default = false) * @method kUnitConfig setScheduledTasks(Array $value) * @method kUnitConfig addScheduledTasks(Array $value, string $name = null) * @method kUnitConfig removeScheduledTasks(string $name = null) * * @method Array getTitlePresets(mixed $default = false) * @method Array getTitlePresetByName(string $name, mixed $default = false) * @method kUnitConfig setTitlePresets(Array $value) * @method kUnitConfig addTitlePresets(Array $value, string $name = null) * @method kUnitConfig removeTitlePresets(string $name = null) * * @method Array getSections(mixed $default = false) * @method Array getSectionByName(string $name, mixed $default = false) * @method kUnitConfig setSections(Array $value) * @method kUnitConfig addSections(Array $value, string $name = null) * @method kUnitConfig removeSections(string $name = null) * * @method Array getFields(mixed $default = false) * @method Array getFieldByName(string $name, mixed $default = false) * @method kUnitConfig setFields(Array $value) * @method kUnitConfig addFields(Array $value, string $name = null) * @method kUnitConfig removeFields(string $name = null) * * @method Array getVirtualFields(mixed $default = false) * @method Array getVirtualFieldByName(string $name, mixed $default = false) * @method kUnitConfig setVirtualFields(Array $value) * @method kUnitConfig addVirtualFields(Array $value, string $name = null) * @method kUnitConfig removeVirtualFields(string $name = null) * * @method Array getGrids(mixed $default = false) * @method Array getGridByName(string $name, mixed $default = false) * @method kUnitConfig setGrids(Array $value) * @method kUnitConfig addGrids(Array $value, string $name = null) * @method kUnitConfig removeGrids(string $name = null) * * @method Array getHooks(mixed $default = false) * @method kUnitConfig setHooks(Array $value) * @method kUnitConfig addHooks(Array $value, string $name = null) * @method kUnitConfig removeHooks(string $name = null) * * @method Array getAggregateTags(mixed $default = false) * @method kUnitConfig setAggregateTags(Array $value) * @method kUnitConfig addAggregateTags(Array $value, string $name = null) * @method kUnitConfig removeAggregateTags(string $name = null) * * @method Array getEditTabPresets(mixed $default = false) * @method Array getEditTabPresetByName(string $name, mixed $default = false) * @method kUnitConfig setEditTabPresets(Array $value) * @method kUnitConfig addEditTabPresets(Array $value, string $name = null) * @method kUnitConfig removeEditTabPresets(string $name = null) * * @method Array getSubItems(mixed $default = false) * @method kUnitConfig setSubItems(Array $value) * @method kUnitConfig addSubItems(string $value, string $name = null) * @method kUnitConfig removeSubItems(string $name = null) * * @method Array getCustomFields(mixed $default = false) * @method string getCustomFieldByName(string $name, mixed $default = false) * @method kUnitConfig setCustomFields(Array $value) * @method kUnitConfig addCustomFields(Array $value, string $name = null) * @method kUnitConfig removeCustomFields(string $name = null) * * @method Array getClones(mixed $default = false) * @method Array getCloneByName(string $name, mixed $default = false) * @method kUnitConfig setClones(Array $value) * @method kUnitConfig addClones(Array $value, string $name = null) * @method kUnitConfig removeClones(string $name = null) * * @method Array getProcessPrefixes(mixed $default = false) * @method kUnitConfig setProcessPrefixes(Array $value) * @method kUnitConfig addProcessPrefixes(string $value, string $name = null) * @method kUnitConfig removeProcessPrefixes(string $name = null) * * @method Array getForms(mixed $default = false) * @method Array getFormByName(string $name, mixed $default = false) * @method kUnitConfig setForms(Array $value) * @method kUnitConfig addForms(Array $value, string $name = null) * @method kUnitConfig removeForms(string $name = null) * * @method Array getReplacementTemplates(mixed $default = false) * @method string getReplacementTemplateByName(string $name, mixed $default = false) * @method kUnitConfig setReplacementTemplates(Array $value) * @method kUnitConfig addReplacementTemplates(string $value, string $name = null) * @method kUnitConfig removeReplacementTemplates(string $name = null) * * @method Array getItemPropertyMappings(mixed $default = false) * @method string getItemPropertyMappingByName(string $name, mixed $default = false) * @method kUnitConfig setItemPropertyMappings(Array $value) * @method kUnitConfig addItemPropertyMappings(string $value, string $name = null) * @method kUnitConfig removeItemPropertyMappings(string $name = null) * * @method Array getSectionAdjustments(mixed $default = false) * @method mixed getSectionAdjustmentByName(string $name, mixed $default = false) * @method kUnitConfig setSectionAdjustments(Array $value) * @method kUnitConfig addSectionAdjustments(mixed $value, string $name = null) * @method kUnitConfig removeSectionAdjustments(string $name = null) * * @method Array getImportKeys(mixed $default = false) * @method kUnitConfig setImportKeys(Array $value) * @method kUnitConfig addImportKeys(mixed $value, string $name = null) * @method kUnitConfig removeImportKeys(string $name = null) * * * @method Array getCalculatedFieldSpecials(mixed $default = Array ()) * @method Array getCalculatedFieldsBySpecial(mixed $special, mixed $default = Array ()) * @method kUnitConfig setCalculatedFieldsBySpecial(mixed $special, Array $value) * @method kUnitConfig addCalculatedFieldsBySpecial(mixed $special, mixed $value, string $name = null) * @method kUnitConfig removeCalculatedFieldsBySpecial(mixed $special, string $name = null) * * @method Array getAggregatedCalculatedFieldSpecials(mixed $default = Array ()) * @method Array getAggregatedCalculatedFieldsBySpecial(mixed $special, mixed $default = Array ()) * @method kUnitConfig setAggregatedCalculatedFieldsBySpecial(mixed $special, Array $value) * @method kUnitConfig addAggregatedCalculatedFieldsBySpecial(mixed $special, mixed $value, string $name = null) * @method kUnitConfig removeAggregatedCalculatedFieldsBySpecial(mixed $special, string $name = null) * * @method Array getListSQLSpecials(mixed $default = Array ()) * @method string getListSQLsBySpecial(mixed $special, mixed $default = Array ()) * @method kUnitConfig setListSQLsBySpecial(mixed $special, string $value) * @method kUnitConfig removeListSQLsBySpecial(mixed $special, string $name = null) * * @method Array getListSortingSpecials(mixed $default = Array ()) * @method Array getListSortingsBySpecial(mixed $special, mixed $default = Array ()) * @method kUnitConfig setListSortingsBySpecial(mixed $special, Array $value) * @method kUnitConfig addListSortingsBySpecial(mixed $special, mixed $value, string $name = null) * @method kUnitConfig removeListSortingsBySpecial(mixed $special, string $name = null) * * @method Array getItemSQLSpecials(mixed $default = Array ()) * @method string getItemSQLsBySpecial(mixed $special, mixed $default = Array ()) * @method kUnitConfig setItemSQLsBySpecial(mixed $special, string $value) * @method kUnitConfig removeItemSQLsBySpecial(mixed $special, string $name = null) */ class kUnitConfig { /** + * Reference to global kApplication instance + * + * @var kApplication + * @access protected + */ + protected $Application = null; + + /** + * Connection to database + * + * @var kDBConnection + * @access protected + */ + protected $Conn = null; + + /** * Unit config prefix * * @var string * @access protected */ protected $_prefix = ''; /** + * Filename, where unit config is stored + * + * @var string + * @access protected + */ + protected $_filename = ''; + + /** * Default unit config data * * @var Array * @access protected */ protected static $_defaults = Array ( 'ItemClass' => Array ('class' => 'kDBItem', 'file' => '', 'build_event' => 'OnItemBuild'), 'ListClass' => Array ('class' => 'kDBList', 'file' => '', 'build_event' => 'OnListBuild'), 'EventHandlerClass' => Array ('class' => 'kDBEventHandler', 'file' => '', 'build_event' => 'OnBuild'), 'TagProcessorClass' => Array ('class' => 'kDBTagProcessor', 'file' => '', 'build_event' => 'OnBuild'), 'AutoLoad' => true, 'QueryString' => Array ( 1 => 'id', 2 => 'Page', 3 => 'PerPage', 4 => 'event', 5 => 'mode', ), 'ListSQLs' => Array ( '' => ' SELECT %1$s.* %2$s FROM %1$s', ), 'Fields' => Array (), ); /** * Word endings, that are suffixed with "es" instead of just "s" during pluralisation * * @var string * @access protected */ protected static $_singularEndingsRegExp = '/(x|s|z|sh|ch)$/'; /** * Words, that ends with es/s, but are always singulars * * @var string * @access protected */ protected static $_alwaysSingularRegExp = '/(class|LogChanges|ForceDontLogChanges|PopulateMlFields)$/i'; /** * Unit config data * * @var Array * @access protected */ protected $_data = Array (); /** * Unit config settings that can have different values based on special * * @var Array * @access protected */ protected $_specialBased = Array ( 'CalculatedFields', 'AggregatedCalculatedFields', 'ListSQLs', 'ListSortings', 'ItemSQLs', ); /** * Unit config settings, that allow data to be added without a key * * @var Array * @access protected */ protected $_withoutKeys = Array ( 'RegisterClasses', 'Hooks', 'AggregateTags' ); /** * Dynamic method name, that was called * * @var string * @access protected */ protected $_currentMethodName = ''; /** * Arguments given to dynamic method name, that was called * * @var Array * @access protected */ protected $_currentArguments = Array (); /** * Creates new instance of kUnitConfig class * * @param string $prefix * @param Array $defaults * @param bool $use_global_defaults * @access public */ public function __construct($prefix, $defaults = null, $use_global_defaults = true) { $this->_prefix = $prefix; $merge_with = $use_global_defaults ? self::$_defaults : Array (); $this->_data = array_merge($merge_with, isset($defaults) ? $defaults : Array ()); + + $this->Application =& kApplication::Instance(); + $this->Conn =& $this->Application->GetADODBConnection(); + } + + /** + * Sets filename, where unit config is stored + * + * @param string $filename + * @return void + * @access public + */ + public function setFilename($filename) + { + $this->_filename = $filename; } /** * Returns unit config prefix * * @return string * @access public */ public function getPrefix() { return $this->_prefix; } /** * Ensures, that only unit config data is saved when object is serialized * * @return Array * @access public */ public function __sleep() { - return Array ('_prefix', '_data'); + return Array ('_prefix', '_data', 'Application', 'Conn'); } /** * Dumps unit config into a file * * @return void * @access public */ public function dump() { kUtil::print_r($this->_data, 'Unit Config', true); } /** * Returns unit config data in raw form * * @return Array * @access public */ public function getRaw() { return $this->_data; } /** * Processed dynamically created methods for changing unit config settings * * @param string $method_name * @param Array $arguments * @return mixed * @throws InvalidArgumentException * @throws RuntimeException * @access public */ public function __call($method_name, $arguments) { // === regular === // get{SettingName}() // set{SettingName}($value) // add{SettingName}s(string $value, string $name = null) // remove{SettingName}(string $name = null) // === by special === // get{SettingName}Specials() // get{SettingName}sBySpecial($special) // set{SettingName}BySpecial($special, $value) // add{SettingName}sBySpecial(string $special, Array $value, string $name = null) // remove{SettingName}sBySpecial(string $special, $name = null) if ( !preg_match('/^(get|set|add|remove)(.*?)(BySpecial|Specials|ByName)*$/', $method_name, $regs) ) { throw new RuntimeException('Unknown method <strong>' . __CLASS__ . '::' . $this->_currentMethodName . '</strong>'); } $this->_currentMethodName = $method_name; $this->_currentArguments = $arguments; $to_call = '_process' . ucfirst($regs[1]); return $this->$to_call($regs[2], isset($regs[3]) ? $regs[3] : ''); } /** * Processes calls to "get*" methods * * @param string $setting_name * @param string $suffix * @return mixed * @access protected */ protected function _processGet($setting_name, $suffix = '') { $is_plural = $this->_isPluralSetting($setting_name); if ( $suffix == 'BySpecial' && $is_plural ) { // get{SettingName}BySpecial($special, $default = false) $this->_verifyArguments(Array ('special')); return $this->getSetting($setting_name, $this->_getDefaultValue(1, Array ()), $this->_currentArguments[0]); } elseif ( $suffix == 'Specials' && !$is_plural ) { // get{SettingName}Specials($default = Array ()) $result = $this->getSetting($this->_getPlural($setting_name), $this->_getDefaultValue(0, Array ())); return array_keys($result); } elseif ( $suffix == 'ByName' && !$is_plural ) { $sub_key = $this->_currentArguments[0]; $result = $this->getSetting($this->_getPlural($setting_name), Array ()); return isset($result[$sub_key]) ? $result[$sub_key] : $this->_getDefaultValue(1, false); } // get{SettingName}($default = false) return $this->getSetting($setting_name, $this->_getDefaultValue(0, false)); } /** * Returns default value from given argument or false, when not passed * * @param int $arg_index * @param mixed $default * @return bool * @access protected */ protected function _getDefaultValue($arg_index, $default = false) { return isset($this->_currentArguments[$arg_index]) ? $this->_currentArguments[$arg_index] : $default; } /** * Processes calls to "set*" methods * * @param string $setting_name * @param string $suffix * @return kUnitConfig * @access protected */ protected function _processSet($setting_name, $suffix = '') { $is_plural = $this->_isPluralSetting($setting_name); if ( $suffix == 'BySpecial' && $is_plural ) { // set{SettingName}BySpecial($special, $value) $this->_verifyArguments(Array ('special', 'value')); return $this->setSetting($setting_name, $this->_currentArguments[1], $this->_currentArguments[0]); } // set{SettingName}($value) $this->_verifyArguments(Array ('value')); return $this->setSetting($setting_name, $this->_currentArguments[0]); } /** * Processes calls to "add*" method * * @param string $setting_name * @param string $suffix * @return kUnitConfig * @access protected * @throws InvalidArgumentException */ protected function _processAdd($setting_name, $suffix = '') { $arguments = $this->_currentArguments; if ( !$this->_isPluralSetting($setting_name) ) { throw new InvalidArgumentException('Setting "' . $setting_name . '" isn\'t plural'); } if ( $suffix == 'BySpecial' ) { // add{SettingName}BySpecial(string $special, string $value, string $name = null) $this->_verifyArguments(Array ('special', 'value')); if ( isset($arguments[2]) ) { // add a single value $this->_addToSetting($this->_getSingular($setting_name), $arguments[1], $arguments[2], $arguments[0]); } else { // add multiple values $this->_addToSetting($setting_name, $arguments[1], null, $arguments[0]); } } else { // add{SettingName}(string $value, string $name = null) $this->_verifyArguments(Array ('value')); if ( isset($arguments[1]) ) { // add a single value $this->_addToSetting($this->_getSingular($setting_name), $arguments[0], $arguments[1]); } else { // add multiple value $this->_addToSetting($setting_name, $arguments[0], null); } } return $this; } /** * Adds a value to a setting * * @param string $name * @param mixed $value * @param string $array_key * @param string $special * @return kUnitConfig * @throws InvalidArgumentException */ protected function _addToSetting($name, $value, $array_key, $special = null) { if ( $this->_isPluralSetting($name) ) { // multiple values given - merge with current values if ( !is_array($value) ) { throw new InvalidArgumentException('Argument $value must be an array'); } $result = $this->getSetting($name, Array (), $special); $this->setSetting($name, array_merge($result, $value), $special); } else { // single value given $result = $this->getSetting($this->_getPlural($name), Array (), $special); if ( $this->_isWithoutKeySetting($name) ) { $result[] = $value; } else { $result[$array_key] = $value; } $this->setSetting($this->_getPlural($name), $result, $special); } return $this; } /** * Processes calls to "remove*" method * * @param string $setting_name * @param string $suffix * @return kUnitConfig * @access protected * @throws InvalidArgumentException */ protected function _processRemove($setting_name, $suffix = '') { $arguments = $this->_currentArguments; if ( !$this->_isPluralSetting($setting_name) ) { throw new InvalidArgumentException('Setting "' . $setting_name . '" isn\'t plural'); } if ( $suffix == 'BySpecial' ) { // remove{SettingName}BySpecial(string $special, string $name = null) $this->_verifyArguments(Array ('special')); if ( isset($arguments[1]) ) { // remove single value $this->_removeFromSetting($this->_getSingular($setting_name), $arguments[1], $arguments[0]); } else { // remove multiple value $this->_removeFromSetting($setting_name, null, $arguments[0]); } } else { // remove{SettingName}(string $name = null) if ( isset($arguments[0]) ) { // remove single value $this->_removeFromSetting($this->_getSingular($setting_name), $arguments[0]); } else { // remove multiple values $this->_removeFromSetting($setting_name, null); } } return $this; } /** * Removes a value from a setting * * @param string $name * @param string $array_key * @param string $special * @return kUnitConfig * @access protected * @throws RuntimeException */ protected function _removeFromSetting($name, $array_key = null, $special = null) { if ( $this->_isPluralSetting($name) ) { // remove multiple values if ( $this->getSetting($name, false, $special) !== false ) { $this->setSetting($name, null, $special); } } else { // remove single value if ( $this->_isWithoutKeySetting($name) ) { throw new RuntimeException('Unable to change setting without key'); } $result = $this->getSetting($this->_getPlural($name), false, $special); if ( $result !== false ) { unset($result[$array_key]); $this->setSetting($this->_getPlural($name), $result, $special); } } return $this; } /** * Verifies argument count given * * @param Array $argument_names * @return void * @access protected * @throws InvalidArgumentException */ protected function _verifyArguments($argument_names) { if ( count($this->_currentArguments) < count($argument_names) ) { throw new InvalidArgumentException('Method <strong>' . __CLASS__ . '::' . $this->_currentMethodName . '</strong> expects following arguments: <strong>$' . implode('</strong>, <strong>$', $argument_names) . '</strong>'); } } /** * Returns setting value by name and filter by special (if passed) * * @param string $name * @param mixed $default * @param string|kBase $special * @return mixed * @access public */ public function getSetting($name, $default = false, $special = null) { if ( in_array($name, $this->_specialBased) && isset($special) ) { $use_special = $this->_guessSpecial($name, $special); return isset($this->_data[$name][$use_special]) ? $this->_data[$name][$use_special] : $default; } return isset($this->_data[$name]) ? $this->_data[$name] : $default; } /** * Changes value of a setting * * @param string $name * @param mixed $value * @param string|kBase $special * @return kUnitConfig * @access public */ public function setSetting($name, $value, $special = null) { if ( in_array($name, $this->_specialBased) && isset($special) ) { if ( !isset($this->_data[$name]) ) { $this->_data[$name] = Array (); } $use_special = $this->_guessSpecial($name, $special); if ( !isset($value) ) { unset($this->_data[$name][$use_special]); } else { $this->_data[$name][$use_special] = $value; } } else { if ( !isset($value) ) { unset($this->_data[$name]); } else { $this->_data[$name] = $value; } } return $this; } /** * Detects settings, that accept arrays * * @param string $name * @return bool * @access protected */ protected function _isPluralSetting($name) { if ( preg_match(self::$_alwaysSingularRegExp, $name) ) { // simplified exceptions return false; } return preg_match('/(es|s)$/', $name); } /** * Detects anonymous settings * * @param string $name * @return bool * @access protected */ protected function _isWithoutKeySetting($name) { return in_array($this->_getPlural($name), $this->_withoutKeys); } /** * Returns plural form given word * * @param string $name * @return string * @access protected */ protected function _getPlural($name) { return preg_match(self::$_singularEndingsRegExp, $name) ? $name . 'es' : $name . 's'; } /** * Returns singular form of given word * * @param $name * @return mixed * @throws InvalidArgumentException */ protected function _getSingular($name) { if ( !$this->_isPluralSetting($name) ) { throw new InvalidArgumentException('Setting "' . $name . '" isn\'t plural'); } return preg_replace('/(es|s)$/', '', $name); } /** * Guesses special to be used by event * * @param string $setting_name * @param string|kBase $special * @return string * @access protected */ protected function _guessSpecial($setting_name, $special) { $use_special = $special instanceof kBase ? $special->Special : $special; $value = $this->getSetting($setting_name, Array ()); return isset($value[$use_special]) ? $use_special : ''; } /** * Adds new tab to existing edit tab preset * * @param string $preset_name * @param Array $value * @param string $name * @return kUnitConfig * @throws InvalidArgumentException * @access public */ public function addEditTabPresetTabs($preset_name, $value, $name = null) { $preset = $this->getEditTabPresetByName($preset_name, false); if ( $preset === false ) { throw new InvalidArgumentException('Edit tab preset "' . $preset_name . '" not defined in "' . $this->_prefix . '" unit config'); } if ( isset($name) ) { $preset[$name] = $value; } else { $preset = array_merge($preset, $value); } $this->addEditTabPresets($preset, $preset_name); return $this; } public function addGridFields($grid_name, $value, $name = null) { $grid = $this->getGridByName($grid_name, false); if ( $grid === false ) { throw new InvalidArgumentException('Grid "' . $grid_name . '" not defined in "' . $this->_prefix . '" unit config'); } if ( isset($name) ) { $grid['Fields'][$name] = $value; } else { $grid['Fields'] = array_merge($grid['Fields'], $value); } $this->addGrids($grid, $grid_name); return $this; } /** * Returns individual permission section * * @param string $name * @param Array $default * @return Array * @access public * @todo Rename setting to plural form and them remove this method */ public function getPermSectionByName($name, $default = Array ()) { $perm_sections = $this->getPermSection(Array ()); return isset($perm_sections[$name]) ? $perm_sections[$name] : $default; } /** * Returns foreign key by given prefix * * @param string $parent_prefix * @param mixed $default * @return string|bool * @access public */ public function getForeignKey($parent_prefix = null, $default = false) { return $this->_getSettingByPrefix('ForeignKey', $parent_prefix, $default); } /** * Returns parent table key by given prefix * * @param string $parent_prefix * @param mixed $default * @return string|bool * @access public */ public function getParentTableKey($parent_prefix = null, $default = false) { return $this->_getSettingByPrefix('ParentTableKey', $parent_prefix, $default); } /** * Returns value of a setting by prefix (special workaround for non-special settings) * * @param string $name * @param string $prefix * @param mixed $default * @return mixed * @access protected */ protected function _getSettingByPrefix($name, $prefix = null, $default = false) { $value = $this->getSetting($name, $default); if ( !is_array($value) || !isset($prefix) ) { return $value; } return isset($value[$prefix]) ? $value[$prefix] : $default; } /** * Returns status field with option to get first field only * * @param bool $first_only * @param mixed $default * @return mixed * @access public */ public function getStatusField($first_only = false, $default = false) { $value = $this->getSetting('StatusField', $default); return $first_only ? $value[0] : $value; } + + /** + * Register necessary classes + * This method should only process the data which is cached! + * + * @return void + * @access public + */ + public function parse() + { + $this->_parseClasses(); + $this->_parseScheduledTasks(); + $this->_parseHooks(); + $this->_parseAggregatedTags(); + } + + protected function _parseClasses() + { + $register_classes = $this->_getClasses(); + + foreach ($register_classes as $class_info) { + $this->Application->registerClass( + $class_info['class'], + $this->getBasePath() . DIRECTORY_SEPARATOR . $class_info['file'], + $class_info['pseudo'] + ); + + if ( isset($class_info['build_event']) && $class_info['build_event'] && $class_info['build_event'] != 'OnBuild' ) { + $this->Application->delayUnitProcessing('registerBuildEvent', Array ($class_info['pseudo'], $class_info['build_event'])); + } + } + } + + protected function _getClasses() + { + $register_classes = $this->getRegisterClasses(); + $class_params = Array ('ItemClass', 'ListClass', 'EventHandlerClass', 'TagProcessorClass'); + + foreach ($class_params as $param_name) { + $value = $this->getSetting($param_name); + + if ( !$value ) { + continue; + } + + $value['pseudo'] = $this->_getPseudoByOptionName($param_name); + $this->setSetting($param_name, $value); + + $register_classes[] = $value; + } + + return $register_classes; + } + + protected function _getPseudoByOptionName($option_name) + { + $pseudo_class_map = Array ( + 'ItemClass' => '%s', + 'ListClass' => '%s_List', + 'EventHandlerClass' => '%s_EventHandler', + 'TagProcessorClass' => '%s_TagProcessor' + ); + + return sprintf($pseudo_class_map[$option_name], $this->_prefix); + } + + protected function _parseScheduledTasks() + { + if ( !$this->getScheduledTasks() ) { + return ; + } + + $scheduled_tasks = $this->getScheduledTasks(); + + foreach ($scheduled_tasks as $short_name => $scheduled_task_info) { + $event_status = array_key_exists('Status', $scheduled_task_info) ? $scheduled_task_info['Status'] : STATUS_ACTIVE; + $this->Application->delayUnitProcessing('registerScheduledTask', Array ($short_name, $this->_prefix . ':' . $scheduled_task_info['EventName'], $scheduled_task_info['RunSchedule'], $event_status)); + } + } + + protected function _parseHooks() + { + $hooks = $this->getHooks(); + + if ( !$hooks ) { + return; + } + + foreach ($hooks as $hook) { + if ( $this->getParentPrefix() && ($hook['HookToPrefix'] == $this->getParentPrefix()) ) { + trigger_error('Deprecated Hook Usage [prefix: <strong>' . $this->_prefix . '</strong>; do_prefix: <strong>' . $hook['DoPrefix'] . '</strong>] use <strong>#PARENT#</strong> as <strong>HookToPrefix</strong> value, where HookToPrefix is same as ParentPrefix', defined('E_USER_DEPRECATED') ? E_USER_DEPRECATED : E_USER_NOTICE); + } + + if ( $hook['HookToPrefix'] == '' ) { + // new: set hooktoprefix to current prefix if not set + $hook['HookToPrefix'] = $this->_prefix; + } + + if ( $this->getParentPrefix() ) { + // new: allow to set hook to parent prefix what ever it is + if ( $hook['HookToPrefix'] == '#PARENT#' ) { + $hook['HookToPrefix'] = $this->getParentPrefix(); + } + + if ( $hook['DoPrefix'] == '#PARENT#' ) { + $hook['DoPrefix'] = $this->getParentPrefix(); + } + } + elseif ( $hook['HookToPrefix'] == '#PARENT#' || $hook['DoPrefix'] == '#PARENT#' ) { + // we need parent prefix but it's not set ! + continue; + } + + $hook_events = (array)$hook['HookToEvent']; + $do_prefix = $hook['DoPrefix'] == '' ? $this->_prefix : $hook['DoPrefix']; + + foreach ($hook_events as $hook_event) { + $hook_event = $hook['HookToPrefix'] . '.' . $hook['HookToSpecial'] . ':' . $hook_event; + $do_event = $do_prefix . '.' . $hook['DoSpecial'] . ':' . $hook['DoEvent']; + + $this->Application->delayUnitProcessing('registerHook', Array ($hook_event, $do_event, $hook['Mode'], $hook['Conditional'])); + } + } + } + + protected function _parseAggregatedTags() + { + $aggregated_tags = $this->getAggregateTags(); + + if ( !$aggregated_tags ) { + return; + } + + foreach ($aggregated_tags as $aggregate_tag) { + if ( $this->getParentPrefix() ) { + if ( $aggregate_tag['AggregateTo'] == $this->getParentPrefix() ) { + trigger_error('Deprecated Aggregate Tag Usage [prefix: <b>' . $this->_prefix . '</b>; AggregateTo: <b>' . $aggregate_tag['AggregateTo'] . '</b>] use <b>#PARENT#</b> as <b>AggregateTo</b> value, where AggregateTo is same as ParentPrefix', defined('E_USER_DEPRECATED') ? E_USER_DEPRECATED : E_USER_NOTICE); + } + + if ( $aggregate_tag['AggregateTo'] == '#PARENT#' ) { + $aggregate_tag['AggregateTo'] = $this->getParentPrefix(); + } + } + + $aggregate_tag['LocalPrefix'] = $this->_prefix; + $this->Application->delayUnitProcessing('registerAggregateTag', Array ($aggregate_tag)); + } + } + + public function validate() + { + global $debugger; + + $table_name = $this->getTableName(); + $float_types = Array ('float', 'double', 'numeric'); + + $table_found = $this->Conn->Query('SHOW TABLES LIKE "' . $table_name . '"'); + + if ( !$table_found ) { + // config present, but table missing, strange + kUtil::safeDefine('DBG_RAISE_ON_WARNINGS', 1); + $debugger->appendHTML("<b class='debug_error'>Config Warning: </b>Table <strong>$table_name</strong> missing, but prefix <b>" . $this->_prefix . "</b> requires it!"); + $debugger->WarningCount++; + + return; + } + + $res = $this->Conn->Query('DESCRIBE ' . $table_name); + $config_link = $debugger->getFileLink(FULL_PATH . $this->_filename, 1, $this->_prefix); + + $error_messages = Array ( + 'field_not_found' => 'Field <strong>%s</strong> exists in the database, but <strong>is not defined</strong> in config', + 'default_missing' => 'Default value for field <strong>%s</strong> not set in config', + 'not_null_error1' => 'Field <strong>%s</strong> is NOT NULL in the database, but is not configured as not_null', // or required', + 'not_null_error2' => 'Field <strong>%s</strong> is described as NOT NULL in config, but <strong>does not have DEFAULT value</strong>', + 'not_null_error3' => 'Field <strong>%s</strong> is described as <strong>NOT NULL in config</strong>, but is <strong>NULL in db</strong>', + 'invalid_default' => '<strong>Default value</strong> for field %s<strong>%s</strong> not sync. to db (in config = %s, in db = %s)', + 'date_column_not_null_error' => 'Field <strong>%s</strong> must be NULL in config and database, since it contains date', + 'user_column_default_error' => 'Field <strong>%s</strong> must be have NULL as default value, since it holds user id', + 'type_missing' => '<strong>Type definition</strong> for field <strong>%s</strong> missing in config', + 'virtual_type_missing' => '<strong>Type definition</strong> for virtual field <strong>%s</strong> missing in config', + 'virtual_default_missing' => 'Default value for virtual field <strong>%s</strong> not set in config', + 'virtual_not_null_error' => 'Virtual field <strong>%s</strong> cannot be not null, since it doesn\'t exist in database', + 'invalid_calculated_field' => 'Calculated field <strong>%s</strong> is missing corresponding virtual field', + ); + + $config_errors = Array (); + $fields = $this->getFields(); + $table_name = preg_replace('/^' . preg_quote(TABLE_PREFIX, '/') . '(.*)/', '\\1', $table_name); // remove table prefix + + if ( $fields ) { + // validate unit config field declaration in relation to database table structure + foreach ($res as $field) { + $f_name = $field['Field']; + + if ( preg_match('/l[\d]+_[\w]/', $f_name) ) { + // skip multilingual fields + continue; + } + + if ( !array_key_exists($f_name, $fields) ) { + $config_errors[] = sprintf($error_messages['field_not_found'], $f_name); + } + else { + $db_default = $field['Default']; + + if ( is_numeric($db_default) ) { + $db_default = preg_match('/[\.,]/', $db_default) ? (float)$db_default : (int)$db_default; + } + + $default_missing = false; + $options = $fields[$f_name]; + $not_null = isset($options['not_null']) && $options['not_null']; + $formatter = array_key_exists('formatter', $options) ? $options['formatter'] : false; + + if ( !array_key_exists('default', $options) ) { + $config_errors[] = sprintf($error_messages['default_missing'], $f_name); + $default_missing = true; + } + + if ( $field['Null'] != 'YES' ) { + // field is NOT NULL in database (MySQL5 for null returns "NO", but MySQL4 returns "") + if ( $f_name != $this->getIDField() && !isset($options['not_null']) /*&& !isset($options['required'])*/ ) { + $config_errors[] = sprintf($error_messages['not_null_error1'], $f_name); + } + if ( $not_null && !isset($options['default']) ) { + $config_errors[] = sprintf($error_messages['not_null_error2'], $f_name); + } + } + elseif ( $not_null ) { + $config_errors[] = sprintf($error_messages['not_null_error3'], $f_name); + } + + if ( ($formatter == 'kDateFormatter') && $not_null ) { + $config_errors[] = sprintf($error_messages['date_column_not_null_error'], $f_name); + } + + // columns, holding userid should have NULL as default value + if ( array_key_exists('type', $options) && !$default_missing ) { + // both type and default value set + + if ( preg_match('/ById$/', $f_name) && $options['default'] !== null ) { + $config_errors[] = sprintf($error_messages['user_column_default_error'], $f_name); + } + } + + if ( !array_key_exists('type', $options) ) { + $config_errors[] = sprintf($error_messages['type_missing'], $f_name); + } + + if ( !$default_missing && ($field['Type'] != 'text') ) { + if ( is_null($db_default) && $not_null ) { + $db_default = $options['type'] == 'string' ? '' : 0; + } + + if ( $f_name == $this->getIDField() && $options['type'] != 'string' && $options['default'] !== 0 ) { + $config_errors[] = sprintf($error_messages['invalid_default'], '<span class="debug_error">IDField</span> ', $f_name, $this->_varDump($options['default']), $this->_varDump($field['Default'])); + } + else if ( ((string)$options['default'] != '#NOW#') && ($db_default !== $options['default']) && !in_array($options['type'], $float_types) ) { + $config_errors[] = sprintf($error_messages['invalid_default'], '', $f_name, $this->_varDump($options['default']), $this->_varDump($db_default)); + } + } + } + } + } + + // validate virtual fields + $virtual_fields = $this->getVirtualFields(); + + if ( $virtual_fields ) { + foreach ($virtual_fields as $f_name => $options) { + if ( !array_key_exists('type', $options) ) { + $config_errors[] = sprintf($error_messages['virtual_type_missing'], $f_name); + } + + if ( array_key_exists('not_null', $options) ) { + $config_errors[] = sprintf($error_messages['virtual_not_null_error'], $f_name); + } + + if ( !array_key_exists('default', $options) ) { + $config_errors[] = sprintf($error_messages['virtual_default_missing'], $f_name); + } + } + } + + // validate calculated fields + if ( $this->getCalculatedFieldSpecials() ) { + foreach ($this->getCalculatedFieldSpecials() as $special) { + foreach ($this->getCalculatedFieldsBySpecial($special) as $calculated_field => $calculated_field_expr) { + if ( !isset($virtual_fields[$calculated_field]) ) { + $config_errors[] = sprintf($error_messages['invalid_calculated_field'], $calculated_field); + } + } + } + + $config_errors = array_unique($config_errors); + } + + if ( $config_errors ) { + $error_prefix = '<strong class="debug_error">Config Error' . (count($config_errors) > 1 ? 's' : '') . ': </strong> for prefix <strong>' . $config_link . '</strong> (' . $table_name . ') in unit config:<br />'; + $config_errors = $error_prefix . ' ' . implode('<br /> ', $config_errors); + + kUtil::safeDefine('DBG_RAISE_ON_WARNINGS', 1); + $debugger->appendHTML($config_errors); + $debugger->WarningCount++; + } + } + + protected function _varDump($value) + { + return '<strong>' . var_export($value, true) . '</strong> of ' . gettype($value); + } } Index: branches/5.3.x/core/kernel/utility/unit_config_reader.php =================================================================== --- branches/5.3.x/core/kernel/utility/unit_config_reader.php (revision 15731) +++ branches/5.3.x/core/kernel/utility/unit_config_reader.php (revision 15732) @@ -1,1009 +1,691 @@ <?php /** * @version $Id$ * @package In-Portal * @copyright Copyright (C) 1997 - 2009 Intechnic. All rights reserved. * @license GNU/GPL * In-Portal is Open Source software. * This means that this software may have been modified pursuant * the GNU General Public License, and as distributed it includes * or is derivative of works licensed under the GNU General Public License * or other free or open source software licenses. * See http://www.in-portal.org/license for copyright notices and details. */ defined('FULL_PATH') or die('restricted access!'); class kUnitConfigReader extends kBase implements kiCacheable { /** * Configs reader * * @var Array|kUnitConfig[] * @access private */ var $configData = Array(); var $configFiles = Array(); var $CacheExpired = false; var $prefixFiles = array(); var $ProcessAllConfigs = false; var $FinalStage = false; var $StoreCache = false; var $AfterConfigProcessed = array(); /** * Escaped directory separator for using in regular expressions * * @var string */ var $_directorySeparator = ''; /** * Regular expression for detecting module folder * * @var string */ var $_moduleFolderRegExp = ''; /** * Folders to skip during unit config search * * @var Array */ var $_skipFolders = Array ('CVS', '.svn', 'admin_templates', 'libchart'); /** * Creates instance of unit config reader * */ public function __construct() { parent::__construct(); $this->_directorySeparator = preg_quote(DIRECTORY_SEPARATOR); $editor_path = explode('/', trim(EDITOR_PATH, '/')); $this->_skipFolders[] = array_pop($editor_path); // last of cmseditor folders $this->_moduleFolderRegExp = '#' . $this->_directorySeparator . '(core|modules' . $this->_directorySeparator . '.*?)' . $this->_directorySeparator . '#'; } /** * Sets data from cache to object * * @param Array $data * @access public */ public function setFromCache(&$data) { $this->prefixFiles = $data['ConfigReader.prefixFiles']; } /** * Gets object data for caching * * @access public * @return Array */ public function getToCache() { return Array ( 'ConfigReader.prefixFiles' => $this->prefixFiles, ); } function scanModules($folderPath, $cache = true) { if (defined('IS_INSTALL') && IS_INSTALL && !defined('FORCE_CONFIG_CACHE')) { // disable config caching during installation $cache = false; } if ($cache) { $restored = $this->Application->cacheManager->LoadUnitCache(); if ($restored) { if ( defined('DEBUG_MODE') && DEBUG_MODE && $this->Application->isDebugMode() ) { $this->Application->Debugger->appendHTML('UnitConfigReader: Restoring Cache'); } return; } } if ( defined('DEBUG_MODE') && DEBUG_MODE && $this->Application->isDebugMode() ) { $this->Application->Debugger->appendHTML('UnitConfigReader: Generating Cache'); } $this->ProcessAllConfigs = true; $this->includeConfigFiles($folderPath, $cache); $this->ParseConfigs(); // tell AfterConfigRead to store cache if needed // can't store it here because AfterConfigRead needs ability to change config data $this->StoreCache = $cache; if ( !$this->Application->InitDone ) { // scanModules is called multiple times during installation process $this->Application->InitManagers(); // get build-in rewrite listeners ONLY to be able to parse mod-rewrite url when unit config cache is missing $this->retrieveCollections(); $this->_sortRewriteListeners(); } $this->Application->cacheManager->applyDelayedUnitProcessing(); } function findConfigFiles($folderPath, $level = 0) { // if FULL_PATH = "/" ensure, that all "/" in $folderPath are not deleted $reg_exp = '/^' . preg_quote(FULL_PATH, '/') . '/'; $folderPath = preg_replace($reg_exp, '', $folderPath, 1); // this make sense, since $folderPath may NOT contain FULL_PATH $base_folder = FULL_PATH . $folderPath . DIRECTORY_SEPARATOR; $sub_folders = glob($base_folder . '*', GLOB_ONLYDIR); if (!$sub_folders) { return ; } if ($level == 0) { // don't scan Front-End themes because of extensive directory structure $sub_folders = array_diff($sub_folders, Array ($base_folder . 'themes', $base_folder . 'tools')); } foreach ($sub_folders as $full_path) { $sub_folder = substr($full_path, strlen($base_folder)); if (in_array($sub_folder, $this->_skipFolders)) { continue; } if (preg_match('/^\./', $sub_folder)) { // don't scan ".folders" continue; } $config_name = $this->getConfigName($folderPath . DIRECTORY_SEPARATOR . $sub_folder); if (file_exists(FULL_PATH . $config_name)) { $this->configFiles[] = $config_name; } $this->findConfigFiles($full_path, $level + 1); } } function includeConfigFiles($folderPath, $cache = true) { $this->Application->refreshModuleInfo(); if ( $this->Application->isCachingType(CACHING_TYPE_MEMORY) ) { $data = $this->Application->getCache('master:config_files', false, $cache ? CacheSettings::$unitCacheRebuildTime : 0); } else { $data = $this->Application->getDBCache('config_files', $cache ? CacheSettings::$unitCacheRebuildTime : 0); } if ( $data ) { $this->configFiles = unserialize($data); if ( !defined('DBG_VALIDATE_CONFIGS') && !DBG_VALIDATE_CONFIGS ) { shuffle($this->configFiles); } } else { $this->findConfigFiles(FULL_PATH . DIRECTORY_SEPARATOR . 'core'); // search from core directory $this->findConfigFiles($folderPath); // search from modules directory if ( $cache ) { if ( $this->Application->isCachingType(CACHING_TYPE_MEMORY) ) { $this->Application->setCache('master:config_files', serialize($this->configFiles)); } else { $this->Application->setDBCache('config_files', serialize($this->configFiles)); } } } foreach ($this->configFiles as $filename) { $prefix = $this->PreloadConfigFile($filename); if (!$prefix) { throw new Exception('Prefix not defined in config file <strong>' . $filename . '</strong>'); } } if ($cache) { unset($this->configFiles); } } /** * Process all read config files - called ONLY when there is no cache! * */ function ParseConfigs() { // 1. process normal configs $prioritized_configs = Array (); foreach ($this->configData as $prefix => $config) { if ( $config->getConfigPriority() !== false ) { $prioritized_configs[$prefix] = $config->getConfigPriority(); continue; } - $this->parseConfig($prefix); + $config->parse(); } foreach ($this->configData as $prefix => $config) { $this->postProcessConfig($prefix, 'AggregateConfigs', 'sub_prefix'); $clones = $this->postProcessConfig($prefix, 'Clones', 'prefix'); } // 2. process prioritized configs asort($prioritized_configs); + foreach ($prioritized_configs as $prefix => $priority) { - $this->parseConfig($prefix); + $this->configData[$prefix]->parse(); } } function AfterConfigRead($store_cache = null) { // if (!$this->ProcessAllConfigs) return ; $this->FinalStage = true; foreach ($this->configData as $prefix => $config) { $this->runAfterConfigRead($prefix); } if ( !isset($store_cache) ) { // $store_cache not overridden -> use global setting $store_cache = $this->StoreCache; } if ( $store_cache || (defined('IS_INSTALL') && IS_INSTALL) ) { // cache is not stored during install, but dynamic clones should be processed in any case $this->processDynamicClones(); $this->retrieveCollections(); } if ( $store_cache ) { $this->_sortRewriteListeners(); $this->Application->HandleEvent(new kEvent('adm:OnAfterCacheRebuild')); $this->Application->cacheManager->UpdateUnitCache(); if ( defined('DEBUG_MODE') && DEBUG_MODE && defined('DBG_VALIDATE_CONFIGS') && DBG_VALIDATE_CONFIGS ) { // validate configs here to have changes from OnAfterConfigRead hooks to prefixes foreach ($this->configData as $prefix => $config) { if ( !$config->getTableName() ) { continue; } - $this->ValidateConfig($prefix); + $config->validate(); } } } } /** * Sort rewrite listeners according to RewritePriority (non-prioritized listeners goes first) * */ function _sortRewriteListeners() { $listeners = Array (); $prioritized_listeners = Array (); // process non-prioritized listeners foreach ($this->Application->RewriteListeners as $prefix => $listener_data) { if ($listener_data['priority'] === false) { $listeners[$prefix] = $listener_data; } else { $prioritized_listeners[$prefix] = $listener_data['priority']; } } // process prioritized listeners asort($prioritized_listeners, SORT_NUMERIC); foreach ($prioritized_listeners as $prefix => $priority) { $listeners[$prefix] = $this->Application->RewriteListeners[$prefix]; } $this->Application->RewriteListeners = $listeners; } /** * Re-reads all configs * */ function ReReadConfigs() { // don't reset prefix file, since file scanning could slow down the process $prefix_files_backup = $this->prefixFiles; $this->Application->cacheManager->EmptyUnitCache(); $this->prefixFiles = $prefix_files_backup; // parse all configs $this->ProcessAllConfigs = true; $this->AfterConfigProcessed = Array (); $this->includeConfigFiles(MODULES_PATH, false); $this->ParseConfigs(); $this->AfterConfigRead(false); $this->processDynamicClones(); // don't call kUnitConfigReader::retrieveCollections since it // will overwrite what we already have in kApplication class instance } /** * Process clones, that were defined via OnAfterConfigRead event * */ function processDynamicClones() { $new_clones = Array(); foreach ($this->configData as $prefix => $config) { $clones = $this->postProcessConfig($prefix, 'Clones', 'prefix'); if ($clones) { $new_clones = array_merge($new_clones, $clones); } } // execute delayed methods for cloned unit configs $this->Application->cacheManager->applyDelayedUnitProcessing(); // call OnAfterConfigRead for cloned configs $new_clones = array_unique($new_clones); foreach ($new_clones as $prefix) { $this->runAfterConfigRead($prefix); } } /** * Process all collectible unit config options here to also catch ones, defined from OnAfterConfigRead events * */ function retrieveCollections() { foreach ($this->configData as $prefix => $config) { // collect replacement templates if ( $config->getReplacementTemplates() ) { $this->Application->ReplacementTemplates = array_merge($this->Application->ReplacementTemplates, $config->getReplacementTemplates()); } // collect rewrite listeners if ( $config->getRewriteListener() ) { $rewrite_listeners = $config->getRewriteListener(); if ( !is_array($rewrite_listeners) ) { // when one method is used to build and parse url $rewrite_listeners = Array ($rewrite_listeners, $rewrite_listeners); } foreach ($rewrite_listeners as $index => $rewrite_listener) { if ( strpos($rewrite_listener, ':') === false ) { $rewrite_listeners[$index] = $prefix . '_EventHandler:' . $rewrite_listener; } } $rewrite_priority = $config->getRewritePriority(); $this->Application->RewriteListeners[$prefix] = Array ('listener' => $rewrite_listeners, 'priority' => $rewrite_priority); } } } - /** - * Register nessasary classes - * This method should only process the data which is cached! - * - * @param string $prefix - * @access private - */ - function parseConfig($prefix) - { - $this->parseClasses($prefix); - $this->parseScheduledTasks($prefix); - $this->parseHooks($prefix); - $this->parseAggregatedTags($prefix); - } - - protected function parseClasses($prefix) - { - $config = $this->configData[$prefix]; - $register_classes = $this->getClasses($prefix); - - foreach ($register_classes as $class_info) { - $this->Application->registerClass( - $class_info['class'], - $config->getBasePath() . DIRECTORY_SEPARATOR . $class_info['file'], - $class_info['pseudo'] - ); - - if ( isset($class_info['build_event']) && $class_info['build_event'] ) { - $this->Application->delayUnitProcessing('registerBuildEvent', Array ($class_info['pseudo'], $class_info['build_event'])); - } - } - } - - protected function parseScheduledTasks($prefix) - { - $config = $this->configData[$prefix]; - - if ( !$config->getScheduledTasks() ) { - return ; - } - - $scheduled_tasks = $config->getScheduledTasks(); - - foreach ($scheduled_tasks as $short_name => $scheduled_task_info) { - $event_status = array_key_exists('Status', $scheduled_task_info) ? $scheduled_task_info['Status'] : STATUS_ACTIVE; - $this->Application->delayUnitProcessing('registerScheduledTask', Array ( $short_name, $config->getPrefix() . ':' . $scheduled_task_info['EventName'], $scheduled_task_info['RunSchedule'], $event_status )); - } - } - - protected function parseHooks($prefix) - { - $config = $this->configData[$prefix]; - - if ( !$config->getHooks() ) { - return; - } - - $hooks = $config->getHooks(); - - foreach ($hooks as $hook) { - if ( $config->getParentPrefix() && ($hook['HookToPrefix'] == $config->getParentPrefix()) ) { - trigger_error('Deprecated Hook Usage [prefix: <strong>' . $config->getPrefix() . '</strong>; do_prefix: <strong>' . $hook['DoPrefix'] . '</strong>] use <strong>#PARENT#</strong> as <strong>HookToPrefix</strong> value, where HookToPrefix is same as ParentPrefix', defined('E_USER_DEPRECATED') ? E_USER_DEPRECATED : E_USER_NOTICE); - } - - if ( $hook['HookToPrefix'] == '' ) { - // new: set hooktoprefix to current prefix if not set - $hook['HookToPrefix'] = $config->getPrefix(); - } - - if ( $config->getParentPrefix() ) { - // new: allow to set hook to parent prefix what ever it is - if ( $hook['HookToPrefix'] == '#PARENT#' ) { - $hook['HookToPrefix'] = $config->getParentPrefix(); - } - - if ( $hook['DoPrefix'] == '#PARENT#' ) { - $hook['DoPrefix'] = $config->getParentPrefix(); - } - } - elseif ( $hook['HookToPrefix'] == '#PARENT#' || $hook['DoPrefix'] == '#PARENT#' ) { - // we need parent prefix but it's not set ! - continue; - } - - $hook_events = (array)$hook['HookToEvent']; - $do_prefix = $hook['DoPrefix'] == '' ? $config->getPrefix() : $hook['DoPrefix']; - - foreach ($hook_events as $hook_event) { - $hook_event = $hook['HookToPrefix'] . '.' . $hook['HookToSpecial'] . ':' . $hook_event; - $do_event = $do_prefix . '.' . $hook['DoSpecial'] . ':' . $hook['DoEvent']; - - $this->Application->delayUnitProcessing('registerHook', Array ($hook_event, $do_event, $hook['Mode'], $hook['Conditional'])); - } - } - } - - protected function parseAggregatedTags($prefix) - { - $config = $this->configData[$prefix]; - $aggregated_tags = $config->getAggregateTags(); - - if ( !$aggregated_tags ) { - return; - } - - foreach ($aggregated_tags as $aggregate_tag) { - if ( $config->getParentPrefix() ) { - if ( $aggregate_tag['AggregateTo'] == $config->getParentPrefix() ) { - trigger_error('Deprecated Aggregate Tag Usage [prefix: <b>' . $config->getPrefix() . '</b>; AggregateTo: <b>' . $aggregate_tag['AggregateTo'] . '</b>] use <b>#PARENT#</b> as <b>AggregateTo</b> value, where AggregateTo is same as ParentPrefix', defined('E_USER_DEPRECATED') ? E_USER_DEPRECATED : E_USER_NOTICE); - } - - if ( $aggregate_tag['AggregateTo'] == '#PARENT#' ) { - $aggregate_tag['AggregateTo'] = $config->getParentPrefix(); - } - } - - $aggregate_tag['LocalPrefix'] = $config->getPrefix(); - $this->Application->delayUnitProcessing('registerAggregateTag', Array ($aggregate_tag)); - } - } - - function ValidateConfig($prefix) - { - global $debugger; - - $config = $this->configData[$prefix]; - - $table_name = $config->getTableName(); - $float_types = Array ('float', 'double', 'numeric'); - - $table_found = $this->Conn->Query('SHOW TABLES LIKE "' . $table_name . '"'); - if ( !$table_found ) { - // config present, but table missing, strange - kUtil::safeDefine('DBG_RAISE_ON_WARNINGS', 1); - $debugger->appendHTML("<b class='debug_error'>Config Warning: </b>Table <strong>$table_name</strong> missing, but prefix <b>" . $prefix . "</b> requires it!"); - $debugger->WarningCount++; - - return; - } - - $res = $this->Conn->Query('DESCRIBE ' . $table_name); - $config_link = $debugger->getFileLink(FULL_PATH . $this->prefixFiles[$prefix], 1, $prefix); - - $error_messages = Array ( - 'field_not_found' => 'Field <strong>%s</strong> exists in the database, but <strong>is not defined</strong> in config', - 'default_missing' => 'Default value for field <strong>%s</strong> not set in config', - 'not_null_error1' => 'Field <strong>%s</strong> is NOT NULL in the database, but is not configured as not_null', // or required', - 'not_null_error2' => 'Field <strong>%s</strong> is described as NOT NULL in config, but <strong>does not have DEFAULT value</strong>', - 'not_null_error3' => 'Field <strong>%s</strong> is described as <strong>NOT NULL in config</strong>, but is <strong>NULL in db</strong>', - 'invalid_default' => '<strong>Default value</strong> for field %s<strong>%s</strong> not sync. to db (in config = %s, in db = %s)', - 'date_column_not_null_error' => 'Field <strong>%s</strong> must be NULL in config and database, since it contains date', - 'user_column_default_error' => 'Field <strong>%s</strong> must be have NULL as default value, since it holds user id', - 'type_missing' => '<strong>Type definition</strong> for field <strong>%s</strong> missing in config', - 'virtual_type_missing' => '<strong>Type definition</strong> for virtual field <strong>%s</strong> missing in config', - 'virtual_default_missing' => 'Default value for virtual field <strong>%s</strong> not set in config', - 'virtual_not_null_error' => 'Virtual field <strong>%s</strong> cannot be not null, since it doesn\'t exist in database', - 'invalid_calculated_field' => 'Calculated field <strong>%s</strong> is missing corresponding virtual field', - ); - - $config_errors = Array (); - $fields = $config->getFields(); - $table_name = preg_replace('/^' . preg_quote(TABLE_PREFIX, '/') . '(.*)/', '\\1', $table_name); // remove table prefix - - if ( $fields ) { - // validate unit config field declaration in relation to database table structure - foreach ($res as $field) { - $f_name = $field['Field']; - - if ( preg_match('/l[\d]+_[\w]/', $f_name) ) { - // skip multilingual fields - continue; - } - - if ( !array_key_exists($f_name, $fields) ) { - $config_errors[] = sprintf($error_messages['field_not_found'], $f_name); - } - else { - $db_default = $field['Default']; - - if ( is_numeric($db_default) ) { - $db_default = preg_match('/[\.,]/', $db_default) ? (float)$db_default : (int)$db_default; - } - - $default_missing = false; - $options = $fields[$f_name]; - $not_null = isset($options['not_null']) && $options['not_null']; - $formatter = array_key_exists('formatter', $options) ? $options['formatter'] : false; - - if ( !array_key_exists('default', $options) ) { - $config_errors[] = sprintf($error_messages['default_missing'], $f_name); - $default_missing = true; - } - - if ( $field['Null'] != 'YES' ) { - // field is NOT NULL in database (MySQL5 for null returns "NO", but MySQL4 returns "") - if ( $f_name != $config->getIDField() && !isset($options['not_null']) /*&& !isset($options['required'])*/ ) { - $config_errors[] = sprintf($error_messages['not_null_error1'], $f_name); - } - if ( $not_null && !isset($options['default']) ) { - $config_errors[] = sprintf($error_messages['not_null_error2'], $f_name); - } - } - elseif ( $not_null ) { - $config_errors[] = sprintf($error_messages['not_null_error3'], $f_name); - } - - if ( ($formatter == 'kDateFormatter') && $not_null ) { - $config_errors[] = sprintf($error_messages['date_column_not_null_error'], $f_name); - } - - // columns, holding userid should have NULL as default value - if ( array_key_exists('type', $options) && !$default_missing ) { - // both type and default value set - - if ( preg_match('/ById$/', $f_name) && $options['default'] !== null ) { - $config_errors[] = sprintf($error_messages['user_column_default_error'], $f_name); - } - } - - if ( !array_key_exists('type', $options) ) { - $config_errors[] = sprintf($error_messages['type_missing'], $f_name); - } - - if ( !$default_missing && ($field['Type'] != 'text') ) { - if ( is_null($db_default) && $not_null ) { - $db_default = $options['type'] == 'string' ? '' : 0; - } - - if ( $f_name == $config->getIDField() && $options['type'] != 'string' && $options['default'] !== 0 ) { - $config_errors[] = sprintf($error_messages['invalid_default'], '<span class="debug_error">IDField</span> ', $f_name, $this->varDump($options['default']), $this->varDump($field['Default'])); - } - else if ( ((string)$options['default'] != '#NOW#') && ($db_default !== $options['default']) && !in_array($options['type'], $float_types) ) { - $config_errors[] = sprintf($error_messages['invalid_default'], '', $f_name, $this->varDump($options['default']), $this->varDump($db_default)); - } - } - } - } - } - - // validate virtual fields - if ( $config->getVirtualFields() ) { - foreach ($config->getVirtualFields() as $f_name => $options) { - if ( !array_key_exists('type', $options) ) { - $config_errors[] = sprintf($error_messages['virtual_type_missing'], $f_name); - } - - if ( array_key_exists('not_null', $options) ) { - $config_errors[] = sprintf($error_messages['virtual_not_null_error'], $f_name); - } - - if ( !array_key_exists('default', $options) ) { - $config_errors[] = sprintf($error_messages['virtual_default_missing'], $f_name); - } - } - } - - // validate calculated fields - if ( $config->getCalculatedFieldSpecials() ) { - $virtual_fields = $config->getVirtualFields(); - - foreach ($config->getCalculatedFieldSpecials() as $special) { - foreach ($config->getCalculatedFieldsBySpecial($special) as $calculated_field => $calculated_field_expr) { - if ( !isset($virtual_fields[$calculated_field]) ) { - $config_errors[] = sprintf($error_messages['invalid_calculated_field'], $calculated_field); - } - } - } - - $config_errors = array_unique($config_errors); - } - - if ( $config_errors ) { - $error_prefix = '<strong class="debug_error">Config Error' . (count($config_errors) > 1 ? 's' : '') . ': </strong> for prefix <strong>' . $config_link . '</strong> (' . $table_name . ') in unit config:<br />'; - $config_errors = $error_prefix . ' ' . implode('<br /> ', $config_errors); - - kUtil::safeDefine('DBG_RAISE_ON_WARNINGS', 1); - $debugger->appendHTML($config_errors); - $debugger->WarningCount++; - } - } - - function varDump($value) - { - return '<strong>'.var_export($value, true).'</strong> of '.gettype($value); - } - function postProcessConfig($prefix, $config_key, $dst_prefix_var) { $main_config = $this->configData[$prefix]; $sub_configs = $main_config->getSetting($config_key); if ( !$sub_configs ) { return Array (); } $processed = Array (); $main_config->setSetting($config_key, null); foreach ($sub_configs as $sub_prefix => $sub_config_data) { if ( $config_key == 'AggregateConfigs' && !isset($this->configData[$sub_prefix]) ) { $this->loadConfig($sub_prefix); } $sub_config_data['Prefix'] = $sub_prefix; $sub_config_base = $this->configData[$$dst_prefix_var]->getRaw(); $sub_config = new kUnitConfig($sub_prefix, kUtil::array_merge_recursive($sub_config_base, $sub_config_data)); $this->configData[$sub_prefix] = $sub_config; // when merging empty array to non-empty results non-empty array, but empty is required foreach ($sub_config_data as $sub_key => $sub_value) { if ( !$sub_value && is_array($sub_value) ) { $sub_config->setSetting($sub_key, null); } } if ( $config_key == 'Clones' ) { $this->prefixFiles[$sub_prefix] = $this->prefixFiles[$prefix]; } $this->postProcessConfig($sub_prefix, $config_key, $dst_prefix_var); if ( $config_key == 'AggregateConfigs' ) { $processed = array_merge($this->postProcessConfig($sub_prefix, 'Clones', 'prefix'), $processed); } elseif ( $this->ProcessAllConfigs ) { - $this->parseConfig($sub_prefix); + $this->configData[$sub_prefix]->parse(); } array_push($processed, $sub_prefix); } if ( !$prefix ) { // configs, that used only for cloning & not used itself unset($this->configData[$prefix]); } return array_unique($processed); } function PreloadConfigFile($filename) { $config_found = file_exists(FULL_PATH . $filename) && $this->configAllowed($filename); if ( defined('DEBUG_MODE') && DEBUG_MODE && defined('DBG_PROFILE_INCLUDES') && DBG_PROFILE_INCLUDES ) { if ( in_array($filename, get_included_files()) ) { return ''; } global $debugger; if ( $config_found ) { $file = FULL_PATH . $filename; $file_crc = crc32($file); $debugger->ProfileStart('inc_' . $file_crc, $file); include_once($file); $debugger->ProfileFinish('inc_' . $file_crc); $debugger->profilerAddTotal('includes', 'inc_' . $file_crc); } } elseif ( $config_found ) { include_once(FULL_PATH . $filename); } if ( $config_found ) { /* @var $config kUnitConfig|Array */ if ( isset($config) && $config ) { // config file is included for 1st time -> save it's content for future processing if ( !is_object($config) ) { $prefix = array_key_exists('Prefix', $config) ? $config['Prefix'] : ''; $config = new kUnitConfig($prefix, $config); } else { $prefix = $config->getPrefix(); } preg_match($this->_moduleFolderRegExp, $filename, $regs); $config->setModuleFolder(str_replace(DIRECTORY_SEPARATOR, '/', $regs[1])); $config->setBasePath(dirname(FULL_PATH . $filename)); + $config->setFilename($filename); if ( $config->getAdminTemplatePath() !== false ) { // append template base folder for admin templates path of this prefix $module_templates = $regs[1] == 'core' ? '' : substr($regs[1], 8) . '/'; $config->setAdminTemplatePath($module_templates . $config->getAdminTemplatePath()); } if ( array_key_exists($prefix, $this->prefixFiles) && ($this->prefixFiles[$prefix] != $filename) ) { trigger_error( 'Single unit config prefix "<strong>' . $prefix . '</strong>" ' . 'is used in multiple unit config files: ' . '"<strong>' . $this->prefixFiles[$prefix] . '</strong>", "<strong>' . $filename . '</strong>"', E_USER_WARNING ); } $this->configData[$prefix] = $config; $this->prefixFiles[$prefix] = $filename; return $prefix; } else { $prefix = array_search($filename, $this->prefixFiles); if ( $prefix ) { // attempt is made to include config file twice or more, but include_once prevents that, // but file exists on hdd, then it is already saved to all required arrays, just return it's prefix return $prefix; } } } return 'dummy'; } function loadConfig($prefix) { if ( !isset($this->prefixFiles[$prefix]) ) { throw new Exception('Configuration file for prefix "<strong>' . $prefix . '</strong>" is unknown'); return ; } $file = $this->prefixFiles[$prefix]; $prefix = $this->PreloadConfigFile($file); if ($this->FinalStage) { // run prefix OnAfterConfigRead so all - // hooks to it can define their clonses + // hooks to it can define their clones $this->runAfterConfigRead($prefix); } $clones = $this->postProcessConfig($prefix, 'AggregateConfigs', 'sub_prefix'); $clones = array_merge($this->postProcessConfig($prefix, 'Clones', 'prefix'), $clones); if ($this->FinalStage) { $clones = array_unique($clones); foreach ($clones as $a_prefix) { $this->runAfterConfigRead($a_prefix); } } } function runAfterConfigRead($prefix) { if (in_array($prefix, $this->AfterConfigProcessed)) { return ; } $this->Application->HandleEvent( new kEvent($prefix . ':OnAfterConfigRead') ); if (!(defined('IS_INSTALL') && IS_INSTALL)) { // allow to call OnAfterConfigRead multiple times during install array_push($this->AfterConfigProcessed, $prefix); } } /** * Returns unit config for given prefix * * @param string $prefix * @return kUnitConfig * @access public */ public function getUnitConfig($prefix = null) { if ( !isset($this->configData[$prefix]) ) { $this->loadConfig($prefix); } return $this->configData[$prefix]; } /** * Returns prefixes of unit configs, that were registered * * @return Array * @access public */ public function getPrefixes() { return array_keys($this->configData); } - protected function getClasses($prefix) - { - $config = $this->configData[$prefix]; - $class_params = Array ('ItemClass', 'ListClass', 'EventHandlerClass', 'TagProcessorClass'); - $register_classes = $config->getRegisterClasses(); - - foreach ($class_params as $param_name) { - $value = $config->getSetting($param_name); - - if ( !$value ) { - continue; - } - - $value['pseudo'] = $this->getPseudoByOptionName($param_name, $prefix); - $config->setSetting($param_name, $value); - - $register_classes[] = $value; - } - - return $register_classes; - } - - protected function getPseudoByOptionName($option_name, $prefix) - { - $pseudo_class_map = Array ( - 'ItemClass' => '%s', - 'ListClass' => '%s_List', - 'EventHandlerClass' => '%s_EventHandler', - 'TagProcessorClass' => '%s_TagProcessor' - ); - - return sprintf($pseudo_class_map[$option_name], $prefix); - } - /** * Get's config file name based * on folder name supplied * * @param string $folderPath * @return string * @access private */ function getConfigName($folderPath) { return $folderPath . DIRECTORY_SEPARATOR . basename($folderPath) . '_config.php'; } /** * Checks if config file is allowed for includion (if module of config is installed) * * @param string $config_path relative path from in-portal directory */ function configAllowed($config_path) { static $module_paths = null; if (defined('IS_INSTALL') && IS_INSTALL) { // at installation start no modules in db and kernel configs could not be read return true; } if (preg_match('#^' . $this->_directorySeparator . 'core#', $config_path)) { // always allow to include configs from "core" module's folder return true; } if (!$this->Application->ModuleInfo) { return false; } if (!isset($module_paths)) { $module_paths = Array (); foreach ($this->Application->ModuleInfo as $module_name => $module_info) { $module_paths[] = str_replace('/', DIRECTORY_SEPARATOR, rtrim($module_info['Path'], '/')); } $module_paths = array_unique($module_paths); } preg_match($this->_moduleFolderRegExp, $config_path, $rets); // config file path starts with module folder path return in_array($rets[1], $module_paths); } /** * Returns true if config exists and is allowed for reading * * @param string $prefix * @return bool */ function prefixRegistred($prefix) { return isset($this->prefixFiles[$prefix]) ? true : false; } /** * Returns config file for given prefix * * @param string $prefix * @return string */ function getPrefixFile($prefix) { return array_key_exists($prefix, $this->prefixFiles) ? $this->prefixFiles[$prefix] : false; } function iterateConfigs($callback_function, $params) { $this->includeConfigFiles(MODULES_PATH); //make sure to re-read all configs $this->AfterConfigRead(); foreach ($this->configData as $prefix => $config) { $callback_function[0]->$callback_function[1]($prefix, $config, $params); } } } \ No newline at end of file