Index: branches/5.3.x/core/kernel/session/session.php =================================================================== --- branches/5.3.x/core/kernel/session/session.php (revision 16518) +++ branches/5.3.x/core/kernel/session/session.php (revision 16519) @@ -1,1132 +1,1132 @@ SetCookieDomain('my.domain.com'); $session->SetCookiePath('/myscript'); $session->SetCookieName('my_sid_cookie'); $session->SetGETName('sid'); $session->InitSession(); ... //link output: echo "NeedQueryString() ? 'sid='.$session->SID : '' ) .">My Link"; */ class BaseSession extends kBase { const smAUTO = 1; const smCOOKIES_ONLY = 2; const smGET_ONLY = 3; const smCOOKIES_AND_GET = 4; var $Checkers; var $Mode; var $OriginalMode = null; var $GETName = 'sid'; var $CookiesEnabled = true; var $CookieName = 'sid'; var $CookieDomain; var $CookiePath; var $CookieSecure = 0; var $SessionTimeout = 3600; var $Expiration; var $SID; var $CachedSID; var $SessionSet = false; /** * Session ID is used from GET * * @var bool */ var $_fromGet = false; /** * Enter description here... * * @var BaseSessionStorage * @access protected */ protected $Storage; var $CachedNeedQueryString = null; /** * Session Data array * * @var Params */ var $Data; /** * Names of optional session keys with their optional values (which does not need to be always stored) * * @var Array */ var $OptionalData = Array (); /** * Session expiration mark * * @var bool */ var $expired = false; /** * Creates session * * @param int $mode * @access public */ public function __construct($mode = self::smAUTO) { parent::__construct(); $this->SetMode($mode); } function SetMode($mode) { $this->Mode = $mode; $this->CachedNeedQueryString = null; $this->CachedSID = null; } function SetCookiePath($path) { $this->CookiePath = str_replace(' ', '%20', $path); } /** * Setting cookie domain. Set false for local domains, because they don't contain dots in their names. * * @param string $domain */ function SetCookieDomain($domain) { // 1. localhost or other like it without "." in domain name if (!substr_count($domain, '.')) { // don't use cookie domain at all $this->CookieDomain = false; return ; } // 2. match using predefined cookie domains from configuration $cookie_domains = $this->Application->ConfigValue('SessionCookieDomains'); if ($cookie_domains) { $cookie_domains = array_map('trim', explode("\n", $cookie_domains)); foreach ($cookie_domains as $cookie_domain) { if (ltrim($cookie_domain, '.') == $domain) { $this->CookieDomain = $cookie_domain; // as defined in configuration return ; } } } // 3. only will execute, when none of domains were matched at previous step $this->CookieDomain = $this->_autoGuessDomain($domain); } /** * Auto-guess cookie domain based on $_SERVER['HTTP_HOST'] * * @param $domain * @return string */ function _autoGuessDomain($domain) { static $cache = Array (); if (!array_key_exists($domain, $cache)) { switch ( substr_count($domain, '.') ) { case 2: // 3rd level domain (3 parts) $cache[$domain] = substr($domain, strpos($domain, '.')); // with leading "." break; case 1: // 2rd level domain (2 parts) $cache[$domain] = '.' . $domain; // with leading "." break; default: // more then 3rd level $cache[$domain] = ltrim($domain, '.'); // without leading "." break; } } return $cache[$domain]; } function SetGETName($get_name) { $this->GETName = $get_name; } function SetCookieName($cookie_name) { $this->CookieName = $cookie_name; } function InitStorage($special) { $this->Storage = $this->Application->recallObject('SessionStorage.'.$special); $this->Storage->setSession($this); } public function Init($prefix, $special) { parent::Init($prefix, $special); if ( php_sapi_name() == 'cli' ) { $this->SetMode(self::smGET_ONLY); } $this->CheckIfCookiesAreOn(); $this->Checkers = Array(); $this->InitStorage($special); $this->Data = new Params(); $tmp_sid = $this->GetPassedSIDValue(); $check = $this->Check(); if ($this->Application->isAdmin) { // 1. Front-End session may not be created (SID is present, but no data in database). // Check expiration LATER from kApplication::Init, because template, used in session // expiration redirect should be retrieved from mod-rewrite url first. // 2. Admin sessions are always created, so case when SID is present, // but session in database isn't is 100% session expired. Check expiration // HERE because Session::SetSession will create missing session in database // and when Session::ValidateExpired will be called later from kApplication::Init // it won't consider such session as expired !!! $this->ValidateExpired(); } if ($check) { $this->SID = $this->GetPassedSIDValue(); $this->Refresh(); $this->LoadData(); } else { $this->SetSession(); } if (!is_null($this->OriginalMode)) $this->SetMode($this->OriginalMode); } function ValidateExpired() { if (defined('IS_INSTALL') && IS_INSTALL) { return ; } // $this->DeleteExpired(); // called from u:OnDeleteExpiredSessions scheduled task now if ($this->expired || ($this->CachedSID && !$this->_fromGet && !$this->SessionSet)) { $this->RemoveSessionCookie(); // true was here to force new session creation, but I (kostja) used // RemoveCookie a line above, to avoid redirect loop with expired sid // not being removed setSession with true was used before, to set NEW // session cookie $this->SetSession(); // case #1: I've OR other site visitor expired my session // case #2: I have no session in database, but SID is present $this->expired = false; $this->Application->HandleEvent(new kEvent('u:OnSessionExpire')); } } /** * Helper method for detecting cookie availability * * @return bool */ function _checkCookieReferer() { // removing /admin for compatability with in-portal (in-link/admin/add_link.php) $path = preg_replace('/admin[\/]{0,1}$/', '', $this->CookiePath); $reg = '#^'.preg_quote(PROTOCOL.ltrim($this->CookieDomain, '.').$path).'#'; return preg_match($reg, getArrayValue($_SERVER, 'HTTP_REFERER') ); } function CheckIfCookiesAreOn() { if ( $this->Mode == self::smGET_ONLY ) { //we don't need to bother checking if we would not use it $this->CookiesEnabled = false; return false; } + /** @var kHTTPQuery $http_query */ $http_query = $this->Application->recallObject('kHTTPQuery'); - /* @var $http_query kHTTPQuery */ $cookies_on = array_key_exists('cookies_on', $http_query->Cookie); // not good here $get_sid = getArrayValue($http_query->Get, $this->GETName); if ( ($this->Application->HttpQuery->IsHTTPSRedirect() && $get_sid) || $this->getFlashSID() ) { // Redirect from http to https on different domain OR flash uploader $this->OriginalMode = $this->Mode; $this->SetMode(self::smGET_ONLY); } if ( !$cookies_on || $this->Application->HttpQuery->IsHTTPSRedirect() || $this->getFlashSID() ) { //If referer is our server, but we don't have our cookies_on, it's definetly off $is_install = defined('IS_INSTALL') && IS_INSTALL; if ( !$is_install && $this->_checkCookieReferer() && !$this->Application->GetVar('admin') && !$this->Application->HttpQuery->IsHTTPSRedirect() ) { $this->CookiesEnabled = false; } else { //Otherwise we still suppose cookies are on, because may be it's the first time user visits the site //So we send cookies on to get it next time (when referal will tell us if they are realy off $this->SetCookie('cookies_on', 1, time() + 31104000); //one year should be enough } } else { $this->CookiesEnabled = true; } return $this->CookiesEnabled; } /** * Sets cookie for current site using path and domain * * @param string $name * @param mixed $value * @param int $expires */ function SetCookie($name, $value, $expires = null) { if (isset($expires) && $expires < time()) { unset($this->Application->HttpQuery->Cookie[$name]); } else { $this->Application->HttpQuery->Cookie[$name] = $value; } $old_style_domains = Array ( // domain like in pre 5.1.0 versions '.' . SERVER_NAME, // auto-guessed domain (when user specified other domain in configuration variable) $this->_autoGuessDomain(SERVER_NAME) ); $cookie_hasher = $this->Application->makeClass('kCookieHasher'); /* @var $cookie_hasher kCookieHasher */ $encrypted_value = $cookie_hasher->encrypt($name, $value); foreach ($old_style_domains as $old_style_domain) { if ($this->CookieDomain != $old_style_domain) { // new style cookie domain -> delete old style cookie to prevent infinite redirect setcookie($name, $encrypted_value, time() - 3600, $this->CookiePath, $old_style_domain, $this->CookieSecure, true); } } setcookie($name, $encrypted_value, $expires, $this->CookiePath, $this->CookieDomain, $this->CookieSecure, true); } function Check() { // don't check referer here, because it doesn't provide any security option and can be easily falsified $sid = $this->GetPassedSIDValue(); if (empty($sid)) { return false; } //try to load session by sid, if everything is fine $result = $this->LoadSession($sid); $this->SessionSet = $result; // fake front-end session will given "false" here return $result; } function LoadSession($sid) { if( $this->Storage->LocateSession($sid) ) { // if we have session with such SID - get its expiration $this->Expiration = $this->Storage->GetExpiration(); // If session has expired if ($this->Expiration < time()) { // when expired session is loaded, then SID is // not assigned, but used in Destroy method $this->SID = $sid; $this->Destroy(); $this->expired = true; // when Destory methods calls SetSession inside and new session get created return $this->SessionSet; } // Otherwise it's ok return true; } else { // fake or deleted due to expiration SID if (!$this->_fromGet) { $this->expired = true; } return false; } } function getFlashSID() { + /** @var kHTTPQuery $http_query */ $http_query = $this->Application->recallObject('kHTTPQuery'); - /* @var $http_query kHTTPQuery */ return getArrayValue($http_query->Post, 'flashsid'); } function GetPassedSIDValue($use_cache = 1) { if (!empty($this->CachedSID) && $use_cache) { return $this->CachedSID; } // flash sid overrides regular sid $get_sid = $this->getFlashSID(); if (!$get_sid) { + /** @var kHTTPQuery $http_query */ $http_query = $this->Application->recallObject('kHTTPQuery'); - /* @var $http_query kHTTPQuery */ $get_sid = getArrayValue($http_query->Get, $this->GETName); } $sid_from_get = $get_sid ? true : false; if ($this->Application->GetVar('admin') == 1 && $get_sid) { $sid = $get_sid; } else { switch ($this->Mode) { case self::smAUTO: //Cookies has the priority - we ignore everything else $sid = $this->CookiesEnabled ? $this->GetSessionCookie() : $get_sid; if ($this->CookiesEnabled) { $sid_from_get = false; } break; case self::smCOOKIES_ONLY: $sid = $this->GetSessionCookie(); break; case self::smGET_ONLY: $sid = $get_sid; break; case self::smCOOKIES_AND_GET: $cookie_sid = $this->GetSessionCookie(); //both sids should match if cookies are enabled if (!$this->CookiesEnabled || ($cookie_sid == $get_sid)) { $sid = $get_sid; //we use get here just in case cookies are disabled } else { $sid = ''; $sid_from_get = false; } break; } } $this->CachedSID = $sid; $this->_fromGet = $sid_from_get; return $this->CachedSID; } /** * Returns session id * * @return int * @access public */ function GetID() { return $this->SID; } /** * Generates new session id * * @return int * @access private */ function GenerateSID() { $this->setSID(kUtil::generateId()); return $this->SID; } /** * Set's new session id * * @param int $new_sid * @access private */ function setSID($new_sid) { $this->SID /*= $this->CachedSID*/ = $new_sid; // don't set cached sid here $this->Application->SetVar($this->GETName,$new_sid); } function NeedSession() { $data = $this->Data->GetParams(); $data_keys = array_keys($data); $optional_keys = array_keys($this->OptionalData); $real_keys = array_diff($data_keys, $optional_keys); return $real_keys ? true : false; } function SetSession($force = false) { if ( $this->SessionSet && !$force ) { return true; } $this->Expiration = time() + $this->SessionTimeout; if ( !$force && /*!$this->Application->isAdmin &&*/ !$this->Application->GetVar('admin') && !$this->NeedSession() ) { // don't create session (in db) on Front-End, when sid is present (GPC), but data in db isn't if ( $this->_fromGet ) { // set sid, that was given in GET $this->setSID($this->GetPassedSIDValue()); } else { // re-generate sid only, when cookies are used $this->GenerateSID(); } $this->Storage->StoreSession(false); return false; } if ( !$this->SID || $force ) { $this->GenerateSID(); } switch ( $this->Mode ) { case self::smAUTO: if ( $this->CookiesEnabled ) { $this->SetSessionCookie(); } break; case self::smGET_ONLY: break; case self::smCOOKIES_ONLY: case self::smCOOKIES_AND_GET: $this->SetSessionCookie(); break; } $this->Storage->StoreSession(); if ( $this->Application->isAdmin || $this->Special == 'admin' ) { $this->StoreVar('admin', 1); } $this->SessionSet = true; // should be called before SaveData, because SaveData will try to SetSession again if ( $this->Special != '' ) { // front-session called from admin or otherwise, then save it's data $this->SaveData(); } $this->Application->resetCounters('UserSessions'); return true; } /** * Returns SID from cookie. * * Use 2 cookies to have 2 expiration: * - 1. for normal expiration when browser is not closed (30 minutes by default), configurable * - 2. for advanced expiration when browser is closed * * @return int */ function GetSessionCookie() { $keep_session_on_browser_close = $this->Application->ConfigValue('KeepSessionOnBrowserClose'); if (isset($this->Application->HttpQuery->Cookie[$this->CookieName]) && ( $keep_session_on_browser_close || ( !$keep_session_on_browser_close && isset($this->Application->HttpQuery->Cookie[$this->CookieName.'_live']) && $this->Application->HttpQuery->Cookie[$this->CookieName] == $this->Application->HttpQuery->Cookie[$this->CookieName.'_live'] ) ) ) { return $this->Application->HttpQuery->Cookie[$this->CookieName]; } return false; } /** * Updates SID in cookie with new value * */ function SetSessionCookie() { $this->SetCookie($this->CookieName, $this->SID, $this->Expiration); $this->SetCookie($this->CookieName.'_live', $this->SID); } function RemoveSessionCookie() { $this->SetCookie($this->CookieName, ''); $this->SetCookie($this->CookieName.'_live', ''); } /** * Refreshes session expiration time * * @access private */ function Refresh() { if ($this->Application->GetVar('skip_session_refresh')) { return ; } if ($this->CookiesEnabled) { // we need to refresh the cookie $this->SetSessionCookie(); } $this->Storage->UpdateSession(); } function Destroy() { $this->Storage->DeleteSession(); $this->Data = new Params(); $this->SID = $this->CachedSID = ''; $this->SessionSet = false; if ($this->CookiesEnabled) { $this->SetSessionCookie(); //will remove the cookie due to value (sid) is empty } $this->SetSession(true); //will create a new session, true to force } function NeedQueryString($use_cache = 1) { if ($this->CachedNeedQueryString != null && $use_cache) { return $this->CachedNeedQueryString; } $result = false; switch ($this->Mode) { case self::smAUTO: if (!$this->CookiesEnabled) { $result = true; } break; /*case self::smCOOKIES_ONLY: break;*/ case self::smGET_ONLY: case self::smCOOKIES_AND_GET: $result = true; break; } $this->CachedNeedQueryString = $result; return $result; } function LoadData() { $this->Data->AddParams( $this->Storage->LoadData() ); } /** * Returns information about session contents * * @param bool $include_optional * @return array * @access public */ public function getSessionData($include_optional = true) { $session_data = $this->Data->GetParams(); ksort($session_data); foreach ($session_data as $session_key => $session_value) { if ( kUtil::IsSerialized($session_value) ) { $session_data[$session_key] = unserialize($session_value); } } if ( !$include_optional ) { $optional_keys = array_keys($this->OptionalData); foreach ($session_data as $session_key => $session_value) { if ( in_array($session_key, $optional_keys) ) { unset($session_data[$session_key]); } } } return $session_data; } /** * Returns real session data, that was saved * * @param Array $session_data * @return Array * @access protected */ protected function _getRealSessionData($session_data) { $data_keys = array_keys($session_data); $optional_keys = array_keys($this->OptionalData); $real_keys = array_diff($data_keys, $optional_keys); if ( !$real_keys ) { return Array (); } $ret = Array (); foreach ($real_keys as $real_key) { $ret[$real_key] = $session_data[$real_key]; } return $ret; } function PrintSession($comment = '') { if ( defined('DEBUG_MODE') && $this->Application->isDebugMode() && kUtil::constOn('DBG_SHOW_SESSIONDATA') ) { // dump session data $this->Application->Debugger->appendHTML('SessionStorage [' . ($this->RecallVar('admin') == 1 ? 'Admin' : 'Front-End') . '] (' . $comment . '):'); $session_data = $this->getSessionData(); $this->Application->Debugger->dumpVars($session_data); if ( !$this->RecallVar('admin') ) { // dump real keys (only for front-end) $real_session_data = $this->_getRealSessionData($session_data); if ( $real_session_data ) { $this->Application->Debugger->appendHTML('Real Keys:'); $this->Application->Debugger->dumpVars($real_session_data); } } } if ( defined('DEBUG_MODE') && $this->Application->isDebugMode() && kUtil::constOn('DBG_SHOW_PERSISTENTDATA') ) { // dump persistent session data if ( $this->Storage->PersistentVars ) { $this->Application->Debugger->appendHTML('Persistant Session:'); $session_data = $this->Storage->PersistentVars; ksort($session_data); foreach ($session_data as $session_key => $session_value) { if ( kUtil::IsSerialized($session_value) ) { $session_data[$session_key] = unserialize($session_value); } } $this->Application->Debugger->dumpVars($session_data); } } } function SaveData($params = Array ()) { if (!$this->SetSession()) { // call it here - it may be not set before, because there was no need; if there is a need, it will be set here return; } if (!$this->Application->GetVar('skip_last_template') && $this->Application->GetVar('ajax') != 'yes') { $this->SaveLastTemplate( $this->Application->GetVar('t'), $params ); } $this->PrintSession('after save'); $this->Storage->SaveData(); } /** * Save last template * * @param string $t * @param Array $params */ function SaveLastTemplate($t, $params = Array ()) { $wid = $this->Application->GetVar('m_wid'); $last_env = $this->getLastTemplateENV($t, array('m_opener' => 'u')); $last_template = basename($_SERVER['PHP_SELF']) . '|' . $last_env; $this->StoreVar(rtrim('last_template_' . $wid, '_'), $last_template); // prepare last_template for opener stack, module & session could be added later $last_env = $this->getLastTemplateENV($t); $last_template = basename($_SERVER['PHP_SELF']) . '|' . $last_env; // save last_template in persistent session if (!$wid) { if ($this->Application->isAdmin) { // only for main window, not popups, not login template, not temp mode (used in adm:MainFrameLink tag) $temp_mode = false; $passed = explode(',', $this->Application->GetVar('passed')); foreach ($passed as $passed_prefix) { if ($this->Application->GetVar($passed_prefix.'_mode')) { $temp_mode = true; break; } } if (!$temp_mode) { if ( $this->Application->GetVarDirect('section', 'Get') !== false ) { // check directly in GET, because LinkVar (session -> request) used on these vars $last_template .= '§ion='.$this->Application->GetVar('section').'&module='.$this->Application->GetVar('module'); } $this->StorePersistentVar('last_template_popup', $last_template); } } elseif ($this->Application->GetVar('admin')) { // admin checking by session data to prevent recursive session save static $admin_saved = null; if (!$this->RecallVar('admin') && !isset($admin_saved)) { // bug: we get recursion in this place, when cookies are disabled in browser and we are browsing // front-end in admin's frame (front-end session is initialized using admin's sid and they are // mixed together) $admin_saved = true; + /** @var Session $admin_session */ $admin_session = $this->Application->recallObject('Session.admin'); - /* @var $admin_session Session */ // save to admin last_template too, because when F5 is pressed in frameset Front-End frame should reload as well $admin_session->StoreVar('last_template_popup', '../' . $last_template); $admin_session->StorePersistentVar('last_template_popup', '../' . $last_template); $admin_session->SaveData( Array ('save_last_template' => false) ); } else { // don't allow admin=1 & editing_mode=* to get in admin last_template $last_template = preg_replace('/&(admin|editing_mode)=[\d]/', '', $last_template); } } } // save other last... variables for mystical purposes (customizations may be) $this->StoreVar('last_url', $_SERVER['REQUEST_URI']); // needed by ord:StoreContinueShoppingLink $this->StoreVar('last_env', $last_env); $save_last_template = array_key_exists('save_last_template', $params) ? $params['save_last_template'] : true; if ($save_last_template) { // save last template here, because section & module could be added before $this->StoreVar(rtrim('last_template_popup_'.$wid, '_'), $last_template); } } protected function getLastTemplateENV($t, $params = null) { if (!isset($params)) { $params = Array (); } if ($this->Application->GetVar('admin') && !array_key_exists('admin', $params) && !defined('EDITING_MODE')) { $params['editing_mode'] = ''; // used in kApplication::Run } $params = array_merge($this->Application->getPassThroughVariables($params), $params); return $this->Application->BuildEnv($t, $params, 'all', false, false); } /** * Stores variable $val in session under name $var * * Use this method to store variable in session. Later this variable could be recalled. * * @param string $name Variable name * @param mixed $value Variable value * @param bool $optional * @return void * @access public * @see Session::RecallVar() */ public function StoreVar($name, $value, $optional = false) { $this->Data->Set($name, $value); if ( $optional ) { // make variable optional, also remember optional value $this->OptionalData[$name] = $value; } elseif ( !$optional && array_key_exists($name, $this->OptionalData) ) { if ( $this->OptionalData[$name] == $value ) { // same value as optional -> don't remove optional mark return; } // make variable non-optional unset($this->OptionalData[$name]); } } /** * Stores variable to persistent session * * @param string $name * @param mixed $value * @param bool $optional * @return void * @access public */ public function StorePersistentVar($name, $value, $optional = false) { $this->Storage->StorePersistentVar($name, $value, $optional); } function LoadPersistentVars() { $this->Storage->LoadPersistentVars(); } /** * Stores default value for session variable * * @param string $name * @param string $value * @param bool $optional * @return void * @access public * @see Session::RecallVar() * @see Session::StoreVar() */ public function StoreVarDefault($name, $value, $optional = false) { $tmp = $this->RecallVar($name); if ( $tmp === false || $tmp == '' ) { $this->StoreVar($name, $value, $optional); } } /** * Returns session variable value * * Return value of $var variable stored in Session. An optional default value could be passed as second parameter. * * @param string $name Variable name * @param mixed $default Default value to return if no $var variable found in session * @return mixed * @access public */ public function RecallVar($name, $default = false) { $ret = $this->Data->Get($name); return ($ret === false) ? $default : $ret; } /** * Returns variable value from persistent session * * @param string $name * @param mixed $default * @return mixed * @access public */ public function RecallPersistentVar($name, $default = false) { return $this->Storage->RecallPersistentVar($name, $default); } /** * Deletes Session variable * * @param string $var * @return void * @access public */ public function RemoveVar($name) { $this->Storage->RemoveFromData($name); $this->Data->Remove($name); } /** * Removes variable from persistent session * * @param string $name * @return void * @access public */ public function RemovePersistentVar($name) { $this->Storage->RemovePersistentVar($name); } /** * Ignores session variable value set before * * @param string $name * @return void * @access public */ public function RestoreVar($name) { $value = $this->Storage->GetFromData($name, '__missing__'); if ( $value === '__missing__' ) { // there is nothing to restore (maybe session was not saved), look in optional variable values $value = array_key_exists($name, $this->OptionalData) ? $this->OptionalData[$name] : false; } $this->StoreVar($name, $value); } function GetField($var_name, $default = false) { return $this->Storage->GetField($var_name, $default); } function SetField($var_name, $value) { $this->Storage->SetField($var_name, $value); } /** * Deletes expired sessions * * @return Array expired sids if any * @access private */ function DeleteExpired() { return $this->Storage->DeleteExpired(); } /** * Deletes given sessions * * @param $session_ids * @param int $delete_reason * @return void */ function DeleteSessions($session_ids, $delete_reason = SESSION_LOG_EXPIRED) { $this->Storage->DeleteSessions($session_ids, $delete_reason); } /** * Allows to check if user in this session is logged in or not * * @return bool */ function LoggedIn() { $user_id = $this->RecallVar('user_id'); $ret = $user_id > 0; if (($this->RecallVar('admin') == 1 || defined('ADMIN')) && ($user_id == USER_ROOT)) { $ret = true; } return $ret; } } Index: branches/5.3.x/core/kernel/db/cat_dbitem.php =================================================================== --- branches/5.3.x/core/kernel/db/cat_dbitem.php (revision 16518) +++ branches/5.3.x/core/kernel/db/cat_dbitem.php (revision 16519) @@ -1,632 +1,632 @@ CategoryPath = Array(); } /** * Set's prefix and special * * @param string $prefix * @param string $special * @access public */ function Init($prefix, $special) { parent::Init($prefix, $special); $this->usePendingEditing = $this->getUnitConfig()->getUsePendingEditing(); } /** * Assigns primary category for the item * * @access public */ public function assignPrimaryCategory() { if ( $this->GetDBField('CategoryId') <= 0 ) { // set primary category in item object $this->SetDBField('CategoryId', $this->Application->GetVar('m_cat_id')); } $this->assignToCategory($this->GetDBField('CategoryId'), true); } /** * Updates previously loaded record with current item' values * * @access public * @param int $id Primary Key Id to update * @param Array $update_fields * @param bool $system_update * @return bool * @access public */ public function Update($id = null, $update_fields = null, $system_update = false) { if ( $this->useFilenames ) { $this->checkFilename(); $this->generateFilename(); } $ret = parent::Update($id, $update_fields, $system_update); if ( $ret ) { $filename = $this->useFilenames ? (string)$this->GetDBField('Filename') : ''; $sql = 'UPDATE ' . $this->CategoryItemsTable() . ' SET Filename = ' . $this->Conn->qstr($filename) . ' WHERE ItemResourceId = ' . $this->GetDBField('ResourceId'); $this->Conn->Query($sql); } return $ret; } /** * Returns CategoryItems table based on current item mode (temp/live) * * @return string */ function CategoryItemsTable() { $table = TABLE_PREFIX.'CategoryItems'; if ($this->Application->IsTempTable($this->TableName)) { $table = $this->Application->GetTempName($table, 'prefix:'.$this->Prefix); } return $table; } function checkFilename() { if( !$this->GetDBField('AutomaticFilename') ) { $filename = $this->GetDBField('Filename'); $this->SetDBField('Filename', $this->stripDisallowed($filename) ); } } function Copy($cat_id=null) { if (!isset($cat_id)) $cat_id = $this->Application->GetVar('m_cat_id'); $this->NameCopy($cat_id); return $this->Create($cat_id); } /** * Sets new name for item in case if it is being copied in same table * * @param array $master Table data from TempHandler * @param int $foreign_key ForeignKey value to filter name check query by * @param string $title_field FieldName to alter, by default - TitleField of the prefix * @param string $format sprintf-style format of renaming pattern, by default Copy %1$s of %2$s which makes it Copy [Number] of Original Name * @access public */ public function NameCopy($master=null, $foreign_key=null, $title_field=null, $format='Copy %1$s of %2$s') { $title_field = $this->getUnitConfig()->getTitleField(); if (!$title_field) return; $new_name = $this->GetDBField($title_field); $cat_id = (int)$this->Application->GetVar('m_cat_id'); $original_checked = false; do { if ( preg_match('/Copy ([0-9]*) *of (.*)/', $new_name, $regs) ) { $new_name = 'Copy '.( (int)$regs[1] + 1 ).' of '.$regs[2]; } elseif ($original_checked) { $new_name = 'Copy of '.$new_name; } $query = 'SELECT '.$title_field.' FROM '.$this->TableName.' LEFT JOIN '.TABLE_PREFIX.'CategoryItems ON ('.TABLE_PREFIX.'CategoryItems.ItemResourceId = '.$this->TableName.'.ResourceId) WHERE ('.TABLE_PREFIX.'CategoryItems.CategoryId = '.$cat_id.') AND '. $title_field.' = '.$this->Conn->qstr($new_name); $res = $this->Conn->GetOne($query); $original_checked = true; } while ($res !== false); $this->SetDBField($title_field, $new_name); // this is needed, because Create will create items in its own CategoryId (if it's set), // but we need to create it in target Paste category @see{kCatDBItem::Create} and its primary_category detection $this->SetDBField('CategoryId', $cat_id); } /** * Changes item primary category to given/current category * * @param int $category_id */ function MoveToCat($category_id = null) { // $this->NameCopy(); if (!isset($category_id)) { $category_id = $this->Application->GetVar('m_cat_id'); } $table_name = TABLE_PREFIX . 'CategoryItems'; if ($this->IsTempTable()) { $table_name = $this->Application->GetTempName($table_name, 'prefix:' . $this->Prefix); } // check if the item already exists in destination category $sql = 'SELECT PrimaryCat FROM ' . $table_name . ' WHERE (CategoryId = ' . (int)$category_id . ') AND (ItemResourceId = ' . $this->GetDBField('ResourceId') . ')'; $is_primary = $this->Conn->GetOne($sql); // if it's not found is_primary will be FALSE, if it's found but not primary it will be int 0 $exists = $is_primary !== false; if ($exists) { // if the item already exists in destination category if ($is_primary) { // do nothing when we paste to primary return ; } // if it's not primary - delete it from destination category, as we will move it from current primary below $sql = 'DELETE FROM ' . $table_name . ' WHERE (CategoryId = ' . (int)$category_id . ') AND (ItemResourceId = ' . $this->GetDBField('ResourceId') . ')'; $this->Conn->Query($sql); } // change category id in existing primary category record $sql = 'UPDATE ' . $table_name . ' SET CategoryId = ' . (int)$category_id . ' WHERE (ItemResourceId = ' . $this->GetDBField('ResourceId') . ') AND (PrimaryCat = 1)'; $this->Conn->Query($sql); $this->Update(); } /** * When item is deleted, then also delete it from all categories * * @param int $id * @return bool * @access public */ public function Delete($id = null) { if ( isset($id) ) { $this->setID($id); } $this->Load($this->GetID()); $ret = parent::Delete(); if ( $ret ) { // TODO: move to OnAfterItemDelete method $query = ' DELETE FROM ' . $this->CategoryItemsTable() . ' WHERE ItemResourceId = ' . $this->GetDBField('ResourceId'); $this->Conn->Query($query); } return $ret; } /** * Deletes item from categories * * @param Array $delete_category_ids * @author Alex */ function DeleteFromCategories($delete_category_ids) { $id_field = $this->getUnitConfig()->getIDField(); // because item was loaded before by ResourceId $ci_table = $this->Application->getUnitConfig($this->Prefix . '-ci')->getTableName(); $resource_id = $this->GetDBField('ResourceId'); $item_cats_sql = ' SELECT CategoryId FROM %s WHERE ItemResourceId = %s'; $delete_category_items_sql = ' DELETE FROM %s WHERE ItemResourceId = %s AND CategoryId IN (%s)'; $category_ids = $this->Conn->GetCol( sprintf($item_cats_sql, $ci_table, $resource_id) ); $cats_left = array_diff($category_ids, $delete_category_ids); if ( !$cats_left ) { $sql = 'SELECT %s FROM %s WHERE ResourceId = %s'; $ids = $this->Conn->GetCol(sprintf($sql, $id_field, $this->TableName, $resource_id)); + /** @var kTempTablesHandler $temp_handler */ $temp_handler = $this->Application->recallObject($this->getPrefixSpecial() . '_TempHandler', 'kTempTablesHandler'); - /* @var $temp_handler kTempTablesHandler */ $temp_handler->DeleteItems($this->Prefix, $this->Special, $ids); } else { $this->Conn->Query( sprintf($delete_category_items_sql, $ci_table, $resource_id, implode(',', $delete_category_ids)) ); $sql = 'SELECT CategoryId FROM %s WHERE PrimaryCat = 1 AND ItemResourceId = %s'; $primary_cat_id = $this->Conn->GetCol(sprintf($sql, $ci_table, $resource_id)); if ( count($primary_cat_id) == 0 ) { $sql = 'UPDATE %s SET PrimaryCat = 1 WHERE (CategoryId = %s) AND (ItemResourceId = %s)'; $this->Conn->Query( sprintf($sql, $ci_table, reset($cats_left), $resource_id) ); } } } /** * replace not allowed symbols with "_" chars + remove duplicate "_" chars in result * * @param string $filename * @return string */ function stripDisallowed($filename) { + /** @var kFilenamesHelper $filenames_helper */ $filenames_helper = $this->Application->recallObject('FilenamesHelper'); - /* @var $filenames_helper kFilenamesHelper */ $table = $this->IsTempTable() ? $this->Application->GetTempName(TABLE_PREFIX.'CategoryItems', 'prefix:'.$this->Prefix) : TABLE_PREFIX.'CategoryItems'; return $filenames_helper->stripDisallowed($table, 'ItemResourceId', $this->GetDBField('ResourceId'), $filename); } /* commented out because it's called only from stripDisallowed body, which is moved to helper function checkAutoFilename($filename) { $filenames_helper = $this->Application->recallObject('FilenamesHelper'); return $filenames_helper->checkAutoFilename($this->TableName, $this->IDField, $this->GetID(), $filename); }*/ /** * Generate item's filename based on it's title field value * * @return void * @access protected */ protected function generateFilename() { if ( !$this->GetDBField('AutomaticFilename') && $this->GetDBField('Filename') ) { return ; } $title_field = $this->getUnitConfig()->getTitleField(); if ( preg_match('/l([\d]+)_(.*)/', $title_field, $regs) ) { // if title field is multilingual, then use it's name from primary language $title_field = 'l' . $this->Application->GetDefaultLanguageId() . '_' . $regs[2]; } $name = $this->stripDisallowed( $this->GetDBField($title_field) ); if ( $name != $this->GetDBField('Filename') ) { $this->SetDBField('Filename', $name); } } /** * Adds item to other category * * @param int $category_id * @param bool $is_primary * @return void * @access public */ public function assignToCategory($category_id, $is_primary = false) { $table = $this->CategoryItemsTable(); $key_clause = '(ItemResourceId = ' . $this->GetDBField('ResourceId') . ')'; // get all categories, where item is in $sql = 'SELECT PrimaryCat, CategoryId FROM ' . $table . ' WHERE ' . $key_clause; $item_categories = $this->Conn->GetCol($sql, 'CategoryId'); $primary_found = $item_category_id = false; if ( $item_categories ) { // find primary category foreach ($item_categories as $item_category_id => $primary_found) { if ( $primary_found ) { break; } } } if ( $primary_found && ($item_category_id == $category_id) && !$is_primary ) { // want to make primary category as non-primary :( return; } elseif ( !$primary_found ) { $is_primary = true; } if ( $is_primary && $item_categories ) { // reset primary mark from all other categories $sql = 'UPDATE ' . $table . ' SET PrimaryCat = 0 WHERE ' . $key_clause; $this->Conn->Query($sql); } // UPDATE & INSERT instead of REPLACE because CategoryItems table has no primary key defined in database if ( isset($item_categories[$category_id]) ) { $sql = 'UPDATE ' . $table . ' SET PrimaryCat = ' . ($is_primary ? 1 : 0) . ' WHERE ' . $key_clause . ' AND (CategoryId = ' . $category_id . ')'; $this->Conn->Query($sql); } else { $fields_hash = Array ( 'CategoryId' => $category_id, 'ItemResourceId' => $this->GetField('ResourceId'), 'PrimaryCat' => $is_primary ? 1 : 0, 'ItemPrefix' => $this->Prefix, 'Filename' => $this->useFilenames ? (string)$this->GetDBField('Filename') : '', // because some prefixes does not use filenames, ); if ( $this->Application->IsTempTable($table) ) { $new_id = (int)$this->Conn->GetOne('SELECT MIN(Id) FROM ' . $table .' WHERE Id < 0' ); $fields_hash['Id'] = $new_id - 1; } $this->Conn->doInsert($fields_hash, $table); } // to ensure filename update after adding to another category // this is critical since there may be an item with same filename in newly added category! $this->Update(); } /** * Removes item from category specified * * @param int $category_id */ function removeFromCategory($category_id) { $sql = 'DELETE FROM '.TABLE_PREFIX.'CategoryItems WHERE (CategoryId = %s) AND (ItemResourceId = %s)'; $this->Conn->Query( sprintf($sql, $category_id, $this->GetDBField('ResourceId')) ); } /** * Returns list of columns, that could exist in imported file * * @return Array */ function getPossibleExportColumns() { static $columns = null; if (!is_array($columns)) { $columns = array_merge($this->Fields['AvailableColumns']['options'], $this->Fields['ExportColumns']['options']); } return $columns; } /** * Returns item's primary image data * * @return Array */ function getPrimaryImageData() { $sql = 'SELECT * FROM '.TABLE_PREFIX.'CatalogImages WHERE (ResourceId = '.$this->GetDBField('ResourceId').') AND (DefaultImg = 1)'; $image_data = $this->Conn->GetRow($sql); if (!$image_data) { // 2. no primary image, then get image with name "main" $sql = 'SELECT * FROM '.TABLE_PREFIX.'CatalogImages WHERE (ResourceId = '.$this->GetDBField('ResourceId').') AND (Name = "main")'; $image_data = $this->Conn->GetRow($sql); } return $image_data; } function ChangeStatus($new_status, $pending_editing = false) { $status_field = $this->getUnitConfig()->getStatusField(true); if ($new_status != $this->GetDBField($status_field)) { // status was changed $this->sendEmails($new_status, $pending_editing); } $this->SetDBField($status_field, $new_status); return $this->Update(); } function sendEmails($new_status, $pending_editing = false) { $config = $this->getUnitConfig(); $owner_field = $config->getOwnerField('CreatedById'); $event_name = $config->getPermItemPrefix(); if ($pending_editing) { $event_name .= '.MODIFY'; } $event_name .= $new_status == STATUS_ACTIVE ? '.APPROVE' : '.DENY'; $this->Application->emailUser($event_name, $this->GetDBField($owner_field), $this->getEmailParams()); } /** * Approves changes made to category item * * @return bool */ function ApproveChanges() { $original_id = $this->GetDBField('OrgId'); if ( !($this->usePendingEditing && $original_id) ) { // non-pending copy of original link return $this->ChangeStatus(STATUS_ACTIVE); } if ( $this->raiseEvent('OnBeforeDeleteOriginal', null, Array ('original_id' => $original_id)) ) { // delete original item, because changes made in pending copy (this item) got to be approved in this method + /** @var kTempTablesHandler $temp_handler */ $temp_handler = $this->Application->recallObject($this->getPrefixSpecial() . '_TempHandler', 'kTempTablesHandler'); - /* @var $temp_handler kTempTablesHandler */ $temp_handler->DeleteItems($this->Prefix, $this->Special, Array ($original_id)); $this->raiseEvent('OnAfterDeleteOriginal', null, Array ('original_id' => $original_id)); $this->SetDBField('OrgId', 0); return $this->ChangeStatus(STATUS_ACTIVE, true); } return false; } /** * Decline changes made to category item * * @return bool */ function DeclineChanges() { $original_id = $this->GetDBField('OrgId'); if ( !($this->usePendingEditing && $original_id) ) { // non-pending copy of original link return $this->ChangeStatus(STATUS_DISABLED); } // delete this item, because changes made in pending copy (this item) will be declined in this method + /** @var kTempTablesHandler $temp_handler */ $temp_handler = $this->Application->recallObject($this->getPrefixSpecial() . '_TempHandler', 'kTempTablesHandler'); - /* @var $temp_handler kTempTablesHandler */ $temp_handler->DeleteItems($this->Prefix, $this->Special, Array ($this->GetID())); $this->sendEmails(STATUS_DISABLED, true); // original item is not changed here, because it is already enabled (thrus pending copy is visible to item's owner or admin with permission) return true; } function RegisterHit() { $already_viewed = $this->Application->RecallVar($this->getPrefixSpecial().'_already_viewed'); $already_viewed = $already_viewed ? unserialize($already_viewed) : Array (); $id = $this->GetID(); if (!in_array($id, $already_viewed)) { $property_map = $this->getUnitConfig()->getItemPropertyMappings(Array ()); if (!$property_map) { return ; } $hits_field = $property_map['ClickField']; $new_hits = $this->GetDBField($hits_field) + 1; $sql = 'SELECT MAX('.$hits_field.') FROM '.$this->TableName.' WHERE FLOOR('.$hits_field.') = '.$new_hits; $max_hits = $this->Conn->GetOne($sql); if ($max_hits) { $new_hits = $max_hits + 0.000001; } $fields_hash = Array ($hits_field => $new_hits,); $this->Conn->doUpdate($fields_hash, $this->TableName, $this->IDField.' = '.$id); array_push($already_viewed, $id); $this->Application->StoreVar($this->getPrefixSpecial().'_already_viewed', serialize($already_viewed)); } } /** * Returns part of SQL WHERE clause identifying the record, ex. id = 25 * * @param string $method Child class may want to know who called GetKeyClause, Load(), Update(), Delete() send its names as method * @param Array $keys_hash alternative, then item id, keys hash to load item by * @see kDBItem::Load() * @see kDBItem::Update() * @see kDBItem::Delete() * @return string * @access protected */ protected function GetKeyClause($method = null, $keys_hash = null) { if ( $method == 'load' && !isset($keys_hash) ) { // for item with many categories makes primary to load $ci_table = TABLE_PREFIX . 'CategoryItems'; if ($this->IsTempTable()) { $ci_table = $this->Application->GetTempName($ci_table, 'prefix:' . $this->Prefix); } if ( $this->Application->isAdmin ) { // When coping new item from temp table, where no CategoryItems record present yet. return parent::GetKeyClause($method, $keys_hash) . ' ORDER BY `' . $ci_table . '`.PrimaryCat DESC'; } else { // Ensures, that CategoryId calculated field has primary category id in it. $keys_hash = Array ( $this->IDField => $this->ID, '`' . $ci_table . '`.`PrimaryCat`' => 1 ); } } return parent::GetKeyClause($method, $keys_hash); } } Index: branches/5.3.x/core/kernel/db/db_load_balancer.php =================================================================== --- branches/5.3.x/core/kernel/db/db_load_balancer.php (revision 16518) +++ branches/5.3.x/core/kernel/db/db_load_balancer.php (revision 16519) @@ -1,884 +1,884 @@ dbType = $db_type; $this->setErrorHandler($error_handler); $this->DBClusterTimeout *= 1e6; // convert to milliseconds } /** * Sets new error handler. * * @param mixed $error_handler Error handler. * * @return self */ public function setErrorHandler($error_handler) { $this->errorHandler = $error_handler; return $this; } /** * Setups load balancer according given configuration. * * @param Array $config * @return void * @access public */ public function setup($config) { $this->servers = Array (); $this->servers[0] = Array ( 'DBHost' => $config['Database']['DBHost'], 'DBUser' => $config['Database']['DBUser'], 'DBUserPassword' => $config['Database']['DBUserPassword'], 'DBName' => $config['Database']['DBName'], 'DBLoad' => 0, ); if ( isset($config['Databases']) ) { $this->servers = array_merge($this->servers, $config['Databases']); } foreach ($this->servers as $server_index => $server_setting) { $this->serverLoads[$server_index] = $server_setting['DBLoad']; } } /** * Returns connection index to master database * * @return int * @access protected */ protected function getMasterIndex() { return 0; } /** * Returns connection index to slave database. This takes into account load ratios and lag times. * Side effect: opens connections to databases * * @return int * @access protected */ protected function getSlaveIndex() { if ( count($this->servers) == 1 || $this->Application->isAdmin ) { // skip the load balancing if there's only one server OR in admin console return 0; } elseif ( $this->slaveIndex !== false ) { // shortcut if generic reader exists already return $this->slaveIndex; } $total_elapsed = 0; $non_error_loads = $this->serverLoads; $i = $found = $lagged_slave_mode = false; // first try quickly looking through the available servers for a server that meets our criteria do { $current_loads = $non_error_loads; $overloaded_servers = $total_threads_connected = 0; while ($current_loads) { if ( $lagged_slave_mode ) { // when all slave servers are too lagged, then ignore lag and pick random server $i = $this->pickRandom($current_loads); } else { $i = $this->getRandomNonLagged($current_loads); if ( $i === false && $current_loads ) { // all slaves lagged -> pick random lagged slave then $lagged_slave_mode = true; $i = $this->pickRandom( $current_loads ); } } if ( $i === false ) { // all slaves are down -> use master as a slave $this->slaveIndex = $this->getMasterIndex(); return $this->slaveIndex; } $conn =& $this->openConnection($i); if ( !$conn ) { unset($non_error_loads[$i], $current_loads[$i]); continue; } // Perform post-connection backoff $threshold = isset($this->servers[$i]['DBMaxThreads']) ? $this->servers[$i]['DBMaxThreads'] : false; $backoff = $this->postConnectionBackoff($conn, $threshold); if ( $backoff ) { // post-connection overload, don't use this server for now $total_threads_connected += $backoff; $overloaded_servers++; unset( $current_loads[$i] ); } else { // return this server break 2; } } // no server found yet $i = false; // if all servers were down, quit now if ( !$non_error_loads ) { break; } // back off for a while // scale the sleep time by the number of connected threads, to produce a roughly constant global poll rate $avg_threads = $total_threads_connected / $overloaded_servers; usleep($this->DBAvgStatusPoll * $avg_threads); $total_elapsed += $this->DBAvgStatusPoll * $avg_threads; } while ( $total_elapsed < $this->DBClusterTimeout ); if ( $i !== false ) { // slave connection successful if ( $this->slaveIndex <= 0 && $this->serverLoads[$i] > 0 && $i !== false ) { $this->slaveIndex = $i; } } return $i; } /** * Returns random non-lagged server * * @param Array $loads * @return int * @access protected */ protected function getRandomNonLagged($loads) { // unset excessively lagged servers $lags = $this->getLagTimes(); foreach ($lags as $i => $lag) { if ( $i != 0 && isset($this->servers[$i]['DBMaxLag']) ) { if ( $lag === false ) { unset( $loads[$i] ); // server is not replicating } elseif ( $lag > $this->servers[$i]['DBMaxLag'] ) { unset( $loads[$i] ); // server is excessively lagged } } } // find out if all the slaves with non-zero load are lagged if ( !$loads || array_sum($loads) == 0 ) { return false; } // return a random representative of the remainder return $this->pickRandom($loads); } /** * Select an element from an array of non-normalised probabilities * * @param Array $weights * @return int * @access protected */ protected function pickRandom($weights) { if ( !is_array($weights) || !$weights ) { return false; } $sum = array_sum($weights); if ( $sum == 0 ) { return false; } $max = mt_getrandmax(); $rand = mt_rand(0, $max) / $max * $sum; $index = $sum = 0; foreach ($weights as $index => $weight) { $sum += $weight; if ( $sum >= $rand ) { break; } } return $index; } /** * Get lag time for each server * Results are cached for a short time in memcached, and indefinitely in the process cache * * @return Array * @access protected */ protected function getLagTimes() { if ( $this->serverLagTimes ) { return $this->serverLagTimes; } $expiry = 5; $request_rate = 10; $cache_key = 'lag_times:' . $this->servers[0]['DBHost']; $times = $this->Application->getCache($cache_key); if ( $times ) { // randomly recache with probability rising over $expiry $elapsed = time() - $times['timestamp']; $chance = max(0, ($expiry - $elapsed) * $request_rate); if ( mt_rand(0, $chance) != 0 ) { unset( $times['timestamp'] ); $this->serverLagTimes = $times; return $times; } } // cache key missing or expired $times = Array(); foreach ($this->servers as $index => $server) { if ($index == 0) { $times[$index] = 0; // master } else { $conn =& $this->openConnection($index); if ($conn !== false) { $times[$index] = $conn->getSlaveLag(); } } } // add a timestamp key so we know when it was cached $times['timestamp'] = time(); $this->Application->setCache($cache_key, $times, $expiry); // but don't give the timestamp to the caller unset($times['timestamp']); $this->serverLagTimes = $times; return $this->serverLagTimes; } /** * Determines whatever server should not be used, even, when connection was made * * @param kDBConnection $conn * @param int $threshold * @return int * @access protected */ protected function postConnectionBackoff(&$conn, $threshold) { if ( !$threshold ) { return 0; } $status = $conn->getStatus('Thread%'); return $status['Threads_running'] > $threshold ? $status['Threads_connected'] : 0; } /** * Open a connection to the server given by the specified index * Index must be an actual index into the array. * If the server is already open, returns it. * * On error, returns false. * * @param integer $i Server index * @return kDBConnection|false * @access protected */ protected function &openConnection($i) { if ( isset($this->connections[$i]) ) { $conn =& $this->connections[$i]; } else { $server = $this->servers[$i]; $server['serverIndex'] = $i; $conn =& $this->reallyOpenConnection($server, $i == $this->getMasterIndex()); if ( $conn->connectionOpened ) { $this->connections[$i] =& $conn; $this->lastUsedIndex = $i; } else { $conn = false; } } if ( $this->nextQueryCachable && is_object($conn) ) { $conn->nextQueryCachable = true; $this->nextQueryCachable = false; } return $conn; } /** * Checks if previous query execution raised an error. * * @return bool * @access public */ public function hasError() { $conn =& $this->openConnection($this->lastUsedIndex); return $conn->hasError(); } /** * Checks if connection to the server given by the specified index is opened. * * @param integer $i Server index * @return bool * @access public */ public function connectionOpened($i = null) { $conn =& $this->openConnection(isset($i) ? $i : $this->getMasterIndex()); return $conn ? $conn->connectionOpened : false; } /** * Really opens a connection. * Returns a database object whether or not the connection was successful. * * @param Array $server * @param bool $is_master * @return kDBConnection */ protected function &reallyOpenConnection($server, $is_master) { $debug_mode = $this->Application->isDebugMode(); $db_class = $debug_mode ? 'kDBConnectionDebug' : 'kDBConnection'; + /** @var kDBConnection $db */ $db = $this->Application->makeClass($db_class, Array ($this->dbType, $this->errorHandler, $server['serverIndex'])); - /* @var $db kDBConnection */ $db->debugMode = $debug_mode; $db->Connect($server['DBHost'], $server['DBUser'], $server['DBUserPassword'], $this->servers[0]['DBName'], !$is_master); return $db; } /** * Returns first field of first line of recordset if query ok or false otherwise. * * @param string $sql * @param int $offset * @return string * @access public */ public function GetOne($sql, $offset = 0) { $conn =& $this->chooseConnection($sql); return $conn->GetOne($sql, $offset); } /** * Returns first row of recordset if query ok, false otherwise. * * @param string $sql * @param int $offset * @return Array * @access public */ public function GetRow($sql, $offset = 0) { $conn =& $this->chooseConnection($sql); return $conn->GetRow($sql, $offset); } /** * Returns 1st column of recordset as one-dimensional array or false otherwise. * * Optional parameter $key_field can be used to set field name to be used as resulting array key. * * @param string $sql * @param string $key_field * @return Array * @access public */ public function GetCol($sql, $key_field = null) { $conn =& $this->chooseConnection($sql); return $conn->GetCol($sql, $key_field); } /** * Returns iterator for 1st column of a recordset or false in case of error. * * Optional parameter $key_field can be used to set field name to be used as resulting array key. * * @param string $sql * @param string $key_field * @return bool|kMySQLQueryCol */ public function GetColIterator($sql, $key_field = null) { $conn =& $this->chooseConnection($sql); return $conn->GetColIterator($sql, $key_field); } /** * Queries db with $sql query supplied and returns rows selected if any, false otherwise. * * Optional parameter $key_field allows to set one of the query fields value as key in string array. * * @param string $sql * @param string $key_field * @param bool $no_debug * @return Array * @access public */ public function Query($sql, $key_field = null, $no_debug = false) { $conn =& $this->chooseConnection($sql); return $conn->Query($sql, $key_field, $no_debug); } /** * Returns iterator to a recordset, produced from running $sql query. * * Queries db with $sql query supplied and returns kMySQLQuery iterator or false in case of error. * Optional parameter $key_field allows to set one of the query fields value as key in string array. * * @param string $sql * @param string $key_field * @param bool $no_debug * @param string $iterator_class * @return kMySQLQuery|bool * @access public */ public function GetIterator($sql, $key_field = null, $no_debug = false, $iterator_class = 'kMySQLQuery') { $conn =& $this->chooseConnection($sql); return $conn->GetIterator($sql, $key_field, $no_debug, $iterator_class); } /** * Free memory used to hold recordset handle. * * @access public */ public function Destroy() { $conn =& $this->openConnection($this->lastUsedIndex); $conn->Destroy(); } /** * Performs sql query, that will change database content. * * @param string $sql * @return bool * @access public */ public function ChangeQuery($sql) { $conn =& $this->chooseConnection($sql); return $conn->ChangeQuery($sql); } /** * Returns auto increment field value from insert like operation if any, zero otherwise. * * @return int * @access public */ public function getInsertID() { $conn =& $this->openConnection($this->lastUsedIndex); return $conn->getInsertID(); } /** * Returns row count affected by last query. * * @return int * @access public */ public function getAffectedRows() { $conn =& $this->openConnection($this->lastUsedIndex); return $conn->getAffectedRows(); } /** * Returns LIMIT sql clause part for specific db. * * @param int $offset * @param int $rows * @return string * @access public */ public function getLimitClause($offset, $rows) { $conn =& $this->openConnection($this->lastUsedIndex); return $conn->getLimitClause($offset, $rows); } /** * If it's a string, adds quotes and backslashes. Otherwise returns as-is. * * @param mixed $string * @return string * @access public */ public function qstr($string) { $conn =& $this->openConnection($this->lastUsedIndex); return $conn->qstr($string); } /** * Calls "qstr" function for each given array element. * * @param Array $array * @param string $function * @return Array */ public function qstrArray($array, $function = 'qstr') { $conn =& $this->openConnection($this->lastUsedIndex); return $conn->qstrArray($array, $function); } /** * Escapes string. * * @param mixed $string * @return string * @access public */ public function escape($string) { $conn =& $this->openConnection($this->lastUsedIndex); return $conn->escape($string); } /** * Returns last error code occurred. * * @return int * @access public */ public function getErrorCode() { $conn =& $this->openConnection($this->lastUsedIndex); return $conn->getErrorCode(); } /** * Returns last error message. * * @return string * @access public */ public function getErrorMsg() { $conn =& $this->openConnection($this->lastUsedIndex); return $conn->getErrorMsg(); } /** * Performs insert of given data (useful with small number of queries) * or stores it to perform multiple insert later (useful with large number of queries). * * @param Array $fields_hash * @param string $table * @param string $type * @param bool $insert_now * @return bool * @access public */ public function doInsert($fields_hash, $table, $type = 'INSERT', $insert_now = true) { $conn =& $this->openConnection( $this->getMasterIndex() ); return $conn->doInsert($fields_hash, $table, $type, $insert_now); } /** * Update given field values to given record using $key_clause. * * @param Array $fields_hash * @param string $table * @param string $key_clause * @return bool * @access public */ public function doUpdate($fields_hash, $table, $key_clause) { $conn =& $this->openConnection( $this->getMasterIndex() ); return $conn->doUpdate($fields_hash, $table, $key_clause); } /** * Allows to detect table's presence in database. * * @param string $table_name * @param bool $force * @return bool * @access public */ public function TableFound($table_name, $force = false) { $conn =& $this->openConnection($this->lastUsedIndex); return $conn->TableFound($table_name, $force); } /** * Returns query processing statistics. * * @return Array * @access public */ public function getQueryStatistics() { $conn =& $this->openConnection($this->lastUsedIndex); return $conn->getQueryStatistics(); } /** * Get status information from SHOW STATUS in an associative array. * * @param string $which * @return Array * @access public */ public function getStatus($which = '%') { $conn =& $this->openConnection($this->lastUsedIndex); return $conn->getStatus($which); } /** * When undefined method is called, then send it directly to last used slave server connection * * @param string $name * @param Array $arguments * @return mixed * @access public */ public function __call($name, $arguments) { $conn =& $this->openConnection($this->lastUsedIndex); return call_user_func_array( Array (&$conn, $name), $arguments ); } /** * Returns appropriate connection based on sql * * @param string $sql * @return kDBConnection * @access protected */ protected function &chooseConnection($sql) { if ( $this->nextQueryFromMaster ) { $this->nextQueryFromMaster = false; $index = $this->getMasterIndex(); } else { $sid = isset($this->Application->Session) ? $this->Application->GetSID() : '9999999999999999999999'; if ( preg_match('/(^[ \t\r\n]*(ALTER|CREATE|DROP|RENAME|DELETE|DO|INSERT|LOAD|REPLACE|TRUNCATE|UPDATE))|ses_' . $sid . '/', $sql) ) { $index = $this->getMasterIndex(); } else { $index = $this->getSlaveIndex(); } } $this->lastUsedIndex = $index; $conn =& $this->openConnection($index); return $conn; } /** * Get slave replication lag. It will only work if the DB user has the PROCESS privilege. * * @return int * @access public */ public function getSlaveLag() { $conn =& $this->openConnection($this->lastUsedIndex); return $conn->getSlaveLag(); } } Index: branches/5.3.x/core/kernel/db/db_tag_processor.php =================================================================== --- branches/5.3.x/core/kernel/db/db_tag_processor.php (revision 16518) +++ branches/5.3.x/core/kernel/db/db_tag_processor.php (revision 16519) @@ -1,3128 +1,3128 @@ getObject($params); return $object->GetID() <= 0; } /** * Returns view menu name for current prefix * * @param Array $params * @return string */ function GetItemName($params) { $item_name = $this->getUnitConfig()->getViewMenuPhrase(); return $this->Application->Phrase($item_name); } function ViewMenu($params) { $block_params = $params; unset($block_params['block']); $block_params['name'] = $params['block']; $list =& $this->GetList($params); $block_params['PrefixSpecial'] = $list->getPrefixSpecial(); return $this->Application->ParseBlock($block_params); } function SearchKeyword($params) { $list =& $this->GetList($params); return $this->Application->RecallVar($list->getPrefixSpecial() . '_search_keyword'); } /** * Draw filter menu content (for ViewMenu) based on filters defined in config * * @param Array $params * @return string */ function DrawFilterMenu($params) { $block_params = $this->prepareTagParams($params); $block_params['name'] = $params['spearator_block']; $separator = $this->Application->ParseBlock($block_params); $filter_menu = $this->getUnitConfig()->getFilterMenu(); if ( !$filter_menu ) { trigger_error('no filters defined for prefix ' . $this->Prefix . ', but DrawFilterMenu tag used', E_USER_NOTICE); return ''; } // Params: label, filter_action, filter_status $block_params['name'] = $params['item_block']; $view_filter = $this->Application->RecallVar($this->getPrefixSpecial() . '_view_filter'); if ( $view_filter === false ) { $this->Application->HandleEvent(new kEvent($this->getPrefixSpecial() . ':OnRemoveFilters')); $view_filter = $this->Application->RecallVar($this->getPrefixSpecial() . '_view_filter'); } $view_filter = unserialize($view_filter); $filters = Array (); $prefix_special = $this->getPrefixSpecial(); foreach ($filter_menu['Filters'] as $filter_key => $filter_params) { $group_params = isset($filter_params['group_id']) ? $filter_menu['Groups'][$filter_params['group_id']] : Array (); if ( !isset($group_params['element_type']) ) { $group_params['element_type'] = 'checkbox'; } if ( !$filter_params ) { $filters[] = $separator; continue; } $block_params['label'] = $filter_params['label']; if ( getArrayValue($view_filter, $filter_key) ) { $submit = 0; if ( isset($params['old_style']) ) { $status = $group_params['element_type'] == 'checkbox' ? 1 : 2; } else { $status = $group_params['element_type'] == 'checkbox' ? '[\'img/check_on.gif\']' : '[\'img/menu_dot.gif\']'; } } else { $submit = 1; $status = 'null'; } $block_params['filter_action'] = 'set_filter("' . $prefix_special . '","' . $filter_key . '","' . $submit . '",' . $params['ajax'] . ');'; $block_params['filter_status'] = $status; // 1 - checkbox, 2 - radio, 0 - no image $filters[] = $this->Application->ParseBlock($block_params); } return implode('', $filters); } /** * Draws auto-refresh submenu in View Menu. * * @param Array $params * @return string */ function DrawAutoRefreshMenu($params) { $refresh_intervals = $this->Application->ConfigValue('AutoRefreshIntervals'); if (!$refresh_intervals) { trigger_error('no refresh intervals defined for prefix '.$this->Prefix.', but DrawAutoRefreshMenu tag used', E_USER_NOTICE); return ''; } $refresh_intervals = explode(',', $refresh_intervals); $view_name = $this->Application->RecallVar($this->getPrefixSpecial().'_current_view'); $current_refresh_interval = $this->Application->RecallPersistentVar($this->getPrefixSpecial().'_refresh_interval.'.$view_name); if ($current_refresh_interval === false) { // if no interval was selected before, then choose 1st interval $current_refresh_interval = $refresh_intervals[0]; } $ret = ''; $block_params = $this->prepareTagParams($params); $block_params['name'] = $params['render_as']; foreach ($refresh_intervals as $refresh_interval) { $block_params['label'] = $this->_formatInterval($refresh_interval); $block_params['refresh_interval'] = $refresh_interval; $block_params['selected'] = $current_refresh_interval == $refresh_interval; $ret .= $this->Application->ParseBlock($block_params); } return $ret; } /** * Tells, that current grid is using auto refresh * * @param Array $params * @return bool */ function UseAutoRefresh($params) { $view_name = $this->Application->RecallVar($this->getPrefixSpecial().'_current_view'); return $this->Application->RecallPersistentVar($this->getPrefixSpecial().'_auto_refresh.'.$view_name); } /** * Returns current grid refresh interval * * @param Array $params * @return bool */ function AutoRefreshInterval($params) { $view_name = $this->Application->RecallVar($this->getPrefixSpecial().'_current_view'); return $this->Application->RecallPersistentVar($this->getPrefixSpecial().'_refresh_interval.'.$view_name); } /** * Formats time interval using given text for hours and minutes * * @param int $interval minutes * @param string $hour_text Text for hours * @param string $min_text Text for minutes * @return string */ function _formatInterval($interval, $hour_text = 'h', $min_text = 'min') { // 65 $minutes = $interval % 60; $hours = ($interval - $minutes) / 60; $ret = ''; if ($hours) { $ret .= $hours.$hour_text.' '; } if ($minutes) { $ret .= $minutes.$min_text; } return $ret; } function IterateGridFields($params) { $mode = $params['mode']; $def_block = isset($params['block']) ? $params['block'] : ''; $force_block = isset($params['force_block']) ? $params['force_block'] : false; $grid = $this->getUnitConfig()->getGridByName($params['grid']); $grid_config = $grid['Fields']; $picker_helper = new kColumnPickerHelper($this->getPrefixSpecial(), $params['grid']); $grid_config = $picker_helper->apply($grid_config); if ( $mode == 'fields' ) { return "'" . join("','", array_keys($grid_config)) . "'"; } $object =& $this->GetList($params); $o = ''; $i = 0; foreach ($grid_config as $field => $options) { $i++; $block_params = $this->prepareTagParams($params); $block_params = array_merge($block_params, $options); $block_params['block_name'] = array_key_exists($mode . '_block', $block_params) ? $block_params[$mode . '_block'] : $def_block; $block_params['name'] = $force_block ? $force_block : $block_params['block_name']; $block_params['field'] = $field; $block_params['sort_field'] = isset($options['sort_field']) ? $options['sort_field'] : $field; $block_params['filter_field'] = isset($options['filter_field']) ? $options['filter_field'] : $field; $w = $picker_helper->getWidth($field); if ( $w ) { // column picker width overrides width from unit config $block_params['width'] = $w; } $field_options = $object->GetFieldOptions($field); if ( array_key_exists('use_phrases', $field_options) ) { $block_params['use_phrases'] = $field_options['use_phrases']; } $block_params['is_last'] = ($i == count($grid_config)); $o .= $this->Application->ParseBlock($block_params, 1); } return $o; } function PickerCRC($params) { $picker_helper = new kColumnPickerHelper($this->getPrefixSpecial(), $params['grid']); return $picker_helper->getData()->getChecksum(); } function FreezerPosition($params) { $picker_helper = new kColumnPickerHelper($this->getPrefixSpecial(), $params['grid']); $data = $picker_helper->getData(); $freezer_pos = $data->getOrder('__FREEZER__'); return $freezer_pos === false || $data->isHidden('__FREEZER__') ? 1 : ++$freezer_pos; } function GridFieldsCount($params) { $grid = $this->getUnitConfig()->getGridByName($params['grid']); return count($grid['Fields']); } /** * Prints list content using block specified * * @param Array $params * @return string * @access public */ function PrintList($params) { $params['no_table'] = 1; return $this->PrintList2($params); } function InitList($params) { $list_name = isset($params['list_name']) ? $params['list_name'] : ''; if ( getArrayValue($this->nameToSpecialMapping, $list_name) === false ) { $list =& $this->GetList($params); } } function BuildListSpecial($params) { return $this->Special; } /** * Returns key, that identifies each list on template (used internally, not tag) * * @param Array $params * @return string */ function getUniqueListKey($params) { $types = array_key_exists('types', $params) ? $params['types'] : ''; $except = array_key_exists('except', $params) ? $params['except'] : ''; $list_name = array_key_exists('list_name', $params) ? $params['list_name'] : ''; if (!$list_name) { $list_name = $this->Application->Parser->GetParam('list_name'); } return $types . $except . $list_name; } /** * Enter description here... * * @param Array $params * @return kDBList */ function &GetList($params) { $list_name = array_key_exists('list_name', $params) ? $params['list_name'] : ''; if ( !$list_name ) { $list_name = $this->Application->Parser->GetParam('list_name'); } $requery = isset($params['requery']) && $params['requery']; $main_list = array_key_exists('main_list', $params) && $params['main_list']; if ( $list_name && !$requery ) { // list with "list_name" parameter if ( !array_key_exists($list_name, $this->nameToSpecialMapping) ) { // special missing -> generate one $special = $main_list ? $this->Special : $this->BuildListSpecial($params); } else { // get special, formed during list initialization $special = $this->nameToSpecialMapping[$list_name]; } } else { // list without "list_name" parameter $special = $main_list ? $this->Special : $this->BuildListSpecial($params); } $prefix_special = rtrim($this->Prefix . '.' . $special, '.'); $params['skip_counting'] = true; + /** @var kDBList $list */ $list = $this->Application->recallObject($prefix_special, $this->Prefix . '_List', $params); - /* @var $list kDBList */ if ( !array_key_exists('skip_quering', $params) || !$params['skip_quering'] ) { if ( $requery ) { $this->Application->HandleEvent(new kEvent($prefix_special . ':OnListBuild', $params)); } if ( array_key_exists('offset', $params) ) { $list->SetOffset($list->GetOffset() + $params['offset']); // apply custom offset } $list->Query($requery); if ( array_key_exists('offset', $params) ) { $list->SetOffset($list->GetOffset() - $params['offset']); // remove custom offset } } $this->Init($this->Prefix, $special); if ( $list_name ) { $this->nameToSpecialMapping[$list_name] = $special; } return $list; } function ListMarker($params) { $list =& $this->GetList($params); $ret = $list->getPrefixSpecial(); if (array_key_exists('as_preg', $params) && $params['as_preg']) { $ret = preg_quote($ret, '/'); } return $ret; } function CombinedSortingDropDownName($params) { $list =& $this->GetList($params); return $list->getPrefixSpecial() . '_CombinedSorting'; } /** * Prepares name for field with event in it (used only on front-end) * * @param Array $params * @return string */ function SubmitName($params) { $list =& $this->GetList($params); $prefix_special = $list->getPrefixSpecial(); return 'events[' . $prefix_special . '][' . $params['event'] . ']'; } /** * Prints list content using block specified * * @param Array $params * @return string * @access public */ function PrintList2($params) { $per_page = $this->SelectParam($params, 'per_page,max_items'); if ( $per_page !== false ) { $params['per_page'] = $per_page; } $list =& $this->GetList($params); $o = ''; $direction = (isset($params['direction']) && $params['direction'] == "H") ? "H" : "V"; $columns = (isset($params['columns'])) ? $params['columns'] : 1; $config = $this->getUnitConfig(); $id_field = (isset($params['id_field'])) ? $params['id_field'] : $config->getIDField(); if ( $columns > 1 && $direction == 'V' ) { $records_left = array_splice($list->Records, $list->GetSelectedCount()); // because we have 1 more record for "More..." link detection (don't need to sort it) $list->Records = $this->LinearToVertical($list->Records, $columns, $list->GetPerPage()); $list->Records = array_merge($list->Records, $records_left); } $list->GoFirst(); $block_params = $this->prepareTagParams($params); $block_params['name'] = $this->SelectParam($params, 'render_as,block'); $block_params['pass_params'] = 'true'; $block_params['column_width'] = $params['column_width'] = 100 / $columns; $block_start_row_params = $this->prepareTagParams($params); $block_start_row_params['name'] = $this->SelectParam($params, 'row_start_render_as,block_row_start,row_start_block'); $block_end_row_params = $this->prepareTagParams($params); $block_end_row_params['name'] = $this->SelectParam($params, 'row_end_render_as,block_row_end,row_end_block'); $block_empty_cell_params = $this->prepareTagParams($params); $block_empty_cell_params['name'] = $this->SelectParam($params, 'empty_cell_render_as,block_empty_cell,empty_cell_block'); $i = 0; $backup_id = $this->Application->GetVar($this->Prefix . '_id'); $displayed = Array (); $column_number = 1; $cache_mod_rw = $config->getCacheModRewrite() && $this->Application->RewriteURLs() && !$this->Application->isCachingType(CACHING_TYPE_MEMORY); $limit = isset($params['limit']) ? $params['limit'] : false; while (!$list->EOL() && (!$limit || $i<$limit)) { $this->Application->SetVar($this->getPrefixSpecial() . '_id', $list->GetDBField($id_field)); // for edit/delete links using GET $this->Application->SetVar($this->Prefix . '_id', $list->GetDBField($id_field)); $block_params['is_last'] = ($i == $list->GetSelectedCount() - 1); $block_params['last_row'] = ($i + (($i + 1) % $columns) >= $list->GetSelectedCount() - 1); $block_params['not_last'] = !$block_params['is_last']; // for front-end if ( $cache_mod_rw ) { $serial_name = $this->Application->incrementCacheSerial($this->Prefix, $list->GetDBField($id_field), false); if ( $this->Prefix == 'c' ) { // for listing subcategories in category $this->Application->setCache('filenames[%' . $serial_name . '%]', $list->GetDBField('NamedParentPath')); $this->Application->setCache('category_tree[%CIDSerial:' . $list->GetDBField($id_field) . '%]', $list->GetDBField('TreeLeft') . ';' . $list->GetDBField('TreeRight')); } else { // for listing items in category $this->Application->setCache('filenames[%' . $serial_name . '%]', $list->GetDBField('Filename')); $serial_name = $this->Application->incrementCacheSerial('c', $list->GetDBField('CategoryId'), false); $this->Application->setCache('filenames[%' . $serial_name . '%]', $list->GetDBField('CategoryFilename')); } } if ( $i % $columns == 0 ) { // record in this iteration is first in row, then open row $column_number = 1; $o .= $block_start_row_params['name'] ? $this->Application->ParseBlock($block_start_row_params) : (!isset($params['no_table']) ? '' : ''); } else { $column_number++; } $block_params['first_col'] = $column_number == 1 ? 1 : 0; $block_params['last_col'] = $column_number == $columns ? 1 : 0; $block_params['column_number'] = $column_number; $block_params['num'] = ($i + 1); $this->PrepareListElementParams($list, $block_params); // new, no need to rewrite PrintList $o .= $this->Application->ParseBlock($block_params); array_push($displayed, $list->GetDBField($id_field)); if ( $direction == 'V' && $list->GetSelectedCount() % $columns > 0 && $column_number == ($columns - 1) && ceil(($i + 1) / $columns) > $list->GetSelectedCount() % ceil($list->GetSelectedCount() / $columns) ) { // if vertical output, then draw empty cells vertically, not horizontally $o .= $block_empty_cell_params['name'] ? $this->Application->ParseBlock($block_empty_cell_params) : ' '; $i++; } if ( ($i + 1) % $columns == 0 ) { // record in next iteration is first in row too, then close this row $o .= $block_end_row_params['name'] ? $this->Application->ParseBlock($block_end_row_params) : (!isset($params['no_table']) ? '' : ''); } if ( $this->Special && $this->Application->hasObject($this->Prefix) ) { // object, produced by "kDBList::linkToParent" method, that otherwise would keep it's id + /** @var kDBBase $item */ $item = $this->Application->recallObject($this->Prefix); - /* @var $item kDBBase */ if ( $item instanceof kDBItem ) { $this->Application->removeObject($this->Prefix); } } $list->GoNext(); $i++; } // append empty cells in place of missing cells in last row while ($i % $columns != 0) { // until next cell will be in new row append empty cells $o .= $block_empty_cell_params['name'] ? $this->Application->ParseBlock($block_empty_cell_params) : ' '; if ( ($i + 1) % $columns == 0 ) { // record in next iteration is first in row too, then close this row $o .= $block_end_row_params['name'] ? $this->Application->ParseBlock($block_end_row_params) : ''; } $i++; } $cur_displayed = $this->Application->GetVar($this->Prefix . '_displayed_ids'); if ( !$cur_displayed ) { $cur_displayed = Array (); } else { $cur_displayed = explode(',', $cur_displayed); } $displayed = array_unique(array_merge($displayed, $cur_displayed)); $this->Application->SetVar($this->Prefix . '_displayed_ids', implode(',', $displayed)); $this->Application->SetVar($this->Prefix . '_id', $backup_id); $this->Application->SetVar($this->getPrefixSpecial() . '_id', ''); if ( isset($params['more_link_render_as']) ) { $block_params = $params; $params['render_as'] = $params['more_link_render_as']; $o .= $this->MoreLink($params); } return $o; } /** * Returns ID of previous record (related to current) in list. * Use only on item detail pages. * * @param Array $params * @return int * @access protected */ protected function PreviousResource($params) { + /** @var kDBItem $object */ $object = $this->getObject($params); - /* @var $object kDBItem */ + /** @var ListHelper $list_helper */ $list_helper = $this->Application->recallObject('ListHelper'); - /* @var $list_helper ListHelper */ $select_clause = $object->getUnitConfig()->getNavigationSelectClause(null); return $list_helper->getNavigationResource($object, $params['list'], false, $select_clause); } /** * Returns ID of next record (related to current) in list. * Use only on item detail pages. * * @param Array $params * @return int * @access protected */ protected function NextResource($params) { + /** @var kDBItem $object */ $object = $this->getObject($params); - /* @var $object kDBItem */ + /** @var ListHelper $list_helper */ $list_helper = $this->Application->recallObject('ListHelper'); - /* @var $list_helper ListHelper */ $select_clause = $object->getUnitConfig()->getNavigationSelectClause(null); return $list_helper->getNavigationResource($object, $params['list'], true, $select_clause); } /** * Allows to modify block params & current list record before PrintList parses record * * @param kDBList $object * @param Array $block_params * @return void * @access protected */ protected function PrepareListElementParams(&$object, &$block_params) { // $fields_hash =& $object->getCurrentRecord(); } /** * Renders given block name, when there there is more data in list, then are displayed right now * * @param Array $params * @return string * @access protected */ protected function MoreLink($params) { $per_page = $this->SelectParam($params, 'per_page,max_items'); if ( $per_page !== false ) { $params['per_page'] = $per_page; } $list =& $this->GetList($params); if ( $list->isCounted() ) { $has_next_page = $list->GetPage() < $list->GetTotalPages(); } else { // selected more, then on the page -> has more $has_next_page = $list->GetPerPage() < $list->GetRecordsCount(); } if ( $has_next_page ) { $block_params = Array ('name' => $this->SelectParam($params, 'render_as,block')); return $this->Application->ParseBlock($block_params); } return ''; } function PageLink($params) { static $default_per_page = Array (); + /** @var kDBList $object */ $object =& $this->GetList($params); - /* @var $object kDBList */ // process sorting if ($object->isMainList()) { if (!array_key_exists('sort_by', $params)) { $sort_by = $this->Application->GetVar('sort_by'); if ($sort_by !== false) { $params['sort_by'] = $sort_by; } } } $prefix_special = $this->getPrefixSpecial(); // process page $page = array_key_exists('page', $params) ? $params['page'] : $this->Application->GetVar($prefix_special . '_Page'); if (!$page) { // ensure, that page is always present if ($object->isMainList()) { $params[$prefix_special . '_Page'] = $this->Application->GetVar('page', 1); } else { $params[$prefix_special . '_Page'] = 1; } } if (array_key_exists('page', $params)) { $params[$prefix_special . '_Page'] = $params['page']; unset($params['page']); } // process per-page $per_page = array_key_exists('per_page', $params) ? $params['per_page'] : $this->Application->GetVar($prefix_special . '_PerPage'); if (!$per_page) { // ensure, that per-page is always present list ($prefix, ) = explode('.', $prefix_special); if (!array_key_exists($prefix, $default_per_page)) { + /** @var ListHelper $list_helper */ $list_helper = $this->Application->recallObject('ListHelper'); - /* @var $list_helper ListHelper */ $default_per_page[$prefix] = $list_helper->getDefaultPerPage($prefix); } if ($object->isMainList()) { $params[$prefix_special . '_PerPage'] = $this->Application->GetVar('per_page', $default_per_page[$prefix]); } else { $params[$prefix_special . '_PerPage'] = $default_per_page[$prefix]; } } if (array_key_exists('per_page', $params)) { $params[$prefix_special . '_PerPage'] = $params['per_page']; unset($params['per_page']); } if (!array_key_exists('pass', $params)) { $params['pass'] = 'm,' . $prefix_special; } // process template $t = array_key_exists('template', $params) ? $params['template'] : ''; unset($params['template']); if (!$t) { $t = $this->Application->GetVar('t'); } return $this->Application->HREF($t, '', $params); } /** * Deprecated * * @param array $params * @return int * @deprecated Parameter "column_width" of "PrintList" tag does that */ function ColumnWidth($params) { $columns = $this->Application->Parser->GetParam('columns'); return round(100/$columns).'%'; } /** * Append prefix and special to tag * params (get them from tagname) like * they were really passed as params * * @param Array $tag_params * @return Array * @access protected */ function prepareTagParams($tag_params = Array()) { $ret = $tag_params; $ret['Prefix'] = $this->Prefix; $ret['Special'] = $this->Special; $ret['PrefixSpecial'] = $this->getPrefixSpecial(); return $ret; } function GetISO($currency, $field_currency = '') { if ( $currency == 'selected' ) { return $this->Application->RecallVar('curr_iso'); } if ( $currency == 'primary' || $currency == '' ) { return $this->Application->GetPrimaryCurrency(); } // explicit currency return $currency == 'field' && $field_currency ? $field_currency : $currency; } /** * Convert primary currency to selected (if they are the same, converter will just return) * * @param float $value * @param string $target_iso * @param string $source_iso * @return float */ function ConvertCurrency($value, $target_iso, $source_iso = 'PRIMARY') { + /** @var CurrencyRates $converter */ $converter = $this->Application->recallObject('CurrencyRates'); - /* @var $converter CurrencyRates */ return $converter->Convert($value, $source_iso, $target_iso); } function AddCurrencySymbol($value, $iso, $decimal_tag = '') { + /** @var CurrencyRates $converter */ $converter = $this->Application->recallObject('CurrencyRates'); - /* @var $converter CurrencyRates */ return $converter->AddCurrencySymbol($value, $iso, $decimal_tag); } /** * Get's requested field value * * @param Array $params * @return string * @access public */ function Field($params) { $field = $this->SelectParam($params, 'name,field'); if (!$this->Application->isAdmin) { // don't apply kUtil::escape() on any field value on Front-End $params['no_special'] = 'no_special'; } + /** @var kDBItem $object */ $object = $this->getObject($params); - /* @var $object kDBItem */ if (array_key_exists('db', $params) && $params['db']) { $value = $object->GetDBField($field); } else { if (array_key_exists('currency', $params) && $params['currency']) { $source_iso = isset($params['currency_field']) ? $object->GetDBField($params['currency_field']) : 'PRIMARY'; $target_iso = $this->GetISO($params['currency'], $source_iso); $original = $object->GetDBField($field); $value = $this->ConvertCurrency($original, $target_iso, $source_iso); $object->SetDBField($field, $value); $object->SetFieldOption($field, 'converted', true); } $format = array_key_exists('format', $params) ? $params['format'] : false; if (!$format || $format == '$format') { $format = NULL; } $value = $object->GetField($field, $format); if (array_key_exists('negative', $params) && $params['negative']) { if (strpos($value, '-') === 0) { $value = substr($value, 1); } else { $value = '-' . $value; } } if (array_key_exists('currency', $params) && $params['currency']) { $decimal_tag = isset($params['decimal_tag']) ? $params['decimal_tag'] : ''; $value = $this->AddCurrencySymbol($value, $target_iso, $decimal_tag); $params['no_special'] = 1; } } if (!array_key_exists('no_special', $params) || !$params['no_special']) { $value = kUtil::escape($value); } if (array_key_exists('checked', $params) && $params['checked']) { $value = ($value == ( isset($params['value']) ? $params['value'] : 1)) ? 'checked' : ''; } if (array_key_exists('plus_or_as_label', $params) && $params['plus_or_as_label']) { $value = substr($value, 0,1) == '+' ? substr($value, 1) : $this->Application->Phrase($value); } elseif (array_key_exists('as_label', $params) && $params['as_label']) { $value = $this->Application->Phrase($value); } $first_chars = $this->SelectParam($params,'first_chars,cut_first'); if ($first_chars) { $stripped_value = strip_tags($value, $this->SelectParam($params, 'allowed_tags')); if ( mb_strlen($stripped_value) > $first_chars ) { $value = preg_replace('/\s+?(\S+)?$/', '', mb_substr($stripped_value, 0, $first_chars + 1)) . ' ...'; } } if (array_key_exists('nl2br', $params) && $params['nl2br']) { $value = nl2br($value); } if ($value != '') { $this->Application->Parser->DataExists = true; } if (array_key_exists('currency', $params) && $params['currency']) { // restoring value in original currency, for other Field tags to work properly $object->SetDBField($field, $original); } return $value; } function FieldHintLabel($params) { if ( isset($params['direct_label']) && $params['direct_label'] ) { $label = $params['direct_label']; $hint = $this->Application->Phrase($label, false); } else { $label = $params['title_label']; $hint = $this->Application->Phrase('hint:' . $label, false); } return $hint != strtoupper('!' . $label . '!') ? $hint : ''; // $hint } /** * Returns formatted date + time on current language * * @param $params */ function DateField($params) { $field = $this->SelectParam($params, 'name,field'); if ($field) { + /** @var kDBItem $object */ $object = $this->getObject($params); - /* @var $object kDBItem */ $timestamp = $object->GetDBField($field); } else { $timestamp = $params['value']; } $date = $timestamp; // prepare phrase replacements $replacements = Array ( 'l' => 'la_WeekDay', 'D' => 'la_WeekDay', 'M' => 'la_Month', 'F' => 'la_Month', ); // cases allow to append phrase suffix based on requested case (e.g. Genitive) $case_suffixes = array_key_exists('case_suffixes', $params) ? $params['case_suffixes'] : false; if ($case_suffixes) { // apply case suffixes (for russian language only) $case_suffixes = explode(',', $case_suffixes); foreach ($case_suffixes as $case_suffux) { list ($replacement_name, $case_suffix_value) = explode('=', $case_suffux, 2); $replacements[$replacement_name] .= $case_suffix_value; } } $format = array_key_exists('format', $params) ? $params['format'] : false; if (preg_match('/_regional_(.*)/', $format, $regs)) { + /** @var kDBItem $language */ $language = $this->Application->recallObject('lang.current'); - /* @var $language kDBItem */ $format = $language->GetDBField($regs[1]); } elseif (!$format) { $format = null; } // escape formats, that are resolved to words by `date` foreach ($replacements as $format_char => $phrase_prefix) { if (strpos($format, $format_char) === false) { unset($replacements[$format_char]); continue; } $replacements[$format_char] = $this->Application->Phrase($phrase_prefix . date($format_char, $date)); $format = str_replace($format_char, '#' . ord($format_char) . '#', $format); } $date_formatted = date($format, $date); // unescape formats, that are resolved to words by `date` foreach ($replacements as $format_char => $format_replacement) { $date_formatted = str_replace('#' . ord($format_char) . '#', $format_replacement, $date_formatted); } return $date_formatted; } function SetField($params) { // + /** @var kDBItem $object */ $object = $this->getObject($params); - /* @var $object kDBItem */ $dst_field = $this->SelectParam($params, 'name,field'); list($prefix_special, $src_field) = explode(':', $params['src']); + /** @var kDBItem $src_object */ $src_object = $this->Application->recallObject($prefix_special); - /* @var $src_object kDBItem */ $object->SetDBField($dst_field, $src_object->GetDBField($src_field)); } /** * Depricated * * @param Array $params * @return string * @deprecated parameter "as_label" of "Field" tag does the same */ function PhraseField($params) { $field_label = $this->Field($params); $translation = $this->Application->Phrase( $field_label ); return $translation; } function Error($params) { + /** @var kDBItem $object */ $object = $this->getObject($params); - /* @var $object kDBItem */ $field = $this->SelectParam($params, 'name,field'); return $object->GetErrorMsg($field, false); } function HasError($params) { if ($params['field'] == 'any') { + /** @var kDBItem $object */ $object = $this->getObject($params); - /* @var $object kDBItem */ $skip_fields = array_key_exists('except', $params) ? $params['except'] : false; $skip_fields = $skip_fields ? explode(',', $skip_fields) : Array(); return $object->HasErrors($skip_fields); } else { $res = false; $fields = explode(',', $this->SelectParam($params, 'field,fields')); foreach ($fields as $field) { // call kDBTagProcessor::Error instead of kDBItem::GetErrorPseudo to have ability to override Error tag $params['field'] = $field; $res = $res || ($this->Error($params) != ''); } return $res; } } /** * Renders error message block, when there are errors on a form * * @param Array $params * @return string * @access protected */ protected function ErrorWarning($params) { if ( !isset($params['field']) ) { $params['field'] = 'any'; } if ( $this->HasError($params) ) { $params['prefix'] = $this->getPrefixSpecial(); return $this->Application->ParseBlock($params); } return ''; } function IsRequired($params) { + /** @var kDBItem $object */ $object = $this->getObject($params); - /* @var $object kDBItem */ $field = $params['field']; $formatter_class = $object->GetFieldOption($field, 'formatter'); if ( $formatter_class == 'kMultiLanguage' ) { + /** @var kMultiLanguage $formatter */ $formatter = $this->Application->recallObject($formatter_class); - /* @var $formatter kMultiLanguage */ $field = $formatter->LangFieldName($field); } return $object->isRequired($field); } function FieldOption($params) { $object = $this->getObject($params);; $options = $object->GetFieldOptions($params['field']); $ret = isset($options[$params['option']]) ? $options[$params['option']] : ''; if (isset($params['as_label']) && $params['as_label']) $ret = $this->Application->ReplaceLanguageTags($ret); return $ret; } /** * Prints list a all possible field options * * @param Array $params * @return string * @access protected */ protected function PredefinedOptions($params) { + /** @var kDBList $object */ $object = $this->getObject($params); - /* @var $object kDBList */ $field = $params['field']; $value = array_key_exists('value', $params) ? $params['value'] : $object->GetDBField($field); $field_options = $object->GetFieldOptions($field); if (!array_key_exists('options', $field_options) || !is_array($field_options['options'])) { trigger_error('Options not defined for '.$object->Prefix.' field '.$field.'', E_USER_WARNING); return ''; } $options = $field_options['options']; if ( array_key_exists('has_empty', $params) && $params['has_empty'] ) { $empty_value = array_key_exists('empty_value', $params) ? $params['empty_value'] : ''; $empty_label = isset($params['empty_label']) ? $params['empty_label'] : ''; if ( $empty_label ) { if ( mb_substr($empty_label, 0, 1) == '+' ) { // using plain text instead of phrase label $empty_label = mb_substr($empty_label, 1); } else { $empty_label = $this->Application->Phrase($empty_label, false); } } // don't use other array merge function, because they will reset keys !!! $options = kUtil::array_merge_recursive(Array ($empty_value => $empty_label), $options); } $block_params = $this->prepareTagParams($params); $block_params['name'] = $this->SelectParam($params, 'render_as,block'); $block_params['pass_params'] = 'true'; if (method_exists($object, 'EOL') && count($object->Records) == 0) { // for drawing grid column filter $block_params['field_name'] = ''; } else { // deprecated (produces warning when used as grid filter), but used in Front-End (submission create), admin (submission view) $block_params['field_name'] = $this->InputName($params); } $selected_html = isset($params['selected']) ? $params['selected'] : 'selected'; $selected_param_name = array_key_exists('selected_param', $params) ? $params['selected_param'] : false; if (!$selected_param_name) { $selected_param_name = $selected_html; } $o = ''; if (array_key_exists('no_empty', $params) && $params['no_empty'] && !getArrayValue($options, '')) { // removes empty option, when present (needed?) array_shift($options); } $selected_option_keys = $this->getSelectedOptionKeys($value); if ( isset($params['selected_only']) && $params['selected_only'] ) { $options = $this->getSelectedOptions($options, $selected_option_keys); } $column_changed = false; $option_number = $column_number = 1; $option_count = count($options); $column_count = isset($params['columns']) ? $params['columns'] : 1; $options_per_column = ceil($option_count / $column_count); $block_params['option_count'] = $option_count; foreach ( $options as $option_key => $option_title ) { $block_params['key'] = $option_key; $block_params['option'] = $option_title; $block_params[$selected_param_name] = $this->isOptionSelected($option_key, $selected_option_keys) ? ' ' . $selected_html : ''; $block_params['column_number'] = $column_number; $block_params['column_changed'] = $column_changed; $block_params['option_number'] = $option_number; $block_params['is_last'] = $option_number == $option_count; $o .= $this->Application->ParseBlock($block_params); $column_changed = false; $option_number++; if ( $option_number > $column_number * $options_per_column ) { $column_number++; $column_changed = true; } } return $o; } /** * Returns unified representation of selected options based on field value. * * @param mixed $field_value Field value. * * @return array */ protected function getSelectedOptionKeys($field_value) { if ( strpos($field_value, '|') !== false ) { // multiple checkboxes OR multiselect return explode('|', trim($field_value, '|')); } // single selection radio OR checkboxes OR dropdown return array("$field_value"); } /** * Returns only options, that have been selected. * * @param array $options All options. * @param array $selected_option_keys Selected options. * * @return array */ protected function getSelectedOptions(array $options, array $selected_option_keys) { $ret = array(); foreach ( $options as $option_key => $option_title ) { if ( $this->isOptionSelected($option_key, $selected_option_keys) ) { $ret[$option_key] = $option_title; } } return $ret; } /** * Determines if given option is among selected ones. * * @param mixed $option_key Option key. * @param array $selected_option_keys Selected options. * * @return boolean */ protected function isOptionSelected($option_key, array $selected_option_keys) { return in_array("$option_key", $selected_option_keys, true); } function PredefinedSearchOptions($params) { + /** @var kDBList $object */ $object =& $this->GetList($params); - /* @var $object kDBList */ $params['value'] = $this->SearchField($params); return $this->PredefinedOptions($params); } function Format($params, $object = null) { $field = $this->SelectParam($params, 'name,field'); if ( !isset($object) ) { + /** @var kDBItem $object */ $object = $this->getObject($params); - /* @var $object kDBItem */ } $options = $object->GetFieldOptions($field); $format = $options[$this->SelectParam($params, 'input_format') ? 'input_format' : 'format']; $formatter_class = array_key_exists('formatter', $options) ? $options['formatter'] : false; if ( $formatter_class ) { + /** @var kFormatter $formatter */ $formatter = $this->Application->recallObject($formatter_class); - /* @var $formatter kFormatter */ $human_format = array_key_exists('human', $params) ? $params['human'] : false; $edit_size = array_key_exists('edit_size', $params) ? $params['edit_size'] : false; $sample = array_key_exists('sample', $params) ? $params['sample'] : false; if ( $sample ) { return $formatter->GetSample($field, $options, $object); } elseif ( $human_format || $edit_size ) { $format = $formatter->HumanFormat($format); return $edit_size ? strlen($format) : $format; } } return $format; } /** * Returns grid padination information * Can return links to pages * * @param Array $params * @return mixed */ function PageInfo($params) { + /** @var kDBList $object */ $object =& $this->GetList($params); - /* @var $object kDBList */ $type = $params['type']; unset($params['type']); // remove parameters used only by current tag $ret = ''; switch ($type) { case 'current': $ret = $object->GetPage(); break; case 'total': $ret = $object->GetTotalPages(); break; case 'prev': $ret = $object->GetPage() > 1 ? $object->GetPage() - 1 : false; break; case 'next': $ret = $object->GetPage() < $object->GetTotalPages() ? $object->GetPage() + 1 : false; break; } if ($ret && isset($params['as_link']) && $params['as_link']) { unset($params['as_link']); // remove parameters used only by current tag $params['page'] = $ret; $current_page = $object->GetPage(); // backup current page $ret = $this->PageLink($params); $this->Application->SetVar($object->getPrefixSpecial().'_Page', $current_page); // restore page } return $ret; } /** * Print grid pagination using * block names specified * * @param Array $params * @return string * @access public */ function PrintPages($params) { $list =& $this->GetList($params); $prefix_special = $list->getPrefixSpecial(); $total_pages = $list->GetTotalPages(); if ( $total_pages > 1 ) { $this->Application->Parser->DataExists = true; } if ( $total_pages == 0 ) { // display 1st page as selected in case if we have no pages at all $total_pages = 1; } $o = ''; // what are these 2 lines for? $this->Application->SetVar($prefix_special . '_event', ''); $this->Application->SetVar($prefix_special . '_id', ''); $current_page = $list->GetPage(); // $this->Application->RecallVar($prefix_special.'_Page'); $block_params = $this->prepareTagParams($params); $split = (isset($params['split']) ? $params['split'] : 10); $split_start = $current_page - ceil($split / 2); if ( $split_start < 1 ) { $split_start = 1; } $split_end = $split_start + $split - 1; if ( $split_end > $total_pages ) { $split_end = $total_pages; $split_start = max($split_end - $split + 1, 1); } if ( $current_page > 1 ) { $prev_block_params = $this->prepareTagParams($params); if ( $total_pages > $split ) { $prev_block_params['page'] = max($current_page - $split, 1); $prev_block_params['name'] = $this->SelectParam($params, 'prev_page_split_render_as,prev_page_split_block'); if ( $prev_block_params['name'] ) { $this->Application->SetVar($this->getPrefixSpecial() . '_Page', $prev_block_params['page']); $o .= $this->Application->ParseBlock($prev_block_params); } } $prev_block_params['name'] = 'page'; $prev_block_params['page'] = $current_page - 1; $prev_block_params['name'] = $this->SelectParam($params, 'prev_page_render_as,block_prev_page,prev_page_block'); if ( $prev_block_params['name'] ) { $this->Application->SetVar($this->getPrefixSpecial() . '_Page', $prev_block_params['page']); $o .= $this->Application->ParseBlock($prev_block_params); } } else { $no_prev_page_block = $this->SelectParam($params, 'no_prev_page_render_as,block_no_prev_page'); if ( $no_prev_page_block ) { $block_params['name'] = $no_prev_page_block; $o .= $this->Application->ParseBlock($block_params); } } $total_records = $list->GetRecordsCount(); $separator_params['name'] = $this->SelectParam($params, 'separator_render_as,block_separator'); for ($i = $split_start; $i <= $split_end; $i++) { $from_record = ($i - 1) * $list->GetPerPage(); $to_record = $from_record + $list->GetPerPage(); if ( $to_record > $total_records ) { $to_record = $total_records; } $block_params['from_record'] = $from_record + 1; $block_params['to_record'] = $to_record; if ( $i == $current_page ) { $block = $this->SelectParam($params, 'current_render_as,active_render_as,block_current,active_block'); } else { $block = $this->SelectParam($params, 'link_render_as,inactive_render_as,block_link,inactive_block'); } $block_params['name'] = $block; $block_params['page'] = $i; $this->Application->SetVar($this->getPrefixSpecial() . '_Page', $block_params['page']); $o .= $this->Application->ParseBlock($block_params); if ( $this->SelectParam($params, 'separator_render_as,block_separator') && $i < $split_end ) { $o .= $this->Application->ParseBlock($separator_params); } } if ( $current_page < $total_pages ) { $next_block_params = $this->prepareTagParams($params); $next_block_params['page'] = $current_page + 1; $next_block_params['name'] = $this->SelectParam($params, 'next_page_render_as,block_next_page,next_page_block'); if ( $next_block_params['name'] ) { $this->Application->SetVar($this->getPrefixSpecial() . '_Page', $next_block_params['page']); $o .= $this->Application->ParseBlock($next_block_params); } if ( $total_pages > $split ) { $next_block_params['page'] = min($current_page + $split, $total_pages); $next_block_params['name'] = $this->SelectParam($params, 'next_page_split_render_as,next_page_split_block'); if ( $next_block_params['name'] ) { $this->Application->SetVar($this->getPrefixSpecial() . '_Page', $next_block_params['page']); $o .= $this->Application->ParseBlock($next_block_params); } } } else { $no_next_page_block = $this->SelectParam($params, 'no_next_page_render_as,block_no_next_page'); if ( $no_next_page_block ) { $block_params['name'] = $no_next_page_block; $o .= $this->Application->ParseBlock($block_params); } } $this->Application->SetVar($this->getPrefixSpecial() . '_Page', $current_page); return $o; } /** * Print grid pagination using * block names specified * * @param Array $params * @return string * @access public */ function PaginationBar($params) { return $this->PrintPages($params); } function PerPageBar($params) { $object =& $this->GetList($params); $ret = ''; $per_pages = explode(';', $params['per_pages']); $block_params = $this->prepareTagParams($params); $block_params['name'] = $params['render_as']; foreach ($per_pages as $per_page) { $block_params['per_page'] = $per_page; $this->Application->SetVar($this->getPrefixSpecial() . '_PerPage', $per_page); $block_params['selected'] = $per_page == $object->GetPerPage(); $ret .= $this->Application->ParseBlock($block_params, 1); } $this->Application->SetVar($this->getPrefixSpecial() . '_PerPage', $object->GetPerPage()); return $ret; } /** * Returns field name (processed by kMultiLanguage formatter * if required) and item's id from it's IDField or field required * * @param Array $params * @return Array (id,field) * @access private */ function prepareInputName($params) { + /** @var kDBItem $object */ $object = $this->getObject($params); - /* @var $object kDBItem */ $field = $this->SelectParam($params, 'name,field'); $formatter_class = $object->GetFieldOption($field, 'formatter'); if ($formatter_class == 'kMultiLanguage') { + /** @var kMultiLanguage $formatter */ $formatter = $this->Application->recallObject($formatter_class); - /* @var $formatter kMultiLanguage */ $force_primary = $object->GetFieldOption($field, 'force_primary'); $field = $formatter->LangFieldName($field, $force_primary); } if (array_key_exists('force_id', $params)) { $id = $params['force_id']; } else { $id_field = array_key_exists('IdField', $params) ? $params['IdField'] : false; $id = $id_field ? $object->GetDBField($id_field) : $object->GetID(); } return Array($id, $field); } /** * Returns input field name to * be placed on form (for correct * event processing) * * @param Array $params * @return string * @access public */ function InputName($params) { list($id, $field) = $this->prepareInputName($params); $ret = $this->getPrefixSpecial().'['.$id.']['.$field.']'; if (array_key_exists('as_preg', $params) && $params['as_preg']) { $ret = preg_quote($ret, '/'); } return $ret; } /** * Allows to override various field options through hidden fields with specific names in submit. * This tag generates this special names * * @param Array $params * @return string * @author Alex */ function FieldModifier($params) { list($id, $field) = $this->prepareInputName($params); $ret = 'field_modifiers['.$this->getPrefixSpecial().']['.$field.']['.$params['type'].']'; if (array_key_exists('as_preg', $params) && $params['as_preg']) { $ret = preg_quote($ret, '/'); } if (isset($params['value'])) { $object = $this->getObject($params); $field_modifiers[$field][$params['type']] = $params['value']; $object->ApplyFieldModifiers($field_modifiers); } return $ret; } /** * Returns index where 1st changeable sorting field begins * * @return int * @access private */ function getUserSortIndex() { $list_sortings = $this->getUnitConfig()->getListSortingsBySpecial($this, Array ()); $user_sorting_start = 0; $forced_sorting = getArrayValue($list_sortings, 'ForcedSorting'); return $forced_sorting ? count($forced_sorting) : $user_sorting_start; } /** * Returns order direction for given field * * * * @param Array $params * @return string * @access public */ function Order($params) { $field = $params['field']; $user_sorting_start = $this->getUserSortIndex(); $list =& $this->GetList($params); if ($list->GetOrderField($user_sorting_start) == $field) { return strtolower($list->GetOrderDirection($user_sorting_start)); } elseif($this->Application->ConfigValue('UseDoubleSorting') && $list->GetOrderField($user_sorting_start+1) == $field) { return '2_'.strtolower($list->GetOrderDirection($user_sorting_start+1)); } else { return 'no'; } } /** * Detects, that current sorting is not default * * @param Array $params * @return bool */ function OrderChanged($params) { $list =& $this->GetList($params); + /** @var ListHelper $list_helper */ $list_helper = $this->Application->recallObject('ListHelper'); - /* @var $list_helper ListHelper */ return $list_helper->hasUserSorting($list); } /** * Gets information of sorting field at "pos" position, * like sorting field name (type="field") or sorting direction (type="direction") * * @param Array $params * @return string * @access protected */ protected function OrderInfo($params) { $user_sorting_start = $this->getUserSortIndex() + --$params['pos']; $list =& $this->GetList($params); if ( $params['type'] == 'field' ) { return $list->GetOrderField($user_sorting_start); } if ( $params['type'] == 'direction' ) { return $list->GetOrderDirection($user_sorting_start); } return ''; } /** * Checks if sorting field/direction matches passed field/direction parameter * * @param Array $params * @return bool * @access protected */ protected function IsOrder($params) { $params['type'] = isset($params['field']) ? 'field' : 'direction'; $value = $this->OrderInfo($params); if ( isset($params['field']) ) { return $params['field'] == $value; } elseif ( isset($params['direction']) ) { return $params['direction'] == $value; } return false; } /** * Returns list per-page * * @param Array $params * @return int */ function PerPage($params) { $object =& $this->GetList($params); return $object->GetPerPage(); } /** * Checks if list perpage matches value specified * * @param Array $params * @return bool */ function PerPageEquals($params) { $object =& $this->GetList($params); return $object->GetPerPage() == $params['value']; } function SaveEvent($params) { // SaveEvent is set during OnItemBuild, but we may need it before any other tag calls OnItemBuild $object = $this->getObject($params); return $this->Application->GetVar($this->getPrefixSpecial().'_SaveEvent'); } function NextId($params) { $object = $this->getObject($params); $wid = $this->Application->GetTopmostWid($this->Prefix); $session_name = rtrim($this->getPrefixSpecial().'_selected_ids_'.$wid, '_'); $ids = explode(',', $this->Application->RecallVar($session_name)); $cur_id = $object->GetID(); $i = array_search($cur_id, $ids); if ($i !== false) { return $i < count($ids) - 1 ? $ids[$i + 1] : ''; } return ''; } function PrevId($params) { $object = $this->getObject($params); $wid = $this->Application->GetTopmostWid($this->Prefix); $session_name = rtrim($this->getPrefixSpecial().'_selected_ids_'.$wid, '_'); $ids = explode(',', $this->Application->RecallVar($session_name)); $cur_id = $object->GetID(); $i = array_search($cur_id, $ids); if ($i !== false) { return $i > 0 ? $ids[$i - 1] : ''; } return ''; } function IsSingle($params) { return ($this->NextId($params) === '' && $this->PrevId($params) === ''); } function IsLast($params) { return ($this->NextId($params) === ''); } function IsFirst($params) { return ($this->PrevId($params) === ''); } /** * Checks if field value is equal to proposed one * * @param Array $params * @return bool * @deprecated */ function FieldEquals($params) { + /** @var kDBItem $object */ $object = $this->getObject($params); - /* @var $object kDBItem */ return $object->GetDBField( $this->SelectParam($params, 'name,field') ) == $params['value']; } /** * Checks, that grid has icons defined and they should be shown * * @param Array $params * @return bool */ function UseItemIcons($params) { return array_key_exists('Icons', $this->getUnitConfig()->getGridByName($params['grid'])); } /** * Returns corresponding to grid layout selector column width * * @param Array $params * @return int */ function GridSelectorColumnWidth($params) { $width = 0; if ($params['selector']) { $width += $params['selector_width']; } if ($this->UseItemIcons($params)) { $width += $params['icon_width']; } return $width; } /** * Returns grids item selection mode (checkbox, radio, ) * * @param Array $params * @return string */ function GridSelector($params) { $grid = $this->getUnitConfig()->getGridByName($params['grid']); return array_key_exists('Selector', $grid) ? $grid['Selector'] : $params['default']; } function ItemIcon($params) { $config = $this->getUnitConfig(); $grid = $config->getGridByName($params['grid']); if ( !isset($grid['Icons']) ) { return ''; } $icons = $grid['Icons']; if ( isset($params['name']) ) { $icon_name = $params['name']; return isset($icons[$icon_name]) ? $icons[$icon_name] : ''; } $status_fields = $config->getStatusField(false, Array ()); if ( !$status_fields ) { return $icons['default']; } + /** @var kDBList $object */ $object = $this->getObject($params); - /* @var $object kDBList */ $icon = ''; foreach ($status_fields as $status_field) { $icon .= $object->GetDBField($status_field) . '_'; } $icon = rtrim($icon, '_'); return isset($icons[$icon]) ? $icons[$icon] : $icons['default']; } /** * Generates bluebar title + initializes prefixes used on page * * @param Array $params * @return string */ function SectionTitle($params) { $config = $this->getUnitConfig(); $preset_name = kUtil::replaceModuleSection($params['title_preset']); $title_info = $config->getTitlePresetByName($preset_name); if ( $title_info === false ) { $title = str_replace('#preset_name#', $preset_name, $params['title']); if ( $this->Application->ConfigValue('UseSmallHeader') && isset($params['group_title']) && $params['group_title'] ) { $title .= ' - ' . $params['group_title']; } return $title; } $default_title_preset = $config->getTitlePresetByName('default'); if ( $default_title_preset ) { // use default labels + custom labels specified in preset used $title_info = kUtil::array_merge_recursive($default_title_preset, $title_info); } $title = $title_info['format']; // 1. get objects in use for title construction $objects = Array (); $object_status = Array (); $status_labels = Array (); $prefixes = array_key_exists('prefixes', $title_info) ? $title_info['prefixes'] : false; $all_tag_params = array_key_exists('tag_params', $title_info) ? $title_info['tag_params'] : false; - /* @var $prefixes Array */ + /** @var Array $prefixes */ if ( $prefixes ) { // extract tag_params passed directly to SectionTitle tag for specific prefix foreach ($params as $tp_name => $tp_value) { if ( preg_match('/(.*)\[(.*)\]/', $tp_name, $regs) ) { $all_tag_params[$regs[1]][$regs[2]] = $tp_value; unset($params[$tp_name]); } } $tag_params = Array (); foreach ($prefixes as $prefix_special) { $prefix_data = $this->Application->processPrefix($prefix_special); $prefix_data['prefix_special'] = rtrim($prefix_data['prefix_special'], '.'); if ( $all_tag_params ) { $tag_params = getArrayValue($all_tag_params, $prefix_data['prefix_special']); if ( !$tag_params ) { $tag_params = Array (); } } $tag_params = array_merge($params, $tag_params); $objects[$prefix_data['prefix_special']] = $this->Application->recallObject($prefix_data['prefix_special'], $prefix_data['prefix'], $tag_params); $object_status[$prefix_data['prefix_special']] = $objects[$prefix_data['prefix_special']]->IsNewItem() ? 'new' : 'edit'; // a. set object's status field (adding item/editing item) for each object in title if ( getArrayValue($title_info[$object_status[$prefix_data['prefix_special']] . '_status_labels'], $prefix_data['prefix_special']) ) { $status_labels[$prefix_data['prefix_special']] = $title_info[$object_status[$prefix_data['prefix_special']] . '_status_labels'][$prefix_data['prefix_special']]; $title = str_replace('#' . $prefix_data['prefix_special'] . '_status#', $status_labels[$prefix_data['prefix_special']], $title); } // b. setting object's titlefield value (in titlebar ONLY) to default in case if object beeing created with no titlefield filled in if ( $object_status[$prefix_data['prefix_special']] == 'new' ) { $new_value = $this->getInfo($objects[$prefix_data['prefix_special']], 'titlefield'); if ( !$new_value && getArrayValue($title_info['new_titlefield'], $prefix_data['prefix_special']) ) { $new_value = $this->Application->Phrase($title_info['new_titlefield'][$prefix_data['prefix_special']]); } $title = str_replace('#' . $prefix_data['prefix_special'] . '_titlefield#', $new_value, $title); } } } // replace to section title $section = array_key_exists('section', $params) ? $params['section'] : false; if ( $section ) { + /** @var kSectionsHelper $sections_helper */ $sections_helper = $this->Application->recallObject('SectionsHelper'); - /* @var $sections_helper kSectionsHelper */ $section_data =& $sections_helper->getSectionData($section); $title = str_replace('#section_label#', '!' . $section_data['label'] . '!', $title); } // 2. replace phrases if any found in format string $title = $this->Application->ReplaceLanguageTags($title, false); // 3. find and replace any replacement vars preg_match_all('/#(.*_.*)#/Uis', $title, $rets); if ( $rets[1] ) { $replacement_vars = array_keys(array_flip($rets[1])); foreach ($replacement_vars as $replacement_var) { $var_info = explode('_', $replacement_var, 2); $object =& $objects[$var_info[0]]; $new_value = $this->getInfo($object, $var_info[1]); $title = str_replace('#' . $replacement_var . '#', $new_value, $title); } } // replace trailing spaces inside title preset + '' occurrences into single space $title = preg_replace('/[ ]*\'\'[ ]*/', ' ', $title); if ( $this->Application->ConfigValue('UseSmallHeader') && isset($params['group_title']) && $params['group_title'] ) { $title .= ' - ' . $params['group_title']; } $first_chars = $this->SelectParam($params, 'first_chars,cut_first'); if ( $first_chars && !preg_match('/(.*)<\/a>/', $title) ) { // don't cut titles, that contain phrase translation links $stripped_title = strip_tags($title, $this->SelectParam($params, 'allowed_tags')); if ( mb_strlen($stripped_title) > $first_chars ) { $title = mb_substr($stripped_title, 0, $first_chars) . ' ...'; } } return $title; } /** * Returns information about list * * @param kDBList $object * @param string $info_type * @return string * @access protected */ protected function getInfo(&$object, $info_type) { switch ( $info_type ) { case 'titlefield': $field = $object->getUnitConfig()->getTitleField(); return $field !== false ? $object->GetField($field) : 'TitleField Missing'; break; case 'recordcount': if ( $object->GetRecordsCount(false) != $object->GetRecordsCount() ) { $of_phrase = $this->Application->Phrase('lc_of'); return $object->GetRecordsCount() . ' ' . $of_phrase . ' ' . $object->GetRecordsCount(false); } return $object->GetRecordsCount(); break; } return $object->GetField($info_type); } function GridInfo($params) { + /** @var kDBList $object */ $object =& $this->GetList($params); - /* @var $object kDBList */ switch ( $params['type'] ) { case 'filtered': return $object->GetRecordsCount(); case 'total': return $object->GetRecordsCount(false); case 'selected': return $object->GetSelectedCount(); case 'from': return $object->GetRecordsCount() ? $object->GetOffset() + 1 : 0; //0-based case 'to': $record_count = $object->GetRecordsCount(); return $object->GetPerPage(true) != -1 ? min($object->GetOffset() + $object->GetPerPage(), $record_count) : $record_count; case 'total_pages': return $object->GetTotalPages(); case 'needs_pagination': return ($object->GetPerPage(true) != -1) && (($object->GetRecordsCount() > $object->GetPerPage()) || ($object->GetPage() > 1)); } return false; } /** * Parses block depending on its element type. * For radio and select elements values are taken from 'value_list_field' in key1=value1,key2=value2 * format. key=value can be substituted by SELECT f1 AS OptionName, f2 AS OptionValue... FROM TableName * where prefix is TABLE_PREFIX * * @param Array $params * @return string */ function ConfigFormElement($params) { + /** @var kDBItem $object */ $object = $this->getObject($params); - /* @var $object kDBItem */ $field = $params['field']; + /** @var InpCustomFieldsHelper $helper */ $helper = $this->Application->recallObject('InpCustomFieldsHelper'); - /* @var $helper InpCustomFieldsHelper */ $element_type = $object->GetDBField($params['element_type_field']); if ($element_type == 'label') { $element_type = 'text'; } $formatter_class = $object->GetFieldOption($field, 'formatter'); switch ($element_type) { case 'select': case 'multiselect': case 'radio': if ($object->GetDBField('DirectOptions')) { // used for custom fields $options = $object->GetDBField('DirectOptions'); } else { // used for configuration $options = $helper->GetValuesHash( $object->GetDBField($params['value_list_field']) ); } $object->SetFieldOption($field, 'formatter', 'kOptionsFormatter'); $object->SetFieldOption($field, 'options', $options); break; case 'text': case 'textarea': case 'upload': $params['field_params'] = $helper->ParseConfigSQL($object->GetDBField($params['value_list_field'])); break; case 'password': case 'checkbox': default: break; } if (!$element_type) { throw new Exception('Element type missing for "' . $object->GetDBField('VariableName') . '" configuration variable'); } $params['name'] = $params['blocks_prefix'] . $element_type; // use $pass_params to pass 'SourcePrefix' parameter from PrintList to CustomInputName tag $ret = $this->Application->ParseBlock($params, 1); $object->SetFieldOption($field, 'formatter', $formatter_class); return $ret; } /** * Get's requested custom field value * * @param Array $params * @return string * @access public */ function CustomField($params) { $params['name'] = 'cust_'.$this->SelectParam($params, 'name,field'); return $this->Field($params); } function CustomFieldLabel($params) { $object = $this->getObject($params); $field = $this->SelectParam($params, 'name,field'); $sql = 'SELECT FieldLabel FROM ' . $this->Application->getUnitConfig('cf')->getTableName() . ' WHERE FieldName = ' . $this->Conn->qstr($field); return $this->Application->Phrase($this->Conn->GetOne($sql)); } /** * transposes 1-dimensional array elements for vertical alignment according to given columns and per_page parameters * * @param array $arr * @param int $columns * @param int $per_page * @return array */ function LinearToVertical(&$arr, $columns, $per_page) { $rows = $columns; // in case if after applying per_page limit record count less then // can fill requrested column count, then fill as much as we can $cols = min(ceil($per_page / $columns), ceil(count($arr) / $columns)); $imatrix = array(); for ($row = 0; $row < $rows; $row++) { for ($col = 0; $col < $cols; $col++) { $source_index = $row * $cols + $col; if (!isset($arr[$source_index])) { // in case if source array element count is less then element count in one row continue; } $imatrix[$col * $rows + $row] = $arr[$source_index]; } } ksort($imatrix); return array_values($imatrix); } /** * If data was modified & is in TempTables mode, then parse block with name passed; * remove modification mark if not in TempTables mode * * @param Array $params * @return string * @access protected */ protected function SaveWarning($params) { $main_prefix = array_key_exists('main_prefix', $params) ? $params['main_prefix'] : false; if ( $main_prefix ) { $top_prefix = $main_prefix; } else { $top_prefix = $this->Application->GetTopmostPrefix($this->Prefix); } $temp_tables = substr($this->Application->GetVar($top_prefix . '_mode'), 0, 1) == 't'; $modified = $this->Application->RecallVar($top_prefix . '_modified'); if ( $temp_tables && $modified ) { $block_params = $this->prepareTagParams($params); $block_params['name'] = $this->SelectParam($params, 'render_as,name'); $block_params['edit_mode'] = $temp_tables ? 1 : 0; return $this->Application->ParseBlock($block_params); } $this->Application->RemoveVar($top_prefix . '_modified'); return ''; } /** * Returns list record count queries (on all pages) * * @param Array $params * @return int */ function TotalRecords($params) { $list =& $this->GetList($params); return $list->GetRecordsCount(); } /** * Returns selected records count. * * @param array $params Tag params. * * @return string */ protected function SelectedRecords(array $params) { $list =& $this->GetList($params); return $list->GetSelectedCount(); } /** * Range filter field name * * @param Array $params * @return string */ function SearchInputName($params) { $field = $this->SelectParam($params, 'field,name'); $ret = 'custom_filters['.$this->getPrefixSpecial().']['.$params['grid'].']['.$field.']['.$params['filter_type'].']'; if (isset($params['type'])) { $ret .= '['.$params['type'].']'; } if (array_key_exists('as_preg', $params) && $params['as_preg']) { $ret = preg_quote($ret, '/'); } return $ret; } /** * Return range filter field value * * @param Array $params * @return string * @access protected */ protected function SearchField($params) // RangeValue { $field = $this->SelectParam($params, 'field,name'); $view_name = $this->Application->RecallVar($this->getPrefixSpecial() . '_current_view'); $custom_filter = $this->Application->RecallPersistentVar($this->getPrefixSpecial() . '_custom_filter.' . $view_name /*, ALLOW_DEFAULT_SETTINGS*/); $custom_filter = $custom_filter ? unserialize($custom_filter) : Array (); if ( isset($custom_filter[$params['grid']][$field]) ) { $ret = $custom_filter[$params['grid']][$field][$params['filter_type']]['submit_value']; if ( isset($params['type']) ) { $ret = $ret[$params['type']]; } if ( array_key_exists('formatted', $params) && $params['formatted'] ) { $object =& $this->GetList($params); $formatter_class = $object->GetFieldOption($field, 'formatter'); if ( $formatter_class ) { + /** @var kFormatter $formatter */ $formatter = $this->Application->recallObject($formatter_class); - /* @var $formatter kFormatter */ $ret = $formatter->Format($ret, $field, $object); } } if ( !array_key_exists('no_special', $params) || !$params['no_special'] ) { $ret = kUtil::escape($ret); } return $ret; } return ''; } /** * Tells, that at least one of search filters is used by now * * @param Array $params * @return bool */ function SearchActive($params) { if ($this->Application->RecallVar($this->getPrefixSpecial() . '_search_keyword')) { // simple search filter is used return true; } $view_name = $this->Application->RecallVar($this->getPrefixSpecial().'_current_view'); $custom_filter = $this->Application->RecallPersistentVar($this->getPrefixSpecial().'_custom_filter.'.$view_name/*, ALLOW_DEFAULT_SETTINGS*/); $custom_filter = $custom_filter ? unserialize($custom_filter) : Array(); return array_key_exists($params['grid'], $custom_filter); } function SearchFormat($params) { $object =& $this->GetList($params); return $this->Format($params, $object); } /** * Returns error of range field * * @param Array $params * @return string * @access protected */ protected function SearchError($params) { $field = $this->SelectParam($params, 'field,name'); $error_var_name = $this->getPrefixSpecial() . '_' . $field . '_error'; $pseudo = $this->Application->RecallVar($error_var_name); if ( $pseudo ) { $this->Application->RemoveVar($error_var_name); } + /** @var kDBItem $object */ $object = $this->Application->recallObject($this->Prefix . '.' . $this->Special . '-item', null, Array ('skip_autoload' => true)); - /* @var $object kDBItem */ $object->SetError($field, $pseudo); return $object->GetErrorMsg($field, false); } /** * Returns object used in tag processor * * @param Array $params * @access public * @return kDBItem|kDBList */ function getObject($params = Array()) { + /** @var kDBItem $object */ $object = $this->Application->recallObject($this->getPrefixSpecial(), $this->Prefix, $params); - /* @var $object kDBItem */ if ( isset($params['requery']) && $params['requery'] ) { $this->Application->HandleEvent(new kEvent($this->getPrefixSpecial() . ':LoadItem', $params)); } return $object; } /** * Checks if object propery value matches value passed * * @param Array $params * @return bool */ function PropertyEquals($params) { $object = $this->getObject($params); $property_name = $this->SelectParam($params, 'name,var,property'); return $object->$property_name == $params['value']; } function DisplayOriginal($params) { return false; } /*function MultipleEditing($params) { $wid = $this->Application->GetTopmostWid($this->Prefix); $session_name = rtrim($this->getPrefixSpecial().'_selected_ids_'.$wid, '_'); $selected_ids = explode(',', $this->Application->RecallVar($session_name)); $ret = ''; if ($selected_ids) { $selected_ids = explode(',', $selected_ids); $object = $this->getObject( kUtil::array_merge_recursive($params, Array('skip_autoload' => true)) ); $params['name'] = $params['render_as']; foreach ($selected_ids as $id) { $object->Load($id); $ret .= $this->Application->ParseBlock($params); } } return $ret; }*/ /** * Returns import/export process percent * * @param Array $params * @return int * @deprecated Please convert to event-model, not tag based */ function ExportStatus($params) { + /** @var kCatDBItemExportHelper $export_object */ $export_object = $this->Application->recallObject('CatItemExportHelper'); - /* @var $export_object kCatDBItemExportHelper */ $event = new kEvent($this->getPrefixSpecial().':OnDummy'); $action_method = 'perform'.ucfirst($this->Special); $field_values = $export_object->$action_method($event); // finish code is done from JS now if ($field_values['start_from'] >= $field_values['total_records']) { if ($this->Special == 'import') { // this is used? $this->Application->StoreVar('PermCache_UpdateRequired', 1); $this->Application->Redirect('categories/cache_updater', Array('m_opener' => 'r', 'pass' => 'm', 'continue' => 1)); } elseif ($this->Special == 'export') { // used for orders export in In-Commerce $finish_t = $this->Application->RecallVar('export_finish_t'); $this->Application->Redirect($finish_t, Array('pass' => 'all')); $this->Application->RemoveVar('export_finish_t'); } } $export_options = $export_object->loadOptions($event); return $export_options['start_from'] * 100 / $export_options['total_records']; } /** * Returns path where exported category items should be saved * * @param Array $params * @return string * @access protected */ protected function ExportPath($params) { $export_options = unserialize($this->Application->RecallVar($this->getPrefixSpecial() . '_options')); $extension = $export_options['ExportFormat'] == 1 ? 'csv' : 'xml'; $filename = preg_replace('/(.*)\.' . $extension . '$/', '\1', $export_options['ExportFilename']) . '.' . $extension; $path = EXPORT_PATH . '/'; if ( array_key_exists('as_url', $params) && $params['as_url'] ) { $path = str_replace(FULL_PATH . '/', $this->Application->BaseURL(), $path); } return $path . $filename; } function FieldTotal($params) { $list =& $this->GetList($params); $field = $this->SelectParam($params, 'field,name'); $total_function = array_key_exists('function', $params) ? $params['function'] : $list->getTotalFunction($field); if (array_key_exists('function_only', $params) && $params['function_only']) { return $total_function; } if (array_key_exists('currency', $params) && $params['currency']) { $iso = $this->GetISO($params['currency']); $original = $list->getTotal($field, $total_function); $value = $this->ConvertCurrency($original, $iso); $list->setTotal($field, $total_function, $value); } $value = $list->GetFormattedTotal($field, $total_function); if (array_key_exists('currency', $params) && $params['currency']) { $value = $this->AddCurrencySymbol($value, $iso); } return $value; } function FCKEditor($params) { $editor_name = array_key_exists('name', $params) ? $params['name'] : $this->InputName($params); + /** @var fckFCKHelper $fck_helper */ $fck_helper = $this->Application->recallObject('FCKHelper'); - /* @var $fck_helper fckFCKHelper */ if ( isset($params['mode']) && $params['mode'] == 'inline' ) { return $fck_helper->CKEditorInlineTag($editor_name, $params); } return $fck_helper->CKEditorTag($editor_name, $this->CKEditorValue($params), $params); } /** * Returns value, used by FCKEditor tag * * @param array $params * * @return string */ protected function CKEditorValue($params) { $params['no_special'] = 1; $params['format'] = array_key_exists('format', $params) ? $params['format'] . ';fck_ready' : 'fck_ready'; return $this->Field($params); } function IsNewItem($params) { $object = $this->getObject($params); return $object->IsNewItem(); } /** * Creates link to an item including only it's id * * @param Array $params * @return string * @access protected */ protected function ItemLink($params) { + /** @var kDBItem $object */ $object = $this->getObject($params); - /* @var $object kDBItem */ if ( !isset($params['pass']) ) { $params['pass'] = 'm'; } $params[ $object->getPrefixSpecial() . '_id' ] = $object->GetID(); return $this->Application->ProcessParsedTag('m', 'T', $params); } /** * Creates a button for editing item in Admin Console * * @param Array $params * @return string * @access protected * @throws InvalidArgumentException */ protected function AdminEditButton($params) { if ( EDITING_MODE != EDITING_MODE_CONTENT ) { return ''; } + /** @var kDBItem $object */ $object = $this->getObject($params); - /* @var $object kDBItem */ $item_prefix = isset($params['item_prefix']) ? $params['item_prefix'] : $this->Prefix; if ( isset($params['template']) ) { $template = $params['template']; } else { $item_config = $this->Application->getUnitConfig($item_prefix); $admin_template_prefix = $item_config->getAdminTemplatePrefix(); $template = $item_config->getAdminTemplatePath() . '/' . $admin_template_prefix . 'edit'; if ( !$admin_template_prefix ) { throw new InvalidArgumentException('Automatic admin editing template detection failed because of missing "AdminTemplatePrefix" unit config option in "' . $this->Prefix . '" unit config'); } } $form_name = 'kf_' . str_replace('-', '_', $item_prefix) . '_' . $object->GetID(); $form_name .= '_' . kUtil::crc32(json_encode($params)); $button_icon = isset($params['button_icon']) ? $params['button_icon'] : 'content_mode.png'; $button_class = isset($params['button_class']) ? $params['button_class'] : 'admin-edit-btn'; $button_title = isset($params['button_title']) ? $params['button_title'] : 'la_btn_AdminEditItem'; if ( substr($button_title, 0, 1) == '+' ) { $button_title = substr($button_title, 1); } else { $button_title = $this->Application->Phrase($button_title, false, true); } $icon_url = $this->Application->BaseURL() . 'core/admin_templates/img/top_frame/icons/' . $button_icon; $button_onclick = '$form_name = ' . json_encode($form_name) . '; std_edit_item(' . json_encode($item_prefix) . ', ' . json_encode($template) . '); return false;'; $button_code = ''; if ( !isset($params['pass']) ) { $params['pass'] = 'm,' . $item_prefix; } $params['m_opener'] = 'd'; $params[$item_prefix . '_id'] = $object->GetID(); if ( !isset($params['temp_mode']) || (isset($params['temp_mode']) && $params['temp_mode']) ) { $params[$item_prefix . '_mode'] = 't'; $params[$item_prefix . '_event'] = 'OnEdit'; } $params['front'] = 1; // to make opener stack work properly $params['__NO_REWRITE__'] = 1; // since admin link unset($params['button_icon'], $params['button_class'], $params['button_title'], $params['template'], $params['item_prefix'], $params['temp_mode']); // link from Front-End to Admin, don't remove "index.php" $form_name_escaped = kUtil::escape($form_name, kUtil::ESCAPE_HTML); $edit_url = kUtil::escape($this->Application->HREF($template, ADMIN_DIRECTORY, $params, 'index.php'), kUtil::ESCAPE_HTML); $edit_form = '
'; if ( isset($params['forms_later']) && $params['forms_later'] ) { $all_forms = $this->Application->GetVar('all_forms'); $this->Application->SetVar('all_forms', $all_forms . $edit_form); } else { $button_code .= $edit_form; } return $button_code; } /** * Calls OnNew event from template, when no other event submitted * * @param Array $params */ function PresetFormFields($params) { $prefix = $this->getPrefixSpecial(); if ( !$this->Application->GetVar($prefix . '_event') ) { $this->Application->HandleEvent(new kEvent($prefix . ':OnNew')); } } function PrintSerializedFields($params) { + /** @var kDBItem $object */ $object = $this->getObject($params); - /* @var $object kDBItem */ $field = $this->SelectParam($params, 'field'); $data = unserialize($object->GetDBField($field)); $o = ''; $std_params['name'] = $params['render_as']; $std_params['field'] = $params['field']; $std_params['pass_params'] = true; foreach ($data as $key => $row) { $block_params = array_merge($std_params, $row, array('key'=>$key)); $o .= $this->Application->ParseBlock($block_params); } return $o; } /** * Checks if current prefix is main item * * @param Array $params * @return bool */ function IsTopmostPrefix($params) { return $this->Prefix == $this->Application->GetTopmostPrefix($this->Prefix); } function PermSection($params) { $section = $this->SelectParam($params, 'section,name'); return $this->getUnitConfig()->getPermSectionByName($section, ''); } function PerPageSelected($params) { $list =& $this->GetList($params); return $list->GetPerPage(true) == $params['per_page'] ? $params['selected'] : ''; } /** * Returns prefix + generated sepcial + any word * * @param Array $params * @return string */ function VarName($params) { $list =& $this->GetList($params); return $list->getPrefixSpecial() . '_' . $params['type']; } /** * Returns edit tabs by specified preset name or false in case of error * * @param string $preset_name * @return mixed */ function getEditTabs($preset_name) { $presets = $this->getUnitConfig()->getEditTabPresets(); if ( !$presets || !isset($presets[$preset_name]) || count($presets[$preset_name]) == 0 ) { return false; } return count($presets[$preset_name]) > 1 ? $presets[$preset_name] : false; } /** * Detects if specified preset has tabs in it * * @param Array $params * @return bool */ function HasEditTabs($params) { return $this->getEditTabs($params['preset_name']) ? true : false; } /** * Sorts edit tabs based on their priority * * @param Array $tab_a * @param Array $tab_b * @return int */ function sortEditTabs($tab_a, $tab_b) { if ($tab_a['priority'] == $tab_b['priority']) { return 0; } return $tab_a['priority'] < $tab_b['priority'] ? -1 : 1; } /** * Prints edit tabs based on preset name specified * * @param Array $params * @return string * @access protected */ protected function PrintEditTabs($params) { $edit_tabs = $this->getEditTabs($params['preset_name']); if ( !$edit_tabs ) { return ''; } usort($edit_tabs, Array (&$this, 'sortEditTabs')); $ret = ''; $block_params = $this->prepareTagParams($params); $block_params['name'] = $params['render_as']; foreach ($edit_tabs as $tab_info) { $block_params['title'] = $tab_info['title']; $block_params['template'] = $tab_info['t']; $ret .= $this->Application->ParseBlock($block_params); } return $ret; } /** * Performs image resize to required dimensions and returns resulting url (cached resized image) * * @param Array $params * @return string */ function ImageSrc($params) { $max_width = isset($params['MaxWidth']) ? $params['MaxWidth'] : false; $max_height = isset($params['MaxHeight']) ? $params['MaxHeight'] : false; $logo_filename = isset($params['LogoFilename']) ? $params['LogoFilename'] : false; $logo_h_margin = isset($params['LogoHMargin']) ? $params['LogoHMargin'] : false; $logo_v_margin = isset($params['LogoVMargin']) ? $params['LogoVMargin'] : false; $object = $this->getObject($params); $field = $this->SelectParam($params, 'name,field'); return $object->GetField($field, 'resize:'.$max_width.'x'.$max_height.';wm:'.$logo_filename.'|'.$logo_h_margin.'|'.$logo_v_margin); } /** * Allows to retrieve given setting from unit config * * @param Array $params * @return mixed */ function UnitOption($params) { return $this->getUnitConfig()->getSetting($params['name']); } /** * Returns list of allowed toolbar buttons or false, when all is allowed * * @param Array $params * @return string */ function VisibleToolbarButtons($params) { $preset_name = kUtil::replaceModuleSection($params['title_preset']); $preset_info = $this->getUnitConfig()->getTitlePresetByName($preset_name); if ( !$preset_info ) { trigger_error('Title preset not specified or missing (in tag "' . $this->getPrefixSpecial() . ':' . __METHOD__ . '")', E_USER_NOTICE); return false; } if ( !array_key_exists('toolbar_buttons', $preset_info) || !is_array($preset_info['toolbar_buttons']) ) { return false; } // always add search buttons array_push($preset_info['toolbar_buttons'], 'search', 'search_reset_alt'); $toolbar_buttons = array_values($preset_info['toolbar_buttons']); // reset index return $toolbar_buttons ? trim(json_encode($toolbar_buttons), '[]') : 'false'; } /** * Checks, that "To" part of at least one of range filters is used * * @param Array $params * @return bool */ function RangeFiltersUsed($params) { + /** @var kSearchHelper $search_helper */ $search_helper = $this->Application->recallObject('SearchHelper'); - /* @var $search_helper kSearchHelper */ return $search_helper->rangeFiltersUsed($this->getPrefixSpecial(), $params['grid']); } /** * This is abstract tag, used to modify unit config data based on template, where it's used. * Tag is called from "combined_header" block in admin only. * * @param Array $params */ function ModifyUnitConfig($params) { } /** * Checks, that field is visible on edit form * * @param Array $params * @return bool */ function FieldVisible($params) { $check_field = $params['field']; $field_options = $this->_getFieldDefinition($check_field); if ( !$field_options ) { $params['field'] = 'Password'; return $check_field == 'VerifyPassword' ? $this->FieldVisible($params) : true; } $show_mode = array_key_exists('show_mode', $field_options) ? $field_options['show_mode'] : true; if ( $show_mode === smDEBUG ) { return defined('DEBUG_MODE') && DEBUG_MODE; } return $show_mode; } /** * Checks, that there area visible fields in given section on edit form * * @param Array $params * @return bool */ function FieldsVisible($params) { if ( !$params['fields'] ) { return true; } $check_fields = explode(',', $params['fields']); foreach ($check_fields as $check_field) { // when at least one field in subsection is visible, then subsection is visible too $field_options = $this->_getFieldDefinition($check_field); if ( $field_options ) { $show_mode = array_key_exists('show_mode', $field_options) ? $field_options['show_mode'] : true; } else { $show_mode = true; } if ( ($show_mode === true) || (($show_mode === smDEBUG) && (defined('DEBUG_MODE') && DEBUG_MODE)) ) { // field is visible return true; } } return false; } /** * Returns field definition * * @param string $field_name * @return Array * @access protected */ protected function _getFieldDefinition($field_name) { $config = $this->getUnitConfig(); $ret = $config->getFieldByName($field_name); if ( !$ret ) { $ret = $config->getVirtualFieldByName($field_name); } return $ret; } /** * Checks, that requested option is checked inside field value. * * @param array $params Tag params. * * @return boolean */ protected function Selected(array $params) { /** @var kDBItem $object */ $object = $this->getObject($params); $field = $this->SelectParam($params, 'name,field'); $value = $object->GetDBField($field); if ( strpos($value, '|') !== false ) { $selected_values = explode('|', substr($value, 1, -1)); } else { $selected_values = array((string)$value); } return in_array((string)$params['value'], $selected_values, true); } /** * Displays option name by it's value * * @param Array $params * @return string * @access protected */ protected function OptionValue($params) { + /** @var kDBItem $object */ $object = $this->getObject($params); - /* @var $object kDBItem */ $value = $params['value']; $field = $this->SelectParam($params, 'name,field'); $field_options = $object->GetFieldOptions($field); if ( isset($field_options['options'][$value]) ) { $value = $field_options['options'][$value]; $use_phrases = isset($field_options['use_phrases']) ? $field_options['use_phrases'] : false; return $use_phrases ? $this->Application->Phrase($value) : $value; } return ''; } /** * Returns/sets form name for current object * * @param Array $params * @return string */ function FormName($params) { $form_name = $this->SelectParam($params, 'name,form,form_name'); if ( $form_name ) { $prefix = $this->getPrefixSpecial(); if ( $this->Application->hasObject( $this->getPrefixSpecial() ) ) { + /** @var kDBItem $object */ $object = $this->getObject($params); - /* @var $object kDBItem */ if ( $object->getFormName() != $form_name ) { trigger_error('Setting form to "' . $form_name . '" failed, since object "' . $this->getPrefixSpecial() . '" is created before FormName tag (e.g. in event or another tag).', E_USER_WARNING); } } else { $forms = $this->Application->GetVar('forms', Array ()); $forms[ $this->getPrefixSpecial() ] = $form_name; $this->Application->SetVar('forms', $forms); } return ''; } + /** @var kDBItem $object */ $object = $this->getObject($params); - /* @var $object kDBItem */ return $object->getFormName(); } /** * Just reloads the object using given parameters * * @param Array $params * @return string * @access protected */ protected function ReloadItem($params) { $params['requery'] = 1; + /** @var kDBItem $object */ $object = $this->getObject($params); - /* @var $object kDBItem */ return ''; } } Index: branches/5.3.x/core/kernel/db/cat_tag_processor.php =================================================================== --- branches/5.3.x/core/kernel/db/cat_tag_processor.php (revision 16518) +++ branches/5.3.x/core/kernel/db/cat_tag_processor.php (revision 16519) @@ -1,968 +1,969 @@ PermHelper = $this->Application->recallObject('PermissionsHelper'); } function ItemIcon($params) { $config = $this->getUnitConfig(); $grid = $config->getGridByName($params['grid']); if ( !array_key_exists('Icons', $grid) ) { return ''; } $icons = $grid['Icons']; if ( array_key_exists('name', $params) ) { $icon_name = $params['name']; return array_key_exists($icon_name, $icons) ? $icons[$icon_name] : ''; } $status_field = $config->getStatusField(true); if ( !$status_field ) { return $icons['default']; } + /** @var kDBList $object */ $object = $this->getObject($params); - /* @var $object kDBList */ $value = $object->GetDBField($status_field); // sets base status icon if ( $value == STATUS_ACTIVE ) { /*if ( $object->HasField('IsPop') && $object->GetDBField('IsPop') ) { $value = 'POP'; } if ( $object->HasField('IsHot') && $object->GetDBField('IsHot') ) { $value = 'HOT'; }*/ if ( $object->HasField('IsNew') && $object->GetDBField('IsNew') ) { $value = 'NEW'; } /*if ( $object->HasField('EditorsPick') && $object->GetDBField('EditorsPick') ) { $value = 'PICK'; }*/ } return array_key_exists($value, $icons) ? $icons[$value] : $icons['default']; } /** * Allows to create valid mod-rewrite compatible link to module item * * @param Array $params * @param string $id_prefix * @return string */ function ItemLink($params, $id_prefix = null) { if ($this->Application->isAdmin) { // link from Admin to Front-end $params['prefix'] = '_FRONT_END_'; if ( $this->Application->ConfigValue('UseModRewrite') ) { $params['__MOD_REWRITE__'] = 1; } else { $params['index_file'] = 'index.php'; } } if ( !isset($params['pass']) ) { $params['pass'] = 'm,'.$this->Prefix; } // set by PrintList2 tag $item_id = $this->Application->GetVar($this->getPrefixSpecial() . '_id'); if ( !$item_id && ($this->Special != 'next' && $this->Special != 'previous') ) { // set from page url $item_id = $this->Application->GetVar($this->Prefix . '_id'); } + /** @var kDBItem $object */ $object = $this->getObject($params); - /* @var $object kDBItem */ $params['m_cat_page'] = 1; $params['m_cat_id'] = $object->GetDBField('CategoryId'); $params['pass_category'] = 1; $params[$this->Prefix . '_id'] = $item_id ? $item_id : $object->GetID(); return $this->Application->ProcessParsedTag('m', 't', $params); } /** * Builds link for browsing current item on Front-End * * @param Array $params * @return string */ function PageBrowseLink($params) { + /** @var kThemesHelper $themes_helper */ $themes_helper = $this->Application->recallObject('ThemesHelper'); - /* @var $themes_helper kThemesHelper */ + /** @var SiteConfigHelper $site_config_helper */ $site_config_helper = $this->Application->recallObject('SiteConfigHelper'); - /* @var $site_config_helper SiteConfigHelper */ $settings = $site_config_helper->getSettings(); $params['editing_mode'] = $settings['default_editing_mode']; $params['m_theme'] = $themes_helper->getCurrentThemeId(); $params['admin'] = 1; return $this->ItemLink($params); } function CategoryPath($params) { if ($this->Application->isAdminUser) { // path for module root category in admin if (!isset($params['cat_id'])) { $params['cat_id'] = $this->Application->RecallVar($params['session_var'], 0); } } else { // path for category item category in front-end $object = $this->getObject($params); $params['cat_id'] = $object->GetDBField('CategoryId'); } + /** @var kNavigationBar $navigation_bar */ $navigation_bar = $this->Application->recallObject('kNavigationBar'); - /* @var $navigation_bar kNavigationBar */ return $navigation_bar->build($params); } function BuildListSpecial($params) { if ($this->Special != '') return $this->Special; if ( isset($params['parent_cat_id']) ) { $parent_cat_id = $params['parent_cat_id']; } else { $parent_cat_id = $this->Application->GetVar('c_id'); if (!$parent_cat_id) { $parent_cat_id = $this->Application->GetVar('m_cat_id'); } } $recursive = isset($params['recursive']); $list_unique_key = $this->getUniqueListKey($params).$recursive; if ($list_unique_key == '') { return parent::BuildListSpecial($params); } return crc32($parent_cat_id.$list_unique_key); } function CatalogItemCount($params) { $params['skip_quering'] = true; $object =& $this->GetList($params); return $object->GetRecordsCount(false) != $object->GetRecordsCount() ? $object->GetRecordsCount().' / '.$object->GetRecordsCount(false) : $object->GetRecordsCount(); } function ListReviews($params) { $prefix = $this->Prefix . '-rev'; + /** @var kDBTagProcessor $review_tag_processor */ $review_tag_processor = $this->Application->recallObject($prefix . '.item_TagProcessor'); - /* @var $review_tag_processor kDBTagProcessor */ return $review_tag_processor->PrintList($params); } function ReviewCount($params) { + /** @var kDBTagProcessor $review_tag_processor */ $review_tag_processor = $this->Application->recallObject('rev.item_TagProcessor'); - /* @var $review_tag_processor kDBTagProcessor */ return $review_tag_processor->TotalRecords($params); } function InitCatalogTab($params) { $tab_params['mode'] = $this->Application->GetVar('tm'); // single/multi selection possible $tab_params['special'] = $this->Application->GetVar('ts'); // use special for this tab $tab_params['dependant'] = $this->Application->GetVar('td'); // is grid dependant on categories grid // set default params (same as in catalog) if ($tab_params['mode'] === false) $tab_params['mode'] = 'multi'; if ($tab_params['special'] === false) $tab_params['special'] = ''; if ($tab_params['dependant'] === false) $tab_params['dependant'] = 'yes'; // pass params to block with tab content $params['name'] = $params['render_as']; $special = $tab_params['special'] ? $tab_params['special'] : $this->Special; $params['prefix'] = trim($this->Prefix.'.'.$special, '.'); $prefix_append = $this->Application->GetVar('prefix_append'); if ($prefix_append) { $params['prefix'] .= $prefix_append; } $default_grid = array_key_exists('default_grid', $params) ? $params['default_grid'] : 'Default'; $radio_grid = array_key_exists('radio_grid', $params) ? $params['radio_grid'] : 'Radio'; $params['cat_prefix'] = trim('c.'.$special, '.'); $params['tab_mode'] = $tab_params['mode']; $params['grid_name'] = ($tab_params['mode'] == 'multi') ? $default_grid : $radio_grid; $params['tab_dependant'] = $tab_params['dependant']; $params['show_category'] = $tab_params['special'] == 'showall' ? 1 : 0; // this is advanced view -> show category name if ($special == 'showall' || $special == 'user') { $params['grid_name'] .= 'ShowAll'; } // use $pass_params to be able to pass 'tab_init' parameter from m_ModuleInclude tag return $this->Application->ParseBlock($params, 1); } /** * Show CachedNavbar of current item primary category * * @param Array $params * @return string */ function CategoryName($params) { // show category cachednavbar of $object = $this->getObject($params); $category_id = isset($params['cat_id']) ? $params['cat_id'] : $object->GetDBField('CategoryId'); $cache_key = 'category_paths[%CIDSerial:' . $category_id . '%][%PhrasesSerial%][Adm:' . (int)$this->Application->isAdmin . ']'; $category_path = $this->Application->getCache($cache_key); if ($category_path === false) { // not chached if ($category_id > 0) { $cached_navbar = preg_replace('/^(Content&\|&|Content)/i', '', $object->GetField('CachedNavbar')); $category_path = trim($this->CategoryName( Array('cat_id' => 0) ).' > '.str_replace('&|&', ' > ', $cached_navbar), ' > '); } else { $category_path = $this->Application->Phrase(($this->Application->isAdmin ? 'la_' : 'lu_') . 'rootcategory_name'); } $this->Application->setCache($cache_key, $category_path); } return $category_path; } /** * Allows to determine if original value should be shown * * @param Array $params * @return bool */ function DisplayOriginal($params) { // original id found & greater then zero + show original $display_original = isset($params['display_original']) && $params['display_original']; $object = $this->getObject($params); /* @var $object kCatDBItem */ $owner_field = $this->getUnitConfig()->getOwnerField('CreatedById'); $perm_value = $this->PermHelper->ModifyCheckPermission($object->GetDBField($owner_field), $object->GetDBField('CategoryId'), $this->Prefix); return $display_original && ($perm_value == 1) && $this->Application->GetVar($this->Prefix.'.original_id'); } /** * Checks if user have one of required permissions * * @param Array $params * @return bool */ function HasPermission($params) { + /** @var kPermissionsHelper $perm_helper */ $perm_helper = $this->Application->recallObject('PermissionsHelper'); - /* @var $perm_helper kPermissionsHelper */ $params['raise_warnings'] = 0; + + /** @var kCatDBItem $object */ $object = $this->getObject($params); - /* @var $object kCatDBItem */ // 1. category restriction $params['cat_id'] = $object->isLoaded() ? $object->GetDBField('ParentPath') : $this->Application->GetVar('m_cat_id'); // 2. owner restriction $owner_field = $this->getUnitConfig()->getOwnerField('CreatedById'); $is_owner = $object->GetDBField($owner_field) == $this->Application->RecallVar('user_id'); return $perm_helper->TagPermissionCheck($params, $is_owner); } /** * Creates link to current category or to module root category, when current category is home * * @param Array $params * @return string */ function SuggestItemLink($params) { if (!isset($params['cat_id'])) { $params['cat_id'] = $this->Application->GetVar('m_cat_id'); } if ($params['cat_id'] == 0) { $params['cat_id'] = $this->Application->findModule('Var', $this->Prefix, 'RootCat'); } $params['m_cat_page'] = 1; return $this->Application->ProcessParsedTag('c', 'CategoryLink', $params); } /** * Allows to detect if item has any additional images available * * @param Array $params * @return string */ function HasAdditionalImages($params) { + /** @var kDBItem $object */ $object = $this->getObject($params); - /* @var $object kDBItem */ $cache_key = $object->Prefix . '_additional_images[%' . $this->Application->incrementCacheSerial($object->Prefix, $object->GetID(), false) . '%]'; $ret = $this->Application->getCache($cache_key); if ( $ret === false ) { $this->Conn->nextQueryCachable = true; $sql = 'SELECT ImageId FROM ' . $this->Application->getUnitConfig('img')->getTableName() . ' WHERE ResourceId = ' . $object->GetDBField('ResourceId') . ' AND DefaultImg != 1 AND Enabled = 1'; $ret = $this->Conn->GetOne($sql) ? 1 : 0; $this->Application->setCache($cache_key, $ret); } return $ret; } /** * Checks that item is pending * * @param Array $params * @return bool */ function IsPending($params) { $object = $this->getObject($params); $pending_status = Array (STATUS_PENDING, STATUS_PENDING_EDITING); return in_array($object->GetDBField('Status'), $pending_status); } function IsFavorite($params) { static $favorite_status = Array (); + /** @var kDBList $object */ $object = $this->getObject($params); - /* @var $object kDBList */ if ( !isset($favorite_status[$this->Special]) ) { $resource_ids = $object->GetCol('ResourceId'); $user_id = $this->Application->RecallVar('user_id'); $sql = 'SELECT FavoriteId, ResourceId FROM ' . $this->Application->getUnitConfig('fav')->getTableName() . ' WHERE (PortalUserId = ' . $user_id . ') AND (ResourceId IN (' . implode(',', $resource_ids) . '))'; $favorite_status[$this->Special] = $this->Conn->GetCol($sql, 'ResourceId'); } return isset($favorite_status[$this->Special][$object->GetDBField('ResourceId')]); } /** * Returns item's editors pick status (using not formatted value) * * @param Array $params * @return bool */ function IsEditorsPick($params) { $object = $this->getObject($params); return $object->GetDBField('EditorsPick') == 1; } function FavoriteToggleLink($params) { $fav_prefix = $this->Prefix.'-fav'; $params['pass'] = implode(',', Array('m', $this->Prefix, $fav_prefix)); $params[$fav_prefix.'_event'] = 'OnFavoriteToggle'; return $this->ItemLink($params); } /** * Checks if item is passed in url * * @param Array $params * @return bool */ function ItemAvailable($params) { return $this->Application->GetVar($this->getPrefixSpecial().'_id') > 0; } function SortingSelected($params) { $list =& $this->GetList($params); $user_sorting_start = $this->getUserSortIndex(); // remove language prefix from $current_sorting_field $current_sorting_field = preg_replace('/^l[\d]+_(.*)/', '\\1', $list->GetOrderField($user_sorting_start)); $current_sorting = $current_sorting_field . '|' . $list->GetOrderDirection($user_sorting_start); return strtolower($current_sorting) == strtolower($params['sorting']) ? $params['selected'] : ''; } function CombinedSortingDropDownName($params) { $list =& $this->GetList($params); if ($list->isMainList()) { return parent::CombinedSortingDropDownName($params); } return $list->Prefix . '_CombinedSorting'; } /** * Prepares name for field with event in it (used only on front-end) * * @param Array $params * @return string */ function SubmitName($params) { $list =& $this->GetList($params); if ($list->isMainList()) { return parent::SubmitName($params); } return 'events[' . $list->Prefix . '][' . $params['event'] . ']'; } /** * Returns prefix + any word (used for shared between categories per page settings) * * @param Array $params * @return string */ function VarName($params) { $list =& $this->GetList($params); if ($list->isMainList()) { return parent::VarName($params); } return $list->Prefix . '_' . $params['type']; } /** * Checks if we are viewing module root category * * @param Array $params * @return bool */ function IsModuleHome($params) { $root_category = $this->Application->findModule('Var', $this->Prefix, 'RootCat'); return $root_category == $this->Application->GetVar('m_cat_id'); } /** * Dynamic votes indicator * * @param Array $params * * @return string */ function VotesIndicator($params) { + /** @var kDBItem $object */ $object = $this->getObject($params); - /* @var $object kDBItem */ + /** @var RatingHelper $rating_helper */ $rating_helper = $this->Application->recallObject('RatingHelper'); - /* @var $rating_helper RatingHelper */ $small_style = array_key_exists('small_style', $params) ? $params['small_style'] : false; return $rating_helper->ratingBar($object, true, '', $small_style); } function RelevanceIndicator($params) { $object = $this->getObject($params); /** @var kSearchHelper $search_helper */ $search_helper = $this->Application->recallObject('SearchHelper'); $search_results_table = $search_helper->getSearchTable(); $sql = 'SELECT Relevance FROM '.$search_results_table.' WHERE ResourceId = '.$object->GetDBField('ResourceId'); $percents_off = (int)(100 - (100 * $this->Conn->GetOne($sql))); $percents_off = ($percents_off < 0) ? 0 : $percents_off; if ($percents_off) { $params['percent_off'] = $percents_off; $params['percent_on'] = 100 - $percents_off; $params['name'] = $this->SelectParam($params, 'relevance_normal_render_as,block_relevance_normal'); } else { $params['name'] = $this->SelectParam($params, 'relevance_full_render_as,block_relevance_full'); } return $this->Application->ParseBlock($params); } function SearchResultField($params) { $ret = $this->Field($params); $keywords = unserialize( $this->Application->RecallVar('highlight_keywords') ); $opening = $this->Application->ParseBlock( Array('name' => $this->SelectParam($params, 'highlight_opening_render_as,block_highlight_opening')) ); $closing = $this->Application->ParseBlock( Array('name' => $this->SelectParam($params, 'highlight_closing_render_as,block_highlight_closing')) ); foreach ($keywords as $index => $keyword) { $keywords[$index] = preg_quote($keyword, '/'); } return preg_replace('/('.implode('|', $keywords).')/i', $opening.'\\1'.$closing, $ret); } /** * Shows keywords, that user searched * * @param Array $params * @return bool */ function SearchKeywords($params) { $keywords = $this->Application->GetVar('keywords'); $sub_search = $this->Application->GetVar('search_type') == 'subsearch'; return ($keywords !== false) && !$sub_search ? $keywords : $this->Application->RecallVar('keywords'); } function AdvancedSearchForm($params) { $search_table = $this->Application->getUnitConfig('confs')->getTableName(); $module_name = $this->Application->findModule('Var', $this->Prefix, 'Name'); $sql = 'SELECT * FROM ' . $search_table . ' WHERE (ModuleName = ' . $this->Conn->qstr($module_name) . ') AND (AdvancedSearch = 1) ORDER BY DisplayOrder'; $search_config = $this->Conn->Query($sql); $ret = ''; foreach ($search_config as $record) { $params['name'] = $this->SelectParam($params, 'and_or_render_as,and_or_block'); $params['field'] = $record['FieldName']; $params['andor'] = $this->Application->ParseBlock($params); $params['name'] = $this->SelectParam($params, $record['FieldType'] . '_render_as,' . $record['FieldType'] . '_block'); $params['caption'] = $this->Application->Phrase($record['DisplayName']); $ret .= $this->Application->ParseBlock($params); } return $ret; } /** * Returns last modification date of items in category / system * * @param Array $params * @return string */ function LastUpdated($params) { $category_id = (int)$this->Application->GetVar('m_cat_id'); $local = array_key_exists('local', $params) && ($category_id > 0) ? $params['local'] : false; $serial_name1 = $this->Application->incrementCacheSerial('c', $local ? $category_id : null, false); $serial_name2 = $this->Application->incrementCacheSerial($this->Prefix, null, false); $cache_key = 'categoryitems_last_updated[%' . $serial_name1 . '%][%' . $serial_name2 . '%]'; $row_data = $this->Application->getCache($cache_key); if ( $row_data === false ) { $config = $this->getUnitConfig(); if ( $local && ($category_id > 0) ) { // scan only current category & it's children list ($tree_left, $tree_right) = $this->Application->getTreeIndex($category_id); $sql = 'SELECT MAX(item_table.Modified) AS ModDate, MAX(item_table.CreatedOn) AS NewDate FROM ' . $config->getTableName() . ' item_table LEFT JOIN ' . TABLE_PREFIX . 'CategoryItems ci ON (item_table.ResourceId = ci.ItemResourceId) LEFT JOIN ' . TABLE_PREFIX . 'Categories c ON c.CategoryId = ci.CategoryId WHERE c.TreeLeft BETWEEN ' . $tree_left . ' AND ' . $tree_right; } else { // scan all categories in system $sql = 'SELECT MAX(Modified) AS ModDate, MAX(CreatedOn) AS NewDate FROM ' . $config->getTableName(); } $this->Conn->nextQueryCachable = true; $row_data = $this->Conn->GetRow($sql); $this->Application->setCache($cache_key, $row_data); } if ( !$row_data ) { return ''; } $date = $row_data[$row_data['NewDate'] > $row_data['ModDate'] ? 'NewDate' : 'ModDate']; // format date $format = isset($params['format']) ? $params['format'] : '_regional_DateTimeFormat'; if ( preg_match("/_regional_(.*)/", $format, $regs) ) { + /** @var LanguagesItem $lang */ $lang = $this->Application->recallObject('lang.current'); - /* @var $lang LanguagesItem */ if ( $regs[1] == 'DateTimeFormat' ) { // combined format $format = $lang->GetDBField('DateFormat') . ' ' . $lang->GetDBField('TimeFormat'); } else { // simple format $format = $lang->GetDBField($regs[1]); } } return date($format, $date); } /** * Counts category item count in system (not category-dependent) * * @param Array $params * @return int */ function ItemCount($params) { + /** @var kCountHelper $count_helper */ $count_helper = $this->Application->recallObject('CountHelper'); - /* @var $count_helper kCountHelper */ $today_only = isset($params['today']) && $params['today']; return $count_helper->ItemCount($this->Prefix, $today_only); } /** * Displays list of allowed categories on "Suggest Link" and similar forms. * * @param array $params Tag params. * * @return string * @access protected */ protected function CategorySelector($params) { $category_id = isset($params['category_id']) && is_numeric($params['category_id']) ? $params['category_id'] : false; if ( $category_id === false ) { // if category id not given use module root category $category_id = $this->Application->findModule('Var', $this->Prefix, 'RootCat'); } $id_field = $this->Application->getUnitConfig('c')->getIDField(); // get category list (permission based) $categories = $this->Conn->Query($this->getCategorySelectorQuery($category_id), $id_field); $block_params = $this->prepareTagParams($params); $block_params['name'] = $params['render_as']; $block_params['strip_nl'] = 2; $ret = ''; foreach ($categories as $category_id => $category_data) { // print category $block_params['separator'] = isset($params['category_id']) ? $params['separator'] : ''; // return original separator, remove separator for top level categories $block_params['category_id'] = $category_id; $block_params['category_name'] = $category_data['CategoryName']; $cached_navbar = preg_replace('/^(Content&\|&|Content)/i', '', $category_data['CachedNavbar']); $block_params['full_path'] = str_replace('&|&', ' > ', $cached_navbar); $ret .= $this->Application->ParseBlock($block_params); // print it's children $block_params['separator'] = '   ' . $params['separator']; $ret .= $this->CategorySelector($block_params); } return $ret; } /** * Returns given category sub-categories, that user have rights to view. * * @param int $category_id Category. * * @return array * @access protected */ protected function getCategorySelectorQuery($category_id) { $category_config = $this->Application->getUnitConfig('c'); $title_field = $category_config->getTitleField(); $where_clause = Array ( 'c.ParentId = ' . $category_id, 'c.Status = ' . STATUS_ACTIVE, ); $sql = 'SELECT c.' . $title_field . ' AS CategoryName, c.' . $category_config->getIDField() . ', c.l' . $this->Application->GetVar('m_lang') . '_CachedNavbar AS CachedNavbar FROM ' . $category_config->getTableName() . ' c'; + /** @var kCountHelper $count_helper */ $count_helper = $this->Application->recallObject('CountHelper'); - /* @var $count_helper kCountHelper */ list ($sql, $where_clause) = $count_helper->attachViewPermissionCheck('c', $sql, $where_clause); return $sql . ' WHERE (' . implode(') AND (', $where_clause) . ') ORDER BY c.' . $title_field . ' ASC'; } function PrintMoreCategories($params) { + /** @var kDBItem $object */ $object = $this->getObject($params); - /* @var $object kDBItem */ $category_ids = $this->Field($params); if (!$category_ids) { return ''; } $category_ids = explode('|', substr($category_ids, 1, -1)); $category_config = $this->Application->getUnitConfig('c'); $id_field = $category_config->getIDField(); $title_field = $category_config->getTitleField(); $table_name = $category_config->getTableName(); $sql = 'SELECT '.$title_field.' AS CategoryName, '.$id_field.', l' . $this->Application->GetVar('m_lang') . '_CachedNavbar AS CachedNavbar FROM '.$table_name.' WHERE '.$id_field.' IN ('.implode(',', $category_ids).')'; $categories = $this->Conn->Query($sql, $id_field); $block_params = $this->prepareTagParams($params); $block_params['name'] = $params['render_as']; $ret = ''; foreach ($categories as $category_id => $category_data) { $block_params['category_id'] = $category_id; $block_params['category_name'] = $category_data['CategoryName']; $cached_navbar = preg_replace('/^(Content&\|&|Content)/i', '', $category_data['CachedNavbar']); $block_params['full_path'] = str_replace('&|&', ' > ', $cached_navbar); $ret .= $this->Application->ParseBlock($block_params); } return $ret; } function DownloadFileLink($params) { $params[$this->getPrefixSpecial().'_event'] = 'OnDownloadFile'; return $this->ItemLink($params); } function ImageSrc($params) { list ($ret, $tag_processed) = $this->processAggregatedTag('ImageSrc', $params, $this->getPrefixSpecial()); return $tag_processed ? $ret : false; } /** * Registers hit for item (one time per session) * * @param Array $params */ function RegisterHit($params) { + /** @var kCatDBItem $object */ $object = $this->getObject($params); - /* @var $object kCatDBItem */ if ($object->isLoaded()) { $object->RegisterHit(); } } /** * Returns link to item's author public profile * * @param Array $params * @return string */ function ProfileLink($params) { $object = $this->getObject($params); $owner_field = array_key_exists('owner_field', $params) ? $params['owner_field'] : 'CreatedById'; $params['user_id'] = $object->GetDBField($owner_field); unset($params['owner_field']); return $this->Application->ProcessParsedTag('m', 'Link', $params); } /** * Checks, that "view in browse mode" functionality available * * @param Array $params * @return bool */ function BrowseModeAvailable($params) { $valid_special = $valid_special = $params['Special'] != 'user'; $not_selector = $this->Application->GetVar('type') != 'item_selector'; return $valid_special && $not_selector; } /** * Returns a link for editing product * * @param Array $params * @return string */ function ItemEditLink($params) { + /** @var kDBList $object */ $object = $this->getObject($params); - /* @var $object kDBList */ $config = $this->getUnitConfig(); $edit_template = $config->getAdminTemplatePath() . '/' . $config->getAdminTemplatePrefix() . 'edit'; $url_params = Array ( 'm_opener' => 'd', $this->Prefix.'_mode' => 't', $this->Prefix.'_event' => 'OnEdit', $this->Prefix.'_id' => $object->GetID(), 'm_cat_id' => $object->GetDBField('CategoryId'), 'pass' => 'all,'.$this->Prefix, 'no_pass_through' => 1, ); return $this->Application->HREF($edit_template,'', $url_params); } function LanguageVisible($params) { $field = $this->SelectParam($params, 'name,field'); preg_match('/l([\d]+)_(.*)/', $field, $regs); $params['name'] = $regs[2]; return $this->HasLanguageError($params) || $this->Application->GetVar('m_lang') == $regs[1]; } function HasLanguageError($params) { static $languages = null; if ( !isset($languages) ) { $language_config = $this->Application->getUnitConfig('lang'); $sql = 'SELECT ' . $language_config->getIDField() . ' FROM ' . $language_config->getTableName() . ' WHERE Enabled = 1'; $languages = $this->Conn->GetCol($sql); } $field = $this->SelectParam($params, 'name,field'); + /** @var kDBItem $object */ $object = $this->getObject($params); - /* @var $object kDBItem */ foreach ($languages as $language_id) { $check_field = 'l' . $language_id . '_' . $field; if ( $object->GetErrorMsg($check_field, false) ) { return true; } } return false; } /** * Returns list of categories, that have add/edit permission for current category item type * * @param Array $params * @return string */ function AllowedCategoriesJSON($params) { if ( $this->Application->RecallVar('user_id') == USER_ROOT ) { $categories = true; } else { + /** @var kDBItem $object */ $object = $this->getObject($params); - /* @var $object kDBItem */ + /** @var kPermissionsHelper $perm_helper */ $perm_helper = $this->Application->recallObject('PermissionsHelper'); - /* @var $perm_helper kPermissionsHelper */ $perm_prefix = $this->getUnitConfig()->getPermItemPrefix(); $categories = $perm_helper->getPermissionCategories($perm_prefix . '.' . ($object->IsNewItem() ? 'ADD' : 'MODIFY')); } return json_encode($categories); } } Index: branches/5.3.x/core/kernel/db/dblist.php =================================================================== --- branches/5.3.x/core/kernel/db/dblist.php (revision 16518) +++ branches/5.3.x/core/kernel/db/dblist.php (revision 16519) @@ -1,1769 +1,1769 @@ OrderFields = Array(); foreach ( $this->getFilterStructure() as $filter_params ) { $property_name = $filter_params['type']; $filter_group =& $this->$property_name; $filter_group[$filter_params['class']] = $this->Application->makeClass( 'kMultipleFilter', array($filter_params['join_using']) ); } $this->PerPage = -1; } function setGridName($grid_name) { $this->gridName = $grid_name; } /** * Returns information about all possible filter types * * @return Array * @access protected */ protected function getFilterStructure() { $filters = Array ( Array ('type' => 'WhereFilter', 'class' => self::FLT_SYSTEM, 'join_using' => self::FLT_TYPE_AND), Array ('type' => 'WhereFilter', 'class' => self::FLT_NORMAL, 'join_using' => self::FLT_TYPE_OR), Array ('type' => 'WhereFilter', 'class' => self::FLT_SEARCH, 'join_using' => self::FLT_TYPE_OR), Array ('type' => 'WhereFilter', 'class' => self::FLT_VIEW, 'join_using' => self::FLT_TYPE_AND), Array ('type' => 'WhereFilter', 'class' => self::FLT_CUSTOM, 'join_using' => self::FLT_TYPE_AND), Array ('type' => 'HavingFilter', 'class' => self::FLT_SYSTEM, 'join_using' => self::FLT_TYPE_AND), Array ('type' => 'HavingFilter', 'class' => self::FLT_NORMAL, 'join_using' => self::FLT_TYPE_OR), Array ('type' => 'HavingFilter', 'class' => self::FLT_SEARCH, 'join_using' => self::FLT_TYPE_OR), Array ('type' => 'HavingFilter', 'class' => self::FLT_VIEW, 'join_using' => self::FLT_TYPE_AND), Array ('type' => 'HavingFilter', 'class' => self::FLT_CUSTOM, 'join_using' => self::FLT_TYPE_AND), Array ('type' => 'AggregateFilter', 'class' => self::FLT_SYSTEM, 'join_using' => self::FLT_TYPE_AND), Array ('type' => 'AggregateFilter', 'class' => self::FLT_NORMAL, 'join_using' => self::FLT_TYPE_OR), Array ('type' => 'AggregateFilter', 'class' => self::FLT_VIEW, 'join_using' => self::FLT_TYPE_AND), ); return $filters; } /** * Adds new or replaces old filter with same name * * @param string $name filter name (for internal use) * @param string $clause where/having clause part (no OR/AND allowed) * @param int $filter_type is filter having filter or where filter * @param int $filter_scope filter subtype: FLT_NORMAL,FLT_SYSTEM,FLT_SEARCH,FLT_VIEW,FLT_CUSTOM * @access public */ public function addFilter($name, $clause, $filter_type = self::WHERE_FILTER, $filter_scope = self::FLT_SYSTEM) { $this->getFilterCollection($filter_type, $filter_scope)->addFilter($name, $clause); } /** * Reads filter content * * @param string $name filter name (for internal use) * @param int $filter_type is filter having filter or where filter * @param int $filter_scope filter subtype: FLT_NORMAL,FLT_SYSTEM,FLT_SEARCH,FLT_VIEW,FLT_CUSTOM * @return string * @access public */ public function getFilter($name, $filter_type = self::WHERE_FILTER, $filter_scope = self::FLT_SYSTEM) { return $this->getFilterCollection($filter_type, $filter_scope)->getFilter($name); } /** * Removes specified filter from filters list * * @param string $name filter name (for internal use) * @param int $filter_type is filter having filter or where filter * @param int $filter_scope filter subtype: FLT_NORMAL,FLT_SYSTEM,FLT_SEARCH,FLT_VIEW,FLT_CUSTOM * @access public */ public function removeFilter($name, $filter_type = self::WHERE_FILTER, $filter_scope = self::FLT_SYSTEM) { $this->getFilterCollection($filter_type, $filter_scope)->removeFilter($name); } /** * Returns filter collection. * * @param integer $filter_type Is filter having filter or where filter. * @param integer $filter_scope Filter subtype: FLT_NORMAL,FLT_SYSTEM,FLT_SEARCH,FLT_VIEW,FLT_CUSTOM. * * @return kMultipleFilter */ protected function getFilterCollection($filter_type = self::WHERE_FILTER, $filter_scope = self::FLT_SYSTEM) { $filter_source = array( self::WHERE_FILTER => 'WhereFilter', self::HAVING_FILTER => 'HavingFilter', self::AGGREGATE_FILTER => 'AggregateFilter' ); /** @var kMultipleFilter[] $filters */ $property_name = $filter_source[$filter_type]; $filters =& $this->$property_name; return $filters[$filter_scope]; } /** * Clear all filters * * @access public */ public function clearFilters() { foreach ( $this->getFilterStructure() as $filter_params ) { $property_name = $filter_params['type']; $filter_group =& $this->$property_name; $filter_group[$filter_params['class']]->clearFilters(); } } /** * Counts the total number of records base on the query resulted from {@link kDBList::GetSelectSQL()} * * The method modifies the query to substitude SELECT part (fields listing) with COUNT(*). * Special care should be applied when working with lists based on grouped queries, all aggregate function fields * like SUM(), AVERAGE() etc. should be added to CountedSQL by using {@link kDBList::SetCountedSQL()} * * @access protected */ protected function CountRecs() { $all_sql = $this->GetSelectSQL(true,false); $sql = $this->getCountSQL($all_sql); $this->Counted = true; if( $this->GetGroupClause() ) { $this->RecordsCount = count( $this->Conn->GetCol($sql) ); } else { $this->RecordsCount = (int)$this->Conn->GetOne($sql); } $system_sql = $this->GetSelectSQL(true,true); if($system_sql == $all_sql) //no need to query the same again { $this->NoFilterCount = $this->RecordsCount; return; } $sql = $this->getCountSQL($system_sql); if( $this->GetGroupClause() ) { $this->NoFilterCount = count( $this->Conn->GetCol($sql) ); } else { $this->NoFilterCount = (int)$this->Conn->GetOne($sql); } } /** * Returns record count in list with/without user filters applied * * @param bool $with_filters * @return int * @access public */ public function GetRecordsCount($with_filters = true) { if (!$this->Counted) { $this->CountRecs(); } return $with_filters ? $this->RecordsCount : $this->NoFilterCount; } /** * Returns record count, that were actually selected * * @return int * @access public */ public function GetSelectedCount() { $this->Query(); return $this->SelectedCount; } /** * Transforms given query into count query (DISTINCT is also processed) * * @param string $sql * @return string * @access public */ public function getCountSQL($sql) { if ( preg_match("/^\s*SELECT\s+DISTINCT(.*?\s)FROM(?!_)/is",$sql,$regs ) ) { return preg_replace("/^\s*SELECT\s+DISTINCT(.*?\s)FROM(?!_)/is", "SELECT COUNT(DISTINCT ".$regs[1].") AS count FROM", $sql); } else { return preg_replace("/^\s*SELECT(.*?\s)FROM(?!_)/is", "SELECT COUNT(*) AS count FROM ", $sql); } } /** * Queries the database with SQL resulted from {@link kDBList::GetSelectSQL()} and stores result in {@link kDBList::SelectRS} * * All the sorting, pagination, filtration of the list should be set prior to calling Query(). * * @param bool $force force re-query, when already queried * @return bool * @access public */ public function Query($force=false) { if (!$force && $this->Queried) return true; $q = $this->GetSelectSQL(); //$rs = $this->Conn->SelectLimit($q, $this->PerPage, $this->Offset); //in case we have not counted records try to select one more item to find out if we have something more than perpage $limit = $this->Counted ? $this->PerPage : $this->PerPage+1; $sql = $q.' '.$this->Conn->getLimitClause($this->Offset,$limit); $this->Records = $this->Conn->Query($sql); if (!$this->Records && ($this->Page > 1)) { if ( $this->Application->isAdmin ) { // no records & page > 1, try to reset to 1st page (works only when list in not counted before) $this->Application->StoreVar($this->getPrefixSpecial() . '_Page', 1, true); $this->SetPage(1); $this->Query($force); } else if ( $this->Application->HttpQuery->refererIsOurSite() ) { // no records & page > 1, try to reset to last page $this->SetPage($this->GetTotalPages()); $this->Query($force); } else { // no records & page > 1, show 404 page trigger_error('Unknown page ' . $this->Page . ' in ' . $this->getPrefixSpecial() . ' list, leading to "404 Not Found"', E_USER_NOTICE); $this->Application->UrlManager->show404(); } } $this->SelectedCount = count($this->Records); if (!$this->Counted) $this->RecordsCount = $this->SelectedCount; if (!$this->Counted && $this->SelectedCount > $this->PerPage && $this->PerPage != -1) $this->SelectedCount--; if ($this->Records === false) { //handle errors here return false; } $this->Queried = true; $this->Application->HandleEvent(new kEvent($this->getPrefixSpecial() . ':OnAfterListQuery')); return true; } /** * Adds one more record to list virtually and updates all counters * * @param Array $record * @access public */ public function addRecord($record) { $this->Records[] = $record; $this->SelectedCount++; $this->RecordsCount++; } /** * Calculates totals based on config * * @access protected */ protected function CalculateTotals() { $fields = Array (); $this->Totals = Array (); if ( $this->gridName ) { $grid = $this->getUnitConfig()->getGridByName($this->gridName); $grid_fields = $grid['Fields']; } else { $grid_fields = $this->Fields; } foreach ($grid_fields as $field_name => $field_options) { if ( $this->gridName && array_key_exists('totals', $field_options) && $field_options['totals'] ) { $totals = $field_options['totals']; } elseif ( array_key_exists('totals', $this->Fields[$field_name]) && $this->Fields[$field_name]['totals'] ) { $totals = $this->Fields[$field_name]['totals']; } else { continue; } $calculated_field = array_key_exists($field_name, $this->CalculatedFields) && array_key_exists($field_name, $this->VirtualFields); $db_field = !array_key_exists($field_name, $this->VirtualFields); if ( $calculated_field || $db_field ) { $field_expression = $calculated_field ? $this->CalculatedFields[$field_name] : '`' . $this->TableName . '`.`' . $field_name . '`'; $fields[$field_name] = $totals . '(' . $field_expression . ') AS ' . $field_name . '_' . $totals; } } if ( !$fields ) { return; } $sql = $this->GetSelectSQL(true, false); $fields = str_replace('%1$s', $this->TableName, implode(', ', $fields)); if ( preg_match("/DISTINCT(.*?\s)FROM(?!_)/is", $sql, $regs) ) { $sql = preg_replace("/^\s*SELECT DISTINCT(.*?\s)FROM(?!_)/is", 'SELECT ' . $fields . ' FROM', $sql); } else { $sql = preg_replace("/^\s*SELECT(.*?\s)FROM(?!_)/is", 'SELECT ' . $fields . ' FROM ', $sql); } $totals = $this->Conn->Query($sql); foreach ($totals as $totals_row) { foreach ($totals_row as $total_field => $field_value) { if ( !isset($this->Totals[$total_field]) ) { $this->Totals[$total_field] = 0; } $this->Totals[$total_field] += $field_value; } } $this->TotalsCalculated = true; } /** * Returns previously calculated total (not formatted) * * @param string $field * @param string $total_function * @return float * @access public */ public function getTotal($field, $total_function) { if (!$this->TotalsCalculated) { $this->CalculateTotals(); } return $this->Totals[$field . '_' . $total_function]; } function setTotal($field, $total_function, $value) { $this->Totals[$field . '_' . $total_function] = $value; } function getTotalFunction($field) { if ( $this->gridName ) { $grid = $this->getUnitConfig()->getGridByName($this->gridName); $field_options = $grid['Fields'][$field]; } else { $field_options = $this->Fields[$field]; } if ( $this->gridName && array_key_exists('totals', $field_options) && $field_options['totals'] ) { return $field_options['totals']; } elseif ( array_key_exists('totals', $this->Fields[$field]) && $this->Fields[$field]['totals'] ) { return $this->Fields[$field]['totals']; } return false; } /** * Returns previously calculated total (formatted) * * @param string $field * @param string $total_function * @return float * @access public */ function GetFormattedTotal($field, $total_function) { $res = $this->getTotal($field, $total_function); $formatter_class = $this->GetFieldOption($field, 'formatter'); if ( $formatter_class ) { + /** @var kFormatter $formatter */ $formatter = $this->Application->recallObject($formatter_class); - /* @var $formatter kFormatter */ $res = $formatter->Format($res, $field, $this); } return $res; } /** * Builds full select query except for LIMIT clause * * @param bool $for_counting * @param bool $system_filters_only * @param string $keep_clause * @return string * @access public */ public function GetSelectSQL($for_counting = false, $system_filters_only = false, $keep_clause = '') { $q = parent::GetSelectSQL($this->SelectClause); $q = !$for_counting ? $this->addCalculatedFields($q, 0) : str_replace('%2$s', '', $q); $where = $this->GetWhereClause($for_counting,$system_filters_only); $having = $this->GetHavingClause($for_counting,$system_filters_only); $order = $this->GetOrderClause(); $group = $this->GetGroupClause(); if ( $for_counting ) { $usage_string = $where . '|' . $having . '|' . $order . '|' . $group . '|' . $keep_clause; $optimizer = new LeftJoinOptimizer($q, $this->replaceModePrefix( str_replace('%1$s', $this->TableName, $usage_string) )); $q = $optimizer->simplify(); } if (!empty($where)) $q .= ' WHERE ' . $where; if (!empty($group)) $q .= ' GROUP BY ' . $group; if (!empty($having)) $q .= ' HAVING ' . $having; if ( !$for_counting && !empty($order) ) $q .= ' ORDER BY ' . $order; return $this->replaceModePrefix( str_replace('%1$s', $this->TableName, $q) ); } /** * Replaces all calculated field occurrences with their associated expressions * * @param string $clause where clause to extract calculated fields from * @param int $aggregated 0 - having + aggregated, 1 - having only, 2 - aggregated only * @param bool $replace_table * @return string * @access public */ public function extractCalculatedFields($clause, $aggregated = 1, $replace_table = false) { $fields = $this->getCalculatedFields($aggregated); if ( is_array($fields) && count($fields) > 0 ) { $fields = str_replace('%2$s', $this->Application->GetVar('m_lang'), $fields); foreach ($fields as $field_name => $field_expression) { $clause = preg_replace('/(\\(+)[(,` ]*' . $field_name . '[` ]{1}/', '\1 (' . $field_expression . ') ', $clause); $clause = preg_replace('/[,` ]{1}' . $field_name . '[` ]{1}/', ' (' . $field_expression . ') ', $clause); } } return $replace_table ? str_replace('%1$s', $this->TableName, $clause) : $clause; } /** * Returns WHERE clause of the query * * @param bool $for_counting merge where filters with having filters + replace field names for having fields with their values * @param bool $system_filters_only * @return string * @access private */ private function GetWhereClause($for_counting=false,$system_filters_only=false) { + /** @var kMultipleFilter $where */ $where = $this->Application->makeClass('kMultipleFilter'); - /* @var $where kMultipleFilter */ $where->addFilter( 'system_where', $this->extractCalculatedFields($this->WhereFilter[self::FLT_SYSTEM]->getSQL()) ); if (!$system_filters_only) { $where->addFilter('view_where', $this->WhereFilter[self::FLT_VIEW] ); $search_w = $this->WhereFilter[self::FLT_SEARCH]->getSQL(); if ($search_w || $for_counting) { // move search_having to search_where in case search_where isset or we are counting $search_h = $this->extractCalculatedFields( $this->HavingFilter[self::FLT_SEARCH]->getSQL() ); $search_w = ($search_w && $search_h) ? $search_w.' OR '.$search_h : $search_w.$search_h; $where->addFilter('search_where', $search_w ); } // CUSTOM $search_w = $this->WhereFilter[self::FLT_CUSTOM]->getSQL(); if ($search_w || $for_counting) { // move search_having to search_where in case search_where isset or we are counting $search_h = $this->extractCalculatedFields( $this->HavingFilter[self::FLT_CUSTOM]->getSQL() ); $search_w = ($search_w && $search_h) ? $search_w.' AND '.$search_h : $search_w.$search_h; $where->addFilter('custom_where', $search_w ); } // CUSTOM } if( $for_counting ) // add system_having and view_having to where { $where->addFilter('system_having', $this->extractCalculatedFields($this->HavingFilter[kDBList::FLT_SYSTEM]->getSQL()) ); if (!$system_filters_only) $where->addFilter('view_having', $this->extractCalculatedFields( $this->HavingFilter[kDBList::FLT_VIEW]->getSQL() ) ); } return $where->getSQL(); } /** * Returns HAVING clause of the query * * @param bool $for_counting don't return having filter in case if this is counting sql * @param bool $system_filters_only return only system having filters * @param int $aggregated 0 - aggregated and having, 1 - having only, 2 - aggregated only * @return string * @access private */ private function GetHavingClause($for_counting=false, $system_filters_only=false, $aggregated = 0) { if ($for_counting) { + /** @var kMultipleFilter $aggregate_filter */ $aggregate_filter = $this->Application->makeClass('kMultipleFilter'); - /* @var $aggregate_filter kMultipleFilter */ $aggregate_filter->addFilter('aggregate_system', $this->AggregateFilter[kDBList::FLT_SYSTEM]); if (!$system_filters_only) { $aggregate_filter->addFilter('aggregate_view', $this->AggregateFilter[kDBList::FLT_VIEW]); } return $this->extractCalculatedFields($aggregate_filter->getSQL(), 2); } + /** @var kMultipleFilter $having */ $having = $this->Application->makeClass('kMultipleFilter'); - /* @var $having kMultipleFilter */ $having->addFilter('system_having', $this->HavingFilter[kDBList::FLT_SYSTEM] ); if ($aggregated == 0) { if (!$system_filters_only) { $having->addFilter('view_aggregated', $this->AggregateFilter[kDBList::FLT_VIEW] ); } $having->addFilter('system_aggregated', $this->AggregateFilter[kDBList::FLT_SYSTEM]); } if (!$system_filters_only) { $having->addFilter('view_having', $this->HavingFilter[kDBList::FLT_VIEW] ); $having->addFilter('custom_having', $this->HavingFilter[kDBList::FLT_CUSTOM] ); $search_w = $this->WhereFilter[kDBList::FLT_SEARCH]->getSQL(); if (!$search_w) { $having->addFilter('search_having', $this->HavingFilter[kDBList::FLT_SEARCH] ); } } return $having->getSQL(); } /** * Returns GROUP BY clause of the query * * @return string * @access protected */ protected function GetGroupClause() { return $this->GroupByFields ? implode(',', $this->GroupByFields) : ''; } /** * Adds new group by field * * @param string $field * @access public */ public function AddGroupByField($field) { $this->GroupByFields[$field] = $field; } /** * Removes group by field added before * * @param string $field * @access public */ public function RemoveGroupByField($field) { unset($this->GroupByFields[$field]); } /** * Adds order field to ORDER BY clause * * @param string $field Field name * @param string $direction Direction of ordering (asc|desc) * @param bool $is_expression this is expression, that should not be escapted by "`" symbols * @return int * @access public */ public function AddOrderField($field, $direction = 'asc', $is_expression = false) { // original multilanguage field - convert to current lang field $formatter = isset($this->Fields[$field]['formatter']) ? $this->Fields[$field]['formatter'] : false; if ($formatter == 'kMultiLanguage' && !isset($this->Fields[$field]['master_field'])) { // for now kMultiLanguage formatter is only supported for real (non-virtual) fields $is_expression = true; $field = $this->getMLSortField($field); } if (!isset($this->Fields[$field]) && $field != 'RAND()' && !$is_expression) { trigger_error('Incorrect sorting defined (field = '.$field.'; direction = '.$direction.') in config for prefix '.$this->Prefix.'', E_USER_NOTICE); } $this->OrderFields[] = Array($field, $direction, $is_expression); return count($this->OrderFields) - 1; } /** * Sets new order fields, replacing existing ones * * @param Array $order_fields * @return void * @access public */ public function setOrderFields($order_fields) { $this->OrderFields = $order_fields; } /** * Changes sorting direction for a given sorting field index * * @param int $field_index * @param string $direction * @return void * @access public */ public function changeOrderDirection($field_index, $direction) { if ( !isset($this->OrderFields[$field_index]) ) { return; } $this->OrderFields[$field_index][1] = $direction; } /** * Returns expression, used to sort given multilingual field * * @param string $field * @return string */ function getMLSortField($field) { $table_name = '`' . $this->TableName . '`'; $lang = $this->Application->GetVar('m_lang'); $primary_lang = $this->Application->GetDefaultLanguageId(); $ret = 'IF(COALESCE(%1$s.l' . $lang . '_' . $field . ', ""), %1$s.l' . $lang . '_' . $field . ', %1$s.l' . $primary_lang . '_' . $field . ')'; return sprintf($ret, $table_name); } /** * Removes all order fields * * @access public */ public function ClearOrderFields() { $this->OrderFields = Array(); } /** * Returns ORDER BY Clause of the query * * The method builds order by clause by iterating {@link kDBList::OrderFields} array and concatenating it. * * @return string * @access private */ private function GetOrderClause() { $ret = ''; foreach ($this->OrderFields as $field) { $name = $field[0]; $ret .= isset($this->Fields[$name]) && !isset($this->VirtualFields[$name]) ? '`'.$this->TableName.'`.' : ''; if ($field[0] == 'RAND()' || $field[2]) { $ret .= $field[0].' '.$field[1].','; } else { $ret .= (strpos($field[0], '.') === false ? '`'.$field[0] . '`' : $field[0]) . ' ' . $field[1] . ','; } } $ret = rtrim($ret, ','); return $ret; } /** * Returns order field name in given position * * @param int $pos * @param bool $no_default * @return string * @access public */ public function GetOrderField($pos = NULL, $no_default = false) { if ( !(isset($this->OrderFields[$pos]) && $this->OrderFields[$pos]) && !$no_default ) { $pos = 0; } if ( isset($this->OrderFields[$pos][0]) ) { $field = $this->OrderFields[$pos][0]; $lang = $this->Application->GetVar('m_lang'); if ( preg_match('/^IF\(COALESCE\(.*?\.(l' . $lang . '_.*?), ""\),/', $field, $regs) ) { // undo result of kDBList::getMLSortField method return $regs[1]; } return $field; } return ''; } /** * Returns list order fields * * @return Array * @access public */ public function getOrderFields() { return $this->OrderFields; } /** * Returns order field direction in given position * * @param int $pos * @param bool $no_default * @return string * @access public */ public function GetOrderDirection($pos = NULL, $no_default = false) { if ( !(isset($this->OrderFields[$pos]) && $this->OrderFields[$pos]) && !$no_default ) { $pos = 0; } return isset($this->OrderFields[$pos][1]) ? $this->OrderFields[$pos][1] : ''; } /** * Returns ID of currently processed record * * @return int * @access public */ public function GetID() { return $this->Queried ? $this->GetDBField($this->IDField) : null; } /** * Allows kDBTagProcessor.SectionTitle to detect if it's editing or new item creation * * @return bool * @access public */ public function IsNewItem() { // no such thing as NewItem for lists :) return false; } /** * Return unformatted field value * * @param string $name * @return string * @access public */ public function GetDBField($name) { $row =& $this->getCurrentRecord(); if (defined('DEBUG_MODE') && DEBUG_MODE && $this->Queried && !array_key_exists($name, $row)) { if ( $this->Application->isDebugMode() ) { $this->Application->Debugger->appendTrace(); } trigger_error('Field "' . $name . '" doesn\'t exist in prefix ' . $this->getPrefixSpecial() . '', E_USER_WARNING); return 'NO SUCH FIELD'; } // return "null" for missing fields, because formatter require such behaviour ! return array_key_exists($name, $row) ? $row[$name] : null; } /** * Checks if requested field is present after database query * * @param string $name * @return bool * @access public */ public function HasField($name) { $row =& $this->getCurrentRecord(); return isset($row[$name]); } /** * Returns current record fields * * @return Array * @access public */ public function GetFieldValues() { $record =& $this->getCurrentRecord(); return $record; } /** * Returns current record from list * * @param int $offset Offset relative to current record index * @return Array * @access public */ public function &getCurrentRecord($offset = 0) { $record_index = $this->CurrentIndex + $offset; if ($record_index >=0 && $record_index < $this->SelectedCount) { return $this->Records[$record_index]; } $false = false; return $false; } /** * Goes to record with given index * * @param int $index * @access public */ public function GoIndex($index) { $this->CurrentIndex = $index; } /** * Goes to first record * * @access public */ public function GoFirst() { $this->CurrentIndex = 0; } /** * Goes to next record * * @access public */ public function GoNext() { $this->CurrentIndex++; } /** * Goes to previous record * * @access public */ public function GoPrev() { if ($this->CurrentIndex>0) { $this->CurrentIndex--; } } /** * Checks if there is no end of list * * @return bool * @access public */ public function EOL() { return ($this->CurrentIndex >= $this->SelectedCount); } /** * Returns total page count based on list per-page * * @return int * @access public */ public function GetTotalPages() { if ( !$this->Counted ) { $this->CountRecs(); } if ( $this->PerPage == -1 ) { return 1; } $integer_part = ($this->RecordsCount - ($this->RecordsCount % $this->PerPage)) / $this->PerPage; $reminder = ($this->RecordsCount % $this->PerPage) != 0; // adds 1 if there is a reminder $this->TotalPages = $integer_part + $reminder; return $this->TotalPages; } /** * Sets number of records to query per page * * @param int $per_page Number of records to display per page * @access public */ public function SetPerPage($per_page) { $this->PerPage = $per_page; } /** * Returns records per page count * * @param bool $in_fact * @return int * @access public */ public function GetPerPage($in_fact = false) { if ($in_fact) { return $this->PerPage; } return $this->PerPage == -1 ? $this->RecordsCount : $this->PerPage; } /** * Sets current page in list * * @param int $page * @access public */ public function SetPage($page) { if ($this->PerPage == -1) { $this->Page = 1; return; } if ($page < 1) $page = 1; $this->Offset = ($page-1)*$this->PerPage; if ($this->Counted && $this->Offset > $this->RecordsCount) { $this->SetPage(1); } else { $this->Page = $page; } //$this->GoFirst(); } /** * Returns current list page * * @return int * @access public */ public function GetPage() { return $this->Page; } /** * Sets list query offset * * @param int $offset * @access public */ public function SetOffset($offset) { $this->Offset = $offset; } /** * Gets list query offset * * @return int * @access public */ public function GetOffset() { return $this->Offset; } /** * Sets current item field value (doesn't apply formatting) * * @param string $name Name of the field * @param mixed $value Value to set the field to * @access public */ public function SetDBField($name,$value) { $this->Records[$this->CurrentIndex][$name] = $value; } /** * Apply where clause, that links this object to it's parent item * * @param string $special * @access public */ public function linkToParent($special) { $config = $this->getUnitConfig(); $parent_prefix = $config->getParentPrefix(); if ( $parent_prefix ) { $parent_table_key = $config->getParentTableKey($parent_prefix); $foreign_key_field = $config->getForeignKey($parent_prefix); if ( !$parent_table_key || !$foreign_key_field ) { return; } + /** @var kDBItem $parent_object */ $parent_object = $this->Application->recallObject($parent_prefix . '.' . $special); - /* @var $parent_object kDBItem */ if ( !$parent_object->isLoaded() ) { $this->addFilter('parent_filter', 'FALSE'); trigger_error('Parent ID not found (prefix: "' . rtrim($parent_prefix . '.' . $special, '.') . '"; sub-prefix: "' . $this->getPrefixSpecial() . '")', E_USER_NOTICE); return; } // only for list in this case $parent_id = $parent_object->GetDBField($parent_table_key); $this->addFilter('parent_filter', '`' . $this->TableName . '`.`' . $foreign_key_field . '` = ' . $this->Conn->qstr($parent_id)); } } /** * Returns true if list was queried (same name as for kDBItem for easy usage) * * @return bool * @access public */ public function isLoaded() { return $this->Queried && !$this->EOL(); } /** * Returns specified field value from all selected rows. * Don't affect current record index * * @param string $field * @param bool $formatted * @param string $format * @return Array * @access public */ public function GetCol($field, $formatted = false, $format = null) { $i = 0; $ret = Array (); if ($formatted && array_key_exists('formatter', $this->Fields[$field])) { + /** @var kFormatter $formatter */ $formatter = $this->Application->recallObject($this->Fields[$field]['formatter']); - /* @var $formatter kFormatter */ while ($i < $this->SelectedCount) { $ret[] = $formatter->Format($this->Records[$i][$field], $field, $this, $format); $i++; } } else { while ($i < $this->SelectedCount) { $ret[] = $this->Records[$i][$field]; $i++; } } return $ret; } /** * Set's field error, if pseudo passed not found then create it with message text supplied. * Don't overwrite existing pseudo translation. * * @param string $field * @param string $pseudo * @param string $error_label * @param Array $error_params * @return bool * @access public * @see kSearchHelper::processRangeField() * @see kDateFormatter::Parse() */ public function SetError($field, $pseudo, $error_label = null, $error_params = null) { $error_field = isset($this->Fields[$field]['error_field']) ? $this->Fields[$field]['error_field'] : $field; $this->FieldErrors[$error_field]['pseudo'] = $pseudo; $var_name = $this->getPrefixSpecial() . '_' . $field . '_error'; $previous_pseudo = $this->Application->RecallVar($var_name); if ( $previous_pseudo ) { // don't set more then one error on field return false; } $this->Application->StoreVar($var_name, $pseudo); return true; } /** * Returns error pseudo * * @param string $field * @return string * @access public * @see kSearchHelper::processRangeField() */ public function GetErrorPseudo($field) { if ( !isset($this->FieldErrors[$field]) ) { return ''; } return isset($this->FieldErrors[$field]['pseudo']) ? $this->FieldErrors[$field]['pseudo'] : ''; } /** * Removes error on field * * @param string $field * @access public */ public function RemoveError($field) { unset( $this->FieldErrors[$field] ); } /** * Group list records by header, saves internal order in group * * @param string $heading_field * @access public */ public function groupRecords($heading_field) { $i = 0; $sorted = Array (); while ($i < $this->SelectedCount) { $sorted[ $this->Records[$i][$heading_field] ][] = $this->Records[$i]; $i++; } $this->Records = Array (); foreach ($sorted as $heading => $heading_records) { $this->Records = array_merge_recursive($this->Records, $heading_records); } } /** * Reset list (use for requering purposes) * * @access public */ public function reset() { $this->Counted = false; $this->clearFilters(); $this->ClearOrderFields(); } /** * Checks if list was counted * * @return bool * @access public */ public function isCounted() { return $this->Counted; } /** * Tells, that given list is main * * @return bool * @access public */ public function isMainList() { return $this->mainList; } /** * Makes given list as main * * @access public */ public function becameMain() { $this->mainList = true; } /** * Moves recordset pointer to first element * * @return void * @access public * @implements Iterator::rewind */ public function rewind() { $this->Query(); $this->GoFirst(); } /** * Returns value at current position * * @return mixed * @access public * @implements Iterator::current */ function current() { return $this->getCurrentRecord(); } /** * Returns key at current position * * @return mixed * @access public * @implements Iterator::key */ function key() { return $this->CurrentIndex; } /** * Moves recordset pointer to next position * * @return void * @access public * @implements Iterator::next */ function next() { $this->GoNext(); } /** * Detects if current position is within recordset bounds * * @return bool * @access public * @implements Iterator::valid */ public function valid() { return !$this->EOL(); } /** * Counts recordset rows * * @return int * @access public * @implements Countable::count */ public function count() { return $this->SelectedCount; } } class LeftJoinOptimizer { /** * Input sql for optimization * * @var string * @access private */ private $sql = ''; /** * All sql parts, where LEFT JOINed table aliases could be used * * @var string * @access private */ private $usageString = ''; /** * List of discovered LEFT JOINs * * @var Array * @access private */ private $joins = Array (); /** * LEFT JOIN relations * * @var Array * @access private */ private $joinRelations = Array (); /** * LEFT JOIN table aliases scheduled for removal * * @var Array * @access private */ private $aliasesToRemove = Array (); /** * Creates new instance of the class * * @param string $sql * @param string $usage_string */ public function __construct($sql, $usage_string) { $this->sql = $sql; $this->usageString = $usage_string; $this->parseJoins(); } /** * Tries to remove unused LEFT JOINs * * @return string * @access public */ public function simplify() { if ( !$this->joins ) { // no LEFT JOIN used, return unchanged sql return $this->sql; } $this->updateRelations(); $this->removeAliases(); return $this->sql; } /** * Discovers LEFT JOINs based on given sql * * @return void * @access private */ private function parseJoins() { if ( !preg_match_all('/LEFT\s+JOIN\s+(.*?|.*?\s+AS\s+.*?|.*?\s+.*?)\s+ON\s+(.*?\n|.*?$)/si', $this->sql, $regs) ) { $this->joins = Array (); } // get all LEFT JOIN clause info from sql (without filters) foreach ($regs[1] as $index => $match) { $match_parts = preg_split('/\s+AS\s+|\s+/i', $match, 2); $table_alias = count($match_parts) == 1 ? $match : $match_parts[1]; $this->joins[$table_alias] = Array ( 'table' => $match_parts[0], 'join_clause' => $regs[0][$index], ); } } /** * Detects relations between LEFT JOINs * * @return void * @access private */ private function updateRelations() { foreach ($this->joins as $table_alias => $left_join_info) { $escaped_alias = preg_quote($table_alias, '/'); foreach ($this->joins as $sub_table_alias => $sub_left_join_info) { if ($table_alias == $sub_table_alias) { continue; } if ( $this->matchAlias($escaped_alias, $sub_left_join_info['join_clause']) ) { $this->joinRelations[] = $sub_table_alias . ':' . $table_alias; } } } } /** * Removes scheduled LEFT JOINs, but only if they are not protected * * @return void * @access private */ private function removeAliases() { $this->prepareAliasesRemoval(); foreach ($this->aliasesToRemove as $to_remove_alias) { if ( !$this->aliasProtected($to_remove_alias) ) { $this->sql = str_replace($this->joins[$to_remove_alias]['join_clause'], '', $this->sql); } } } /** * Schedules unused LEFT JOINs to for removal * * @return void * @access private */ private function prepareAliasesRemoval() { foreach ($this->joins as $table_alias => $left_join_info) { $escaped_alias = preg_quote($table_alias, '/'); if ( !$this->matchAlias($escaped_alias, $this->usageString) ) { $this->aliasesToRemove[] = $table_alias; } } } /** * Checks if someone wants to remove LEFT JOIN, but it's used by some other LEFT JOIN, that stays * * @param string $table_alias * @return bool * @access private */ private function aliasProtected($table_alias) { foreach ($this->joinRelations as $relation) { list ($main_alias, $used_alias) = explode(':', $relation); if ( ($used_alias == $table_alias) && !in_array($main_alias, $this->aliasesToRemove) ) { return true; } } return false; } /** * Matches given escaped alias to a string * * @param string $escaped_alias * @param string $string * @return bool * @access private */ private function matchAlias($escaped_alias, $string) { return preg_match('/(`' . $escaped_alias . '`|' . $escaped_alias . ')\./', $string); } } Index: branches/5.3.x/core/kernel/db/db_event_handler.php =================================================================== --- branches/5.3.x/core/kernel/db/db_event_handler.php (revision 16518) +++ branches/5.3.x/core/kernel/db/db_event_handler.php (revision 16519) @@ -1,3453 +1,3455 @@ getPrefixSpecial(true) instead of * $event->getPrefixSpecial() as usual. This is due PHP * is converting "." symbols in variable names during * submit info "_". $event->getPrefixSpecial optional * 1st parameter returns correct current Prefix_Special * for variables being submitted such way (e.g. variable * name that will be converted by PHP: "users.read_only_id" * will be submitted as "users_read_only_id". * * 2. When using $this->Application-LinkVar on variables submitted * from form which contain $Prefix_Special then note 1st item. Example: * LinkVar($event->getPrefixSpecial(true).'_varname',$event->getPrefixSpecial().'_varname') * */ /** * EventHandler that is used to process * any database related events * */ class kDBEventHandler extends kEventHandler { /** * Checks permissions of user * * @param kEvent $event * @return bool * @access public */ public function CheckPermission(kEvent $event) { $section = $event->getSection(); if ( !$this->Application->isAdmin ) { $allow_events = Array ('OnSearch', 'OnSearchReset', 'OnNew'); if ( in_array($event->Name, $allow_events) ) { // allow search on front return true; } } elseif ( ($event->Name == 'OnPreSaveAndChangeLanguage') && !$this->UseTempTables($event) ) { // allow changing language in grids, when not in editing mode return $this->Application->CheckPermission($section . '.view', 1); } if ( !preg_match('/^CATEGORY:(.*)/', $section) ) { // only if not category item events if ( (substr($event->Name, 0, 9) == 'OnPreSave') || ($event->Name == 'OnSave') ) { if ( $this->isNewItemCreate($event) ) { return $this->Application->CheckPermission($section . '.add', 1); } else { return $this->Application->CheckPermission($section . '.add', 1) || $this->Application->CheckPermission($section . '.edit', 1); } } } if ( $event->Name == 'OnPreCreate' ) { // save category_id before item create (for item category selector not to destroy permission checking category) $this->Application->LinkVar('m_cat_id'); } return parent::CheckPermission($event); } /** * Allows to override standard permission mapping * * @return void * @access protected * @see kEventHandler::$permMapping */ protected function mapPermissions() { parent::mapPermissions(); $permissions = Array ( 'OnLoad' => Array ('self' => 'view', 'subitem' => 'view'), 'OnItemBuild' => Array ('self' => 'view', 'subitem' => 'view'), 'OnSuggestValues' => Array ('self' => 'admin', 'subitem' => 'admin'), 'OnSuggestValuesJSON' => Array ('self' => 'admin', 'subitem' => 'admin'), 'OnBuild' => Array ('self' => true), 'OnNew' => Array ('self' => 'add', 'subitem' => 'add|edit'), 'OnCreate' => Array ('self' => 'add', 'subitem' => 'add|edit'), 'OnUpdate' => Array ('self' => 'edit', 'subitem' => 'add|edit'), 'OnSetPrimary' => Array ('self' => 'add|edit', 'subitem' => 'add|edit'), 'OnDelete' => Array ('self' => 'delete', 'subitem' => 'add|edit'), 'OnDeleteAll' => Array ('self' => 'delete', 'subitem' => 'add|edit'), 'OnMassDelete' => Array ('self' => 'delete', 'subitem' => 'add|edit'), 'OnMassClone' => Array ('self' => 'add', 'subitem' => 'add|edit'), 'OnCut' => Array ('self'=>'edit', 'subitem' => 'edit'), 'OnCopy' => Array ('self'=>'edit', 'subitem' => 'edit'), 'OnPaste' => Array ('self'=>'edit', 'subitem' => 'edit'), 'OnSelectItems' => Array ('self' => 'add|edit', 'subitem' => 'add|edit'), 'OnProcessSelected' => Array ('self' => 'add|edit', 'subitem' => 'add|edit'), 'OnStoreSelected' => Array ('self' => 'add|edit', 'subitem' => 'add|edit'), 'OnSelectUser' => Array ('self' => 'add|edit', 'subitem' => 'add|edit'), 'OnMassApprove' => Array ('self' => 'advanced:approve|edit', 'subitem' => 'advanced:approve|add|edit'), 'OnMassDecline' => Array ('self' => 'advanced:decline|edit', 'subitem' => 'advanced:decline|add|edit'), 'OnMassMoveUp' => Array ('self' => 'advanced:move_up|edit', 'subitem' => 'advanced:move_up|add|edit'), 'OnMassMoveDown' => Array ('self' => 'advanced:move_down|edit', 'subitem' => 'advanced:move_down|add|edit'), 'OnPreCreate' => Array ('self' => 'add|add.pending', 'subitem' => 'edit|edit.pending'), 'OnEdit' => Array ('self' => 'edit|edit.pending', 'subitem' => 'edit|edit.pending'), 'OnExport' => Array ('self' => 'view|advanced:export'), 'OnExportBegin' => Array ('self' => 'view|advanced:export'), 'OnExportProgress' => Array ('self' => 'view|advanced:export'), 'OnSetAutoRefreshInterval' => Array ('self' => true, 'subitem' => true), 'OnAutoRefreshToggle' => Array ('self' => true, 'subitem' => true), // theese event do not harm, but just in case check them too :) 'OnCancelEdit' => Array ('self' => true, 'subitem' => true), 'OnCancel' => Array ('self' => true, 'subitem' => true), 'OnReset' => Array ('self' => true, 'subitem' => true), 'OnSetSorting' => Array ('self' => true, 'subitem' => true), 'OnSetSortingDirect' => Array ('self' => true, 'subitem' => true), 'OnResetSorting' => Array ('self' => true, 'subitem' => true), 'OnSetFilter' => Array ('self' => true, 'subitem' => true), 'OnApplyFilters' => Array ('self' => true, 'subitem' => true), 'OnRemoveFilters' => Array ('self' => true, 'subitem' => true), 'OnSetFilterPattern' => Array ('self' => true, 'subitem' => true), 'OnSetPerPage' => Array ('self' => true, 'subitem' => true), 'OnSetPage' => Array ('self' => true, 'subitem' => true), 'OnSearch' => Array ('self' => true, 'subitem' => true), 'OnSearchReset' => Array ('self' => true, 'subitem' => true), 'OnGoBack' => Array ('self' => true, 'subitem' => true), // it checks permission itself since flash uploader does not send cookies 'OnUploadFile' => Array ('self' => true, 'subitem' => true), 'OnDeleteFile' => Array ('self' => true, 'subitem' => true), 'OnViewFile' => Array ('self' => true, 'subitem' => true), 'OnSaveWidths' => Array ('self' => 'admin', 'subitem' => 'admin'), 'OnValidateMInputFields' => Array ('self' => 'view'), 'OnValidateField' => Array ('self' => true, 'subitem' => true), ); $this->permMapping = array_merge($this->permMapping, $permissions); } /** * Define alternative event processing method names * * @return void * @see kEventHandler::$eventMethods * @access protected */ protected function mapEvents() { $events_map = Array ( 'OnRemoveFilters' => 'FilterAction', 'OnApplyFilters' => 'FilterAction', 'OnMassApprove' => 'iterateItems', 'OnMassDecline' => 'iterateItems', 'OnMassMoveUp' => 'iterateItems', 'OnMassMoveDown' => 'iterateItems', ); $this->eventMethods = array_merge($this->eventMethods, $events_map); } /** * Returns ID of current item to be edited * by checking ID passed in get/post as prefix_id * or by looking at first from selected ids, stored. * Returned id is also stored in Session in case * it was explicitly passed as get/post * * @param kEvent $event * @return int * @access public */ public function getPassedID(kEvent $event) { if ( $event->getEventParam('raise_warnings') === false ) { $event->setEventParam('raise_warnings', 1); } if ( $event->Special == 'previous' || $event->Special == 'next' ) { + /** @var kDBItem $object */ $object = $this->Application->recallObject($event->getEventParam('item')); - /* @var $object kDBItem */ + /** @var ListHelper $list_helper */ $list_helper = $this->Application->recallObject('ListHelper'); - /* @var $list_helper ListHelper */ $select_clause = $object->getUnitConfig()->getNavigationSelectClause(NULL); return $list_helper->getNavigationResource($object, $event->getEventParam('list'), $event->Special == 'next', $select_clause); } elseif ( $event->Special == 'filter' ) { // temporary object, used to print filter options only return 0; } if ( preg_match('/^auto-(.*)/', $event->Special, $regs) && $this->Application->prefixRegistred($regs[1]) ) { // - returns field DateFormat value from language (LanguageId is extracted from current phrase object) + /** @var kDBItem $main_object */ $main_object = $this->Application->recallObject($regs[1]); - /* @var $main_object kDBItem */ return $main_object->GetDBField($event->getUnitConfig()->getIDField()); } // 1. get id from post (used in admin) $ret = $this->Application->GetVar($event->getPrefixSpecial(true) . '_id'); if ( ($ret !== false) && ($ret != '') ) { $event->setEventParam(kEvent::FLAG_ID_FROM_REQUEST, true); return $ret; } // 2. get id from env (used in front) $ret = $this->Application->GetVar($event->getPrefixSpecial() . '_id'); if ( ($ret !== false) && ($ret != '') ) { $event->setEventParam(kEvent::FLAG_ID_FROM_REQUEST, true); return $ret; } // recall selected ids array and use the first one $ids = $this->Application->GetVar($event->getPrefixSpecial() . '_selected_ids'); if ( $ids != '' ) { $ids = explode(',', $ids); if ( $ids ) { $ret = array_shift($ids); $event->setEventParam(kEvent::FLAG_ID_FROM_REQUEST, true); } } else { // if selected ids are not yet stored $this->StoreSelectedIDs($event); // StoreSelectedIDs sets this variable. $ret = $this->Application->GetVar($event->getPrefixSpecial() . '_id'); if ( ($ret !== false) && ($ret != '') ) { $event->setEventParam(kEvent::FLAG_ID_FROM_REQUEST, true); return $ret; } } return $ret; } /** * Prepares and stores selected_ids string * in Session and Application Variables * by getting all checked ids from grid plus * id passed in get/post as prefix_id * * @param kEvent $event * @param Array $direct_ids * @return Array * @access protected */ protected function StoreSelectedIDs(kEvent $event, $direct_ids = NULL) { $wid = $this->Application->GetTopmostWid($event->Prefix); $session_name = rtrim($event->getPrefixSpecial() . '_selected_ids_' . $wid, '_'); $ids = $event->getEventParam('ids'); if ( isset($direct_ids) || ($ids !== false) ) { // save ids directly if they given + reset array indexes $resulting_ids = $direct_ids ? array_values($direct_ids) : ($ids ? array_values($ids) : false); if ( $resulting_ids ) { $this->Application->SetVar($event->getPrefixSpecial() . '_selected_ids', implode(',', $resulting_ids)); $this->Application->LinkVar($event->getPrefixSpecial() . '_selected_ids', $session_name, '', true); $this->Application->SetVar($event->getPrefixSpecial() . '_id', $resulting_ids[0]); return $resulting_ids; } return Array (); } $ret = Array (); // May be we don't need this part: ? $passed = $this->Application->GetVar($event->getPrefixSpecial(true) . '_id'); if ( $passed !== false && $passed != '' ) { array_push($ret, $passed); } $ids = Array (); // get selected ids from post & save them to session $items_info = $this->Application->GetVar($event->getPrefixSpecial(true)); if ( $items_info ) { $id_field = $event->getUnitConfig()->getIDField(); foreach ($items_info as $id => $field_values) { if ( getArrayValue($field_values, $id_field) ) { array_push($ids, $id); } } //$ids = array_keys($items_info); } $ret = array_unique(array_merge($ret, $ids)); $this->Application->SetVar($event->getPrefixSpecial() . '_selected_ids', implode(',', $ret)); $this->Application->LinkVar($event->getPrefixSpecial() . '_selected_ids', $session_name, '', !$ret); // optional when IDs are missing // This is critical - otherwise getPassedID will return last ID stored in session! (not exactly true) // this smells... needs to be refactored $first_id = getArrayValue($ret, 0); if ( ($first_id === false) && ($event->getEventParam('raise_warnings') == 1) ) { if ( $this->Application->isDebugMode() ) { $this->Application->Debugger->appendTrace(); } trigger_error('Requested ID for prefix ' . $event->getPrefixSpecial() . ' not passed', E_USER_NOTICE); } $this->Application->SetVar($event->getPrefixSpecial() . '_id', $first_id); return $ret; } /** * Returns stored selected ids as an array * * @param kEvent $event * @param bool $from_session return ids from session (written, when editing was started) * @return Array * @access protected */ protected function getSelectedIDs(kEvent $event, $from_session = false) { if ( $from_session ) { $wid = $this->Application->GetTopmostWid($event->Prefix); $var_name = rtrim($event->getPrefixSpecial() . '_selected_ids_' . $wid, '_'); $ret = $this->Application->RecallVar($var_name); } else { $ret = $this->Application->GetVar($event->getPrefixSpecial() . '_selected_ids'); } return explode(',', $ret); } /** * Stores IDs, selected in grid in session * * @param kEvent $event * @return void * @access protected */ protected function OnStoreSelected(kEvent $event) { $this->StoreSelectedIDs($event); $id = $this->Application->GetVar($event->getPrefixSpecial() . '_id'); if ( $id !== false ) { $event->SetRedirectParam($event->getPrefixSpecial() . '_id', $id); $event->SetRedirectParam('pass', 'all,' . $event->getPrefixSpecial()); } } /** * Returns associative array of submitted fields for current item * Could be used while creating/editing single item - * meaning on any edit form, except grid edit * * @param kEvent $event * @return Array * @access protected */ protected function getSubmittedFields(kEvent $event) { $items_info = $this->Application->GetVar($event->getPrefixSpecial(true)); $field_values = $items_info ? array_shift($items_info) : Array (); return $field_values; } /** * Removes any information about current/selected ids * from Application variables and Session * * @param kEvent $event * @return void * @access protected */ protected function clearSelectedIDs(kEvent $event) { $prefix_special = $event->getPrefixSpecial(); $ids = implode(',', $this->getSelectedIDs($event, true)); $event->setEventParam('ids', $ids); $wid = $this->Application->GetTopmostWid($event->Prefix); $session_name = rtrim($prefix_special . '_selected_ids_' . $wid, '_'); $this->Application->RemoveVar($session_name); $this->Application->SetVar($prefix_special . '_selected_ids', ''); $this->Application->SetVar($prefix_special . '_id', ''); // $event->getPrefixSpecial(true) . '_id' too may be } /** * Common builder part for Item & List * * @param kDBBase|kDBItem|kDBList $object * @param kEvent $event * @return void * @access protected */ protected function dbBuild(&$object, kEvent $event) { // for permission checking inside item/list build events $event->setEventParam('top_prefix', $this->Application->GetTopmostPrefix($event->Prefix, true)); if ( $event->getEventParam('form_name') !== false ) { $form_name = $event->getEventParam('form_name'); } else { $request_forms = $this->Application->GetVar('forms', Array ()); $form_name = (string)getArrayValue($request_forms, $object->getPrefixSpecial()); } $object->Configure($event->getEventParam('populate_ml_fields') || $event->getUnitConfig()->getPopulateMlFields(), $form_name); $this->PrepareObject($object, $event); $parent_event = $event->getEventParam('parent_event'); if ( is_object($parent_event) ) { $object->setParentEvent($parent_event); } // force live table if specified or is original item $live_table = $event->getEventParam('live_table') || $event->Special == 'original'; if ( $this->UseTempTables($event) && !$live_table ) { $object->SwitchToTemp(); } $this->Application->setEvent($event->getPrefixSpecial(), ''); $save_event = $this->UseTempTables($event) && $this->Application->GetTopmostPrefix($event->Prefix) == $event->Prefix ? 'OnSave' : 'OnUpdate'; $this->Application->SetVar($event->getPrefixSpecial() . '_SaveEvent', $save_event); } /** * Checks, that currently loaded item is allowed for viewing (non permission-based) * * @param kEvent $event * @return bool * @access protected */ protected function checkItemStatus(kEvent $event) { $status_field = $event->getUnitConfig()->getStatusField(true); if ( !$status_field ) { return true; } if ( $status_field == 'Status' || $status_field == 'Enabled' ) { + /** @var kDBItem $object */ $object = $event->getObject(); - /* @var $object kDBItem */ if ( !$object->isLoaded() ) { return true; } return $object->GetDBField($status_field) == STATUS_ACTIVE; } return true; } /** * Shows not found template content * * @param kEvent $event * @return void * @access protected */ protected function _errorNotFound(kEvent $event) { if ( $event->getEventParam('raise_warnings') === 0 ) { // when it's possible, that autoload fails do nothing return; } if ( $this->Application->isDebugMode() ) { $this->Application->Debugger->appendTrace(); } trigger_error('ItemLoad Permission Failed for prefix [' . $event->getPrefixSpecial() . '] in checkItemStatus, leading to "404 Not Found"', E_USER_NOTICE); $this->Application->UrlManager->show404(); } /** * Builds item (loads if needed) * * Pattern: Prototype Manager * * @param kEvent $event * @access protected */ protected function OnItemBuild(kEvent $event) { + /** @var kDBItem $object */ $object = $event->getObject(); - /* @var $object kDBItem */ $this->dbBuild($object, $event); $sql = $this->ItemPrepareQuery($event); $sql = $this->Application->ReplaceLanguageTags($sql); $object->setSelectSQL($sql); // 2. loads if allowed $auto_load = $event->getUnitConfig()->getAutoLoad(); $skip_autoload = $event->getEventParam('skip_autoload'); if ( $auto_load && !$skip_autoload ) { $perm_status = true; $user_id = $this->Application->InitDone ? $this->Application->RecallVar('user_id') : USER_ROOT; $event->setEventParam('top_prefix', $this->Application->GetTopmostPrefix($event->Prefix, true)); $status_checked = false; if ( $user_id == USER_ROOT || $this->CheckPermission($event) ) { // don't autoload item, when user doesn't have view permission $this->LoadItem($event); $status_checked = true; $editing_mode = defined('EDITING_MODE') ? EDITING_MODE : false; $id_from_request = $event->getEventParam(kEvent::FLAG_ID_FROM_REQUEST); if ( $user_id != USER_ROOT && !$this->Application->isAdmin && !($editing_mode || ($id_from_request ? $this->checkItemStatus($event) : true)) ) { // non-root user AND on front-end AND (not editing mode || incorrect status) $perm_status = false; } } else { $perm_status = false; } if ( !$perm_status ) { // when no permission to view item -> redirect to no permission template $this->_processItemLoadingError($event, $status_checked); } } + /** @var Params $actions */ $actions = $this->Application->recallObject('kActions'); - /* @var $actions Params */ $actions->Set($event->getPrefixSpecial() . '_GoTab', ''); $actions->Set($event->getPrefixSpecial() . '_GoId', ''); $actions->Set('forms[' . $event->getPrefixSpecial() . ']', $object->getFormName()); } /** * Processes case, when item wasn't loaded because of lack of permissions * * @param kEvent $event * @param bool $status_checked * @throws kNoPermissionException * @return void * @access protected */ protected function _processItemLoadingError($event, $status_checked) { $current_template = $this->Application->GetVar('t'); $redirect_template = $this->Application->isAdmin ? 'no_permission' : $this->Application->ConfigValue('NoPermissionTemplate'); $error_msg = 'ItemLoad Permission Failed for prefix [' . $event->getPrefixSpecial() . '] in ' . ($status_checked ? 'checkItemStatus' : 'CheckPermission') . ''; if ( $current_template == $redirect_template ) { // don't perform "no_permission" redirect if already on a "no_permission" template if ( $this->Application->isDebugMode() ) { $this->Application->Debugger->appendTrace(); } trigger_error($error_msg, E_USER_NOTICE); return; } if ( MOD_REWRITE ) { $redirect_params = Array ( 'm_cat_id' => 0, 'next_template' => 'external:' . $_SERVER['REQUEST_URI'], ); } else { $redirect_params = Array ( 'next_template' => $current_template, ); } $exception = new kNoPermissionException($error_msg); $exception->setup($redirect_template, $redirect_params); throw $exception; } /** * Build sub-tables array from configs * * @param kEvent $event * @return void * @access protected */ protected function OnTempHandlerBuild(kEvent $event) { + /** @var kTempTablesHandler $object */ $object = $this->Application->recallObject($event->getPrefixSpecial() . '_TempHandler', 'kTempTablesHandler'); - /* @var $object kTempTablesHandler */ + /** @var kEvent $parent_event */ $parent_event = $event->getEventParam('parent_event'); - /* @var $parent_event kEvent */ if ( is_object($parent_event) ) { $object->setParentEvent($parent_event); } $object->BuildTables($event->Prefix, $this->getSelectedIDs($event)); } /** * Checks, that object used in event should use temp tables * * @param kEvent $event * @return bool * @access protected */ protected function UseTempTables(kEvent $event) { $top_prefix = $this->Application->GetTopmostPrefix($event->Prefix); // passed parent, not always actual $special = ($top_prefix == $event->Prefix) ? $event->Special : $this->getMainSpecial($event); return $this->Application->IsTempMode($event->Prefix, $special); } /** * Load item if id is available * * @param kEvent $event * @return void * @access protected */ protected function LoadItem(kEvent $event) { + /** @var kDBItem $object */ $object = $event->getObject(); - /* @var $object kDBItem */ $id = $this->getPassedID($event); if ( $object->isLoaded() && !is_array($id) && ($object->GetID() == $id) ) { // object is already loaded by same id return ; } if ( $object->Load($id) ) { + /** @var Params $actions */ $actions = $this->Application->recallObject('kActions'); - /* @var $actions Params */ $actions->Set($event->getPrefixSpecial() . '_id', $object->GetID()); } else { $object->setID( is_array($id) ? false : $id ); } } /** * Builds list * * Pattern: Prototype Manager * * @param kEvent $event * @access protected */ protected function OnListBuild(kEvent $event) { + /** @var kDBList $object */ $object = $event->getObject(); - /* @var $object kDBList */ /*if ( $this->Application->isDebugMode() ) { $event_params = http_build_query($event->getEventParams()); $this->Application->Debugger->appendHTML('InitList "' . $event->getPrefixSpecial() . '" (' . $event_params . ')'); }*/ $this->dbBuild($object, $event); if ( !$object->isMainList() && $event->getEventParam('main_list') ) { // once list is set to main, then even "requery" parameter can't remove that /*$passed = $this->Application->GetVar('passed'); $this->Application->SetVar('passed', $passed . ',' . $event->Prefix);*/ $object->becameMain(); } $object->setGridName($event->getEventParam('grid')); $sql = $this->ListPrepareQuery($event); $sql = $this->Application->ReplaceLanguageTags($sql); $object->setSelectSQL($sql); $object->reset(); if ( $event->getEventParam('skip_parent_filter') === false ) { $object->linkToParent($this->getMainSpecial($event)); } $this->AddFilters($event); $this->SetCustomQuery($event); // new!, use this for dynamic queries based on specials for ex. $this->SetPagination($event); $this->SetSorting($event); + /** @var Params $actions */ $actions = $this->Application->recallObject('kActions'); - /* @var $actions Params */ $actions->Set('remove_specials[' . $event->getPrefixSpecial() . ']', '0'); $actions->Set($event->getPrefixSpecial() . '_GoTab', ''); } /** * Returns special of main item for linking with sub-item * * @param kEvent $event * @return string * @access protected */ protected function getMainSpecial(kEvent $event) { $main_special = $event->getEventParam('main_special'); if ( $main_special === false ) { // main item's special not passed if ( substr($event->Special, -5) == '-item' ) { // temp handler added "-item" to given special -> process that here return substr($event->Special, 0, -5); } // by default subitem's special is used for main item searching return $event->Special; } return $main_special; } /** * Apply any custom changes to list's sql query * * @param kEvent $event * @return void * @access protected * @see kDBEventHandler::OnListBuild() */ protected function SetCustomQuery(kEvent $event) { } /** * Set's new per-page for grid * * @param kEvent $event * @return void * @access protected */ protected function OnSetPerPage(kEvent $event) { $per_page = $this->Application->GetVar($event->getPrefixSpecial(true) . '_PerPage'); $event->SetRedirectParam($event->getPrefixSpecial() . '_PerPage', $per_page); $event->SetRedirectParam('pass', 'all,' . $event->getPrefixSpecial()); if ( !$this->Application->isAdminUser ) { + /** @var ListHelper $list_helper */ $list_helper = $this->Application->recallObject('ListHelper'); - /* @var $list_helper ListHelper */ $this->_passListParams($event, 'per_page'); } } /** * Occurs when page is changed (only for hooking) * * @param kEvent $event * @return void * @access protected */ protected function OnSetPage(kEvent $event) { $page = $this->Application->GetVar($event->getPrefixSpecial(true) . '_Page'); $event->SetRedirectParam($event->getPrefixSpecial() . '_Page', $page); $event->SetRedirectParam('pass', 'all,' . $event->getPrefixSpecial()); if ( !$this->Application->isAdminUser ) { $this->_passListParams($event, 'page'); } } /** * Passes through main list pagination and sorting * * @param kEvent $event * @param string $skip_var * @return void * @access protected */ protected function _passListParams($event, $skip_var) { $param_names = array_diff(Array ('page', 'per_page', 'sort_by'), Array ($skip_var)); + /** @var ListHelper $list_helper */ $list_helper = $this->Application->recallObject('ListHelper'); - /* @var $list_helper ListHelper */ foreach ($param_names as $param_name) { $value = $this->Application->GetVar($param_name); switch ($param_name) { case 'page': if ( $value > 1 ) { $event->SetRedirectParam('page', $value); } break; case 'per_page': if ( $value > 0 ) { if ( $value != $list_helper->getDefaultPerPage($event->Prefix) ) { $event->SetRedirectParam('per_page', $value); } } break; case 'sort_by': $event->setPseudoClass('_List'); + + /** @var kDBList $object */ $object = $event->getObject(Array ('main_list' => 1)); - /* @var $object kDBList */ if ( $list_helper->hasUserSorting($object) ) { $event->SetRedirectParam('sort_by', $value); } break; } } } /** * Set's correct page for list based on data provided with event * * @param kEvent $event * @return void * @access protected * @see kDBEventHandler::OnListBuild() */ protected function SetPagination(kEvent $event) { + /** @var kDBList $object */ $object = $event->getObject(); - /* @var $object kDBList */ // get PerPage (forced -> session -> config -> 10) $object->SetPerPage($this->getPerPage($event)); // main lists on Front-End have special get parameter for page $page = $object->isMainList() ? $this->Application->GetVar('page') : false; if ( !$page ) { // page is given in "env" variable for given prefix $page = $this->Application->GetVar($event->getPrefixSpecial() . '_Page'); } if ( !$page && $event->Special ) { // when not part of env, then variables like "prefix.special_Page" are // replaced (by PHP) with "prefix_special_Page", so check for that too $page = $this->Application->GetVar($event->getPrefixSpecial(true) . '_Page'); } if ( !$object->isMainList() ) { // main lists doesn't use session for page storing $this->Application->StoreVarDefault($event->getPrefixSpecial() . '_Page', 1, true); // true for optional if ( $page ) { // page found in request -> store in session $this->Application->StoreVar($event->getPrefixSpecial() . '_Page', $page, true); //true for optional } else { // page not found in request -> get from session $page = $this->Application->RecallVar($event->getPrefixSpecial() . '_Page'); } if ( !$event->getEventParam('skip_counting') ) { // when stored page is larger, then maximal list page number // (such case is also processed in kDBList::Query method) $pages = $object->GetTotalPages(); if ( $page > $pages ) { $page = 1; $this->Application->StoreVar($event->getPrefixSpecial() . '_Page', 1, true); } } } $object->SetPage($page); } /** * Returns current per-page setting for list * * @param kEvent $event * @return int * @access protected */ protected function getPerPage(kEvent $event) { + /** @var kDBList $object */ $object = $event->getObject(); - /* @var $object kDBList */ $per_page = $event->getEventParam('per_page'); if ( $per_page ) { // per-page is passed as tag parameter to PrintList, InitList, etc. $config_mapping = $event->getUnitConfig()->getConfigMapping(); // 2. per-page setting is stored in configuration variable if ( $config_mapping ) { // such pseudo per-pages are only defined in templates directly switch ($per_page) { case 'short_list': $per_page = $this->Application->ConfigValue($config_mapping['ShortListPerPage']); break; case 'default': $per_page = $this->Application->ConfigValue($config_mapping['PerPage']); break; } } return $per_page; } if ( !$per_page && $object->isMainList() ) { // main lists on Front-End have special get parameter for per-page $per_page = $this->Application->GetVar('per_page'); } if ( !$per_page ) { // per-page is given in "env" variable for given prefix $per_page = $this->Application->GetVar($event->getPrefixSpecial() . '_PerPage'); } if ( !$per_page && $event->Special ) { // when not part of env, then variables like "prefix.special_PerPage" are // replaced (by PHP) with "prefix_special_PerPage", so check for that too $per_page = $this->Application->GetVar($event->getPrefixSpecial(true) . '_PerPage'); } if ( !$object->isMainList() ) { // per-page given in env and not in main list $view_name = $this->Application->RecallVar($event->getPrefixSpecial() . '_current_view'); if ( $per_page ) { // per-page found in request -> store in session and persistent session $this->setListSetting($event, 'PerPage', $per_page); } else { // per-page not found in request -> get from pesistent session (or session) $per_page = $this->getListSetting($event, 'PerPage'); } } if ( !$per_page ) { // per page wan't found in request/session/persistent session + /** @var ListHelper $list_helper */ $list_helper = $this->Application->recallObject('ListHelper'); - /* @var $list_helper ListHelper */ // allow to override default per-page value from tag $default_per_page = $event->getEventParam('default_per_page'); if ( !is_numeric($default_per_page) ) { $default_per_page = $this->Application->ConfigValue('DefaultGridPerPage'); } $per_page = $list_helper->getDefaultPerPage($event->Prefix, $default_per_page); } return $per_page; } /** * Set's correct sorting for list based on data provided with event * * @param kEvent $event * @return void * @access protected * @see kDBEventHandler::OnListBuild() */ protected function SetSorting(kEvent $event) { $event->setPseudoClass('_List'); + /** @var kDBList $object */ $object = $event->getObject(); - /* @var $object kDBList */ if ( $object->isMainList() ) { $sort_by = $this->Application->GetVar('sort_by'); $cur_sort1 = $cur_sort1_dir = $cur_sort2 = $cur_sort2_dir = false; if ( $sort_by ) { $sortings = explode('|', $sort_by); list ($cur_sort1, $cur_sort1_dir) = explode(',', $sortings[0]); if ( isset($sortings[1]) ) { list ($cur_sort2, $cur_sort2_dir) = explode(',', $sortings[1]); } } } else { $sorting_settings = $this->getListSetting($event, 'Sortings'); $cur_sort1 = getArrayValue($sorting_settings, 'Sort1'); $cur_sort1_dir = getArrayValue($sorting_settings, 'Sort1_Dir'); $cur_sort2 = getArrayValue($sorting_settings, 'Sort2'); $cur_sort2_dir = getArrayValue($sorting_settings, 'Sort2_Dir'); } $tag_sort_by = $event->getEventParam('sort_by'); if ( $tag_sort_by ) { if ( $tag_sort_by == 'random' ) { $object->AddOrderField('RAND()', ''); } else { // multiple sortings could be specified at once $tag_sort_by = explode('|', $tag_sort_by); foreach ($tag_sort_by as $sorting_element) { list ($by, $dir) = explode(',', $sorting_element); $object->AddOrderField($by, $dir); } } } $list_sortings = $this->_getDefaultSorting($event); // use default if not specified in session if ( !$cur_sort1 || !$cur_sort1_dir ) { $sorting = getArrayValue($list_sortings, 'Sorting'); if ( $sorting ) { reset($sorting); $cur_sort1 = key($sorting); $cur_sort1_dir = current($sorting); if ( next($sorting) ) { $cur_sort2 = key($sorting); $cur_sort2_dir = current($sorting); } } } // always add forced sorting before any user sorting fields + /** @var Array $forced_sorting */ $forced_sorting = getArrayValue($list_sortings, 'ForcedSorting'); - /* @var $forced_sorting Array */ if ( $forced_sorting ) { foreach ($forced_sorting as $field => $dir) { $object->AddOrderField($field, $dir); } } // add user sorting fields if ( $cur_sort1 != '' && $cur_sort1_dir != '' ) { $object->AddOrderField($cur_sort1, $cur_sort1_dir); } if ( $cur_sort2 != '' && $cur_sort2_dir != '' ) { $object->AddOrderField($cur_sort2, $cur_sort2_dir); } } /** * Returns default list sortings * * @param kEvent $event * @return Array * @access protected */ protected function _getDefaultSorting(kEvent $event) { $config = $event->getUnitConfig(); $sorting_configs = $config->getConfigMapping(); $list_sortings = $config->getListSortingsBySpecial($event); if ( $sorting_configs && array_key_exists('DefaultSorting1Field', $sorting_configs) ) { // sorting defined in configuration variables overrides one from unit config $list_sortings['Sorting'] = Array ( $this->Application->ConfigValue($sorting_configs['DefaultSorting1Field']) => $this->Application->ConfigValue($sorting_configs['DefaultSorting1Dir']), $this->Application->ConfigValue($sorting_configs['DefaultSorting2Field']) => $this->Application->ConfigValue($sorting_configs['DefaultSorting2Dir']), ); // TODO: lowercase configuration variable values in db, instead of here $list_sortings['Sorting'] = array_map('strtolower', $list_sortings['Sorting']); } return $list_sortings ? $list_sortings : Array (); } /** * Gets list setting by name (persistent or real session) * * @param kEvent $event * @param string $variable_name * @return string|Array * @access protected */ protected function getListSetting(kEvent $event, $variable_name) { $view_name = $this->Application->RecallVar($event->getPrefixSpecial() . '_current_view'); $storage_prefix = $event->getEventParam('same_special') ? $event->Prefix : $event->getPrefixSpecial(); // get sorting from persistent session $default_value = $this->Application->isAdmin ? ALLOW_DEFAULT_SETTINGS : false; $variable_value = $this->Application->RecallPersistentVar($storage_prefix . '_' . $variable_name . '.' . $view_name, $default_value); /*if ( !$variable_value ) { // get sorting from session $variable_value = $this->Application->RecallVar($storage_prefix . '_' . $variable_name); }*/ if ( kUtil::IsSerialized($variable_value) ) { $variable_value = unserialize($variable_value); } return $variable_value; } /** * Sets list setting by name (persistent and real session) * * @param kEvent $event * @param string $variable_name * @param string|Array $variable_value * @return void * @access protected */ protected function setListSetting(kEvent $event, $variable_name, $variable_value = NULL) { $view_name = $this->Application->RecallVar($event->getPrefixSpecial() . '_current_view'); // $this->Application->StoreVar($event->getPrefixSpecial() . '_' . $variable_name, $variable_value, true); //true for optional if ( isset($variable_value) ) { if ( is_array($variable_value) ) { $variable_value = serialize($variable_value); } $this->Application->StorePersistentVar($event->getPrefixSpecial() . '_' . $variable_name . '.' . $view_name, $variable_value, true); //true for optional } else { $this->Application->RemovePersistentVar($event->getPrefixSpecial() . '_' . $variable_name . '.' . $view_name); } } /** * Add filters found in session * * @param kEvent $event * @return void * @access protected */ protected function AddFilters(kEvent $event) { + /** @var kDBList $object */ $object = $event->getObject(); - /* @var $object kDBList */ $edit_mark = rtrim($this->Application->GetSID() . '_' . $this->Application->GetTopmostWid($event->Prefix), '_'); // add search filter $filter_data = $this->Application->RecallVar($event->getPrefixSpecial() . '_search_filter'); if ( $filter_data ) { $filter_data = unserialize($filter_data); foreach ($filter_data as $filter_field => $filter_params) { $filter_type = ($filter_params['type'] == 'having') ? kDBList::HAVING_FILTER : kDBList::WHERE_FILTER; $filter_value = str_replace(EDIT_MARK, $edit_mark, $filter_params['value']); $object->addFilter($filter_field, $filter_value, $filter_type, kDBList::FLT_SEARCH); } } // add custom filter $view_name = $this->Application->RecallVar($event->getPrefixSpecial() . '_current_view'); $custom_filters = $this->Application->RecallPersistentVar($event->getPrefixSpecial() . '_custom_filter.' . $view_name); if ( $custom_filters ) { $grid_name = $event->getEventParam('grid'); $custom_filters = unserialize($custom_filters); if ( isset($custom_filters[$grid_name]) ) { foreach ($custom_filters[$grid_name] as $field_name => $field_options) { list ($filter_type, $field_options) = each($field_options); if ( isset($field_options['value']) && $field_options['value'] ) { $filter_type = ($field_options['sql_filter_type'] == 'having') ? kDBList::HAVING_FILTER : kDBList::WHERE_FILTER; $filter_value = str_replace(EDIT_MARK, $edit_mark, $field_options['value']); $object->addFilter($field_name, $filter_value, $filter_type, kDBList::FLT_CUSTOM); } } } } // add view filter $view_filter = $this->Application->RecallVar($event->getPrefixSpecial() . '_view_filter'); if ( $view_filter ) { $view_filter = unserialize($view_filter); + /** @var kMultipleFilter $temp_filter */ $temp_filter = $this->Application->makeClass('kMultipleFilter'); - /* @var $temp_filter kMultipleFilter */ $filter_menu = $event->getUnitConfig()->getFilterMenu(); $group_key = 0; $group_count = count($filter_menu['Groups']); while ($group_key < $group_count) { $group_info = $filter_menu['Groups'][$group_key]; $temp_filter->setType(constant('kDBList::FLT_TYPE_' . $group_info['mode'])); $temp_filter->clearFilters(); foreach ($group_info['filters'] as $flt_id) { $sql_key = getArrayValue($view_filter, $flt_id) ? 'on_sql' : 'off_sql'; if ( $filter_menu['Filters'][$flt_id][$sql_key] != '' ) { $temp_filter->addFilter('view_filter_' . $flt_id, $filter_menu['Filters'][$flt_id][$sql_key]); } } $object->addFilter('view_group_' . $group_key, $temp_filter, $group_info['type'], kDBList::FLT_VIEW); $group_key++; } } // add item filter if ( $object->isMainList() ) { $this->applyItemFilters($event); } } /** * Applies item filters * * @param kEvent $event * @return void * @access protected */ protected function applyItemFilters($event) { $filter_values = $this->Application->GetVar('filters', Array ()); if ( !$filter_values ) { return; } + /** @var kDBList $object */ $object = $event->getObject(); - /* @var $object kDBList */ $where_clause = Array ( 'ItemPrefix = ' . $this->Conn->qstr($object->Prefix), 'FilterField IN (' . implode(',', $this->Conn->qstrArray(array_keys($filter_values))) . ')', 'Enabled = 1', ); $sql = 'SELECT * FROM ' . $this->Application->getUnitConfig('item-filter')->getTableName() . ' WHERE (' . implode(') AND (', $where_clause) . ')'; $filters = $this->Conn->Query($sql, 'FilterField'); foreach ($filters as $filter_field => $filter_data) { $filter_value = $filter_values[$filter_field]; if ( "$filter_value" === '' ) { // ListManager don't pass empty values, but check here just in case continue; } $table_name = $object->isVirtualField($filter_field) ? '' : '%1$s.'; switch ($filter_data['FilterType']) { case 'radio': $filter_value = $table_name . '`' . $filter_field . '` = ' . $this->Conn->qstr($filter_value); break; case 'checkbox': $filter_value = explode('|', substr($filter_value, 1, -1)); $filter_value = $this->Conn->qstrArray($filter_value, 'escape'); if ( $object->GetFieldOption($filter_field, 'multiple') ) { $filter_value = $table_name . '`' . $filter_field . '` LIKE "%|' . implode('|%" OR ' . $table_name . '`' . $filter_field . '` LIKE "%|', $filter_value) . '|%"'; } else { $filter_value = $table_name . '`' . $filter_field . '` IN (' . implode(',', $filter_value) . ')'; } break; case 'range': $filter_value = $this->Conn->qstrArray(explode('-', $filter_value)); $filter_value = $table_name . '`' . $filter_field . '` BETWEEN ' . $filter_value[0] . ' AND ' . $filter_value[1]; break; } $object->addFilter('item_filter_' . $filter_field, $filter_value, $object->isVirtualField($filter_field) ? kDBList::HAVING_FILTER : kDBList::WHERE_FILTER); } } /** * Set's new sorting for list * * @param kEvent $event * @return void * @access protected */ protected function OnSetSorting(kEvent $event) { $sorting_settings = $this->getListSetting($event, 'Sortings'); $cur_sort1 = getArrayValue($sorting_settings, 'Sort1'); $cur_sort1_dir = getArrayValue($sorting_settings, 'Sort1_Dir'); $use_double_sorting = $this->Application->ConfigValue('UseDoubleSorting'); if ( $use_double_sorting ) { $cur_sort2 = getArrayValue($sorting_settings, 'Sort2'); $cur_sort2_dir = getArrayValue($sorting_settings, 'Sort2_Dir'); } $passed_sort1 = $this->Application->GetVar($event->getPrefixSpecial(true) . '_Sort1'); if ( $cur_sort1 == $passed_sort1 ) { $cur_sort1_dir = $cur_sort1_dir == 'asc' ? 'desc' : 'asc'; } else { if ( $use_double_sorting ) { $cur_sort2 = $cur_sort1; $cur_sort2_dir = $cur_sort1_dir; } $cur_sort1 = $passed_sort1; $cur_sort1_dir = 'asc'; } $sorting_settings = Array ('Sort1' => $cur_sort1, 'Sort1_Dir' => $cur_sort1_dir); if ( $use_double_sorting ) { $sorting_settings['Sort2'] = $cur_sort2; $sorting_settings['Sort2_Dir'] = $cur_sort2_dir; } $this->setListSetting($event, 'Sortings', $sorting_settings); } /** * Set sorting directly to session (used for category item sorting (front-end), grid sorting (admin, view menu) * * @param kEvent $event * @return void * @access protected */ protected function OnSetSortingDirect(kEvent $event) { // used on Front-End in category item lists $prefix_special = $event->getPrefixSpecial(); $combined = $this->Application->GetVar($event->getPrefixSpecial(true) . '_CombinedSorting'); if ( $combined ) { list ($field, $dir) = explode('|', $combined); if ( $this->Application->isAdmin || !$this->Application->GetVar('main_list') ) { $this->setListSetting($event, 'Sortings', Array ('Sort1' => $field, 'Sort1_Dir' => $dir)); } else { $event->setPseudoClass('_List'); $this->Application->SetVar('sort_by', $field . ',' . $dir); + /** @var kDBList $object */ $object = $event->getObject(Array ('main_list' => 1)); - /* @var $object kDBList */ + /** @var ListHelper $list_helper */ $list_helper = $this->Application->recallObject('ListHelper'); - /* @var $list_helper ListHelper */ $this->_passListParams($event, 'sort_by'); if ( $list_helper->hasUserSorting($object) ) { $event->SetRedirectParam('sort_by', $field . ',' . strtolower($dir)); } $event->SetRedirectParam('pass', 'm'); } return; } // used in "View Menu -> Sort" menu in administrative console $field_pos = $this->Application->GetVar($event->getPrefixSpecial(true) . '_SortPos'); $this->Application->LinkVar($event->getPrefixSpecial(true) . '_Sort' . $field_pos, $prefix_special . '_Sort' . $field_pos); $this->Application->LinkVar($event->getPrefixSpecial(true) . '_Sort' . $field_pos . '_Dir', $prefix_special . '_Sort' . $field_pos . '_Dir'); } /** * Reset grid sorting to default (from config) * * @param kEvent $event * @return void * @access protected */ protected function OnResetSorting(kEvent $event) { $this->setListSetting($event, 'Sortings'); } /** * Sets grid refresh interval * * @param kEvent $event * @return void * @access protected */ protected function OnSetAutoRefreshInterval(kEvent $event) { $refresh_interval = $this->Application->GetVar('refresh_interval'); $view_name = $this->Application->RecallVar($event->getPrefixSpecial() . '_current_view'); $this->Application->StorePersistentVar($event->getPrefixSpecial() . '_refresh_interval.' . $view_name, $refresh_interval); } /** * Changes auto-refresh state for grid * * @param kEvent $event * @return void * @access protected */ protected function OnAutoRefreshToggle(kEvent $event) { $refresh_intervals = $this->Application->ConfigValue('AutoRefreshIntervals'); if ( !$refresh_intervals ) { return; } $view_name = $this->Application->RecallVar($event->getPrefixSpecial() . '_current_view'); $auto_refresh = $this->Application->RecallPersistentVar($event->getPrefixSpecial() . '_auto_refresh.' . $view_name); if ( $auto_refresh === false ) { $refresh_intervals = explode(',', $refresh_intervals); $this->Application->StorePersistentVar($event->getPrefixSpecial() . '_refresh_interval.' . $view_name, $refresh_intervals[0]); } $this->Application->StorePersistentVar($event->getPrefixSpecial() . '_auto_refresh.' . $view_name, $auto_refresh ? 0 : 1); } /** * Creates needed sql query to load item, * if no query is defined in config for * special requested, then use list query * * @param kEvent $event * @return string * @access protected */ protected function ItemPrepareQuery(kEvent $event) { + /** @var kDBItem $object */ $object = $event->getObject(); - /* @var $object kDBItem */ $sqls = $object->getFormOption('ItemSQLs', Array ()); $special = isset($sqls[$event->Special]) ? $event->Special : ''; // preferred special not found in ItemSQLs -> use analog from ListSQLs return isset($sqls[$special]) ? $sqls[$special] : $this->ListPrepareQuery($event); } /** * Creates needed sql query to load list, * if no query is defined in config for * special requested, then use default * query * * @param kEvent $event * @return string * @access protected */ protected function ListPrepareQuery(kEvent $event) { + /** @var kDBItem $object */ $object = $event->getObject(); - /* @var $object kDBItem */ $sqls = $object->getFormOption('ListSQLs', Array ()); return $sqls[array_key_exists($event->Special, $sqls) ? $event->Special : '']; } /** * Apply custom processing to item * * @param kEvent $event * @param string $type * @return void * @access protected */ protected function customProcessing(kEvent $event, $type) { } /* Edit Events mostly used in Admin */ /** * Creates new kDBItem * * @param kEvent $event * @return void * @access protected */ protected function OnCreate(kEvent $event) { + /** @var kDBItem $object */ $object = $event->getObject(Array ('skip_autoload' => true)); - /* @var $object kDBItem */ $items_info = $this->Application->GetVar($event->getPrefixSpecial(true)); if ( !$items_info ) { return; } list($id, $field_values) = each($items_info); $object->setID($id); $object->SetFieldsFromHash($field_values); $event->setEventParam('form_data', $field_values); $this->customProcessing($event, 'before'); // look at kDBItem' Create for ForceCreateId description, it's rarely used and is NOT set by default if ( $object->Create($event->getEventParam('ForceCreateId')) ) { $this->customProcessing($event, 'after'); $event->SetRedirectParam('opener', 'u'); return; } $event->redirect = false; $event->status = kEvent::erFAIL; $this->Application->SetVar($event->getPrefixSpecial() . '_SaveEvent', 'OnCreate'); } /** * Updates kDBItem * * @param kEvent $event * @return void * @access protected */ protected function OnUpdate(kEvent $event) { if ( $this->Application->CheckPermission('SYSTEM_ACCESS.READONLY', 1) ) { $event->status = kEvent::erFAIL; return; } $this->_update($event); $event->SetRedirectParam('opener', 'u'); } /** * Updates data in database based on request * * @param kEvent $event * @return void * @access protected */ protected function _update(kEvent $event) { + /** @var kDBItem $object */ $object = $event->getObject(Array ('skip_autoload' => true)); - /* @var $object kDBItem */ $items_info = $this->Application->GetVar( $event->getPrefixSpecial(true) ); if ( $items_info ) { foreach ($items_info as $id => $field_values) { $object->Load($id); $object->SetFieldsFromHash($field_values); $event->setEventParam('form_data', $field_values); $this->customProcessing($event, 'before'); if ( $object->Update($id) ) { $this->customProcessing($event, 'after'); $event->status = kEvent::erSUCCESS; } else { $event->status = kEvent::erFAIL; $event->redirect = false; break; } } } } /** * Delete's kDBItem object * * @param kEvent $event * @return void * @access protected */ protected function OnDelete(kEvent $event) { if ( $this->Application->CheckPermission('SYSTEM_ACCESS.READONLY', 1) ) { $event->status = kEvent::erFAIL; return; } + /** @var kTempTablesHandler $temp_handler */ $temp_handler = $this->Application->recallObject($event->getPrefixSpecial() . '_TempHandler', 'kTempTablesHandler', Array ('parent_event' => $event)); - /* @var $temp_handler kTempTablesHandler */ $temp_handler->DeleteItems($event->Prefix, $event->Special, Array ($this->getPassedID($event))); } /** * Deletes all records from table * * @param kEvent $event * @return void * @access protected */ protected function OnDeleteAll(kEvent $event) { $config = $event->getUnitConfig(); $sql = 'SELECT ' . $config->getIDField() . ' FROM ' . $config->getTableName(); $ids = $this->Conn->GetCol($sql); if ( $ids ) { + /** @var kTempTablesHandler $temp_handler */ $temp_handler = $this->Application->recallObject($event->getPrefixSpecial() . '_TempHandler', 'kTempTablesHandler', Array ('parent_event' => $event)); - /* @var $temp_handler kTempTablesHandler */ $temp_handler->DeleteItems($event->Prefix, $event->Special, $ids); } } /** * Prepares new kDBItem object * * @param kEvent $event * @return void * @access protected */ protected function OnNew(kEvent $event) { + /** @var kDBItem $object */ $object = $event->getObject(Array ('skip_autoload' => true)); - /* @var $object kDBItem */ $object->Clear(0); $this->Application->SetVar($event->getPrefixSpecial() . '_SaveEvent', 'OnCreate'); if ( $event->getEventParam('top_prefix') != $event->Prefix ) { // this is subitem prefix, so use main item special $table_info = $object->getLinkedInfo($this->getMainSpecial($event)); } else { $table_info = $object->getLinkedInfo(); } $object->SetDBField($table_info['ForeignKey'], $table_info['ParentId']); $event->redirect = false; } /** * Cancels kDBItem Editing/Creation * * @param kEvent $event * @return void * @access protected */ protected function OnCancel(kEvent $event) { + /** @var kDBItem $object */ $object = $event->getObject(Array ('skip_autoload' => true)); - /* @var $object kDBItem */ $items_info = $this->Application->GetVar($event->getPrefixSpecial(true)); if ( $items_info ) { $delete_ids = Array (); + /** @var kTempTablesHandler $temp_handler */ $temp_handler = $this->Application->recallObject($event->getPrefixSpecial() . '_TempHandler', 'kTempTablesHandler', Array ('parent_event' => $event)); - /* @var $temp_handler kTempTablesHandler */ foreach ($items_info as $id => $field_values) { $object->Load($id); // record created for using with selector (e.g. Reviews->Select User), and not validated => Delete it if ( $object->isLoaded() && !$object->Validate() && ($id <= 0) ) { $delete_ids[] = $id; } } if ( $delete_ids ) { $temp_handler->DeleteItems($event->Prefix, $event->Special, $delete_ids); } } $event->SetRedirectParam('opener', 'u'); } /** * Deletes all selected items. * Automatically recurse into sub-items using temp handler, and deletes sub-items * by calling its Delete method if sub-item has AutoDelete set to true in its config file * * @param kEvent $event * @return void * @access protected */ protected function OnMassDelete(kEvent $event) { if ( $this->Application->CheckPermission('SYSTEM_ACCESS.READONLY', 1) ) { $event->status = kEvent::erFAIL; return ; } + /** @var kTempTablesHandler $temp_handler */ $temp_handler = $this->Application->recallObject($event->getPrefixSpecial() . '_TempHandler', 'kTempTablesHandler', Array ('parent_event' => $event)); - /* @var $temp_handler kTempTablesHandler */ $ids = $this->StoreSelectedIDs($event); $event->setEventParam('ids', $ids); $this->customProcessing($event, 'before'); $ids = $event->getEventParam('ids'); if ( $ids ) { $temp_handler->DeleteItems($event->Prefix, $event->Special, $ids); } $this->clearSelectedIDs($event); } /** * Sets window id (of first opened edit window) to temp mark in uls * * @param kEvent $event * @return void * @access protected */ protected function setTempWindowID(kEvent $event) { $prefixes = Array ($event->Prefix, $event->getPrefixSpecial(true)); foreach ($prefixes as $prefix) { $mode = $this->Application->GetVar($prefix . '_mode'); if ($mode == 't') { $wid = $this->Application->GetVar('m_wid'); $this->Application->SetVar(str_replace('_', '.', $prefix) . '_mode', 't' . $wid); break; } } } /** * Prepare temp tables and populate it * with items selected in the grid * * @param kEvent $event * @return void * @access protected */ protected function OnEdit(kEvent $event) { $this->setTempWindowID($event); $ids = $this->StoreSelectedIDs($event); + /** @var kDBItem $object */ $object = $event->getObject(Array('skip_autoload' => true)); - /* @var $object kDBItem */ $object->setPendingActions(null, true); $changes_var_name = $this->Prefix . '_changes_' . $this->Application->GetTopmostWid($this->Prefix); $this->Application->RemoveVar($changes_var_name); + /** @var kTempTablesHandler $temp_handler */ $temp_handler = $this->Application->recallObject($event->getPrefixSpecial() . '_TempHandler', 'kTempTablesHandler', Array ('parent_event' => $event)); - /* @var $temp_handler kTempTablesHandler */ $temp_handler->PrepareEdit(); $event->SetRedirectParam('m_lang', $this->Application->GetDefaultLanguageId()); $event->SetRedirectParam($event->getPrefixSpecial() . '_id', array_shift($ids)); $event->SetRedirectParam('pass', 'all,' . $event->getPrefixSpecial()); $simultaneous_edit_message = $this->Application->GetVar('_simultaneous_edit_message'); if ( $simultaneous_edit_message ) { $event->SetRedirectParam('_simultaneous_edit_message', $simultaneous_edit_message); } } /** * Saves content of temp table into live and * redirects to event' default redirect (normally grid template) * * @param kEvent $event * @return void * @access protected */ protected function OnSave(kEvent $event) { $event->CallSubEvent('OnPreSave'); if ( $event->status != kEvent::erSUCCESS ) { return; } $skip_master = false; + + /** @var kTempTablesHandler $temp_handler */ $temp_handler = $this->Application->recallObject($event->getPrefixSpecial() . '_TempHandler', 'kTempTablesHandler', Array ('parent_event' => $event)); - /* @var $temp_handler kTempTablesHandler */ $changes_var_name = $this->Prefix . '_changes_' . $this->Application->GetTopmostWid($this->Prefix); if ( !$this->Application->CheckPermission('SYSTEM_ACCESS.READONLY', 1) ) { $live_ids = $temp_handler->SaveEdit($event->getEventParam('master_ids') ? $event->getEventParam('master_ids') : Array ()); if ( $live_ids === false ) { // coping from table failed, because we have another coping process to same table, that wasn't finished $event->status = kEvent::erFAIL; return; } if ( $live_ids ) { // ensure, that newly created item ids are available as if they were selected from grid // NOTE: only works if main item has sub-items !!! $this->StoreSelectedIDs($event, $live_ids); } + /** @var kDBItem $object */ $object = $event->getObject(); - /* @var $object kDBItem */ $this->SaveLoggedChanges($changes_var_name, $object->ShouldLogChanges()); } else { $event->status = kEvent::erFAIL; } $this->clearSelectedIDs($event); $event->SetRedirectParam('opener', 'u'); $this->Application->RemoveVar($event->getPrefixSpecial() . '_modified'); // all temp tables are deleted here => all after hooks should think, that it's live mode now $this->Application->SetVar($event->Prefix . '_mode', ''); } /** * Saves changes made in temporary table to log * * @param string $changes_var_name * @param bool $save * @return void * @access public */ public function SaveLoggedChanges($changes_var_name, $save = true) { // 1. get changes, that were made $changes = $this->Application->RecallVar($changes_var_name); $changes = $changes ? unserialize($changes) : Array (); $this->Application->RemoveVar($changes_var_name); if (!$changes) { // no changes, skip processing return ; } // TODO: 2. optimize change log records (replace multiple changes to same record with one change record) $to_increment = Array (); // 3. collect serials to reset based on foreign keys foreach ($changes as $index => $rec) { if (array_key_exists('DependentFields', $rec)) { foreach ($rec['DependentFields'] as $field_name => $field_value) { // will be "ci|ItemResourceId:345" $to_increment[] = $rec['Prefix'] . '|' . $field_name . ':' . $field_value; // also reset sub-item prefix general serial $to_increment[] = $rec['Prefix']; } unset($changes[$index]['DependentFields']); } unset($changes[$index]['ParentId'], $changes[$index]['ParentPrefix']); } // 4. collect serials to reset based on changed ids foreach ($changes as $change) { $to_increment[] = $change['MasterPrefix'] . '|' . $change['MasterId']; if ($change['MasterPrefix'] != $change['Prefix']) { // also reset sub-item prefix general serial $to_increment[] = $change['Prefix']; // will be "ci|ItemResourceId" $to_increment[] = $change['Prefix'] . '|' . $change['ItemId']; } } // 5. reset serials collected before $to_increment = array_unique($to_increment); $this->Application->incrementCacheSerial($this->Prefix); foreach ($to_increment as $to_increment_mixed) { if (strpos($to_increment_mixed, '|') !== false) { list ($to_increment_prefix, $to_increment_id) = explode('|', $to_increment_mixed, 2); $this->Application->incrementCacheSerial($to_increment_prefix, $to_increment_id); } else { $this->Application->incrementCacheSerial($to_increment_mixed); } } // save changes to database $session_log_id = $this->Application->RecallVar('_SessionLogId_'); if (!$save || !$session_log_id) { // saving changes to database disabled OR related session log missing return ; } $add_fields = Array ( 'PortalUserId' => $this->Application->RecallVar('user_id'), 'SessionLogId' => $session_log_id, ); $change_log_table = $this->Application->getUnitConfig('change-log')->getTableName(); foreach ($changes as $rec) { $this->Conn->doInsert(array_merge($rec, $add_fields), $change_log_table); } $this->Application->incrementCacheSerial('change-log'); $sql = 'UPDATE ' . $this->Application->getUnitConfig('session-log')->getTableName() . ' SET AffectedItems = AffectedItems + ' . count($changes) . ' WHERE SessionLogId = ' . $session_log_id; $this->Conn->Query($sql); $this->Application->incrementCacheSerial('session-log'); } /** * Cancels edit * Removes all temp tables and clears selected ids * * @param kEvent $event * @return void * @access protected */ protected function OnCancelEdit(kEvent $event) { + /** @var kTempTablesHandler $temp_handler */ $temp_handler = $this->Application->recallObject($event->getPrefixSpecial() . '_TempHandler', 'kTempTablesHandler', Array ('parent_event' => $event)); - /* @var $temp_handler kTempTablesHandler */ $temp_handler->CancelEdit(); $this->clearSelectedIDs($event); $this->Application->RemoveVar($event->getPrefixSpecial() . '_modified'); $changes_var_name = $this->Prefix . '_changes_' . $this->Application->GetTopmostWid($this->Prefix); $this->Application->RemoveVar($changes_var_name); $event->SetRedirectParam('opener', 'u'); } /** * Allows to determine if we are creating new item or editing already created item * * @param kEvent $event * @return bool * @access public */ public function isNewItemCreate(kEvent $event) { + /** @var kDBItem $object */ $object = $event->getObject( Array ('raise_warnings' => 0) ); - /* @var $object kDBItem */ return !$object->isLoaded(); } /** * Saves edited item into temp table * If there is no id, new item is created in temp table * * @param kEvent $event * @return void * @access protected */ protected function OnPreSave(kEvent $event) { // if there is no id - it means we need to create an item if ( is_object($event->MasterEvent) ) { $event->MasterEvent->setEventParam('IsNew', false); } if ( $this->isNewItemCreate($event) ) { $event->CallSubEvent('OnPreSaveCreated'); if ( is_object($event->MasterEvent) ) { $event->MasterEvent->setEventParam('IsNew', true); } return ; } // don't just call OnUpdate event here, since it maybe overwritten to Front-End specific behavior $this->_update($event); } /** * Analog of OnPreSave event for usage in AJAX request * * @param kEvent $event * * @return void */ protected function OnPreSaveAjax(kEvent $event) { + /** @var AjaxFormHelper $ajax_form_helper */ $ajax_form_helper = $this->Application->recallObject('AjaxFormHelper'); - /* @var $ajax_form_helper AjaxFormHelper */ $ajax_form_helper->transitEvent($event, 'OnPreSave'); } /** * [HOOK] Saves sub-item * * @param kEvent $event * @return void * @access protected */ protected function OnPreSaveSubItem(kEvent $event) { $not_created = $this->isNewItemCreate($event); $event->CallSubEvent($not_created ? 'OnCreate' : 'OnUpdate'); if ( $event->status == kEvent::erSUCCESS ) { + /** @var kDBItem $object */ $object = $event->getObject(); - /* @var $object kDBItem */ $this->Application->SetVar($event->getPrefixSpecial() . '_id', $object->GetID()); } else { $event->MasterEvent->status = $event->status; } $event->SetRedirectParam('opener', 's'); } /** * Saves edited item in temp table and loads * item with passed id in current template * Used in Prev/Next buttons * * @param kEvent $event * @return void * @access protected */ protected function OnPreSaveAndGo(kEvent $event) { $event->CallSubEvent('OnPreSave'); if ( $event->status == kEvent::erSUCCESS ) { $id = $this->Application->GetVar($event->getPrefixSpecial(true) . '_GoId'); $event->SetRedirectParam($event->getPrefixSpecial() . '_id', $id); } } /** * Saves edited item in temp table and goes * to passed tabs, by redirecting to it with OnPreSave event * * @param kEvent $event * @return void * @access protected */ protected function OnPreSaveAndGoToTab(kEvent $event) { $event->CallSubEvent('OnPreSave'); if ( $event->status == kEvent::erSUCCESS ) { $event->redirect = $this->Application->GetVar($event->getPrefixSpecial(true) . '_GoTab'); } } /** * Saves editable list and goes to passed tab, * by redirecting to it with empty event * * @param kEvent $event * @return void * @access protected */ protected function OnUpdateAndGoToTab(kEvent $event) { $event->setPseudoClass('_List'); $event->CallSubEvent('OnUpdate'); if ( $event->status == kEvent::erSUCCESS ) { $event->redirect = $this->Application->GetVar($event->getPrefixSpecial(true) . '_GoTab'); } } /** * Prepare temp tables for creating new item * but does not create it. Actual create is * done in OnPreSaveCreated * * @param kEvent $event * @return void * @access protected */ protected function OnPreCreate(kEvent $event) { $this->setTempWindowID($event); $this->clearSelectedIDs($event); $this->Application->SetVar('m_lang', $this->Application->GetDefaultLanguageId()); + /** @var kDBItem $object */ $object = $event->getObject(Array ('skip_autoload' => true)); - /* @var $object kDBItem */ + /** @var kTempTablesHandler $temp_handler */ $temp_handler = $this->Application->recallObject($event->Prefix . '_TempHandler', 'kTempTablesHandler', Array ('parent_event' => $event)); - /* @var $temp_handler kTempTablesHandler */ $temp_handler->PrepareEdit(); $object->setID(0); $this->Application->SetVar($event->getPrefixSpecial() . '_id', 0); $this->Application->SetVar($event->getPrefixSpecial() . '_PreCreate', 1); $changes_var_name = $this->Prefix . '_changes_' . $this->Application->GetTopmostWid($this->Prefix); $this->Application->RemoveVar($changes_var_name); $event->redirect = false; } /** * Creates a new item in temp table and * stores item id in App vars and Session on success * * @param kEvent $event * @return void * @access protected */ protected function OnPreSaveCreated(kEvent $event) { + /** @var kDBItem $object */ $object = $event->getObject( Array('skip_autoload' => true) ); - /* @var $object kDBItem */ $object->setID(0); $field_values = $this->getSubmittedFields($event); $object->SetFieldsFromHash($field_values); $event->setEventParam('form_data', $field_values); $this->customProcessing($event, 'before'); if ( $object->Create() ) { $this->customProcessing($event, 'after'); $event->SetRedirectParam($event->getPrefixSpecial(true) . '_id', $object->GetID()); } else { $event->status = kEvent::erFAIL; $event->redirect = false; } } /** * Reloads form to loose all changes made during item editing * * @param kEvent $event * @return void * @access protected */ protected function OnReset(kEvent $event) { //do nothing - should reset :) if ( $this->isNewItemCreate($event) ) { // just reset id to 0 in case it was create + /** @var kDBItem $object */ $object = $event->getObject( Array ('skip_autoload' => true) ); - /* @var $object kDBItem */ $object->setID(0); $this->Application->SetVar($event->getPrefixSpecial() . '_id', 0); } } /** * Apply same processing to each item being selected in grid * * @param kEvent $event * @return void * @access protected */ protected function iterateItems(kEvent $event) { if ( $this->Application->CheckPermission('SYSTEM_ACCESS.READONLY', 1) ) { $event->status = kEvent::erFAIL; return ; } + /** @var kDBItem $object */ $object = $event->getObject(Array ('skip_autoload' => true)); - /* @var $object kDBItem */ $ids = $this->StoreSelectedIDs($event); if ( $ids ) { $config = $event->getUnitConfig(); $status_field = $config->getStatusField(true); $order_field = $config->getOrderField(); if ( !$order_field ) { $order_field = 'Priority'; } foreach ($ids as $id) { $object->Load($id); switch ( $event->Name ) { case 'OnMassApprove': $object->SetDBField($status_field, 1); break; case 'OnMassDecline': $object->SetDBField($status_field, 0); break; case 'OnMassMoveUp': $object->SetDBField($order_field, $object->GetDBField($order_field) + 1); break; case 'OnMassMoveDown': $object->SetDBField($order_field, $object->GetDBField($order_field) - 1); break; } $object->Update(); } } $this->clearSelectedIDs($event); } /** * Clones selected items in list * * @param kEvent $event * @return void * @access protected */ protected function OnMassClone(kEvent $event) { if ( $this->Application->CheckPermission('SYSTEM_ACCESS.READONLY', 1) ) { $event->status = kEvent::erFAIL; return; } + /** @var kTempTablesHandler $temp_handler */ $temp_handler = $this->Application->recallObject($event->getPrefixSpecial() . '_TempHandler', 'kTempTablesHandler', Array ('parent_event' => $event)); - /* @var $temp_handler kTempTablesHandler */ $ids = $this->StoreSelectedIDs($event); if ( $ids ) { $temp_handler->CloneItems($event->Prefix, $event->Special, $ids); } $this->clearSelectedIDs($event); } /** * Checks if given value is present in given array * * @param Array $records * @param string $field * @param mixed $value * @return bool * @access protected */ protected function check_array($records, $field, $value) { foreach ($records as $record) { if ($record[$field] == $value) { return true; } } return false; } /** * Saves data from editing form to database without checking required fields * * @param kEvent $event * @return void * @access protected */ protected function OnPreSavePopup(kEvent $event) { + /** @var kDBItem $object */ $object = $event->getObject(); - /* @var $object kDBItem */ $this->RemoveRequiredFields($object); $event->CallSubEvent('OnPreSave'); $event->SetRedirectParam('opener', 'u'); } /* End of Edit events */ // III. Events that allow to put some code before and after Update,Load,Create and Delete methods of item /** * Occurs before loading item, 'id' parameter * allows to get id of item being loaded * * @param kEvent $event * @return void * @access protected */ protected function OnBeforeItemLoad(kEvent $event) { } /** * Occurs after loading item, 'id' parameter * allows to get id of item that was loaded * * @param kEvent $event * @return void * @access protected */ protected function OnAfterItemLoad(kEvent $event) { } /** * Occurs before creating item * * @param kEvent $event * @return void * @access protected */ protected function OnBeforeItemCreate(kEvent $event) { } /** * Occurs after creating item * * @param kEvent $event * @return void * @access protected */ protected function OnAfterItemCreate(kEvent $event) { + /** @var kDBItem $object */ $object = $event->getObject(); - /* @var $object kDBItem */ if ( !$object->IsTempTable() ) { $this->_processPendingActions($event); } } /** * Occurs before updating item * * @param kEvent $event * @return void * @access protected */ protected function OnBeforeItemUpdate(kEvent $event) { } /** * Occurs after updating item * * @param kEvent $event * @return void * @access protected */ protected function OnAfterItemUpdate(kEvent $event) { + /** @var kDBItem $object */ $object = $event->getObject(); - /* @var $object kDBItem */ if ( !$object->IsTempTable() ) { $this->_processPendingActions($event); } } /** * Occurs before deleting item, id of item being * deleted is stored as 'id' event param * * @param kEvent $event * @return void * @access protected */ protected function OnBeforeItemDelete(kEvent $event) { } /** * Occurs after deleting item, id of deleted item * is stored as 'id' param of event * * Also deletes subscriptions to that particual item once it's deleted * * @param kEvent $event * @return void * @access protected */ protected function OnAfterItemDelete(kEvent $event) { + /** @var kDBItem $object */ $object = $event->getObject(); - /* @var $object kDBItem */ // 1. delete direct subscriptions to item, that was deleted $this->_deleteSubscriptions($event->Prefix, 'ItemId', $object->GetID()); // 2. delete this item sub-items subscriptions, that reference item, that was deleted foreach ($event->getUnitConfig()->getSubItems(Array ()) as $sub_prefix) { $this->_deleteSubscriptions($sub_prefix, 'ParentItemId', $object->GetID()); } } /** * Deletes all subscriptions, associated with given item * * @param string $prefix * @param string $field * @param int $value * @return void * @access protected */ protected function _deleteSubscriptions($prefix, $field, $value) { $sql = 'SELECT TemplateId FROM ' . $this->Application->getUnitConfig('email-template')->getTableName() . ' WHERE BindToSystemEvent REGEXP "' . $this->Conn->escape($prefix) . '(\\\\.[^:]*:.*|:.*)"'; $email_template_ids = $this->Conn->GetCol($sql); if ( !$email_template_ids ) { return; } // e-mail events, connected to that unit prefix are found $sql = 'SELECT SubscriptionId FROM ' . TABLE_PREFIX . 'SystemEventSubscriptions WHERE ' . $field . ' = ' . $value . ' AND EmailTemplateId IN (' . implode(',', $email_template_ids) . ')'; $ids = $this->Conn->GetCol($sql); if ( !$ids ) { return; } + /** @var kTempTablesHandler $temp_handler */ $temp_handler = $this->Application->recallObject('system-event-subscription_TempHandler', 'kTempTablesHandler'); - /* @var $temp_handler kTempTablesHandler */ $temp_handler->DeleteItems('system-event-subscription', '', $ids); } /** * Occurs before validation attempt * * @param kEvent $event * @return void * @access protected */ protected function OnBeforeItemValidate(kEvent $event) { } /** * Occurs after successful item validation * * @param kEvent $event * @return void * @access protected */ protected function OnAfterItemValidate(kEvent $event) { } /** * Occurs after an item has been copied to temp * Id of copied item is passed as event' 'id' param * * @param kEvent $event * @return void * @access protected */ protected function OnAfterCopyToTemp(kEvent $event) { } /** * Occurs before an item is deleted from live table when copying from temp * (temp handler deleted all items from live and then copy over all items from temp) * Id of item being deleted is passed as event' 'id' param * * @param kEvent $event * @return void * @access protected */ protected function OnBeforeDeleteFromLive(kEvent $event) { } /** * Occurs before an item is copied to live table (after all foreign keys have been updated) * Id of item being copied is passed as event' 'id' param * * @param kEvent $event * @return void * @access protected */ protected function OnBeforeCopyToLive(kEvent $event) { } /** * Occurs after an item has been copied to live table * Id of copied item is passed as event' 'id' param * * @param kEvent $event * @return void * @access protected */ protected function OnAfterCopyToLive(kEvent $event) { + /** @var kDBItem $object */ $object = $event->getObject(array('skip_autoload' => true)); - /* @var $object kDBItem */ $object->SwitchToLive(); $object->Load($event->getEventParam('id')); $this->_processPendingActions($event); } /** * Processing file pending actions (e.g. delete scheduled files) * * @param kEvent $event * @return void * @access protected */ protected function _processPendingActions(kEvent $event) { + /** @var kDBItem $object */ $object = $event->getObject(); - /* @var $object kDBItem */ $update_required = false; $temp_id = $event->getEventParam('temp_id'); $id = $temp_id !== false ? $temp_id : $object->GetID(); foreach ($object->getPendingActions($id) as $data) { switch ( $data['action'] ) { case 'delete': unlink($data['file']); break; case 'make_live': + /** @var FileHelper $file_helper */ $file_helper = $this->Application->recallObject('FileHelper'); - /* @var $file_helper FileHelper */ if ( !file_exists($data['file']) ) { // file removal was requested too continue; } $old_name = basename($data['file']); $new_name = $file_helper->ensureUniqueFilename(dirname($data['file']), kUtil::removeTempExtension($old_name)); rename($data['file'], dirname($data['file']) . '/' . $new_name); $db_value = $object->GetDBField($data['field']); $object->SetDBField($data['field'], str_replace($old_name, $new_name, $db_value)); $update_required = true; break; default: trigger_error('Unsupported pending action "' . $data['action'] . '" for "' . $event->getPrefixSpecial() . '" unit', E_USER_WARNING); break; } } // remove pending actions before updating to prevent recursion $object->setPendingActions(); if ( $update_required ) { $object->Update(); } } /** * Occurs before an item has been cloned * Id of newly created item is passed as event' 'id' param * * @param kEvent $event * @return void * @access protected */ protected function OnBeforeClone(kEvent $event) { } /** * Occurs after an item has been cloned * Id of newly created item is passed as event' 'id' param * * @param kEvent $event * @return void * @access protected */ protected function OnAfterClone(kEvent $event) { } /** * Occurs after list is queried * * @param kEvent $event * @return void * @access protected */ protected function OnAfterListQuery(kEvent $event) { } /** * Ensures that popup will be closed automatically * and parent window will be refreshed with template * passed * * @param kEvent $event * @return void * @access protected * @deprecated */ protected function finalizePopup(kEvent $event) { $event->SetRedirectParam('opener', 'u'); } /** * Create search filters based on search query * * @param kEvent $event * @return void * @access protected */ protected function OnSearch(kEvent $event) { $event->setPseudoClass('_List'); + /** @var kSearchHelper $search_helper */ $search_helper = $this->Application->recallObject('SearchHelper'); - /* @var $search_helper kSearchHelper */ $search_helper->performSearch($event); } /** * Clear search keywords * * @param kEvent $event * @return void * @access protected */ protected function OnSearchReset(kEvent $event) { + /** @var kSearchHelper $search_helper */ $search_helper = $this->Application->recallObject('SearchHelper'); - /* @var $search_helper kSearchHelper */ $search_helper->resetSearch($event); } /** * Set's new filter value (filter_id meaning from config) * * @param kEvent $event * @return void * @access protected * @deprecated */ protected function OnSetFilter(kEvent $event) { $filter_id = $this->Application->GetVar('filter_id'); $filter_value = $this->Application->GetVar('filter_value'); $view_filter = $this->Application->RecallVar($event->getPrefixSpecial() . '_view_filter'); $view_filter = $view_filter ? unserialize($view_filter) : Array (); $view_filter[$filter_id] = $filter_value; $this->Application->StoreVar($event->getPrefixSpecial() . '_view_filter', serialize($view_filter)); } /** * Sets view filter based on request * * @param kEvent $event * @return void * @access protected */ protected function OnSetFilterPattern(kEvent $event) { $filters = $this->Application->GetVar($event->getPrefixSpecial(true) . '_filters'); if ( !$filters ) { return; } $view_filter = $this->Application->RecallVar($event->getPrefixSpecial() . '_view_filter'); $view_filter = $view_filter ? unserialize($view_filter) : Array (); $filters = explode(',', $filters); foreach ($filters as $a_filter) { list($id, $value) = explode('=', $a_filter); $view_filter[$id] = $value; } $this->Application->StoreVar($event->getPrefixSpecial() . '_view_filter', serialize($view_filter)); $event->redirect = false; } /** * Add/Remove all filters applied to list from "View" menu * * @param kEvent $event * @return void * @access protected */ protected function FilterAction(kEvent $event) { $view_filter = Array (); $filter_menu = $event->getUnitConfig()->getFilterMenu(); switch ($event->Name) { case 'OnRemoveFilters': $filter_value = 1; break; case 'OnApplyFilters': $filter_value = 0; break; default: $filter_value = 0; break; } foreach ($filter_menu['Filters'] as $filter_key => $filter_params) { if ( !$filter_params ) { continue; } $view_filter[$filter_key] = $filter_value; } $this->Application->StoreVar($event->getPrefixSpecial() . '_view_filter', serialize($view_filter)); } /** * Enter description here... * * @param kEvent $event * @access protected */ protected function OnPreSaveAndOpenTranslator(kEvent $event) { $this->Application->SetVar('allow_translation', true); + /** @var kDBItem $object */ $object = $event->getObject(); - /* @var $object kDBItem */ $this->RemoveRequiredFields($object); $event->CallSubEvent('OnPreSave'); if ( $event->status == kEvent::erSUCCESS ) { $resource_id = $this->Application->GetVar('translator_resource_id'); if ( $resource_id ) { $t_prefixes = explode(',', $this->Application->GetVar('translator_prefixes')); + /** @var kDBItem $cdata */ $cdata = $this->Application->recallObject($t_prefixes[1], NULL, Array ('skip_autoload' => true)); - /* @var $cdata kDBItem */ $cdata->Load($resource_id, 'ResourceId'); if ( !$cdata->isLoaded() ) { $cdata->SetDBField('ResourceId', $resource_id); $cdata->Create(); } $this->Application->SetVar($cdata->getPrefixSpecial() . '_id', $cdata->GetID()); } $event->redirect = $this->Application->GetVar('translator_t'); $redirect_params = Array ( 'pass' => 'all,trans,' . $this->Application->GetVar('translator_prefixes'), 'opener' => 's', $event->getPrefixSpecial(true) . '_id' => $object->GetID(), 'trans_event' => 'OnLoad', 'trans_prefix' => $this->Application->GetVar('translator_prefixes'), 'trans_field' => $this->Application->GetVar('translator_field'), 'trans_multi_line' => $this->Application->GetVar('translator_multi_line'), ); $event->setRedirectParams($redirect_params); // 1. SAVE LAST TEMPLATE TO SESSION (really needed here, because of tweaky redirect) $last_template = $this->Application->RecallVar('last_template'); preg_match('/index4\.php\|' . $this->Application->GetSID() . '-(.*):/U', $last_template, $rets); $this->Application->StoreVar('return_template', $this->Application->GetVar('t')); } } /** * Makes all fields non-required * * @param kDBItem $object * @return void * @access protected */ protected function RemoveRequiredFields(&$object) { // making all field non-required to achieve successful presave $fields = array_keys( $object->getFields() ); foreach ($fields as $field) { if ( $object->isRequired($field) ) { $object->setRequired($field, false); } } } /** * Saves selected user in needed field * * @param kEvent $event * @return void * @access protected */ protected function OnSelectUser(kEvent $event) { + /** @var kDBItem $object */ $object = $event->getObject(); - /* @var $object kDBItem */ $items_info = $this->Application->GetVar('u'); if ( $items_info ) { list ($user_id, ) = each($items_info); $this->RemoveRequiredFields($object); $is_new = !$object->isLoaded(); $is_main = substr($this->Application->GetVar($event->Prefix . '_mode'), 0, 1) == 't'; if ( $is_new ) { $new_event = $is_main ? 'OnPreCreate' : 'OnNew'; $event->CallSubEvent($new_event); $event->redirect = true; } $object->SetDBField($this->Application->RecallVar('dst_field'), $user_id); if ( $is_new ) { $object->Create(); } else { $object->Update(); } } $event->SetRedirectParam($event->getPrefixSpecial() . '_id', $object->GetID()); $event->SetRedirectParam('opener', 'u'); } /** EXPORT RELATED **/ /** * Shows export dialog * * @param kEvent $event * @return void * @access protected */ protected function OnExport(kEvent $event) { $selected_ids = $this->StoreSelectedIDs($event); if ( implode(',', $selected_ids) == '' ) { // K4 fix when no ids found bad selected ids array is formed $selected_ids = false; } $this->Application->StoreVar($event->Prefix . '_export_ids', $selected_ids ? implode(',', $selected_ids) : ''); $this->Application->LinkVar('export_finish_t'); $this->Application->LinkVar('export_progress_t'); $this->Application->StoreVar('export_special', $event->Special); $this->Application->StoreVar('export_grid', $this->Application->GetVar('grid', 'Default')); $redirect_params = Array ( $this->Prefix . '.export_event' => 'OnNew', 'pass' => 'all,' . $this->Prefix . '.export' ); $event->setRedirectParams($redirect_params); } /** * Apply some special processing to object being * recalled before using it in other events that * call prepareObject * * @param kDBItem|kDBList $object * @param kEvent $event * @return void * @access protected */ protected function prepareObject(&$object, kEvent $event) { if ( $event->Special == 'export' || $event->Special == 'import' ) { + /** @var kCatDBItemExportHelper $export_helper */ $export_helper = $this->Application->recallObject('CatItemExportHelper'); - /* @var $export_helper kCatDBItemExportHelper */ $export_helper->prepareExportColumns($event); } } /** * Returns specific to each item type columns only * * @param kEvent $event * @return Array * @access public */ public function getCustomExportColumns(kEvent $event) { return Array (); } /** * Export form validation & processing * * @param kEvent $event * @return void * @access protected */ protected function OnExportBegin(kEvent $event) { + /** @var kCatDBItemExportHelper $export_helper */ $export_helper = $this->Application->recallObject('CatItemExportHelper'); - /* @var $export_helper kCatDBItemExportHelper */ $export_helper->OnExportBegin($event); } /** * Enter description here... * * @param kEvent $event * @return void * @access protected */ protected function OnExportCancel(kEvent $event) { $this->OnGoBack($event); } /** * Allows configuring export options * * @param kEvent $event * @return void * @access protected */ protected function OnBeforeExportBegin(kEvent $event) { } /** * Deletes export preset * * @param kEvent $event * @return void * @access protected */ protected function OnDeleteExportPreset(kEvent $event) { $field_values = $this->getSubmittedFields($event); if ( !$field_values ) { return ; } $preset_key = $field_values['ExportPresets']; $export_settings = $this->Application->RecallPersistentVar('export_settings'); if ( !$export_settings ) { return ; } $export_settings = unserialize($export_settings); if ( !isset($export_settings[$event->Prefix]) ) { return ; } $to_delete = ''; foreach ($export_settings[$event->Prefix] as $key => $val) { if ( implode('|', $val['ExportColumns']) == $preset_key ) { $to_delete = $key; break; } } if ( $to_delete ) { unset($export_settings[$event->Prefix][$to_delete]); $this->Application->StorePersistentVar('export_settings', serialize($export_settings)); } } /** * Saves changes & changes language * * @param kEvent $event * @return void * @access protected */ protected function OnPreSaveAndChangeLanguage(kEvent $event) { if ( $this->UseTempTables($event) ) { $event->CallSubEvent('OnPreSave'); } if ( $event->status == kEvent::erSUCCESS ) { $this->Application->SetVar('m_lang', $this->Application->GetVar('language')); $data = $this->Application->GetVar('st_id'); if ( $data ) { $event->SetRedirectParam('st_id', $data); } } } /** * Used to save files uploaded via Plupload * * @param kEvent $event * @return void * @access protected */ protected function OnUploadFile(kEvent $event) { $event->status = kEvent::erSTOP; /** @var kUploadHelper $upload_helper */ $upload_helper = $this->Application->recallObject('kUploadHelper'); try { $filename = $upload_helper->handle($event); $response = array( 'jsonrpc' => '2.0', 'status' => 'success', 'result' => $filename, ); } catch ( kUploaderException $e ) { $response = array( 'jsonrpc' => '2.0', 'status' => 'error', 'error' => array('code' => $e->getCode(), 'message' => $e->getMessage()), ); } echo json_encode($response); } /** * Remembers, that file should be deleted on item's save from temp table * * @param kEvent $event * @return void * @access protected */ protected function OnDeleteFile(kEvent $event) { $event->status = kEvent::erSTOP; $field_id = $this->Application->GetVar('field_id'); if ( !preg_match_all('/\[([^\[\]]*)\]/', $field_id, $regs) ) { return; } $field = $regs[1][1]; $record_id = $regs[1][0]; /** @var kUploadHelper $upload_helper */ $upload_helper = $this->Application->recallObject('kUploadHelper'); $object = $upload_helper->prepareUploadedFile($event, $field); if ( !$object->GetDBField($field) ) { return; } $pending_actions = $object->getPendingActions($record_id); $pending_actions[] = Array ( 'action' => 'delete', 'id' => $record_id, 'field' => $field, 'file' => $object->GetField($field, 'full_path'), ); $object->setPendingActions($pending_actions, $record_id); } /** * Returns url for viewing uploaded file * * @param kEvent $event * @return void * @access protected */ protected function OnViewFile(kEvent $event) { $event->status = kEvent::erSTOP; $field = $this->Application->GetVar('field'); /** @var kUploadHelper $upload_helper */ $upload_helper = $this->Application->recallObject('kUploadHelper'); $object = $upload_helper->prepareUploadedFile($event, $field); if ( !$object->GetDBField($field) ) { return; } // get url to uploaded file if ( $this->Application->GetVar('thumb') ) { $url = $object->GetField($field, $object->GetFieldOption($field, 'thumb_format')); } else { $url = $object->GetField($field, 'raw_url'); } /** @var FileHelper $file_helper */ $file_helper = $this->Application->recallObject('FileHelper'); $path = $file_helper->urlToPath($url); if ( !file_exists($path) ) { exit; } header('Content-Length: ' . filesize($path)); $this->Application->setContentType(kUtil::mimeContentType($path), false); header('Content-Disposition: inline; filename="' . kUtil::removeTempExtension($object->GetDBField($field)) . '"'); readfile($path); } /** * Validates MInput control fields * * @param kEvent $event * @return void * @access protected */ protected function OnValidateMInputFields(kEvent $event) { + /** @var MInputHelper $minput_helper */ $minput_helper = $this->Application->recallObject('MInputHelper'); - /* @var $minput_helper MInputHelper */ $minput_helper->OnValidateMInputFields($event); } /** * Validates individual object field and returns the result * * @param kEvent $event * @return void * @access protected */ protected function OnValidateField(kEvent $event) { $event->status = kEvent::erSTOP; $field = $this->Application->GetVar('field'); if ( ($this->Application->GetVar('ajax') != 'yes') || !$field ) { return; } + /** @var kDBItem $object */ $object = $event->getObject(Array ('skip_autoload' => true)); - /* @var $object kDBItem */ $items_info = $this->Application->GetVar($event->getPrefixSpecial(true)); if ( !$items_info ) { return; } list ($id, $field_values) = each($items_info); $object->Load($id); $object->SetFieldsFromHash($field_values); $event->setEventParam('form_data', $field_values); $object->setID($id); $response = Array ('status' => 'OK'); $event->CallSubEvent($object->isLoaded() ? 'OnBeforeItemUpdate' : 'OnBeforeItemCreate'); // validate all fields, since "Password_plain" field sets error to "Password" field, which is passed here $error_field = $object->GetFieldOption($field, 'error_field', false, $field); if ( !$object->Validate() && $object->GetErrorPseudo($error_field) ) { $response['status'] = $object->GetErrorMsg($error_field, false); } + /** @var AjaxFormHelper $ajax_form_helper */ $ajax_form_helper = $this->Application->recallObject('AjaxFormHelper'); - /* @var $ajax_form_helper AjaxFormHelper */ $response['other_errors'] = $ajax_form_helper->getErrorMessages($object); $response['uploader_info'] = $ajax_form_helper->getUploaderInfo($object, array_keys($field_values)); $event->status = kEvent::erSTOP; // since event's OnBefore... events can change this event status echo json_encode($response); } /** * Returns auto-complete values for ajax-dropdown * * @param kEvent $event * @return void * @access protected */ protected function OnSuggestValues(kEvent $event) { $event->status = kEvent::erSTOP; $this->Application->XMLHeader(); $data = $this->getAutoCompleteSuggestions($event, $this->Application->GetVar('cur_value')); echo ''; if ( kUtil::isAssoc($data) ) { foreach ($data as $key => $title) { echo '' . kUtil::escape($title, kUtil::ESCAPE_HTML) . ''; } } else { foreach ($data as $title) { echo '' . kUtil::escape($title, kUtil::ESCAPE_HTML) . ''; } } echo ''; } /** * Returns auto-complete values for jQueryUI.AutoComplete * * @param kEvent $event * @return void * @access protected */ protected function OnSuggestValuesJSON(kEvent $event) { $event->status = kEvent::erSTOP; $data = $this->getAutoCompleteSuggestions($event, $this->Application->GetVar('term')); if ( kUtil::isAssoc($data) ) { $transformed_data = array(); foreach ($data as $key => $title) { $transformed_data[] = array('value' => $key, 'label' => $title); } $data = $transformed_data; } echo json_encode($data); } /** * Prepares a suggestion list based on a given term. * * @param kEvent $event Event. * @param string $term Term. * * @return Array * @access protected */ protected function getAutoCompleteSuggestions(kEvent $event, $term) { + /** @var kDBItem $object */ $object = $event->getObject(); - /* @var $object kDBItem */ $field = $this->Application->GetVar('field'); if ( !$field || !$term || !$object->isField($field) ) { return array(); } $limit = $this->Application->GetVar('limit'); if ( !$limit ) { $limit = 20; } $sql = 'SELECT DISTINCT ' . $field . ' FROM ' . $event->getUnitConfig()->getTableName() . ' WHERE ' . $field . ' LIKE ' . $this->Conn->qstr($term . '%') . ' ORDER BY ' . $field . ' LIMIT 0,' . $limit; return $this->Conn->GetCol($sql); } /** * Enter description here... * * @param kEvent $event * @return void * @access protected */ protected function OnSaveWidths(kEvent $event) { $event->status = kEvent::erSTOP; // $this->Application->setContentType('text/xml'); $picker_helper = new kColumnPickerHelper( $event->getPrefixSpecial(), $this->Application->GetVar('grid_name') ); $picker_helper->saveWidths($this->Application->GetVar('widths')); echo 'OK'; } /** * Called from CSV import script after item fields * are set and validated, but before actual item create/update. * If event status is kEvent::erSUCCESS, line will be imported, * else it will not be imported but added to skipped lines * and displayed in the end of import. * Event status is preset from import script. * * @param kEvent $event * @return void * @access protected */ protected function OnBeforeCSVLineImport(kEvent $event) { // abstract, for hooking } /** * [HOOK] Allows to add cloned subitem to given prefix * * @param kEvent $event * @return void * @access protected */ protected function OnCloneSubItem(kEvent $event) { $sub_item_prefix = $event->Prefix . '-' . preg_replace('/^#/', '', $event->MasterEvent->Prefix); $event->MasterEvent->getUnitConfig()->addClones(Array ( $sub_item_prefix => Array ('ParentPrefix' => $event->Prefix), )); } /** * Returns constrain for priority calculations * * @param kEvent $event * @return void * @see PriorityEventHandler * @access protected */ protected function OnGetConstrainInfo(kEvent $event) { $event->setEventParam('constrain_info', Array ('', '')); } } Index: branches/5.3.x/core/kernel/db/db_connection.php =================================================================== --- branches/5.3.x/core/kernel/db/db_connection.php (revision 16518) +++ branches/5.3.x/core/kernel/db/db_connection.php (revision 16519) @@ -1,1415 +1,1415 @@ '', 'user' => '', 'pass' => '', 'db' => ''); /** * Index of database server * * @var int * @access protected */ protected $serverIndex = 0; /** * Handle of currently processed recordset * * @var mysqli_result * @access protected */ protected $queryID = null; /** * Function to handle sql errors * * @var mixed * @access protected */ protected $errorHandler = ''; /** * Error code * * @var int * @access protected */ protected $errorCode = 0; /** * Error message * * @var string * @access protected */ protected $errorMessage = ''; /** * Defines if database connection * operations should generate debug * information * * @var bool * @access public */ public $debugMode = false; /** * Save query execution statistics * * @var bool * @access protected */ protected $_captureStatistics = false; /** * Last query to database * * @var string * @access public */ public $lastQuery = ''; /** * Total processed queries count * * @var int * @access protected */ protected $_queryCount = 0; /** * Total time, used for serving queries * * @var Array * @access protected */ protected $_queryTime = 0; /** * Indicates, that next database query could be cached, when memory caching is enabled * * @var bool * @access public */ public $nextQueryCachable = false; /** * For backwards compatibility with kDBLoadBalancer class * * @var bool * @access public */ public $nextQueryFromMaster = false; /** * Initializes connection class with * db type to used in future * * @param string $db_type * @param mixed $error_handler * @param int $server_index * @access public */ public function __construct($db_type, $error_handler = '', $server_index = 0) { if ( class_exists('kApplication') ) { // prevents "Fatal Error" on 2nd installation step (when database is empty) parent::__construct(); } $this->serverIndex = $server_index; $this->setErrorHandler($error_handler ? $error_handler : array(&$this, 'handleError')); $this->_captureStatistics = defined('DBG_CAPTURE_STATISTICS') && DBG_CAPTURE_STATISTICS && !(defined('ADMIN') && ADMIN); } /** * Sets new error handler. * * @param mixed $error_handler Error handler. * * @return self */ public function setErrorHandler($error_handler) { $this->errorHandler = $error_handler; return $this; } /** * Set's custom error * * @param int $code * @param string $msg * @access protected */ protected function setError($code, $msg) { $this->errorCode = $code; $this->errorMessage = $msg; } /** * Checks if previous query execution raised an error. * * @return bool * @access public */ public function hasError() { return $this->errorCode != 0; } /** * Try to connect to database server using specified parameters and set database to $db if connection made. * * @param string $host * @param string $user * @param string $pass * @param string $db * @param bool $retry * * @return bool * @access public * @throws RuntimeException When connection failed. */ public function Connect($host, $user, $pass, $db, $retry = false) { $this->connectionParams = Array ('host' => $host, 'user' => $user, 'pass' => $pass, 'db' => $db); $this->setError(0, ''); // reset error $this->connectionID = mysqli_connect($host, $user, $pass, $db); $this->errorCode = mysqli_connect_errno(); if ( is_object($this->connectionID) ) { if ( defined('DBG_SQL_MODE') ) { $this->Query('SET SQL_MODE = "' . DBG_SQL_MODE . '"'); } if ( defined('SQL_COLLATION') && defined('SQL_CHARSET') ) { $this->Query('SET NAMES \'' . SQL_CHARSET . '\' COLLATE \'' . SQL_COLLATION . '\''); } if ( !$this->hasError() ) { $this->connectionOpened = true; return true; } } $this->errorMessage = mysqli_connect_error(); $error_msg = 'Database connection failed, please check your connection settings.
Error (' . $this->errorCode . '): ' . $this->errorMessage; if ( (defined('IS_INSTALL') && IS_INSTALL) || $retry ) { trigger_error($error_msg, E_USER_WARNING); } else { $this->Application->redirectToMaintenance(); throw new RuntimeException($error_msg); } $this->connectionOpened = false; return false; } /** * Checks if connection to database is opened. * * @return bool * @access public */ public function connectionOpened() { return $this->connectionOpened; } /** * Setups the connection according given configuration. * * @param Array $config * @return bool * @access public */ public function setup($config) { if ( is_object($this->Application) ) { $this->debugMode = $this->Application->isDebugMode(); } return $this->Connect( $config['Database']['DBHost'], $config['Database']['DBUser'], $config['Database']['DBUserPassword'], $config['Database']['DBName'] ); } /** * Performs 3 reconnect attempts in case if connection to a DB was lost in the middle of script run (e.g. server restart) * * @return bool * @access protected */ protected function ReConnect() { $retry_count = 0; $connected = false; $this->connectionID->close(); while ( $retry_count < 3 ) { sleep(5); // wait 5 seconds before each reconnect attempt $connected = $this->Connect( $this->connectionParams['host'], $this->connectionParams['user'], $this->connectionParams['pass'], $this->connectionParams['db'], true ); if ( $connected ) { break; } $retry_count++; } return $connected; } /** * Shows error message from previous operation * if it failed * * @param string $sql * @param string $key_field * @param bool $no_debug * @return bool * @access protected */ protected function showError($sql = '', $key_field = null, $no_debug = false) { static $retry_count = 0; if ( !is_object($this->connectionID) ) { // no connection while doing mysql_query $this->errorCode = mysqli_connect_errno(); if ( $this->hasError() ) { $this->errorMessage = mysqli_connect_error(); $ret = $this->callErrorHandler($sql); if (!$ret) { exit; } } return false; } // checking if there was an error during last mysql_query $this->errorCode = $this->connectionID->errno; if ( $this->hasError() ) { $this->errorMessage = $this->connectionID->error; $ret = $this->callErrorHandler($sql); if ( ($this->errorCode == 2006 || $this->errorCode == 2013) && ($retry_count < 3) ) { // #2006 - MySQL server has gone away // #2013 - Lost connection to MySQL server during query $retry_count++; if ( $this->ReConnect() ) { return $this->Query($sql, $key_field, $no_debug); } } if (!$ret) { exit; } } else { $retry_count = 0; } return false; } /** * Sends db error to a predefined error handler * * @param $sql * @return bool * @access protected */ protected function callErrorHandler($sql) { return call_user_func($this->errorHandler, $this->errorCode, $this->errorMessage, $sql); } /** * Default error handler for sql errors * * @param int $code * @param string $msg * @param string $sql * @return bool * @access public */ public function handleError($code, $msg, $sql) { echo 'Processing SQL: ' . $sql . '
'; echo 'Error (' . $code . '): ' . $msg . '
'; return false; } /** * Returns first field of first line of recordset if query ok or false otherwise. * * @param string $sql * @param int $offset * @return string * @access public */ public function GetOne($sql, $offset = 0) { $row = $this->GetRow($sql, $offset); if ( !$row ) { return false; } return array_shift($row); } /** * Returns first row of recordset if query ok, false otherwise. * * @param string $sql * @param int $offset * @return Array * @access public */ public function GetRow($sql, $offset = 0) { $sql .= ' ' . $this->getLimitClause($offset, 1); $ret = $this->Query($sql); if ( !$ret ) { return false; } return array_shift($ret); } /** * Returns 1st column of recordset as one-dimensional array or false otherwise. * * Optional parameter $key_field can be used to set field name to be used as resulting array key. * * @param string $sql * @param string $key_field * @return Array * @access public */ public function GetCol($sql, $key_field = null) { $rows = $this->Query($sql); if ( !$rows ) { return $rows; } $i = 0; $row_count = count($rows); $ret = Array (); if ( isset($key_field) ) { while ( $i < $row_count ) { $ret[$rows[$i][$key_field]] = array_shift($rows[$i]); $i++; } } else { while ( $i < $row_count ) { $ret[] = array_shift($rows[$i]); $i++; } } return $ret; } /** * Returns iterator for 1st column of a recordset or false in case of error. * * Optional parameter $key_field can be used to set field name to be used as resulting array key. * * @param string $sql * @param string $key_field * @return bool|kMySQLQueryCol */ public function GetColIterator($sql, $key_field = null) { return $this->GetIterator($sql, $key_field, false, 'kMySQLQueryCol'); } /** * Queries db with $sql query supplied and returns rows selected if any, false otherwise. * * Optional parameter $key_field allows to set one of the query fields value as key in string array. * * @param string $sql * @param string $key_field * @param bool $no_debug * @return Array * @access public */ public function Query($sql, $key_field = null, $no_debug = false) { $this->_queryCount++; $this->lastQuery = $sql; // set 1st checkpoint: begin $start_time = $this->_captureStatistics ? microtime(true) : 0; // set 1st checkpoint: end $this->setError(0, ''); // reset error $this->queryID = $this->connectionID->query($sql); if ( is_object($this->queryID) ) { $ret = Array (); if ( isset($key_field) ) { while ( $row = $this->queryID->fetch_assoc() ) { $ret[$row[$key_field]] = $row; } } else { while ( $row = $this->queryID->fetch_assoc() ) { $ret[] = $row; } } // set 2nd checkpoint: begin if ( $this->_captureStatistics ) { $query_time = microtime(true) - $start_time; if ( $query_time > DBG_MAX_SQL_TIME ) { $this->Application->logSlowQuery($sql, $query_time); } $this->_queryTime += $query_time; } // set 2nd checkpoint: end $this->Destroy(); return $ret; } else { // set 2nd checkpoint: begin if ( $this->_captureStatistics ) { $this->_queryTime += microtime(true) - $start_time; } // set 2nd checkpoint: end } return $this->showError($sql, $key_field, $no_debug); } /** * Returns iterator to a recordset, produced from running $sql query. * * Queries db with $sql query supplied and returns kMySQLQuery iterator or false in case of error. * Optional parameter $key_field allows to set one of the query fields value as key in string array. * * @param string $sql * @param string $key_field * @param bool $no_debug * @param string $iterator_class * @return kMySQLQuery|bool * @access public */ public function GetIterator($sql, $key_field = null, $no_debug = false, $iterator_class = 'kMySQLQuery') { $this->_queryCount++; $this->lastQuery = $sql; // set 1st checkpoint: begin $start_time = $this->_captureStatistics ? microtime(true) : 0; // set 1st checkpoint: end $this->setError(0, ''); // reset error $this->queryID = $this->connectionID->query($sql); if ( is_object($this->queryID) ) { + /** @var kMySQLQuery $ret */ $ret = new $iterator_class($this->queryID, $key_field); - /* @var $ret kMySQLQuery */ // set 2nd checkpoint: begin if ( $this->_captureStatistics ) { $query_time = microtime(true) - $start_time; if ( $query_time > DBG_MAX_SQL_TIME ) { $this->Application->logSlowQuery($sql, $query_time); } $this->_queryTime += $query_time; } // set 2nd checkpoint: end return $ret; } else { // set 2nd checkpoint: begin if ( $this->_captureStatistics ) { $this->_queryTime += microtime(true) - $start_time; } // set 2nd checkpoint: end } return $this->showError($sql, $key_field, $no_debug); } /** * Free memory used to hold recordset handle. * * @access public */ public function Destroy() { $this->queryID->free(); unset($this->queryID); } /** * Performs sql query, that will change database content. * * @param string $sql * @return bool * @access public */ public function ChangeQuery($sql) { $this->Query($sql); return !$this->hasError(); } /** * Returns auto increment field value from insert like operation if any, zero otherwise. * * @return int * @access public */ public function getInsertID() { return $this->connectionID->insert_id; } /** * Returns row count affected by last query. * * @return int * @access public */ public function getAffectedRows() { return $this->connectionID->affected_rows; } /** * Returns LIMIT sql clause part for specific db. * * @param int $offset * @param int $rows * @return string * @access public */ public function getLimitClause($offset, $rows) { if ( !($rows > 0) ) { return ''; } return 'LIMIT ' . $offset . ',' . $rows; } /** * If it's a string, adds quotes and backslashes. Otherwise returns as-is. * * @param mixed $string * @return string * @access public */ public function qstr($string) { if ( is_null($string) ) { return 'NULL'; } # This will also quote numeric values. This should be harmless, # and protects against weird problems that occur when they really # _are_ strings such as article titles and string->number->string # conversion is not 1:1. return "'" . $this->connectionID->real_escape_string($string) . "'"; } /** * Calls "qstr" function for each given array element. * * @param Array $array * @param string $function * @return Array */ public function qstrArray($array, $function = 'qstr') { return array_map(Array (&$this, $function), $array); } /** * Escapes string. * * @param mixed $string * @return string * @access public */ public function escape($string) { if ( is_null($string) ) { return 'NULL'; } $string = $this->connectionID->real_escape_string($string); // prevent double-escaping of MySQL wildcard symbols ("%" and "_") in case if they were already escaped return str_replace(Array ('\\\\%', '\\\\_'), Array ('\\%', '\\_'), $string); } /** * Returns last error code occurred. * * @return int * @access public */ public function getErrorCode() { return $this->errorCode; } /** * Returns last error message. * * @return string * @access public */ public function getErrorMsg() { return $this->errorMessage; } /** * Performs insert of given data (useful with small number of queries) * or stores it to perform multiple insert later (useful with large number of queries). * * @param Array $fields_hash * @param string $table * @param string $type * @param bool $insert_now * @return bool * @access public */ public function doInsert($fields_hash, $table, $type = 'INSERT', $insert_now = true) { static $value_sqls = Array (); if ($insert_now) { $fields_sql = '`' . implode('`,`', array_keys($fields_hash)) . '`'; } $values_sql = ''; foreach ($fields_hash as $field_name => $field_value) { $values_sql .= $this->qstr($field_value) . ','; } // don't use preg here, as it may fail when string is too long $value_sqls[] = rtrim($values_sql, ','); $insert_result = true; if ($insert_now) { $insert_count = count($value_sqls); if (($insert_count > 1) && ($value_sqls[$insert_count - 1] == $value_sqls[$insert_count - 2])) { // last two records are the same array_pop($value_sqls); } $sql = strtoupper($type) . ' INTO `' . $table . '` (' . $fields_sql . ') VALUES (' . implode('),(', $value_sqls) . ')'; $value_sqls = Array (); // reset before query to prevent repeated call from error handler to insert 2 records instead of 1 $insert_result = $this->ChangeQuery($sql); } return $insert_result; } /** * Update given field values to given record using $key_clause. * * @param Array $fields_hash * @param string $table * @param string $key_clause * @return bool * @access public */ public function doUpdate($fields_hash, $table, $key_clause) { if (!$fields_hash) return true; $fields_sql = ''; foreach ($fields_hash as $field_name => $field_value) { $fields_sql .= '`'.$field_name.'` = ' . $this->qstr($field_value) . ','; } // don't use preg here, as it may fail when string is too long $fields_sql = rtrim($fields_sql, ','); $sql = 'UPDATE `'.$table.'` SET '.$fields_sql.' WHERE '.$key_clause; return $this->ChangeQuery($sql); } /** * Allows to detect table's presence in database. * * @param string $table_name * @param bool $force * @return bool * @access public */ public function TableFound($table_name, $force = false) { static $table_found = false; if ( $table_found === false ) { $table_found = array_flip($this->GetCol('SHOW TABLES')); } if ( !preg_match('/^' . preg_quote(TABLE_PREFIX, '/') . '(.*)/', $table_name) ) { $table_name = TABLE_PREFIX . $table_name; } if ( $force ) { if ( $this->Query('SHOW TABLES LIKE ' . $this->qstr($table_name)) ) { $table_found[$table_name] = 1; } else { unset($table_found[$table_name]); } } return isset($table_found[$table_name]); } /** * Returns query processing statistics. * * @return Array * @access public */ public function getQueryStatistics() { return Array ('time' => $this->_queryTime, 'count' => $this->_queryCount); } /** * Get status information from SHOW STATUS in an associative array. * * @param string $which * @return Array * @access public */ public function getStatus($which = '%') { $status = Array (); $records = $this->Query('SHOW STATUS LIKE "' . $which . '"'); foreach ($records as $record) { $status[ $record['Variable_name'] ] = $record['Value']; } return $status; } /** * Get slave replication lag. It will only work if the DB user has the PROCESS privilege. * * @return int * @access public */ public function getSlaveLag() { // don't use kDBConnection::Query method, since it will create an array of all server processes $processes = $this->GetIterator('SHOW PROCESSLIST'); $skip_states = Array ( 'Waiting for master to send event', 'Connecting to master', 'Queueing master event to the relay log', 'Waiting for master update', 'Requesting binlog dump', ); // find slave SQL thread foreach ($processes as $process) { if ( $process['User'] == 'system user' && !in_array($process['State'], $skip_states) ) { // this is it, return the time (except -ve) return $process['Time'] > 0x7fffffff ? false : $process['Time']; } } return false; } } class kDBConnectionDebug extends kDBConnection { protected $_profileSQLs = false; /** * Info about this database connection to show in debugger report * * @var string * @access protected */ protected $serverInfoLine = ''; /** * Initializes connection class with * db type to used in future * * @param string $db_type * @param string $error_handler * @param int $server_index * @access public */ public function __construct($db_type, $error_handler = '', $server_index = 0) { parent::__construct($db_type, $error_handler, $server_index); $this->_profileSQLs = defined('DBG_SQL_PROFILE') && DBG_SQL_PROFILE; } /** * Try to connect to database server * using specified parameters and set * database to $db if connection made * * @param string $host * @param string $user * @param string $pass * @param string $db * @param bool $force_new * @param bool $retry * @return bool * @access public */ public function Connect($host, $user, $pass, $db, $force_new = false, $retry = false) { if ( defined('DBG_SQL_SERVERINFO') && DBG_SQL_SERVERINFO ) { $this->serverInfoLine = $this->serverIndex . ' (' . $host . ')'; } return parent::Connect($host, $user, $pass, $db, $force_new, $retry); } /** * Queries db with $sql query supplied and returns rows selected if any, false otherwise. * * Optional parameter $key_field allows to set one of the query fields value as key in string array. * * @param string $sql * @param string $key_field * @param bool $no_debug * @return Array * @access public */ public function Query($sql, $key_field = null, $no_debug = false) { if ( $no_debug ) { return parent::Query($sql, $key_field, $no_debug); } global $debugger; $this->_queryCount++; $this->lastQuery = $sql; // set 1st checkpoint: begin if ( $this->_profileSQLs ) { $queryID = $debugger->generateID(); $debugger->profileStart('sql_' . $queryID, $debugger->formatSQL($sql)); } // set 1st checkpoint: end $this->setError(0, ''); // reset error $this->queryID = $this->connectionID->query($sql); if ( is_object($this->queryID) ) { $ret = Array (); if ( isset($key_field) ) { while ( $row = $this->queryID->fetch_assoc() ) { $ret[$row[$key_field]] = $row; } } else { while ( $row = $this->queryID->fetch_assoc() ) { $ret[] = $row; } } // set 2nd checkpoint: begin if ( $this->_profileSQLs ) { $current_element = current($ret); $first_cell = count($ret) == 1 && count($current_element) == 1 ? current($current_element) : null; if ( strlen($first_cell) > 200 ) { $first_cell = substr($first_cell, 0, 50) . ' ...'; } $debugger->profileFinish('sql_' . $queryID, null, null, $this->getAffectedRows(), $first_cell, $this->_queryCount, $this->nextQueryCachable, $this->serverInfoLine); $debugger->profilerAddTotal('sql', 'sql_' . $queryID); $this->nextQueryCachable = false; } // set 2nd checkpoint: end $this->Destroy(); return $ret; } else { // set 2nd checkpoint: begin if ( $this->_profileSQLs ) { $debugger->profileFinish('sql_' . $queryID, null, null, $this->getAffectedRows(), null, $this->_queryCount, $this->nextQueryCachable, $this->serverInfoLine); $debugger->profilerAddTotal('sql', 'sql_' . $queryID); $this->nextQueryCachable = false; } // set 2nd checkpoint: end } return $this->showError($sql, $key_field); } /** * Returns iterator to a recordset, produced from running $sql query. * * Queries db with $sql query supplied and returns kMySQLQuery iterator or false in case of error. * Optional parameter $key_field allows to set one of the query fields value as key in string array. * * @param string $sql * @param string $key_field * @param bool $no_debug * @param string $iterator_class * @return kMySQLQuery|bool * @access public */ public function GetIterator($sql, $key_field = null, $no_debug = false, $iterator_class = 'kMySQLQuery') { if ( $no_debug ) { return parent::Query($sql, $key_field, $no_debug, $iterator_class); } global $debugger; $this->_queryCount++; $this->lastQuery = $sql; // set 1st checkpoint: begin if ( $this->_profileSQLs ) { $queryID = $debugger->generateID(); $debugger->profileStart('sql_' . $queryID, $debugger->formatSQL($sql)); } // set 1st checkpoint: end $this->setError(0, ''); // reset error $this->queryID = $this->connectionID->query($sql); if ( is_object($this->queryID) ) { + /** @var kMySQLQuery $ret */ $ret = new $iterator_class($this->queryID, $key_field); - /* @var $ret kMySQLQuery */ // set 2nd checkpoint: begin if ( $this->_profileSQLs ) { $current_row = $ret->current(); if ( count($ret) == 1 && $ret->fieldCount() == 1 ) { if ( is_array($current_row) ) { $first_cell = current($current_row); } else { $first_cell = $current_row; } } else { $first_cell = null; } if ( strlen($first_cell) > 200 ) { $first_cell = substr($first_cell, 0, 50) . ' ...'; } $debugger->profileFinish('sql_' . $queryID, null, null, $this->getAffectedRows(), $first_cell, $this->_queryCount, $this->nextQueryCachable, $this->serverInfoLine); $debugger->profilerAddTotal('sql', 'sql_' . $queryID); $this->nextQueryCachable = false; } // set 2nd checkpoint: end return $ret; } else { // set 2nd checkpoint: begin if ( $this->_profileSQLs ) { $debugger->profileFinish('sql_' . $queryID, null, null, $this->getAffectedRows(), null, $this->_queryCount, $this->nextQueryCachable, $this->serverInfoLine); $debugger->profilerAddTotal('sql', 'sql_' . $queryID); $this->nextQueryCachable = false; } // set 2nd checkpoint: end } return $this->showError($sql, $key_field); } } class kMySQLQuery implements Iterator, Countable, SeekableIterator { /** * Current index in recordset * * @var int * @access protected */ protected $position = -1; /** * Query resource * * @var mysqli_result * @access protected */ protected $result; /** * Field to act as key in a resulting array * * @var string * @access protected */ protected $keyField = null; /** * Data in current row of recordset * * @var Array * @access protected */ protected $rowData = Array (); /** * Row count in a result * * @var int * @access protected */ protected $rowCount = 0; /** * Creates new instance of a class * * @param mysqli_result $result * @param null|string $key_field */ public function __construct(mysqli_result $result, $key_field = null) { $this->result = $result; $this->keyField = $key_field; $this->rowCount = $this->result->num_rows; $this->rewind(); } /** * Moves recordset pointer to first element * * @return void * @access public * @implements Iterator::rewind */ public function rewind() { $this->seek(0); } /** * Returns value at current position * * @return mixed * @access public * @implements Iterator::current */ function current() { return $this->rowData; } /** * Returns key at current position * * @return mixed * @access public * @implements Iterator::key */ function key() { return $this->keyField ? $this->rowData[$this->keyField] : $this->position; } /** * Moves recordset pointer to next position * * @return void * @access public * @implements Iterator::next */ function next() { $this->seek($this->position + 1); } /** * Detects if current position is within recordset bounds * * @return bool * @access public * @implements Iterator::valid */ public function valid() { return $this->position < $this->rowCount; } /** * Counts recordset rows * * @return int * @access public * @implements Countable::count */ public function count() { return $this->rowCount; } /** * Counts fields in current row * * @return int * @access public */ public function fieldCount() { return count($this->rowData); } /** * Moves cursor into given position within recordset * * @param int $position * @throws OutOfBoundsException * @access public * @implements SeekableIterator::seek */ public function seek($position) { if ( $this->position == $position ) { return; } $this->position = $position; if ( $this->valid() ) { $this->result->data_seek($this->position); $this->rowData = $this->result->fetch_assoc(); } /*if ( !$this->valid() ) { throw new OutOfBoundsException('Invalid seek position (' . $position . ')'); }*/ } /** * Returns first recordset row * * @return Array * @access public */ public function first() { $this->seek(0); return $this->rowData; } /** * Closes recordset and freese memory * * @return void * @access public */ public function close() { $this->result->free(); unset($this->result); } /** * Frees memory when object is destroyed * * @return void * @access public */ public function __destruct() { $this->close(); } /** * Returns all keys * * @return Array * @access public */ public function keys() { $ret = Array (); foreach ($this as $key => $value) { $ret[] = $key; } return $ret; } /** * Returns all values * * @return Array * @access public */ public function values() { $ret = Array (); foreach ($this as $value) { $ret[] = $value; } return $ret; } /** * Returns whole recordset as array * * @return Array * @access public */ public function toArray() { $ret = Array (); foreach ($this as $key => $value) { $ret[$key] = $value; } return $ret; } } class kMySQLQueryCol extends kMySQLQuery { /** * Returns value at current position * * @return mixed * @access public * @implements Iterator::current */ function current() { return reset($this->rowData); } /** * Returns first column of first recordset row * * @return string * @access public */ public function first() { $this->seek(0); return reset($this->rowData); } } Index: branches/5.3.x/core/kernel/db/dbitem.php =================================================================== --- branches/5.3.x/core/kernel/db/dbitem.php (revision 16518) +++ branches/5.3.x/core/kernel/db/dbitem.php (revision 16519) @@ -1,1611 +1,1611 @@ validator) ) { $validator_class = $this->getUnitConfig()->getValidatorClass('kValidator'); $this->validator = $this->Application->makeClass($validator_class); } $this->validator->setDataSource($this); } public function SetDirtyField($field_name, $field_value) { $this->DirtyFieldValues[$field_name] = $field_value; } public function GetDirtyField($field_name) { return $this->DirtyFieldValues[$field_name]; } public function GetOriginalField($field_name, $formatted = false, $format=null) { if (array_key_exists($field_name, $this->OriginalFieldValues)) { // item was loaded before $value = $this->OriginalFieldValues[$field_name]; } else { // no original fields -> use default field value $value = $this->Fields[$field_name]['default']; } if (!$formatted) { return $value; } $res = $value; $formatter = $this->GetFieldOption($field_name, 'formatter'); if ( $formatter ) { + /** @var kFormatter $formatter */ $formatter = $this->Application->recallObject($formatter); - /* @var $formatter kFormatter */ $res = $formatter->Format($value, $field_name, $this, $format); } return $res; } /** * Sets original field value (useful for custom virtual fields) * * @param string $field_name * @param string $field_value */ public function SetOriginalField($field_name, $field_value) { $this->OriginalFieldValues[$field_name] = $field_value; } /** * Set's default values for all fields * * @access public */ public function SetDefaultValues() { parent::SetDefaultValues(); if ($this->populateMultiLangFields) { $this->PopulateMultiLangFields(); } foreach ($this->Fields as $field => $field_options) { $default_value = isset($field_options['default']) ? $field_options['default'] : NULL; $this->SetDBField($field, $default_value); } } /** * Sets current item field value * (applies formatting) * * @access public * @param string $name Name of the field * @param mixed $value Value to set the field to * @return void */ public function SetField($name,$value) { $options = $this->GetFieldOptions($name); $parsed = $value; if ($value == '') { $parsed = NULL; } // kFormatter is always used, to make sure, that numeric value is converted to normal representation // according to regional format, even when formatter is not set (try seting format to 1.234,56 to understand why) + /** @var kFormatter $formatter */ $formatter = $this->Application->recallObject(isset($options['formatter']) ? $options['formatter'] : 'kFormatter'); - /* @var $formatter kFormatter */ $parsed = $formatter->Parse($value, $name, $this); $this->SetDBField($name,$parsed); } /** * Sets current item field value * (doesn't apply formatting) * * @access public * @param string $name Name of the field * @param mixed $value Value to set the field to * @return void */ public function SetDBField($name,$value) { $this->FieldValues[$name] = $value; } /** * Set's field error, if pseudo passed not found then create it with message text supplied. * Don't overwrite existing pseudo translation. * * @param string $field * @param string $pseudo * @param string $error_label * @param Array $error_params * * @return bool * @access public */ public function SetError($field, $pseudo, $error_label = null, $error_params = null) { $this->initValidator(); return $this->validator->SetError($field, $pseudo, $error_label, $error_params); } /** * Removes error on field * * @param string $field * @access public */ public function RemoveError($field) { if ( !is_object($this->validator) ) { return ; } $this->validator->RemoveError($field); } /** * Returns error pseudo * * @param string $field * @return string */ public function GetErrorPseudo($field) { if ( !is_object($this->validator) ) { return ''; } return $this->validator->GetErrorPseudo($field); } /** * Return current item' field value by field name * (doesn't apply formatter) * * @param string $name field name to return * @return mixed * @access public */ public function GetDBField($name) { /*if (!array_key_exists($name, $this->FieldValues) && defined('DEBUG_MODE') && DEBUG_MODE) { $this->Application->Debugger->appendTrace(); }*/ return $this->FieldValues[$name]; } public function HasField($name) { return array_key_exists($name, $this->FieldValues); } public function GetFieldValues() { return $this->FieldValues; } /** * Sets item' fields corresponding to elements in passed $hash values. * The function sets current item fields to values passed in $hash, by matching $hash keys with field names * of current item. If current item' fields are unknown {@link kDBItem::PrepareFields()} is called before actually setting the fields * * @param Array $hash Fields hash. * @param Array $set_fields Optional param, field names in target object to set, other fields will be skipped * * @return void */ public function SetFieldsFromHash($hash, $set_fields = Array ()) { if ( !$set_fields ) { $set_fields = array_keys($hash); } $skip_fields = $this->getRequestProtectedFields($hash); if ( $skip_fields ) { $set_fields = array_diff($set_fields, $skip_fields); } $set_fields = array_intersect($set_fields, array_keys($this->Fields)); // used in formatter which work with multiple fields together foreach ($set_fields as $field_name) { $this->SetDirtyField($field_name, $hash[$field_name]); } // formats all fields using associated formatters foreach ($set_fields as $field_name) { $this->SetField($field_name, $hash[$field_name]); } } /** * Returns fields, that are not allowed to be changed from request. * * @param array $fields_hash Fields hash. * * @return array */ protected function getRequestProtectedFields(array $fields_hash) { // by default don't allow changing ID or foreign key from request $config = $this->getUnitConfig(); $fields = Array (); $fields[] = $config->getIDField(); $parent_prefix = $config->getParentPrefix(); if ( $parent_prefix && $this->isLoaded() && !$this->Application->isAdmin ) { // don't allow changing foreign key of existing item from request $fields[] = $config->getForeignKey($parent_prefix); } return $fields; } /** * Sets object fields from $hash array * @param Array $hash * @param Array|null $set_fields * @return void * @access public */ public function SetDBFieldsFromHash($hash, $set_fields = Array ()) { if ( !$set_fields ) { $set_fields = array_keys($hash); } $set_fields = array_intersect($set_fields, array_keys($this->Fields)); foreach ($set_fields as $field_name) { $this->SetDBField($field_name, $hash[$field_name]); } } /** * Returns part of SQL WHERE clause identifying the record, ex. id = 25 * * @param string $method Child class may want to know who called GetKeyClause, Load(), Update(), Delete() send its names as method * @param Array $keys_hash alternative, then item id, keys hash to load item by * @see kDBItem::Load() * @see kDBItem::Update() * @see kDBItem::Delete() * @return string * @access protected */ protected function GetKeyClause($method = null, $keys_hash = null) { if ( !isset($keys_hash) ) { $keys_hash = Array ($this->IDField => $this->ID); } $ret = ''; foreach ($keys_hash as $field => $value) { $value_part = is_null($value) ? ' IS NULL' : ' = ' . $this->Conn->qstr($value); $ret .= '(' . (strpos($field, '.') === false ? '`' . $this->TableName . '`.' : '') . $field . $value_part . ') AND '; } return substr($ret, 0, -5); } /** * Loads item from the database by given id * * @access public * @param mixed $id item id of keys->values hash to load item by * @param string $id_field_name Optional parameter to load item by given Id field * @param bool $cachable cache this query result based on it's prefix serial * @return bool True if item has been loaded, false otherwise */ public function Load($id, $id_field_name = null, $cachable = false) { $this->Clear(); if ( isset($id_field_name) ) { $this->IDField = $id_field_name; // set new IDField } $keys_sql = ''; if (is_array($id)) { $keys_sql = $this->GetKeyClause('load', $id); } else { $this->setID($id); $keys_sql = $this->GetKeyClause('load'); } if ( isset($id_field_name) ) { // restore original IDField from unit config $this->IDField = $this->getUnitConfig()->getIDField(); } if (($id === false) || !$keys_sql) { return false; } if (!$this->raiseEvent('OnBeforeItemLoad', $id)) { return false; } $q = $this->GetSelectSQL() . ' WHERE ' . $keys_sql; if ($cachable && $this->Application->isCachingType(CACHING_TYPE_MEMORY)) { $serial_name = $this->Application->incrementCacheSerial($this->Prefix == 'st' ? 'c' : $this->Prefix, isset($id_field_name) ? null : $id, false); $cache_key = 'kDBItem::Load_' . crc32(serialize($id) . '-' . $this->IDField) . '[%' . $serial_name . '%]'; $field_values = $this->Application->getCache($cache_key, false); if ($field_values === false) { $field_values = $this->Conn->GetRow($q); if ($field_values !== false) { // only cache, when data was retrieved $this->Application->setCache($cache_key, $field_values); } } } else { $field_values = $this->Conn->GetRow($q); } if ($field_values) { $this->FieldValues = array_merge($this->FieldValues, $field_values); $this->OriginalFieldValues = $this->FieldValues; $this->Loaded = true; } else { return false; } if (is_array($id) || isset($id_field_name)) { $this->setID($this->FieldValues[$this->IDField]); } $this->UpdateFormattersSubFields(); // used for updating separate virtual date/time fields from DB timestamp (for example) $this->raiseEvent('OnAfterItemLoad', $this->GetID()); return true; } /** * Loads object from hash (not db) * * @param Array $fields_hash * @param string $id_field */ public function LoadFromHash($fields_hash, $id_field = null) { if (!isset($id_field)) { $id_field = $this->IDField; } $this->Clear(); if (!$fields_hash || !array_key_exists($id_field, $fields_hash)) { // no data OR id field missing return false; } $id = $fields_hash[$id_field]; if ( !$this->raiseEvent('OnBeforeItemLoad', $id) ) { return false; } $this->FieldValues = array_merge($this->FieldValues, $fields_hash); $this->OriginalFieldValues = $this->FieldValues; $this->setID($id); $this->UpdateFormattersSubFields(); // used for updating separate virtual date/time fields from DB timestamp (for example) $this->raiseEvent('OnAfterItemLoad', $id); $this->Loaded = true; return true; } /** * Builds select sql, SELECT ... FROM parts only * * @access public * @return string */ /** * Returns SELECT part of list' query * * @param string $base_query * @param bool $replace_table * @return string * @access public */ public function GetSelectSQL($base_query = null, $replace_table = true) { if (!isset($base_query)) { $base_query = $this->SelectClause; } $base_query = $this->addCalculatedFields($base_query); return parent::GetSelectSQL($base_query, $replace_table); } public function UpdateFormattersMasterFields() { $this->initValidator(); // used, when called not from kValidator::Validate method foreach ($this->Fields as $field => $options) { if ( isset($options['formatter']) ) { + /** @var kFormatter $formatter */ $formatter = $this->Application->recallObject($options['formatter']); - /* @var $formatter kFormatter */ $formatter->UpdateMasterFields($field, $this->GetDBField($field), $options, $this); } } } /** * Returns variable name, used to store pending file actions * * @return string * @access protected */ protected function _getPendingActionVariableName() { $window_id = $this->Application->GetTopmostWid($this->Prefix); return $this->Prefix . '_file_pending_actions' . $window_id; } /** * Returns pending actions * * @param mixed $id * @return Array * @access public */ public function getPendingActions($id = null) { if ( !isset($id) ) { $id = $this->GetID(); } $pending_actions = $this->Application->RecallVar($this->_getPendingActionVariableName()); $pending_actions = $pending_actions ? unserialize($pending_actions) : Array (); if ( is_numeric($id) ) { // filter by given/current id $ret = Array (); foreach ($pending_actions as $pending_action) { if ( $pending_action['id'] == $id ) { $ret[] = $pending_action; } } return $ret; } return $pending_actions; } /** * Sets new pending actions * * @param Array|null $new_pending_actions * @param mixed $id * @return void * @access public */ public function setPendingActions($new_pending_actions = null, $id = null) { if ( !isset($new_pending_actions) ) { $new_pending_actions = Array (); } if ( !isset($id) ) { $id = $this->GetID(); } $pending_actions = Array (); $old_pending_actions = $this->getPendingActions(true); if ( is_numeric($id) ) { // remove old actions for this id foreach ($old_pending_actions as $pending_action) { if ( $pending_action['id'] != $id ) { $pending_actions[] = $pending_action; } } // add new actions for this id $pending_actions = array_merge($pending_actions, $new_pending_actions); } else { $pending_actions = $new_pending_actions; } // save changes $var_name = $this->_getPendingActionVariableName(); if ( !$pending_actions ) { $this->Application->RemoveVar($var_name); } else { $this->Application->StoreVar($var_name, serialize($this->sortPendingActions($pending_actions))); } } /** * Sorts pending actions the way, that `delete` action will come before other actions. * * @param array $pending_actions Pending actions. * * @return array */ protected function sortPendingActions(array $pending_actions) { usort($pending_actions, array($this, 'comparePendingActions')); return $pending_actions; } protected function comparePendingActions($pending_action_a, $pending_action_b) { if ( $pending_action_a['action'] == $pending_action_b['action'] ) { return 0; } return $pending_action_a['action'] == 'delete' ? -1 : 1; } /** * Allows to skip certain fields from getting into sql queries * * @param string $field_name * @param mixed $force_id * @return bool */ public function skipField($field_name, $force_id = false) { $skip = false; // 1. skipping 'virtual' field $skip = $skip || array_key_exists($field_name, $this->VirtualFields); // 2. don't write empty field value to db, when "skip_empty" option is set $field_value = array_key_exists($field_name, $this->FieldValues) ? $this->FieldValues[$field_name] : false; if (array_key_exists($field_name, $this->Fields)) { $skip_empty = array_key_exists('skip_empty', $this->Fields[$field_name]) ? $this->Fields[$field_name]['skip_empty'] : false; } else { // field found in database, but not declared in unit config $skip_empty = false; } $skip = $skip || (!$field_value && $skip_empty); // 3. skipping field not in Fields (nor virtual, nor real) $skip = $skip || !array_key_exists($field_name, $this->Fields); return $skip; } /** * Updates previously loaded record with current item' values * * @access public * @param int $id Primary Key Id to update * @param Array $update_fields * @param bool $system_update * @return bool * @access public */ public function Update($id = null, $update_fields = null, $system_update = false) { if ( isset($id) ) { $this->setID($id); } if ( !$this->raiseEvent('OnBeforeItemUpdate') ) { return false; } if ( !isset($this->ID) ) { // ID could be set inside OnBeforeItemUpdate event, so don't combine this check with previous one return false; } // validate before updating if ( !$this->Validate() ) { return false; } if ( !$this->FieldValues ) { // nothing to update return true; } $sql = ''; $set_fields = isset($update_fields) ? $update_fields : array_keys($this->FieldValues); foreach ($set_fields as $field_name) { if ( $this->skipField($field_name) ) { continue; } $field_value = $this->FieldValues[$field_name]; if ( is_null($field_value) ) { if ( array_key_exists('not_null', $this->Fields[$field_name]) && $this->Fields[$field_name]['not_null'] ) { // "kFormatter::Parse" methods converts empty values to NULL and for // not-null fields they are replaced with default value here $field_value = $this->Fields[$field_name]['default']; } } $sql .= '`' . $field_name . '` = ' . $this->Conn->qstr($field_value) . ', '; } $sql = 'UPDATE ' . $this->TableName . ' SET ' . substr($sql, 0, -2) . ' WHERE ' . $this->GetKeyClause('update'); if ( $this->Conn->ChangeQuery($sql) === false ) { // there was and sql error $this->SetError($this->IDField, 'sql_error', '#' . $this->Conn->getErrorCode() . ': ' . $this->Conn->getErrorMsg()); return false; } $affected_rows = $this->Conn->getAffectedRows(); if ( !$system_update && ($affected_rows > 0) ) { $this->setModifiedFlag(ChangeLog::UPDATE); } $this->saveCustomFields(); $this->raiseEvent('OnAfterItemUpdate'); // Preserve OriginalFieldValues during recursive Update() method calls. $this->Loaded = true; if ( !$this->IsTempTable() ) { $this->Application->resetCounters($this->TableName); } return true; } /** * Validates given field * * @param string $field * @return bool * @access public */ public function ValidateField($field) { $this->initValidator(); return $this->validator->ValidateField($field); } /** * Validate all item fields based on * constraints set in each field options * in config * * @return bool * @access private */ public function Validate() { if ( $this->IgnoreValidation ) { return true; } $this->initValidator(); // will apply any custom validation to the item $this->raiseEvent('OnBeforeItemValidate'); if ( $this->validator->Validate() ) { // no validation errors $this->raiseEvent('OnAfterItemValidate'); return true; } return false; } /** * Check if item has errors * * @param Array $skip_fields fields to skip during error checking * @return bool */ public function HasErrors($skip_fields = Array ()) { if ( !is_object($this->validator) ) { return false; } return $this->validator->HasErrors($skip_fields); } /** * Check if value is set for required field * * @param string $field field name * @param Array $params field options from config * @return bool * @access public * @todo Find a way to get rid of direct call from kMultiLanguage::UpdateMasterFields method */ public function ValidateRequired($field, $params) { return $this->validator->ValidateRequired($field, $params); } /** * Return error message for field * * @param string $field * @param bool $force_escape * @return string * @access public */ public function GetErrorMsg($field, $force_escape = null) { if ( !is_object($this->validator) ) { return ''; } return $this->validator->GetErrorMsg($field, $force_escape); } /** * Returns field errors * * @return Array * @access public */ public function GetFieldErrors() { if ( !is_object($this->validator) ) { return Array (); } return $this->validator->GetFieldErrors(); } /** * Creates a record in the database table with current item' values * * @param mixed $force_id Set to TRUE to force creating of item's own ID or to value to force creating of passed id. Do not pass 1 for true, pass exactly TRUE! * @param bool $system_create * @return bool * @access public */ public function Create($force_id = false, $system_create = false) { if (!$this->raiseEvent('OnBeforeItemCreate')) { return false; } // Validating fields before attempting to create record if (!$this->Validate()) { return false; } if (is_int($force_id)) { $this->FieldValues[$this->IDField] = $force_id; } elseif (!$force_id || !is_bool($force_id)) { $this->FieldValues[$this->IDField] = $this->generateID(); } $fields_sql = ''; $values_sql = ''; foreach ($this->FieldValues as $field_name => $field_value) { if ($this->skipField($field_name, $force_id)) { continue; } if (is_null($field_value)) { if (array_key_exists('not_null', $this->Fields[$field_name]) && $this->Fields[$field_name]['not_null']) { // "kFormatter::Parse" methods converts empty values to NULL and for // not-null fields they are replaced with default value here $values_sql .= $this->Conn->qstr($this->Fields[$field_name]['default']); } else { $values_sql .= $this->Conn->qstr($field_value); } } else { if (($field_name == $this->IDField) && ($field_value == 0) && !is_int($force_id)) { // don't skip IDField in INSERT statement, just use DEFAULT keyword as it's value $values_sql .= 'DEFAULT'; } else { $values_sql .= $this->Conn->qstr($field_value); } } $fields_sql .= '`' . $field_name . '`, '; //Adding field name to fields block of Insert statement $values_sql .= ', '; } $sql = 'INSERT INTO ' . $this->TableName . ' (' . substr($fields_sql, 0, -2) . ') VALUES (' . substr($values_sql, 0, -2) . ')'; //Executing the query and checking the result if ($this->Conn->ChangeQuery($sql) === false) { $this->SetError($this->IDField, 'sql_error', '#' . $this->Conn->getErrorCode() . ': ' . $this->Conn->getErrorMsg()); return false; } $insert_id = $this->Conn->getInsertID(); if ($insert_id == 0) { // insert into temp table (id is not auto-increment field) $insert_id = $this->FieldValues[$this->IDField]; } $temp_id = $this->GetID(); $this->setID($insert_id); $this->OriginalFieldValues = $this->FieldValues; if (!$system_create){ $this->setModifiedFlag(ChangeLog::CREATE); } $this->saveCustomFields(); if (!$this->IsTempTable()) { $this->Application->resetCounters($this->TableName); } if ($this->IsTempTable() && ($this->Application->GetTopmostPrefix($this->Prefix) != $this->Prefix) && !is_int($force_id)) { // temp table + subitem = set negative id $this->setTempID(); } $this->raiseEvent('OnAfterItemCreate', null, array('temp_id' => $temp_id)); $this->Loaded = true; return true; } /** * Deletes the record from database * * @param int $id * @return bool * @access public */ public function Delete($id = null) { if ( isset($id) ) { $this->setID($id); } if ( !$this->raiseEvent('OnBeforeItemDelete') ) { return false; } $sql = 'DELETE FROM ' . $this->TableName . ' WHERE ' . $this->GetKeyClause('Delete'); $ret = $this->Conn->ChangeQuery($sql); $affected_rows = $this->Conn->getAffectedRows(); if ( $affected_rows > 0 ) { $this->setModifiedFlag(ChangeLog::DELETE); // will change affected rows, so get it before this line // something was actually deleted $this->raiseEvent('OnAfterItemDelete'); } if ( !$this->IsTempTable() ) { $this->Application->resetCounters($this->TableName); } return $ret; } public function PopulateMultiLangFields() { foreach ($this->Fields as $field => $options) { // master field is set only for CURRENT language $formatter = array_key_exists('formatter', $options) ? $options['formatter'] : false; if ( ($formatter == 'kMultiLanguage') && isset($options['master_field']) && isset($options['error_field']) ) { // MuliLanguage formatter sets error_field to master_field, but in PopulateMlFields mode, // we display ML fields directly so we set it back to itself, otherwise error won't be displayed unset( $this->Fields[$field]['error_field'] ); } } } /** * Sets new name for item in case if it is being copied in same table * * @param array $master Table data from TempHandler * @param int $foreign_key ForeignKey value to filter name check query by * @param string $title_field FieldName to alter, by default - TitleField of the prefix * @param string $format sprintf-style format of renaming pattern, by default Copy %1$s of %2$s which makes it Copy [Number] of Original Name * @access public */ public function NameCopy($master=null, $foreign_key=null, $title_field=null, $format='Copy %1$s of %2$s') { if ( !isset($title_field) ) { $title_field = $this->getUnitConfig()->getTitleField(); if ( !$title_field || isset($this->CalculatedFields[$title_field]) ) { return; } } $original_checked = false; $new_name = $this->GetDBField($title_field); do { if ( preg_match('/' . sprintf($format, '([0-9]*) *', '(.*)') . '/', $new_name, $regs) ) { $new_name = sprintf($format, ($regs[1] + 1), $regs[2]); } elseif ( $original_checked ) { $new_name = sprintf($format, '', $new_name); } // if we are cloning in temp table this will look for names in temp table, // since object' TableName contains correct TableName (for temp also!) // if we are cloning live - look in live $sql = 'SELECT ' . $title_field . ' FROM ' . $this->TableName . ' WHERE ' . $title_field . ' = ' . $this->Conn->qstr($new_name); $foreign_key_field = getArrayValue($master, 'ForeignKey'); $foreign_key_field = is_array($foreign_key_field) ? $foreign_key_field[$master['ParentPrefix']] : $foreign_key_field; if ( $foreign_key_field && isset($foreign_key) ) { $sql .= ' AND ' . $foreign_key_field . ' = ' . $foreign_key; } $res = $this->Conn->GetOne($sql); // if not found in live table, check in temp table if applicable /*if ( $res === false && $this->Special == 'temp' ) { $sql = 'SELECT ' . $name_field . ' FROM ' . $this->Application->GetTempName($master['TableName']) . ' WHERE ' . $name_field . ' = ' . $this->Conn->qstr($new_name); $res = $this->Conn->GetOne($sql); }*/ $original_checked = true; } while ( $res !== false ); $this->SetDBField($title_field, $new_name); } protected function raiseEvent($name, $id = null, $additional_params = Array()) { $additional_params['id'] = isset($id) ? $id : $this->GetID(); $event = new kEvent($this->getPrefixSpecial() . ':' . $name, $additional_params); if ( is_object($this->parentEvent) ) { $event->MasterEvent = $this->parentEvent; } $this->Application->HandleEvent($event); return $event->status == kEvent::erSUCCESS; } /** * Set's new ID for item * * @param int $new_id * @access public */ public function setID($new_id) { $this->ID = $new_id; $this->SetDBField($this->IDField, $new_id); } /** * Generate and set new temporary id * * @access private */ public function setTempID() { $new_id = (int)$this->Conn->GetOne('SELECT MIN(' . $this->IDField . ') FROM ' . $this->TableName); if ( $new_id > 0 ) { $new_id = 0; } --$new_id; $this->Conn->Query('UPDATE ' . $this->TableName . ' SET `' . $this->IDField . '` = ' . $new_id . ' WHERE `' . $this->IDField . '` = ' . $this->GetID()); if ( $this->ShouldLogChanges(true) ) { // Updating TempId in ChangesLog, if changes are disabled $ses_var_name = $this->Application->GetTopmostPrefix($this->Prefix) . '_changes_' . $this->Application->GetTopmostWid($this->Prefix); $changes = $this->Application->RecallVar($ses_var_name); $changes = $changes ? unserialize($changes) : Array (); if ( $changes ) { foreach ($changes as $key => $rec) { if ( $rec['Prefix'] == $this->Prefix && $rec['ItemId'] == $this->GetID() ) { // change log for record, that's ID was just updated -> update in change log record too $changes[$key]['ItemId'] = $new_id; } if ( $rec['MasterPrefix'] == $this->Prefix && $rec['MasterId'] == $this->GetID() ) { // master item id was changed $changes[$key]['MasterId'] = $new_id; } if ( in_array($this->Prefix, $rec['ParentPrefix']) && $rec['ParentId'][$this->Prefix] == $this->GetID() ) { // change log record of given item's sub item -> update changed id's in dependent fields $changes[$key]['ParentId'][$this->Prefix] = $new_id; if ( array_key_exists('DependentFields', $rec) ) { // these are fields from table of $rec['Prefix'] table! // when one of dependent fields goes into id field of it's parent item, that was changed $config = $this->Application->getUnitConfig($rec['Prefix']); $parent_table_key = $config->getParentTableKey($this->Prefix); if ( $parent_table_key == $this->IDField ) { $foreign_key = $config->getForeignKey($this->Prefix); $changes[$key]['DependentFields'][$foreign_key] = $new_id; } } } } } $this->Application->StoreVar($ses_var_name, serialize($changes)); } $this->SetID($new_id); } /** * Set's modification flag for main prefix of current prefix to true * * @param int $mode * @access private */ public function setModifiedFlag($mode = null) { $main_prefix = $this->Application->GetTopmostPrefix($this->Prefix); $this->Application->StoreVar($main_prefix . '_modified', '1', true); // true for optional if ($this->ShouldLogChanges(true)) { $this->LogChanges($main_prefix, $mode); if (!$this->IsTempTable()) { + /** @var kDBEventHandler $handler */ $handler = $this->Application->recallObject($this->Prefix . '_EventHandler'); - /* @var $handler kDBEventHandler */ $ses_var_name = $main_prefix . '_changes_' . $this->Application->GetTopmostWid($this->Prefix); $handler->SaveLoggedChanges($ses_var_name, $this->ShouldLogChanges()); } } } /** * Determines, that changes made to this item should be written to change log * * @param bool $log_changes * @return bool */ public function ShouldLogChanges($log_changes = null) { $config = $this->getUnitConfig(); if ( !isset($log_changes) ) { // specific logging mode no forced -> use global logging settings $log_changes = $config->getLogChanges() || $this->Application->ConfigValue('UseChangeLog'); } return $log_changes && !$config->getForceDontLogChanges(); } protected function LogChanges($main_prefix, $mode) { if ( !$mode ) { return ; } $ses_var_name = $main_prefix . '_changes_' . $this->Application->GetTopmostWid($this->Prefix); $changes = $this->Application->RecallVar($ses_var_name); $changes = $changes ? unserialize($changes) : Array (); $fields_hash = Array ( 'Prefix' => $this->Prefix, 'ItemId' => $this->GetID(), 'OccuredOn' => time(), 'MasterPrefix' => $main_prefix, 'Action' => $mode, ); if ( $this->Prefix == $main_prefix ) { // main item $fields_hash['MasterId'] = $this->GetID(); $fields_hash['ParentPrefix'] = Array ($main_prefix); $fields_hash['ParentId'] = Array ($main_prefix => $this->GetID()); } else { // sub item // collect foreign key values (for serial reset) $config = $this->getUnitConfig(); $foreign_keys = $config->getForeignKey(null, Array ()); $dependent_fields = $fields_hash['ParentId'] = $fields_hash['ParentPrefix'] = Array (); - /* @var $foreign_keys Array */ + /** @var Array $foreign_keys */ if ( is_array($foreign_keys) ) { foreach ($foreign_keys as $prefix => $field_name) { $dependent_fields[$field_name] = $this->GetDBField($field_name); $fields_hash['ParentPrefix'][] = $prefix; $fields_hash['ParentId'][$prefix] = $this->getParentId($prefix); } } else { $dependent_fields[$foreign_keys] = $this->GetDBField($foreign_keys); $fields_hash['ParentPrefix'] = Array ( $config->getParentPrefix() ); $fields_hash['ParentId'][ $fields_hash['ParentPrefix'][0] ] = $this->getParentId('auto'); } $fields_hash['DependentFields'] = $dependent_fields; // works only, when main item is present in url, when sub-item is changed $master_id = $this->Application->GetVar($main_prefix . '_id'); if ( $master_id === false ) { // works in case of we are not editing topmost item, when sub-item is created/updated/deleted $master_id = $this->getParentId('auto', true); } $fields_hash['MasterId'] = $master_id; } switch ( $mode ) { case ChangeLog::UPDATE: $to_save = array_merge($this->GetTitleField(), $this->GetChangedFields()); break; case ChangeLog::CREATE: $to_save = $this->GetTitleField(); break; case ChangeLog::DELETE: $to_save = array_merge($this->GetTitleField(), $this->GetRealFields()); break; default: $to_save = Array (); break; } $fields_hash['Changes'] = serialize($to_save); $changes[] = $fields_hash; $this->Application->StoreVar($ses_var_name, serialize($changes)); } /** * Returns current item parent's ID * * @param string $parent_prefix * @param bool $top_most return topmost parent, when used * @return int * @access public */ public function getParentId($parent_prefix, $top_most = false) { $current_id = $this->GetID(); $current_prefix = $this->Prefix; $current_config = $this->Application->getUnitConfig($current_prefix); if ( $parent_prefix == 'auto' ) { $parent_prefix = $current_config->getParentPrefix(); } if ( !$parent_prefix ) { return $current_id; } do { // field in this table $foreign_key = $current_config->getForeignKey($parent_prefix); // get foreign key value for $current_prefix if ( $current_prefix == $this->Prefix ) { $foreign_key_value = $this->GetDBField($foreign_key); } else { $table_name = $current_config->getTableName(); if ( $this->IsTempTable() ) { $table_name = $this->Application->GetTempName($table_name, 'prefix:' . $current_prefix); } $sql = 'SELECT ' . $foreign_key . ' FROM ' . $table_name . ' WHERE ' . $current_config->getIDField() . ' = ' . $current_id; $foreign_key_value = $this->Conn->GetOne($sql); } // field in parent table $parent_table_key = $current_config->getParentTableKey($parent_prefix); $parent_config = $this->Application->getUnitConfig($parent_prefix); $parent_id_field = $parent_config->getIDField(); $parent_table_name = $parent_config->getTableName(); if ( $this->IsTempTable() ) { $parent_table_name = $this->Application->GetTempName($parent_table_name, 'prefix:' . $current_prefix); } if ( $parent_id_field == $parent_table_key ) { // sub-item is related by parent item id field $current_id = $foreign_key_value; } else { // sub-item is related by other parent item field $sql = 'SELECT ' . $parent_id_field . ' FROM ' . $parent_table_name . ' WHERE ' . $parent_table_key . ' = ' . $foreign_key_value; $current_id = $this->Conn->GetOne($sql); } $current_prefix = $parent_prefix; $current_config = $this->Application->getUnitConfig($current_prefix); if ( !$top_most ) { break; } } while ( $parent_prefix = $current_config->getParentPrefix() ); return $current_id; } /** * Returns title field (if any) * * @return Array */ public function GetTitleField() { $title_field = $this->getUnitConfig()->getTitleField(); if ( $title_field ) { $value = $this->GetField($title_field); return $value ? Array ($title_field => $value) : Array (); } return Array (); } /** * Returns only fields, that are present in database (no virtual and no calculated fields) * * @return Array */ public function GetRealFields() { return array_diff_key($this->FieldValues, $this->VirtualFields, $this->CalculatedFields); } /** * Returns only changed database field * * @param bool $include_virtual_fields * @return Array */ public function GetChangedFields($include_virtual_fields = false) { $changes = Array (); $fields = $include_virtual_fields ? $this->FieldValues : $this->GetRealFields(); $diff = array_diff_assoc($fields, $this->OriginalFieldValues); foreach ($diff as $field => $new_value) { $old_value = $this->GetOriginalField($field, true); $new_value = $this->GetField($field); if ($old_value != $new_value) { // "0.00" and "0.0000" are stored as strings and will differ. Double check to prevent that. $changes[$field] = Array ('old' => $old_value, 'new' => $new_value); } } return $changes; } /** * Returns ID of currently processed record * * @return int * @access public */ public function GetID() { return $this->ID; } /** * Generates ID for new items before inserting into database * * @return int * @access private */ protected function generateID() { return 0; } /** * Returns true if item was loaded successfully by Load method * * @return bool */ public function isLoaded() { return $this->Loaded; } /** * Checks if field is required * * @param string $field * @return bool */ public function isRequired($field) { return isset($this->Fields[$field]['required']) && $this->Fields[$field]['required']; } /** * Sets new required flag to field * * @param mixed $fields * @param bool $is_required */ public function setRequired($fields, $is_required = true) { if ( !is_array($fields) ) { $fields = explode(',', $fields); } foreach ($fields as $field) { $this->Fields[$field]['required'] = $is_required; } } /** * Removes all data from an object * * @param int $new_id * @return bool * @access public */ public function Clear($new_id = null) { $this->Loaded = false; $this->FieldValues = $this->OriginalFieldValues = Array (); $this->SetDefaultValues(); // will wear off kDBItem::setID effect, so set it later if ( is_object($this->validator) ) { $this->validator->reset(); } $this->setID($new_id); return $this->Loaded; } public function Query($force = false) { throw new Exception('Query method is called in class ' . get_class($this) . ' for prefix ' . $this->getPrefixSpecial() . ''); } protected function saveCustomFields() { if ( !$this->customFields || $this->inCloning ) { return true; } $cdata_key = rtrim($this->Prefix . '-cdata.' . $this->Special, '.'); + /** @var kDBItem $cdata */ $cdata = $this->Application->recallObject($cdata_key, null, Array ('skip_autoload' => true)); - /* @var $cdata kDBItem */ $resource_id = $this->GetDBField('ResourceId'); $cdata->Load($resource_id, 'ResourceId'); $cdata->SetDBField('ResourceId', $resource_id); + /** @var kMultiLanguage $ml_formatter */ $ml_formatter = $this->Application->recallObject('kMultiLanguage'); - /* @var $ml_formatter kMultiLanguage */ + /** @var kMultiLanguageHelper $ml_helper */ $ml_helper = $this->Application->recallObject('kMultiLanguageHelper'); - /* @var $ml_helper kMultiLanguageHelper */ $languages = $ml_helper->getLanguages(); foreach ($this->customFields as $custom_id => $custom_name) { $force_primary = $cdata->GetFieldOption('cust_' . $custom_id, 'force_primary'); if ( $force_primary ) { $cdata->SetDBField($ml_formatter->LangFieldName('cust_' . $custom_id, true), $this->GetDBField('cust_' . $custom_name)); } else { foreach ($languages as $language_id) { $cdata->SetDBField('l' . $language_id . '_cust_' . $custom_id, $this->GetDBField('l' . $language_id . '_cust_' . $custom_name)); } } } return $cdata->isLoaded() ? $cdata->Update() : $cdata->Create(); } /** * Returns specified field value from all selected rows. * Don't affect current record index * * @param string $field * @param bool $formatted * @param string $format * @return Array */ public function GetCol($field, $formatted = false, $format = null) { if ($formatted) { return Array (0 => $this->GetField($field, $format)); } return Array (0 => $this->GetDBField($field)); } /** * Set's loaded status of object * * @param bool $is_loaded * @access public * @todo remove this method, since item can't be marked as loaded externally */ public function setLoaded($is_loaded = true) { $this->Loaded = $is_loaded; } /** * Returns parser parameters, used to identify this object in the e-mail template. * * @param array $merge_with Original send params to merge with. * * @return array */ public function getEmailParams(array $merge_with = array()) { $merge_with['item_id'] = $this->GetID(); $merge_with['PrefixSpecial'] = $this->getPrefixSpecial(); return $merge_with; } } Index: branches/5.3.x/core/kernel/db/cat_event_handler.php =================================================================== --- branches/5.3.x/core/kernel/db/cat_event_handler.php (revision 16518) +++ branches/5.3.x/core/kernel/db/cat_event_handler.php (revision 16519) @@ -1,3099 +1,3100 @@ Array ('self' => 'add|edit|advanced:import'), 'OnResetSettings' => Array ('self' => 'add|edit|advanced:import'), 'OnBeforeDeleteOriginal' => Array ('self' => 'edit|advanced:approve'), 'OnAfterDeleteOriginal' => Array ('self' => 'edit|advanced:approve'), 'OnCopy' => Array ('self' => true), 'OnDownloadFile' => Array ('self' => 'view'), 'OnCancelAction' => Array ('self' => true), 'OnItemBuild' => Array ('self' => true), 'OnMakeVote' => Array ('self' => true), 'OnReviewHelpful' => Array ('self' => true), ); $this->permMapping = array_merge($this->permMapping, $permissions); } /** * Load item if id is available * * @param kEvent $event * @return void * @access protected */ protected function LoadItem(kEvent $event) { + /** @var kDBItem $object */ $object = $event->getObject(); - /* @var $object kDBItem */ $id = $this->getPassedID($event); if ( $object->Load($id) ) { + /** @var Params $actions */ $actions = $this->Application->recallObject('kActions'); - /* @var $actions Params */ $actions->Set($event->getPrefixSpecial() . '_id', $object->GetID()); $use_pending_editing = $event->getUnitConfig()->getUsePendingEditing(); if ( $use_pending_editing && $event->Special != 'original' ) { $this->Application->SetVar($event->Prefix . '.original_id', $object->GetDBField('OrgId')); } } else { $object->setID($id); } } /** * Checks user permission to execute given $event * * @param kEvent $event * @return bool * @access public */ public function CheckPermission(kEvent $event) { if ( !$this->Application->isAdmin ) { if ( $event->Name == 'OnSetSortingDirect' ) { // allow sorting on front event without view permission return true; } } if ( $event->Name == 'OnExport' ) { // save category_id before doing export $this->Application->LinkVar('m_cat_id'); } if ( in_array($event->Name, $this->_getMassPermissionEvents()) ) { $items = $this->_getPermissionCheckInfo($event); + /** @var kPermissionsHelper $perm_helper */ $perm_helper = $this->Application->recallObject('PermissionsHelper'); - /* @var $perm_helper kPermissionsHelper */ if ( ($event->Name == 'OnSave') && array_key_exists(0, $items) ) { // adding new item (ID = 0) $perm_value = $perm_helper->AddCheckPermission($items[0]['CategoryId'], $event->Prefix) > 0; } else { // leave only items, that can be edited $ids = Array (); $check_method = in_array($event->Name, Array ('OnMassDelete', 'OnCut')) ? 'DeleteCheckPermission' : 'ModifyCheckPermission'; foreach ($items as $item_id => $item_data) { if ( $perm_helper->$check_method($item_data['CreatedById'], $item_data['CategoryId'], $event->Prefix) > 0 ) { $ids[] = $item_id; } } if ( !$ids ) { // no items left for editing -> no permission return $perm_helper->finalizePermissionCheck($event, false); } $perm_value = true; $event->setEventParam('ids', $ids); // will be used later by "kDBEventHandler::StoreSelectedIDs" method } return $perm_helper->finalizePermissionCheck($event, $perm_value); } $export_events = array('OnSaveSettings', 'OnResetSettings', 'OnExportBegin'); if ( in_array($event->Name, $export_events) || ($event->Special == 'export' && $event->Name == 'OnNew') ) { /** @var kPermissionsHelper $perm_helper */ $perm_helper = $this->Application->recallObject('PermissionsHelper'); $perm_value = $this->Application->CheckPermission('in-portal:main_import.view'); return $perm_helper->finalizePermissionCheck($event, $perm_value); } if ( $event->Name == 'OnProcessSelected' ) { if ( $this->Application->RecallVar('dst_field') == 'ImportCategory' ) { // when selecting target import category return $this->Application->CheckPermission('in-portal:main_import.view'); } } return parent::CheckPermission($event); } /** * Returns events, that require item-based (not just event-name based) permission check * * @return Array */ function _getMassPermissionEvents() { return Array ( 'OnEdit', 'OnSave', 'OnMassDelete', 'OnMassApprove', 'OnMassDecline', 'OnMassMoveUp', 'OnMassMoveDown', 'OnCut', ); } /** * Returns category item IDs, that require permission checking * * @param kEvent $event * @return string */ function _getPermissionCheckIDs($event) { if ($event->Name == 'OnSave') { $selected_ids = implode(',', $this->getSelectedIDs($event, true)); if (!$selected_ids) { $selected_ids = 0; // when saving newly created item (OnPreCreate -> OnPreSave -> OnSave) } } else { // OnEdit, OnMassDelete events, when items are checked in grid $selected_ids = implode(',', $this->StoreSelectedIDs($event)); } return $selected_ids; } /** * Returns information used in permission checking * * @param kEvent $event * @return Array */ function _getPermissionCheckInfo($event) { + /** @var kPermissionsHelper $perm_helper */ $perm_helper = $this->Application->recallObject('PermissionsHelper'); - /* @var $perm_helper kPermissionsHelper */ // when saving data from temp table to live table check by data from temp table $item_ids = $this->_getPermissionCheckIDs($event); $items = $perm_helper->GetCategoryItemData($event->Prefix, $item_ids, $event->Name == 'OnSave'); if (!$items) { // when item not present in temp table, then permission is not checked, because there are no data in db to check $items_info = $this->Application->GetVar( $event->getPrefixSpecial(true) ); list ($id, $fields_hash) = each($items_info); if (array_key_exists('CategoryId', $fields_hash)) { $item_category = $fields_hash['CategoryId']; } else { $item_category = $this->Application->GetVar('m_cat_id'); } $items[$id] = Array ( 'CreatedById' => $this->Application->RecallVar('use_id'), 'CategoryId' => $item_category, ); } return $items; } /** * Add selected items to clipboard with mode = COPY (CLONE) * * @param kEvent $event * @return void * @access protected */ protected function OnCopy($event) { $this->Application->RemoveVar('clipboard'); + /** @var kClipboardHelper $clipboard_helper */ $clipboard_helper = $this->Application->recallObject('ClipboardHelper'); - /* @var $clipboard_helper kClipboardHelper */ $clipboard_helper->setClipboard($event, 'copy', $this->StoreSelectedIDs($event)); $this->clearSelectedIDs($event); } /** * Add selected items to clipboard with mode = CUT * * @param kEvent $event * @return void * @access protected */ protected function OnCut($event) { $this->Application->RemoveVar('clipboard'); + + /** @var kClipboardHelper $clipboard_helper */ $clipboard_helper = $this->Application->recallObject('ClipboardHelper'); - /* @var $clipboard_helper kClipboardHelper */ $clipboard_helper->setClipboard($event, 'cut', $this->StoreSelectedIDs($event)); $this->clearSelectedIDs($event); } /** * Checks permission for OnPaste event * * @param kEvent $event * @return bool */ function _checkPastePermission($event) { + /** @var kPermissionsHelper $perm_helper */ $perm_helper = $this->Application->recallObject('PermissionsHelper'); - /* @var $perm_helper kPermissionsHelper */ $category_id = $this->Application->GetVar('m_cat_id'); if ($perm_helper->AddCheckPermission($category_id, $event->Prefix) == 0) { // no items left for editing -> no permission return $perm_helper->finalizePermissionCheck($event, false); } return true; } /** * Performs category item paste * * @param kEvent $event * @return void * @access protected */ protected function OnPaste($event) { if ( $this->Application->CheckPermission('SYSTEM_ACCESS.READONLY', 1) || !$this->_checkPastePermission($event) ) { $event->status = kEvent::erFAIL; return; } $clipboard_data = $event->getEventParam('clipboard_data'); if ( !$clipboard_data['cut'] && !$clipboard_data['copy'] ) { return; } if ( $clipboard_data['copy'] ) { + /** @var kTempTablesHandler $temp */ $temp = $this->Application->recallObject($event->getPrefixSpecial() . '_TempHandler', 'kTempTablesHandler', Array ('parent_event' => $event)); - /* @var $temp kTempTablesHandler */ $this->Application->SetVar('ResetCatBeforeClone', 1); // used in "kCatDBEventHandler::OnBeforeClone" $temp->CloneItems($event->Prefix, $event->Special, $clipboard_data['copy']); } if ( $clipboard_data['cut'] ) { + /** @var kCatDBItem $object */ $object = $this->Application->recallObject($event->getPrefixSpecial() . '.item', $event->Prefix, Array ('skip_autoload' => true)); - /* @var $object kCatDBItem */ foreach ($clipboard_data['cut'] as $id) { $object->Load($id); $object->MoveToCat(); } } } /** * Deletes all selected items. * Automatically recurse into sub-items using temp handler, and deletes sub-items * by calling its Delete method if sub-item has AutoDelete set to true in its config file * * @param kEvent $event * @return void * @access protected */ protected function OnMassDelete(kEvent $event) { if ( $this->Application->CheckPermission('SYSTEM_ACCESS.READONLY', 1) ) { $event->status = kEvent::erFAIL; return; } $ids = $this->StoreSelectedIDs($event); $to_delete = Array (); $recycle_bin = $this->Application->ConfigValue('RecycleBinFolder'); if ( $recycle_bin ) { + /** @var CategoriesItem $rb */ $rb = $this->Application->recallObject('c.recycle', NULL, array ('skip_autoload' => true)); - /* @var $rb CategoriesItem */ $rb->Load($recycle_bin); + /** @var kCatDBItem $object */ $object = $this->Application->recallObject($event->Prefix . '.recycleitem', NULL, Array ('skip_autoload' => true)); - /* @var $object kCatDBItem */ foreach ($ids as $id) { $object->Load($id); if ( preg_match('/^' . preg_quote($rb->GetDBField('ParentPath'), '/') . '/', $object->GetDBField('ParentPath')) ) { $to_delete[] = $id; continue; } $object->MoveToCat($recycle_bin); } $ids = $to_delete; } + /** @var kTempTablesHandler $temp_handler */ $temp_handler = $this->Application->recallObject($event->getPrefixSpecial() . '_TempHandler', 'kTempTablesHandler', Array ('parent_event' => $event)); - /* @var $temp_handler kTempTablesHandler */ $event->setEventParam('ids', $ids); $this->customProcessing($event, 'before'); $ids = $event->getEventParam('ids'); if ( $ids ) { $temp_handler->DeleteItems($event->Prefix, $event->Special, $ids); } $this->clearSelectedIDs($event); } /** * Return type clauses for list bulding on front * * @param kEvent $event * @return Array */ function getTypeClauses($event) { $types = $event->getEventParam('types'); $types = $types ? explode(',', $types) : Array (); $except_types = $event->getEventParam('except'); $except_types = $except_types ? explode(',', $except_types) : Array (); $type_clauses = Array(); $user_id = $this->Application->RecallVar('user_id'); $owner_field = $this->getOwnerField($event->Prefix); $type_clauses['my_items']['include'] = '%1$s.'.$owner_field.' = '.$user_id; $type_clauses['my_items']['except'] = '%1$s.'.$owner_field.' <> '.$user_id; $type_clauses['my_items']['having_filter'] = false; $type_clauses['pick']['include'] = '%1$s.EditorsPick = 1 AND '.TABLE_PREFIX.'CategoryItems.PrimaryCat = 1'; $type_clauses['pick']['except'] = '%1$s.EditorsPick! = 1 AND '.TABLE_PREFIX.'CategoryItems.PrimaryCat = 1'; $type_clauses['pick']['having_filter'] = false; $type_clauses['hot']['include'] = '`IsHot` = 1 AND PrimaryCat = 1'; $type_clauses['hot']['except'] = '`IsHot`! = 1 AND PrimaryCat = 1'; $type_clauses['hot']['having_filter'] = true; $type_clauses['pop']['include'] = '`IsPop` = 1 AND PrimaryCat = 1'; $type_clauses['pop']['except'] = '`IsPop`! = 1 AND PrimaryCat = 1'; $type_clauses['pop']['having_filter'] = true; $type_clauses['new']['include'] = '`IsNew` = 1 AND PrimaryCat = 1'; $type_clauses['new']['except'] = '`IsNew`! = 1 AND PrimaryCat = 1'; $type_clauses['new']['having_filter'] = true; $type_clauses['displayed']['include'] = ''; $displayed = $this->Application->GetVar($event->Prefix.'_displayed_ids'); if ($displayed) { $id_field = $event->getUnitConfig()->getIDField(); $type_clauses['displayed']['except'] = '%1$s.'.$id_field.' NOT IN ('.$displayed.')'; } else { $type_clauses['displayed']['except'] = ''; } $type_clauses['displayed']['having_filter'] = false; if (in_array('search', $types) || in_array('search', $except_types)) { $event_mapping = Array ( 'simple' => 'OnSimpleSearch', 'subsearch' => 'OnSubSearch', 'advanced' => 'OnAdvancedSearch' ); $keywords = $event->getEventParam('keyword_string'); $type = $this->Application->GetVar('search_type', 'simple'); if ( $keywords ) { // processing keyword_string param of ListProducts tag $this->Application->SetVar('keywords', $keywords); $type = 'simple'; } $search_event = $event_mapping[$type]; $this->$search_event($event); + /** @var kDBList $object */ $object = $event->getObject(); - /* @var $object kDBList */ /** @var kSearchHelper $search_helper */ $search_helper = $this->Application->recallObject('SearchHelper'); $search_sql = ' FROM ' . $search_helper->getSearchTable() . ' search_result JOIN %1$s ON %1$s.ResourceId = search_result.ResourceId'; $sql = str_replace('FROM %1$s', $search_sql, $object->GetPlainSelectSQL()); $object->SetSelectSQL($sql); $object->addCalculatedField('Relevance', 'search_result.Relevance'); $type_clauses['search']['include'] = 'PrimaryCat = 1 AND ('.TABLE_PREFIX.'Categories.Status = '.STATUS_ACTIVE.')'; $type_clauses['search']['except'] = 'PrimaryCat = 1 AND ('.TABLE_PREFIX.'Categories.Status = '.STATUS_ACTIVE.')'; $type_clauses['search']['having_filter'] = false; } if (in_array('related', $types) || in_array('related', $except_types)) { $related_to = $event->getEventParam('related_to'); if (!$related_to) { $related_prefix = $event->Prefix; } else { $sql = 'SELECT Prefix FROM '.TABLE_PREFIX.'ItemTypes WHERE ItemName = '.$this->Conn->qstr($related_to); $related_prefix = $this->Conn->GetOne($sql); } $rel_table = $this->Application->getUnitConfig('rel')->getTableName(); $item_type = (int)$event->getUnitConfig()->getItemType(); if ($item_type == 0) { trigger_error('ItemType not defined for prefix ' . $event->Prefix . '', E_USER_WARNING); } // process case, then this list is called inside another list $prefix_special = $event->getEventParam('PrefixSpecial'); if (!$prefix_special) { $prefix_special = $this->Application->Parser->GetParam('PrefixSpecial'); } $id = false; if ($prefix_special !== false) { $processed_prefix = $this->Application->processPrefix($prefix_special); if ($processed_prefix['prefix'] == $related_prefix) { // printing related categories within list of items (not on details page) + /** @var kDBList $list */ $list = $this->Application->recallObject($prefix_special); - /* @var $list kDBList */ $id = $list->GetID(); } } if ($id === false) { // printing related categories for single item (possibly on details page) if ($related_prefix == 'c') { $id = $this->Application->GetVar('m_cat_id'); } else { $id = $this->Application->GetVar($related_prefix . '_id'); } } + /** @var kCatDBItem $p_item */ $p_item = $this->Application->recallObject($related_prefix.'.current', NULL, Array('skip_autoload' => true)); - /* @var $p_item kCatDBItem */ $p_item->Load( (int)$id ); $p_resource_id = $p_item->GetDBField('ResourceId'); $sql = 'SELECT SourceId, TargetId FROM '.$rel_table.' WHERE (Enabled = 1) AND ( (Type = 0 AND SourceId = '.$p_resource_id.' AND TargetType = '.$item_type.') OR (Type = 1 AND ( (SourceId = '.$p_resource_id.' AND TargetType = '.$item_type.') OR (TargetId = '.$p_resource_id.' AND SourceType = '.$item_type.') ) ) )'; $related_ids_array = $this->Conn->Query($sql); $related_ids = Array(); foreach ($related_ids_array as $record) { $related_ids[] = $record[ $record['SourceId'] == $p_resource_id ? 'TargetId' : 'SourceId' ]; } if (count($related_ids) > 0) { $type_clauses['related']['include'] = '%1$s.ResourceId IN ('.implode(',', $related_ids).') AND PrimaryCat = 1'; $type_clauses['related']['except'] = '%1$s.ResourceId NOT IN ('.implode(',', $related_ids).') AND PrimaryCat = 1'; } else { $type_clauses['related']['include'] = '0'; $type_clauses['related']['except'] = '1'; } $type_clauses['related']['having_filter'] = false; } if (in_array('favorites', $types) || in_array('favorites', $except_types)) { $sql = 'SELECT ResourceId FROM ' . $this->Application->getUnitConfig('fav')->getTableName() . ' WHERE PortalUserId = '.$this->Application->RecallVar('user_id'); $favorite_ids = $this->Conn->GetCol($sql); if ($favorite_ids) { $type_clauses['favorites']['include'] = '%1$s.ResourceId IN ('.implode(',', $favorite_ids).') AND PrimaryCat = 1'; $type_clauses['favorites']['except'] = '%1$s.ResourceId NOT IN ('.implode(',', $favorite_ids).') AND PrimaryCat = 1'; } else { $type_clauses['favorites']['include'] = 0; $type_clauses['favorites']['except'] = 1; } $type_clauses['favorites']['having_filter'] = false; } return $type_clauses; } /** * Returns SQL clause, that will help to select only data from specified category & it's children * * @param int $category_id * @return string */ function getCategoryLimitClause($category_id) { if (!$category_id) { return false; } $tree_indexes = $this->Application->getTreeIndex($category_id); if (!$tree_indexes) { // id of non-existing category was given return 'FALSE'; } return TABLE_PREFIX.'Categories.TreeLeft BETWEEN '.$tree_indexes['TreeLeft'].' AND '.$tree_indexes['TreeRight']; } /** * Apply any custom changes to list's sql query * * @param kEvent $event * @return void * @access protected * @see kDBEventHandler::OnListBuild() */ protected function SetCustomQuery(kEvent $event) { parent::SetCustomQuery($event); + /** @var kCatDBList $object */ $object = $event->getObject(); - /* @var $object kCatDBList */ // add category filter if needed if ($event->Special != 'showall' && $event->Special != 'user') { if ( (string)$event->getEventParam('parent_cat_id') !== '' ) { $parent_cat_id = $event->getEventParam('parent_cat_id'); } else { $parent_cat_id = $this->Application->GetVar('c_id'); if (!$parent_cat_id) { $parent_cat_id = $this->Application->GetVar('m_cat_id'); } if (!$parent_cat_id) { $parent_cat_id = 0; } } if ("$parent_cat_id" == '0') { // replace "0" category with "Content" category id (this way template $parent_cat_id = $this->Application->getBaseCategory(); } if ((string)$parent_cat_id != 'any') { if ($event->getEventParam('recursive')) { $filter_clause = $this->getCategoryLimitClause($parent_cat_id); if ($filter_clause !== false) { $object->addFilter('category_filter', $filter_clause); } $object->addFilter('primary_filter', 'PrimaryCat = 1'); } else { $object->addFilter('category_filter', TABLE_PREFIX.'CategoryItems.CategoryId = '.$parent_cat_id ); } } else { $object->addFilter('primary_filter', 'PrimaryCat = 1'); } } else { $object->addFilter('primary_filter', 'PrimaryCat = 1'); // if using recycle bin don't show items from there $recycle_bin = $this->Application->ConfigValue('RecycleBinFolder'); if ($recycle_bin) { $object->addFilter('recyclebin_filter', TABLE_PREFIX.'CategoryItems.CategoryId <> '.$recycle_bin); } } if ($event->Special == 'user') { $editable_user = $this->Application->GetVar('u_id'); $object->addFilter('owner_filter', '%1$s.'.$this->getOwnerField($event->Prefix).' = '.$editable_user); } $this->applyViewPermissionFilter($object); $types = $event->getEventParam('types'); $this->applyItemStatusFilter($object, $types); $except_types = $event->getEventParam('except'); $type_clauses = $this->getTypeClauses($event); + /** @var kSearchHelper $search_helper */ $search_helper = $this->Application->recallObject('SearchHelper'); - /* @var $search_helper kSearchHelper */ $search_helper->SetComplexFilter($event, $type_clauses, $types, $except_types); } /** * Adds filter, that uses *.VIEW permissions to determine if an item should be shown to a user. * * @param kCatDBList $object Object. * * @return void * @access protected */ protected function applyViewPermissionFilter(kCatDBList $object) { if ( !$this->Application->ConfigValue('CheckViewPermissionsInCatalog') ) { return; } if ( $this->Application->RecallVar('user_id') == USER_ROOT ) { // for "root" CATEGORY.VIEW permission is checked for items lists too $view_perm = 1; } else { // for any real user item list view permission is checked instead of CATEGORY.VIEW + /** @var kCountHelper $count_helper */ $count_helper = $this->Application->recallObject('CountHelper'); - /* @var $count_helper kCountHelper */ list ($view_perm, $view_filter) = $count_helper->GetPermissionClause($object->Prefix, 'perm'); $object->addFilter('perm_filter2', $view_filter); } $object->addFilter('perm_filter', 'perm.PermId = ' . $view_perm); } /** * Adds filter that filters out items with non-required statuses * * @param kDBList $object * @param string $types */ function applyItemStatusFilter(&$object, $types) { // Link1 (before modifications) [Status = 1, OrgId = NULL], Link2 (after modifications) [Status = -2, OrgId = Link1_ID] $pending_editing = $object->getUnitConfig()->getUsePendingEditing(); if (!$this->Application->isAdminUser) { $types = explode(',', $types); if (in_array('my_items', $types)) { $allow_statuses = Array (STATUS_ACTIVE, STATUS_PENDING, STATUS_PENDING_EDITING); $object->addFilter('status_filter', '%1$s.Status IN ('.implode(',', $allow_statuses).')'); if ($pending_editing) { $user_id = $this->Application->RecallVar('user_id'); $this->applyPendingEditingFilter($object, $user_id); } } else { $object->addFilter('status_filter', '(%1$s.Status = ' . STATUS_ACTIVE . ') AND (' . TABLE_PREFIX . 'Categories.Status = ' . STATUS_ACTIVE . ')'); if ($pending_editing) { // if category item uses pending editing abilities, then in no cases show pending copies on front $object->addFilter('original_filter', '%1$s.OrgId = 0 OR %1$s.OrgId IS NULL'); } } } else { if ($pending_editing) { $this->applyPendingEditingFilter($object); } } } /** * Adds filter, that removes live items if they have pending editing copies * * @param kDBList $object * @param int $user_id */ function applyPendingEditingFilter(&$object, $user_id = NULL) { $sql = 'SELECT OrgId FROM '.$object->TableName.' WHERE Status = '.STATUS_PENDING_EDITING.' AND OrgId IS NOT NULL'; if (isset($user_id)) { $owner_field = $this->getOwnerField($object->Prefix); $sql .= ' AND '.$owner_field.' = '.$user_id; } $pending_ids = $this->Conn->GetCol($sql); if ($pending_ids) { $object->addFilter('no_original_filter', '%1$s.'.$object->IDField.' NOT IN ('.implode(',', $pending_ids).')'); } } /** * Adds calculates fields for item statuses * * @param kDBItem|kDBList $object * @param kEvent $event * @return void * @access protected */ protected function prepareObject(&$object, kEvent $event) { $this->prepareItemStatuses($event); $object->addCalculatedField('CachedNavbar', 'l' . $this->Application->GetVar('m_lang') . '_CachedNavbar'); if ( $event->Special == 'export' || $event->Special == 'import' ) { + /** @var kCatDBItemExportHelper $export_helper */ $export_helper = $this->Application->recallObject('CatItemExportHelper'); - /* @var $export_helper kCatDBItemExportHelper */ $export_helper->prepareExportColumns($event); } } /** * Creates calculated fields for all item statuses based on config settings * * @param kEvent $event */ function prepareItemStatuses($event) { $object = $event->getObject( Array('skip_autoload' => true) ); $property_map = $event->getUnitConfig()->getItemPropertyMappings(); if (!$property_map) { return ; } // new items $object->addCalculatedField('IsNew', ' IF(%1$s.NewItem = 2, IF(%1$s.CreatedOn >= (UNIX_TIMESTAMP() - ' . $this->Application->ConfigValue($property_map['NewDays']) . '*3600*24), 1, 0), %1$s.NewItem )'); // hot items (cache updated every hour) if ($this->Application->isCachingType(CACHING_TYPE_MEMORY)) { $serial_name = $this->Application->incrementCacheSerial($event->Prefix, NULL, false); $hot_limit = $this->Application->getCache($property_map['HotLimit'] . '[%' . $serial_name . '%]'); } else { $hot_limit = $this->Application->getDBCache($property_map['HotLimit']); } if ($hot_limit === false) { $hot_limit = $this->CalculateHotLimit($event); } $object->addCalculatedField('IsHot', ' IF(%1$s.HotItem = 2, IF(%1$s.'.$property_map['ClickField'].' >= '.$hot_limit.', 1, 0), %1$s.HotItem )'); // popular items $object->addCalculatedField('IsPop', ' IF(%1$s.PopItem = 2, IF(%1$s.CachedVotesQty >= ' . $this->Application->ConfigValue($property_map['MinPopVotes']) . ' AND %1$s.CachedRating >= ' . $this->Application->ConfigValue($property_map['MinPopRating']) . ', 1, 0), %1$s.PopItem)'); } /** * Calculates hot limit for current item's table * * @param kEvent $event * @return float * @access protected */ protected function CalculateHotLimit($event) { $config = $event->getUnitConfig(); $property_map = $config->getItemPropertyMappings(); if ( !$property_map ) { return 0.00; } $click_field = $property_map['ClickField']; $last_hot = $this->Application->ConfigValue($property_map['MaxHotNumber']) - 1; $sql = 'SELECT ' . $click_field . ' FROM ' . $config->getTableName() . ' ORDER BY ' . $click_field . ' DESC LIMIT ' . $last_hot . ', 1'; $res = $this->Conn->GetCol($sql); $hot_limit = (double)array_shift($res); if ( $this->Application->isCachingType(CACHING_TYPE_MEMORY) ) { $serial_name = $this->Application->incrementCacheSerial($event->Prefix, NULL, false); $this->Application->setCache($property_map['HotLimit'] . '[%' . $serial_name . '%]', $hot_limit); } else { $this->Application->setDBCache($property_map['HotLimit'], $hot_limit, 3600); } return $hot_limit; } /** * Moves item to preferred category, updates item hits * * @param kEvent $event * @return void * @access protected */ protected function OnBeforeItemUpdate(kEvent $event) { parent::OnBeforeItemUpdate($event); + /** @var kCatDBItem $object */ $object = $event->getObject(); - /* @var $object kCatDBItem */ // update hits field $config = $event->getUnitConfig(); $property_map = $config->getUserProfileMapping(); if ( $property_map ) { $click_field = $property_map['ClickField']; if ( $this->Application->isAdminUser && ($this->Application->GetVar($click_field . '_original') !== false) && floor($this->Application->GetVar($click_field . '_original')) != $object->GetDBField($click_field) ) { $sql = 'SELECT MAX(' . $click_field . ') FROM ' . $config->getTableName() . ' WHERE FLOOR(' . $click_field . ') = ' . $object->GetDBField($click_field); $hits = ($res = $this->Conn->GetOne($sql)) ? $res + 0.000001 : $object->GetDBField($click_field); $object->SetDBField($click_field, $hits); } } // change category $target_category = $object->GetDBField('CategoryId'); if ( $object->GetOriginalField('CategoryId') != $target_category ) { $object->MoveToCat($target_category); } } /** * Occurs after loading item, 'id' parameter * allows to get id of item that was loaded * * @param kEvent $event * @return void * @access protected */ protected function OnAfterItemLoad(kEvent $event) { parent::OnAfterItemLoad($event); $special = substr($event->Special, -6); + /** @var kCatDBItem $object */ $object = $event->getObject(); - /* @var $object kCatDBItem */ if ( $special == 'import' || $special == 'export' ) { $image_data = $object->getPrimaryImageData(); if ( $image_data ) { $thumbnail_image = $image_data[$image_data['LocalThumb'] ? 'ThumbPath' : 'ThumbUrl']; if ( $image_data['SameImages'] ) { $full_image = ''; } else { $full_image = $image_data[$image_data['LocalImage'] ? 'LocalPath' : 'Url']; } $object->SetDBField('ThumbnailImage', $thumbnail_image); $object->SetDBField('FullImage', $full_image); $object->SetDBField('ImageAlt', $image_data['AltName']); } } // substituting pending status value for pending editing if ( $object->HasField('OrgId') && $object->GetDBField('OrgId') > 0 && $object->GetDBField('Status') == -2 ) { $new_options = Array (); $options = $object->GetFieldOption('Status', 'options', false, Array ()); foreach ($options as $key => $val) { if ( $key == 2 ) { $key = -2; } $new_options[$key] = $val; } $object->SetFieldOption('Status', 'options', $new_options); } if ( !$this->Application->isAdmin ) { // linking existing images for item with virtual fields + /** @var ImageHelper $image_helper */ $image_helper = $this->Application->recallObject('ImageHelper'); - /* @var $image_helper ImageHelper */ $image_helper->LoadItemImages($object); // linking existing files for item with virtual fields + /** @var FileHelper $file_helper */ $file_helper = $this->Application->recallObject('FileHelper'); - /* @var $file_helper FileHelper */ $file_helper->LoadItemFiles($object); } if ( $object->isVirtualField('MoreCategories') ) { // set item's additional categories to virtual field (used in editing) $item_categories = $this->getItemCategories($object->GetDBField('ResourceId')); $object->SetDBField('MoreCategories', $item_categories ? '|' . implode('|', $item_categories) . '|' : ''); } } /** * Occurs after updating item * * @param kEvent $event * @return void * @access protected */ protected function OnAfterItemUpdate(kEvent $event) { parent::OnAfterItemUpdate($event); $this->CalculateHotLimit($event); if ( substr($event->Special, -6) == 'import' ) { $this->setCustomExportColumns($event); } + /** @var kCatDBItem $object */ $object = $event->getObject(); - /* @var $object kCatDBItem */ if ( !$this->Application->isAdmin ) { + /** @var ImageHelper $image_helper */ $image_helper = $this->Application->recallObject('ImageHelper'); - /* @var $image_helper ImageHelper */ // process image upload in virtual fields $image_helper->SaveItemImages($object); + /** @var FileHelper $file_helper */ $file_helper = $this->Application->recallObject('FileHelper'); - /* @var $file_helper FileHelper */ // process file upload in virtual fields $file_helper->SaveItemFiles($object); if ( $event->Special != '-item' ) { // don't touch categories during cloning $this->processAdditionalCategories($object, 'update'); } } $recycle_bin = $this->Application->ConfigValue('RecycleBinFolder'); if ( $this->Application->isAdminUser && $recycle_bin ) { $sql = 'SELECT CategoryId FROM ' . $this->Application->getUnitConfig('ci')->getTableName() . ' WHERE ItemResourceId = ' . $object->GetDBField('ResourceId') . ' AND PrimaryCat = 1'; $primary_category = $this->Conn->GetOne($sql); if ( $primary_category == $recycle_bin ) { $event->CallSubEvent('OnAfterItemDelete'); } } if ( $object->GetChangedFields() ) { $now = time(); $object->SetDBField('Modified_date', $now); $object->SetDBField('Modified_time', $now); $object->SetDBField('ModifiedById', $this->Application->RecallVar('user_id')); } } /** * Sets values for import process * * @param kEvent $event * @return void * @access protected */ protected function OnAfterItemCreate(kEvent $event) { parent::OnAfterItemCreate($event); + /** @var kCatDBItem $object */ $object = $event->getObject(); - /* @var $object kCatDBItem */ if ( substr($event->Special, -6) == 'import' ) { $this->setCustomExportColumns($event); } $object->assignPrimaryCategory(); if ( !$this->Application->isAdmin ) { + /** @var ImageHelper $image_helper */ $image_helper = $this->Application->recallObject('ImageHelper'); - /* @var $image_helper ImageHelper */ // process image upload in virtual fields $image_helper->SaveItemImages($object); + /** @var FileHelper $file_helper */ $file_helper = $this->Application->recallObject('FileHelper'); - /* @var $file_helper FileHelper */ // process file upload in virtual fields $file_helper->SaveItemFiles($object); if ( $event->Special != '-item' ) { // don't touch categories during cloning $this->processAdditionalCategories($object, 'create'); } } } /** * Make record to search log * * @param string $keywords * @param int $search_type 0 - simple search, 1 - advanced search */ function saveToSearchLog($keywords, $search_type = 0) { // don't save keywords for each module separately, just one time // static variable can't help here, because each module uses it's own class instance ! if (!$this->Application->GetVar('search_logged')) { $sql = 'UPDATE '.TABLE_PREFIX.'SearchLogs SET Indices = Indices + 1 WHERE Keyword = '.$this->Conn->qstr($keywords).' AND SearchType = '.$search_type; // 0 - simple search, 1 - advanced search $this->Conn->Query($sql); if ($this->Conn->getAffectedRows() == 0) { $fields_hash = Array('Keyword' => $keywords, 'Indices' => 1, 'SearchType' => $search_type); $this->Conn->doInsert($fields_hash, TABLE_PREFIX.'SearchLogs'); } $this->Application->SetVar('search_logged', 1); } } /** * Makes simple search for category items * based on keywords string * * @param kEvent $event */ function OnSimpleSearch($event) { $event->redirect = false; $keywords = $this->Application->unescapeRequestVariable(trim($this->Application->GetVar('keywords'))); + /** @var kHTTPQuery $query_object */ $query_object = $this->Application->recallObject('kHTTPQuery'); - /* @var $query_object kHTTPQuery */ /** @var kSearchHelper $search_helper */ $search_helper = $this->Application->recallObject('SearchHelper'); $search_table = $search_helper->getSearchTable(); $sql = 'SHOW TABLES LIKE "'.$search_table.'"'; if(!isset($query_object->Get['keywords']) && !isset($query_object->Post['keywords']) && $this->Conn->Query($sql)) { return; // used when navigating by pages or changing sorting in search results } if(!$keywords || strlen($keywords) < $this->Application->ConfigValue('Search_MinKeyword_Length')) { $search_helper->ensureEmptySearchTable(); $this->Application->SetVar('keywords_too_short', 1); return; // if no or too short keyword entered, doing nothing } $this->Application->StoreVar('keywords', $keywords); $this->saveToSearchLog($keywords, 0); // 0 - simple search, 1 - advanced search $event->setPseudoClass('_List'); + /** @var kDBList $object */ $object = $event->getObject(); - /* @var $object kDBList */ $config = $event->getUnitConfig(); $this->Application->SetVar($event->getPrefixSpecial().'_Page', 1); $lang = $this->Application->GetVar('m_lang'); $items_table = $config->getTableName(); $module_name = $this->Application->findModule('Var', $event->Prefix, 'Name'); $sql = 'SELECT * FROM ' . $this->Application->getUnitConfig('confs')->getTableName() . ' WHERE ModuleName = ' . $this->Conn->qstr($module_name) . ' AND SimpleSearch = 1'; $search_config = $this->Conn->Query($sql, 'FieldName'); $field_list = array_keys($search_config); $join_clauses = Array(); // field processing $weight_sum = 0; $alias_counter = 0; $custom_fields = $config->getCustomFields(); if ($custom_fields) { $custom_table = $this->Application->getUnitConfig($event->Prefix . '-cdata')->getTableName(); $join_clauses[] = ' LEFT JOIN '.$custom_table.' custom_data ON '.$items_table.'.ResourceId = custom_data.ResourceId'; } // what field in search config becomes what field in sql (key - new field, value - old field (from searchconfig table)) $search_config_map = Array(); foreach ($field_list as $key => $field) { $local_table = TABLE_PREFIX.$search_config[$field]['TableName']; $weight_sum += $search_config[$field]['Priority']; // counting weight sum; used when making relevance clause // processing multilingual fields if ( !$search_config[$field]['CustomFieldId'] && $object->GetFieldOption($field, 'formatter') == 'kMultiLanguage' ) { $field_list[$key.'_primary'] = 'l'.$this->Application->GetDefaultLanguageId().'_'.$field; $field_list[$key] = 'l'.$lang.'_'.$field; if (!isset($search_config[$field]['ForeignField'])) { $field_list[$key.'_primary'] = $local_table.'.'.$field_list[$key.'_primary']; $search_config_map[ $field_list[$key.'_primary'] ] = $field; } } // processing fields from other tables $foreign_field = $search_config[$field]['ForeignField']; if ( $foreign_field ) { $exploded = explode(':', $foreign_field, 2); if ($exploded[0] == 'CALC') { // ignoring having type clauses in simple search unset($field_list[$key]); continue; } else { $multi_lingual = false; if ($exploded[0] == 'MULTI') { $multi_lingual = true; $foreign_field = $exploded[1]; } $exploded = explode('.', $foreign_field); // format: table.field_name $foreign_table = TABLE_PREFIX.$exploded[0]; $alias_counter++; $alias = 't'.$alias_counter; if ($multi_lingual) { $field_list[$key] = $alias.'.'.'l'.$lang.'_'.$exploded[1]; $field_list[$key.'_primary'] = 'l'.$this->Application->GetDefaultLanguageId().'_'.$field; $search_config_map[ $field_list[$key] ] = $field; $search_config_map[ $field_list[$key.'_primary'] ] = $field; } else { $field_list[$key] = $alias.'.'.$exploded[1]; $search_config_map[ $field_list[$key] ] = $field; } $join_clause = str_replace('{ForeignTable}', $alias, $search_config[$field]['JoinClause']); $join_clause = str_replace('{LocalTable}', $items_table, $join_clause); $join_clauses[] = ' LEFT JOIN '.$foreign_table.' '.$alias.' ON '.$join_clause; } } else { // processing fields from local table if ($search_config[$field]['CustomFieldId']) { $local_table = 'custom_data'; // search by custom field value on current language $custom_field_id = array_search($field_list[$key], $custom_fields); $field_list[$key] = 'l'.$lang.'_cust_'.$custom_field_id; // search by custom field value on primary language $field_list[$key.'_primary'] = $local_table.'.l'.$this->Application->GetDefaultLanguageId().'_cust_'.$custom_field_id; $search_config_map[ $field_list[$key.'_primary'] ] = $field; } $field_list[$key] = $local_table.'.'.$field_list[$key]; $search_config_map[ $field_list[$key] ] = $field; } } // Keyword string processing. $where_clause = Array (); foreach ($field_list as $field) { if (preg_match('/^' . preg_quote($items_table, '/') . '\.(.*)/', $field, $regs)) { // local real field $filter_data = $search_helper->getSearchClause($object, $regs[1], $keywords, false); if ($filter_data) { $where_clause[] = $filter_data['value']; } } elseif (preg_match('/^custom_data\.(.*)/', $field, $regs)) { $custom_field_name = 'cust_' . $search_config_map[$field]; $filter_data = $search_helper->getSearchClause($object, $custom_field_name, $keywords, false); if ($filter_data) { $where_clause[] = str_replace('`' . $custom_field_name . '`', $field, $filter_data['value']); } } else { $where_clause[] = $search_helper->buildWhereClause($keywords, Array ($field)); } } $where_clause = '((' . implode(') OR (', $where_clause) . '))'; // 2 braces for next clauses, see below! $search_scope = $this->Application->GetVar('search_scope'); if ($search_scope == 'category') { $category_id = $this->Application->GetVar('m_cat_id'); $category_filter = $this->getCategoryLimitClause($category_id); if ($category_filter !== false) { $join_clauses[] = ' LEFT JOIN '.TABLE_PREFIX.'CategoryItems ON '.TABLE_PREFIX.'CategoryItems.ItemResourceId = '.$items_table.'.ResourceId'; $join_clauses[] = ' LEFT JOIN '.TABLE_PREFIX.'Categories ON '.TABLE_PREFIX.'Categories.CategoryId = '.TABLE_PREFIX.'CategoryItems.CategoryId'; $where_clause = '('.$this->getCategoryLimitClause($category_id).') AND '.$where_clause; } } $where_clause = $where_clause . ' AND (' . $items_table . '.Status = ' . STATUS_ACTIVE . ')'; if ($event->MasterEvent && $event->MasterEvent->Name == 'OnListBuild') { $sub_search_ids = $event->MasterEvent->getEventParam('ResultIds'); if ( $sub_search_ids !== false ) { if ( $sub_search_ids ) { $where_clause .= 'AND (' . $items_table . '.ResourceId IN (' . implode(',', $sub_search_ids) . '))'; } else { $where_clause .= 'AND FALSE'; } } } // making relevance clause $positive_words = $search_helper->getPositiveKeywords($keywords); $this->Application->StoreVar('highlight_keywords', serialize($positive_words)); $relevance_parts = Array(); reset($search_config); foreach ($positive_words as $keyword_index => $positive_word) { $positive_word = $search_helper->transformWildcards($positive_word); $positive_words[$keyword_index] = $this->Conn->escape($positive_word); } foreach ($field_list as $field) { if (!array_key_exists($field, $search_config_map)) { $map_key = $search_config_map[$items_table . '.' . $field]; } else { $map_key = $search_config_map[$field]; } $config_elem = $search_config[ $map_key ]; $weight = $config_elem['Priority']; // search by whole words only ([[:<:]] - word boundary) /*$relevance_parts[] = 'IF('.$field.' REGEXP "[[:<:]]('.implode(' ', $positive_words).')[[:>:]]", '.$weight.', 0)'; foreach ($positive_words as $keyword) { $relevance_parts[] = 'IF('.$field.' REGEXP "[[:<:]]('.$keyword.')[[:>:]]", '.$weight.', 0)'; }*/ if ( count($positive_words) > 1 ) { $condition = $field . ' LIKE "%' . implode(' ', $positive_words) . '%"'; $relevance_parts[] = 'IF(' . $condition . ', ' . $weight_sum . ', 0)'; } // search by partial word matches too foreach ( $positive_words as $keyword ) { $relevance_parts[] = 'IF(' . $field . ' LIKE "%' . $keyword . '%", ' . $weight . ', 0)'; } } $relevance_parts = array_unique($relevance_parts); $conf_postfix = $config->getSearchConfigPostfix(); $rel_keywords = $this->Application->ConfigValue('SearchRel_Keyword_'.$conf_postfix) / 100; $rel_pop = $this->Application->ConfigValue('SearchRel_Pop_'.$conf_postfix) / 100; $rel_rating = $this->Application->ConfigValue('SearchRel_Rating_'.$conf_postfix) / 100; $relevance_clause = '('.implode(' + ', $relevance_parts).') / '.$weight_sum.' * '.$rel_keywords; if ($rel_pop && $object->isField('Hits')) { $relevance_clause .= ' + (Hits + 1) / (MAX(Hits) + 1) * '.$rel_pop; } if ($rel_rating && $object->isField('CachedRating')) { $relevance_clause .= ' + (CachedRating + 1) / (MAX(CachedRating) + 1) * '.$rel_rating; } // building final search query if (!$this->Application->GetVar('do_not_drop_search_table')) { $this->Conn->Query('DROP TABLE IF EXISTS '.$search_table); // erase old search table if clean k4 event $this->Application->SetVar('do_not_drop_search_table', true); } $search_table_exists = $this->Conn->Query('SHOW TABLES LIKE "'.$search_table.'"'); if ($search_table_exists) { $select_intro = 'INSERT INTO '.$search_table.' (Relevance, ItemId, ResourceId, ItemType, EdPick) '; } else { $select_intro = 'CREATE TABLE '.$search_table.' AS '; } $edpick_clause = $config->getFieldByName('EditorsPick') ? $items_table.'.EditorsPick' : '0'; $sql = $select_intro.' SELECT '.$relevance_clause.' AS Relevance, '.$items_table.'.'.$config->getIDField().' AS ItemId, '.$items_table.'.ResourceId, '.$config->getItemType().' AS ItemType, '.$edpick_clause.' AS EdPick FROM '.$object->TableName.' '.implode(' ', $join_clauses).' WHERE '.$where_clause.' GROUP BY '.$items_table.'.'.$config->getIDField().' ORDER BY Relevance DESC'; $this->Conn->Query($sql); if ( !$search_table_exists ) { $sql = 'ALTER TABLE ' . $search_table . ' ADD INDEX (ResourceId), ADD INDEX (Relevance)'; $this->Conn->Query($sql); } } /** * Enter description here... * * @param kEvent $event */ function OnSubSearch($event) { // keep search results from other items after doing a sub-search on current item type $this->Application->SetVar('do_not_drop_search_table', true); /** @var kSearchHelper $search_helper */ $search_helper = $this->Application->recallObject('SearchHelper'); $search_table = $search_helper->getSearchTable(); $sql = 'SHOW TABLES LIKE "' . $search_table . '"'; $ids = array(); if ( $this->Conn->Query($sql) ) { $item_type = $event->getUnitConfig()->getItemType(); // 1. get ids to be used as search bounds $sql = 'SELECT DISTINCT ResourceId FROM ' . $search_table . ' WHERE ItemType = ' . $item_type; $ids = $this->Conn->GetCol($sql); // 2. delete previously found ids $sql = 'DELETE FROM ' . $search_table . ' WHERE ItemType = ' . $item_type; $this->Conn->Query($sql); } $event->setEventParam('ResultIds', $ids); $event->CallSubEvent('OnSimpleSearch'); } /** * Enter description here... * * @param kEvent $event * @todo Change all hardcoded Products table & In-Commerce module usage to dynamic usage from item config !!! */ function OnAdvancedSearch($event) { + /** @var kHTTPQuery $query_object */ $query_object = $this->Application->recallObject('kHTTPQuery'); - /* @var $query_object kHTTPQuery */ if ( !isset($query_object->Post['andor']) ) { // used when navigating by pages or changing sorting in search results return; } $this->Application->RemoveVar('keywords'); $this->Application->RemoveVar('Search_Keywords'); $module_name = $this->Application->findModule('Var', $event->Prefix, 'Name'); $sql = 'SELECT * FROM '.$this->Application->getUnitConfig('confs')->getTableName().' WHERE (ModuleName = '.$this->Conn->qstr($module_name).') AND (AdvancedSearch = 1)'; $search_config = $this->Conn->Query($sql); $lang = $this->Application->GetVar('m_lang'); + /** @var kDBList $object */ $object = $event->getObject(); - /* @var $object kDBList */ $object->SetPage(1); $config = $event->getUnitConfig(); $items_table = $config->getTableName(); $search_keywords = $this->Application->GetVar('value'); // will not be changed $keywords = $this->Application->GetVar('value'); // will be changed down there $verbs = $this->Application->GetVar('verb'); $glues = $this->Application->GetVar('andor'); $and_conditions = Array(); $or_conditions = Array(); $and_having_conditions = Array(); $or_having_conditions = Array(); $join_clauses = Array(); $highlight_keywords = Array(); $relevance_parts = Array(); $alias_counter = 0; $custom_fields = $config->getCustomFields(); if ($custom_fields) { $custom_table = $this->Application->getUnitConfig($event->Prefix . '-cdata')->getTableName(); $join_clauses[] = ' LEFT JOIN '.$custom_table.' custom_data ON '.$items_table.'.ResourceId = custom_data.ResourceId'; } $search_log = ''; $weight_sum = 0; // processing fields and preparing conditions foreach ($search_config as $record) { $field = $record['FieldName']; $join_clause = ''; $condition_mode = 'WHERE'; // field processing $local_table = TABLE_PREFIX.$record['TableName']; $weight_sum += $record['Priority']; // counting weight sum; used when making relevance clause // processing multilingual fields if ( $object->GetFieldOption($field, 'formatter') == 'kMultiLanguage' ) { $field_name = 'l'.$lang.'_'.$field; } else { $field_name = $field; } // processing fields from other tables $foreign_field = $record['ForeignField']; if ( $foreign_field ) { $exploded = explode(':', $foreign_field, 2); if($exploded[0] == 'CALC') { $user_groups = $this->Application->RecallVar('UserGroups'); $field_name = str_replace('{PREFIX}', TABLE_PREFIX, $exploded[1]); $join_clause = str_replace('{PREFIX}', TABLE_PREFIX, $record['JoinClause']); $join_clause = str_replace('{USER_GROUPS}', $user_groups, $join_clause); $join_clause = ' LEFT JOIN '.$join_clause; $condition_mode = 'HAVING'; } else { $exploded = explode('.', $foreign_field); $foreign_table = TABLE_PREFIX.$exploded[0]; if($record['CustomFieldId']) { $exploded[1] = 'l'.$lang.'_'.$exploded[1]; } $alias_counter++; $alias = 't'.$alias_counter; $field_name = $alias.'.'.$exploded[1]; $join_clause = str_replace('{ForeignTable}', $alias, $record['JoinClause']); $join_clause = str_replace('{LocalTable}', $items_table, $join_clause); if($record['CustomFieldId']) { $join_clause .= ' AND '.$alias.'.CustomFieldId='.$record['CustomFieldId']; } $join_clause = ' LEFT JOIN '.$foreign_table.' '.$alias.' ON '.$join_clause; } } else { // processing fields from local table if ($record['CustomFieldId']) { $local_table = 'custom_data'; $field_name = 'l'.$lang.'_cust_'.array_search($field_name, $custom_fields); } $field_name = $local_table.'.'.$field_name; } $condition = $this->getAdvancedSearchCondition($field_name, $record, $keywords, $verbs, $highlight_keywords); if ($record['CustomFieldId'] && strlen($condition)) { // search in primary value of custom field + value in current language $field_name = $local_table.'.'.'l'.$this->Application->GetDefaultLanguageId().'_cust_'.array_search($field, $custom_fields); $primary_condition = $this->getAdvancedSearchCondition($field_name, $record, $keywords, $verbs, $highlight_keywords); $condition = '('.$condition.' OR '.$primary_condition.')'; } if ($condition) { if ($join_clause) { $join_clauses[] = $join_clause; } $relevance_parts[] = 'IF('.$condition.', '.$record['Priority'].', 0)'; if ($glues[$field] == 1) { // and if ($condition_mode == 'WHERE') { $and_conditions[] = $condition; } else { $and_having_conditions[] = $condition; } } else { // or if ($condition_mode == 'WHERE') { $or_conditions[] = $condition; } else { $or_having_conditions[] = $condition; } } // create search log record $search_log_data = Array('search_config' => $record, 'verb' => getArrayValue($verbs, $field), 'value' => ($record['FieldType'] == 'range') ? $search_keywords[$field.'_from'].'|'.$search_keywords[$field.'_to'] : $search_keywords[$field]); $search_log[] = $this->Application->Phrase('la_Field').' "'.$this->getHuman('Field', $search_log_data).'" '.$this->getHuman('Verb', $search_log_data).' '.$this->Application->Phrase('la_Value').' '.$this->getHuman('Value', $search_log_data).' '.$this->Application->Phrase($glues[$field] == 1 ? 'lu_And' : 'lu_Or'); } } if ($search_log) { $search_log = implode('
', $search_log); $search_log = preg_replace('/(.*) '.preg_quote($this->Application->Phrase('lu_and'), '/').'|'.preg_quote($this->Application->Phrase('lu_or'), '/').'$/is', '\\1', $search_log); $this->saveToSearchLog($search_log, 1); // advanced search } $this->Application->StoreVar('highlight_keywords', serialize($highlight_keywords)); // making relevance clause if($relevance_parts) { $conf_postfix = $config->getSearchConfigPostfix(); $rel_keywords = $this->Application->ConfigValue('SearchRel_Keyword_'.$conf_postfix) / 100; $rel_pop = $this->Application->ConfigValue('SearchRel_Pop_'.$conf_postfix) / 100; $rel_rating = $this->Application->ConfigValue('SearchRel_Rating_'.$conf_postfix) / 100; $relevance_clause = '('.implode(' + ', $relevance_parts).') / '.$weight_sum.' * '.$rel_keywords; $relevance_clause .= ' + (Hits + 1) / (MAX(Hits) + 1) * '.$rel_pop; $relevance_clause .= ' + (CachedRating + 1) / (MAX(CachedRating) + 1) * '.$rel_rating; } else { $relevance_clause = '0'; } // building having clause if($or_having_conditions) { $and_having_conditions[] = '('.implode(' OR ', $or_having_conditions).')'; } $having_clause = implode(' AND ', $and_having_conditions); $having_clause = $having_clause ? ' HAVING '.$having_clause : ''; // building where clause if($or_conditions) { $and_conditions[] = '('.implode(' OR ', $or_conditions).')'; } // $and_conditions[] = $items_table.'.Status = 1'; $where_clause = implode(' AND ', $and_conditions); if(!$where_clause) { if($having_clause) { $where_clause = '1'; } else { $where_clause = '0'; $this->Application->SetVar('adv_search_error', 1); } } $where_clause .= ' AND '.$items_table.'.Status = 1'; /** @var kSearchHelper $search_helper */ $search_helper = $this->Application->recallObject('SearchHelper'); // Building final search query. $search_table = $search_helper->getSearchTable(); $this->Conn->Query('DROP TABLE IF EXISTS '.$search_table); $id_field = $config->getIDField(); $pick_field = $config->getFieldByName('EditorsPick') ? $items_table.'.EditorsPick' : '0'; $sql = ' CREATE TABLE '.$search_table.' SELECT '.$relevance_clause.' AS Relevance, '.$items_table.'.'.$id_field.' AS ItemId, '.$items_table.'.ResourceId AS ResourceId, 11 AS ItemType, '.$pick_field.' AS EdPick FROM '.$items_table.' '.implode(' ', $join_clauses).' WHERE '.$where_clause.' GROUP BY '.$items_table.'.'.$id_field. $having_clause; $this->Conn->Query($sql); } function getAdvancedSearchCondition($field_name, $record, $keywords, $verbs, &$highlight_keywords) { $field = $record['FieldName']; $condition_patterns = Array ( 'any' => '%s LIKE %s', 'contains' => '%s LIKE %s', 'notcontains' => '(NOT (%1$s LIKE %2$s) OR %1$s IS NULL)', 'is' => '%s = %s', 'isnot' => '(%1$s != %2$s OR %1$s IS NULL)' ); $condition = ''; switch ($record['FieldType']) { case 'select': $keywords[$field] = $this->Application->unescapeRequestVariable($keywords[$field]); if ($keywords[$field]) { $condition = sprintf($condition_patterns['is'], $field_name, $this->Conn->qstr( $keywords[$field] )); } break; case 'multiselect': $keywords[$field] = $this->Application->unescapeRequestVariable($keywords[$field]); if ($keywords[$field]) { $condition = Array (); $values = explode('|', substr($keywords[$field], 1, -1)); foreach ($values as $selected_value) { $condition[] = sprintf($condition_patterns['contains'], $field_name, $this->Conn->qstr('%|'.$selected_value.'|%')); } $condition = '('.implode(' OR ', $condition).')'; } break; case 'text': $keywords[$field] = $this->Application->unescapeRequestVariable($keywords[$field]); if (mb_strlen($keywords[$field]) >= $this->Application->ConfigValue('Search_MinKeyword_Length')) { $highlight_keywords[] = $keywords[$field]; if (in_array($verbs[$field], Array('any', 'contains', 'notcontains'))) { $keywords[$field] = '%'.strtr($keywords[$field], Array('%' => '\\%', '_' => '\\_')).'%'; } $condition = sprintf($condition_patterns[$verbs[$field]], $field_name, $this->Conn->qstr( $keywords[$field] )); } break; case 'boolean': if ($keywords[$field] != -1) { $config = $this->getUnitConfig(); $items_table = $config->getTableName(); $property_mappings = $config->getItemPropertyMappings(); switch ($field) { case 'HotItem': $hot_limit_var = getArrayValue($property_mappings, 'HotLimit'); if ($hot_limit_var) { $hot_limit = (int)$this->Application->getDBCache($hot_limit_var); $condition = 'IF('.$items_table.'.HotItem = 2, IF('.$items_table.'.Hits >= '. $hot_limit. ', 1, 0), '.$items_table.'.HotItem) = '.$keywords[$field]; } break; case 'PopItem': $votes2pop_var = getArrayValue($property_mappings, 'VotesToPop'); $rating2pop_var = getArrayValue($property_mappings, 'RatingToPop'); if ($votes2pop_var && $rating2pop_var) { $condition = 'IF('.$items_table.'.PopItem = 2, IF('.$items_table.'.CachedVotesQty >= '. $this->Application->ConfigValue($votes2pop_var). ' AND '.$items_table.'.CachedRating >= '. $this->Application->ConfigValue($rating2pop_var). ', 1, 0), '.$items_table.'.PopItem) = '.$keywords[$field]; } break; case 'NewItem': $new_days_var = getArrayValue($property_mappings, 'NewDays'); if ($new_days_var) { $condition = 'IF('.$items_table.'.NewItem = 2, IF('.$items_table.'.CreatedOn >= (UNIX_TIMESTAMP() - '. $this->Application->ConfigValue($new_days_var). '*3600*24), 1, 0), '.$items_table.'.NewItem) = '.$keywords[$field]; } break; case 'EditorsPick': $condition = $items_table.'.EditorsPick = '.$keywords[$field]; break; } } break; case 'range': $range_conditions = Array(); if ($keywords[$field.'_from'] && !preg_match("/[^0-9]/i", $keywords[$field.'_from'])) { $range_conditions[] = $field_name.' >= '.$keywords[$field.'_from']; } if ($keywords[$field.'_to'] && !preg_match("/[^0-9]/i", $keywords[$field.'_to'])) { $range_conditions[] = $field_name.' <= '.$keywords[$field.'_to']; } if ($range_conditions) { $condition = implode(' AND ', $range_conditions); } break; case 'date': if ($keywords[$field]) { if (in_array($keywords[$field], Array('today', 'yesterday'))) { $current_time = getdate(); $day_begin = mktime(0, 0, 0, $current_time['mon'], $current_time['mday'], $current_time['year']); $time_mapping = Array('today' => $day_begin, 'yesterday' => ($day_begin - 86400)); $min_time = $time_mapping[$keywords[$field]]; } else { $time_mapping = Array ( 'last_week' => 604800, 'last_month' => 2628000, 'last_3_months' => 7884000, 'last_6_months' => 15768000, 'last_year' => 31536000, ); $min_time = time() - $time_mapping[$keywords[$field]]; } $condition = $field_name.' > '.$min_time; } break; } return $condition; } /** * Returns human readable representation of searched data to be placed in search log * @param string $type * @param Array $search_data * @return string * @access protected */ protected function getHuman($type, $search_data) { // all 3 variables are retrieved from $search_data array - /* @var $search_config Array */ - /* @var $verb string */ - /* @var $value string */ + /** @var Array $search_config */ + /** @var string $verb */ + /** @var string $value */ $type = ucfirst(strtolower($type)); extract($search_data, EXTR_SKIP); switch ($type) { case 'Field': return $this->Application->Phrase($search_config['DisplayName']); break; case 'Verb': return $verb ? $this->Application->Phrase('lu_advsearch_'.$verb) : ''; break; case 'Value': switch ($search_config['FieldType']) { case 'date': $values = Array(0 => 'lu_comm_Any', 'today' => 'lu_comm_Today', 'yesterday' => 'lu_comm_Yesterday', 'last_week' => 'lu_comm_LastWeek', 'last_month' => 'lu_comm_LastMonth', 'last_3_months' => 'lu_comm_Last3Months', 'last_6_months' => 'lu_comm_Last6Months', 'last_year' => 'lu_comm_LastYear'); $ret = $this->Application->Phrase($values[$value]); break; case 'range': $value = explode('|', $value); return $this->Application->Phrase('lu_comm_From').' "'.$value[0].'" '.$this->Application->Phrase('lu_comm_To').' "'.$value[1].'"'; break; case 'boolean': $values = Array(1 => 'lu_comm_Yes', 0 => 'lu_comm_No', -1 => 'lu_comm_Both'); $ret = $this->Application->Phrase($values[$value]); break; default: $ret = $value; break; } return '"'.$ret.'"'; break; } return ''; } /** * Set's correct page for list based on data provided with event * * @param kEvent $event * @return void * @access protected * @see kDBEventHandler::OnListBuild() */ protected function SetPagination(kEvent $event) { + /** @var kDBList $object */ $object = $event->getObject(); - /* @var $object kDBList */ // get PerPage (forced -> session -> config -> 10) $object->SetPerPage($this->getPerPage($event)); // main lists on Front-End have special get parameter for page $page = $object->isMainList() ? $this->Application->GetVar('page') : false; if ( !$page ) { // page is given in "env" variable for given prefix $page = $this->Application->GetVar($event->getPrefixSpecial() . '_Page'); } if ( !$page && $event->Special ) { // when not part of env, then variables like "prefix.special_Page" are // replaced (by PHP) with "prefix_special_Page", so check for that too $page = $this->Application->GetVar($event->getPrefixSpecial(true) . '_Page'); } if ( !$object->isMainList() ) { // main lists doesn't use session for page storing $this->Application->StoreVarDefault($event->getPrefixSpecial() . '_Page', 1, true); // true for optional if ( !$page ) { if ( $this->Application->RewriteURLs() ) { // when page not found by prefix+special, then try to search it without special at all $page = $this->Application->GetVar($event->Prefix . '_Page'); if ( !$page ) { // page not found in request -> get from session $page = $this->Application->RecallVar($event->Prefix . '_Page'); } if ( $page ) { // page found in request -> store in session $this->Application->StoreVar($event->getPrefixSpecial() . '_Page', $page, true); //true for optional } } else { // page not found in request -> get from session $page = $this->Application->RecallVar($event->getPrefixSpecial() . '_Page'); } } else { // page found in request -> store in session $this->Application->StoreVar($event->getPrefixSpecial() . '_Page', $page, true); //true for optional } if ( !$event->getEventParam('skip_counting') ) { // when stored page is larger, then maximal list page number // (such case is also processed in kDBList::Query method) $pages = $object->GetTotalPages(); if ( $page > $pages ) { $page = 1; $this->Application->StoreVar($event->getPrefixSpecial() . '_Page', 1, true); } } } $object->SetPage($page); } /* === RELATED TO IMPORT/EXPORT: BEGIN === */ /** * Shows export dialog * * @param kEvent $event * @return void * @access protected */ protected function OnExport(kEvent $event) { $selected_ids = $this->StoreSelectedIDs($event); if ( implode(',', $selected_ids) == '' ) { // K4 fix when no ids found bad selected ids array is formed $selected_ids = false; } $selected_cats_ids = $this->Application->GetVar('export_categories'); $this->Application->StoreVar($event->Prefix . '_export_ids', $selected_ids ? implode(',', $selected_ids) : ''); $this->Application->StoreVar($event->Prefix . '_export_cats_ids', $selected_cats_ids); + /** @var kCatDBItemExportHelper $export_helper */ $export_helper = $this->Application->recallObject('CatItemExportHelper'); - /* @var $export_helper kCatDBItemExportHelper */ $redirect_params = Array ( $this->Prefix . '.export_event' => 'OnNew', 'pass' => 'all,' . $this->Prefix . '.export' ); $event->setRedirectParams($redirect_params); } /** * Performs each export step & displays progress percent * * @param kEvent $event */ function OnExportProgress($event) { + /** @var kCatDBItemExportHelper $export_object */ $export_object = $this->Application->recallObject('CatItemExportHelper'); - /* @var $export_object kCatDBItemExportHelper */ $action_method = 'perform'.ucfirst($event->Special); $field_values = $export_object->$action_method($event); // finish code is done from JS now if ($field_values['start_from'] == $field_values['total_records']) { if ($event->Special == 'import') { $this->Application->StoreVar('PermCache_UpdateRequired', 1); $event->SetRedirectParam('m_cat_id', $this->Application->RecallVar('ImportCategory')); $event->SetRedirectParam('anchor', 'tab-' . $event->Prefix); $event->redirect = 'catalog/catalog'; } elseif ($event->Special == 'export') { $event->redirect = $export_object->getModuleName($event) . '/' . $event->Special . '_finish'; $event->SetRedirectParam('pass', 'all'); } return ; } $export_options = $export_object->loadOptions($event); echo $export_options['start_from'] * 100 / $export_options['total_records']; $event->status = kEvent::erSTOP; } /** * Returns specific to each item type columns only * * @param kEvent $event * @return Array * @access protected */ public function getCustomExportColumns(kEvent $event) { return Array ( '__VIRTUAL__ThumbnailImage' => 'ThumbnailImage', '__VIRTUAL__FullImage' => 'FullImage', '__VIRTUAL__ImageAlt' => 'ImageAlt' ); } /** * Sets non standart virtual fields (e.g. to other tables) * * @param kEvent $event */ function setCustomExportColumns($event) { $this->restorePrimaryImage($event); } /** * Create/Update primary image record in info found in imported data * * @param kEvent $event * @return void * @access protected */ protected function restorePrimaryImage($event) { + /** @var kCatDBItem $object */ $object = $event->getObject(); - /* @var $object kCatDBItem */ if ( !$object->GetDBField('ThumbnailImage') && !$object->GetDBField('FullImage') ) { return ; } $image_data = $object->getPrimaryImageData(); + /** @var kDBItem $image */ $image = $this->Application->recallObject('img', NULL, Array ('skip_autoload' => true)); - /* @var $image kDBItem */ if ( $image_data ) { $image->Load($image_data['ImageId']); } else { $image->Clear(); $image->SetDBField('Name', 'main'); $image->SetDBField('DefaultImg', 1); $image->SetDBField('ResourceId', $object->GetDBField('ResourceId')); } if ( $object->GetDBField('ImageAlt') ) { $image->SetDBField('AltName', $object->GetDBField('ImageAlt')); } if ( $object->GetDBField('ThumbnailImage') ) { $thumbnail_field = $this->isURL($object->GetDBField('ThumbnailImage')) ? 'ThumbUrl' : 'ThumbPath'; $image->SetDBField($thumbnail_field, $object->GetDBField('ThumbnailImage')); $image->SetDBField('LocalThumb', $thumbnail_field == 'ThumbPath' ? 1 : 0); } if ( !$object->GetDBField('FullImage') ) { $image->SetDBField('SameImages', 1); } else { $image->SetDBField('SameImages', 0); $full_field = $this->isURL($object->GetDBField('FullImage')) ? 'Url' : 'LocalPath'; $image->SetDBField($full_field, $object->GetDBField('FullImage')); $image->SetDBField('LocalImage', $full_field == 'LocalPath' ? 1 : 0); } if ( $image->isLoaded() ) { $image->Update(); } else { $image->Create(); } } /** * Detects if image url is specified in a given path (instead of path on disk) * * @param string $path * @return bool * @access protected */ protected function isURL($path) { return preg_match('#(http|https)://(.*)#', $path); } /** * Prepares item for import/export operations * * @param kEvent $event * @return void * @access protected */ protected function OnNew(kEvent $event) { parent::OnNew($event); if ( $event->Special == 'import' || $event->Special == 'export' ) { + /** @var kCatDBItemExportHelper $export_helper */ $export_helper = $this->Application->recallObject('CatItemExportHelper'); - /* @var $export_helper kCatDBItemExportHelper */ $export_helper->setRequiredFields($event); } } /** * Process items selected in item_selector * * @param kEvent $event */ function OnProcessSelected($event) { $dst_field = $this->Application->RecallVar('dst_field'); $selected_ids = $this->Application->GetVar('selected_ids'); if ( $dst_field == 'ItemCategory' ) { // Item Edit -> Categories Tab -> New Categories + /** @var kCatDBItem $object */ $object = $event->getObject(); - /* @var $object kCatDBItem */ $category_ids = explode(',', $selected_ids['c']); foreach ($category_ids as $category_id) { $object->assignToCategory($category_id); } } if ($dst_field == 'ImportCategory') { // Tools -> Import -> Item Import -> Select Import Category $this->Application->StoreVar('ImportCategory', $selected_ids['c']); $event->SetRedirectParam($event->getPrefixSpecial() . '_id', 0); $event->SetRedirectParam($event->getPrefixSpecial() . '_event', 'OnExportBegin'); } $event->SetRedirectParam('opener', 'u'); } /** * Saves Import/Export settings to session * * @param kEvent $event */ function OnSaveSettings($event) { $event->redirect = false; $items_info = $this->Application->GetVar($event->getPrefixSpecial(true)); if ( $items_info ) { list($id, $field_values) = each($items_info); + /** @var kDBItem $object */ $object = $event->getObject(Array ('skip_autoload' => true)); - /* @var $object kDBItem */ $object->setID($id); $object->SetFieldsFromHash($field_values); $event->setEventParam('form_data', $field_values); $field_values['ImportFilename'] = $object->GetDBField('ImportFilename'); //if upload formatter has renamed the file during moving !!! $field_values['ImportSource'] = 2; $field_values['ImportLocalFilename'] = $object->GetDBField('ImportFilename'); $items_info[$id] = $field_values; $this->Application->StoreVar($event->getPrefixSpecial() . '_ItemsInfo', serialize($items_info)); } } /** * Saves Import/Export settings to session * * @param kEvent $event */ function OnResetSettings($event) { $this->Application->StoreVar('ImportCategory', $this->Application->getBaseCategory()); } /** * Cancels item editing * @param kEvent $event * @return void * @todo Used? */ function OnCancelAction($event) { $event->redirect = $this->Application->GetVar('cancel_template'); $event->SetRedirectParam('pass', 'all,' . $event->getPrefixSpecial()); } /* === RELATED TO IMPORT/EXPORT: END === */ /** * Stores item's owner login into separate field together with id * * @param kEvent $event * @param string $id_field * @param string $cached_field */ function cacheItemOwner($event, $id_field, $cached_field) { + /** @var kDBItem $object */ $object = $event->getObject(); - /* @var $object kDBItem */ $object->SetDBField($cached_field, $object->GetField($id_field)); } /** * Saves edited item into temp table * If there is no id, new item is created in temp table * * @param kEvent $event * @return void * @access protected */ protected function OnPreSave(kEvent $event) { parent::OnPreSave($event); $use_pending_editing = $event->getUnitConfig()->getUsePendingEditing(); if ( $event->status == kEvent::erSUCCESS && $use_pending_editing ) { // decision: clone or not clone + /** @var kCatDBItem $object */ $object = $event->getObject(); - /* @var $object kCatDBItem */ if ( $object->GetID() == 0 || $object->GetDBField('OrgId') > 0 ) { // new items or cloned items shouldn't be cloned again return ; } + /** @var kPermissionsHelper $perm_helper */ $perm_helper = $this->Application->recallObject('PermissionsHelper'); - /* @var $perm_helper kPermissionsHelper */ $owner_field = $this->getOwnerField($event->Prefix); if ( $perm_helper->ModifyCheckPermission($object->GetDBField($owner_field), $object->GetDBField('CategoryId'), $event->Prefix) == 2 ) { // 1. clone original item + /** @var kTempTablesHandler $temp_handler */ $temp_handler = $this->Application->recallObject($event->getPrefixSpecial() . '_TempHandler', 'kTempTablesHandler', Array ('parent_event' => $event)); - /* @var $temp_handler kTempTablesHandler */ $cloned_ids = $temp_handler->CloneItems($event->Prefix, $event->Special, Array ($object->GetID()), NULL, NULL, NULL, true); $ci_table = $this->Application->GetTempName(TABLE_PREFIX . 'CategoryItems'); // 2. delete record from CategoryItems (about cloned item) that was automatically created during call of Create method of kCatDBItem $sql = 'SELECT ResourceId FROM ' . $object->TableName . ' WHERE ' . $object->IDField . ' = ' . $cloned_ids[0]; $clone_resource_id = $this->Conn->GetOne($sql); $sql = 'DELETE FROM ' . $ci_table . ' WHERE ItemResourceId = ' . $clone_resource_id . ' AND PrimaryCat = 1'; $this->Conn->Query($sql); // 3. copy main item categoryitems to cloned item $sql = ' INSERT INTO ' . $ci_table . ' (CategoryId, ItemResourceId, PrimaryCat, ItemPrefix, Filename) SELECT CategoryId, ' . $clone_resource_id . ' AS ItemResourceId, PrimaryCat, ItemPrefix, Filename FROM ' . $ci_table . ' WHERE ItemResourceId = ' . $object->GetDBField('ResourceId'); $this->Conn->Query($sql); // 4. put cloned id to OrgId field of item being cloned $sql = 'UPDATE ' . $object->TableName . ' SET OrgId = ' . $object->GetID() . ' WHERE ' . $object->IDField . ' = ' . $cloned_ids[0]; $this->Conn->Query($sql); // 5. substitute id of item being cloned with clone id $this->Application->SetVar($event->getPrefixSpecial() . '_id', $cloned_ids[0]); $selected_ids = $this->getSelectedIDs($event, true); $selected_ids[ array_search($object->GetID(), $selected_ids) ] = $cloned_ids[0]; $this->StoreSelectedIDs($event, $selected_ids); // 6. delete original item from temp table $temp_handler->DeleteItems($event->Prefix, $event->Special, Array ($object->GetID())); } } } /** * Sets item's owner field * * @param kEvent $event * @return void * @access protected */ protected function OnPreCreate(kEvent $event) { parent::OnPreCreate($event); if ( $event->status != kEvent::erSUCCESS ) { return ; } + /** @var kDBItem $object */ $object = $event->getObject(); - /* @var $object kDBItem */ $owner_field = $this->getOwnerField($event->Prefix); $object->SetDBField($owner_field, $this->Application->RecallVar('user_id')); } /** * Occurs before original item of item in pending editing got deleted (for hooking only) * * @param kEvent $event * @return void * @access protected */ protected function OnBeforeDeleteOriginal(kEvent $event) { } /** * Occurs after original item of item in pending editing got deleted (for hooking only) * * @param kEvent $event * @return void * @access protected */ protected function OnAfterDeleteOriginal(kEvent $event) { } /** * Occurs before an item has been cloned * Id of newly created item is passed as event' 'id' param * * @param kEvent $event * @return void * @access protected */ protected function OnBeforeClone(kEvent $event) { parent::OnBeforeClone($event); + /** @var kDBItem $object */ $object = $event->getObject(); - /* @var $object kDBItem */ $object->SetDBField('ResourceId', 0); // this will reset it if ( $this->Application->GetVar('ResetCatBeforeClone') ) { $object->SetDBField('CategoryId', NULL); } } /** * Set status for new category item based on user permission in category * * @param kEvent $event * @return void * @access protected */ protected function OnBeforeItemCreate(kEvent $event) { parent::OnBeforeItemCreate($event); + /** @var kCatDBItem $object */ $object = $event->getObject(); - /* @var $object kCatDBItem */ $owner_field = $this->getOwnerField($event->Prefix); // Don't allow creating records on behalf of another user. if ( !$this->Application->isAdminUser && !defined('CRON') ) { $object->SetDBField($owner_field, $object->GetOriginalField($owner_field)); } // Auto-assign records to currently logged-in user. if ( !$object->GetDBField($owner_field) ) { $object->SetDBField($owner_field, $this->Application->RecallVar('user_id')); } if ( !$this->Application->isAdmin ) { $this->setItemStatusByPermission($event); } } /** * Sets category item status based on user permissions (only on Front-end) * * @param kEvent $event */ function setItemStatusByPermission($event) { $use_pending_editing = $event->getUnitConfig()->getUsePendingEditing(); if (!$use_pending_editing) { return ; } + /** @var kCatDBItem $object */ $object = $event->getObject(); - /* @var $object kCatDBItem */ + /** @var kPermissionsHelper $perm_helper */ $perm_helper = $this->Application->recallObject('PermissionsHelper'); - /* @var $perm_helper kPermissionsHelper */ $primary_category = $object->GetDBField('CategoryId') > 0 ? $object->GetDBField('CategoryId') : $this->Application->GetVar('m_cat_id'); $item_status = $perm_helper->AddCheckPermission($primary_category, $event->Prefix); if ($item_status == STATUS_DISABLED) { $event->status = kEvent::erFAIL; } else { $object->SetDBField('Status', $item_status); } } /** * Creates category item & redirects to confirmation template (front-end only) * * @param kEvent $event * @return void * @access protected */ protected function OnCreate(kEvent $event) { parent::OnCreate($event); $this->SetFrontRedirectTemplate($event, 'suggest'); } /** * Returns item's categories (allows to exclude primary category) * * @param int $resource_id * @param bool $with_primary * @return Array */ function getItemCategories($resource_id, $with_primary = false) { $sql = 'SELECT CategoryId FROM '.TABLE_PREFIX.'CategoryItems WHERE (ItemResourceId = '.$resource_id.')'; if (!$with_primary) { $sql .= ' AND (PrimaryCat = 0)'; } return $this->Conn->GetCol($sql); } /** * Adds new and removes old additional categories from category item * * @param kCatDBItem $object * @param int $mode */ function processAdditionalCategories(&$object, $mode) { if ( !$object->isVirtualField('MoreCategories') ) { // given category item doesn't require such type of processing return ; } $process_categories = $object->GetDBField('MoreCategories'); if ($process_categories === '') { // field was not in submit & have default value (when no categories submitted, then value is null) return ; } if ($mode == 'create') { // prevents first additional category to become primary $object->assignPrimaryCategory(); } $process_categories = $process_categories ? explode('|', substr($process_categories, 1, -1)) : Array (); $existing_categories = $this->getItemCategories($object->GetDBField('ResourceId')); $add_categories = array_diff($process_categories, $existing_categories); foreach ($add_categories as $category_id) { $object->assignToCategory($category_id); } $remove_categories = array_diff($existing_categories, $process_categories); foreach ($remove_categories as $category_id) { $object->removeFromCategory($category_id); } } /** * Creates category item & redirects to confirmation template (front-end only) * * @param kEvent $event * @return void * @access protected */ protected function OnUpdate(kEvent $event) { $use_pending = $event->getUnitConfig()->getUsePendingEditing(); if ($this->Application->isAdminUser || !$use_pending) { parent::OnUpdate($event); $this->SetFrontRedirectTemplate($event, 'modify'); return ; } + /** @var kCatDBItem $object */ $object = $event->getObject(Array('skip_autoload' => true)); - /* @var $object kCatDBItem */ $items_info = $this->Application->GetVar($event->getPrefixSpecial(true)); if ($items_info) { + /** @var kPermissionsHelper $perm_helper */ $perm_helper = $this->Application->recallObject('PermissionsHelper'); - /* @var $perm_helper kPermissionsHelper */ + /** @var kTempTablesHandler $temp_handler */ $temp_handler = $this->Application->recallObject($event->getPrefixSpecial().'_TempHandler', 'kTempTablesHandler', Array ('parent_event' => $event)); - /* @var $temp_handler kTempTablesHandler */ $owner_field = $this->getOwnerField($event->Prefix); + /** @var FileHelper $file_helper */ $file_helper = $this->Application->recallObject('FileHelper'); - /* @var $file_helper FileHelper */ foreach ($items_info as $id => $field_values) { $object->Load($id); $edit_perm = $perm_helper->ModifyCheckPermission($object->GetDBField($owner_field), $object->GetDBField('CategoryId'), $event->Prefix); if ($use_pending && !$object->GetDBField('OrgId') && ($edit_perm == STATUS_PENDING)) { // pending editing enabled + not pending copy -> get/create pending copy & save changes to it $original_id = $object->GetID(); $original_resource_id = $object->GetDBField('ResourceId'); $file_helper->PreserveItemFiles($field_values); $object->Load($original_id, 'OrgId'); if (!$object->isLoaded()) { // 1. user has no pending copy of live item -> clone live item $cloned_ids = $temp_handler->CloneItems($event->Prefix, $event->Special, Array($original_id), NULL, NULL, NULL, true); $object->Load($cloned_ids[0]); $object->SetFieldsFromHash($field_values); $event->setEventParam('form_data', $field_values); // 1a. delete record from CategoryItems (about cloned item) that was automatically created during call of Create method of kCatDBItem $ci_table = $this->Application->getUnitConfig('ci')->getTableName(); $sql = 'DELETE FROM '.$ci_table.' WHERE ItemResourceId = '.$object->GetDBField('ResourceId').' AND PrimaryCat = 1'; $this->Conn->Query($sql); // 1b. copy main item categoryitems to cloned item $sql = 'INSERT INTO '.$ci_table.' (CategoryId, ItemResourceId, PrimaryCat, ItemPrefix, Filename) SELECT CategoryId, '.$object->GetDBField('ResourceId').' AS ItemResourceId, PrimaryCat, ItemPrefix, Filename FROM '.$ci_table.' WHERE ItemResourceId = '.$original_resource_id; $this->Conn->Query($sql); // 1c. put cloned id to OrgId field of item being cloned $object->SetDBField('Status', STATUS_PENDING_EDITING); $object->SetDBField('OrgId', $original_id); } else { // 2. user has pending copy of live item -> just update field values $object->SetFieldsFromHash($field_values); $event->setEventParam('form_data', $field_values); } // update id in request (used for redirect in mod-rewrite mode) $this->Application->SetVar($event->getPrefixSpecial().'_id', $object->GetID()); } else { // 3. already editing pending copy -> just update field values $object->SetFieldsFromHash($field_values); $event->setEventParam('form_data', $field_values); } if ($object->Update()) { $event->status = kEvent::erSUCCESS; } else { $event->status = kEvent::erFAIL; $event->redirect = false; break; } } } $this->SetFrontRedirectTemplate($event, 'modify'); } /** * Sets next template to one required for front-end after adding/modifying item * * @param kEvent $event * @param string $template_key - {suggest,modify} */ function SetFrontRedirectTemplate($event, $template_key) { if ( $this->Application->isAdmin || $event->status != kEvent::erSUCCESS ) { return; } // prepare redirect template + /** @var kDBItem $object */ $object = $event->getObject(); - /* @var $object kDBItem */ $is_active = ($object->GetDBField('Status') == STATUS_ACTIVE); $next_template = $is_active ? 'confirm_template' : 'pending_confirm_template'; $event->redirect = $this->Application->GetVar($template_key . '_' . $next_template); $event->SetRedirectParam('opener', 's'); // send email events $perm_prefix = $event->getUnitConfig()->getPermItemPrefix(); $owner_field = $this->getOwnerField($event->Prefix); $owner_id = $object->GetDBField($owner_field); $send_params = $object->getEmailParams(); switch ( $event->Name ) { case 'OnCreate': $event_suffix = $is_active ? 'ADD' : 'ADD.PENDING'; $this->Application->emailUser($perm_prefix . '.' . $event_suffix, $owner_id, $send_params); $this->Application->emailAdmin($perm_prefix . '.' . $event_suffix, null, $send_params); // there are no ADD.PENDING event for admin :( break; case 'OnUpdate': $event_suffix = $is_active ? 'MODIFY' : 'MODIFY.PENDING'; $user_id = is_numeric($object->GetDBField('ModifiedById')) ? $object->GetDBField('ModifiedById') : $owner_id; $this->Application->emailUser($perm_prefix . '.' . $event_suffix, $user_id, $send_params); $this->Application->emailAdmin($perm_prefix . '.' . $event_suffix, null, $send_params); // there are no ADD.PENDING event for admin :( break; } } /** * Apply same processing to each item being selected in grid * * @param kEvent $event * @return void * @access protected */ protected function iterateItems(kEvent $event) { if ( $event->Name != 'OnMassApprove' && $event->Name != 'OnMassDecline' ) { parent::iterateItems($event); } if ( $this->Application->CheckPermission('SYSTEM_ACCESS.READONLY', 1) ) { $event->status = kEvent::erFAIL; return ; } + /** @var kCatDBItem $object */ $object = $event->getObject(Array ('skip_autoload' => true)); - /* @var $object kCatDBItem */ $ids = $this->StoreSelectedIDs($event); if ( $ids ) { foreach ($ids as $id) { $object->Load($id); switch ( $event->Name ) { case 'OnMassApprove': $object->ApproveChanges(); break; case 'OnMassDecline': $object->DeclineChanges(); break; } } } $this->clearSelectedIDs($event); } /** * Deletes items & preserves clean env * * @param kEvent $event * @return void * @access protected */ protected function OnDelete(kEvent $event) { parent::OnDelete($event); if ( $event->status == kEvent::erSUCCESS && !$this->Application->isAdmin ) { $event->SetRedirectParam('pass', 'm'); $event->SetRedirectParam('m_cat_id', 0); } } /** * Checks, that currently loaded item is allowed for viewing (non permission-based) * * @param kEvent $event * @return bool * @access protected */ protected function checkItemStatus(kEvent $event) { + /** @var kDBItem $object */ $object = $event->getObject(); - /* @var $object kDBItem */ if ( !$object->isLoaded() ) { if ( $event->Special != 'previous' && $event->Special != 'next' ) { $this->_errorNotFound($event); } return true; } $status = $object->GetDBField('Status'); $user_id = $this->Application->RecallVar('user_id'); $owner_field = $this->getOwnerField($event->Prefix); if ( ($status == STATUS_PENDING_EDITING || $status == STATUS_PENDING) && ($object->GetDBField($owner_field) == $user_id) ) { return true; } return $status == STATUS_ACTIVE; } /** * Set's correct sorting for list based on data provided with event * * @param kEvent $event * @return void * @access protected * @see kDBEventHandler::OnListBuild() */ protected function SetSorting(kEvent $event) { if ( !$this->Application->isAdmin ) { $event->setEventParam('same_special', true); } $types = $event->getEventParam('types'); $types = $types ? explode(',', $types) : Array (); if ( in_array('search', $types) ) { $event->setPseudoClass('_List'); + /** @var kDBList $object */ $object = $event->getObject(); - /* @var $object kDBList */ // 1. no user sorting - sort by relevance $default_sortings = parent::_getDefaultSorting($event); $default_sorting = key($default_sortings['Sorting']) . ',' . current($default_sortings['Sorting']); if ( $object->isMainList() ) { $sort_by = $this->Application->GetVar('sort_by', ''); if ( !$sort_by ) { $this->Application->SetVar('sort_by', 'Relevance,desc|' . $default_sorting); } } else { $sorting_settings = $this->getListSetting($event, 'Sortings'); $sort_by = trim(getArrayValue($sorting_settings, 'Sort1') . ',' . getArrayValue($sorting_settings, 'Sort1_Dir'), ','); if ( !$sort_by ) { $event->setEventParam('sort_by', 'Relevance,desc|' . $default_sorting); } } $this->_removeForcedSortings($event); } parent::SetSorting($event); } /** * Removes forced sortings * * @param kEvent $event */ protected function _removeForcedSortings(kEvent $event) { $config = $event->getUnitConfig(); foreach ($config->getListSortingSpecials() as $special) { $list_sortings = $config->getListSortingsBySpecial($special); unset($list_sortings['ForcedSorting']); $config->setListSortingsBySpecial('', $list_sortings); } } /** * Default sorting in search results only comes from relevance field * * @param kEvent $event * @return Array * @access protected */ protected function _getDefaultSorting(kEvent $event) { $types = $event->getEventParam('types'); $types = $types ? explode(',', $types) : Array (); return in_array('search', $types) ? Array () : parent::_getDefaultSorting($event); } /** * Returns current per-page setting for list * * @param kEvent $event * @return int * @access protected */ protected function getPerPage(kEvent $event) { if ( !$this->Application->isAdmin ) { $event->setEventParam('same_special', true); } return parent::getPerPage($event); } /** * Returns owner field for given prefix * * @param $prefix * @return string * @access protected */ protected function getOwnerField($prefix) { return $this->Application->getUnitConfig($prefix)->getOwnerField('CreatedById'); } /** * Creates virtual image fields for item * * @param kEvent $event * @return void * @access protected */ protected function OnAfterConfigRead(kEvent $event) { parent::OnAfterConfigRead($event); if (defined('IS_INSTALL') && IS_INSTALL) { $this->addViewPermissionJoin($event); return ; } if ( !$this->Application->isAdmin ) { + /** @var FileHelper $file_helper */ $file_helper = $this->Application->recallObject('FileHelper'); - /* @var $file_helper FileHelper */ $file_helper->createItemFiles($event->Prefix, true); // create image fields $file_helper->createItemFiles($event->Prefix, false); // create file fields } $this->changeSortings($event)->addViewPermissionJoin($event); // add grids for advanced view (with primary category column) $config = $event->getUnitConfig(); foreach (Array ('Default', 'Radio') as $process_grid) { $grid_data = $config->getGridByName($process_grid); $grid_data['Fields']['CachedNavbar'] = Array ('title' => 'la_col_Path', 'data_block' => 'grid_primary_category_td', 'filter_block' => 'grid_like_filter'); $config->addGrids($grid_data, $process_grid . 'ShowAll'); } // add options for CategoryId field (quick way to select item's primary category) + /** @var CategoryHelper $category_helper */ $category_helper = $this->Application->recallObject('CategoryHelper'); - /* @var $category_helper CategoryHelper */ $virtual_fields = $config->getVirtualFields(); $virtual_fields['CategoryId']['default'] = (int)$this->Application->GetVar('m_cat_id'); $virtual_fields['CategoryId']['options'] = $category_helper->getStructureTreeAsOptions(); $config->setVirtualFields($virtual_fields); } /** * Changes default sorting according to system settings. * * @param kEvent $event Event. * * @return self * @access protected */ protected function changeSortings(kEvent $event) { $remove_sortings = Array (); $config = $event->getUnitConfig(); if ( !$this->Application->isAdmin ) { // remove Pick sorting on Front-end, when not required $config_mapping = $config->getConfigMapping(Array ()); if ( !isset($config_mapping['ForceEditorPick']) || !$this->Application->ConfigValue($config_mapping['ForceEditorPick']) ) { $remove_sortings[] = 'EditorsPick'; } } else { // remove all forced sortings in Admin Console $remove_sortings = array_merge($remove_sortings, Array ('Priority', 'EditorsPick')); } if ( !$remove_sortings ) { return $this; } foreach ($config->getListSortingSpecials() as $special) { $list_sortings = $config->getListSortingsBySpecial($special); foreach ($remove_sortings as $sorting_field) { unset($list_sortings['ForcedSorting'][$sorting_field]); } $config->setListSortingsBySpecial('', $list_sortings); } return $this; } /** * Adds permission table table JOIN clause only, when advanced catalog view permissions enabled. * * @param kEvent $event Event. * * @return self * @access protected */ protected function addViewPermissionJoin(kEvent $event) { if ( $this->Application->ConfigValue('CheckViewPermissionsInCatalog') ) { $join_clause = 'LEFT JOIN ' . TABLE_PREFIX . 'CategoryPermissionsCache perm ON perm.CategoryId = ' . TABLE_PREFIX . '%3$sCategoryItems.CategoryId'; } else { $join_clause = ''; } $config = $event->getUnitConfig(); foreach ( $config->getListSQLSpecials() as $special ) { $list_sql = str_replace('{PERM_JOIN}', $join_clause, $config->getListSQLsBySpecial($special)); $config->setListSQLsBySpecial($special, $list_sql); } return $this; } /** * Returns file contents associated with item * * @param kEvent $event */ function OnDownloadFile($event) { + /** @var kCatDBItem $object */ $object = $event->getObject(); - /* @var $object kCatDBItem */ $event->status = kEvent::erSTOP; $field = $this->Application->GetVar('field'); if (!preg_match('/^File([\d]+)/', $field)) { return ; } + /** @var FileHelper $file_helper */ $file_helper = $this->Application->recallObject('FileHelper'); - /* @var $file_helper FileHelper */ $filename = $object->GetField($field, 'full_path'); $file_helper->DownloadFile($filename); } /** * Saves user's vote * * @param kEvent $event */ function OnMakeVote($event) { $event->status = kEvent::erSTOP; if ($this->Application->GetVar('ajax') != 'yes') { // this is supposed to call from AJAX only return ; } + /** @var RatingHelper $rating_helper */ $rating_helper = $this->Application->recallObject('RatingHelper'); - /* @var $rating_helper RatingHelper */ + /** @var kCatDBItem $object */ $object = $event->getObject( Array ('skip_autoload' => true) ); - /* @var $object kCatDBItem */ $object->Load( $this->Application->GetVar('id') ); echo $rating_helper->makeVote($object); } /** * Marks review as useful * * @param kEvent $event * @return void * @access protected */ protected function OnReviewHelpful($event) { if ( $this->Application->GetVar('ajax') == 'yes' ) { $event->status = kEvent::erSTOP; } $review_id = (int)$this->Application->GetVar('review_id'); if ( !$review_id ) { return; } + /** @var SpamHelper $spam_helper */ $spam_helper = $this->Application->recallObject('SpamHelper'); - /* @var $spam_helper SpamHelper */ $spam_helper->InitHelper($review_id, 'ReviewHelpful', strtotime('+1 month') - strtotime('now')); $field = (int)$this->Application->GetVar('helpful') ? 'HelpfulCount' : 'NotHelpfulCount'; $review_config = $this->Application->getUnitConfig('rev'); $sql = 'SELECT ' . $field . ' FROM ' . $review_config->getTableName() . ' WHERE ' . $review_config->getIDField() . ' = ' . $review_id; $count = $this->Conn->GetOne($sql); if ( $spam_helper->InSpamControl() ) { if ( $this->Application->GetVar('ajax') == 'yes' ) { echo $count; } return; } $sql = 'UPDATE ' . $review_config->getTableName() . ' SET ' . $field . ' = ' . $field . ' + 1 WHERE ' . $review_config->getIDField() . ' = ' . $review_id; $this->Conn->Query($sql); if ( $this->Conn->getAffectedRows() ) { // db was changed -> review with such ID exists $spam_helper->AddToSpamControl(); } if ( $this->Application->GetVar('ajax') == 'yes' ) { echo $count + 1; } } /** * [HOOK] Allows to add cloned subitem to given prefix * * @param kEvent $event * @return void * @access protected */ protected function OnCloneSubItem(kEvent $event) { parent::OnCloneSubItem($event); if ( $event->MasterEvent->Prefix == 'fav' ) { $master_config = $event->MasterEvent->getUnitConfig(); $clones = $master_config->getClones(); $sub_item_prefix = $event->Prefix . '-' . $event->MasterEvent->Prefix; $clones[$sub_item_prefix]['ParentTableKey'] = 'ResourceId'; $clones[$sub_item_prefix]['ForeignKey'] = 'ResourceId'; $master_config->setClones($clones); } } /** * Set's new unique resource id to user * * @param kEvent $event * @return void * @access protected */ protected function OnAfterItemValidate(kEvent $event) { + /** @var kDBItem $object */ $object = $event->getObject(); - /* @var $object kDBItem */ $resource_id = $object->GetDBField('ResourceId'); if ( !$resource_id ) { $object->SetDBField('ResourceId', $this->Application->NextResourceId()); } } } Index: branches/5.3.x/core/kernel/application.php =================================================================== --- branches/5.3.x/core/kernel/application.php (revision 16518) +++ branches/5.3.x/core/kernel/application.php (revision 16519) @@ -1,3092 +1,3092 @@ * The class encapsulates the main run-cycle of the script, provide access to all other objects in the framework.
*
* The class is a singleton, which means that there could be only one instance of kApplication in the script.
* This could be guaranteed by NOT calling the class constructor directly, but rather calling kApplication::Instance() method, * which returns an instance of the application. The method guarantees that it will return exactly the same instance for any call.
* See singleton pattern by GOF. */ class kApplication implements kiCacheable { /** * Location of module helper class (used in installator too) */ const MODULE_HELPER_PATH = '/../units/helpers/modules_helper.php'; /** * Is true, when Init method was called already, prevents double initialization * * @var bool */ public $InitDone = false; /** * Holds internal NParser object * * @var NParser * @access public */ public $Parser; /** * Holds parser output buffer * * @var string * @access protected */ protected $HTML = ''; /** * The main Factory used to create * almost any class of kernel and * modules * * @var kFactory * @access protected */ protected $Factory; /** * Template names, that will be used instead of regular templates * * @var Array * @access public */ public $ReplacementTemplates = Array (); /** * Registered routers, that are used during url building and parsing. * * @var array */ public $routers = array(); /** * Reference to debugger * * @var Debugger * @access public */ public $Debugger = null; /** * Holds all phrases used * in code and template * * @var kPhraseCache * @access public */ public $Phrases; /** * Modules table content, key - module name * * @var Array * @access public */ public $ModuleInfo = Array (); /** * Holds DBConnection * * @var IDBConnection * @access public */ public $Conn = null; /** * Reference to event log * * @var Array|kLogger * @access public */ protected $_logger = Array (); // performance needs: /** * Holds a reference to httpquery * * @var kHttpQuery * @access public */ public $HttpQuery = null; /** * Holds a reference to UnitConfigReader * * @var kUnitConfigReader * @access public */ public $UnitConfigReader = null; /** * Holds a reference to Session * * @var Session * @access public */ public $Session = null; /** * Holds a ref to kEventManager * * @var kEventManager * @access public */ public $EventManager = null; /** * Holds a ref to kUrlManager * * @var kUrlManager * @access public */ public $UrlManager = null; /** * Ref for TemplatesCache * * @var TemplatesCache * @access public */ public $TemplatesCache = null; /** * Holds current NParser tag while parsing, can be used in error messages to display template file and line * * @var _BlockTag * @access public */ public $CurrentNTag = null; /** * Object of unit caching class * * @var kCacheManager * @access public */ public $cacheManager = null; /** * Tells, that administrator has authenticated in administrative console * Should be used to manipulate data change OR data restrictions! * * @var bool * @access public */ public $isAdminUser = false; /** * Tells, that admin version of "index.php" was used, nothing more! * Should be used to manipulate data display! * * @var bool * @access public */ public $isAdmin = false; /** * Instance of site domain object * * @var kDBItem * @access public * @todo move away into separate module */ public $siteDomain = null; /** * Prevent kApplication class to be created directly, only via Instance method * * @access private */ private function __construct() { } final private function __clone() {} /** * Returns kApplication instance anywhere in the script. * * This method should be used to get single kApplication object instance anywhere in the * Kernel-based application. The method is guaranteed to return the SAME instance of kApplication. * Anywhere in the script you could write: * * $application =& kApplication::Instance(); * * or in an object: * * $this->Application =& kApplication::Instance(); * * to get the instance of kApplication. Note that we call the Instance method as STATIC - directly from the class. * To use descendant of standard kApplication class in your project you would need to define APPLICATION_CLASS constant * BEFORE calling kApplication::Instance() for the first time. If APPLICATION_CLASS is not defined the method would * create and return default KernelApplication instance. * * Pattern: Singleton * * @static * @return kApplication * @access public */ public static function &Instance() { static $instance = false; if ( !$instance ) { $class = defined('APPLICATION_CLASS') ? APPLICATION_CLASS : 'kApplication'; $instance = new $class(); } return $instance; } /** * Initializes the Application * * @param string $factory_class * @return bool Was Init actually made now or before * @access public * @see kHTTPQuery * @see Session * @see TemplatesCache */ public function Init($factory_class = 'kFactory') { if ( $this->InitDone ) { return false; } if ( preg_match('/utf-8/i', CHARSET) ) { setlocale(LC_ALL, 'en_US.UTF-8'); mb_internal_encoding('UTF-8'); } $this->isAdmin = kUtil::constOn('ADMIN'); if ( !kUtil::constOn('SKIP_OUT_COMPRESSION') ) { ob_start(); // collect any output from method (other then tags) into buffer } if ( defined('DEBUG_MODE') && $this->isDebugMode() && kUtil::constOn('DBG_PROFILE_MEMORY') ) { $this->Debugger->appendMemoryUsage('Application before Init:'); } $this->_logger = new kLogger($this->_logger); $this->Factory = new $factory_class(); $this->registerDefaultClasses(); $system_config = new kSystemConfig(true); $vars = $system_config->getData(); $db_class = isset($vars['Databases']) ? 'kDBLoadBalancer' : ($this->isDebugMode() ? 'kDBConnectionDebug' : 'kDBConnection'); $this->Conn = $this->Factory->makeClass($db_class, Array (SQL_TYPE, Array ($this->_logger, 'handleSQLError'))); $this->Conn->setup($vars); $this->cacheManager = $this->makeClass('kCacheManager'); $this->cacheManager->InitCache(); if ( defined('DEBUG_MODE') && $this->isDebugMode() ) { $this->Debugger->appendTimestamp('Before UnitConfigReader'); } // init config reader and all managers $this->UnitConfigReader = $this->makeClass('kUnitConfigReader'); $this->UnitConfigReader->scanModules(MODULES_PATH); // Will also set routers. $this->registerModuleConstants(); if ( defined('DEBUG_MODE') && $this->isDebugMode() ) { $this->Debugger->appendTimestamp('After UnitConfigReader'); } define('MOD_REWRITE', $this->ConfigValue('UseModRewrite') && !$this->isAdmin ? 1 : 0); // start processing request $this->HttpQuery = $this->recallObject('kHTTPQuery'); $this->HttpQuery->process(); if ( defined('DEBUG_MODE') && $this->isDebugMode() ) { $this->Debugger->appendTimestamp('Processed HTTPQuery initial'); } $this->Session = $this->recallObject('Session'); if ( defined('DEBUG_MODE') && $this->isDebugMode() ) { $this->Debugger->appendTimestamp('Processed Session'); } $this->Session->ValidateExpired(); // needs mod_rewrite url already parsed to keep user at proper template after session expiration if ( defined('DEBUG_MODE') && $this->isDebugMode() ) { $this->Debugger->appendTimestamp('Processed HTTPQuery AfterInit'); } $this->cacheManager->LoadApplicationCache(); $site_timezone = $this->ConfigValue('Config_Site_Time'); if ( $site_timezone ) { date_default_timezone_set($site_timezone); } if ( defined('DEBUG_MODE') && $this->isDebugMode() ) { $this->Debugger->appendTimestamp('Loaded cache and phrases'); } $this->ValidateLogin(); // must be called before AfterConfigRead, because current user should be available there $this->UnitConfigReader->AfterConfigRead(); if ( defined('DEBUG_MODE') && $this->isDebugMode() ) { $this->Debugger->appendTimestamp('Processed AfterConfigRead'); } if ( $this->GetVar('m_cat_id') === false ) { $this->SetVar('m_cat_id', 0); } if ( !$this->RecallVar('curr_iso') && !(defined('IS_INSTALL') && IS_INSTALL) ) { $this->StoreVar('curr_iso', $this->GetPrimaryCurrency(), true); // true for optional } $visit_id = $this->RecallVar('visit_id'); if ( $visit_id !== false ) { $this->SetVar('visits_id', $visit_id); } if ( defined('DEBUG_MODE') && $this->isDebugMode() ) { $this->Debugger->profileFinish('kernel4_startup'); } $this->InitDone = true; if ( PHP_SAPI !== 'cli' && !$this->isAdmin ) { $this->HandleEvent(new kEvent('adm:OnStartup')); } return true; } /** * Performs initialization of manager classes, that can be overridden from unit configs * * @return void * @access public * @throws Exception */ public function InitManagers() { if ( $this->InitDone ) { throw new Exception('Duplicate call of ' . __METHOD__, E_USER_ERROR); } $this->UrlManager = $this->makeClass('kUrlManager'); $this->EventManager = $this->makeClass('kEventManager'); $this->Phrases = $this->makeClass('kPhraseCache'); $this->RegisterDefaultBuildEvents(); } /** * Returns module information. Searches module by requested field * * @param string $field * @param mixed $value * @param string $return_field field value to returns, if not specified, then return all fields * @return Array */ public function findModule($field, $value, $return_field = null) { $found = $module_info = false; foreach ($this->ModuleInfo as $module_info) { if ( strtolower($module_info[$field]) == strtolower($value) ) { $found = true; break; } } if ( $found ) { return isset($return_field) ? $module_info[$return_field] : $module_info; } return false; } /** * Refreshes information about loaded modules * * @return void * @access public */ public function refreshModuleInfo() { if ( defined('IS_INSTALL') && IS_INSTALL && !$this->TableFound('Modules', true) ) { $this->registerModuleConstants(); $this->Factory->configureAutoloader(); return; } // use makeClass over recallObject, since used before kApplication initialization during installation + /** @var kModulesHelper $modules_helper */ $modules_helper = $this->makeClass('kModulesHelper'); - /* @var $modules_helper kModulesHelper */ $this->Conn->nextQueryCachable = true; $sql = 'SELECT * FROM ' . TABLE_PREFIX . 'Modules WHERE ' . $modules_helper->getWhereClause() . ' ORDER BY LoadOrder'; $this->ModuleInfo = $this->Conn->Query($sql, 'Name'); $this->registerModuleConstants(); $this->Factory->configureAutoloader(); } /** * Checks if passed language id if valid and sets it to primary otherwise * * @return void * @access public */ public function VerifyLanguageId() { /** @var LanguagesItem $lang */ $lang = $this->recallObject('lang.current'); if ( !$lang->isLoaded() || (!$this->isAdmin && !$lang->GetDBField('Enabled')) ) { if ( !defined('IS_INSTALL') ) { $this->ApplicationDie('Unknown or disabled language'); } } } /** * Checks if passed theme id if valid and sets it to primary otherwise * * @return void * @access public */ public function VerifyThemeId() { if ( $this->isAdmin ) { kUtil::safeDefine('THEMES_PATH', '/core/admin_templates'); return; } $path = $this->GetFrontThemePath(); if ( $path === false ) { $this->ApplicationDie('No Primary Theme Selected or Current Theme is Unknown or Disabled'); } kUtil::safeDefine('THEMES_PATH', $path); } /** * Returns relative path to current front-end theme * * @param bool $force * @return string * @access public */ public function GetFrontThemePath($force = false) { static $path = null; if ( !$force && isset($path) ) { return $path; } /** @var ThemeItem $theme */ $theme = $this->recallObject('theme.current'); if ( !$theme->isLoaded() || !$theme->GetDBField('Enabled') ) { return false; } // assign & then return, since it's static variable $path = '/themes/' . $theme->GetDBField('Name'); return $path; } /** * Returns primary front/admin language id * * @param bool $init * @return int * @access public */ public function GetDefaultLanguageId($init = false) { $cache_key = 'primary_language_info[%LangSerial%]'; $language_info = $this->getCache($cache_key); if ( $language_info === false ) { // cache primary language info first $language_config = $this->getUnitConfig('lang'); $table = $language_config->getTableName(); $id_field = $language_config->getIDField(); $this->Conn->nextQueryCachable = true; $sql = 'SELECT ' . $id_field . ', IF(AdminInterfaceLang, "Admin", "Front") AS LanguageKey FROM ' . $table . ' WHERE (AdminInterfaceLang = 1 OR PrimaryLang = 1) AND (Enabled = 1)'; $language_info = $this->Conn->GetCol($sql, 'LanguageKey'); if ( $language_info !== false ) { $this->setCache($cache_key, $language_info); } } $language_key = ($this->isAdmin && $init) || count($language_info) == 1 ? 'Admin' : 'Front'; if ( array_key_exists($language_key, $language_info) && $language_info[$language_key] > 0 ) { // get from cache return $language_info[$language_key]; } $language_id = $language_info && array_key_exists($language_key, $language_info) ? $language_info[$language_key] : false; if ( !$language_id && defined('IS_INSTALL') && IS_INSTALL ) { $language_id = 1; } return $language_id; } /** * Returns front-end primary theme id (even, when called from admin console) * * @param bool $force_front * @return int * @access public */ public function GetDefaultThemeId($force_front = false) { static $theme_id = 0; if ( $theme_id > 0 ) { return $theme_id; } if ( kUtil::constOn('DBG_FORCE_THEME') ) { $theme_id = DBG_FORCE_THEME; } elseif ( !$force_front && $this->isAdmin ) { $theme_id = 999; } else { $cache_key = 'primary_theme[%ThemeSerial%]'; $theme_id = $this->getCache($cache_key); if ( $theme_id === false ) { $this->Conn->nextQueryCachable = true; $theme_config = $this->getUnitConfig('theme'); $sql = 'SELECT ' . $theme_config->getIDField() . ' FROM ' . $theme_config->getTableName() . ' WHERE (PrimaryTheme = 1) AND (Enabled = 1)'; $theme_id = $this->Conn->GetOne($sql); if ( $theme_id !== false ) { $this->setCache($cache_key, $theme_id); } } } return $theme_id; } /** * Returns site primary currency ISO code * * @return string * @access public * @todo Move into In-Commerce */ public function GetPrimaryCurrency() { $cache_key = 'primary_currency[%CurrSerial%][%SiteDomainSerial%]:' . $this->siteDomainField('DomainId'); $currency_iso = $this->getCache($cache_key); if ( $currency_iso === false ) { if ( $this->prefixRegistred('curr') ) { $this->Conn->nextQueryCachable = true; $currency_id = $this->siteDomainField('PrimaryCurrencyId'); $sql = 'SELECT ISO FROM ' . $this->getUnitConfig('curr')->getTableName() . ' WHERE ' . ($currency_id > 0 ? 'CurrencyId = ' . $currency_id : 'IsPrimary = 1'); $currency_iso = $this->Conn->GetOne($sql); } else { $currency_iso = 'USD'; } $this->setCache($cache_key, $currency_iso); } return $currency_iso; } /** * Returns site domain field. When none of site domains are found false is returned. * * @param string $field * @param bool $formatted * @param string $format * @return mixed * @todo Move into separate module */ public function siteDomainField($field, $formatted = false, $format = null) { if ( $this->isAdmin ) { // don't apply any filtering in administrative console return false; } if ( !$this->siteDomain ) { $this->siteDomain = $this->recallObject('site-domain.current', null, Array ('live_table' => true)); - /* @var $site_domain kDBItem */ + /** @var kDBItem $site_domain */ } if ( $this->siteDomain->isLoaded() ) { return $formatted ? $this->siteDomain->GetField($field, $format) : $this->siteDomain->GetDBField($field); } return false; } /** * Registers classes, that are used before unit configs (where class registration usually is done) are read. * * Called automatically while initializing kApplication. * * @return void * @access public */ public function RegisterDefaultClasses() { // Database. $this->registerClass('IDBConnection', KERNEL_PATH . '/db/i_db_connection.php'); $this->registerClass('kDBConnection', KERNEL_PATH . '/db/db_connection.php'); $this->registerClass('kDBConnectionDebug', KERNEL_PATH . '/db/db_connection.php'); $this->registerClass('kDBLoadBalancer', KERNEL_PATH . '/db/db_load_balancer.php'); // Cache. $this->registerClass('kCacheManager', KERNEL_PATH . '/managers/cache_manager.php'); $this->registerClass('kCache', KERNEL_PATH . '/utility/cache.php'); $this->registerClass('kSubscriptionItem', KERNEL_PATH . '/managers/subscription_manager.php'); // Unit configs. $this->registerClass('kUnitConfigReader', KERNEL_PATH . '/utility/unit_config_reader.php'); $this->registerClass('kUnitConfigCloner', KERNEL_PATH . '/utility/unit_config_cloner.php'); // Urls. $this->registerClass('kUrlManager', KERNEL_PATH . '/managers/url_manager.php'); $this->registerClass('kUrlProcessor', KERNEL_PATH . '/managers/url_processor.php'); $this->registerClass('kPlainUrlProcessor', KERNEL_PATH . '/managers/plain_url_processor.php'); // $this->registerClass('kRewriteUrlProcessor', KERNEL_PATH . '/managers/rewrite_url_processor.php'); // Events. $this->registerClass('kEventManager', KERNEL_PATH . '/event_manager.php'); $this->registerClass('kHookManager', KERNEL_PATH . '/managers/hook_manager.php'); $this->registerClass('kScheduledTaskManager', KERNEL_PATH . '/managers/scheduled_task_manager.php'); $this->registerClass('kRequestManager', KERNEL_PATH . '/managers/request_manager.php'); // Misc. $this->registerClass('kPhraseCache', KERNEL_PATH . '/languages/phrases_cache.php'); $this->registerClass('kModulesHelper', KERNEL_PATH . self::MODULE_HELPER_PATH); // Aliased. $this->registerClass('Params', KERNEL_PATH . '/utility/params.php', 'kActions'); $this->registerClass('kMainTagProcessor', KERNEL_PATH . '/processors/main_processor.php', 'm_TagProcessor'); $this->registerClass('kEmailSendingHelper', KERNEL_PATH . '/utility/email_send.php', 'EmailSender'); } /** * Registers default build events * * @return void */ public function RegisterDefaultBuildEvents() { $this->EventManager->registerBuildEvent('kTempTablesHandler', 'OnTempHandlerBuild'); } /** * Returns cached category information by given cache name. All given category * information is recached, when at least one of 4 caches is missing. * * @param int $category_id * @param string $name cache name = {filenames, category_designs, category_tree} * @return string * @access public */ public function getCategoryCache($category_id, $name) { return $this->cacheManager->getCategoryCache($category_id, $name); } /** * Returns caching type (none, memory, temporary) * * @param int $caching_type * @return bool * @access public */ public function isCachingType($caching_type) { return $this->cacheManager->isCachingType($caching_type); } /** * Increments serial based on prefix and it's ID (optional) * * @param string $prefix * @param int $id ID (value of IDField) or ForeignKeyField:ID * @param bool $increment * @return string * @access public */ public function incrementCacheSerial($prefix, $id = null, $increment = true) { return $this->cacheManager->incrementCacheSerial($prefix, $id, $increment); } /** * Returns cached $key value from cache named $cache_name * * @param int $key key name from cache * @param bool $store_locally store data locally after retrieved * @param int $max_rebuild_seconds * @return mixed * @access public */ public function getCache($key, $store_locally = true, $max_rebuild_seconds = 0) { return $this->cacheManager->getCache($key, $store_locally, $max_rebuild_seconds); } /** * Stores new $value in cache with $key name * * @param int $key key name to add to cache * @param mixed $value value of cached record * @param int $expiration when value expires (0 - doesn't expire) * @return bool * @access public */ public function setCache($key, $value, $expiration = 0) { return $this->cacheManager->setCache($key, $value, $expiration); } /** * Stores new $value in cache with $key name (only if it's not there) * * @param int $key key name to add to cache * @param mixed $value value of cached record * @param int $expiration when value expires (0 - doesn't expire) * @return bool * @access public */ public function addCache($key, $value, $expiration = 0) { return $this->cacheManager->addCache($key, $value, $expiration); } /** * Sets rebuilding mode for given cache * * @param string $name * @param int $mode * @param int $max_rebuilding_time * @return bool * @access public */ public function rebuildCache($name, $mode = null, $max_rebuilding_time = 0) { return $this->cacheManager->rebuildCache($name, $mode, $max_rebuilding_time); } /** * Deletes key from cache * * @param string $key * @return void * @access public */ public function deleteCache($key) { $this->cacheManager->deleteCache($key); } /** * Reset's all memory cache at once * * @return void * @access public */ public function resetCache() { $this->cacheManager->resetCache(); } /** * Returns value from database cache * * @param string $name key name * @param int $max_rebuild_seconds * @return mixed * @access public */ public function getDBCache($name, $max_rebuild_seconds = 0) { return $this->cacheManager->getDBCache($name, $max_rebuild_seconds); } /** * Sets value to database cache * * @param string $name * @param mixed $value * @param int|bool $expiration * @return void * @access public */ public function setDBCache($name, $value, $expiration = false) { $this->cacheManager->setDBCache($name, $value, $expiration); } /** * Sets rebuilding mode for given cache * * @param string $name * @param int $mode * @param int $max_rebuilding_time * @return bool * @access public */ public function rebuildDBCache($name, $mode = null, $max_rebuilding_time = 0) { return $this->cacheManager->rebuildDBCache($name, $mode, $max_rebuilding_time); } /** * Deletes key from database cache * * @param string $name * @return void * @access public */ public function deleteDBCache($name) { $this->cacheManager->deleteDBCache($name); } /** * Registers each module specific constants if any found * * @return bool * @access protected */ protected function registerModuleConstants() { if ( file_exists(KERNEL_PATH . '/constants.php') ) { kUtil::includeOnce(KERNEL_PATH . '/constants.php'); } if ( !$this->ModuleInfo ) { return false; } foreach ($this->ModuleInfo as $module_info) { $constants_file = FULL_PATH . '/' . $module_info['Path'] . 'constants.php'; if ( file_exists($constants_file) ) { kUtil::includeOnce($constants_file); } } return true; } /** * Performs redirect to hard maintenance template * * @return void * @access public */ public function redirectToMaintenance() { $maintenance_page = WRITEBALE_BASE . '/maintenance.html'; $query_string = ''; // $this->isAdmin ? '' : '?next_template=' . kUtil::escape($_SERVER['REQUEST_URI'], kUtil::ESCAPE_URL); if ( file_exists(FULL_PATH . $maintenance_page) ) { header('Location: ' . BASE_PATH . $maintenance_page . $query_string); exit; } } /** * Actually runs the parser against current template and stores parsing result * * This method gets 't' variable passed to the script, loads the template given in 't' variable and * parses it. The result is store in {@link $this->HTML} property. * * @return void * @access public */ public function Run() { // process maintenance mode redirect: begin $maintenance_mode = $this->getMaintenanceMode(); if ( $maintenance_mode == MaintenanceMode::HARD ) { $this->redirectToMaintenance(); } elseif ( $maintenance_mode == MaintenanceMode::SOFT ) { $maintenance_template = $this->isAdmin ? 'login' : $this->ConfigValue('SoftMaintenanceTemplate'); if ( $this->GetVar('t') != $maintenance_template ) { $redirect_params = Array (); if ( !$this->isAdmin ) { $redirect_params['next_template'] = $_SERVER['REQUEST_URI']; } $this->Redirect($maintenance_template, $redirect_params); } } // process maintenance mode redirect: end if ( defined('DEBUG_MODE') && $this->isDebugMode() && kUtil::constOn('DBG_PROFILE_MEMORY') ) { $this->Debugger->appendMemoryUsage('Application before Run:'); } if ( $this->isAdminUser ) { // for permission checking in events & templates $this->LinkVar('module'); // for common configuration templates $this->LinkVar('module_key'); // for common search templates $this->LinkVar('section'); // for common configuration templates if ( $this->GetVar('m_opener') == 'p' ) { $this->LinkVar('main_prefix'); // window prefix, that opened selector $this->LinkVar('dst_field'); // field to set value choosed in selector } if ( $this->GetVar('ajax') == 'yes' && !$this->GetVar('debug_ajax') ) { // hide debug output from ajax requests automatically kUtil::safeDefine('DBG_SKIP_REPORTING', 1); // safeDefine, because debugger also defines it } } elseif ( $this->GetVar('admin') ) { + /** @var Session $admin_session */ $admin_session = $this->recallObject('Session.admin'); - /* @var $admin_session Session */ // store Admin Console User's ID to Front-End's session for cross-session permission checks $this->StoreVar('admin_user_id', (int)$admin_session->RecallVar('user_id')); if ( $this->CheckAdminPermission('CATEGORY.MODIFY', 0, $this->getBaseCategory()) ) { // user can edit cms blocks (when viewing front-end through admin's frame) $editing_mode = $this->GetVar('editing_mode'); define('EDITING_MODE', $editing_mode ? $editing_mode : EDITING_MODE_BROWSE); } } kUtil::safeDefine('EDITING_MODE', ''); // user can't edit anything $this->Phrases->setPhraseEditing(); $this->EventManager->ProcessRequest(); $this->InitParser(); $t = $this->GetVar('render_template', $this->GetVar('t')); if ( !$this->TemplatesCache->TemplateExists($t) && !$this->isAdmin ) { + /** @var CategoriesEventHandler $cms_handler */ $cms_handler = $this->recallObject('st_EventHandler'); - /* @var $cms_handler CategoriesEventHandler */ $t = ltrim($cms_handler->GetDesignTemplate(), '/'); if ( defined('DEBUG_MODE') && $this->isDebugMode() ) { $this->Debugger->appendHTML('Design Template: ' . $t . '; CategoryID: ' . $this->GetVar('m_cat_id')); } } /*else { $cms_handler->SetCatByTemplate(); }*/ if ( defined('DEBUG_MODE') && $this->isDebugMode() && kUtil::constOn('DBG_PROFILE_MEMORY') ) { $this->Debugger->appendMemoryUsage('Application before Parsing:'); } $this->HTML = $this->Parser->Run($t); if ( defined('DEBUG_MODE') && $this->isDebugMode() && kUtil::constOn('DBG_PROFILE_MEMORY') ) { $this->Debugger->appendMemoryUsage('Application after Parsing:'); } } /** * Returns console application. * * @return ConsoleApplication */ public function getConsoleApplication() { return $this->makeClass('InPortal\Core\kernel\Console\ConsoleApplication'); } /** * Replaces current rendered template with given one. * * @param string|null $template Template. * * @return void */ public function QuickRun($template) { /** @var kThemesHelper $themes_helper */ $themes_helper = $this->recallObject('ThemesHelper'); // Set Web Request variables to affect link building on template itself. $this->SetVar('t', $template); $this->SetVar('m_cat_id', $themes_helper->getPageByTemplate($template)); $this->SetVar('passed', 'm'); // Replace current page content with given template. $this->InitParser(); $this->Parser->Clear(); $this->HTML = $this->Parser->Run($template); } /** * Performs template parser/cache initialization * * @param bool|string $theme_name * @return void * @access public */ public function InitParser($theme_name = false) { if ( !is_object($this->Parser) ) { $this->Parser = $this->recallObject('NParser'); $this->TemplatesCache = $this->recallObject('TemplatesCache'); } $this->TemplatesCache->forceThemeName = $theme_name; } /** * Send the parser results to browser * * Actually send everything stored in {@link $this->HTML}, to the browser by echoing it. * * @return void * @access public */ public function Done() { $this->HandleEvent(new kEvent('adm:OnBeforeShutdown')); $debug_mode = defined('DEBUG_MODE') && $this->isDebugMode(); if ( $debug_mode ) { if ( kUtil::constOn('DBG_PROFILE_MEMORY') ) { $this->Debugger->appendMemoryUsage('Application before Done:'); } $this->Session->SaveData(); // adds session data to debugger report $this->HTML = ob_get_clean() . $this->HTML . $this->Debugger->printReport(true); } else { // send "Set-Cookie" header before any output is made $this->Session->SetSession(); $this->HTML = ob_get_clean() . $this->HTML; } $this->_outputPage(); $this->cacheManager->UpdateApplicationCache(); if ( !$debug_mode ) { $this->Session->SaveData(); } $this->EventManager->runScheduledTasks(); if ( defined('DBG_CAPTURE_STATISTICS') && DBG_CAPTURE_STATISTICS && !$this->isAdmin ) { $this->_storeStatistics(); } } /** * Outputs generated page content to end-user * * @return void * @access protected */ protected function _outputPage() { $this->setContentType(); ob_start(); if ( $this->UseOutputCompression() ) { $compression_level = $this->ConfigValue('OutputCompressionLevel'); if ( !$compression_level || $compression_level < 0 || $compression_level > 9 ) { $compression_level = 7; } header('Content-Encoding: gzip'); echo gzencode($this->HTML, $compression_level); } else { // when gzip compression not used connection won't be closed early! echo $this->HTML; } // send headers to tell the browser to close the connection header('Content-Length: ' . ob_get_length()); header('Connection: close'); // flush all output ob_end_flush(); if ( ob_get_level() ) { ob_flush(); } flush(); // close current session if ( session_id() ) { session_write_close(); } } /** * Stores script execution statistics to database * * @return void * @access protected */ protected function _storeStatistics() { global $start; $script_time = microtime(true) - $start; $query_statistics = $this->Conn->getQueryStatistics(); // time & count $sql = 'SELECT * FROM ' . TABLE_PREFIX . 'StatisticsCapture WHERE TemplateName = ' . $this->Conn->qstr($this->GetVar('t')); $data = $this->Conn->GetRow($sql); if ( $data ) { $this->_updateAverageStatistics($data, 'ScriptTime', $script_time); $this->_updateAverageStatistics($data, 'SqlTime', $query_statistics['time']); $this->_updateAverageStatistics($data, 'SqlCount', $query_statistics['count']); $data['Hits']++; $data['LastHit'] = time(); $this->Conn->doUpdate($data, TABLE_PREFIX . 'StatisticsCapture', 'StatisticsId = ' . $data['StatisticsId']); } else { $data['ScriptTimeMin'] = $data['ScriptTimeAvg'] = $data['ScriptTimeMax'] = $script_time; $data['SqlTimeMin'] = $data['SqlTimeAvg'] = $data['SqlTimeMax'] = $query_statistics['time']; $data['SqlCountMin'] = $data['SqlCountAvg'] = $data['SqlCountMax'] = $query_statistics['count']; $data['TemplateName'] = $this->GetVar('t'); $data['Hits'] = 1; $data['LastHit'] = time(); $this->Conn->doInsert($data, TABLE_PREFIX . 'StatisticsCapture'); } } /** * Calculates average time for statistics * * @param Array $data * @param string $field_prefix * @param float $current_value * @return void * @access protected */ protected function _updateAverageStatistics(&$data, $field_prefix, $current_value) { $data[$field_prefix . 'Avg'] = (($data['Hits'] * $data[$field_prefix . 'Avg']) + $current_value) / ($data['Hits'] + 1); if ( $current_value < $data[$field_prefix . 'Min'] ) { $data[$field_prefix . 'Min'] = $current_value; } if ( $current_value > $data[$field_prefix . 'Max'] ) { $data[$field_prefix . 'Max'] = $current_value; } } /** * Remembers slow query SQL and execution time into log * * @param string $slow_sql * @param int $time * @return void * @access public */ public function logSlowQuery($slow_sql, $time) { $query_crc = kUtil::crc32($slow_sql); $sql = 'SELECT * FROM ' . TABLE_PREFIX . 'SlowSqlCapture WHERE QueryCrc = ' . $query_crc; $data = $this->Conn->Query($sql, null, true); if ( $data ) { $this->_updateAverageStatistics($data, 'Time', $time); $template_names = explode(',', $data['TemplateNames']); array_push($template_names, $this->GetVar('t')); $data['TemplateNames'] = implode(',', array_unique($template_names)); $data['Hits']++; $data['LastHit'] = time(); $this->Conn->doUpdate($data, TABLE_PREFIX . 'SlowSqlCapture', 'CaptureId = ' . $data['CaptureId']); } else { $data['TimeMin'] = $data['TimeAvg'] = $data['TimeMax'] = $time; $data['SqlQuery'] = $slow_sql; $data['QueryCrc'] = $query_crc; $data['TemplateNames'] = $this->GetVar('t'); $data['Hits'] = 1; $data['LastHit'] = time(); $this->Conn->doInsert($data, TABLE_PREFIX . 'SlowSqlCapture'); } } /** * Checks if output compression options is available * * @return bool * @access protected */ protected function UseOutputCompression() { if ( kUtil::constOn('IS_INSTALL') || kUtil::constOn('DBG_ZEND_PRESENT') || kUtil::constOn('SKIP_OUT_COMPRESSION') ) { return false; } $accept_encoding = isset($_SERVER['HTTP_ACCEPT_ENCODING']) ? $_SERVER['HTTP_ACCEPT_ENCODING'] : ''; return $this->ConfigValue('UseOutputCompression') && function_exists('gzencode') && strstr($accept_encoding, 'gzip'); } // Facade /** * Returns current session id (SID) * * @return int * @access public */ public function GetSID() { + /** @var Session $session */ $session = $this->recallObject('Session'); - /* @var $session Session */ return $session->GetID(); } /** * Destroys current session * * @return void * @access public * @see UserHelper::logoutUser() */ public function DestroySession() { + /** @var Session $session */ $session = $this->recallObject('Session'); - /* @var $session Session */ $session->Destroy(); } /** * Returns variable passed to the script as GET/POST/COOKIE * * @param string $name Name of variable to retrieve * @param mixed $default default value returned in case if variable not present * @return mixed * @access public */ public function GetVar($name, $default = false) { return isset($this->HttpQuery->_Params[$name]) ? $this->HttpQuery->_Params[$name] : $default; } /** * Removes forceful escaping done to the variable upon Front-End submission. * * @param string|array $value Value. * * @return string|array * @see kHttpQuery::StripSlashes * @todo Temporary method for marking problematic places to take care of, when forceful escaping will be removed. */ public function unescapeRequestVariable($value) { return $this->HttpQuery->unescapeRequestVariable($value); } /** * Returns variable passed to the script as $type * * @param string $name Name of variable to retrieve * @param string $type Get/Post/Cookie * @param mixed $default default value returned in case if variable not present * @return mixed * @access public */ public function GetVarDirect($name, $type, $default = false) { // $type = ucfirst($type); $array = $this->HttpQuery->$type; return isset($array[$name]) ? $array[$name] : $default; } /** * Returns ALL variables passed to the script as GET/POST/COOKIE * * @return Array * @access public * @deprecated */ public function GetVars() { return $this->HttpQuery->GetParams(); } /** * Set the variable 'as it was passed to the script through GET/POST/COOKIE' * * This could be useful to set the variable when you know that * other objects would relay on variable passed from GET/POST/COOKIE * or you could use SetVar() / GetVar() pairs to pass the values between different objects.
* * @param string $var Variable name to set * @param mixed $val Variable value * @return void * @access public */ public function SetVar($var,$val) { $this->HttpQuery->Set($var, $val); } /** * Deletes kHTTPQuery variable * * @param string $var * @return void * @todo Think about method name */ public function DeleteVar($var) { $this->HttpQuery->Remove($var); } /** * Deletes Session variable * * @param string $var * @return void * @access public */ public function RemoveVar($var) { $this->Session->RemoveVar($var); } /** * Removes variable from persistent session * * @param string $var * @return void * @access public */ public function RemovePersistentVar($var) { $this->Session->RemovePersistentVar($var); } /** * Restores Session variable to it's db version * * @param string $var * @return void * @access public */ public function RestoreVar($var) { $this->Session->RestoreVar($var); } /** * Returns session variable value * * Return value of $var variable stored in Session. An optional default value could be passed as second parameter. * * @param string $var Variable name * @param mixed $default Default value to return if no $var variable found in session * @return mixed * @access public * @see Session::RecallVar() */ public function RecallVar($var,$default=false) { return $this->Session->RecallVar($var,$default); } /** * Returns variable value from persistent session * * @param string $var * @param mixed $default * @return mixed * @access public * @see Session::RecallPersistentVar() */ public function RecallPersistentVar($var, $default = false) { return $this->Session->RecallPersistentVar($var, $default); } /** * Stores variable $val in session under name $var * * Use this method to store variable in session. Later this variable could be recalled. * * @param string $var Variable name * @param mixed $val Variable value * @param bool $optional * @return void * @access public * @see kApplication::RecallVar() */ public function StoreVar($var, $val, $optional = false) { + /** @var Session $session */ $session = $this->recallObject('Session'); - /* @var $session Session */ $this->Session->StoreVar($var, $val, $optional); } /** * Stores variable to persistent session * * @param string $var * @param mixed $val * @param bool $optional * @return void * @access public */ public function StorePersistentVar($var, $val, $optional = false) { $this->Session->StorePersistentVar($var, $val, $optional); } /** * Stores default value for session variable * * @param string $var * @param string $val * @param bool $optional * @return void * @access public * @see Session::RecallVar() * @see Session::StoreVar() */ public function StoreVarDefault($var, $val, $optional = false) { + /** @var Session $session */ $session = $this->recallObject('Session'); - /* @var $session Session */ $this->Session->StoreVarDefault($var, $val, $optional); } /** * Links HTTP Query variable with session variable * * If variable $var is passed in HTTP Query it is stored in session for later use. If it's not passed it's recalled from session. * This method could be used for making sure that GetVar will return query or session value for given * variable, when query variable should overwrite session (and be stored there for later use).
* This could be used for passing item's ID into popup with multiple tab - * in popup script you just need to call LinkVar('id', 'current_id') before first use of GetVar('id'). * After that you can be sure that GetVar('id') will return passed id or id passed earlier and stored in session * * @param string $var HTTP Query (GPC) variable name * @param mixed $ses_var Session variable name * @param mixed $default Default variable value * @param bool $optional * @return void * @access public */ public function LinkVar($var, $ses_var = null, $default = '', $optional = false) { if ( !isset($ses_var) ) { $ses_var = $var; } if ( $this->GetVar($var) !== false ) { $this->StoreVar($ses_var, $this->GetVar($var), $optional); } else { $this->SetVar($var, $this->RecallVar($ses_var, $default)); } } /** * Returns variable from HTTP Query, or from session if not passed in HTTP Query * * The same as LinkVar, but also returns the variable value taken from HTTP Query if passed, or from session if not passed. * Returns the default value if variable does not exist in session and was not passed in HTTP Query * * @param string $var HTTP Query (GPC) variable name * @param mixed $ses_var Session variable name * @param mixed $default Default variable value * @return mixed * @access public * @see LinkVar */ public function GetLinkedVar($var, $ses_var = null, $default = '') { $this->LinkVar($var, $ses_var, $default); return $this->GetVar($var); } /** * Renders given tag and returns it's output * * @param string $prefix * @param string $tag * @param Array $params * @return mixed * @access public * @see kApplication::InitParser() */ public function ProcessParsedTag($prefix, $tag, $params) { + /** @var kDBTagProcessor $processor */ $processor = $this->Parser->GetProcessor($prefix); - /* @var $processor kDBTagProcessor */ return $processor->ProcessParsedTag($tag, $params, $prefix); } /** * Return object of IDBConnection interface * * Return object of IDBConnection interface already connected to the project database, configurable in config.php * * @return IDBConnection * @access public */ public function &GetADODBConnection() { return $this->Conn; } /** * Allows to parse given block name or include template * * @param Array $params Parameters to pass to block. Reserved parameter "name" used to specify block name. * @param bool $pass_params Forces to pass current parser params to this block/template. Use with caution, because you can accidentally pass "block_no_data" parameter. * @param bool $as_template * @return string * @access public */ public function ParseBlock($params, $pass_params = false, $as_template = false) { if ( substr($params['name'], 0, 5) == 'html:' ) { return substr($params['name'], 5); } return $this->Parser->ParseBlock($params, $pass_params, $as_template); } /** * Checks, that we have given block defined * * @param string $name * @return bool * @access public */ public function ParserBlockFound($name) { return $this->Parser->blockFound($name); } /** * Allows to include template with a given name and given parameters * * @param Array $params Parameters to pass to template. Reserved parameter "name" used to specify template name. * @return string * @access public */ public function IncludeTemplate($params) { return $this->Parser->IncludeTemplate($params, isset($params['is_silent']) ? 1 : 0); } /** * Return href for template * * @param string $t Template path * @param string $prefix index.php prefix - could be blank, 'admin' * @param Array $params * @param string $index_file * @return string */ public function HREF($t, $prefix = '', $params = Array (), $index_file = null) { return $this->UrlManager->HREF($t, $prefix, $params, $index_file); } /** * Returns theme template filename and it's corresponding page_id based on given seo template * * @param string $seo_template * @return string * @access public */ public function getPhysicalTemplate($seo_template) { return $this->UrlManager->getPhysicalTemplate($seo_template); } /** * Returns seo template by physical template * * @param string $physical_template * @return string * @access public */ public function getSeoTemplate($physical_template) { return $this->UrlManager->getSeoTemplate($physical_template); } /** * Returns template name, that corresponds with given virtual (not physical) page id * * @param int $page_id * @return string|bool * @access public */ public function getVirtualPageTemplate($page_id) { return $this->UrlManager->getVirtualPageTemplate($page_id); } /** * Returns section template for given physical/virtual template * * @param string $template * @param int $theme_id * @return string * @access public */ public function getSectionTemplate($template, $theme_id = null) { return $this->UrlManager->getSectionTemplate($template, $theme_id); } /** * Returns variables with values that should be passed through with this link + variable list * * @param Array $params * @return Array * @access public */ public function getPassThroughVariables(&$params) { return $this->UrlManager->getPassThroughVariables($params); } /** * Builds url * * @param string $t * @param Array $params * @param string $pass * @param bool $pass_events * @param bool $env_var * @return string * @access public */ public function BuildEnv($t, $params, $pass = 'all', $pass_events = false, $env_var = true) { return $this->UrlManager->plain->build($t, $params, $pass, $pass_events, $env_var); } /** * Process QueryString only, create * events, ids, based on config * set template name and sid in * desired application variables. * * @param string $env_var environment string value * @param string $pass_name * @return Array * @access public */ public function processQueryString($env_var, $pass_name = 'passed') { return $this->UrlManager->plain->parse($env_var, $pass_name); } /** * Parses rewrite url and returns parsed variables * * @param string $url * @param string $pass_name * @return Array * @access public */ public function parseRewriteUrl($url, $pass_name = 'passed') { return $this->UrlManager->rewrite->parse($url, $pass_name); } /** * Returns base part of all urls, build on website * * @param string $domain Domain override. * @param boolean $ssl_redirect Redirect to/from SSL. * * @return string */ public function BaseURL($domain = '', $ssl_redirect = null) { if ( $ssl_redirect === null ) { // stay on same encryption level return PROTOCOL . ($domain ? $domain : SERVER_NAME) . (defined('PORT') ? ':' . PORT : '') . BASE_PATH . '/'; } if ( $ssl_redirect ) { // going from http:// to https:// $protocol = 'https://'; $domain = $this->getSecureDomain(); } else { // going from https:// to http:// $protocol = 'http://'; $domain = $this->siteDomainField('DomainName'); if ( $domain === false ) { $domain = DOMAIN; // not on site domain } } return $protocol . $domain . (defined('PORT') ? ':' . PORT : '') . BASE_PATH . '/'; } /** * Returns secure domain. * * @return string */ public function getSecureDomain() { $ret = $this->isAdmin ? $this->ConfigValue('AdminSSLDomain') : false; if ( !$ret ) { $ssl_domain = $this->siteDomainField('SSLDomainName'); return strlen($ssl_domain) ? $ssl_domain : $this->ConfigValue('SSLDomain'); } return $ret; } /** * Redirects user to url, that's build based on given parameters * * @param string $t * @param Array $params * @param string $prefix * @param string $index_file * @return void * @access public */ public function Redirect($t = '', $params = Array(), $prefix = '', $index_file = null) { $js_redirect = getArrayValue($params, 'js_redirect'); if ( $t == '' || $t === true ) { $t = $this->GetVar('t'); } // pass prefixes and special from previous url if ( array_key_exists('js_redirect', $params) ) { unset($params['js_redirect']); } // allows to send custom responce code along with redirect header if ( array_key_exists('response_code', $params) ) { $response_code = (int)$params['response_code']; unset($params['response_code']); } else { $response_code = 302; // Found } if ( !array_key_exists('pass', $params) ) { $params['pass'] = 'all'; } if ( $this->GetVar('ajax') == 'yes' && $t == $this->GetVar('t') ) { // redirects to the same template as current $params['ajax'] = 'yes'; } $location = $this->HREF($t, $prefix, $params, $index_file); if ( $this->isDebugMode() && (kUtil::constOn('DBG_REDIRECT') || (kUtil::constOn('DBG_RAISE_ON_WARNINGS') && $this->Debugger->WarningCount)) ) { $this->Debugger->appendTrace(); echo 'Debug output above !!!
' . "\n"; if ( array_key_exists('HTTP_REFERER', $_SERVER) ) { echo 'Referer: ' . kUtil::escape($_SERVER['HTTP_REFERER'], kUtil::ESCAPE_HTML) . '
' . "\n"; } echo "Proceed to redirect:
{$location}
\n"; } else { if ( $js_redirect ) { // show "redirect" template instead of redirecting, // because "Set-Cookie" header won't work, when "Location" // header is used later $this->SetVar('t', 'redirect'); $this->SetVar('redirect_to', $location); // make all additional parameters available on "redirect" template too foreach ($params as $name => $value) { $this->SetVar($name, $value); } return; } else { if ( $this->GetVar('ajax') == 'yes' && ($t != $this->GetVar('t') || !$this->isSOPSafe($location, $t)) ) { // redirection to other then current template during ajax request OR SOP violation kUtil::safeDefine('DBG_SKIP_REPORTING', 1); echo '#redirect#' . $location; } elseif ( headers_sent() != '' ) { // some output occurred -> redirect using javascript echo ''; } else { // no output before -> redirect using HTTP header // header('HTTP/1.1 302 Found'); header('Location: ' . $location, true, $response_code); } } } // session expiration is called from session initialization, // that's why $this->Session may be not defined here + /** @var Session $session */ $session = $this->recallObject('Session'); - /* @var $session Session */ if ( $this->InitDone ) { // if redirect happened in the middle of application initialization don't call event, // that presumes that application was successfully initialized $this->HandleEvent(new kEvent('adm:OnBeforeShutdown')); } $session->SaveData(); ob_end_flush(); exit; } /** * Determines if real redirect should be made within AJAX request. * * @param string $url Location. * @param string $template Template. * * @return boolean * @link http://en.wikipedia.org/wiki/Same-origin_policy */ protected function isSOPSafe($url, $template) { $parsed_url = parse_url($url); if ( $parsed_url['scheme'] . '://' != PROTOCOL ) { return false; } if ( $parsed_url['host'] != SERVER_NAME ) { return false; } if ( defined('PORT') && isset($parsed_url['port']) && $parsed_url['port'] != PORT ) { return false; } return true; } /** * Returns translation of given label * * @param string $label * @param bool $allow_editing return translation link, when translation is missing on current language * @param bool $use_admin use current Admin Console language to translate phrase * @return string * @access public */ public function Phrase($label, $allow_editing = true, $use_admin = false) { return $this->Phrases->GetPhrase($label, $allow_editing, $use_admin); } /** * Replace language tags in exclamation marks found in text * * @param string $text * @param bool $force_escape force escaping, not escaping of resulting string * @return string * @access public */ public function ReplaceLanguageTags($text, $force_escape = null) { return $this->Phrases->ReplaceLanguageTags($text, $force_escape); } /** * Checks if user is logged in, and creates * user object if so. User object can be recalled * later using "u.current" prefix_special. Also you may * get user id by getting "u.current_id" variable. * * @return void * @access protected */ protected function ValidateLogin() { + /** @var Session $session */ $session = $this->recallObject('Session'); - /* @var $session Session */ $user_id = $session->GetField('PortalUserId'); if ( !$user_id && $user_id != USER_ROOT ) { $user_id = USER_GUEST; } $this->SetVar('u.current_id', $user_id); if ( !$this->isAdmin ) { // needed for "profile edit", "registration" forms ON FRONT ONLY $this->SetVar('u_id', $user_id); } $this->StoreVar('user_id', $user_id, $user_id == USER_GUEST); // storing Guest user_id (-2) is optional $this->isAdminUser = $this->isAdmin && $this->LoggedIn(); if ( $this->GetVar('expired') == 1 ) { // this parameter is set only from admin + /** @var UsersItem $user */ $user = $this->recallObject('u.login-admin', null, Array ('form_name' => 'login')); - /* @var $user UsersItem */ $user->SetError('UserLogin', 'session_expired', 'la_text_sess_expired'); } $this->HandleEvent(new kEvent('adm:OnLogHttpRequest')); if ( $user_id != USER_GUEST ) { // normal users + root $this->LoadPersistentVars(); } $user_timezone = $this->Session->GetField('TimeZone'); if ( $user_timezone ) { date_default_timezone_set($user_timezone); } } /** * Loads current user persistent session data * * @return void * @access public */ public function LoadPersistentVars() { $this->Session->LoadPersistentVars(); } /** * Returns configuration option value by name * * @param string $name * @return string * @access public */ public function ConfigValue($name) { return $this->cacheManager->ConfigValue($name); } /** * Changes value of individual configuration variable (+resets cache, when needed) * * @param string $name * @param string $value * @param bool $local_cache_only * @return string * @access public */ public function SetConfigValue($name, $value, $local_cache_only = false) { return $this->cacheManager->SetConfigValue($name, $value, $local_cache_only); } /** * Allows to process any type of event. * * @param kEvent $event Event. * * @return void */ public function HandleEvent(kEvent $event) { $this->EventManager->HandleEvent($event); } /** * Notifies event subscribers, that event has occured * * @param kEvent $event * @return void */ public function notifyEventSubscribers(kEvent $event) { $this->EventManager->notifySubscribers($event); } /** * Allows to process any type of event * * @param kEvent $event * @return bool * @access public */ public function eventImplemented(kEvent $event) { return $this->EventManager->eventImplemented($event); } /** * Registers new class in the factory * * @param string $real_class Real name of class as in class declaration * @param string $file Filename in what $real_class is declared * @param string $pseudo_class Name under this class object will be accessed using getObject method * @return void * @access public */ public function registerClass($real_class, $file, $pseudo_class = null) { $this->Factory->registerClass($real_class, $file, $pseudo_class); } /** * Unregisters existing class from factory * * @param string $real_class Real name of class as in class declaration * @param string $pseudo_class Name under this class object is accessed using getObject method * @return void * @access public */ public function unregisterClass($real_class, $pseudo_class = null) { $this->Factory->unregisterClass($real_class, $pseudo_class); } /** * Finds the absolute path to the file where the class is defined. * * @param string $class The name of the class. * * @return string|false */ public function findClassFile($class) { return $this->Factory->findClassFile($class); } /** * Add new scheduled task * * @param string $short_name name to be used to store last maintenance run info * @param string $event_string * @param int $run_schedule run schedule like for Cron * @param string $module * @param int $status * @access public */ public function registerScheduledTask($short_name, $event_string, $run_schedule, $module, $status = STATUS_ACTIVE) { $this->EventManager->registerScheduledTask($short_name, $event_string, $run_schedule, $module, $status); } /** * Registers Hook from subprefix event to master prefix event * * Pattern: Observer * * @param string $hook_event * @param string $do_event * @param int $mode * @param bool $conditional * @access public */ public function registerHook($hook_event, $do_event, $mode = hAFTER, $conditional = false) { $this->EventManager->registerHook($hook_event, $do_event, $mode, $conditional); } /** * Registers build event for given pseudo class * * @param string $pseudo_class * @param string $event_name * @access public */ public function registerBuildEvent($pseudo_class, $event_name) { $this->EventManager->registerBuildEvent($pseudo_class, $event_name); } /** * Allows one TagProcessor tag act as other TagProcessor tag * * @param Array $tag_info * @return void * @access public */ public function registerAggregateTag($tag_info) { + /** @var kArray $aggregator */ $aggregator = $this->recallObject('TagsAggregator', 'kArray'); - /* @var $aggregator kArray */ $tag_data = Array ( $tag_info['LocalPrefix'], $tag_info['LocalTagName'], getArrayValue($tag_info, 'LocalSpecial') ); $aggregator->SetArrayValue($tag_info['AggregateTo'], $tag_info['AggregatedTagName'], $tag_data); } /** * Returns object using params specified, creates it if is required * * @param string $name * @param string $pseudo_class * @param Array $event_params * @param Array $arguments * @return kBase */ public function recallObject($name, $pseudo_class = null, array $event_params = array(), array $arguments = array()) { /*if ( !$this->hasObject($name) && $this->isDebugMode() && ($name == '_prefix_here_') ) { // first time, when object with "_prefix_here_" prefix is accessed $this->Debugger->appendTrace(); }*/ return $this->Factory->getObject($name, $pseudo_class, $event_params, $arguments); } /** * Returns tag processor for prefix specified * * @param string $prefix * @return kDBTagProcessor * @access public */ public function recallTagProcessor($prefix) { $this->InitParser(); // because kDBTagProcesor is in NParser dependencies return $this->recallObject($prefix . '_TagProcessor'); } /** * Checks if object with prefix passes was already created in factory * * @param string $name object pseudo_class, prefix * @return bool * @access public */ public function hasObject($name) { return $this->Factory->hasObject($name); } /** * Removes object from storage by given name * * @param string $name Object's name in the Storage * @return void * @access public */ public function removeObject($name) { $this->Factory->DestroyObject($name); } /** * Get's real class name for pseudo class, includes class file and creates class instance * * Pattern: Factory Method * * @param string $pseudo_class * @param Array $arguments * @return kBase * @access public */ public function makeClass($pseudo_class, array $arguments = array()) { return $this->Factory->makeClass($pseudo_class, $arguments); } /** * Returns sub-classes of given ancestor class. * * @param string $ancestor_class Ancestor class. * @param boolean $concrete_only Return only non-abstract classes. * * @return array */ public function getSubClasses($ancestor_class, $concrete_only = true) { return $this->Factory->getSubClasses($ancestor_class, $concrete_only); } /** * Checks if application is in debug mode * * @param bool $check_debugger check if kApplication debugger is initialized too, not only for defined DEBUG_MODE constant * @return bool * @author Alex * @access public */ public function isDebugMode($check_debugger = true) { $debug_mode = defined('DEBUG_MODE') && DEBUG_MODE; if ($check_debugger) { $debug_mode = $debug_mode && is_object($this->Debugger); } return $debug_mode; } /** * Apply url rewriting used by mod_rewrite or not * * @param bool|null $ssl Force ssl link to be build * @return bool * @access public */ public function RewriteURLs($ssl = false) { // case #1,#4: // we want to create https link from http mode // we want to create https link from https mode // conditions: ($ssl || PROTOCOL == 'https://') && $this->ConfigValue('UseModRewriteWithSSL') // case #2,#3: // we want to create http link from https mode // we want to create http link from http mode // conditions: !$ssl && (PROTOCOL == 'https://' || PROTOCOL == 'http://') $allow_rewriting = (!$ssl && (PROTOCOL == 'https://' || PROTOCOL == 'http://')) // always allow mod_rewrite for http || // or allow rewriting for redirect TO httpS or when already in httpS (($ssl || PROTOCOL == 'https://') && $this->ConfigValue('UseModRewriteWithSSL')); // but only if it's allowed in config! return kUtil::constOn('MOD_REWRITE') && $allow_rewriting; } /** * Returns unit config for given prefix * * @param string $prefix * @return kUnitConfig * @access public */ public function getUnitConfig($prefix) { return $this->UnitConfigReader->getUnitConfig($prefix); } /** * Returns true if config exists and is allowed for reading * * @param string $prefix * @return bool */ public function prefixRegistred($prefix) { return $this->UnitConfigReader->prefixRegistered($prefix); } /** * Splits any mixing of prefix and * special into correct ones * * @param string $prefix_special * @return Array * @access public */ public function processPrefix($prefix_special) { return $this->Factory->processPrefix($prefix_special); } /** * Set's new event for $prefix_special * passed * * @param string $prefix_special * @param string $event_name * @return void * @access public */ public function setEvent($prefix_special, $event_name) { $this->EventManager->setEvent($prefix_special, $event_name); } /** * SQL Error Handler * * @param int $code * @param string $msg * @param string $sql * @return bool * @access public * @throws Exception * @deprecated */ public function handleSQLError($code, $msg, $sql) { return $this->_logger->handleSQLError($code, $msg, $sql); } /** * Returns & blocks next ResourceId available in system * * @return int * @access public */ public function NextResourceId() { $table_name = TABLE_PREFIX . 'IdGenerator'; $this->Conn->Query('LOCK TABLES ' . $table_name . ' WRITE'); $this->Conn->Query('UPDATE ' . $table_name . ' SET lastid = lastid + 1'); $id = $this->Conn->GetOne('SELECT lastid FROM ' . $table_name); if ( $id === false ) { $this->Conn->Query('INSERT INTO ' . $table_name . ' (lastid) VALUES (2)'); $id = 2; } $this->Conn->Query('UNLOCK TABLES'); return $id - 1; } /** * Returns genealogical main prefix for sub-table prefix passes * OR prefix, that has been found in REQUEST and some how is parent of passed sub-table prefix * * @param string $current_prefix * @param bool $real_top if set to true will return real topmost prefix, regardless of its id is passed or not * @return string * @access public */ public function GetTopmostPrefix($current_prefix, $real_top = false) { // 1. get genealogical tree of $current_prefix $prefixes = Array ($current_prefix); while ($parent_prefix = $this->getUnitConfig($current_prefix)->getParentPrefix()) { if ( !$this->prefixRegistred($parent_prefix) ) { // stop searching, when parent prefix is not registered break; } $current_prefix = $parent_prefix; array_unshift($prefixes, $current_prefix); } if ( $real_top ) { return $current_prefix; } // 2. find what if parent is passed $passed = explode(',', $this->GetVar('all_passed')); foreach ($prefixes as $a_prefix) { if ( in_array($a_prefix, $passed) ) { return $a_prefix; } } return $current_prefix; } /** * Triggers email event of type Admin * * @param string $email_template_name * @param int $to_user_id * @param array $send_params associative array of direct send params, possible keys: to_email, to_name, from_email, from_name, message, message_text * @return kEvent * @access public */ public function emailAdmin($email_template_name, $to_user_id = null, $send_params = Array ()) { return $this->_email($email_template_name, EmailTemplate::TEMPLATE_TYPE_ADMIN, $to_user_id, $send_params); } /** * Triggers email event of type User * * @param string $email_template_name * @param int $to_user_id * @param array $send_params associative array of direct send params, possible keys: to_email, to_name, from_email, from_name, message, message_text * @return kEvent * @access public */ public function emailUser($email_template_name, $to_user_id = null, $send_params = Array ()) { return $this->_email($email_template_name, EmailTemplate::TEMPLATE_TYPE_FRONTEND, $to_user_id, $send_params); } /** * Triggers general email event * * @param string $email_template_name * @param int $email_template_type (0 for User, 1 for Admin) * @param int $to_user_id * @param array $send_params associative array of direct send params, * possible keys: to_email, to_name, from_email, from_name, message, message_text * @return kEvent * @access protected */ protected function _email($email_template_name, $email_template_type, $to_user_id = null, $send_params = Array ()) { + /** @var kEmail $email */ $email = $this->makeClass('kEmail'); - /* @var $email kEmail */ if ( !$email->findTemplate($email_template_name, $email_template_type) ) { return false; } $email->setParams($send_params); return $email->send($to_user_id); } /** * Allows to check if user in this session is logged in or not * * @return bool * @access public */ public function LoggedIn() { // no session during expiration process return is_null($this->Session) ? false : $this->Session->LoggedIn(); } /** * Check current user permissions based on it's group permissions in specified category * * @param string $name permission name * @param int $cat_id category id, current used if not specified * @param int $type permission type {1 - system, 0 - per category} * @return int * @access public */ public function CheckPermission($name, $type = 1, $cat_id = null) { + /** @var kPermissionsHelper $perm_helper */ $perm_helper = $this->recallObject('PermissionsHelper'); - /* @var $perm_helper kPermissionsHelper */ return $perm_helper->CheckPermission($name, $type, $cat_id); } /** * Check current admin permissions based on it's group permissions in specified category * * @param string $name permission name * @param int $cat_id category id, current used if not specified * @param int $type permission type {1 - system, 0 - per category} * @return int * @access public */ public function CheckAdminPermission($name, $type = 1, $cat_id = null) { + /** @var kPermissionsHelper $perm_helper */ $perm_helper = $this->recallObject('PermissionsHelper'); - /* @var $perm_helper kPermissionsHelper */ return $perm_helper->CheckAdminPermission($name, $type, $cat_id); } /** * Set's any field of current visit * * @param string $field * @param mixed $value * @return void * @access public * @todo move to separate module */ public function setVisitField($field, $value) { if ( $this->isAdmin || !$this->ConfigValue('UseVisitorTracking') ) { // admin logins are not registered in visits list return; } + /** @var kDBItem $visit */ $visit = $this->recallObject('visits', null, Array ('raise_warnings' => 0)); - /* @var $visit kDBItem */ if ( $visit->isLoaded() ) { $visit->SetDBField($field, $value); $visit->Update(); } } /** * Allows to check if in-portal is installed * * @return bool * @access public */ public function isInstalled() { return $this->InitDone && (count($this->ModuleInfo) > 0); } /** * Allows to determine if module is installed & enabled * * @param string $module_name * @return bool * @access public */ public function isModuleEnabled($module_name) { return $this->findModule('Name', $module_name) !== false; } /** * Returns Window ID of passed prefix main prefix (in edit mode) * * @param string $prefix * @return int * @access public */ public function GetTopmostWid($prefix) { $top_prefix = $this->GetTopmostPrefix($prefix); $mode = $this->GetVar($top_prefix . '_mode'); return $mode != '' ? substr($mode, 1) : ''; } /** * Get temp table name * * @param string $table * @param mixed $wid * @return string * @access public */ public function GetTempName($table, $wid = '') { return $this->GetTempTablePrefix($wid) . $table; } /** * Builds temporary table prefix based on given window id * * @param string $wid * @return string * @access public */ public function GetTempTablePrefix($wid = '') { if ( preg_match('/prefix:(.*)/', $wid, $regs) ) { $wid = $this->GetTopmostWid($regs[1]); } return TABLE_PREFIX . 'ses_' . $this->GetSID() . ($wid ? '_' . $wid : '') . '_edit_'; } /** * Checks if given table is a temporary table * * @param string $table * @return bool * @access public */ public function IsTempTable($table) { static $cache = Array (); if ( !array_key_exists($table, $cache) ) { $cache[$table] = preg_match('/' . TABLE_PREFIX . 'ses_' . $this->GetSID() . '(_[\d]+){0,1}_edit_(.*)/', $table); } return (bool)$cache[$table]; } /** * Checks, that given prefix is in temp mode * * @param string $prefix * @param string $special * @return bool * @access public */ public function IsTempMode($prefix, $special = '') { $top_prefix = $this->GetTopmostPrefix($prefix); $var_names = Array ( $top_prefix, rtrim($top_prefix . '_' . $special, '_'), // from post rtrim($top_prefix . '.' . $special, '.'), // assembled locally ); $var_names = array_unique($var_names); $temp_mode = false; foreach ($var_names as $var_name) { $value = $this->GetVar($var_name . '_mode'); if ( $value && (substr($value, 0, 1) == 't') ) { $temp_mode = true; break; } } return $temp_mode; } /** * Return live table name based on temp table name * * @param string $temp_table * @return string */ public function GetLiveName($temp_table) { if ( preg_match('/' . TABLE_PREFIX . 'ses_' . $this->GetSID() . '(_[\d]+){0,1}_edit_(.*)/', $temp_table, $rets) ) { // cut wid from table end if any return $rets[2]; } else { return $temp_table; } } /** * Stops processing of user request and displays given message * * @param string $message * @access public */ public function ApplicationDie($message = '') { while ( ob_get_level() ) { ob_end_clean(); } if ( $this->isDebugMode() ) { $message .= $this->Debugger->printReport(true); } $this->HTML = $message; $this->_outputPage(); } /** * Returns comma-separated list of groups from given user * * @param int $user_id * @return string */ public function getUserGroups($user_id) { switch ($user_id) { case USER_ROOT: $user_groups = $this->ConfigValue('User_LoggedInGroup'); break; case USER_GUEST: $user_groups = $this->ConfigValue('User_LoggedInGroup') . ',' . $this->ConfigValue('User_GuestGroup'); break; default: $sql = 'SELECT GroupId FROM ' . TABLE_PREFIX . 'UserGroupRelations WHERE PortalUserId = ' . (int)$user_id; $res = $this->Conn->GetCol($sql); $user_groups = Array ($this->ConfigValue('User_LoggedInGroup')); if ( $res ) { $user_groups = array_merge($user_groups, $res); } $user_groups = implode(',', $user_groups); } return $user_groups; } /** * Allows to detect if page is browsed by spider (293 scheduled_tasks supported) * * @return bool * @access public */ /*public function IsSpider() { static $is_spider = null; if ( !isset($is_spider) ) { $user_agent = trim($_SERVER['HTTP_USER_AGENT']); $robots = file(FULL_PATH . '/core/robots_list.txt'); foreach ($robots as $robot_info) { $robot_info = explode("\t", $robot_info, 3); if ( $user_agent == trim($robot_info[2]) ) { $is_spider = true; break; } } } return $is_spider; }*/ /** * Allows to detect table's presence in database * * @param string $table_name * @param bool $force * @return bool * @access public */ public function TableFound($table_name, $force = false) { return $this->Conn->TableFound($table_name, $force); } /** * Returns counter value * * @param string $name counter name * @param Array $params counter parameters * @param string $query_name specify query name directly (don't generate from parameters) * @param bool $multiple_results * @return mixed * @access public */ public function getCounter($name, $params = Array (), $query_name = null, $multiple_results = false) { + /** @var kCountHelper $count_helper */ $count_helper = $this->recallObject('CountHelper'); - /* @var $count_helper kCountHelper */ return $count_helper->getCounter($name, $params, $query_name, $multiple_results); } /** * Resets counter, which are affected by one of specified tables * * @param string $tables comma separated tables list used in counting sqls * @return void * @access public */ public function resetCounters($tables) { if ( kUtil::constOn('IS_INSTALL') ) { return; } + /** @var kCountHelper $count_helper */ $count_helper = $this->recallObject('CountHelper'); - /* @var $count_helper kCountHelper */ $count_helper->resetCounters($tables); } /** * Sends XML header + optionally displays xml heading * * @param string|bool $xml_version * @return string * @access public * @author Alex */ public function XMLHeader($xml_version = false) { $this->setContentType('text/xml'); return $xml_version ? '' : ''; } /** * Returns category tree * * @param int $category_id * @return Array * @access public */ public function getTreeIndex($category_id) { $tree_index = $this->getCategoryCache($category_id, 'category_tree'); if ( $tree_index ) { $ret = Array (); list ($ret['TreeLeft'], $ret['TreeRight']) = explode(';', $tree_index); return $ret; } return false; } /** * Base category of all categories * Usually replaced category, with ID = 0 in category-related operations. * * @return int * @access public */ public function getBaseCategory() { // same, what $this->findModule('Name', 'Core', 'RootCat') does // don't cache while IS_INSTALL, because of kInstallToolkit::createModuleCategory and upgrade return $this->ModuleInfo['Core']['RootCat']; } /** * Deletes all data, that was cached during unit config parsing (excluding unit config locations) * * @param Array $config_variables * @access public */ public function DeleteUnitCache($config_variables = null) { $this->cacheManager->DeleteUnitCache($config_variables); } /** * Deletes cached section tree, used during permission checking and admin console tree display * * @return void * @access public */ public function DeleteSectionCache() { $this->cacheManager->DeleteSectionCache(); } /** * Sets data from cache to object * * @param Array $data * @access public */ public function setFromCache(&$data) { $this->Factory->setFromCache($data); $this->UnitConfigReader->setFromCache($data); $this->EventManager->setFromCache($data); $this->ReplacementTemplates = $data['Application.ReplacementTemplates']; $this->routers = $data['Application.Routers']; $this->ModuleInfo = $data['Application.ModuleInfo']; } /** * Gets object data for caching * The following caches should be reset based on admin interaction (adjusting config, enabling modules etc) * * @access public * @return Array */ public function getToCache() { return array_merge( $this->Factory->getToCache(), $this->UnitConfigReader->getToCache(), $this->EventManager->getToCache(), Array ( 'Application.ReplacementTemplates' => $this->ReplacementTemplates, 'Application.Routers' => $this->routers, 'Application.ModuleInfo' => $this->ModuleInfo, ) ); } public function delayUnitProcessing($method, $params) { $this->cacheManager->delayUnitProcessing($method, $params); } /** * Returns current maintenance mode state * * @param bool $check_ips * @return int * @access public */ public function getMaintenanceMode($check_ips = true) { $exception_ips = defined('MAINTENANCE_MODE_IPS') ? MAINTENANCE_MODE_IPS : ''; $setting_name = $this->isAdmin ? 'MAINTENANCE_MODE_ADMIN' : 'MAINTENANCE_MODE_FRONT'; if ( defined($setting_name) && constant($setting_name) > MaintenanceMode::NONE ) { $exception_ip = $check_ips ? kUtil::ipMatch($exception_ips) : false; if ( !$exception_ip ) { return constant($setting_name); } } return MaintenanceMode::NONE; } /** * Sets content type of the page * * @param string $content_type * @param bool $include_charset * @return void * @access public */ public function setContentType($content_type = 'text/html', $include_charset = null) { static $already_set = false; if ( $already_set ) { return; } $header = 'Content-type: ' . $content_type; if ( !isset($include_charset) ) { $include_charset = $content_type = 'text/html' || $content_type == 'text/plain' || $content_type = 'text/xml'; } if ( $include_charset ) { $header .= '; charset=' . CHARSET; } $already_set = true; header($header); } /** * Posts message to event log * * @param string $message * @param int $code * @param bool $write_now Allows further customization of log record by returning kLog object * @return bool|int|kLogger * @access public */ public function log($message, $code = null, $write_now = false) { $log = $this->_logger->prepare($message, $code)->addSource($this->_logger->createTrace(null, 1)); if ( $write_now ) { return $log->write(); } return $log; } /** * Deletes log with given id from database or disk, when database isn't available * * @param int $unique_id * @param int $storage_medium * @return void * @access public * @throws InvalidArgumentException */ public function deleteLog($unique_id, $storage_medium = kLogger::LS_AUTOMATIC) { $this->_logger->delete($unique_id, $storage_medium); } /** * Returns the client IP address. * * @return string The client IP address * @access public */ public function getClientIp() { return $this->HttpQuery->getClientIp(); } } Index: branches/5.3.x/core/kernel/managers/scheduled_task_manager.php =================================================================== --- branches/5.3.x/core/kernel/managers/scheduled_task_manager.php (revision 16518) +++ branches/5.3.x/core/kernel/managers/scheduled_task_manager.php (revision 16519) @@ -1,233 +1,233 @@ tasks = $data['EventManager.scheduledTasks']; } /** * Gets object data for caching * * @return Array * @access public */ public function getToCache() { return Array ( 'EventManager.scheduledTasks' => $this->tasks, ); } /** * Returns information about registered scheduled tasks * * @param bool $from_cache * @return Array * @access public */ public function getAll($from_cache = false) { static $scheduled_tasks = null; if ( $from_cache ) { return $this->tasks; } if ( !isset($scheduled_tasks) ) { $timeout_clause = 'LastRunStatus = ' . ScheduledTask::LAST_RUN_RUNNING . ' AND Timeout > 0 AND ' . time() . ' - LastRunOn > Timeout'; $sql = 'SELECT * FROM ' . $this->Application->getUnitConfig('scheduled-task')->getTableName() . ' WHERE (Status = ' . STATUS_ACTIVE . ') AND ((LastRunStatus != ' . ScheduledTask::LAST_RUN_RUNNING . ') OR (' . $timeout_clause . '))'; $scheduled_tasks = $this->Conn->Query($sql, 'Name'); } return $scheduled_tasks; } /** * Add new scheduled task * * @param string $short_name name to be used to store last maintenance run info * @param string $event_string * @param int $run_schedule run schedule like for Cron * @param string $module * @param int $status * @access public */ public function add($short_name, $event_string, $run_schedule, $module, $status = STATUS_ACTIVE) { $this->tasks[$short_name] = Array ( 'Event' => $event_string, 'RunSchedule' => $run_schedule, 'Module' => $module, 'Status' => $status ); } /** * Run registered scheduled tasks with specified event type * * @param bool $from_cron * @access public */ public function runAll($from_cron = false) { if ( defined('IS_INSTALL') ) { return ; } $use_cron = $this->Application->ConfigValue('RunScheduledTasksFromCron'); if ( ($use_cron && !$from_cron) || (!$use_cron && $from_cron) ) { // match execution place with one, set in config return ; } ignore_user_abort(true); set_time_limit(0); $events_source = $this->getAll(); $user_id = $this->Application->RecallVar('user_id'); $this->Application->StoreVar('user_id', USER_ROOT, true); // to prevent permission checking inside events, true for optional storage + /** @var SiteHelper $site_helper */ $site_helper = $this->Application->recallObject('SiteHelper'); - /* @var $site_helper SiteHelper */ $site_domain_id = $site_helper->getDomainByName('DomainName', DOMAIN); foreach ($events_source as $short_name => $event_data) { if ( $site_domain_id && $event_data['SiteDomainLimitation'] != '' ) { $site_domains = explode('|', substr($event_data['SiteDomainLimitation'], 1, -1)); if ( !in_array($site_domain_id, $site_domains) ) { // scheduled task isn't allowed on this site domain continue; } } // remember LastTimeoutOn only for events that are still running and will be reset if ( $event_data['LastRunStatus'] == ScheduledTask::LAST_RUN_RUNNING ) { $this->update($event_data, Array ('LastTimeoutOn' => time())); } $next_run = (int)$event_data['NextRunOn']; if ($next_run && ($next_run > time())) { continue; } $this->run($event_data); } $this->Application->StoreVar('user_id', $user_id, $user_id == USER_GUEST); } /** * Runs scheduled task based on given data * * @param Array $scheduled_task_data * @return bool * @access public */ public function run($scheduled_task_data) { $event = new kEvent($scheduled_task_data['Event']); if ( !$this->Application->prefixRegistred($event->Prefix) ) { // don't process scheduled tasks, left from disabled modules return false; } + /** @var kCronHelper $cron_helper */ $cron_helper = $this->Application->recallObject('kCronHelper'); - /* @var $cron_helper kCronHelper */ $start_time = time(); // remember, when scheduled task execution started $fields_hash = Array ( 'LastRunOn' => $start_time, 'LastRunStatus' => ScheduledTask::LAST_RUN_RUNNING, 'NextRunOn' => $cron_helper->getMatch($scheduled_task_data['RunSchedule'], $start_time), ); $this->update($scheduled_task_data, $fields_hash); $scheduled_task = $this->Application->recallObject('scheduled-task', null, Array ('skip_autoload' => true)); /* @var $scheduled_task kDBItem */ $scheduled_task->LoadFromHash($scheduled_task_data); $event->redirect = false; // The fake MasterEvent is needed so that $event can access $scheduled_task! $event->MasterEvent = new kEvent('scheduled-task:OnRun'); $this->Application->HandleEvent($event); $now = time(); $next_run = $cron_helper->getMatch($scheduled_task_data['RunSchedule'], $start_time); while ($next_run < $now) { // in case event execution took longer, then RunSchedule (don't use <=, because RunSchedule can be 0) $next_run = $cron_helper->getMatch($scheduled_task_data['RunSchedule'], $next_run); } // remember, when scheduled task execution ended $fields_hash = Array ( 'NextRunOn' => $next_run, 'RunTime' => $now - $start_time, 'LastRunStatus' => $event->status == kEvent::erSUCCESS ? ScheduledTask::LAST_RUN_SUCCEEDED : ScheduledTask::LAST_RUN_FAILED, ); $this->update($scheduled_task_data, $fields_hash); return true; } /** * Updates scheduled task record with latest changes about it's invocation progress * * @param Array $scheduled_task_data * @param Array $fields_hash * @return void * @access protected */ protected function update(&$scheduled_task_data, $fields_hash) { $this->Conn->doUpdate( $fields_hash, $this->Application->getUnitConfig('scheduled-task')->getTableName(), 'Name = ' . $this->Conn->qstr($scheduled_task_data['Name']) ); $scheduled_task_data = array_merge($scheduled_task_data, $fields_hash); } } Index: branches/5.3.x/core/kernel/managers/url_manager.php =================================================================== --- branches/5.3.x/core/kernel/managers/url_manager.php (revision 16518) +++ branches/5.3.x/core/kernel/managers/url_manager.php (revision 16519) @@ -1,500 +1,500 @@ plain = $this->Application->makeClass('kPlainUrlProcessor', Array (&$this)); } /** * Delay initialization of rewrite processor, since it uses site domains & http query * * @return void * @access public */ public function initRewrite() { if ( $this->rewriteReady ) { return; } $this->rewrite = $this->Application->recallObject('kRewriteUrlProcessor', null, Array (), Array (&$this)); $this->rewriteReady = true; } /** * Return href for template * * @param string $t Template path * @param string $prefix index.php prefix - could be blank, 'admin' * @param Array $params * @param string $index_file * @return string */ public function HREF($t, $prefix = '', $params = Array (), $index_file = null) { if ( !$t ) { // when template not specified, use current $t = $this->Application->GetVar('t'); } $t = preg_replace('/^Content\//i', '', $t); if ( substr($t, -4) == '.tpl' ) { // cut template extension (deprecated link format) $t = substr($t, 0, strlen($t) - 4); } if ( substr($t, 0, 3) == 'id:' ) { // link to structure page using it's id $params['m_cat_id'] = substr($t, 3); $t = $this->structureTemplateMapping[$t]; } if ( array_key_exists('use_section', $params) ) { $use_section = $params['use_section']; unset($params['use_section']); } if ( isset($use_section) && $use_section ) { $theme_id = isset($params['m_theme']) ? $params['m_theme'] : null; $t = $this->getSectionTemplate($t, $theme_id); } if ( preg_match('/external:(.*)/', $t, $regs) ) { // external url return $regs[1]; } if ( $this->Application->isAdmin && $prefix == '' ) { $prefix = ADMIN_DIRECTORY; } if ( $this->Application->isAdmin && $prefix == '_FRONT_END_' ) { $prefix = ''; } if ( isset($params['_auto_prefix_']) ) { unset($params['_auto_prefix_']); // this is parser-related param, do not need to pass it here } $ssl = isset($params['__SSL__']) ? $params['__SSL__'] : NULL; if ( $ssl !== NULL ) { + /** @var Session $session */ $session = $this->Application->recallObject('Session'); - /* @var $session Session */ $target_url = rtrim($this->Application->BaseURL('', $ssl), '/'); $cookie_url = trim($session->CookieDomain . $session->CookiePath, '/.'); // set session to GET_ONLY, to pass sid only if sid is REAL AND session is set if ( !preg_match('#' . preg_quote($cookie_url) . '#', $target_url) && $session->SessionSet ) { // when SSL<->NON-SSL redirect to different domain pass SID in url $session->SetMode(Session::smGET_ONLY); } } if ( isset($params['opener']) && $params['opener'] == 'u' ) { $ret = $this->processPopupClose($prefix, $params); if ( $ret !== false ) { return $ret; } else { //define('DBG_REDIRECT', 1); $t = $this->Application->GetVar('t'); } } $pass = isset($params['pass']) ? $params['pass'] : ''; // pass events with url $pass_events = false; if ( isset($params['pass_events']) ) { $pass_events = $params['pass_events']; unset($params['pass_events']); } $map_link = ''; if ( isset($params['anchor']) ) { $map_link = '#' . $params['anchor']; unset($params['anchor']); } $rewrite = true; if ( isset($params['__NO_REWRITE__']) ) { $rewrite = false; unset($params['__NO_REWRITE__']); } $force_rewrite = false; if ( isset($params['__MOD_REWRITE__']) ) { $force_rewrite = true; unset($params['__MOD_REWRITE__']); } $force_no_sid = false; if ( isset($params['__NO_SID__']) ) { $force_no_sid = true; unset($params['__NO_SID__']); } $domain = ''; if ( isset($params['domain']) ) { $domain = $params['domain']; unset($params['domain']); } // append pass through variables to each link to be build $params = array_merge($this->getPassThroughVariables($params), $params); $session = $this->Application->recallObject('Session'); if ( $session->NeedQueryString() && !$force_no_sid ) { $params['sid'] = $this->Application->GetSID(); } if ( $force_rewrite || ($this->Application->RewriteURLs($ssl) && $rewrite) ) { if ( !$this->rewriteReady ) { $this->initRewrite(); } $url = $this->rewrite->build($t, $params, $pass, $pass_events); } else { unset($params['pass_category']); // we don't need to pass it when mod_rewrite is off $index_file = $this->getIndexFile($prefix, $index_file, $params); $url = $index_file . '?' . $this->plain->build($t, $params, $pass, $pass_events); } if ( $prefix ) { $prefix = trim($prefix, '/') . '/'; } return $this->Application->BaseURL($domain, $ssl) . $prefix . $url . $map_link; } /** * Returns popup's parent window url and optionally removes it from opener stack * * @param string $prefix * @param Array $params * @return bool|string * @access protected */ protected function processPopupClose($prefix = '', $params = Array ()) { + /** @var kOpenerStack $opener_stack */ $opener_stack = $this->Application->makeClass('kOpenerStack'); - /* @var $opener_stack kOpenerStack */ if ( $opener_stack->isEmpty() ) { return false; } $ssl = isset($params['__SSL__']) ? $params['__SSL__'] : null; list($index_file, $env) = explode('|', $opener_stack->get(kOpenerStack::LAST_ELEMENT, true)); if ( $prefix ) { $prefix = trim($prefix, '/') . '/'; } $ret = $this->Application->BaseURL('', $ssl) . $prefix . $index_file . '?' . ENV_VAR_NAME . '=' . $env; if ( isset($params['m_opener']) && $params['m_opener'] == 'u' ) { $opener_stack->pop(); $opener_stack->save(true); if ( $opener_stack->isEmpty() ) { // remove popups last templates, because popup is closing now $this->Application->RemoveVar('last_template_' . $opener_stack->getWindowID()); $this->Application->RemoveVar('last_template_popup_' . $opener_stack->getWindowID()); // don't save popups last templates again :) $this->Application->SetVar('skip_last_template', 1); } // store window relations /*$window_relations = $this->Application->RecallVar('window_relations'); $window_relations = $window_relations ? unserialize($window_relations) : Array (); if (array_key_exists($wid, $window_relations)) { unset($window_relations[$wid]); $this->Application->StoreVar('window_relations', serialize($window_relations)); }*/ } return $ret; } /** * Returns variables with values that should be passed through with this link + variable list * * @param Array $params * @return Array * @access public */ public function getPassThroughVariables(&$params) { static $cached_pass_through = null; if ( isset($params['no_pass_through']) && $params['no_pass_through'] ) { unset($params['no_pass_through']); return Array (); } // because pass through is not changed during script run, then we can cache it if ( is_null($cached_pass_through) ) { $cached_pass_through = Array (); $pass_through = $this->Application->GetVar('pass_through'); if ( $pass_through ) { // names of variables to pass to each link $cached_pass_through['pass_through'] = $pass_through; $pass_through = explode(',', $pass_through); foreach ($pass_through as $pass_through_var) { $cached_pass_through[$pass_through_var] = $this->Application->GetVar($pass_through_var); } } } return $cached_pass_through; } /** * Returns index file, that could be passed as parameter to method, as parameter to tag and as constant or not passed at all * * @param string $prefix * @param string $index_file * @param Array $params * @return string * @access protected */ protected function getIndexFile($prefix, $index_file = null, &$params) { static $cache = Array (); if ( isset($params['index_file']) ) { $index_file = $params['index_file']; unset($params['index_file']); return $index_file; } if ( isset($index_file) ) { return $index_file; } if ( defined('INDEX_FILE') ) { return INDEX_FILE; } // detect index file only once for given script and $cut_prefix $php_self = $_SERVER['PHP_SELF']; $cut_prefix = BASE_PATH . '/' . trim($prefix, '/'); if ( isset($cache[$php_self . ':' . $cut_prefix]) ) { return $cache[$php_self . ':' . $cut_prefix]; } $cache[$php_self . ':' . $cut_prefix] = trim(preg_replace('/' . preg_quote($cut_prefix, '/') . '(.*)/', '\\1', $php_self), '/'); return $cache[$php_self . ':' . $cut_prefix]; } /** * Returns theme template filename and it's corresponding page_id based on given seo template * * @param string $seo_template * @return string * @access public */ public function getPhysicalTemplate($seo_template) { $physical_template = false; $found_templates = array_keys($this->structureTemplateMapping, $seo_template); foreach ($found_templates as $found_template) { if ( substr($found_template, 0, 3) == 'id:' ) { // exclude virtual templates continue; } // several templates matched (physical and sym-linked to it) $physical_template = $found_template; } if ( $physical_template === false ) { // physical template from ".smsignore" file return $seo_template; } list ($physical_template,) = explode(':', $physical_template, 2); // template_path:theme_id => seo_template return $physical_template; } /** * Returns seo template by physical template * * @param string $physical_template * @return string * @access public */ public function getSeoTemplate($physical_template) { $mapping_key = $physical_template . ':' . $this->Application->GetVar('m_theme'); return isset($this->structureTemplateMapping[$mapping_key]) ? $this->structureTemplateMapping[$mapping_key] : ''; } /** * Returns template name, that corresponds with given virtual (not physical) page id * * @param int $page_id * @return string|bool * @access public */ public function getVirtualPageTemplate($page_id) { return isset($this->structureTemplateMapping['id:' . $page_id]) ? $this->structureTemplateMapping['id:' . $page_id] : false; } /** * Returns section template for given physical/virtual template * * @param string $template * @param int $theme_id * @return string * @access public */ public function getSectionTemplate($template, $theme_id = null) { static $current_theme_id = null; if ( !isset($current_theme_id) ) { $current_theme_id = $this->Application->GetVar('m_theme'); } if ( !isset($theme_id) ) { $theme_id = $current_theme_id; } if ( array_key_exists($template . ':' . $theme_id, $this->structureTemplateMapping) ) { // structure template corresponding to given physical template return $this->structureTemplateMapping[$template . ':' . $theme_id]; } return $template; } /** * Loads template mapping for Front-End * * @return void * @access public */ public function LoadStructureTemplateMapping() { if (!$this->Application->isAdmin) { + /** @var CategoryHelper $category_helper */ $category_helper = $this->Application->recallObject('CategoryHelper'); - /* @var $category_helper CategoryHelper */ $this->structureTemplateMapping = $category_helper->getTemplateMapping(); } } /** * Removes tpl part from template name + resolved template ID to name * * @param string $default_template * @return string * @access public */ public function getTemplateName($default_template = '') { if ( $this->Application->GetVarDirect('t', 'Get') !== false ) { // template name is passed directly in url (GET method) $t = $this->Application->GetVarDirect('t', 'Get'); } elseif ( $this->Application->GetVar('env') && $this->Application->RewriteURLs() && $this->Application->GetVar('t') ) { // if t was set through env, even in mod_rewrite mode! $t = $this->Application->GetVar('t'); } else { $t = trim($default_template ? $default_template : 'index', '/'); } return trim(preg_replace('/\.tpl$/', '', $t), '/'); } /** * Show 404 page and exit * * @return void * @access public */ public function show404() { header('HTTP/1.0 404 Not Found'); $not_found = $this->Application->ConfigValue('ErrorTemplate'); $template = $not_found ? $not_found : 'error_notfound'; $this->Application->QuickRun($template); $this->Application->Done(); exit; } } Index: branches/5.3.x/core/kernel/managers/cache_manager.php =================================================================== --- branches/5.3.x/core/kernel/managers/cache_manager.php (revision 16518) +++ branches/5.3.x/core/kernel/managers/cache_manager.php (revision 16519) @@ -1,853 +1,853 @@ Array (), 'registerScheduledTask' => Array (), 'registerHook' => Array (), 'registerBuildEvent' => Array (), 'registerAggregateTag' => Array (), ); /** * Name of database table, where configuration settings are stored * * @var string * @access protected */ protected $settingTableName = ''; /** * Set's references to kApplication and DBConnection interface class instances * * @access public */ public function __construct() { parent::__construct(); $this->settingTableName = TABLE_PREFIX . 'SystemSettings'; if ( defined('IS_INSTALL') && IS_INSTALL ) { // table substitution required, so "root" can perform login to upgrade to 5.2.0, where setting table was renamed if ( !$this->Application->TableFound(TABLE_PREFIX . 'SystemSettings') ) { $this->settingTableName = TABLE_PREFIX . 'ConfigurationValues'; } } } /** * Creates caching manager instance * * @access public */ public function InitCache() { $this->cacheHandler = $this->Application->makeClass('kCache'); } /** * Returns cache key, used to cache phrase and configuration variable IDs used on current page * * @return string * @access protected */ protected function getCacheKey() { // TODO: maybe language part isn't required, since same phrase from different languages have one ID now return $this->Application->GetVar('t') . $this->Application->GetVar('m_theme') . $this->Application->GetVar('m_lang') . $this->Application->isAdmin; } /** * Loads phrases and configuration variables, that were used on this template last time * * @access public */ public function LoadApplicationCache() { $phrase_ids = $config_ids = Array (); $sql = 'SELECT PhraseList, ConfigVariables FROM ' . TABLE_PREFIX . 'PhraseCache WHERE Template = ' . $this->Conn->qstr( md5($this->getCacheKey()) ); $res = $this->Conn->GetRow($sql); if ($res) { if ( $res['PhraseList'] ) { $phrase_ids = explode(',', $res['PhraseList']); } if ( $res['ConfigVariables'] ) { $config_ids = array_diff( explode(',', $res['ConfigVariables']), $this->originalConfigIDs); } } $this->Application->Phrases->Init('phrases', '', null, $phrase_ids); $this->configIDs = $this->originalConfigIDs = $config_ids; $this->InitConfig(); } /** * Updates phrases and configuration variables, that were used on this template * * @access public */ public function UpdateApplicationCache() { $update = false; //something changed $update = $update || $this->Application->Phrases->NeedsCacheUpdate(); $update = $update || (count($this->configIDs) && $this->configIDs != $this->originalConfigIDs); if ($update) { $fields_hash = Array ( 'PhraseList' => implode(',', $this->Application->Phrases->Ids), 'CacheDate' => time(), 'Template' => md5( $this->getCacheKey() ), 'ConfigVariables' => implode(',', array_unique($this->configIDs)), ); $this->Conn->doInsert($fields_hash, TABLE_PREFIX . 'PhraseCache', 'REPLACE'); } } /** * Loads configuration variables, that were used on this template last time * * @access protected */ protected function InitConfig() { if (!$this->originalConfigIDs) { return ; } $sql = 'SELECT VariableValue, VariableName FROM ' . $this->settingTableName . ' WHERE VariableId IN (' . implode(',', $this->originalConfigIDs) . ')'; $config_variables = $this->Conn->GetCol($sql, 'VariableName'); $this->configVariables = array_merge($this->configVariables, $config_variables); } /** * Returns configuration option value by name * * @param string $name * @return string * @access public */ public function ConfigValue($name) { $site_domain_override = Array ( 'DefaultEmailSender' => 'AdminEmail', 'DefaultEmailRecipients' => 'DefaultEmailRecipients', ); if ( isset($site_domain_override[$name]) ) { $res = $this->Application->siteDomainField($site_domain_override[$name]); if ( $res ) { return $res; } } if ( array_key_exists($name, $this->configVariables) ) { return $this->configVariables[$name]; } if ( defined('IS_INSTALL') && IS_INSTALL && !$this->Application->TableFound($this->settingTableName, true) ) { return false; } $this->Conn->nextQueryCachable = true; $sql = 'SELECT VariableId, VariableValue FROM ' . $this->settingTableName . ' WHERE VariableName = ' . $this->Conn->qstr($name); $res = $this->Conn->GetRow($sql); if ( $res !== false ) { $this->configIDs[] = $res['VariableId']; $this->configVariables[$name] = $res['VariableValue']; return $res['VariableValue']; } trigger_error('Usage of undefined configuration variable "' . $name . '"', E_USER_NOTICE); return false; } /** * Changes value of individual configuration variable (+resets cache, when needed) * * @param string $name * @param string $value * @param bool $local_cache_only * @return string * @access public */ public function SetConfigValue($name, $value, $local_cache_only = false) { $this->configVariables[$name] = $value; if ( $local_cache_only ) { return; } $fields_hash = Array ('VariableValue' => $value); $this->Conn->doUpdate($fields_hash, $this->settingTableName, 'VariableName = ' . $this->Conn->qstr($name)); if ( array_key_exists($name, $this->originalConfigVariables) && $value != $this->originalConfigVariables[$name] ) { $this->DeleteUnitCache(); } } /** * Loads data, that was cached during unit config parsing * * @return bool * @access public */ public function LoadUnitCache() { if ( $this->Application->isCachingType(CACHING_TYPE_MEMORY) ) { $data = $this->Application->getCache('master:configs_parsed', false, CacheSettings::$unitCacheRebuildTime); } else { $data = $this->Application->getDBCache('configs_parsed', CacheSettings::$unitCacheRebuildTime); } if ( $data ) { $cache = unserialize($data); // 126 KB all modules unset($data); $this->Application->InitManagers(); $this->Application->setFromCache($cache); + /** @var kArray $aggregator */ $aggregator = $this->Application->recallObject('TagsAggregator', 'kArray'); - /* @var $aggregator kArray */ $aggregator->setFromCache($cache); $this->setFromCache($cache); unset($cache); return true; } return false; } /** * Empties factory and event manager cache (without storing changes) */ public function EmptyUnitCache() { // maybe discover keys automatically from corresponding classes $cache_keys = Array ( 'Factory.Files', 'Factory.ClassInfo', 'Factory.ClassTree', 'Factory.Namespaces', 'Factory.realClasses', 'ConfigReader.prefixFiles', 'ConfigCloner.clones', 'EventManager.beforeHooks', 'EventManager.afterHooks', 'EventManager.scheduledTasks', 'EventManager.buildEvents', 'Application.ReplacementTemplates', 'Application.Routers', 'Application.ModuleInfo', 'Application.ConfigHash', 'Application.ConfigCacheIds', ); $empty_cache = Array (); foreach ($cache_keys as $cache_key) { $empty_cache[$cache_key] = Array (); } $this->Application->setFromCache($empty_cache); $this->setFromCache($empty_cache); // Otherwise kModulesHelper indirectly used from includeConfigFiles won't work. $this->Application->RegisterDefaultClasses(); $this->Application->RegisterDefaultBuildEvents(); } /** * Updates data, that was parsed from unit configs this time * * @access public */ public function UpdateUnitCache() { + /** @var kArray $aggregator */ $aggregator = $this->Application->recallObject('TagsAggregator', 'kArray'); - /* @var $aggregator kArray */ $this->preloadConfigVars(); // preloading will put to cache $cache = array_merge( $this->Application->getToCache(), $aggregator->getToCache(), $this->getToCache() ); $cache_rebuild_by = SERVER_NAME . ' (' . $this->Application->getClientIp() . ') - ' . date('d/m/Y H:i:s'); if ($this->Application->isCachingType(CACHING_TYPE_MEMORY)) { $this->Application->setCache('master:configs_parsed', serialize($cache)); $this->Application->setCache('master:last_cache_rebuild', $cache_rebuild_by); } else { $this->Application->setDBCache('configs_parsed', serialize($cache)); $this->Application->setDBCache('last_cache_rebuild', $cache_rebuild_by); } } public function delayUnitProcessing($method, $params) { if ($this->Application->InitDone) { // init already done -> call immediately (happens during installation) $function = Array (&$this->Application, $method); call_user_func_array($function, $params); return ; } $this->temporaryCache[$method][] = $params; } public function applyDelayedUnitProcessing() { foreach ($this->temporaryCache as $method => $method_calls) { $function = Array (&$this->Application, $method); foreach ($method_calls as $method_call) { call_user_func_array($function, $method_call); } $this->temporaryCache[$method] = Array (); } } /** * Deletes all data, that was cached during unit config parsing (excluding unit config locations) * * @param Array $config_variables * @access public */ public function DeleteUnitCache($config_variables = null) { if ( isset($config_variables) && !array_intersect(array_keys($this->originalConfigVariables), $config_variables) ) { // prevent cache reset, when given config variables are not in unit cache return; } if ( $this->Application->isCachingType(CACHING_TYPE_MEMORY) ) { $this->Application->rebuildCache('master:configs_parsed', kCache::REBUILD_LATER, CacheSettings::$unitCacheRebuildTime); } else { $this->rebuildDBCache('configs_parsed', kCache::REBUILD_LATER, CacheSettings::$unitCacheRebuildTime); } } /** * Deletes cached section tree, used during permission checking and admin console tree display * * @return void * @access public */ public function DeleteSectionCache() { if ( $this->Application->isCachingType(CACHING_TYPE_MEMORY) ) { $this->Application->rebuildCache('master:sections_parsed', kCache::REBUILD_LATER, CacheSettings::$sectionsParsedRebuildTime); } else { $this->rebuildDBCache('sections_parsed', kCache::REBUILD_LATER, CacheSettings::$sectionsParsedRebuildTime); } } /** * Preloads 21 widely used configuration variables, so they will get to cache for sure * * @access protected */ protected function preloadConfigVars() { $config_vars = Array ( // session related 'SessionTimeout', 'SessionCookieName', 'SessionCookieDomains', 'SessionBrowserSignatureCheck', 'SessionIPAddressCheck', 'CookieSessions', 'KeepSessionOnBrowserClose', 'User_GuestGroup', 'User_LoggedInGroup', 'RegistrationUsernameRequired', // output related 'UseModRewrite', 'UseContentLanguageNegotiation', 'UseOutputCompression', 'OutputCompressionLevel', 'Config_Site_Time', 'SystemTagCache', 'DefaultGridPerPage', // tracking related 'UseChangeLog', 'UseVisitorTracking', 'ModRewriteUrlEnding', 'ForceModRewriteUrlEnding', 'RunScheduledTasksFromCron', ); $escaped_config_vars = $this->Conn->qstrArray($config_vars); $sql = 'SELECT VariableId, VariableName, VariableValue FROM ' . $this->settingTableName . ' WHERE VariableName IN (' . implode(',', $escaped_config_vars) . ')'; $data = $this->Conn->Query($sql, 'VariableId'); foreach ($data as $variable_id => $variable_info) { $this->configIDs[] = $variable_id; $this->configVariables[ $variable_info['VariableName'] ] = $variable_info['VariableValue']; } } /** * Sets data from cache to object * * Used for cases, when ConfigValue is called before LoadApplicationCache method (e.g. session init, url engine init) * * @param Array $data * @access public */ public function setFromCache(&$data) { $this->configVariables = $this->originalConfigVariables = $data['Application.ConfigHash']; $this->configIDs = $this->originalConfigIDs = $data['Application.ConfigCacheIds']; } /** * Gets object data for caching * The following caches should be reset based on admin interaction (adjusting config, enabling modules etc) * * @access public * @return Array */ public function getToCache() { return Array ( 'Application.ConfigHash' => $this->configVariables, 'Application.ConfigCacheIds' => $this->configIDs, // not in use, since it only represents template specific values, not global ones // 'Application.Caches.ConfigVariables' => $this->originalConfigIDs, ); } /** * Returns caching type (none, memory, temporary) * * @param int $caching_type * @return bool * @access public */ public function isCachingType($caching_type) { return $this->cacheHandler->getCachingType() == $caching_type; } /** * Returns cached $key value from cache named $cache_name * * @param int $key key name from cache * @param bool $store_locally store data locally after retrieved * @param int $max_rebuild_seconds * @return mixed * @access public */ public function getCache($key, $store_locally = true, $max_rebuild_seconds = 0) { return $this->cacheHandler->getCache($key, $store_locally, $max_rebuild_seconds); } /** * Stores new $value in cache with $key name * * @param int $key key name to add to cache * @param mixed $value value of cached record * @param int $expiration when value expires (0 - doesn't expire) * @return bool * @access public */ public function setCache($key, $value, $expiration = 0) { return $this->cacheHandler->setCache($key, $value, $expiration); } /** * Stores new $value in cache with $key name (only if not there already) * * @param int $key key name to add to cache * @param mixed $value value of cached record * @param int $expiration when value expires (0 - doesn't expire) * @return bool * @access public */ public function addCache($key, $value, $expiration = 0) { return $this->cacheHandler->addCache($key, $value, $expiration); } /** * Sets rebuilding mode for given cache * * @param string $name * @param int $mode * @param int $max_rebuilding_time * @return bool * @access public */ public function rebuildCache($name, $mode = null, $max_rebuilding_time = 0) { return $this->cacheHandler->rebuildCache($name, $mode, $max_rebuilding_time); } /** * Deletes key from cache * * @param string $key * @return void * @access public */ public function deleteCache($key) { $this->cacheHandler->delete($key); } /** * Reset's all memory cache at once * * @return void * @access public */ public function resetCache() { $this->cacheHandler->reset(); } /** * Returns value from database cache * * @param string $name key name * @param int $max_rebuild_seconds * @return mixed * @access public */ public function getDBCache($name, $max_rebuild_seconds = 0) { // no serials in cache key OR cache is outdated $rebuilding = false; $wait_seconds = $max_rebuild_seconds; while (true) { $cached_data = $this->_getDBCache(Array ($name, $name . '_rebuilding', $name . '_rebuild')); if ( $cached_data[$name . '_rebuild'] ) { // cache rebuild requested -> rebuild now $this->deleteDBCache($name . '_rebuild'); if ( $this->rebuildDBCache($name, kCache::REBUILD_NOW, $max_rebuild_seconds, '[M1]') ) { return false; } } $cache = $cached_data[$name]; $rebuilding = $cached_data[$name . '_rebuilding']; if ( ($cache === false) && (!$rebuilding || $wait_seconds == 0) ) { // cache missing and nobody rebuilding it -> rebuild; enough waiting for cache to be ready $this->rebuildDBCache($name, kCache::REBUILD_NOW, $max_rebuild_seconds, '[M2' . ($rebuilding ? 'R' : '!R') . ',WS=' . $wait_seconds . ']'); return false; } elseif ( $cache !== false ) { // cache present -> return it $this->cacheHandler->storeStatistics($name, $rebuilding ? 'h' : 'H'); return $cache; } $wait_seconds -= kCache::WAIT_STEP; sleep(kCache::WAIT_STEP); } $this->rebuildDBCache($name, kCache::REBUILD_NOW, $max_rebuild_seconds, '[M3' . ($rebuilding ? 'R' : '!R') . ',WS=' . $wait_seconds . ']'); return false; } /** * Returns value from database cache * * @param string|Array $names key name * @return mixed * @access protected */ protected function _getDBCache($names) { $res = Array (); $names = (array)$names; $this->Conn->nextQueryCachable = true; $sql = 'SELECT Data, Cached, LifeTime, VarName FROM ' . TABLE_PREFIX . 'SystemCache WHERE VarName IN (' . implode(',', $this->Conn->qstrArray($names)) . ')'; $cached_data = $this->Conn->Query($sql, 'VarName'); foreach ($names as $name) { if ( !isset($cached_data[$name]) ) { $res[$name] = false; continue; } $lifetime = (int)$cached_data[$name]['LifeTime']; // in seconds if ( ($lifetime > 0) && ($cached_data[$name]['Cached'] + $lifetime < time()) ) { // delete expired $this->Conn->nextQueryCachable = true; $sql = 'DELETE FROM ' . TABLE_PREFIX . 'SystemCache WHERE VarName = ' . $this->Conn->qstr($name); $this->Conn->Query($sql); $res[$name] = false; continue; } $res[$name] = $cached_data[$name]['Data']; } return count($res) == 1 ? array_pop($res) : $res; } /** * Sets value to database cache * * @param string $name * @param mixed $value * @param int|bool $expiration * @return void * @access public */ public function setDBCache($name, $value, $expiration = false) { $this->cacheHandler->storeStatistics($name, 'WU'); $this->deleteDBCache($name . '_rebuilding'); $this->_setDBCache($name, $value, $expiration); } /** * Sets value to database cache * * @param string $name * @param mixed $value * @param int|bool $expiration * @param string $insert_type * @return bool * @access protected */ protected function _setDBCache($name, $value, $expiration = false, $insert_type = 'REPLACE') { if ( (int)$expiration <= 0 ) { $expiration = -1; } $fields_hash = Array ( 'VarName' => $name, 'Data' => &$value, 'Cached' => time(), 'LifeTime' => (int)$expiration, ); $this->Conn->nextQueryCachable = true; return $this->Conn->doInsert($fields_hash, TABLE_PREFIX . 'SystemCache', $insert_type); } /** * Sets value to database cache * * @param string $name * @param mixed $value * @param int|bool $expiration * @return bool * @access protected */ protected function _addDBCache($name, $value, $expiration = false) { return $this->_setDBCache($name, $value, $expiration, 'INSERT'); } /** * Sets rebuilding mode for given cache * * @param string $name * @param int $mode * @param int $max_rebuilding_time * @param string $miss_type * @return bool * @access public */ public function rebuildDBCache($name, $mode = null, $max_rebuilding_time = 0, $miss_type = 'M') { if ( !isset($mode) || $mode == kCache::REBUILD_NOW ) { $this->cacheHandler->storeStatistics($name, $miss_type); if ( !$max_rebuilding_time ) { return true; } if ( !$this->_addDBCache($name . '_rebuilding', 1, $max_rebuilding_time) ) { $this->cacheHandler->storeStatistics($name, 'l'); return false; } $this->deleteDBCache($name . '_rebuild'); $this->cacheHandler->storeStatistics($name, 'L'); } elseif ( $mode == kCache::REBUILD_LATER ) { $this->_setDBCache($name . '_rebuild', 1, 0); $this->deleteDBCache($name . '_rebuilding'); } return true; } /** * Deletes key from database cache * * @param string $name * @return void * @access public */ public function deleteDBCache($name) { $sql = 'DELETE FROM ' . TABLE_PREFIX . 'SystemCache WHERE VarName = ' . $this->Conn->qstr($name); $this->Conn->Query($sql); } /** * Increments serial based on prefix and it's ID (optional) * * @param string $prefix * @param int $id ID (value of IDField) or ForeignKeyField:ID * @param bool $increment * @return string * @access public */ public function incrementCacheSerial($prefix, $id = null, $increment = true) { $pascal_case_prefix = implode('', array_map('ucfirst', explode('-', $prefix))); $serial_name = $pascal_case_prefix . (isset($id) ? 'IDSerial:' . $id : 'Serial'); if ($increment) { if (defined('DEBUG_MODE') && DEBUG_MODE && $this->Application->isDebugMode()) { $this->Application->Debugger->appendHTML('Incrementing serial: ' . $serial_name . '.'); } $this->setCache($serial_name, (int)$this->getCache($serial_name) + 1); if (!defined('IS_INSTALL') || !IS_INSTALL) { // delete cached mod-rewrite urls related to given prefix and id $delete_clause = isset($id) ? $prefix . ':' . $id : $prefix; $sql = 'DELETE FROM ' . TABLE_PREFIX . 'CachedUrls WHERE Prefixes LIKE ' . $this->Conn->qstr('%|' . $delete_clause . '|%'); $this->Conn->Query($sql); } } return $serial_name; } /** * Returns cached category informaton by given cache name. All given category * information is recached, when at least one of 4 caches is missing. * * @param int $category_id * @param string $name cache name = {filenames, category_designs, category_tree} * @return string * @access public */ public function getCategoryCache($category_id, $name) { $serial_name = '[%CIDSerial:' . $category_id . '%]'; $cache_key = $name . $serial_name; $ret = $this->getCache($cache_key); if ($ret === false) { if (!$category_id) { // don't query database for "Home" category (ID = 0), because it doesn't exist in database return false; } // this allows to save 2 sql queries for each category $this->Conn->nextQueryCachable = true; $sql = 'SELECT NamedParentPath, CachedTemplate, TreeLeft, TreeRight FROM ' . TABLE_PREFIX . 'Categories WHERE CategoryId = ' . (int)$category_id; $category_data = $this->Conn->GetRow($sql); if ($category_data !== false) { // only direct links to category pages work (symlinks, container pages and so on won't work) $this->setCache('filenames' . $serial_name, $category_data['NamedParentPath']); $this->setCache('category_designs' . $serial_name, ltrim($category_data['CachedTemplate'], '/')); $this->setCache('category_tree' . $serial_name, $category_data['TreeLeft'] . ';' . $category_data['TreeRight']); } } return $this->getCache($cache_key); } } Index: branches/5.3.x/core/kernel/managers/subscription_manager.php =================================================================== --- branches/5.3.x/core/kernel/managers/subscription_manager.php (revision 16518) +++ branches/5.3.x/core/kernel/managers/subscription_manager.php (revision 16519) @@ -1,204 +1,204 @@ subscriptions[] = new kSubscriptionItem($fields); } /** * Detects if current user is subscribed to new posts in given topic * * @return bool * @access public */ public function subscribed() { foreach ($this->subscriptions as $subscription) { if ( !$subscription->getSubscription()->isLoaded() ) { - /* @var $subscription kSubscriptionItem */ + /** @var kSubscriptionItem $subscription */ return false; } } return true; } /** * Subscribes current user to new posts in a given topic * * @return bool * @access public */ public function subscribe() { foreach ($this->subscriptions as $subscription) { if ( !$subscription->subscribe() ) { - /* @var $subscription kSubscriptionItem */ + /** @var kSubscriptionItem $subscription */ return false; } } return true; } /** * Unsubscribes current user from reciving e-mails about new posts in a gvein topic * * @return bool * @access public */ public function unsubscribe() { foreach ($this->subscriptions as $subscription) { if ( !$subscription->unsubscribe() ) { - /* @var $subscription kSubscriptionItem */ + /** @var kSubscriptionItem $subscription */ return false; } } return true; } /** * Returns e-mail event id or throws an exception, when such event not found * * @param string $template_name * @param int $type * @return string * @throws Exception * @access public */ public function getEmailTemplateId($template_name, $type = EmailTemplate::TEMPLATE_TYPE_FRONTEND) { $sql = 'SELECT TemplateId FROM ' . $this->Application->getUnitConfig('email-template')->getTableName() . ' WHERE TemplateName = ' . $this->Conn->qstr($template_name) . ' AND Type = ' . $type; $id = $this->Conn->GetOne($sql); if ( !$id ) { throw new Exception('E-mail template "' . $template_name . '" not found'); } return $id; } } class kSubscriptionItem extends kBase { /** * Fields set, that uniquely identifies subscription * * @var Array * @access protected */ protected $fields = Array (); /** * Creates new subscription item * * @param Array $fields * @access public */ public function __construct($fields) { parent::__construct(); $this->fields = $fields; } /** * Returns user subscription * * @param bool $reload * @return kBase|kDBItem * @access public */ public function getSubscription($reload = false) { $special = kUtil::crc32(serialize($this->fields)); + /** @var kDBItem $subscription */ $subscription = $this->Application->recallObject('system-event-subscription.' . $special, null, Array ('skip_autoload' => true)); - /* @var $subscription kDBItem */ if ( !$subscription->isLoaded() || $reload ) { $subscription->Load($this->fields); } return $subscription; } /** * Subscribes user * * @return bool * @access public */ public function subscribe() { $subscription = $this->getSubscription(); if ( $subscription->isLoaded() ) { return true; } $subscription->SetDBFieldsFromHash($this->fields); return $subscription->Create(); } /** * Unsubscribes user * * @return bool * @access public */ public function unsubscribe() { $subscription = $this->getSubscription(); if ( !$subscription->isLoaded() ) { return true; } + /** @var kTempTablesHandler $temp_handler */ $temp_handler = $this->Application->recallObject($subscription->getPrefixSpecial() . '_TempHandler', 'kTempTablesHandler'); - /* @var $temp_handler kTempTablesHandler */ $temp_handler->DeleteItems($subscription->Prefix, $subscription->Special, Array ($subscription->GetID())); return true; } } \ No newline at end of file Index: branches/5.3.x/core/kernel/managers/request_manager.php =================================================================== --- branches/5.3.x/core/kernel/managers/request_manager.php (revision 16518) +++ branches/5.3.x/core/kernel/managers/request_manager.php (revision 16519) @@ -1,478 +1,478 @@ processed ) { return; } $this->dumpRequest(); $this->processOpener(); $events = $this->getEvents(); $all_passed = $this->getAllPassed($events); // set "all_passed" before kApplication::GetTopmostPrefix method call ! $this->Application->SetVar('all_passed', implode(',', $all_passed)); foreach ($events as $prefix_special => $event_name) { $event =& $this->runEvent($prefix_special, $event_name); if ( $event->status == kEvent::erSTOP ) { // event requested to stop processing at this point kUtil::safeDefine('DBG_SKIP_REPORTING', 1); $this->Application->Session->SaveData(); exit; } if ( $event->status == kEvent::erPERM_FAIL ) { $this->processPermissionError($event); } if ( ($event->status == kEvent::erSUCCESS || $event->status == kEvent::erPERM_FAIL) && $this->canRedirect($event) ) { $this->performRedirect($event); } } $this->Application->SetVar('events', $events); $this->Application->SetVar('passed', implode(',', $all_passed)); $this->processed = true; } /** * Dumps user request to debugger (only when enabled) * * @return void * @access protected */ protected function dumpRequest() { if ( defined('DEBUG_MODE') && $this->Application->isDebugMode() && kUtil::constOn('DBG_SHOW_HTTPQUERY') ) { $this->Application->Debugger->appendHTML('HTTPQuery:'); $this->Application->Debugger->dumpVars($this->Application->HttpQuery->_Params); } } /** * Returns event names, given in request (post, get) * * @return Array * @access protected */ protected function getEvents() { $post_events = $this->getEventsFromPost(); return $post_events ? $post_events : $this->getEventsFromGet(); } /** * Get all passed prefixes * * @param Array $events * @return Array * @access protected */ protected function getAllPassed($events) { $ret = explode(',', $this->Application->GetVar('passed')); foreach ($events as $prefix_special => $event_name) { if (!$event_name) { continue; } if ($this->Application->isAdmin) { array_push($ret, $prefix_special); } else { // don't add special on Front-end because of category item list special is autogenerated $prefix_special = explode('.', $prefix_special); array_push($ret, $prefix_special[0]); } } return $ret; } /** * Creates and runs event. Returns false, when prefix of given event isn't registered * * @param string $prefix_special * @param string $event_name * * @return kEvent|false * @access protected */ protected function &runEvent($prefix_special, $event_name) { $event = new kEvent($prefix_special . ':' . $event_name); if ( !$this->Application->EventManager->verifyEventPrefix($event, true) ) { $false = false; return $false; } $event->SetRedirectParam('opener', 's'); // stay on same page after event is called $event->setEventParam('top_prefix', $this->Application->GetTopmostPrefix($event->Prefix, true)); + /** @var kEventHandler $event_handler */ $event_handler = $this->Application->recallObject($event->Prefix . '_EventHandler'); - /* @var $event_handler kEventHandler */ if ( ($this->Application->RecallVar('user_id') == USER_ROOT) || $event_handler->CheckPermission($event) ) { $this->Application->HandleEvent($event); $this->Application->notifyEventSubscribers($event); } return $event; } /** * Processes case, when event finished with permission error * * @param kEvent $event * @access protected */ protected function processPermissionError($event) { // should do redirect but to no_permissions template $event->redirect = $this->Application->isAdmin ? 'no_permission' : $this->Application->ConfigValue('NoPermissionTemplate'); $event->SetRedirectParam('pass', 'm'); + /** @var kThemesHelper $themes_helper */ $themes_helper = $this->Application->recallObject('ThemesHelper'); - /* @var $themes_helper kThemesHelper */ $event->SetRedirectParam( 'm_cat_id', $themes_helper->getPageByTemplate($event->redirect) ); // restore stuff, that processOpener() changed $wid = $this->Application->GetVar('m_wid'); $this->Application->RestoreVar( rtrim('opener_stack_' . $wid, '_') ); // don't save last_template, because no_permission template does js history.back and could cause invalid opener_stack content $this->Application->SetVar('skip_last_template', 1); } /** * Performs redirect after event execution * * @param kEvent $event * @access protected */ protected function performRedirect($event) { // we need to pass category if the action was submitted to self-template, with the category passed // and it has not explicitly set redirect template or pass_category param if ( $this->samePageRedirect($event) && ($event->getEventParam('pass_category') === false) && $this->Application->GetVar('m_cat_id') ) { $event->SetRedirectParam('pass_category', 1); } $wid = $this->Application->GetVar('m_wid'); $redirect_params = $event->getRedirectParams(); if ( $wid && $event->getRedirectParam('opener') == 'u' ) { // update last element in current opener stack unset($redirect_params['opener']); $redirect_template = is_string($event->redirect) ? $event->redirect : null; $this->openerStackChange($redirect_template, $redirect_params); // reset opener, because kApplication::HREF will react differently when 'opener' => 'u' $event->SetRedirectParam('opener', 's'); $event->redirect = defined('CLOSE_POPUP_TPL') ? CLOSE_POPUP_TPL : 'incs/close_popup'; } if ( $event->getRedirectParam('pass') === false ) { // pass all discovered units to redirected page unless developer decided otherwise $event->SetRedirectParam('pass', 'all'); } $this->Application->Redirect($event->redirect, $event->getRedirectParams(), '', $event->redirectScript); } /** * Checks, if redirect can be made * * @param kEvent $event * @return bool * @access protected */ protected function canRedirect($event) { return $this->samePageRedirect($event) || strlen($event->redirect) > 0; } /** * Checks, that current template will be displayed after redirect * * @param kEvent $event * @return bool * @access protected */ protected function samePageRedirect($event) { return $event->redirect === true || $event->redirect == $this->Application->GetVar('t'); } /** * Returns event names given in GET * * @return Array * @access protected */ protected function getEventsFromGet() { $events = Array (); $discovered_units = $this->Application->HttpQuery->getDiscoveredUnits(false); foreach ($discovered_units as $prefix_special => $query_string) { $query_string = array_flip($query_string); if ( !isset($query_string['event']) ) { continue; } $event_name = $this->Application->GetVar($prefix_special . '_event'); // we need to check for pre 5.1.0 url format, because of "PerPage" // query string part (that was added in place of "event" query // string part) is available only since 5.1.0 version if ($event_name && !is_numeric($event_name)) { $events[$prefix_special] = $event_name; } } return $events; } /** * Returns event names given in POST * * @return Array * @access protected */ protected function getEventsFromPost() { $ret = Array (); $events = $this->Application->GetVar('events'); if (!$events) { return Array (); } foreach ($events as $prefix_special => $event_name) { if (!$event_name) { continue; } if ( is_array($event_name) ) { // HTML-input names like "events[prefix.special][event_name]", input value don't matter $event_name = key($event_name); $this->Application->SetVar($prefix_special . '_event', $event_name); } // HTML-input names like "events[prefix.special]", input value is event name $ret[$prefix_special] = $event_name; } return $ret; } /** * Processes window opener stack * * @access protected */ protected function processOpener() { + /** @var kOpenerStack $opener_stack */ $opener_stack = $this->Application->makeClass('kOpenerStack'); - /* @var $opener_stack kOpenerStack */ switch ( $this->Application->GetVar('m_opener') ) { case 'r': $opener_stack->reset(); break; case 'd': // "down/push" new template to opener stack, deeplevel++ if ( $this->Application->GetVar('front') ) { + /** @var Session $front_session */ $front_session = $this->Application->recallObject('Session.front'); - /* @var $front_session Session */ $opener_stack->pushRaw( '../' . $front_session->RecallVar('last_template') ); } else { $opener_stack->pushRaw( $this->Application->RecallVar('last_template') ); } break; case 'u': // "up/pop" last template from opener stack, deeplevel-- $opener_stack->pop(); break; case 'p': // pop-up - generate new wid $parent_wid = $this->Application->GetVar('m_wid'); // window_id of popup's parent window $popup_wid = (int)$this->Application->RecallVar('last_wid') + 1; $this->Application->StoreVar('last_wid', $popup_wid); $this->Application->SetVar('m_wid', $popup_wid); + /** @var kOpenerStack $popup_opener_stack */ $popup_opener_stack = $this->Application->makeClass('kOpenerStack', Array ($popup_wid)); - /* @var $popup_opener_stack kOpenerStack */ $popup_opener_stack->pushRaw( $this->getLastTemplate($parent_wid) ); $popup_opener_stack->save(); $this->Application->SetVar('m_opener', 's'); /*// store window relations $window_relations = $this->Application->RecallVar('window_relations'); $window_relations = $window_relations ? unserialize($window_relations) : Array (); $window_relations[$popup_wid] = $parent_wid; $this->Application->StoreVar('window_relations', serialize($window_relations));*/ return; break; default: // "s/0," stay on same deep level break; } $this->Application->SetVar('m_opener', 's'); $opener_stack->save(); } /** * Returns last template from window with given id * * @param int $window_id * @return string * @access protected */ protected function getLastTemplate($window_id) { if ( $this->Application->GetVar('front') ) { + /** @var Session $front_session */ $front_session = $this->Application->recallObject('Session.front'); - /* @var $front_session Session */ return '../' . $front_session->RecallVar( rtrim('last_template_popup_' . $window_id, '_') ); } if ( $this->Application->GetVar('merge_opener_stack') ) { // get last template from parent (that was closed) window opener stack + /** @var kOpenerStack $parent_opener_stack */ $parent_opener_stack = $this->Application->makeClass('kOpenerStack', Array ($window_id)); - /* @var $parent_opener_stack kOpenerStack */ $last_template = $parent_opener_stack->pop(true); $parent_opener_stack->save(true); } else { $last_template = $this->Application->RecallVar( rtrim('last_template_popup_' . $window_id, '_') ); } return $last_template; } /** * Allows to add new element to opener stack * * @param string $template * @param Array $params * @param int $wid * @access public */ public function openerStackPush($template = '', $params = Array (), $wid = null) { if ( !isset($params['pass']) ) { $params['pass'] = 'all'; } /*// get parent window wid, when this was popup $window_relations = $this->Application->RecallVar('window_relations'); $window_relations = $window_relations ? unserialize($window_relations) : Array (); $wid = isset($window_relations[$wid]) ? $window_relations[$wid] : false;*/ + /** @var kOpenerStack $opener_stack */ $opener_stack = $this->Application->makeClass('kOpenerStack', Array ($wid)); - /* @var $opener_stack kOpenerStack */ // change opener stack $default_params = Array ('m_opener' => 'u'); if ( !$this->Application->ConfigValue('UsePopups') && $opener_stack->getWindowID() ) { // remove wid to show combined header block in editing window $default_params['m_wid'] = ''; list ($last_template, $last_params, ) = $opener_stack->get(kOpenerStack::LAST_ELEMENT); // move last popup's opener stack element to main window's opener stack if ( $last_params ) { $last_params = array_merge($last_params, $default_params); $this->openerStackPush($last_template, $last_params, ''); } } $params = array_merge($default_params, $params); $opener_stack->push($template, $params, 'index.php'); $opener_stack->save(); } /** * Allows to change last element in opener stack * * @param string $new_template * @param Array $new_params * @access protected */ protected function openerStackChange($new_template = null, $new_params = null) { + /** @var kOpenerStack $opener_stack */ $opener_stack = $this->Application->makeClass('kOpenerStack'); - /* @var $opener_stack kOpenerStack */ list ($template, $params, $index_file) = $opener_stack->pop(); if ( isset($new_template) ) { $template = $new_template; } if ( isset($new_params) ) { $params = array_merge($params, $new_params); } if ( !isset($params['pass_events']) ) { // don't pass events, unless requested $params['pass_events'] = false; } $opener_stack->push($template, $params, $index_file); $opener_stack->save(); } } \ No newline at end of file Index: branches/5.3.x/core/kernel/managers/rewrite_url_processor.php =================================================================== --- branches/5.3.x/core/kernel/managers/rewrite_url_processor.php (revision 16518) +++ branches/5.3.x/core/kernel/managers/rewrite_url_processor.php (revision 16519) @@ -1,1069 +1,1069 @@ HTTPQuery = $this->Application->recallObject('kHTTPQuery'); // domain based primary language $this->primaryLanguageId = $this->Application->siteDomainField('PrimaryLanguageId'); if (!$this->primaryLanguageId) { // when domain-based language not found -> use site-wide language $this->primaryLanguageId = $this->Application->GetDefaultLanguageId(); } // domain based primary theme $this->primaryThemeId = $this->Application->siteDomainField('PrimaryThemeId'); if (!$this->primaryThemeId) { // when domain-based theme not found -> use site-wide theme $this->primaryThemeId = $this->Application->GetDefaultThemeId(true); } $this->initRouters(); } /** * Sets module prefix. * * @param string $prefix Unit config prefix. * * @return void */ public function setModulePrefix($prefix) { $this->modulePrefix = $prefix; } /** * Parses url * * @return void */ public function parseRewriteURL() { $url = $this->Application->GetVar('_mod_rw_url_'); if ( $url ) { $this->_redirectToDefaultUrlEnding($url); $url = $this->_removeUrlEnding($url); } $cached = $this->_getCachedUrl($url); if ( $cached !== false ) { $vars = $cached['vars']; $passed = $cached['passed']; } else { $vars = $this->parse($url); $passed = $vars['pass']; // also used in bottom of this method unset($vars['pass']); if ( !$this->_partsToParse ) { // don't cache 404 Not Found $this->_setCachedUrl($url, Array ('vars' => $vars, 'passed' => $passed)); } else { header('HTTP/1.0 404 Not Found'); } if ( $this->Application->GetVarDirect('t', 'Post') ) { // template from POST overrides template from URL. $vars['t'] = $this->Application->GetVarDirect('t', 'Post'); if ( isset($vars['is_virtual']) && $vars['is_virtual'] ) { $vars['m_cat_id'] = 0; // this is virtual template category (for Proj-CMS) } } unset($vars['is_virtual']); } foreach ($vars as $name => $value) { $this->HTTPQuery->Set($name, $value); } $this->_initAll(); // also will use parsed language to load phrases from it $this->HTTPQuery->finalizeParsing($passed); } /** * Detects url ending of given url * * @param string $url * @return string * @access protected */ protected function _findUrlEnding($url) { if ( !$url ) { return ''; } foreach ($this->_urlEndings as $url_ending) { if ( mb_substr($url, mb_strlen($url) - mb_strlen($url_ending)) == $url_ending ) { return $url_ending; } } return ''; } /** * Removes url ending from url * * @param string $url * @return string * @access protected */ protected function _removeUrlEnding($url) { $url_ending = $this->_findUrlEnding($url); if ( !$url_ending ) { return $url; } return mb_substr($url, 0, mb_strlen($url) - mb_strlen($url_ending)); } /** * Redirects user to page with default url ending, where needed * * @param string $url * @return void * @access protected */ protected function _redirectToDefaultUrlEnding($url) { $default_ending = $this->Application->ConfigValue('ModRewriteUrlEnding'); if ( $this->_findUrlEnding($url) == $default_ending || !$this->Application->ConfigValue('ForceModRewriteUrlEnding') ) { return; } // user manually typed url with different url ending -> redirect to same url with default url ending $target_url = $this->Application->BaseURL() . $this->_removeUrlEnding($url) . $default_ending; trigger_error('Mod-rewrite url "' . $_SERVER['REQUEST_URI'] . '" without "' . $default_ending . '" line ending used', E_USER_NOTICE); $this->Application->Redirect('external:' . $target_url, Array ('response_code' => 301)); } /** * Returns url parsing result from cache or false, when not yet parsed * * @param $url * @return Array|bool * @access protected */ protected function _getCachedUrl($url) { if ( !$url || (defined('DBG_CACHE_URLS') && !DBG_CACHE_URLS) ) { return false; } $sql = 'SELECT * FROM ' . TABLE_PREFIX . 'CachedUrls WHERE Hash = ' . kUtil::crc32($url) . ' AND DomainId = ' . (int)$this->Application->siteDomainField('DomainId'); $data = $this->Conn->GetRow($sql); if ( $data ) { $lifetime = (int)$data['LifeTime']; // in seconds if ( ($lifetime > 0) && ($data['Cached'] + $lifetime < TIMENOW) ) { // delete expired $sql = 'DELETE FROM ' . TABLE_PREFIX . 'CachedUrls WHERE UrlId = ' . $data['UrlId']; $this->Conn->Query($sql); return false; } return unserialize($data['ParsedVars']); } return false; } /** * Caches url * * @param string $url * @param Array $data * @return void * @access protected */ protected function _setCachedUrl($url, $data) { if ( !$url || (defined('DBG_CACHE_URLS') && !DBG_CACHE_URLS) ) { return; } $vars = $data['vars']; $passed = $data['passed']; sort($passed); // get expiration if ( $vars['m_cat_id'] > 0 ) { $sql = 'SELECT PageExpiration FROM ' . TABLE_PREFIX . 'Categories WHERE CategoryId = ' . $vars['m_cat_id']; $expiration = $this->Conn->GetOne($sql); } // get prefixes $prefixes = Array (); $m_index = array_search('m', $passed); if ( $m_index !== false ) { unset($passed[$m_index]); if ( $vars['m_cat_id'] > 0 ) { $prefixes[] = 'c:' . $vars['m_cat_id']; } $prefixes[] = 'lang:' . $vars['m_lang']; $prefixes[] = 'theme:' . $vars['m_theme']; } foreach ($passed as $prefix) { if ( array_key_exists($prefix . '_id', $vars) && is_numeric($vars[$prefix . '_id']) ) { $prefixes[] = $prefix . ':' . $vars[$prefix . '_id']; } else { $prefixes[] = $prefix; } } $fields_hash = Array ( 'Url' => $url, 'Hash' => kUtil::crc32($url), 'DomainId' => (int)$this->Application->siteDomainField('DomainId'), 'Prefixes' => $prefixes ? '|' . implode('|', $prefixes) . '|' : '', 'ParsedVars' => serialize($data), 'Cached' => time(), 'LifeTime' => isset($expiration) && is_numeric($expiration) ? $expiration : -1 ); $this->Conn->doInsert($fields_hash, TABLE_PREFIX . 'CachedUrls'); } /** * Loads all registered routers, so they could be quickly accessed later. * * @return void */ protected function initRouters() { static $init_done = false; // Not initialized OR mod-rewrite url with missing config cache. if ( $init_done || count($this->Application->routers) == 0 ) { return; } foreach ( $this->Application->routers as $prefix => $router_data ) { $this->routers[$prefix] = $this->Application->makeClass($router_data['class']); } define('MOD_REWRITE_URL_ENDING', $this->Application->ConfigValue('ModRewriteUrlEnding')); $init_done = true; } /** * Parses given string into a set of variables (url in this case) * * @param string $string * @param string $pass_name * @return Array * @access public */ public function parse($string, $pass_name = 'pass') { // external url (could be back this website as well) if ( preg_match('/external:(.*)/', $string, $regs) ) { $string = $regs[1]; } $vars = Array (); $url_components = parse_url($string); if ( isset($url_components['query']) ) { parse_str(html_entity_decode($url_components['query']), $url_params); if ( isset($url_params[ENV_VAR_NAME]) ) { $url_params = array_merge($url_params, $this->manager->plain->parse($url_params[ENV_VAR_NAME], $pass_name)); unset($url_params[ENV_VAR_NAME]); } $vars = array_merge($vars, $url_params); } $this->_fixPass($vars, $pass_name); if ( isset($url_components['path']) ) { if ( BASE_PATH ) { $string = preg_replace('/^' . preg_quote(BASE_PATH, '/') . '/', '', $url_components['path'], 1); } else { $string = $url_components['path']; } $string = $this->_removeUrlEnding(trim($string, '/')); } else { $string = ''; } $url_parts = $string ? explode('/', mb_strtolower($string)) : Array (); $this->setModulePrefix(false); $this->_partsToParse = $url_parts; if ( ($this->HTTPQuery->Get('rewrite') == 'on') || !$url_parts ) { $this->_setDefaultValues($vars); } if ( !$url_parts ) { $this->_initAll(); $vars['t'] = $this->Application->UrlManager->getTemplateName(); return $vars; } $this->_parseLanguage($url_parts, $vars); $this->_parseTheme($url_parts, $vars); // http://site-url///[_]/