Application =& kApplication::Instance(); $this->Conn =& $this->Application->GetADODBConnection(); } /** * Set's prefix and special * * @param string $prefix * @param string $special * @access public */ public function Init($prefix, $special) { $prefix = explode('_', $prefix, 2); $this->Prefix = $prefix[0]; $this->Special = $special; $this->prefixSpecial = rtrim($this->Prefix . '.' . $this->Special, '.'); } /** * Returns prefix and special (when present) joined by a "." * * @return string * @access public */ public function getPrefixSpecial() { return $this->prefixSpecial; } /** * Creates string representation of a class (for logging) * * @return string * @access public */ public function __toString() { $ret = 'ClassName: ' . get_class($this); try { $ret .= '; PrefixSpecial: ' . $this->getPrefixSpecial(); } catch (Exception $e) {} return $ret; } } class kHelper extends kBase { /** * Performs helper initialization * * @access public */ public function InitHelper() { } /** * Append prefix and special to tag * params (get them from tagname) like * they were really passed as params * * @param string $prefix_special * @param Array $tag_params * @return Array * @access protected */ protected function prepareTagParams($prefix_special, $tag_params = Array()) { $parts = explode('.', $prefix_special); $ret = $tag_params; $ret['Prefix'] = $parts[0]; $ret['Special'] = count($parts) > 1 ? $parts[1] : ''; $ret['PrefixSpecial'] = $prefix_special; return $ret; } } abstract class kDBBase extends kBase { /** * Name of primary key field for the unit * * @var string * @access public * @see kDBBase::TableName */ public $IDField = ''; /** * Unit's database table name * * @var string * @access public */ public $TableName = ''; /** * Form name, used for validation * * @var string */ protected $formName = ''; /** * Final form configuration * * @var Array */ protected $formConfig = Array (); /** * SELECT, FROM, JOIN parts of SELECT query (no filters, no limit, no ordering) * * @var string * @access protected */ protected $SelectClause = ''; /** * Unit fields definitions (fields from database plus virtual fields) * * @var Array * @access protected */ protected $Fields = Array (); /** * Fields, that have current time as their default value. * * @var array */ protected $currentTimeFields = array(); /** * Mapping between unit custom field IDs and their names * * @var Array * @access protected */ protected $customFields = Array (); /** * Unit virtual field definitions * * @var Array * @access protected * @see kDBBase::getVirtualFields() * @see kDBBase::setVirtualFields() */ protected $VirtualFields = Array (); /** * Fields that need to be queried using custom expression, e.g. IF(...) AS value * * @var Array * @access protected */ protected $CalculatedFields = Array (); /** * Fields that contain aggregated functions, e.g. COUNT, SUM, etc. * * @var Array * @access protected */ protected $AggregatedCalculatedFields = Array (); /** * Tells, that multilingual fields sould not be populated by default. * Can be overriden from kDBBase::Configure method * * @var bool * @access protected */ protected $populateMultiLangFields = false; /** * Event, that was used to create this object * * @var kEvent * @access protected */ protected $parentEvent = null; /** * Formatters cache. * * @var kFormatter[] */ static private $_formattersCache = array(); /** * Sets new parent event to the object * * @param kEvent $event * @return void * @access public */ public function setParentEvent($event) { $this->parentEvent = $event; } /** * Set object' TableName to LIVE table, defined in unit config * * @access public */ public function SwitchToLive() { $this->TableName = $this->Application->getUnitOption($this->Prefix, 'TableName'); } /** * Set object' TableName to TEMP table created based on table, defined in unit config * * @access public */ public function SwitchToTemp() { $table_name = $this->Application->getUnitOption($this->Prefix, 'TableName'); $this->TableName = $this->Application->GetTempName($table_name, 'prefix:' . $this->Prefix); } /** * Checks if object uses temp table * * @return bool * @access public */ public function IsTempTable() { return $this->Application->IsTempTable($this->TableName); } /** * Sets SELECT part of list' query * * @param string $sql SELECT and FROM [JOIN] part of the query up to WHERE * @access public */ public function SetSelectSQL($sql) { $this->SelectClause = $sql; } /** * Returns object select clause without any transformations * * @return string * @access public */ public function GetPlainSelectSQL() { return $this->SelectClause; } /** * Returns SELECT part of list' query. * 1. Occurrences of "%1$s" and "%s" are replaced to kDBBase::TableName * 2. Occurrences of "%3$s" are replaced to temp table prefix (only for table, using TABLE_PREFIX) * * @param string $base_query given base query will override unit default select query * @param bool $replace_table replace all possible occurrences * @return string * @access public * @see kDBBase::replaceModePrefix */ public function GetSelectSQL($base_query = null, $replace_table = true) { if (!isset($base_query)) { $base_query = $this->SelectClause; } if (!$replace_table) { return $base_query; } $query = str_replace(Array('%1$s', '%s'), $this->TableName, $base_query); return $this->replaceModePrefix($query); } /** * Allows sub-stables to be in same mode as main item (e.g. LEFT JOINED ones) * * @param string $query * @return string * @access protected */ protected function replaceModePrefix($query) { $live_table = substr($this->Application->GetLiveName($this->TableName), strlen(TABLE_PREFIX)); if (preg_match('/'.preg_quote(TABLE_PREFIX, '/').'(.*)'.preg_quote($live_table, '/').'/', $this->TableName, $rets)) { // will only happen, when table has a prefix (like in K4) return str_replace('%3$s', $rets[1], $query); } // will happen, when K3 table without prefix is used return $query; } /** * Sets calculated fields * * @param Array $fields * @access public */ public function setCalculatedFields($fields) { $this->CalculatedFields = $fields; } /** * Adds calculated field declaration to object. * * @param string $name * @param string $sql_clause * @access public */ public function addCalculatedField($name, $sql_clause) { $this->CalculatedFields[$name] = $sql_clause; } /** * Returns required mixing of aggregated & non-aggregated calculated fields * * @param int $aggregated 0 - having + aggregated, 1 - having only, 2 - aggregated only * @return Array * @access public */ public function getCalculatedFields($aggregated = 1) { switch ($aggregated) { case 0: $fields = array_merge($this->CalculatedFields, $this->AggregatedCalculatedFields); break; case 1: $fields = $this->CalculatedFields; break; case 2: $fields = $this->AggregatedCalculatedFields; // TODO: never used break; default: $fields = Array(); break; } return $fields; } /** * Checks, that given field is a calculated field * * @param string $field * @return bool * @access public */ public function isCalculatedField($field) { return array_key_exists($field, $this->CalculatedFields); } /** * Insert calculated fields sql into query in place of %2$s, * return processed query. * * @param string $query * @param int $aggregated 0 - having + aggregated, 1 - having only, 2 - aggregated only * @return string * @access protected */ protected function addCalculatedFields($query, $aggregated = 1) { $fields = $this->getCalculatedFields($aggregated); if ($fields) { $sql = Array (); $fields = str_replace('%2$s', $this->Application->GetVar('m_lang'), $fields); foreach ($fields as $field_name => $field_expression) { $sql[] = '('.$field_expression.') AS `'.$field_name.'`'; } $sql = implode(',',$sql); return $this->Application->ReplaceLanguageTags( str_replace('%2$s', ','.$sql, $query) ); } return str_replace('%2$s', '', $query); } /** * Performs initial object configuration, which includes setting the following: * - primary key and table name * - field definitions (including field modifiers, formatters, default values) * * @param bool $populate_ml_fields create all ml fields from db in config or not * @param string $form_name form name for validation * @access public */ public function Configure($populate_ml_fields = null, $form_name = null) { if ( isset($populate_ml_fields) ) { $this->populateMultiLangFields = $populate_ml_fields; } $this->IDField = $this->Application->getUnitOption($this->Prefix, 'IDField'); $this->TableName = $this->Application->getUnitOption($this->Prefix, 'TableName'); $this->initForm($form_name); $this->defineFields(); $this->ApplyFieldModifiers(null, true); // should be called only after all fields definitions been set $this->prepareConfigOptions(); // this should go last, but before setDefaultValues, order is significant! // only set on first call of method if ( isset($populate_ml_fields) ) { $this->SetDefaultValues(); } } /** * Adjusts object according to given form name * * @param string $form_name * @return void * @access protected */ protected function initForm($form_name = null) { $forms = $this->Application->getUnitOption($this->Prefix, 'Forms', Array ()); $this->formName = $form_name; $this->formConfig = isset($forms['default']) ? $forms['default'] : Array (); if ( !$this->formName ) { return ; } if ( !array_key_exists($this->formName, $forms) ) { trigger_error('Form "' . $this->formName . '" isn\'t declared in "' . $this->Prefix . '" unit config.', E_USER_NOTICE); } else { $this->formConfig = kUtil::array_merge_recursive($this->formConfig, $forms[$this->formName]); } } /** * Add field definitions from all possible sources * Used field sources: database fields, custom fields, virtual fields, calculated fields, aggregated calculated fields * * @access protected */ protected function defineFields() { $this->Fields = $this->getFormOption('Fields', Array ()); $this->customFields = $this->getFormOption('CustomFields', Array()); $this->setVirtualFields( $this->getFormOption('VirtualFields', Array ()) ); $calculated_fields = $this->getFormOption('CalculatedFields', Array()); $this->CalculatedFields = $this->getFieldsBySpecial($calculated_fields); $aggregated_calculated_fields = $this->getFormOption('AggregatedCalculatedFields', Array()); $this->AggregatedCalculatedFields = $this->getFieldsBySpecial($aggregated_calculated_fields); } /** * Returns form name, used for validation * * @return string */ public function getFormName() { return $this->formName; } /** * Reads unit (specified by $prefix) option specified by $option and applies form change to it * * @param string $option * @param mixed $default * @return string * @access public */ public function getFormOption($option, $default = false) { $ret = $this->Application->getUnitOption($this->Prefix, $option, $default); if ( isset($this->formConfig[$option]) ) { $ret = kUtil::array_merge_recursive($ret, $this->formConfig[$option]); } return $ret; } /** * Only exteracts fields, that match current object Special * * @param Array $fields * @return Array * @access protected */ protected function getFieldsBySpecial($fields) { if ( array_key_exists($this->Special, $fields) ) { return $fields[$this->Special]; } return array_key_exists('', $fields) ? $fields[''] : Array(); } /** * Sets aggeregated calculated fields * * @param Array $fields * @access public */ public function setAggregatedCalculatedFields($fields) { $this->AggregatedCalculatedFields = $fields; } /** * Set's field names from table from config * * @param Array $fields * @access public */ public function setCustomFields($fields) { $this->customFields = $fields; } /** * Returns custom fields information from table from config * * @return Array * @access public */ public function getCustomFields() { return $this->customFields; } /** * Set's fields information from table from config * * @param Array $fields * @access public */ public function setFields($fields) { $this->Fields = $fields; } /** * Returns fields information from table from config * * @return Array * @access public */ public function getFields() { return $this->Fields; } /** * Checks, that given field exists * * @param string $field * @return bool * @access public */ public function isField($field) { return array_key_exists($field, $this->Fields); } /** * Override field options with ones defined in submit via "field_modfiers" array (common for all prefixes) * * @param Array $field_modifiers * @param bool $from_submit * @return void * @access public * @author Alex */ public function ApplyFieldModifiers($field_modifiers = null, $from_submit = false) { $allowed_modifiers = Array ('required', 'multiple'); if ( $this->Application->isAdminUser ) { // can change upload dir on the fly (admin only!) $allowed_modifiers[] = 'upload_dir'; } if ( !isset($field_modifiers) ) { $field_modifiers = $this->Application->GetVar('field_modifiers'); if ( !$field_modifiers ) { // no field modifiers return; } $field_modifiers = getArrayValue($field_modifiers, $this->getPrefixSpecial()); } if ( !$field_modifiers ) { // no field modifiers for current prefix_special return; } $fields = $this->Application->getUnitOption($this->Prefix, 'Fields', Array ()); $virtual_fields = $this->Application->getUnitOption($this->Prefix, 'VirtualFields', Array ()); foreach ($field_modifiers as $field => $field_options) { foreach ($field_options as $option_name => $option_value) { if ( !in_array(strtolower($option_name), $allowed_modifiers) ) { continue; } if ( $from_submit ) { // there are no "lN_FieldName" fields, since ApplyFieldModifiers is // called before PrepareOptions method, which creates them $field = preg_replace('/^l[\d]+_(.*)/', '\\1', $field); } if ( $this->isVirtualField($field) ) { $virtual_fields[$field][$option_name] = $option_value; $this->SetFieldOption($field, $option_name, $option_value, true); } $fields[$field][$option_name] = $option_value; $this->SetFieldOption($field, $option_name, $option_value); } } $this->Application->setUnitOption($this->Prefix, 'Fields', $fields); $this->Application->setUnitOption($this->Prefix, 'VirtualFields', $virtual_fields); } /** * Set fields (+options) for fields that physically doesn't exist in database * * @param Array $fields * @access public */ public function setVirtualFields($fields) { if ($fields) { $this->VirtualFields = $fields; $this->Fields = array_merge($this->VirtualFields, $this->Fields); } } /** * Returns virtual fields * * @return Array * @access public */ public function getVirtualFields() { return $this->VirtualFields; } /** * Checks, that given field is a virtual field * * @param string $field * @return bool * @access public */ public function isVirtualField($field) { return array_key_exists($field, $this->VirtualFields); } /** * Performs additional initialization for field default values * * @access protected */ protected function SetDefaultValues() { foreach ( $this->Fields as $field => $options ) { if ( array_key_exists('default', $options) ) { if ( $options['default'] === '#NOW#' ) { $this->currentTimeFields[] = $field; } if ( in_array($field, $this->currentTimeFields) ) { $this->Fields[$field]['default'] = adodb_mktime(); } } } } /** * Overwrites field definition in unit config * * @param string $field * @param Array $options * @param bool $is_virtual * @access public */ public function SetFieldOptions($field, $options, $is_virtual = false) { if ($is_virtual) { $this->VirtualFields[$field] = $options; $this->Fields = array_merge($this->VirtualFields, $this->Fields); } else { $this->Fields[$field] = $options; } } /** * Changes/sets given option's value in given field definiton * * @param string $field * @param string $option_name * @param mixed $option_value * @param bool $is_virtual * @access public */ public function SetFieldOption($field, $option_name, $option_value, $is_virtual = false) { if ($is_virtual) { $this->VirtualFields[$field][$option_name] = $option_value; } $this->Fields[$field][$option_name] = $option_value; } /** * Returns field definition from unit config. * Also executes sql from "options_sql" field option to form "options" field option * * @param string $field * @param bool $is_virtual * @return Array * @access public */ public function GetFieldOptions($field, $is_virtual = false) { $property_name = $is_virtual ? 'VirtualFields' : 'Fields'; if ( !array_key_exists($field, $this->$property_name) ) { return Array (); } if (!$is_virtual) { if (!array_key_exists('options_prepared', $this->Fields[$field]) || !$this->Fields[$field]['options_prepared']) { // executes "options_sql" from field definition, only when field options are accessed (late binding) $this->PrepareFieldOptions($field); $this->Fields[$field]['options_prepared'] = true; } } return $this->{$property_name}[$field]; } /** * Returns field option * * @param string $field * @param string $option_name * @param bool $is_virtual * @param mixed $default * @return mixed * @access public */ public function GetFieldOption($field, $option_name, $is_virtual = false, $default = false) { $field_options = $this->GetFieldOptions($field, $is_virtual); if ( !$field_options && strpos($field, '#') === false ) { // we use "#FIELD_NAME#" as field for InputName tag in JavaScript, so ignore it $form_name = $this->getFormName(); trigger_error('Field "' . $field . '" is not defined' . ($form_name ? ' on "' . $this->getFormName() . '" form' : '') . ' in "' . $this->Prefix . '" unit config', E_USER_WARNING); return false; } return array_key_exists($option_name, $field_options) ? $field_options[$option_name] : $default; } /** * Returns formatted field value * * @param string $name * @param string $format * * @return string * @access protected */ public function GetField($name, $format = null) { $formatter_class = $this->GetFieldOption($name, 'formatter'); if ( $formatter_class ) { $value = ($formatter_class == 'kMultiLanguage') && !preg_match('/^l[0-9]+_/', $name) ? '' : $this->GetDBField($name); return $this->getFormatter($formatter_class)->Format($value, $name, $this, $format); } return $this->GetDBField($name); } /** * Returns unformatted field value * * @param string $field * @return string * @access public */ abstract public function GetDBField($field); /** * Checks of object has given field * * @param string $name * @return bool * @access public */ abstract public function HasField($name); /** * Returns field values * * @return Array * @access public */ abstract public function GetFieldValues(); /** * Populates values of sub-fields, based on formatters, set to mater fields * * @param Array $fields * @access public * @todo Maybe should not be publicly accessible */ public function UpdateFormattersSubFields($fields = null) { if ( !is_array($fields) ) { $fields = array_keys($this->Fields); } foreach ($fields as $field) { if ( isset($this->Fields[$field]['formatter']) ) { $this ->getFormatter($this->Fields[$field]['formatter']) ->UpdateSubFields($field, $this->GetDBField($field), $this->Fields[$field], $this); } } } /** * Use formatters, specified in field declarations to perform additional field initialization in unit config * * @access protected */ protected function prepareConfigOptions() { $field_names = array_keys($this->Fields); foreach ($field_names as $field_name) { if ( !array_key_exists('formatter', $this->Fields[$field_name]) ) { continue; } $this ->getFormatter($this->Fields[$field_name]['formatter']) ->PrepareOptions($field_name, $this->Fields[$field_name], $this); } } /** * Returns formatter. * * @param string $name Name. * * @return kFormatter */ protected function getFormatter($name) { if ( !isset(self::$_formattersCache[$name]) ) { self::$_formattersCache[$name] = $this->Application->recallObject($name); } return self::$_formattersCache[$name]; } /** * Escapes fields only, not expressions * * @param string $field_expr * @return string * @access protected */ protected function escapeField($field_expr) { return preg_match('/[.(]/', $field_expr) ? $field_expr : '`' . $field_expr . '`'; } /** * Replaces current language id in given field options * * @param string $field_name * @param Array $field_option_names * @access protected */ protected function _replaceLanguageId($field_name, $field_option_names) { // don't use GetVar('m_lang') since it's always equals to default language on editing form in admin $current_language_id = $this->Application->Phrases->LanguageId; $primary_language_id = $this->Application->GetDefaultLanguageId(); $field_options =& $this->Fields[$field_name]; foreach ($field_option_names as $option_name) { $field_options[$option_name] = str_replace('%2$s', $current_language_id, $field_options[$option_name]); $field_options[$option_name] = str_replace('%3$s', $primary_language_id, $field_options[$option_name]); } } /** * Transforms "options_sql" field option into valid "options" array for given field * * @param string $field_name * @access protected */ protected function PrepareFieldOptions($field_name) { $field_options =& $this->Fields[$field_name]; if (array_key_exists('options_sql', $field_options) ) { // get options based on given sql $replace_options = Array ('option_title_field', 'option_key_field', 'options_sql'); $this->_replaceLanguageId($field_name, $replace_options); $select_clause = $this->escapeField($field_options['option_title_field']) . ',' . $this->escapeField($field_options['option_key_field']); $sql = sprintf($field_options['options_sql'], $select_clause); if (array_key_exists('serial_name', $field_options)) { // try to cache option sql on serial basis $cache_key = 'sql_' . crc32($sql) . '[%' . $field_options['serial_name'] . '%]'; $dynamic_options = $this->Application->getCache($cache_key); if ($dynamic_options === false) { $this->Conn->nextQueryCachable = true; $dynamic_options = $this->Conn->GetCol($sql, preg_replace('/^.*?\./', '', $field_options['option_key_field'])); $this->Application->setCache($cache_key, $dynamic_options); } } else { // don't cache options sql $dynamic_options = $this->Conn->GetCol($sql, preg_replace('/^.*?\./', '', $field_options['option_key_field'])); } $options_hash = array_key_exists('options', $field_options) ? $field_options['options'] : Array (); $field_options['options'] = kUtil::array_merge_recursive($options_hash, $dynamic_options); // because of numeric keys } } /** * Returns ID of currently processed record * * @return int * @access public */ public function GetID() { return $this->GetDBField($this->IDField); } /** * Allows kDBTagProcessor.SectionTitle to detect if it's editing or new item creation * * @return bool * @access public */ public function IsNewItem() { return $this->GetID() ? false : true; } /** * Returns parent table information * * @param string $special special of main item * @param bool $guess_special if object retrieved with specified special is not loaded, then try not to use special * @return Array * @access public */ public function getLinkedInfo($special = '', $guess_special = false) { $parent_prefix = $this->Application->getUnitOption($this->Prefix, 'ParentPrefix'); if ($parent_prefix) { // if this is linked table, then set id from main table $table_info = Array ( 'TableName' => $this->Application->getUnitOption($this->Prefix,'TableName'), 'IdField' => $this->Application->getUnitOption($this->Prefix,'IDField'), 'ForeignKey' => $this->Application->getUnitOption($this->Prefix,'ForeignKey'), 'ParentTableKey' => $this->Application->getUnitOption($this->Prefix,'ParentTableKey'), 'ParentPrefix' => $parent_prefix ); if (is_array($table_info['ForeignKey'])) { $table_info['ForeignKey'] = getArrayValue($table_info, 'ForeignKey', $parent_prefix); } if (is_array($table_info['ParentTableKey'])) { $table_info['ParentTableKey'] = getArrayValue($table_info, 'ParentTableKey', $parent_prefix); } /** @var kDBItem $main_object */ $main_object = $this->Application->recallObject($parent_prefix.'.'.$special, null, Array ('raise_warnings' => 0)); if (!$main_object->isLoaded() && $guess_special) { $main_object = $this->Application->recallObject($parent_prefix); } return array_merge($table_info, Array('ParentId'=> $main_object->GetDBField( $table_info['ParentTableKey'] ) ) ); } return false; } /** * Returns true, when list/item was queried/loaded * * @return bool * @access public */ abstract public function isLoaded(); /** * Returns specified field value from all selected rows. * Don't affect current record index * * @param string $field * @return Array * @access public */ abstract public function GetCol($field); } /** * Base class for exceptions, that trigger redirect action once thrown */ class kRedirectException extends Exception { /** * Redirect template * * @var string * @access protected */ protected $template = ''; /** * Redirect params * * @var Array * @access protected */ protected $params = Array (); /** * Creates redirect exception * * @param string $message * @param int $code * @param Exception $previous */ public function __construct($message = '', $code = 0, $previous = NULL) { parent::__construct($message, $code, $previous); } /** * Initializes exception * * @param string $template * @param Array $params * @return void * @access public */ public function setup($template, $params = Array ()) { $this->template = $template; $this->params = $params; } /** * Display exception details in debugger (only useful, when DBG_REDIRECT is enabled) and performs redirect * * @return void * @access public */ public function run() { $application =& kApplication::Instance(); if ( $application->isDebugMode() ) { $application->Debugger->appendException($this); } $application->Redirect($this->template, $this->params); } } /** * Exception, that is thrown when user don't have permission to perform requested action */ class kNoPermissionException extends kRedirectException { }