oxseoencoder.php

Go to the documentation of this file.
00001 <?php
00002 
00007 class oxSeoEncoder extends oxSuperCfg
00008 {
00015     protected static $_aReservedWords = array( 'admin' );
00016 
00022     protected static $_aReservedEntryKeys = null;
00023 
00029     protected static $_sSeparator = null;
00030 
00036     protected $_iIdLength = 255;
00037 
00043     protected static $_sPrefix = null;
00044 
00050     protected $_sAddParams = null;
00051 
00057     protected static $_instance = null;
00058 
00064     protected static $_aFixedCache = array();
00065 
00070     protected static $_sCacheKey = null;
00071 
00076     protected static $_aCache = array();
00077 
00082     protected $_iMaxUrlLength = null;
00083 
00091     public static function getInstance()
00092     {
00093         return oxRegistry::get("oxSeoEncoder");
00094     }
00095 
00104     public function addLanguageParam( $sSeoUrl, $iLang )
00105     {
00106         $iLang    = (int) $iLang;
00107         $iDefLang = (int) $this->getConfig()->getConfigParam( 'iDefSeoLang' );
00108         $aLangIds = oxRegistry::getLang()->getLanguageIds();
00109 
00110         if ( $iLang != $iDefLang && isset( $aLangIds[$iLang] ) && getStr()->strpos( $sSeoUrl, $aLangIds[$iLang] . '/' ) !== 0 ) {
00111             $sSeoUrl = $aLangIds[$iLang] . '/'.$sSeoUrl;
00112         }
00113 
00114         return $sSeoUrl;
00115     }
00116 
00129     protected function _processSeoUrl( $sSeoUrl, $sObjectId = null, $iLang = null, $blExclude = false )
00130     {
00131         if (!$blExclude) {
00132             $sSeoUrl = $this->addLanguageParam( $sSeoUrl, $iLang );
00133         }
00134         return $this->_getUniqueSeoUrl( $sSeoUrl, $sObjectId, $iLang );
00135     }
00136 
00140     public function __construct()
00141     {
00142         $myConfig = $this->getConfig();
00143         if (!self::$_sSeparator) {
00144             $this->setSeparator( $myConfig->getConfigParam( 'sSEOSeparator' ) );
00145         }
00146         if (!self::$_sPrefix) {
00147             $this->setPrefix( $myConfig->getConfigParam( 'sSEOuprefix' ) );
00148         }
00149         $this->setReservedWords( $myConfig->getConfigParam( 'aSEOReservedWords' ) );
00150     }
00151 
00163     protected function _copyToHistory( $sId, $iShopId, $iLang, $sType = null, $sNewId = null )
00164     {
00165         $oDb = oxDb::getDb();
00166         $sObjectid = $sNewId?$oDb->quote( $sNewId ):'oxobjectid';
00167         $sType     = $sType?"oxtype =".$oDb->quote( $sType )." and":'';
00168         $iLang     = (int) $iLang;
00169 
00170         // moving
00171         $sSub = "select $sObjectid, MD5( LOWER( oxseourl ) ), oxshopid, oxlang, now() from oxseo
00172                  where {$sType} oxobjectid = ".$oDb->quote( $sId )." and oxshopid = ".$oDb->quote( $iShopId )." and
00173                  oxlang = {$iLang} and oxexpired = '1'";
00174         $sQ   = "replace oxseohistory ( oxobjectid, oxident, oxshopid, oxlang, oxinsert ) {$sSub}";
00175         $oDb->execute( $sQ );
00176     }
00177 
00186     public function getDynamicObjectId( $iShopId, $sStdUrl )
00187     {
00188         return $this->_getStaticObjectId( $iShopId, $sStdUrl );
00189     }
00190 
00200     protected function _getDynamicUri( $sStdUrl, $sSeoUrl, $iLang )
00201     {
00202         $iShopId = $this->getConfig()->getShopId();
00203 
00204         $sStdUrl   = $this->_trimUrl( $sStdUrl );
00205         $sObjectId = $this->getDynamicObjectId( $iShopId, $sStdUrl );
00206         $sSeoUrl   = $this->_prepareUri( $this->addLanguageParam( $sSeoUrl, $iLang ), $iLang );
00207 
00208         //load details link from DB
00209         $sOldSeoUrl = $this->_loadFromDb( 'dynamic', $sObjectId, $iLang );
00210         if ( $sOldSeoUrl === $sSeoUrl ) {
00211             $sSeoUrl = $sOldSeoUrl;
00212         } else {
00213 
00214             if ( $sOldSeoUrl ) {
00215                 // old must be transferred to history
00216                 $this->_copyToHistory( $sObjectId, $iShopId, $iLang, 'dynamic' );
00217             }
00218 
00219             // creating unique
00220             $sSeoUrl = $this->_processSeoUrl( $sSeoUrl, $sObjectId, $iLang );
00221 
00222             // inserting
00223             $this->_saveToDb( 'dynamic', $sObjectId, $sStdUrl, $sSeoUrl, $iLang, $iShopId );
00224         }
00225 
00226         return $sSeoUrl;
00227     }
00228 
00238     protected function _getFullUrl( $sSeoUrl, $iLang = null, $blSsl = false )
00239     {
00240         if ( $sSeoUrl ) {
00241             $sFullUrl = ( $blSsl ? $this->getConfig()->getSslShopUrl( $iLang ) : $this->getConfig()->getShopUrl( $iLang, false ) ) . $sSeoUrl;
00242             return oxRegistry::get("oxUtilsUrl")->processSeoUrl( $sFullUrl );
00243         }
00244         return false;
00245     }
00246 
00256     protected function _getSeoIdent( $sSeoUrl )
00257     {
00258         return md5( strtolower( $sSeoUrl ) );
00259     }
00260 
00270     protected function _getStaticUri( $sStdUrl, $iShopId, $iLang )
00271     {
00272         $sStdUrl = $this->_trimUrl( $sStdUrl, $iLang );
00273         return $this->_loadFromDb( 'static', $this->_getStaticObjectId( $iShopId, $sStdUrl ), $iLang );
00274     }
00275 
00281     protected function _getUrlExtension()
00282     {
00283         return;
00284     }
00285 
00298     protected function _getUniqueSeoUrl( $sSeoUrl, $sObjectId = null, $iObjectLang = null )
00299     {
00300         $sSeoUrl = $this->_prepareUri( $sSeoUrl, $iObjectLang );
00301         $oStr = getStr();
00302         $sExt = '';
00303         if ( $oStr->preg_match( '/(\.html?|\/)$/i', $sSeoUrl, $aMatched ) ) {
00304             $sExt = $aMatched[0];
00305         }
00306         $sBaseSeoUrl = $sSeoUrl;
00307         if ( $sExt && $oStr->substr( $sSeoUrl, 0 - $oStr->strlen( $sExt ) ) == $sExt ) {
00308             $sBaseSeoUrl = $oStr->substr( $sSeoUrl, 0, $oStr->strlen( $sSeoUrl ) - $oStr->strlen( $sExt ) );
00309         }
00310 
00311         $iShopId = $this->getConfig()->getShopId();
00312         $iCnt = 0;
00313         $sCheckSeoUrl = $this->_trimUrl( $sSeoUrl );
00314         $sQ = "select 1 from oxseo where oxshopid = '{$iShopId}'";
00315 
00316         $oDb = oxDb::getDb();
00317         // skipping self
00318         if ( $sObjectId && isset($iObjectLang) ) {
00319             $iObjectLang = (int) $iObjectLang;
00320             $sQ .= " and not (oxobjectid = " . $oDb->quote( $sObjectId ) . " and oxlang = $iObjectLang)";
00321         }
00322 
00323         while ( $oDb->getOne( $sQ ." and oxident= " . $oDb->quote( $this->_getSeoIdent( $sCheckSeoUrl ) ) ) ) {
00324             $sAdd = '';
00325             if ( self::$_sPrefix ) {
00326                 $sAdd = self::$_sSeparator . self::$_sPrefix;
00327             }
00328             if ( $iCnt ) {
00329                 $sAdd .= self::$_sSeparator . $iCnt;
00330             }
00331             ++$iCnt;
00332 
00333             $sSeoUrl = $sBaseSeoUrl . $sAdd . $sExt;
00334             $sCheckSeoUrl = $this->_trimUrl( $sSeoUrl );
00335         }
00336         return $sSeoUrl;
00337     }
00338 
00353     protected function _isFixed( $sType, $sId, $iLang, $iShopId = null, $sParams = null, $blStrictParamsCheck = true)
00354     {
00355         if ( $iShopId === null ) {
00356             $iShopId = $this->getConfig()->getShopId();
00357         }
00358         $iLang = (int) $iLang;
00359 
00360         if ( !isset( self::$_aFixedCache[$sType][$sShopId][$sId][$iLang] ) ) {
00361             $oDb = oxDb::getDb();
00362 
00363             $sQ = "SELECT `oxfixed`
00364                 FROM `oxseo`
00365                 WHERE `oxtype` = ".$oDb->quote( $sType )."
00366                    AND `oxobjectid` = ".$oDb->quote( $sId ) ."
00367                    AND `oxshopid` = ".$oDb->quote( $iShopId )."
00368                    AND `oxlang` = '{$iLang}'";
00369 
00370             $sParams = $sParams ? $oDb->quote( $sParams ) : "''";
00371             if ( $sParams && $blStrictParamsCheck ) {
00372                 $sQ .= " AND `oxparams` = {$sParams}";
00373             } else {
00374                 $sQ .= " ORDER BY `oxparams` ASC";
00375             }
00376             $sQ .= " LIMIT 1";
00377 
00378             self::$_aFixedCache[$sType][$sShopId][$sId][$iLang] = (bool) $oDb->getOne( $sQ );
00379         }
00380         return self::$_aFixedCache[$sType][$sShopId][$sId][$iLang];
00381     }
00382 
00393     protected function _getCacheKey( $sType, $iLang = null, $iShopId = null, $sParams = null )
00394     {
00395         $blAdmin = $this->isAdmin();
00396         if ( !$blAdmin && $sType !== "oxarticle" ) {
00397             return $sType . ( (int) $iLang ) . ( (int) $iShopId ) . "seo";
00398         }
00399 
00400         // use cache in non admin mode
00401         if ( self::$_sCacheKey === null ) {
00402             self::$_sCacheKey = false;
00403             if ( !$blAdmin && ( $oView = $this->getConfig()->getActiveView() ) ) {
00404                 self::$_sCacheKey = md5( $oView->getViewId() ) . "seo";
00405             }
00406         }
00407         return self::$_sCacheKey;
00408     }
00409 
00421     protected function _loadFromCache( $sCacheIdent, $sType, $iLang = null, $iShopId = null, $sParams = null )
00422     {
00423         if ( !$this->getConfig()->getConfigParam( 'blEnableSeoCache' ) ) {
00424             return false;
00425         }
00426 
00427         startProfile( "seoencoder_loadFromCache" );
00428 
00429         $sCacheKey = $this->_getCacheKey( $sType, $iLang, $iShopId, $sParams );
00430         $sCache = false;
00431 
00432         if ( $sCacheKey && !isset( self::$_aCache[$sCacheKey] ) ) {
00433             self::$_aCache[$sCacheKey] = oxRegistry::getUtils()->fromFileCache( $sCacheKey );
00434         }
00435 
00436         if ( isset( self::$_aCache[$sCacheKey] ) && isset( self::$_aCache[$sCacheKey][$sCacheIdent] ) ) {
00437             $sCache = self::$_aCache[$sCacheKey][$sCacheIdent];
00438         }
00439 
00440         stopProfile( "seoencoder_loadFromCache" );
00441         return $sCache;
00442     }
00443 
00456     protected function _saveInCache( $sCacheIdent, $sCache, $sType, $iLang = null, $iShopId = null, $sParams = null )
00457     {
00458         if ( !$this->getConfig()->getConfigParam( 'blEnableSeoCache' ) ) {
00459             return false;
00460         }
00461 
00462         startProfile( "seoencoder_saveInCache" );
00463 
00464         $blSaved = false;
00465         if ( $sCache && ( $sCacheKey = $this->_getCacheKey( $sType, $iLang, $iShopId, $sParams ) ) !== false ) {
00466             self::$_aCache[$sCacheKey][$sCacheIdent] = $sCache;
00467             $blSaved = oxRegistry::getUtils()->toFileCache( $sCacheKey, self::$_aCache[$sCacheKey] );
00468         }
00469 
00470         stopProfile( "seoencoder_saveInCache" );
00471         return $blSaved;
00472     }
00473 
00489     protected function _loadFromDb( $sType, $sId, $iLang, $iShopId = null, $sParams = null, $blStrictParamsCheck = true)
00490     {
00491 
00492         if ( $iShopId === null ) {
00493             $iShopId = $this->getConfig()->getShopId();
00494         }
00495 
00496         $iLang = (int) $iLang;
00497         $oDb = oxDb::getDb( oxDb::FETCH_MODE_ASSOC );
00498 
00499         $sQ = "
00500             SELECT
00501                 `oxfixed`,
00502                 `oxseourl`,
00503                 `oxexpired`,
00504                 `oxtype`
00505             FROM `oxseo`
00506             WHERE `oxtype` = ".$oDb->quote( $sType )."
00507                AND `oxobjectid` = ".$oDb->quote( $sId ) ."
00508                AND `oxshopid` = ".$oDb->quote( $iShopId )."
00509                AND `oxlang` = '{$iLang}'";
00510 
00511         $sParams = $sParams ? $sParams : '';
00512         if ( $sParams && $blStrictParamsCheck ) {
00513             $sQ .= " AND `oxparams` = '{$sParams}'";
00514         } else {
00515             $sQ .= " ORDER BY `oxparams` ASC";
00516         }
00517         $sQ .= " LIMIT 1";
00518 
00519 
00520         // caching to avoid same queries..
00521         $sIdent = md5( $sQ );
00522 
00523         // looking in cache
00524         if ( ( $sSeoUrl = $this->_loadFromCache( $sIdent, $sType, $iLang, $iShopId, $sParams ) ) === false ) {
00525             $oRs = $oDb->select( $sQ );
00526 
00527             if ( $oRs && $oRs->recordCount() > 0 && !$oRs->EOF ) {
00528                 // moving expired static urls to history ..
00529                 if ( $oRs->fields['oxexpired'] && ( $oRs->fields['oxtype'] == 'static' || $oRs->fields['oxtype'] == 'dynamic' ) ) {
00530                     // if expired - copying to history, marking as not expired
00531                     $this->_copyToHistory( $sId, $iShopId, $iLang );
00532                     $oDb->execute( "update oxseo set oxexpired = 0 where oxobjectid = ".$oDb->quote( $sId )." and oxlang = '{$iLang}'" );
00533                     $sSeoUrl = $oRs->fields['oxseourl'];
00534                 } elseif ( !$oRs->fields['oxexpired'] || $oRs->fields['oxfixed'] ) {
00535                     // if seo url is available and is valid
00536                     $sSeoUrl = $oRs->fields['oxseourl'];
00537                 }
00538 
00539                 // storing in cache
00540                 $this->_saveInCache( $sIdent, $sSeoUrl, $sType, $iLang, $iShopId, $sParams );
00541             }
00542         }
00543         return $sSeoUrl;
00544     }
00545 
00552     protected function _getReservedEntryKeys()
00553     {
00554         if ( !isset( self::$_aReservedEntryKeys ) || !is_array( self::$_aReservedEntryKeys ) ) {
00555             $sDir = getShopBasePath();
00556             self::$_aReservedEntryKeys = array_map('preg_quote', self::$_aReservedWords, array('#'));
00557             $oStr = getStr();
00558             foreach ( glob( "$sDir/*" ) as $sFile ) {
00559                 if ( $oStr->preg_match( '/^(.+)\.php[0-9]*$/i', basename( $sFile ), $aMatches ) ) {
00560                     self::$_aReservedEntryKeys[] = preg_quote( $aMatches[0], '#' );
00561                     self::$_aReservedEntryKeys[] = preg_quote( $aMatches[1], '#' );
00562                 } elseif ( is_dir( $sFile ) ) {
00563                     self::$_aReservedEntryKeys[] = preg_quote( basename( $sFile ), '#' );
00564                 }
00565             }
00566             self::$_aReservedEntryKeys = array_unique(self::$_aReservedEntryKeys);
00567         }
00568         return self::$_aReservedEntryKeys;
00569     }
00570 
00579     protected function _prepareUri( $sUri, $iLang = false )
00580     {
00581         // decoding entities
00582         $sUri = $this->encodeString( $sUri, true, $iLang );
00583 
00584         // basic string preparation
00585         $oStr = getStr();
00586         $sUri = $oStr->strip_tags( $sUri );
00587 
00588         // if found ".html" or "/" at the end - removing it temporary
00589         $sExt = $this->_getUrlExtension();
00590         if ($sExt === null) {
00591             $aMatched = array();
00592             if ( $oStr->preg_match( '/(\.html?|\/)$/i', $sUri, $aMatched ) ) {
00593                 $sExt = $aMatched[0];
00594             } else {
00595                 $sExt = '/';
00596             }
00597         }
00598         if ( $sExt && $oStr->substr( $sUri, 0 - $oStr->strlen( $sExt ) ) == $sExt ) {
00599             $sUri = $oStr->substr( $sUri, 0, $oStr->strlen( $sUri ) - $oStr->strlen( $sExt ) );
00600         }
00601 
00602         // removing any special characters
00603         // #0004282 bugfix, php <5.3 does not escape - char, so we do it manually
00604         $sQuotedPrefix = preg_quote( self::$_sSeparator . self::$_sPrefix, '/');
00605         if ( phpversion() < '5.3' ) {
00606             $sQuotedPrefix = str_replace( '-', '\-', $sQuotedPrefix );
00607         }
00608         $sRegExp = '/[^A-Za-z0-9' . $sQuotedPrefix . '\/]+/';
00609         $sUri  = $oStr->preg_replace( array( "/\W*\/\W*/", $sRegExp ), array( "/", self::$_sSeparator ), $sUri );
00610 
00611         // SEO id is empty ?
00612         if ( !$sUri && self::$_sPrefix ) {
00613             $sUri = $this->_prepareUri( self::$_sPrefix, $iLang );
00614         }
00615 
00616         $sAdd = '';
00617         if ('/' != self::$_sSeparator) {
00618             $sAdd = self::$_sSeparator . self::$_sPrefix;
00619             $sUri = trim($sUri, self::$_sSeparator);
00620         } else {
00621             $sAdd = '_' . self::$_sPrefix;
00622         }
00623 
00624         // binding the ending back
00625         $sUri .= $sExt;
00626 
00627         // fix for not having url, which executes through /other/ script then seo decoder
00628         $sUri = $oStr->preg_replace( "#^(/*)(".implode('|', $this->_getReservedEntryKeys()).")(/|$)#i", "\$1\$2$sAdd\$3", $sUri );
00629 
00630         // cleaning
00631         // #0004282 bugfix, php < 5.3 does not escape - char, so we do it manually\
00632         $sQuotedSeparator = preg_quote( self::$_sSeparator, '/');
00633         if ( phpversion() < '5.3' ) {
00634             $sQuotedSeparator = str_replace( '-', '\-', $sQuotedSeparator );
00635         }
00636         return $oStr->preg_replace( array( '|//+|', '/' . $sQuotedSeparator . $sQuotedSeparator .'+/' ),
00637                              array( '/', self::$_sSeparator ), $sUri );
00638     }
00639 
00640 
00650     protected function _prepareTitle( $sTitle, $blSkipTruncate = false, $iLang = false )
00651     {
00652         $sTitle = $this->encodeString( $sTitle, true, $iLang );
00653         $sSep = self::$_sSeparator;
00654         if (!$sSep || ('/' == $sSep)) {
00655             $sSep = '_';
00656         }
00657 
00658         $sRegExp = '/[^A-Za-z0-9\/'.preg_quote( self::$_sPrefix, '/').preg_quote($sSep, '/').']+/';
00659         $sTitle = preg_replace( array("#/+#", $sRegExp, "# +#", "#(".preg_quote($sSep, '/').")+#"), $sSep, $sTitle );
00660 
00661         $oStr = getStr();
00662         // smart truncate
00663         if ( !$blSkipTruncate && $oStr->strlen( $sTitle ) > $this->_iIdLength ) {
00664             $iFirstSpace = $oStr->strpos( $sTitle, $sSep, $this->_iIdLength);
00665             if ( $iFirstSpace !== false ) {
00666                 $sTitle = $oStr->substr( $sTitle, 0, $iFirstSpace );
00667             }
00668         }
00669 
00670         $sTitle = trim( $sTitle, $sSep );
00671 
00672         if (!$sTitle) {
00673             return self::$_sPrefix;
00674         }
00675         // cleaning
00676         return $sTitle;
00677     }
00678 
00679 
00696     protected function _saveToDb( $sType, $sObjectId, $sStdUrl, $sSeoUrl, $iLang, $iShopId = null, $blFixed = null, $sParams = null )
00697     {
00698         $oDb = oxDb::getDb( oxDb::FETCH_MODE_ASSOC );
00699         if ( $iShopId === null ) {
00700             $iShopId = $this->getConfig()->getShopId();
00701         }
00702 
00703         $iLang = (int) $iLang;
00704 
00705         $sStdUrl = $this->_trimUrl( $sStdUrl );
00706         $sSeoUrl = $this->_trimUrl( $sSeoUrl );
00707         $sIdent  = $this->_getSeoIdent( $sSeoUrl );
00708 
00709         // transferring old url, thus current url will be regenerated
00710         $sQtedObjectId = $oDb->quote( $sObjectId );
00711         $iQtedShopId   = $oDb->quote( $iShopId );
00712         $sQtedType     = $oDb->quote( $sType );
00713         $sQtedSeoUrl   = $oDb->quote( $sSeoUrl );
00714         $sQtedStdUrl   = $oDb->quote( $sStdUrl );
00715         $sQtedParams   = $oDb->quote( $sParams );
00716         $sQtedIdent    = $oDb->quote( $sIdent );
00717 
00718         // transferring old url, thus current url will be regenerated
00719         $sQ  = "select oxfixed, oxexpired, ( oxstdurl like {$sQtedStdUrl} ) as samestdurl,
00720                 oxseourl like {$sQtedSeoUrl} as sameseourl from oxseo where oxtype = {$sQtedType} and
00721                 oxobjectid = {$sQtedObjectId} and oxshopid = {$iQtedShopId}  and oxlang = {$iLang} ";
00722 
00723         $sQ .= $sParams ? " and oxparams = {$sQtedParams} " : '';
00724         $sQ .= "limit 1";
00725         $oDb = oxDb::getDb( oxDb::FETCH_MODE_ASSOC );
00726         $oRs = $oDb->select( $sQ );
00727         if ( $oRs && $oRs->recordCount() > 0 && !$oRs->EOF ) {
00728             if ( $oRs->fields['samestdurl'] && $oRs->fields['sameseourl'] && $oRs->fields['oxexpired'] ) {
00729                 // fixed state change
00730                 $sFixed = isset( $blFixed ) ? ", oxfixed = " . ( (int) $blFixed ) . " " : '';
00731                 // nothing was changed - setting expired status back to 0
00732                 $sSql  = "update oxseo set oxexpired = 0 {$sFixed} where oxtype = {$sQtedType} and
00733                           oxobjectid = {$sQtedObjectId} and oxshopid = {$iQtedShopId} and oxlang = {$iLang} ";
00734                 $sSql .= $sParams ? " and oxparams = {$sQtedParams} " : '';
00735                 $sSql .= " limit 1";
00736 
00737                 return $oDb->execute( $sSql );
00738             } elseif ( $oRs->fields['oxexpired'] ) {
00739                 // copy to history
00740                 $this->_copyToHistory( $sObjectId, $iShopId, $iLang, $sType );
00741             }
00742         }
00743 
00744         // inserting new or updating
00745         $sParams = $sParams ? $oDb->quote( $sParams ) :'""';
00746         $blFixed = (int) $blFixed;
00747 
00748         $sQ  = "insert into oxseo
00749                     (oxobjectid, oxident, oxshopid, oxlang, oxstdurl, oxseourl, oxtype, oxfixed, oxexpired, oxparams)
00750                 values
00751                     ( {$sQtedObjectId}, {$sQtedIdent}, {$iQtedShopId}, {$iLang}, {$sQtedStdUrl}, {$sQtedSeoUrl}, {$sQtedType}, '$blFixed', '0', {$sParams} )
00752                 on duplicate key update
00753                     oxident = {$sQtedIdent}, oxstdurl = {$sQtedStdUrl}, oxseourl = {$sQtedSeoUrl}, oxfixed = '$blFixed', oxexpired = '0'";
00754 
00755         return $oDb->execute( $sQ );
00756     }
00757 
00768     protected function _trimUrl( $sUrl, $iLang = null )
00769     {
00770         $myConfig = $this->getConfig();
00771         $oStr = getStr();
00772         $sUrl = str_replace( array( $myConfig->getShopUrl( $iLang, false ), $myConfig->getSslShopUrl( $iLang ) ), '', $sUrl );
00773         $sUrl = $oStr->preg_replace( '/(\?|&(amp;)?)(force_)?(admin_)?sid=[a-z0-9\.]+&?(amp;)?/i', '\1', $sUrl );
00774         $sUrl = $oStr->preg_replace( '/(\?|&(amp;)?)shp=[0-9]+&?(amp;)?/i', '\1', $sUrl );
00775         $sUrl = $oStr->preg_replace( '/(\?|&(amp;)?)lang=[0-9]+&?(amp;)?/i', '\1', $sUrl );
00776         $sUrl = $oStr->preg_replace( '/(\?|&(amp;)?)cur=[0-9]+&?(amp;)?/i', '\1', $sUrl );
00777         $sUrl = $oStr->preg_replace( '/(\?|&(amp;)?)stoken=[a-z0-9]+&?(amp;)?/i', '\1', $sUrl );
00778         $sUrl = $oStr->preg_replace( '/(\?|&(amp;)?)&(amp;)?/i', '\1', $sUrl );
00779         $sUrl = $oStr->preg_replace( '/(\?|&(amp;)?)+$/i', '', $sUrl );
00780         $sUrl = trim( $sUrl );
00781 
00782         // max length <= $this->_iMaxUrlLength
00783         $iLength = $this->_getMaxUrlLength();
00784         if ( $oStr->strlen( $sUrl ) > $iLength ) {
00785             $sUrl = $oStr->substr( $sUrl, 0, $iLength );
00786         }
00787 
00788         return $sUrl;
00789     }
00790 
00796     protected function _getMaxUrlLength()
00797     {
00798         if ( $this->_iMaxUrlLength === null ) {
00799             // max length <= 2048 / custom
00800             $this->_iMaxUrlLength = $this->getConfig()->getConfigParam( "iMaxSeoUrlLength" ) ;
00801             if ( !$this->_iMaxUrlLength ) {
00802                 $this->_iMaxUrlLength = 2048;
00803             }
00804         }
00805         return $this->_iMaxUrlLength;
00806     }
00807 
00817     public function encodeString( $sString, $blReplaceChars = true, $iLang = false )
00818     {
00819         // decoding entities
00820         $sString = getStr()->html_entity_decode( $sString );
00821 
00822         if ( $blReplaceChars ) {
00823             if ($iLang === false || !is_numeric($iLang)) {
00824                 $iLang = oxRegistry::getLang()->getEditLanguage();
00825             }
00826 
00827             if ( $aReplaceChars = oxRegistry::getLang()->getSeoReplaceChars($iLang) ) {
00828                 $sString = str_replace( array_keys( $aReplaceChars ), array_values( $aReplaceChars ), $sString );
00829             }
00830         }
00831 
00832 
00833         // special chars
00834         $aReplaceWhat = array( '&amp;', '&quot;', '&#039;', '&lt;', '&gt;' );
00835         return str_replace( $aReplaceWhat, '', $sString );
00836     }
00837 
00845     public function setSeparator( $sSeparator = null )
00846     {
00847         self::$_sSeparator = $sSeparator;
00848         if ( !self::$_sSeparator ) {
00849             self::$_sSeparator = '-';
00850         }
00851     }
00852 
00860     public function setPrefix( $sPrefix )
00861     {
00862         if ($sPrefix) {
00863             self::$_sPrefix = $sPrefix;
00864         } else {
00865             self::$_sPrefix = 'oxid';
00866         }
00867     }
00868 
00876     public function setIdLength( $iIdlength = null )
00877     {
00878         if ( isset( $iIdlength ) ) {
00879             $this->_iIdLength = $iIdlength;
00880         }
00881     }
00882 
00891     public function setReservedWords( $aReservedWords )
00892     {
00893         self::$_aReservedWords = array_merge( self::$_aReservedWords, $aReservedWords );
00894     }
00895 
00896 
00908     public function markAsExpired( $sId, $iShopId = null, $iExpStat = 1, $iLang = null, $sParams = null )
00909     {
00910         $oDb = oxDb::getDb();
00911         $sWhere  = $sId ? "where oxobjectid =  " . $oDb->quote( $sId ) : '';
00912         $sWhere .= isset( $iShopId ) ? ( $sWhere ? " and oxshopid = ". $oDb->quote( $iShopId ) : "where oxshopid = ". $oDb->quote( $iShopId ) ) : '';
00913         $sWhere .= $iLang ? ( $sWhere ? " and oxlang = '{$iLang}'" : "where oxlang = '{$iLang}'" ) : '';
00914         $sWhere .= $sParams ? ( $sWhere ? " and {$sParams}" : "where {$sParams}" ) : '';
00915 
00916         $sQ = "update oxseo set oxexpired =  " . $oDb->quote( $iExpStat ) . " $sWhere ";
00917         $oDb->execute( $sQ );
00918     }
00919 
00933     protected function _getPageUri( $oObject, $sType, $sStdUrl, $sSeoUrl, $sParams, $iLang = null, $blFixed = false )
00934     {
00935         if ( !isset( $iLang ) ) {
00936             $iLang = $oObject->getLanguage();
00937         }
00938         $iShopId = $this->getConfig()->getShopId();
00939 
00940         //load page link from DB
00941         $sOldSeoUrl = $this->_loadFromDb( $sType, $oObject->getId(), $iLang, $iShopId, $sParams );
00942         if ( !$sOldSeoUrl ) {
00943             // generating new..
00944             $sSeoUrl = $this->_processSeoUrl( $sSeoUrl, $oObject->getId(), $iLang );
00945             $this->_saveToDb( $sType, $oObject->getId(), $sStdUrl, $sSeoUrl, $iLang, $iShopId, (int) $blFixed, $sParams );
00946         } else {
00947             // using old
00948             $sSeoUrl = $sOldSeoUrl;
00949         }
00950         return $sSeoUrl;
00951     }
00952 
00961     protected function _getStaticObjectId( $iShopId, $sStdUrl )
00962     {
00963         return md5( strtolower ( $iShopId . $this->_trimUrl( $sStdUrl ) ) );
00964     }
00965 
00975     public function encodeStaticUrls( $aStaticUrl, $iShopId, $iLang )
00976     {
00977         $oDb = oxDb::getDb();
00978         $sValues = '';
00979         $sOldObjectId = null;
00980 
00981         // standard url
00982         $sStdUrl = $this->_trimUrl( trim( $aStaticUrl['oxseo__oxstdurl'] ) );
00983         $sObjectId = $aStaticUrl['oxseo__oxobjectid'];
00984 
00985         if ( !$sObjectId || $sObjectId == '-1' ) {
00986             $sObjectId = $this->_getStaticObjectId( $iShopId, $sStdUrl );
00987         } else {
00988             // marking entry as needs to move to history
00989             $sOldObjectId = $sObjectId;
00990 
00991             // if std url does not match old
00992             if ( $this->_getStaticObjectId( $iShopId, $sStdUrl ) != $sObjectId ) {
00993                 $sObjectId = $this->_getStaticObjectId( $iShopId, $sStdUrl );
00994             }
00995         }
00996 
00997         foreach ( $aStaticUrl['oxseo__oxseourl'] as $iLang => $sSeoUrl ) {
00998 
00999             $iLang = (int) $iLang;
01000 
01001             // generating seo url
01002             $sSeoUrl = $this->_trimUrl( $sSeoUrl );
01003             if ( $sSeoUrl ) {
01004                 $sSeoUrl = $this->_processSeoUrl( $sSeoUrl, $sObjectId, $iLang );
01005             }
01006 
01007 
01008             if ( $sOldObjectId ) {
01009                 // move changed records to history
01010                 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) ) {
01011                     $this->_copyToHistory( $sOldObjectId, $iShopId, $iLang, 'static', $sObjectId );
01012                 }
01013             }
01014 
01015             if ( !$sSeoUrl || !$sStdUrl ) {
01016                 continue;
01017             }
01018 
01019             $sIdent = $this->_getSeoIdent( $sSeoUrl );
01020 
01021             if ( $sValues ) {
01022                 $sValues .= ', ';
01023             }
01024 
01025             $sValues .= "( " . $oDb->quote( $sObjectId ) . ", " . $oDb->quote( $sIdent ) . ", " . $oDb->quote( $iShopId ).", '{$iLang}', " . $oDb->quote( $sStdUrl ) . ", " . $oDb->quote( $sSeoUrl ) . ", 'static' )";
01026         }
01027 
01028         // must delete old before insert/update
01029         if ( $sOldObjectId ) {
01030             $oDb->execute( "delete from oxseo where oxobjectid in ( " . $oDb->quote( $sOldObjectId ) . ", " . $oDb->quote( $sObjectId ) . " )" );
01031         }
01032 
01033         // (re)inserting
01034         if ( $sValues ) {
01035 
01036             $sQ = "insert into oxseo ( oxobjectid, oxident, oxshopid, oxlang, oxstdurl, oxseourl, oxtype ) values {$sValues} ";
01037             $oDb->execute( $sQ );
01038         }
01039 
01040         return $sObjectId;
01041     }
01042 
01050     public function copyStaticUrls( $iShopId )
01051     {
01052         $iBaseShopId = $this->getConfig()->getBaseShopId();
01053         if ( $iShopId != $iBaseShopId ) {
01054             $oDb = oxDb::getDb();
01055             foreach (array_keys(oxRegistry::getLang()->getLanguageIds()) as $iLang) {
01056                 $sQ = "insert into oxseo ( oxobjectid, oxident, oxshopid, oxlang, oxstdurl, oxseourl, oxtype )
01057                        select MD5( LOWER( CONCAT( " . $oDb->quote( $iShopId ) . ", oxstdurl ) ) ), MD5( LOWER( oxseourl ) ),
01058                        " . $oDb->quote( $iShopId ) . ", oxlang, oxstdurl, oxseourl, oxtype from oxseo where oxshopid = '{$iBaseShopId}' and oxtype = 'static' and oxlang='$iLang' ";
01059                 $oDb->execute( $sQ );
01060             }
01061         }
01062     }
01063 
01073     public function getStaticUrl( $sStdUrl, $iLang = null, $iShopId = null )
01074     {
01075         if (!isset($iShopId)) {
01076             $iShopId = $this->getConfig()->getShopId();
01077         }
01078         if (!isset($iLang)) {
01079             $iLang   = oxRegistry::getLang()->getEditLanguage();
01080         }
01081 
01082         if ( isset($this->_aStaticUrlCache[$sStdUrl][$iLang][$iShopId])) {
01083             return $this->_aStaticUrlCache[$sStdUrl][$iLang][$iShopId];
01084         }
01085 
01086         $sFullUrl = '';
01087         if ( ( $sSeoUrl = $this->_getStaticUri( $sStdUrl, $iShopId, $iLang ) ) ) {
01088             $sFullUrl = $this->_getFullUrl( $sSeoUrl, $iLang, strpos( $sStdUrl, "https:" ) === 0 );
01089         }
01090 
01091 
01092         $this->_aStaticUrlCache[$sStdUrl][$iLang][$iShopId] = $sFullUrl;
01093 
01094         return $sFullUrl;
01095     }
01096 
01115     public function addSeoEntry( $sObjectId, $iShopId, $iLang, $sStdUrl, $sSeoUrl, $sType, $blFixed = 1, $sKeywords = '', $sDescription = '', $sParams = '', $blExclude = false, $sAltObjectId = null )
01116     {
01117         $sSeoUrl = $this->_processSeoUrl( $this->_trimUrl( $sSeoUrl ? $sSeoUrl : $this->_getAltUri( $sAltObjectId ? $sAltObjectId : $sObjectId, $iLang ) ), $sObjectId, $iLang, $blExclude );
01118         if ( $this->_saveToDb( $sType, $sObjectId, $sStdUrl, $sSeoUrl, $iLang, $iShopId, $blFixed, $sParams ) ) {
01119 
01120             $oDb = oxDb::getDb();
01121 
01122             //
01123             $sQtedObjectId = $oDb->quote( $sAltObjectId ? $sAltObjectId : $sObjectId );
01124             $iQtedShopId   = $oDb->quote( $iShopId );
01125 
01126             $oStr = getStr();
01127             if ( $sKeywords !== false ) {
01128                 $sKeywords = $oDb->quote( $oStr->htmlspecialchars( $this->encodeString( $oStr->strip_tags( $sKeywords ), false, $iLang ) ) );
01129             }
01130 
01131             if ( $sDescription !== false ) {
01132                 $sDescription = $oDb->quote( $oStr->htmlspecialchars( $oStr->strip_tags( $sDescription ) ) );
01133             }
01134 
01135             $sQ = "insert into oxobject2seodata
01136                        ( oxobjectid, oxshopid, oxlang, oxkeywords, oxdescription )
01137                    values
01138                        ( {$sQtedObjectId}, {$iQtedShopId}, {$iLang}, ".( $sKeywords ? $sKeywords : "''" ).", ".( $sDescription ? $sDescription : "''" )." )
01139                    on duplicate key update
01140                        oxkeywords = ".( $sKeywords ? $sKeywords : "oxkeywords" ).", oxdescription = ".( $sDescription ? $sDescription : "oxdescription" );
01141             $oDb->execute( $sQ );
01142         }
01143     }
01144 
01153     protected function _getAltUri( $sObjectId, $iLang )
01154     {
01155     }
01156 
01167     public function deleteSeoEntry( $sObjectId, $iShopId, $iLang, $sType )
01168     {
01169         $oDb = oxDb::getDb();
01170         $sQ = "delete from oxseo where oxobjectid = " . $oDb->quote( $sObjectId ) . " and oxshopid = " . $oDb->quote( $iShopId ) . " and oxlang = " . $oDb->quote( $iLang ) . " and oxtype = " . $oDb->quote( $sType ) . " ";
01171         $oDb->execute( $sQ );
01172     }
01173 
01184     public function getMetaData( $sObjectId, $sMetaType, $iShopId = null, $iLang = null )
01185     {
01186         $oDb = oxDb::getDb();
01187 
01188         $iShopId = ( !isset( $iShopId ) ) ? $this->getConfig()->getShopId():$iShopId;
01189         $iLang   = ( !isset( $iLang ) ) ? oxRegistry::getLang()->getObjectTplLanguage():((int) $iLang);
01190         return $oDb->getOne( "select {$sMetaType} from oxobject2seodata where oxobjectid = " . $oDb->quote( $sObjectId ) . " and oxshopid = " . $oDb->quote( $iShopId )." and oxlang = '{$iLang}'" );
01191     }
01192 
01206     public function getDynamicUrl( $sStdUrl, $sSeoUrl, $iLang )
01207     {
01208         startProfile("getDynamicUrl");
01209         $sDynUrl = $this->_getFullUrl( $this->_getDynamicUri( $sStdUrl, $sSeoUrl, $iLang ), $iLang, strpos( $sStdUrl, "https:" ) === 0 );
01210         stopProfile("getDynamicUrl");
01211         return $sDynUrl;
01212     }
01213 
01222     public function fetchSeoUrl( $sStdUrl, $iLanguage = null )
01223     {
01224         $oDb = oxDb::getDb( oxDb::FETCH_MODE_ASSOC );
01225         $iLanguage = isset( $iLanguage ) ? ( (int) $iLanguage ) : oxRegistry::getLang()->getBaseLanguage();
01226         $sSeoUrl   = false;
01227 
01228         $sShopId = $this->getConfig()->getShopId();
01229 
01230         $sQ = "SELECT `oxseourl`, `oxlang` FROM `oxseo` WHERE `oxstdurl` = " . $oDb->quote( $sStdUrl ) . " AND `oxlang` = '$iLanguage' AND `oxshopid` = '$sShopId' LIMIT 1";
01231 
01232         $oDb = oxDb::getDb( oxDb::FETCH_MODE_ASSOC );
01233         $oRs = $oDb->select( $sQ );
01234 
01235         if ( !$oRs->EOF ) {
01236             $sSeoUrl = $oRs->fields['oxseourl'];
01237         }
01238 
01239         return $sSeoUrl;
01240     }
01241 }