oxseoencoder.php

Go to the documentation of this file.
00001 <?php
00002 
00007 class oxSeoEncoder extends oxSuperCfg
00008 {
00009 
00016     protected static $_aReservedWords = array('admin');
00017 
00023     protected static $_aReservedEntryKeys = null;
00024 
00030     protected static $_sSeparator = null;
00031 
00037     protected $_iIdLength = 255;
00038 
00044     protected static $_sPrefix = null;
00045 
00051     protected $_sAddParams = null;
00052 
00058     protected static $_aFixedCache = array();
00059 
00065     protected static $_sCacheKey = null;
00066 
00072     protected static $_aCache = array();
00073 
00079     protected $_iMaxUrlLength = null;
00080 
00089     public function addLanguageParam($sSeoUrl, $iLang)
00090     {
00091         $iLang = (int) $iLang;
00092         $iDefLang = (int) $this->getConfig()->getConfigParam('iDefSeoLang');
00093         $aLangIds = oxRegistry::getLang()->getLanguageIds();
00094 
00095         if ($iLang != $iDefLang && isset($aLangIds[$iLang]) && getStr()->strpos($sSeoUrl, $aLangIds[$iLang] . '/') !== 0) {
00096             $sSeoUrl = $aLangIds[$iLang] . '/' . $sSeoUrl;
00097         }
00098 
00099         return $sSeoUrl;
00100     }
00101 
00114     protected function _processSeoUrl($sSeoUrl, $sObjectId = null, $iLang = null, $blExclude = false)
00115     {
00116         if (!$blExclude) {
00117             $sSeoUrl = $this->addLanguageParam($sSeoUrl, $iLang);
00118         }
00119 
00120         return $this->_getUniqueSeoUrl($sSeoUrl, $sObjectId, $iLang);
00121     }
00122 
00126     public function __construct()
00127     {
00128         $myConfig = $this->getConfig();
00129         if (!self::$_sSeparator) {
00130             $this->setSeparator($myConfig->getConfigParam('sSEOSeparator'));
00131         }
00132         if (!self::$_sPrefix) {
00133             $this->setPrefix($myConfig->getConfigParam('sSEOuprefix'));
00134         }
00135         $this->setReservedWords($myConfig->getConfigParam('aSEOReservedWords'));
00136     }
00137 
00147     protected function _copyToHistory($sId, $iShopId, $iLang, $sType = null, $sNewId = null)
00148     {
00149         $oDb = oxDb::getDb();
00150         $sObjectid = $sNewId ? $oDb->quote($sNewId) : 'oxobjectid';
00151         $sType = $sType ? "oxtype =" . $oDb->quote($sType) . " and" : '';
00152         $iLang = (int) $iLang;
00153 
00154         // moving
00155         $sSub = "select $sObjectid, MD5( LOWER( oxseourl ) ), oxshopid, oxlang, now() from oxseo
00156                  where {$sType} oxobjectid = " . $oDb->quote($sId) . " and oxshopid = " . $oDb->quote($iShopId) . " and
00157                  oxlang = {$iLang} and oxexpired = '1'";
00158         $sQ = "replace oxseohistory ( oxobjectid, oxident, oxshopid, oxlang, oxinsert ) {$sSub}";
00159         $oDb->execute($sQ);
00160     }
00161 
00170     public function getDynamicObjectId($iShopId, $sStdUrl)
00171     {
00172         return $this->_getStaticObjectId($iShopId, $sStdUrl);
00173     }
00174 
00184     protected function _getDynamicUri($sStdUrl, $sSeoUrl, $iLang)
00185     {
00186         $iShopId = $this->getConfig()->getShopId();
00187 
00188         $sStdUrl = $this->_trimUrl($sStdUrl);
00189         $sObjectId = $this->getDynamicObjectId($iShopId, $sStdUrl);
00190         $sSeoUrl = $this->_prepareUri($this->addLanguageParam($sSeoUrl, $iLang), $iLang);
00191 
00192         //load details link from DB
00193         $sOldSeoUrl = $this->_loadFromDb('dynamic', $sObjectId, $iLang);
00194         if ($sOldSeoUrl === $sSeoUrl) {
00195             $sSeoUrl = $sOldSeoUrl;
00196         } else {
00197 
00198             if ($sOldSeoUrl) {
00199                 // old must be transferred to history
00200                 $this->_copyToHistory($sObjectId, $iShopId, $iLang, 'dynamic');
00201             }
00202 
00203             // creating unique
00204             $sSeoUrl = $this->_processSeoUrl($sSeoUrl, $sObjectId, $iLang);
00205 
00206             // inserting
00207             $this->_saveToDb('dynamic', $sObjectId, $sStdUrl, $sSeoUrl, $iLang, $iShopId);
00208         }
00209 
00210         return $sSeoUrl;
00211     }
00212 
00222     protected function _getFullUrl($sSeoUrl, $iLang = null, $blSsl = false)
00223     {
00224         if ($sSeoUrl) {
00225             $sFullUrl = ($blSsl ? $this->getConfig()->getSslShopUrl($iLang) : $this->getConfig()->getShopUrl($iLang, false)) . $sSeoUrl;
00226 
00227             return oxRegistry::get("oxUtilsUrl")->processSeoUrl($sFullUrl);
00228         }
00229 
00230         return false;
00231     }
00232 
00242     protected function _getSeoIdent($sSeoUrl)
00243     {
00244         return md5(strtolower($sSeoUrl));
00245     }
00246 
00256     protected function _getStaticUri($sStdUrl, $iShopId, $iLang)
00257     {
00258         $sStdUrl = $this->_trimUrl($sStdUrl, $iLang);
00259 
00260         return $this->_loadFromDb('static', $this->_getStaticObjectId($iShopId, $sStdUrl), $iLang);
00261     }
00262 
00268     protected function _getUrlExtension()
00269     {
00270         return;
00271     }
00272 
00285     protected function _getUniqueSeoUrl($sSeoUrl, $sObjectId = null, $iObjectLang = null)
00286     {
00287         $sSeoUrl = $this->_prepareUri($sSeoUrl, $iObjectLang);
00288         $oStr = getStr();
00289         $sExt = '';
00290         if ($oStr->preg_match('/(\.html?|\/)$/i', $sSeoUrl, $aMatched)) {
00291             $sExt = $aMatched[0];
00292         }
00293         $sBaseSeoUrl = $sSeoUrl;
00294         if ($sExt && $oStr->substr($sSeoUrl, 0 - $oStr->strlen($sExt)) == $sExt) {
00295             $sBaseSeoUrl = $oStr->substr($sSeoUrl, 0, $oStr->strlen($sSeoUrl) - $oStr->strlen($sExt));
00296         }
00297 
00298         $iShopId = $this->getConfig()->getShopId();
00299         $iCnt = 0;
00300         $sCheckSeoUrl = $this->_trimUrl($sSeoUrl);
00301         $sQ = "select 1 from oxseo where oxshopid = '{$iShopId}'";
00302 
00303         $oDb = oxDb::getDb();
00304         // skipping self
00305         if ($sObjectId && isset($iObjectLang)) {
00306             $iObjectLang = (int) $iObjectLang;
00307             $sQ .= " and not (oxobjectid = " . $oDb->quote($sObjectId) . " and oxlang = $iObjectLang)";
00308         }
00309 
00310         while ($oDb->getOne($sQ . " and oxident= " . $oDb->quote($this->_getSeoIdent($sCheckSeoUrl)))) {
00311             $sAdd = '';
00312             if (self::$_sPrefix) {
00313                 $sAdd = self::$_sSeparator . self::$_sPrefix;
00314             }
00315             if ($iCnt) {
00316                 $sAdd .= self::$_sSeparator . $iCnt;
00317             }
00318             ++$iCnt;
00319 
00320             $sSeoUrl = $sBaseSeoUrl . $sAdd . $sExt;
00321             $sCheckSeoUrl = $this->_trimUrl($sSeoUrl);
00322         }
00323 
00324         return $sSeoUrl;
00325     }
00326 
00341     protected function _isFixed($sType, $sId, $iLang, $iShopId = null, $sParams = null, $blStrictParamsCheck = true)
00342     {
00343         if ($iShopId === null) {
00344             $iShopId = $this->getConfig()->getShopId();
00345         }
00346         $iLang = (int) $iLang;
00347 
00348         if (!isset(self::$_aFixedCache[$sType][$sShopId][$sId][$iLang])) {
00349             $oDb = oxDb::getDb();
00350 
00351             $sQ = "SELECT `oxfixed`
00352                 FROM `oxseo`
00353                 WHERE `oxtype` = " . $oDb->quote($sType) . "
00354                    AND `oxobjectid` = " . $oDb->quote($sId) . "
00355                    AND `oxshopid` = " . $oDb->quote($iShopId) . "
00356                    AND `oxlang` = '{$iLang}'";
00357 
00358             $sParams = $sParams ? $oDb->quote($sParams) : "''";
00359             if ($sParams && $blStrictParamsCheck) {
00360                 $sQ .= " AND `oxparams` = {$sParams}";
00361             } else {
00362                 $sQ .= " ORDER BY `oxparams` ASC";
00363             }
00364             $sQ .= " LIMIT 1";
00365 
00366             self::$_aFixedCache[$sType][$sShopId][$sId][$iLang] = (bool) $oDb->getOne($sQ);
00367         }
00368 
00369         return self::$_aFixedCache[$sType][$sShopId][$sId][$iLang];
00370     }
00371 
00382     protected function _getCacheKey($sType, $iLang = null, $iShopId = null, $sParams = null)
00383     {
00384         $blAdmin = $this->isAdmin();
00385         if (!$blAdmin && $sType !== "oxarticle") {
00386             return $sType . ((int) $iLang) . ((int) $iShopId) . "seo";
00387         }
00388 
00389         // use cache in non admin mode
00390         if (self::$_sCacheKey === null) {
00391             self::$_sCacheKey = false;
00392             if (!$blAdmin && ($oView = $this->getConfig()->getActiveView())) {
00393                 self::$_sCacheKey = md5($oView->getViewId()) . "seo";
00394             }
00395         }
00396 
00397         return self::$_sCacheKey;
00398     }
00399 
00411     protected function _loadFromCache($sCacheIdent, $sType, $iLang = null, $iShopId = null, $sParams = null)
00412     {
00413         if (!$this->getConfig()->getConfigParam('blEnableSeoCache')) {
00414             return false;
00415         }
00416 
00417         startProfile("seoencoder_loadFromCache");
00418 
00419         $sCacheKey = $this->_getCacheKey($sType, $iLang, $iShopId, $sParams);
00420         $sCache = false;
00421 
00422         if ($sCacheKey && !isset(self::$_aCache[$sCacheKey])) {
00423             self::$_aCache[$sCacheKey] = oxRegistry::getUtils()->fromFileCache($sCacheKey);
00424         }
00425 
00426         if (isset(self::$_aCache[$sCacheKey]) && isset(self::$_aCache[$sCacheKey][$sCacheIdent])) {
00427             $sCache = self::$_aCache[$sCacheKey][$sCacheIdent];
00428         }
00429 
00430         stopProfile("seoencoder_loadFromCache");
00431 
00432         return $sCache;
00433     }
00434 
00447     protected function _saveInCache($sCacheIdent, $sCache, $sType, $iLang = null, $iShopId = null, $sParams = null)
00448     {
00449         if (!$this->getConfig()->getConfigParam('blEnableSeoCache')) {
00450             return false;
00451         }
00452 
00453         startProfile("seoencoder_saveInCache");
00454 
00455         $blSaved = false;
00456         if ($sCache && ($sCacheKey = $this->_getCacheKey($sType, $iLang, $iShopId, $sParams)) !== false) {
00457             self::$_aCache[$sCacheKey][$sCacheIdent] = $sCache;
00458             $blSaved = oxRegistry::getUtils()->toFileCache($sCacheKey, self::$_aCache[$sCacheKey]);
00459         }
00460 
00461         stopProfile("seoencoder_saveInCache");
00462 
00463         return $blSaved;
00464     }
00465 
00481     protected function _loadFromDb($sType, $sId, $iLang, $iShopId = null, $sParams = null, $blStrictParamsCheck = true)
00482     {
00483 
00484         if ($iShopId === null) {
00485             $iShopId = $this->getConfig()->getShopId();
00486         }
00487 
00488         $iLang = (int) $iLang;
00489         $oDb = oxDb::getDb(oxDb::FETCH_MODE_ASSOC);
00490 
00491         $sQ = "
00492             SELECT
00493                 `oxfixed`,
00494                 `oxseourl`,
00495                 `oxexpired`,
00496                 `oxtype`
00497             FROM `oxseo`
00498             WHERE `oxtype` = " . $oDb->quote($sType) . "
00499                AND `oxobjectid` = " . $oDb->quote($sId) . "
00500                AND `oxshopid` = " . $oDb->quote($iShopId) . "
00501                AND `oxlang` = '{$iLang}'";
00502 
00503         $sParams = $sParams ? $sParams : '';
00504         if ($sParams && $blStrictParamsCheck) {
00505             $sQ .= " AND `oxparams` = '{$sParams}'";
00506         } else {
00507             $sQ .= " ORDER BY `oxparams` ASC";
00508         }
00509         $sQ .= " LIMIT 1";
00510 
00511 
00512         // caching to avoid same queries..
00513         $sIdent = md5($sQ);
00514 
00515         // looking in cache
00516         if (($sSeoUrl = $this->_loadFromCache($sIdent, $sType, $iLang, $iShopId, $sParams)) === false) {
00517             $oRs = $oDb->select($sQ);
00518 
00519             if ($oRs && $oRs->recordCount() > 0 && !$oRs->EOF) {
00520                 // moving expired static urls to history ..
00521                 if ($oRs->fields['oxexpired'] && ($oRs->fields['oxtype'] == 'static' || $oRs->fields['oxtype'] == 'dynamic')) {
00522                     // if expired - copying to history, marking as not expired
00523                     $this->_copyToHistory($sId, $iShopId, $iLang);
00524                     $oDb->execute("update oxseo set oxexpired = 0 where oxobjectid = " . $oDb->quote($sId) . " and oxlang = '{$iLang}'");
00525                     $sSeoUrl = $oRs->fields['oxseourl'];
00526                 } elseif (!$oRs->fields['oxexpired'] || $oRs->fields['oxfixed']) {
00527                     // if seo url is available and is valid
00528                     $sSeoUrl = $oRs->fields['oxseourl'];
00529                 }
00530 
00531                 // storing in cache
00532                 $this->_saveInCache($sIdent, $sSeoUrl, $sType, $iLang, $iShopId, $sParams);
00533             }
00534         }
00535 
00536         return $sSeoUrl;
00537     }
00538 
00545     protected function _getReservedEntryKeys()
00546     {
00547         if (!isset(self::$_aReservedEntryKeys) || !is_array(self::$_aReservedEntryKeys)) {
00548             $sDir = getShopBasePath();
00549             self::$_aReservedEntryKeys = array_map('preg_quote', self::$_aReservedWords, array('#'));
00550             $oStr = getStr();
00551             foreach (glob("$sDir/*") as $sFile) {
00552                 if ($oStr->preg_match('/^(.+)\.php[0-9]*$/i', basename($sFile), $aMatches)) {
00553                     self::$_aReservedEntryKeys[] = preg_quote($aMatches[0], '#');
00554                     self::$_aReservedEntryKeys[] = preg_quote($aMatches[1], '#');
00555                 } elseif (is_dir($sFile)) {
00556                     self::$_aReservedEntryKeys[] = preg_quote(basename($sFile), '#');
00557                 }
00558             }
00559             self::$_aReservedEntryKeys = array_unique(self::$_aReservedEntryKeys);
00560         }
00561 
00562         return self::$_aReservedEntryKeys;
00563     }
00564 
00573     protected function _prepareUri($sUri, $iLang = false)
00574     {
00575         // decoding entities
00576         $sUri = $this->encodeString($sUri, true, $iLang);
00577 
00578         // basic string preparation
00579         $oStr = getStr();
00580         $sUri = $oStr->strip_tags($sUri);
00581 
00582         // if found ".html" or "/" at the end - removing it temporary
00583         $sExt = $this->_getUrlExtension();
00584         if ($sExt === null) {
00585             $aMatched = array();
00586             if ($oStr->preg_match('/(\.html?|\/)$/i', $sUri, $aMatched)) {
00587                 $sExt = $aMatched[0];
00588             } else {
00589                 $sExt = '/';
00590             }
00591         }
00592         if ($sExt && $oStr->substr($sUri, 0 - $oStr->strlen($sExt)) == $sExt) {
00593             $sUri = $oStr->substr($sUri, 0, $oStr->strlen($sUri) - $oStr->strlen($sExt));
00594         }
00595 
00596         // removing any special characters
00597         // #0004282 bugfix, php <5.3 does not escape - char, so we do it manually
00598         $sQuotedPrefix = preg_quote(self::$_sSeparator . self::$_sPrefix, '/');
00599         if (phpversion() < '5.3') {
00600             $sQuotedPrefix = str_replace('-', '\-', $sQuotedPrefix);
00601         }
00602         $sRegExp = '/[^A-Za-z0-9' . $sQuotedPrefix . '\/]+/';
00603         $sUri = $oStr->preg_replace(array("/\W*\/\W*/", $sRegExp), array("/", self::$_sSeparator), $sUri);
00604 
00605         // SEO id is empty ?
00606         if (!$sUri && self::$_sPrefix) {
00607             $sUri = $this->_prepareUri(self::$_sPrefix, $iLang);
00608         }
00609 
00610         $sAdd = '';
00611         if ('/' != self::$_sSeparator) {
00612             $sAdd = self::$_sSeparator . self::$_sPrefix;
00613             $sUri = trim($sUri, self::$_sSeparator);
00614         } else {
00615             $sAdd = '_' . self::$_sPrefix;
00616         }
00617 
00618         // binding the ending back
00619         $sUri .= $sExt;
00620 
00621         // fix for not having url, which executes through /other/ script then seo decoder
00622         $sUri = $oStr->preg_replace("#^(/*)(" . implode('|', $this->_getReservedEntryKeys()) . ")(/|$)#i", "\$1\$2$sAdd\$3", $sUri);
00623 
00624         // cleaning
00625         // #0004282 bugfix, php < 5.3 does not escape - char, so we do it manually\
00626         $sQuotedSeparator = preg_quote(self::$_sSeparator, '/');
00627         if (phpversion() < '5.3') {
00628             $sQuotedSeparator = str_replace('-', '\-', $sQuotedSeparator);
00629         }
00630 
00631         return $oStr->preg_replace(
00632             array('|//+|', '/' . $sQuotedSeparator . $sQuotedSeparator . '+/'),
00633             array('/', self::$_sSeparator), $sUri
00634         );
00635     }
00636 
00637 
00647     protected function _prepareTitle($sTitle, $blSkipTruncate = false, $iLang = false)
00648     {
00649         $sTitle = $this->encodeString($sTitle, true, $iLang);
00650         $sSep = self::$_sSeparator;
00651         if (!$sSep || ('/' == $sSep)) {
00652             $sSep = '_';
00653         }
00654 
00655         $sRegExp = '/[^A-Za-z0-9\/' . preg_quote(self::$_sPrefix, '/') . preg_quote($sSep, '/') . ']+/';
00656         $sTitle = preg_replace(array("#/+#", $sRegExp, "# +#", "#(" . preg_quote($sSep, '/') . ")+#"), $sSep, $sTitle);
00657 
00658         $oStr = getStr();
00659         // smart truncate
00660         if (!$blSkipTruncate && $oStr->strlen($sTitle) > $this->_iIdLength) {
00661             $iFirstSpace = $oStr->strpos($sTitle, $sSep, $this->_iIdLength);
00662             if ($iFirstSpace !== false) {
00663                 $sTitle = $oStr->substr($sTitle, 0, $iFirstSpace);
00664             }
00665         }
00666 
00667         $sTitle = trim($sTitle, $sSep);
00668 
00669         if (!$sTitle) {
00670             return self::$_sPrefix;
00671         }
00672 
00673         // cleaning
00674         return $sTitle;
00675     }
00676 
00677 
00694     protected function _saveToDb($sType, $sObjectId, $sStdUrl, $sSeoUrl, $iLang, $iShopId = null, $blFixed = null, $sParams = null)
00695     {
00696         $oDb = oxDb::getDb(oxDb::FETCH_MODE_ASSOC);
00697         if ($iShopId === null) {
00698             $iShopId = $this->getConfig()->getShopId();
00699         }
00700 
00701         $iLang = (int) $iLang;
00702 
00703         $sStdUrl = $this->_trimUrl($sStdUrl);
00704         $sSeoUrl = $this->_trimUrl($sSeoUrl);
00705         $sIdent = $this->_getSeoIdent($sSeoUrl);
00706 
00707         // transferring old url, thus current url will be regenerated
00708         $sQtedObjectId = $oDb->quote($sObjectId);
00709         $iQtedShopId = $oDb->quote($iShopId);
00710         $sQtedType = $oDb->quote($sType);
00711         $sQtedSeoUrl = $oDb->quote($sSeoUrl);
00712         $sQtedStdUrl = $oDb->quote($sStdUrl);
00713         $sQtedParams = $oDb->quote($sParams);
00714         $sQtedIdent = $oDb->quote($sIdent);
00715 
00716         // transferring old url, thus current url will be regenerated
00717         $sQ = "select oxfixed, oxexpired, ( oxstdurl like {$sQtedStdUrl} ) as samestdurl,
00718                 oxseourl like {$sQtedSeoUrl} as sameseourl from oxseo where oxtype = {$sQtedType} and
00719                 oxobjectid = {$sQtedObjectId} and oxshopid = {$iQtedShopId}  and oxlang = {$iLang} ";
00720 
00721         $sQ .= $sParams ? " and oxparams = {$sQtedParams} " : '';
00722         $sQ .= "limit 1";
00723         $oDb = oxDb::getDb(oxDb::FETCH_MODE_ASSOC);
00724         $oRs = $oDb->select($sQ);
00725         if ($oRs && $oRs->recordCount() > 0 && !$oRs->EOF) {
00726             if ($oRs->fields['samestdurl'] && $oRs->fields['sameseourl'] && $oRs->fields['oxexpired']) {
00727                 // fixed state change
00728                 $sFixed = isset($blFixed) ? ", oxfixed = " . ((int) $blFixed) . " " : '';
00729                 // nothing was changed - setting expired status back to 0
00730                 $sSql = "update oxseo set oxexpired = 0 {$sFixed} where oxtype = {$sQtedType} and
00731                           oxobjectid = {$sQtedObjectId} and oxshopid = {$iQtedShopId} and oxlang = {$iLang} ";
00732                 $sSql .= $sParams ? " and oxparams = {$sQtedParams} " : '';
00733                 $sSql .= " limit 1";
00734 
00735                 return $oDb->execute($sSql);
00736             } elseif ($oRs->fields['oxexpired']) {
00737                 // copy to history
00738                 $this->_copyToHistory($sObjectId, $iShopId, $iLang, $sType);
00739             }
00740         }
00741 
00742         // inserting new or updating
00743         $sParams = $sParams ? $oDb->quote($sParams) : '""';
00744         $blFixed = (int) $blFixed;
00745 
00746         $sQ = "insert into oxseo
00747                     (oxobjectid, oxident, oxshopid, oxlang, oxstdurl, oxseourl, oxtype, oxfixed, oxexpired, oxparams)
00748                 values
00749                     ( {$sQtedObjectId}, {$sQtedIdent}, {$iQtedShopId}, {$iLang}, {$sQtedStdUrl}, {$sQtedSeoUrl}, {$sQtedType}, '$blFixed', '0', {$sParams} )
00750                 on duplicate key update
00751                     oxident = {$sQtedIdent}, oxstdurl = {$sQtedStdUrl}, oxseourl = {$sQtedSeoUrl}, oxfixed = '$blFixed', oxexpired = '0'";
00752 
00753         return $oDb->execute($sQ);
00754     }
00755 
00766     protected function _trimUrl($sUrl, $iLang = null)
00767     {
00768         $myConfig = $this->getConfig();
00769         $oStr = getStr();
00770         $sUrl = str_replace(array($myConfig->getShopUrl($iLang, false), $myConfig->getSslShopUrl($iLang)), '', $sUrl);
00771         $sUrl = $oStr->preg_replace('/(\?|&(amp;)?)(force_)?(admin_)?sid=[a-z0-9\.]+&?(amp;)?/i', '\1', $sUrl);
00772         $sUrl = $oStr->preg_replace('/(\?|&(amp;)?)shp=[0-9]+&?(amp;)?/i', '\1', $sUrl);
00773         $sUrl = $oStr->preg_replace('/(\?|&(amp;)?)lang=[0-9]+&?(amp;)?/i', '\1', $sUrl);
00774         $sUrl = $oStr->preg_replace('/(\?|&(amp;)?)cur=[0-9]+&?(amp;)?/i', '\1', $sUrl);
00775         $sUrl = $oStr->preg_replace('/(\?|&(amp;)?)stoken=[a-z0-9]+&?(amp;)?/i', '\1', $sUrl);
00776         $sUrl = $oStr->preg_replace('/(\?|&(amp;)?)&(amp;)?/i', '\1', $sUrl);
00777         $sUrl = $oStr->preg_replace('/(\?|&(amp;)?)+$/i', '', $sUrl);
00778         $sUrl = trim($sUrl);
00779 
00780         // max length <= $this->_iMaxUrlLength
00781         $iLength = $this->_getMaxUrlLength();
00782         if ($oStr->strlen($sUrl) > $iLength) {
00783             $sUrl = $oStr->substr($sUrl, 0, $iLength);
00784         }
00785 
00786         return $sUrl;
00787     }
00788 
00794     protected function _getMaxUrlLength()
00795     {
00796         if ($this->_iMaxUrlLength === null) {
00797             // max length <= 2048 / custom
00798             $this->_iMaxUrlLength = $this->getConfig()->getConfigParam("iMaxSeoUrlLength");
00799             if (!$this->_iMaxUrlLength) {
00800                 $this->_iMaxUrlLength = 2048;
00801             }
00802         }
00803 
00804         return $this->_iMaxUrlLength;
00805     }
00806 
00816     public function encodeString($sString, $blReplaceChars = true, $iLang = false)
00817     {
00818         // decoding entities
00819         $sString = getStr()->html_entity_decode($sString);
00820 
00821         if ($blReplaceChars) {
00822             if ($iLang === false || !is_numeric($iLang)) {
00823                 $iLang = oxRegistry::getLang()->getEditLanguage();
00824             }
00825 
00826             if ($aReplaceChars = oxRegistry::getLang()->getSeoReplaceChars($iLang)) {
00827                 $sString = str_replace(array_keys($aReplaceChars), array_values($aReplaceChars), $sString);
00828             }
00829         }
00830 
00831 
00832         // special chars
00833         $aReplaceWhat = array('&amp;', '&quot;', '&#039;', '&lt;', '&gt;');
00834 
00835         return str_replace($aReplaceWhat, '', $sString);
00836     }
00837 
00843     public function setSeparator($sSeparator = null)
00844     {
00845         self::$_sSeparator = $sSeparator;
00846         if (!self::$_sSeparator) {
00847             self::$_sSeparator = '-';
00848         }
00849     }
00850 
00856     public function setPrefix($sPrefix)
00857     {
00858         if ($sPrefix) {
00859             self::$_sPrefix = $sPrefix;
00860         } else {
00861             self::$_sPrefix = 'oxid';
00862         }
00863     }
00864 
00870     public function setIdLength($iIdlength = null)
00871     {
00872         if (isset($iIdlength)) {
00873             $this->_iIdLength = $iIdlength;
00874         }
00875     }
00876 
00883     public function setReservedWords($aReservedWords)
00884     {
00885         self::$_aReservedWords = array_merge(self::$_aReservedWords, $aReservedWords);
00886     }
00887 
00888 
00898     public function markAsExpired($sId, $iShopId = null, $iExpStat = 1, $iLang = null, $sParams = null)
00899     {
00900         $oDb = oxDb::getDb();
00901         $sWhere = $sId ? "where oxobjectid =  " . $oDb->quote($sId) : '';
00902         $sWhere .= isset($iShopId) ? ($sWhere ? " and oxshopid = " . $oDb->quote($iShopId) : "where oxshopid = " . $oDb->quote($iShopId)) : '';
00903         $sWhere .= !is_null($iLang) ? ($sWhere ? " and oxlang = '{$iLang}'" : "where oxlang = '{$iLang}'") : '';
00904         $sWhere .= $sParams ? ($sWhere ? " and {$sParams}" : "where {$sParams}") : '';
00905 
00906         $sQ = "update oxseo set oxexpired =  " . $oDb->quote($iExpStat) . " $sWhere ";
00907         $oDb->execute($sQ);
00908     }
00909 
00923     protected function _getPageUri($oObject, $sType, $sStdUrl, $sSeoUrl, $sParams, $iLang = null, $blFixed = false)
00924     {
00925         if (!isset($iLang)) {
00926             $iLang = $oObject->getLanguage();
00927         }
00928         $iShopId = $this->getConfig()->getShopId();
00929 
00930         //load page link from DB
00931         $sOldSeoUrl = $this->_loadFromDb($sType, $oObject->getId(), $iLang, $iShopId, $sParams);
00932         if (!$sOldSeoUrl) {
00933             // generating new..
00934             $sSeoUrl = $this->_processSeoUrl($sSeoUrl, $oObject->getId(), $iLang);
00935             $this->_saveToDb($sType, $oObject->getId(), $sStdUrl, $sSeoUrl, $iLang, $iShopId, (int) $blFixed, $sParams);
00936         } else {
00937             // using old
00938             $sSeoUrl = $sOldSeoUrl;
00939         }
00940 
00941         return $sSeoUrl;
00942     }
00943 
00952     protected function _getStaticObjectId($iShopId, $sStdUrl)
00953     {
00954         return md5(strtolower($iShopId . $this->_trimUrl($sStdUrl)));
00955     }
00956 
00966     public function encodeStaticUrls($aStaticUrl, $iShopId, $iLang)
00967     {
00968         $oDb = oxDb::getDb();
00969         $sValues = '';
00970         $sOldObjectId = null;
00971 
00972         // standard url
00973         $sStdUrl = $this->_trimUrl(trim($aStaticUrl['oxseo__oxstdurl']));
00974         $sObjectId = $aStaticUrl['oxseo__oxobjectid'];
00975 
00976         if (!$sObjectId || $sObjectId == '-1') {
00977             $sObjectId = $this->_getStaticObjectId($iShopId, $sStdUrl);
00978         } else {
00979             // marking entry as needs to move to history
00980             $sOldObjectId = $sObjectId;
00981 
00982             // if std url does not match old
00983             if ($this->_getStaticObjectId($iShopId, $sStdUrl) != $sObjectId) {
00984                 $sObjectId = $this->_getStaticObjectId($iShopId, $sStdUrl);
00985             }
00986         }
00987 
00988         foreach ($aStaticUrl['oxseo__oxseourl'] as $iLang => $sSeoUrl) {
00989 
00990             $iLang = (int) $iLang;
00991 
00992             // generating seo url
00993             $sSeoUrl = $this->_trimUrl($sSeoUrl);
00994             if ($sSeoUrl) {
00995                 $sSeoUrl = $this->_processSeoUrl($sSeoUrl, $sObjectId, $iLang);
00996             }
00997 
00998 
00999             if ($sOldObjectId) {
01000                 // move changed records to history
01001                 if (!$oDb->getOne("select (" . $oDb->quote($sSeoUrl) . " like oxseourl) & (" . $oDb->quote($sStdUrl) . " like oxstdurl) from oxseo where oxobjectid = " . $oDb->quote($sOldObjectId) . " and oxshopid = '{$iShopId}' and oxlang = '{$iLang}' ", false, false)) {
01002                     $this->_copyToHistory($sOldObjectId, $iShopId, $iLang, 'static', $sObjectId);
01003                 }
01004             }
01005 
01006             if (!$sSeoUrl || !$sStdUrl) {
01007                 continue;
01008             }
01009 
01010             $sIdent = $this->_getSeoIdent($sSeoUrl);
01011 
01012             if ($sValues) {
01013                 $sValues .= ', ';
01014             }
01015 
01016             $sValues .= "( " . $oDb->quote($sObjectId) . ", " . $oDb->quote($sIdent) . ", " . $oDb->quote($iShopId) . ", '{$iLang}', " . $oDb->quote($sStdUrl) . ", " . $oDb->quote($sSeoUrl) . ", 'static' )";
01017         }
01018 
01019         // must delete old before insert/update
01020         if ($sOldObjectId) {
01021             $oDb->execute("delete from oxseo where oxobjectid in ( " . $oDb->quote($sOldObjectId) . ", " . $oDb->quote($sObjectId) . " )");
01022         }
01023 
01024         // (re)inserting
01025         if ($sValues) {
01026 
01027             $sQ = "insert into oxseo ( oxobjectid, oxident, oxshopid, oxlang, oxstdurl, oxseourl, oxtype ) values {$sValues} ";
01028             $oDb->execute($sQ);
01029         }
01030 
01031         return $sObjectId;
01032     }
01033 
01039     public function copyStaticUrls($iShopId)
01040     {
01041         $iBaseShopId = $this->getConfig()->getBaseShopId();
01042         if ($iShopId != $iBaseShopId) {
01043             $oDb = oxDb::getDb();
01044             foreach (array_keys(oxRegistry::getLang()->getLanguageIds()) as $iLang) {
01045                 $sQ = "insert into oxseo ( oxobjectid, oxident, oxshopid, oxlang, oxstdurl, oxseourl, oxtype )
01046                        select MD5( LOWER( CONCAT( " . $oDb->quote($iShopId) . ", oxstdurl ) ) ), MD5( LOWER( oxseourl ) ),
01047                        " . $oDb->quote($iShopId) . ", oxlang, oxstdurl, oxseourl, oxtype from oxseo where oxshopid = '{$iBaseShopId}' and oxtype = 'static' and oxlang='$iLang' ";
01048                 $oDb->execute($sQ);
01049             }
01050         }
01051     }
01052 
01062     public function getStaticUrl($sStdUrl, $iLang = null, $iShopId = null)
01063     {
01064         if (!isset($iShopId)) {
01065             $iShopId = $this->getConfig()->getShopId();
01066         }
01067         if (!isset($iLang)) {
01068             $iLang = oxRegistry::getLang()->getEditLanguage();
01069         }
01070 
01071         if (isset($this->_aStaticUrlCache[$sStdUrl][$iLang][$iShopId])) {
01072             return $this->_aStaticUrlCache[$sStdUrl][$iLang][$iShopId];
01073         }
01074 
01075         $sFullUrl = '';
01076         if (($sSeoUrl = $this->_getStaticUri($sStdUrl, $iShopId, $iLang))) {
01077             $sFullUrl = $this->_getFullUrl($sSeoUrl, $iLang, strpos($sStdUrl, "https:") === 0);
01078         }
01079 
01080 
01081         $this->_aStaticUrlCache[$sStdUrl][$iLang][$iShopId] = $sFullUrl;
01082 
01083         return $sFullUrl;
01084     }
01085 
01102     public function addSeoEntry($sObjectId, $iShopId, $iLang, $sStdUrl, $sSeoUrl, $sType, $blFixed = 1, $sKeywords = '', $sDescription = '', $sParams = '', $blExclude = false, $sAltObjectId = null)
01103     {
01104         $sSeoUrl = $this->_processSeoUrl($this->_trimUrl($sSeoUrl ? $sSeoUrl : $this->_getAltUri($sAltObjectId ? $sAltObjectId : $sObjectId, $iLang)), $sObjectId, $iLang, $blExclude);
01105         if ($this->_saveToDb($sType, $sObjectId, $sStdUrl, $sSeoUrl, $iLang, $iShopId, $blFixed, $sParams)) {
01106 
01107             $oDb = oxDb::getDb();
01108 
01109             //
01110             $sQtedObjectId = $oDb->quote($sAltObjectId ? $sAltObjectId : $sObjectId);
01111             $iQtedShopId = $oDb->quote($iShopId);
01112 
01113             $oStr = getStr();
01114             if ($sKeywords !== false) {
01115                 $sKeywords = $oDb->quote($oStr->htmlspecialchars($this->encodeString($oStr->strip_tags($sKeywords), false, $iLang)));
01116             }
01117 
01118             if ($sDescription !== false) {
01119                 $sDescription = $oDb->quote($oStr->htmlspecialchars($oStr->strip_tags($sDescription)));
01120             }
01121 
01122             $sQ = "insert into oxobject2seodata
01123                        ( oxobjectid, oxshopid, oxlang, oxkeywords, oxdescription )
01124                    values
01125                        ( {$sQtedObjectId}, {$iQtedShopId}, {$iLang}, " . ($sKeywords ? $sKeywords : "''") . ", " . ($sDescription ? $sDescription : "''") . " )
01126                    on duplicate key update
01127                        oxkeywords = " . ($sKeywords ? $sKeywords : "oxkeywords") . ", oxdescription = " . ($sDescription ? $sDescription : "oxdescription");
01128             $oDb->execute($sQ);
01129         }
01130     }
01131 
01138     protected function _getAltUri($sObjectId, $iLang)
01139     {
01140     }
01141 
01150     public function deleteSeoEntry($sObjectId, $iShopId, $iLang, $sType)
01151     {
01152         $oDb = oxDb::getDb();
01153         $sQ = "delete from oxseo where oxobjectid = " . $oDb->quote($sObjectId) . " and oxshopid = " . $oDb->quote($iShopId) . " and oxlang = " . $oDb->quote($iLang) . " and oxtype = " . $oDb->quote($sType) . " ";
01154         $oDb->execute($sQ);
01155     }
01156 
01167     public function getMetaData($sObjectId, $sMetaType, $iShopId = null, $iLang = null)
01168     {
01169         $oDb = oxDb::getDb();
01170 
01171         $iShopId = (!isset($iShopId)) ? $this->getConfig()->getShopId() : $iShopId;
01172         $iLang = (!isset($iLang)) ? oxRegistry::getLang()->getObjectTplLanguage() : ((int) $iLang);
01173 
01174         return $oDb->getOne("select {$sMetaType} from oxobject2seodata where oxobjectid = " . $oDb->quote($sObjectId) . " and oxshopid = " . $oDb->quote($iShopId) . " and oxlang = '{$iLang}'");
01175     }
01176 
01190     public function getDynamicUrl($sStdUrl, $sSeoUrl, $iLang)
01191     {
01192         startProfile("getDynamicUrl");
01193         $sDynUrl = $this->_getFullUrl($this->_getDynamicUri($sStdUrl, $sSeoUrl, $iLang), $iLang, strpos($sStdUrl, "https:") === 0);
01194         stopProfile("getDynamicUrl");
01195 
01196         return $sDynUrl;
01197     }
01198 
01207     public function fetchSeoUrl($sStdUrl, $iLanguage = null)
01208     {
01209         $oDb = oxDb::getDb(oxDb::FETCH_MODE_ASSOC);
01210         $iLanguage = isset($iLanguage) ? ((int) $iLanguage) : oxRegistry::getLang()->getBaseLanguage();
01211         $sSeoUrl = false;
01212 
01213         $sShopId = $this->getConfig()->getShopId();
01214 
01215         $sQ = "SELECT `oxseourl`, `oxlang` FROM `oxseo` WHERE `oxstdurl` = " . $oDb->quote($sStdUrl) . " AND `oxlang` = '$iLanguage' AND `oxshopid` = '$sShopId' LIMIT 1";
01216 
01217         $oDb = oxDb::getDb(oxDb::FETCH_MODE_ASSOC);
01218         $oRs = $oDb->select($sQ);
01219 
01220         if (!$oRs->EOF) {
01221             $sSeoUrl = $oRs->fields['oxseourl'];
01222         }
01223 
01224         return $sSeoUrl;
01225     }
01226 }