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 
00089     public static function getInstance()
00090     {
00091         if ( defined( 'OXID_PHP_UNIT' ) ) {
00092             self::$_instance = modInstances::getMod( __CLASS__ );
00093         }
00094 
00095         if (!self::$_instance) {
00096             self::$_instance = oxNew("oxSeoEncoder");
00097             if ( defined( 'OXID_PHP_UNIT' ) ) {
00098                 modInstances::addMod( __CLASS__, self::$_instance);
00099             }
00100         }
00101 
00102         return self::$_instance;
00103 
00104     }
00105 
00114     public function addLanguageParam( $sSeoUrl, $iLang )
00115     {
00116         $iLang    = (int) $iLang;
00117         $iDefLang = (int) $this->getConfig()->getConfigParam( 'iDefSeoLang' );
00118         $aLangIds = oxLang::getInstance()->getLanguageIds();
00119 
00120         if ( $iLang != $iDefLang && isset( $aLangIds[$iLang] ) && getStr()->strpos( $sSeoUrl, $aLangIds[$iLang] . '/' ) !== 0 ) {
00121             $sSeoUrl = $aLangIds[$iLang] . '/'.$sSeoUrl;
00122         }
00123 
00124         return $sSeoUrl;
00125     }
00126 
00139     protected function _processSeoUrl( $sSeoUrl, $sObjectId = null, $iLang = null, $blExclude = false )
00140     {
00141         if (!$blExclude) {
00142             $sSeoUrl = $this->addLanguageParam( $sSeoUrl, $iLang );
00143         }
00144         return $this->_getUniqueSeoUrl( $sSeoUrl, $sObjectId, $iLang );
00145     }
00146 
00150     public function __construct()
00151     {
00152         $myConfig = $this->getConfig();
00153         if (!self::$_sSeparator) {
00154             $this->setSeparator( $myConfig->getConfigParam( 'sSEOSeparator' ) );
00155         }
00156         if (!self::$_sPrefix) {
00157             $this->setPrefix( $myConfig->getConfigParam( 'sSEOuprefix' ) );
00158         }
00159         $this->setReservedWords( $myConfig->getConfigParam( 'aSEOReservedWords' ) );
00160     }
00161 
00173     protected function _copyToHistory( $sId, $iShopId, $iLang, $sType = null, $sNewId = null )
00174     {
00175         $oDb = oxDb::getDb();
00176         $sObjectid = $sNewId?$oDb->quote( $sNewId ):'oxobjectid';
00177         $sType     = $sType?"oxtype =".$oDb->quote( $sType )." and":'';
00178         $iLang     = (int) $iLang;
00179 
00180         // moving
00181         $sSub = "select $sObjectid, MD5( LOWER( oxseourl ) ), oxshopid, oxlang, now() from oxseo
00182                  where {$sType} oxobjectid = ".$oDb->quote( $sId )." and oxshopid = ".$oDb->quote( $iShopId )." and
00183                  oxlang = {$iLang} and oxexpired = '1'";
00184         $sQ   = "replace oxseohistory ( oxobjectid, oxident, oxshopid, oxlang, oxinsert ) {$sSub}";
00185         $oDb->execute( $sQ );
00186     }
00187 
00196     public function getDynamicObjectId( $iShopId, $sStdUrl )
00197     {
00198         return $this->_getStaticObjectId( $iShopId, $sStdUrl );
00199     }
00200 
00210     protected function _getDynamicUri( $sStdUrl, $sSeoUrl, $iLang )
00211     {
00212         $iShopId = $this->getConfig()->getShopId();
00213 
00214         $sStdUrl   = $this->_trimUrl( $sStdUrl );
00215         $sObjectId = $this->getDynamicObjectId( $iShopId, $sStdUrl );
00216         $sSeoUrl   = $this->_prepareUri( $this->addLanguageParam( $sSeoUrl, $iLang ), $iLang );
00217 
00218         //load details link from DB
00219         $sOldSeoUrl = $this->_loadFromDb( 'dynamic', $sObjectId, $iLang );
00220         if ( $sOldSeoUrl === $sSeoUrl ) {
00221             $sSeoUrl = $sOldSeoUrl;
00222         } else {
00223 
00224             if ( $sOldSeoUrl ) {
00225                 // old must be transferred to history
00226                 $this->_copyToHistory( $sObjectId, $iShopId, $iLang, 'dynamic' );
00227             }
00228 
00229             // creating unique
00230             $sSeoUrl = $this->_processSeoUrl( $sSeoUrl, $sObjectId, $iLang );
00231 
00232             // inserting
00233             $this->_saveToDb( 'dynamic', $sObjectId, $sStdUrl, $sSeoUrl, $iLang, $iShopId );
00234         }
00235 
00236         return $sSeoUrl;
00237     }
00238 
00248     protected function _getFullUrl( $sSeoUrl, $iLang = null, $blSsl = false )
00249     {
00250         if ( $sSeoUrl ) {
00251             $sFullUrl = ( $blSsl ? $this->getConfig()->getSslShopUrl( $iLang ) : $this->getConfig()->getShopUrl( $iLang, false ) ) . $sSeoUrl;
00252             return oxUtilsUrl::getInstance()->processSeoUrl( $sFullUrl );
00253         }
00254         return false;
00255     }
00256 
00266     protected function _getSeoIdent( $sSeoUrl )
00267     {
00268         return md5( strtolower( $sSeoUrl ) );
00269     }
00270 
00280     protected function _getStaticUri( $sStdUrl, $iShopId, $iLang )
00281     {
00282         $sStdUrl = $this->_trimUrl( $sStdUrl, $iLang );
00283         return $this->_loadFromDb( 'static', $this->_getStaticObjectId( $iShopId, $sStdUrl ), $iLang );
00284     }
00285 
00291     protected function _getUrlExtension()
00292     {
00293         return;
00294     }
00295 
00308     protected function _getUniqueSeoUrl( $sSeoUrl, $sObjectId = null, $iObjectLang = null )
00309     {
00310         $sSeoUrl = $this->_prepareUri( $sSeoUrl, $iObjectLang );
00311         $oStr = getStr();
00312         $sExt = '';
00313         if ( $oStr->preg_match( '/(\.html?|\/)$/i', $sSeoUrl, $aMatched ) ) {
00314             $sExt = $aMatched[0];
00315         }
00316         $sBaseSeoUrl = $sSeoUrl;
00317         if ( $sExt && $oStr->substr( $sSeoUrl, 0 - $oStr->strlen( $sExt ) ) == $sExt ) {
00318             $sBaseSeoUrl = $oStr->substr( $sSeoUrl, 0, $oStr->strlen( $sSeoUrl ) - $oStr->strlen( $sExt ) );
00319         }
00320 
00321         $iShopId = $this->getConfig()->getShopId();
00322         $iCnt = 0;
00323         $sCheckSeoUrl = $this->_trimUrl( $sSeoUrl );
00324         $sQ = "select 1 from oxseo where oxshopid = '{$iShopId}'";
00325 
00326         $oDb = oxDb::getDb();
00327         // skipping self
00328         if ( $sObjectId && isset($iObjectLang) ) {
00329             $iObjectLang = (int) $iObjectLang;
00330             $sQ .= " and not (oxobjectid = " . $oDb->quote( $sObjectId ) . " and oxlang = $iObjectLang)";
00331         }
00332 
00333         while ( $oDb->getOne( $sQ ." and oxident= " . $oDb->quote( $this->_getSeoIdent( $sCheckSeoUrl ) ) ) ) {
00334             $sAdd = '';
00335             if ( self::$_sPrefix ) {
00336                 $sAdd = self::$_sSeparator . self::$_sPrefix;
00337             }
00338             if ( $iCnt ) {
00339                 $sAdd .= self::$_sSeparator . $iCnt;
00340             }
00341             ++$iCnt;
00342 
00343             $sSeoUrl = $sBaseSeoUrl . $sAdd . $sExt;
00344             $sCheckSeoUrl = $this->_trimUrl( $sSeoUrl );
00345         }
00346         return $sSeoUrl;
00347     }
00348 
00363     protected function _isFixed( $sType, $sId, $iLang, $iShopId = null, $sParams = null, $blStrictParamsCheck = true)
00364     {
00365         if ( $iShopId === null ) {
00366             $iShopId = $this->getConfig()->getShopId();
00367         }
00368         $iLang = (int) $iLang;
00369 
00370         if ( !isset( self::$_aFixedCache[$sType][$sShopId][$sId][$iLang] ) ) {
00371             $oDb = oxDb::getDb();
00372 
00373             $sQ = "SELECT `oxfixed`
00374                 FROM `oxseo`
00375                 WHERE `oxtype` = ".$oDb->quote( $sType )."
00376                    AND `oxobjectid` = ".$oDb->quote( $sId ) ."
00377                    AND `oxshopid` = ".$oDb->quote( $iShopId )."
00378                    AND `oxlang` = '{$iLang}'";
00379 
00380             $sParams = $sParams ? $oDb->quote( $sParams ) : "''";
00381             if ( $sParams && $blStrictParamsCheck ) {
00382                 $sQ .= " AND `oxparams` = {$sParams}";
00383             } else {
00384                 $sQ .= " ORDER BY `oxparams` ASC";
00385             }
00386             $sQ .= " LIMIT 1";
00387 
00388             self::$_aFixedCache[$sType][$sShopId][$sId][$iLang] = (bool) $oDb->getOne( $sQ );
00389         }
00390         return self::$_aFixedCache[$sType][$sShopId][$sId][$iLang];
00391     }
00392 
00403     protected function _getCacheKey( $sType, $iLang = null, $iShopId = null, $sParams = null )
00404     {
00405         $blAdmin = $this->isAdmin();
00406         if ( !$blAdmin && $sType !== "oxarticle" ) {
00407             return $sType . ( (int) $iLang ) . ( (int) $iShopId ) . "seo";
00408         }
00409 
00410         // use cache in non admin mode
00411         if ( self::$_sCacheKey === null ) {
00412             self::$_sCacheKey = false;
00413             if ( !$blAdmin && ( $oView = $this->getConfig()->getActiveView() ) ) {
00414                 self::$_sCacheKey = md5( $oView->getViewId() ) . "seo";
00415             }
00416         }
00417         return self::$_sCacheKey;
00418     }
00419 
00431     protected function _loadFromCache( $sCacheIdent, $sType, $iLang = null, $iShopId = null, $sParams = null )
00432     {
00433         if ( !$this->getConfig()->getConfigParam( 'blEnableSeoCache' ) ) {
00434             return false;
00435         }
00436 
00437         startProfile( "seoencoder_loadFromCache" );
00438 
00439         $sCacheKey = $this->_getCacheKey( $sType, $iLang, $iShopId, $sParams );
00440         $sCache = false;
00441 
00442         if ( $sCacheKey && !isset( self::$_aCache[$sCacheKey] ) ) {
00443             self::$_aCache[$sCacheKey] = oxUtils::getInstance()->fromFileCache( $sCacheKey );
00444         }
00445 
00446         if ( isset( self::$_aCache[$sCacheKey] ) && isset( self::$_aCache[$sCacheKey][$sCacheIdent] ) ) {
00447             $sCache = self::$_aCache[$sCacheKey][$sCacheIdent];
00448         }
00449 
00450         stopProfile( "seoencoder_loadFromCache" );
00451         return $sCache;
00452     }
00453 
00466     protected function _saveInCache( $sCacheIdent, $sCache, $sType, $iLang = null, $iShopId = null, $sParams = null )
00467     {
00468         if ( !$this->getConfig()->getConfigParam( 'blEnableSeoCache' ) ) {
00469             return false;
00470         }
00471 
00472         startProfile( "seoencoder_saveInCache" );
00473 
00474         $blSaved = false;
00475         if ( $sCache && ( $sCacheKey = $this->_getCacheKey( $sType, $iLang, $iShopId, $sParams ) ) !== false ) {
00476             self::$_aCache[$sCacheKey][$sCacheIdent] = $sCache;
00477             $blSaved = oxUtils::getInstance()->toFileCache( $sCacheKey, self::$_aCache[$sCacheKey] );
00478         }
00479 
00480         stopProfile( "seoencoder_saveInCache" );
00481         return $blSaved;
00482     }
00483 
00499     protected function _loadFromDb( $sType, $sId, $iLang, $iShopId = null, $sParams = null, $blStrictParamsCheck = true)
00500     {
00501 
00502         if ( $iShopId === null ) {
00503             $iShopId = $this->getConfig()->getShopId();
00504         }
00505 
00506         $iLang = (int) $iLang;
00507         $oDb = oxDb::getDb( oxDb::FETCH_MODE_ASSOC );
00508 
00509         $sQ = "
00510             SELECT
00511                 `oxfixed`,
00512                 `oxseourl`,
00513                 `oxexpired`,
00514                 `oxtype`
00515             FROM `oxseo`
00516             WHERE `oxtype` = ".$oDb->quote( $sType )."
00517                AND `oxobjectid` = ".$oDb->quote( $sId ) ."
00518                AND `oxshopid` = ".$oDb->quote( $iShopId )."
00519                AND `oxlang` = '{$iLang}'";
00520 
00521         $sParams = $sParams ? $sParams : '';
00522         if ( $sParams && $blStrictParamsCheck ) {
00523             $sQ .= " AND `oxparams` = '{$sParams}'";
00524         } else {
00525             $sQ .= " ORDER BY `oxparams` ASC";
00526         }
00527         $sQ .= " LIMIT 1";
00528 
00529 
00530         // caching to avoid same queries..
00531         $sIdent = md5( $sQ );
00532 
00533         // looking in cache
00534         if ( ( $sSeoUrl = $this->_loadFromCache( $sIdent, $sType, $iLang, $iShopId, $sParams ) ) === false ) {
00535             $oRs = $oDb->select( $sQ );
00536 
00537             if ( $oRs && $oRs->recordCount() > 0 && !$oRs->EOF ) {
00538                 // moving expired static urls to history ..
00539                 if ( $oRs->fields['oxexpired'] && ( $oRs->fields['oxtype'] == 'static' || $oRs->fields['oxtype'] == 'dynamic' ) ) {
00540                     // if expired - copying to history, marking as not expired
00541                     $this->_copyToHistory( $sId, $iShopId, $iLang );
00542                     $oDb->execute( "update oxseo set oxexpired = 0 where oxobjectid = ".$oDb->quote( $sId )." and oxlang = '{$iLang}'" );
00543                     $sSeoUrl = $oRs->fields['oxseourl'];
00544                 } elseif ( !$oRs->fields['oxexpired'] || $oRs->fields['oxfixed'] ) {
00545                     // if seo url is available and is valid
00546                     $sSeoUrl = $oRs->fields['oxseourl'];
00547                 }
00548 
00549                 // storing in cache
00550                 $this->_saveInCache( $sIdent, $sSeoUrl, $sType, $iLang, $iShopId, $sParams );
00551             }
00552         }
00553         return $sSeoUrl;
00554     }
00555 
00562     protected function _getReservedEntryKeys()
00563     {
00564         if ( !isset( self::$_aReservedEntryKeys ) || !is_array( self::$_aReservedEntryKeys ) ) {
00565             $sDir = getShopBasePath();
00566             self::$_aReservedEntryKeys = array_map('preg_quote', self::$_aReservedWords, array('#'));
00567             $oStr = getStr();
00568             foreach ( glob( "$sDir/*" ) as $sFile ) {
00569                 if ( $oStr->preg_match( '/^(.+)\.php[0-9]*$/i', basename( $sFile ), $aMatches ) ) {
00570                     self::$_aReservedEntryKeys[] = preg_quote( $aMatches[0], '#' );
00571                     self::$_aReservedEntryKeys[] = preg_quote( $aMatches[1], '#' );
00572                 } elseif ( is_dir( $sFile ) ) {
00573                     self::$_aReservedEntryKeys[] = preg_quote( basename( $sFile ), '#' );
00574                 }
00575             }
00576             self::$_aReservedEntryKeys = array_unique(self::$_aReservedEntryKeys);
00577         }
00578         return self::$_aReservedEntryKeys;
00579     }
00580 
00589     protected function _prepareUri( $sUri, $iLang = false )
00590     {
00591         // decoding entities
00592         $sUri = $this->encodeString( $sUri, true, $iLang );
00593 
00594         // basic string preparation
00595         $oStr = getStr();
00596         $sUri = $oStr->strip_tags( $sUri );
00597 
00598         // if found ".html" or "/" at the end - removing it temporary
00599         $sExt = $this->_getUrlExtension();
00600         if ($sExt === null) {
00601             $aMatched = array();
00602             if ( $oStr->preg_match( '/(\.html?|\/)$/i', $sUri, $aMatched ) ) {
00603                 $sExt = $aMatched[0];
00604             } else {
00605                 $sExt = '/';
00606             }
00607         }
00608         if ( $sExt && $oStr->substr( $sUri, 0 - $oStr->strlen( $sExt ) ) == $sExt ) {
00609             $sUri = $oStr->substr( $sUri, 0, $oStr->strlen( $sUri ) - $oStr->strlen( $sExt ) );
00610         }
00611 
00612         // removing any special characters
00613         // #0004282 bugfix, php <5.3 does not escape - char, so we do it manually
00614         $sQuotedPrefix = preg_quote( self::$_sSeparator . self::$_sPrefix, '/');
00615         if ( phpversion() < '5.3' ) {
00616             $sQuotedPrefix = str_replace( '-', '\-', $sQuotedPrefix );
00617         }
00618         $sRegExp = '/[^A-Za-z0-9' . $sQuotedPrefix . '\/]+/';
00619         $sUri  = $oStr->preg_replace( array( "/\W*\/\W*/", $sRegExp ), array( "/", self::$_sSeparator ), $sUri );
00620 
00621         // SEO id is empty ?
00622         if ( !$sUri && self::$_sPrefix ) {
00623             $sUri = $this->_prepareUri( self::$_sPrefix, $iLang );
00624         }
00625 
00626         $sAdd = '';
00627         if ('/' != self::$_sSeparator) {
00628             $sAdd = self::$_sSeparator . self::$_sPrefix;
00629             $sUri = trim($sUri, self::$_sSeparator);
00630         } else {
00631             $sAdd = '_' . self::$_sPrefix;
00632         }
00633 
00634         // binding the ending back
00635         $sUri .= $sExt;
00636 
00637         // fix for not having url, which executes through /other/ script then seo decoder
00638         $sUri = $oStr->preg_replace( "#^(/*)(".implode('|', $this->_getReservedEntryKeys()).")(/|$)#i", "\$1\$2$sAdd\$3", $sUri );
00639 
00640         // cleaning
00641         // #0004282 bugfix, php < 5.3 does not escape - char, so we do it manually\
00642         $sQuotedSeparator = preg_quote( self::$_sSeparator, '/');
00643         if ( phpversion() < '5.3' ) {
00644             $sQuotedSeparator = str_replace( '-', '\-', $sQuotedSeparator );
00645         }
00646         return $oStr->preg_replace( array( '|//+|', '/' . $sQuotedSeparator . $sQuotedSeparator .'+/' ),
00647                              array( '/', self::$_sSeparator ), $sUri );
00648     }
00649 
00650 
00660     protected function _prepareTitle( $sTitle, $blSkipTruncate = false, $iLang = false )
00661     {
00662         $sTitle = $this->encodeString( $sTitle, true, $iLang );
00663         $sSep = self::$_sSeparator;
00664         if (!$sSep || ('/' == $sSep)) {
00665             $sSep = '_';
00666         }
00667 
00668         $sRegExp = '/[^A-Za-z0-9\/'.preg_quote( self::$_sPrefix, '/').preg_quote($sSep, '/').']+/';
00669         $sTitle = preg_replace( array("#/+#", $sRegExp, "# +#", "#(".preg_quote($sSep, '/').")+#"), $sSep, $sTitle );
00670 
00671         $oStr = getStr();
00672         // smart truncate
00673         if ( !$blSkipTruncate && $oStr->strlen( $sTitle ) > $this->_iIdLength ) {
00674             $iFirstSpace = $oStr->strpos( $sTitle, $sSep, $this->_iIdLength);
00675             if ( $iFirstSpace !== false ) {
00676                 $sTitle = $oStr->substr( $sTitle, 0, $iFirstSpace );
00677             }
00678         }
00679 
00680         $sTitle = trim( $sTitle, $sSep );
00681 
00682         if (!$sTitle) {
00683             return self::$_sPrefix;
00684         }
00685         // cleaning
00686         return $sTitle;
00687     }
00688 
00689 
00706     protected function _saveToDb( $sType, $sObjectId, $sStdUrl, $sSeoUrl, $iLang, $iShopId = null, $blFixed = null, $sParams = null )
00707     {
00708         $oDb = oxDb::getDb( oxDb::FETCH_MODE_ASSOC );
00709         if ( $iShopId === null ) {
00710             $iShopId = $this->getConfig()->getShopId();
00711         }
00712 
00713         $iLang = (int) $iLang;
00714 
00715         $sStdUrl = $this->_trimUrl( $sStdUrl );
00716         $sSeoUrl = $this->_trimUrl( $sSeoUrl );
00717         $sIdent  = $this->_getSeoIdent( $sSeoUrl );
00718 
00719         // transferring old url, thus current url will be regenerated
00720         $sQtedObjectId = $oDb->quote( $sObjectId );
00721         $iQtedShopId   = $oDb->quote( $iShopId );
00722         $sQtedType     = $oDb->quote( $sType );
00723         $sQtedSeoUrl   = $oDb->quote( $sSeoUrl );
00724         $sQtedStdUrl   = $oDb->quote( $sStdUrl );
00725         $sQtedParams   = $oDb->quote( $sParams );
00726         $sQtedIdent    = $oDb->quote( $sIdent );
00727 
00728         // transferring old url, thus current url will be regenerated
00729         $sQ  = "select oxfixed, oxexpired, ( oxstdurl like {$sQtedStdUrl} ) as samestdurl,
00730                 oxseourl like {$sQtedSeoUrl} as sameseourl from oxseo where oxtype = {$sQtedType} and
00731                 oxobjectid = {$sQtedObjectId} and oxshopid = {$iQtedShopId}  and oxlang = {$iLang} ";
00732 
00733         $sQ .= $sParams ? " and oxparams = {$sQtedParams} " : '';
00734         $sQ .= "limit 1";
00735 
00736         $oRs = $oDb->select( $sQ );
00737         if ( $oRs && $oRs->recordCount() > 0 && !$oRs->EOF ) {
00738             if ( $oRs->fields['samestdurl'] && $oRs->fields['sameseourl'] && $oRs->fields['oxexpired'] ) {
00739                 // fixed state change
00740                 $sFixed = isset( $blFixed ) ? ", oxfixed = " . ( (int) $blFixed ) . " " : '';
00741                 // nothing was changed - setting expired status back to 0
00742                 $sSql  = "update oxseo set oxexpired = 0 {$sFixed} where oxtype = {$sQtedType} and
00743                           oxobjectid = {$sQtedObjectId} and oxshopid = {$iQtedShopId} and oxlang = {$iLang} ";
00744                 $sSql .= $sParams ? " and oxparams = {$sQtedParams} " : '';
00745                 $sSql .= " limit 1";
00746 
00747                 return $oDb->execute( $sSql );
00748             } elseif ( $oRs->fields['oxexpired'] ) {
00749                 // copy to history
00750                 $this->_copyToHistory( $sObjectId, $iShopId, $iLang, $sType );
00751             }
00752         }
00753 
00754         // inserting new or updating
00755         $sParams = $sParams ? $oDb->quote( $sParams ) :'""';
00756         $blFixed = (int) $blFixed;
00757 
00758         $sQ  = "insert into oxseo
00759                     (oxobjectid, oxident, oxshopid, oxlang, oxstdurl, oxseourl, oxtype, oxfixed, oxexpired, oxparams)
00760                 values
00761                     ( {$sQtedObjectId}, {$sQtedIdent}, {$iQtedShopId}, {$iLang}, {$sQtedStdUrl}, {$sQtedSeoUrl}, {$sQtedType}, '$blFixed', '0', {$sParams} )
00762                 on duplicate key update
00763                     oxident = {$sQtedIdent}, oxstdurl = {$sQtedStdUrl}, oxseourl = {$sQtedSeoUrl}, oxfixed = '$blFixed', oxexpired = '0'";
00764 
00765         return $oDb->execute( $sQ );
00766     }
00767 
00778     protected function _trimUrl( $sUrl, $iLang = null )
00779     {
00780         $myConfig = $this->getConfig();
00781         $oStr = getStr();
00782         $sUrl = str_replace( array( $myConfig->getShopUrl( $iLang, false ), $myConfig->getSslShopUrl( $iLang ) ), '', $sUrl );
00783         $sUrl = $oStr->preg_replace( '/(\?|&(amp;)?)(force_)?(admin_)?sid=[a-z0-9\.]+&?(amp;)?/i', '\1', $sUrl );
00784         $sUrl = $oStr->preg_replace( '/(\?|&(amp;)?)shp=[0-9]+&?(amp;)?/i', '\1', $sUrl );
00785         $sUrl = $oStr->preg_replace( '/(\?|&(amp;)?)lang=[0-9]+&?(amp;)?/i', '\1', $sUrl );
00786         $sUrl = $oStr->preg_replace( '/(\?|&(amp;)?)cur=[0-9]+&?(amp;)?/i', '\1', $sUrl );
00787         $sUrl = $oStr->preg_replace( '/(\?|&(amp;)?)stoken=[a-z0-9]+&?(amp;)?/i', '\1', $sUrl );
00788         $sUrl = $oStr->preg_replace( '/(\?|&(amp;)?)&(amp;)?/i', '\1', $sUrl );
00789         $sUrl = $oStr->preg_replace( '/(\?|&(amp;)?)+$/i', '', $sUrl );
00790         $sUrl = trim( $sUrl );
00791 
00792         // max length <= $this->_iMaxUrlLength
00793         $iLength = $this->_getMaxUrlLength();
00794         if ( $oStr->strlen( $sUrl ) > $iLength ) {
00795             $sUrl = $oStr->substr( $sUrl, 0, $iLength );
00796         }
00797 
00798         return $sUrl;
00799     }
00800 
00806     protected function _getMaxUrlLength()
00807     {
00808         if ( $this->_iMaxUrlLength === null ) {
00809             // max length <= 2048 / custom
00810             $this->_iMaxUrlLength = $this->getConfig()->getConfigParam( "iMaxSeoUrlLength" ) ;
00811             if ( !$this->_iMaxUrlLength ) {
00812                 $this->_iMaxUrlLength = 2048;
00813             }
00814         }
00815         return $this->_iMaxUrlLength;
00816     }
00817 
00827     public function encodeString( $sString, $blReplaceChars = true, $iLang = false )
00828     {
00829         // decoding entities
00830         $sString = getStr()->html_entity_decode( $sString );
00831 
00832         if ( $blReplaceChars ) {
00833             if ($iLang === false || !is_numeric($iLang)) {
00834                 $iLang = oxLang::getInstance()->getEditLanguage();
00835             }
00836 
00837             if ( $aReplaceChars = oxLang::getInstance()->getSeoReplaceChars($iLang) ) {
00838                 $sString = str_replace( array_keys( $aReplaceChars ), array_values( $aReplaceChars ), $sString );
00839             }
00840         }
00841 
00842 
00843         // special chars
00844         $aReplaceWhat = array( '&amp;', '&quot;', '&#039;', '&lt;', '&gt;' );
00845         return str_replace( $aReplaceWhat, '', $sString );
00846     }
00847 
00855     public function setSeparator( $sSeparator = null )
00856     {
00857         self::$_sSeparator = $sSeparator;
00858         if ( !self::$_sSeparator ) {
00859             self::$_sSeparator = '-';
00860         }
00861     }
00862 
00870     public function setPrefix( $sPrefix )
00871     {
00872         if ($sPrefix) {
00873             self::$_sPrefix = $sPrefix;
00874         } else {
00875             self::$_sPrefix = 'oxid';
00876         }
00877     }
00878 
00886     public function setIdLength( $iIdlength = null )
00887     {
00888         if ( isset( $iIdlength ) ) {
00889             $this->_iIdLength = $iIdlength;
00890         }
00891     }
00892 
00901     public function setReservedWords( $aReservedWords )
00902     {
00903         self::$_aReservedWords = array_merge( self::$_aReservedWords, $aReservedWords );
00904     }
00905 
00906 
00918     public function markAsExpired( $sId, $iShopId = null, $iExpStat = 1, $iLang = null, $sParams = null )
00919     {
00920         $oDb = oxDb::getDb();
00921         $sWhere  = $sId ? "where oxobjectid =  " . $oDb->quote( $sId ) : '';
00922         $sWhere .= isset( $iShopId ) ? ( $sWhere ? " and oxshopid = ". $oDb->quote( $iShopId ) : "where oxshopid = ". $oDb->quote( $iShopId ) ) : '';
00923         $sWhere .= $iLang ? ( $sWhere ? " and oxlang = '{$iLang}'" : "where oxlang = '{$iLang}'" ) : '';
00924         $sWhere .= $sParams ? ( $sWhere ? " and {$sParams}" : "where {$sParams}" ) : '';
00925 
00926         $sQ = "update oxseo set oxexpired =  " . $oDb->quote( $iExpStat ) . " $sWhere ";
00927         $oDb->execute( $sQ );
00928     }
00929 
00943     protected function _getPageUri( $oObject, $sType, $sStdUrl, $sSeoUrl, $sParams, $iLang = null, $blFixed = false )
00944     {
00945         if ( !isset( $iLang ) ) {
00946             $iLang = $oObject->getLanguage();
00947         }
00948         $iShopId = $this->getConfig()->getShopId();
00949 
00950         //load page link from DB
00951         $sOldSeoUrl = $this->_loadFromDb( $sType, $oObject->getId(), $iLang, $iShopId, $sParams );
00952         if ( !$sOldSeoUrl ) {
00953             // generating new..
00954             $sSeoUrl = $this->_processSeoUrl( $sSeoUrl, $oObject->getId(), $iLang );
00955             $this->_saveToDb( $sType, $oObject->getId(), $sStdUrl, $sSeoUrl, $iLang, $iShopId, (int) $blFixed, $sParams );
00956         } else {
00957             // using old
00958             $sSeoUrl = $sOldSeoUrl;
00959         }
00960         return $sSeoUrl;
00961     }
00962 
00971     protected function _getStaticObjectId( $iShopId, $sStdUrl )
00972     {
00973         return md5( strtolower ( $iShopId . $this->_trimUrl( $sStdUrl ) ) );
00974     }
00975 
00985     public function encodeStaticUrls( $aStaticUrl, $iShopId, $iLang )
00986     {
00987         $oDb = oxDb::getDb();
00988         $sValues = '';
00989         $sOldObjectId = null;
00990 
00991         // standard url
00992         $sStdUrl = $this->_trimUrl( trim( $aStaticUrl['oxseo__oxstdurl'] ) );
00993         $sObjectId = $aStaticUrl['oxseo__oxobjectid'];
00994 
00995         if ( !$sObjectId || $sObjectId == '-1' ) {
00996             $sObjectId = $this->_getStaticObjectId( $iShopId, $sStdUrl );
00997         } else {
00998             // marking entry as needs to move to history
00999             $sOldObjectId = $sObjectId;
01000 
01001             // if std url does not match old
01002             if ( $this->_getStaticObjectId( $iShopId, $sStdUrl ) != $sObjectId ) {
01003                 $sObjectId = $this->_getStaticObjectId( $iShopId, $sStdUrl );
01004             }
01005         }
01006 
01007         foreach ( $aStaticUrl['oxseo__oxseourl'] as $iLang => $sSeoUrl ) {
01008 
01009             $iLang = (int) $iLang;
01010 
01011             // generating seo url
01012             $sSeoUrl = $this->_trimUrl( $sSeoUrl );
01013             if ( $sSeoUrl ) {
01014                 $sSeoUrl = $this->_processSeoUrl( $sSeoUrl, $sObjectId, $iLang );
01015             }
01016 
01017 
01018             if ( $sOldObjectId ) {
01019                 // move changed records to history
01020                 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) ) {
01021                     $this->_copyToHistory( $sOldObjectId, $iShopId, $iLang, 'static', $sObjectId );
01022                 }
01023             }
01024 
01025             if ( !$sSeoUrl || !$sStdUrl ) {
01026                 continue;
01027             }
01028 
01029             $sIdent = $this->_getSeoIdent( $sSeoUrl );
01030 
01031             if ( $sValues ) {
01032                 $sValues .= ', ';
01033             }
01034 
01035             $sValues .= "( " . $oDb->quote( $sObjectId ) . ", " . $oDb->quote( $sIdent ) . ", " . $oDb->quote( $iShopId ).", '{$iLang}', " . $oDb->quote( $sStdUrl ) . ", " . $oDb->quote( $sSeoUrl ) . ", 'static' )";
01036         }
01037 
01038         // must delete old before insert/update
01039         if ( $sOldObjectId ) {
01040             $oDb->execute( "delete from oxseo where oxobjectid in ( " . $oDb->quote( $sOldObjectId ) . ", " . $oDb->quote( $sObjectId ) . " )" );
01041         }
01042 
01043         // (re)inserting
01044         if ( $sValues ) {
01045 
01046             $sQ = "insert into oxseo ( oxobjectid, oxident, oxshopid, oxlang, oxstdurl, oxseourl, oxtype ) values {$sValues} ";
01047             $oDb->execute( $sQ );
01048         }
01049 
01050         return $sObjectId;
01051     }
01052 
01060     public function copyStaticUrls( $iShopId )
01061     {
01062         $iBaseShopId = $this->getConfig()->getBaseShopId();
01063         if ( $iShopId != $iBaseShopId ) {
01064             $oDb = oxDb::getDb();
01065             foreach (array_keys(oxLang::getInstance()->getLanguageIds()) as $iLang) {
01066                 $sQ = "insert into oxseo ( oxobjectid, oxident, oxshopid, oxlang, oxstdurl, oxseourl, oxtype )
01067                        select MD5( LOWER( CONCAT( " . $oDb->quote( $iShopId ) . ", oxstdurl ) ) ), MD5( LOWER( oxseourl ) ),
01068                        " . $oDb->quote( $iShopId ) . ", oxlang, oxstdurl, oxseourl, oxtype from oxseo where oxshopid = '{$iBaseShopId}' and oxtype = 'static' and oxlang='$iLang' ";
01069                 $oDb->execute( $sQ );
01070             }
01071         }
01072     }
01073 
01083     public function getStaticUrl( $sStdUrl, $iLang = null, $iShopId = null )
01084     {
01085         if (!isset($iShopId)) {
01086             $iShopId = $this->getConfig()->getShopId();
01087         }
01088         if (!isset($iLang)) {
01089             $iLang   = oxLang::getInstance()->getEditLanguage();
01090         }
01091 
01092         if ( isset($this->_aStaticUrlCache[$sStdUrl][$iLang][$iShopId])) {
01093             return $this->_aStaticUrlCache[$sStdUrl][$iLang][$iShopId];
01094         }
01095 
01096         $sFullUrl = '';
01097         if ( ( $sSeoUrl = $this->_getStaticUri( $sStdUrl, $iShopId, $iLang ) ) ) {
01098             $sFullUrl = $this->_getFullUrl( $sSeoUrl, $iLang, strpos( $sStdUrl, "https:" ) === 0 );
01099         }
01100 
01101 
01102         $this->_aStaticUrlCache[$sStdUrl][$iLang][$iShopId] = $sFullUrl;
01103 
01104         return $sFullUrl;
01105     }
01106 
01125     public function addSeoEntry( $sObjectId, $iShopId, $iLang, $sStdUrl, $sSeoUrl, $sType, $blFixed = 1, $sKeywords = '', $sDescription = '', $sParams = '', $blExclude = false, $sAltObjectId = null )
01126     {
01127         $sSeoUrl = $this->_processSeoUrl( $this->_trimUrl( $sSeoUrl ? $sSeoUrl : $this->_getAltUri( $sAltObjectId ? $sAltObjectId : $sObjectId, $iLang ) ), $sObjectId, $iLang, $blExclude );
01128         if ( $this->_saveToDb( $sType, $sObjectId, $sStdUrl, $sSeoUrl, $iLang, $iShopId, $blFixed, $sParams ) ) {
01129 
01130             $oDb = oxDb::getDb();
01131 
01132             //
01133             $sQtedObjectId = $oDb->quote( $sAltObjectId ? $sAltObjectId : $sObjectId );
01134             $iQtedShopId   = $oDb->quote( $iShopId );
01135 
01136             $oStr = getStr();
01137             if ( $sKeywords !== false ) {
01138                 $sKeywords = $oDb->quote( $oStr->htmlspecialchars( $this->encodeString( $oStr->strip_tags( $sKeywords ), false, $iLang ) ) );
01139             }
01140 
01141             if ( $sDescription !== false ) {
01142                 $sDescription = $oDb->quote( $oStr->htmlspecialchars( $oStr->strip_tags( $sDescription ) ) );
01143             }
01144 
01145             $sQ = "insert into oxobject2seodata
01146                        ( oxobjectid, oxshopid, oxlang, oxkeywords, oxdescription )
01147                    values
01148                        ( {$sQtedObjectId}, {$iQtedShopId}, {$iLang}, ".( $sKeywords ? $sKeywords : "''" ).", ".( $sDescription ? $sDescription : "''" )." )
01149                    on duplicate key update
01150                        oxkeywords = ".( $sKeywords ? $sKeywords : "oxkeywords" ).", oxdescription = ".( $sDescription ? $sDescription : "oxdescription" );
01151             $oDb->execute( $sQ );
01152         }
01153     }
01154 
01163     protected function _getAltUri( $sObjectId, $iLang )
01164     {
01165     }
01166 
01177     public function deleteSeoEntry( $sObjectId, $iShopId, $iLang, $sType )
01178     {
01179         $oDb = oxDb::getDb();
01180         $sQ = "delete from oxseo where oxobjectid = " . $oDb->quote( $sObjectId ) . " and oxshopid = " . $oDb->quote( $iShopId ) . " and oxlang = " . $oDb->quote( $iLang ) . " and oxtype = " . $oDb->quote( $sType ) . " ";
01181         $oDb->execute( $sQ );
01182     }
01183 
01194     public function getMetaData( $sObjectId, $sMetaType, $iShopId = null, $iLang = null )
01195     {
01196         $oDb = oxDb::getDb();
01197 
01198         $iShopId = ( !isset( $iShopId ) ) ? $this->getConfig()->getShopId():$iShopId;
01199         $iLang   = ( !isset( $iLang ) ) ? oxLang::getInstance()->getObjectTplLanguage():((int) $iLang);
01200         return $oDb->getOne( "select {$sMetaType} from oxobject2seodata where oxobjectid = " . $oDb->quote( $sObjectId ) . " and oxshopid = " . $oDb->quote( $iShopId )." and oxlang = '{$iLang}'" );
01201     }
01202 
01216     public function getDynamicUrl( $sStdUrl, $sSeoUrl, $iLang )
01217     {
01218         startProfile("getDynamicUrl");
01219         $sDynUrl = $this->_getFullUrl( $this->_getDynamicUri( $sStdUrl, $sSeoUrl, $iLang ), strpos( $sStdUrl, "https:" ) === 0 );
01220         stopProfile("getDynamicUrl");
01221         return $sDynUrl;
01222     }
01223 
01232     public function fetchSeoUrl( $sStdUrl, $iLanguage = null )
01233     {
01234         $oDb = oxDb::getDb( oxDb::FETCH_MODE_ASSOC );
01235         $iLanguage = isset( $iLanguage ) ? ( (int) $iLanguage ) : oxLang::getInstance()->getBaseLanguage();
01236         $sSeoUrl   = false;
01237 
01238         $sShopId = $this->getConfig()->getShopId();
01239 
01240         $sQ = "SELECT `oxseourl`, `oxlang` FROM `oxseo` WHERE `oxstdurl` = " . $oDb->quote( $sStdUrl ) . " AND `oxlang` = '$iLanguage' AND `oxshopid` = '$sShopId' LIMIT 1";
01241 
01242         $oRs = $oDb->select( $sQ );
01243         if ( !$oRs->EOF ) {
01244             $sSeoUrl = $oRs->fields['oxseourl'];
01245         }
01246 
01247         return $sSeoUrl;
01248     }
01249 }