OXID eShop CE  4.8.12
 All Classes Files Functions Variables Pages
oxseoencoder.php
Go to the documentation of this file.
1 <?php
2 
7 class oxSeoEncoder extends oxSuperCfg
8 {
15  protected static $_aReservedWords = array( 'admin' );
16 
22  protected static $_aReservedEntryKeys = null;
23 
29  protected static $_sSeparator = null;
30 
36  protected $_iIdLength = 255;
37 
43  protected static $_sPrefix = null;
44 
50  protected $_sAddParams = null;
51 
57  protected static $_instance = null;
58 
64  protected static $_aFixedCache = array();
65 
70  protected static $_sCacheKey = null;
71 
76  protected static $_aCache = array();
77 
82  protected $_iMaxUrlLength = null;
83 
91  public static function getInstance()
92  {
93  return oxRegistry::get("oxSeoEncoder");
94  }
95 
104  public function addLanguageParam( $sSeoUrl, $iLang )
105  {
106  $iLang = (int) $iLang;
107  $iDefLang = (int) $this->getConfig()->getConfigParam( 'iDefSeoLang' );
108  $aLangIds = oxRegistry::getLang()->getLanguageIds();
109 
110  if ( $iLang != $iDefLang && isset( $aLangIds[$iLang] ) && getStr()->strpos( $sSeoUrl, $aLangIds[$iLang] . '/' ) !== 0 ) {
111  $sSeoUrl = $aLangIds[$iLang] . '/'.$sSeoUrl;
112  }
113 
114  return $sSeoUrl;
115  }
116 
129  protected function _processSeoUrl( $sSeoUrl, $sObjectId = null, $iLang = null, $blExclude = false )
130  {
131  if (!$blExclude) {
132  $sSeoUrl = $this->addLanguageParam( $sSeoUrl, $iLang );
133  }
134  return $this->_getUniqueSeoUrl( $sSeoUrl, $sObjectId, $iLang );
135  }
136 
140  public function __construct()
141  {
142  $myConfig = $this->getConfig();
143  if (!self::$_sSeparator) {
144  $this->setSeparator( $myConfig->getConfigParam( 'sSEOSeparator' ) );
145  }
146  if (!self::$_sPrefix) {
147  $this->setPrefix( $myConfig->getConfigParam( 'sSEOuprefix' ) );
148  }
149  $this->setReservedWords( $myConfig->getConfigParam( 'aSEOReservedWords' ) );
150  }
151 
163  protected function _copyToHistory( $sId, $iShopId, $iLang, $sType = null, $sNewId = null )
164  {
165  $oDb = oxDb::getDb();
166  $sObjectid = $sNewId?$oDb->quote( $sNewId ):'oxobjectid';
167  $sType = $sType?"oxtype =".$oDb->quote( $sType )." and":'';
168  $iLang = (int) $iLang;
169 
170  // moving
171  $sSub = "select $sObjectid, MD5( LOWER( oxseourl ) ), oxshopid, oxlang, now() from oxseo
172  where {$sType} oxobjectid = ".$oDb->quote( $sId )." and oxshopid = ".$oDb->quote( $iShopId )." and
173  oxlang = {$iLang} and oxexpired = '1'";
174  $sQ = "replace oxseohistory ( oxobjectid, oxident, oxshopid, oxlang, oxinsert ) {$sSub}";
175  $oDb->execute( $sQ );
176  }
177 
186  public function getDynamicObjectId( $iShopId, $sStdUrl )
187  {
188  return $this->_getStaticObjectId( $iShopId, $sStdUrl );
189  }
190 
200  protected function _getDynamicUri( $sStdUrl, $sSeoUrl, $iLang )
201  {
202  $iShopId = $this->getConfig()->getShopId();
203 
204  $sStdUrl = $this->_trimUrl( $sStdUrl );
205  $sObjectId = $this->getDynamicObjectId( $iShopId, $sStdUrl );
206  $sSeoUrl = $this->_prepareUri( $this->addLanguageParam( $sSeoUrl, $iLang ), $iLang );
207 
208  //load details link from DB
209  $sOldSeoUrl = $this->_loadFromDb( 'dynamic', $sObjectId, $iLang );
210  if ( $sOldSeoUrl === $sSeoUrl ) {
211  $sSeoUrl = $sOldSeoUrl;
212  } else {
213 
214  if ( $sOldSeoUrl ) {
215  // old must be transferred to history
216  $this->_copyToHistory( $sObjectId, $iShopId, $iLang, 'dynamic' );
217  }
218 
219  // creating unique
220  $sSeoUrl = $this->_processSeoUrl( $sSeoUrl, $sObjectId, $iLang );
221 
222  // inserting
223  $this->_saveToDb( 'dynamic', $sObjectId, $sStdUrl, $sSeoUrl, $iLang, $iShopId );
224  }
225 
226  return $sSeoUrl;
227  }
228 
238  protected function _getFullUrl( $sSeoUrl, $iLang = null, $blSsl = false )
239  {
240  if ( $sSeoUrl ) {
241  $sFullUrl = ( $blSsl ? $this->getConfig()->getSslShopUrl( $iLang ) : $this->getConfig()->getShopUrl( $iLang, false ) ) . $sSeoUrl;
242  return oxRegistry::get("oxUtilsUrl")->processSeoUrl( $sFullUrl );
243  }
244  return false;
245  }
246 
256  protected function _getSeoIdent( $sSeoUrl )
257  {
258  return md5( strtolower( $sSeoUrl ) );
259  }
260 
270  protected function _getStaticUri( $sStdUrl, $iShopId, $iLang )
271  {
272  $sStdUrl = $this->_trimUrl( $sStdUrl, $iLang );
273  return $this->_loadFromDb( 'static', $this->_getStaticObjectId( $iShopId, $sStdUrl ), $iLang );
274  }
275 
281  protected function _getUrlExtension()
282  {
283  return;
284  }
285 
298  protected function _getUniqueSeoUrl( $sSeoUrl, $sObjectId = null, $iObjectLang = null )
299  {
300  $sSeoUrl = $this->_prepareUri( $sSeoUrl, $iObjectLang );
301  $oStr = getStr();
302  $sExt = '';
303  if ( $oStr->preg_match( '/(\.html?|\/)$/i', $sSeoUrl, $aMatched ) ) {
304  $sExt = $aMatched[0];
305  }
306  $sBaseSeoUrl = $sSeoUrl;
307  if ( $sExt && $oStr->substr( $sSeoUrl, 0 - $oStr->strlen( $sExt ) ) == $sExt ) {
308  $sBaseSeoUrl = $oStr->substr( $sSeoUrl, 0, $oStr->strlen( $sSeoUrl ) - $oStr->strlen( $sExt ) );
309  }
310 
311  $iShopId = $this->getConfig()->getShopId();
312  $iCnt = 0;
313  $sCheckSeoUrl = $this->_trimUrl( $sSeoUrl );
314  $sQ = "select 1 from oxseo where oxshopid = '{$iShopId}'";
315 
316  $oDb = oxDb::getDb();
317  // skipping self
318  if ( $sObjectId && isset($iObjectLang) ) {
319  $iObjectLang = (int) $iObjectLang;
320  $sQ .= " and not (oxobjectid = " . $oDb->quote( $sObjectId ) . " and oxlang = $iObjectLang)";
321  }
322 
323  while ( $oDb->getOne( $sQ ." and oxident= " . $oDb->quote( $this->_getSeoIdent( $sCheckSeoUrl ) ) ) ) {
324  $sAdd = '';
325  if ( self::$_sPrefix ) {
326  $sAdd = self::$_sSeparator . self::$_sPrefix;
327  }
328  if ( $iCnt ) {
329  $sAdd .= self::$_sSeparator . $iCnt;
330  }
331  ++$iCnt;
332 
333  $sSeoUrl = $sBaseSeoUrl . $sAdd . $sExt;
334  $sCheckSeoUrl = $this->_trimUrl( $sSeoUrl );
335  }
336  return $sSeoUrl;
337  }
338 
353  protected function _isFixed( $sType, $sId, $iLang, $iShopId = null, $sParams = null, $blStrictParamsCheck = true)
354  {
355  if ( $iShopId === null ) {
356  $iShopId = $this->getConfig()->getShopId();
357  }
358  $iLang = (int) $iLang;
359 
360  if ( !isset( self::$_aFixedCache[$sType][$sShopId][$sId][$iLang] ) ) {
361  $oDb = oxDb::getDb();
362 
363  $sQ = "SELECT `oxfixed`
364  FROM `oxseo`
365  WHERE `oxtype` = ".$oDb->quote( $sType )."
366  AND `oxobjectid` = ".$oDb->quote( $sId ) ."
367  AND `oxshopid` = ".$oDb->quote( $iShopId )."
368  AND `oxlang` = '{$iLang}'";
369 
370  $sParams = $sParams ? $oDb->quote( $sParams ) : "''";
371  if ( $sParams && $blStrictParamsCheck ) {
372  $sQ .= " AND `oxparams` = {$sParams}";
373  } else {
374  $sQ .= " ORDER BY `oxparams` ASC";
375  }
376  $sQ .= " LIMIT 1";
377 
378  self::$_aFixedCache[$sType][$sShopId][$sId][$iLang] = (bool) $oDb->getOne( $sQ );
379  }
380  return self::$_aFixedCache[$sType][$sShopId][$sId][$iLang];
381  }
382 
393  protected function _getCacheKey( $sType, $iLang = null, $iShopId = null, $sParams = null )
394  {
395  $blAdmin = $this->isAdmin();
396  if ( !$blAdmin && $sType !== "oxarticle" ) {
397  return $sType . ( (int) $iLang ) . ( (int) $iShopId ) . "seo";
398  }
399 
400  // use cache in non admin mode
401  if ( self::$_sCacheKey === null ) {
402  self::$_sCacheKey = false;
403  if ( !$blAdmin && ( $oView = $this->getConfig()->getActiveView() ) ) {
404  self::$_sCacheKey = md5( $oView->getViewId() ) . "seo";
405  }
406  }
407  return self::$_sCacheKey;
408  }
409 
421  protected function _loadFromCache( $sCacheIdent, $sType, $iLang = null, $iShopId = null, $sParams = null )
422  {
423  if ( !$this->getConfig()->getConfigParam( 'blEnableSeoCache' ) ) {
424  return false;
425  }
426 
427  startProfile( "seoencoder_loadFromCache" );
428 
429  $sCacheKey = $this->_getCacheKey( $sType, $iLang, $iShopId, $sParams );
430  $sCache = false;
431 
432  if ( $sCacheKey && !isset( self::$_aCache[$sCacheKey] ) ) {
433  self::$_aCache[$sCacheKey] = oxRegistry::getUtils()->fromFileCache( $sCacheKey );
434  }
435 
436  if ( isset( self::$_aCache[$sCacheKey] ) && isset( self::$_aCache[$sCacheKey][$sCacheIdent] ) ) {
437  $sCache = self::$_aCache[$sCacheKey][$sCacheIdent];
438  }
439 
440  stopProfile( "seoencoder_loadFromCache" );
441  return $sCache;
442  }
443 
456  protected function _saveInCache( $sCacheIdent, $sCache, $sType, $iLang = null, $iShopId = null, $sParams = null )
457  {
458  if ( !$this->getConfig()->getConfigParam( 'blEnableSeoCache' ) ) {
459  return false;
460  }
461 
462  startProfile( "seoencoder_saveInCache" );
463 
464  $blSaved = false;
465  if ( $sCache && ( $sCacheKey = $this->_getCacheKey( $sType, $iLang, $iShopId, $sParams ) ) !== false ) {
466  self::$_aCache[$sCacheKey][$sCacheIdent] = $sCache;
467  $blSaved = oxRegistry::getUtils()->toFileCache( $sCacheKey, self::$_aCache[$sCacheKey] );
468  }
469 
470  stopProfile( "seoencoder_saveInCache" );
471  return $blSaved;
472  }
473 
489  protected function _loadFromDb( $sType, $sId, $iLang, $iShopId = null, $sParams = null, $blStrictParamsCheck = true)
490  {
491 
492  if ( $iShopId === null ) {
493  $iShopId = $this->getConfig()->getShopId();
494  }
495 
496  $iLang = (int) $iLang;
498 
499  $sQ = "
500  SELECT
501  `oxfixed`,
502  `oxseourl`,
503  `oxexpired`,
504  `oxtype`
505  FROM `oxseo`
506  WHERE `oxtype` = ".$oDb->quote( $sType )."
507  AND `oxobjectid` = ".$oDb->quote( $sId ) ."
508  AND `oxshopid` = ".$oDb->quote( $iShopId )."
509  AND `oxlang` = '{$iLang}'";
510 
511  $sParams = $sParams ? $sParams : '';
512  if ( $sParams && $blStrictParamsCheck ) {
513  $sQ .= " AND `oxparams` = '{$sParams}'";
514  } else {
515  $sQ .= " ORDER BY `oxparams` ASC";
516  }
517  $sQ .= " LIMIT 1";
518 
519 
520  // caching to avoid same queries..
521  $sIdent = md5( $sQ );
522 
523  // looking in cache
524  if ( ( $sSeoUrl = $this->_loadFromCache( $sIdent, $sType, $iLang, $iShopId, $sParams ) ) === false ) {
525  $oRs = $oDb->select( $sQ );
526 
527  if ( $oRs && $oRs->recordCount() > 0 && !$oRs->EOF ) {
528  // moving expired static urls to history ..
529  if ( $oRs->fields['oxexpired'] && ( $oRs->fields['oxtype'] == 'static' || $oRs->fields['oxtype'] == 'dynamic' ) ) {
530  // if expired - copying to history, marking as not expired
531  $this->_copyToHistory( $sId, $iShopId, $iLang );
532  $oDb->execute( "update oxseo set oxexpired = 0 where oxobjectid = ".$oDb->quote( $sId )." and oxlang = '{$iLang}'" );
533  $sSeoUrl = $oRs->fields['oxseourl'];
534  } elseif ( !$oRs->fields['oxexpired'] || $oRs->fields['oxfixed'] ) {
535  // if seo url is available and is valid
536  $sSeoUrl = $oRs->fields['oxseourl'];
537  }
538 
539  // storing in cache
540  $this->_saveInCache( $sIdent, $sSeoUrl, $sType, $iLang, $iShopId, $sParams );
541  }
542  }
543  return $sSeoUrl;
544  }
545 
552  protected function _getReservedEntryKeys()
553  {
554  if ( !isset( self::$_aReservedEntryKeys ) || !is_array( self::$_aReservedEntryKeys ) ) {
555  $sDir = getShopBasePath();
556  self::$_aReservedEntryKeys = array_map('preg_quote', self::$_aReservedWords, array('#'));
557  $oStr = getStr();
558  foreach ( glob( "$sDir/*" ) as $sFile ) {
559  if ( $oStr->preg_match( '/^(.+)\.php[0-9]*$/i', basename( $sFile ), $aMatches ) ) {
560  self::$_aReservedEntryKeys[] = preg_quote( $aMatches[0], '#' );
561  self::$_aReservedEntryKeys[] = preg_quote( $aMatches[1], '#' );
562  } elseif ( is_dir( $sFile ) ) {
563  self::$_aReservedEntryKeys[] = preg_quote( basename( $sFile ), '#' );
564  }
565  }
566  self::$_aReservedEntryKeys = array_unique(self::$_aReservedEntryKeys);
567  }
569  }
570 
579  protected function _prepareUri( $sUri, $iLang = false )
580  {
581  // decoding entities
582  $sUri = $this->encodeString( $sUri, true, $iLang );
583 
584  // basic string preparation
585  $oStr = getStr();
586  $sUri = $oStr->strip_tags( $sUri );
587 
588  // if found ".html" or "/" at the end - removing it temporary
589  $sExt = $this->_getUrlExtension();
590  if ($sExt === null) {
591  $aMatched = array();
592  if ( $oStr->preg_match( '/(\.html?|\/)$/i', $sUri, $aMatched ) ) {
593  $sExt = $aMatched[0];
594  } else {
595  $sExt = '/';
596  }
597  }
598  if ( $sExt && $oStr->substr( $sUri, 0 - $oStr->strlen( $sExt ) ) == $sExt ) {
599  $sUri = $oStr->substr( $sUri, 0, $oStr->strlen( $sUri ) - $oStr->strlen( $sExt ) );
600  }
601 
602  // removing any special characters
603  // #0004282 bugfix, php <5.3 does not escape - char, so we do it manually
604  $sQuotedPrefix = preg_quote( self::$_sSeparator . self::$_sPrefix, '/');
605  if ( phpversion() < '5.3' ) {
606  $sQuotedPrefix = str_replace( '-', '\-', $sQuotedPrefix );
607  }
608  $sRegExp = '/[^A-Za-z0-9' . $sQuotedPrefix . '\/]+/';
609  $sUri = $oStr->preg_replace( array( "/\W*\/\W*/", $sRegExp ), array( "/", self::$_sSeparator ), $sUri );
610 
611  // SEO id is empty ?
612  if ( !$sUri && self::$_sPrefix ) {
613  $sUri = $this->_prepareUri( self::$_sPrefix, $iLang );
614  }
615 
616  $sAdd = '';
617  if ('/' != self::$_sSeparator) {
618  $sAdd = self::$_sSeparator . self::$_sPrefix;
619  $sUri = trim($sUri, self::$_sSeparator);
620  } else {
621  $sAdd = '_' . self::$_sPrefix;
622  }
623 
624  // binding the ending back
625  $sUri .= $sExt;
626 
627  // fix for not having url, which executes through /other/ script then seo decoder
628  $sUri = $oStr->preg_replace( "#^(/*)(".implode('|', $this->_getReservedEntryKeys()).")(/|$)#i", "\$1\$2$sAdd\$3", $sUri );
629 
630  // cleaning
631  // #0004282 bugfix, php < 5.3 does not escape - char, so we do it manually\
632  $sQuotedSeparator = preg_quote( self::$_sSeparator, '/');
633  if ( phpversion() < '5.3' ) {
634  $sQuotedSeparator = str_replace( '-', '\-', $sQuotedSeparator );
635  }
636  return $oStr->preg_replace( array( '|//+|', '/' . $sQuotedSeparator . $sQuotedSeparator .'+/' ),
637  array( '/', self::$_sSeparator ), $sUri );
638  }
639 
640 
650  protected function _prepareTitle( $sTitle, $blSkipTruncate = false, $iLang = false )
651  {
652  $sTitle = $this->encodeString( $sTitle, true, $iLang );
653  $sSep = self::$_sSeparator;
654  if (!$sSep || ('/' == $sSep)) {
655  $sSep = '_';
656  }
657 
658  $sRegExp = '/[^A-Za-z0-9\/'.preg_quote( self::$_sPrefix, '/').preg_quote($sSep, '/').']+/';
659  $sTitle = preg_replace( array("#/+#", $sRegExp, "# +#", "#(".preg_quote($sSep, '/').")+#"), $sSep, $sTitle );
660 
661  $oStr = getStr();
662  // smart truncate
663  if ( !$blSkipTruncate && $oStr->strlen( $sTitle ) > $this->_iIdLength ) {
664  $iFirstSpace = $oStr->strpos( $sTitle, $sSep, $this->_iIdLength);
665  if ( $iFirstSpace !== false ) {
666  $sTitle = $oStr->substr( $sTitle, 0, $iFirstSpace );
667  }
668  }
669 
670  $sTitle = trim( $sTitle, $sSep );
671 
672  if (!$sTitle) {
673  return self::$_sPrefix;
674  }
675  // cleaning
676  return $sTitle;
677  }
678 
679 
696  protected function _saveToDb( $sType, $sObjectId, $sStdUrl, $sSeoUrl, $iLang, $iShopId = null, $blFixed = null, $sParams = null )
697  {
699  if ( $iShopId === null ) {
700  $iShopId = $this->getConfig()->getShopId();
701  }
702 
703  $iLang = (int) $iLang;
704 
705  $sStdUrl = $this->_trimUrl( $sStdUrl );
706  $sSeoUrl = $this->_trimUrl( $sSeoUrl );
707  $sIdent = $this->_getSeoIdent( $sSeoUrl );
708 
709  // transferring old url, thus current url will be regenerated
710  $sQtedObjectId = $oDb->quote( $sObjectId );
711  $iQtedShopId = $oDb->quote( $iShopId );
712  $sQtedType = $oDb->quote( $sType );
713  $sQtedSeoUrl = $oDb->quote( $sSeoUrl );
714  $sQtedStdUrl = $oDb->quote( $sStdUrl );
715  $sQtedParams = $oDb->quote( $sParams );
716  $sQtedIdent = $oDb->quote( $sIdent );
717 
718  // transferring old url, thus current url will be regenerated
719  $sQ = "select oxfixed, oxexpired, ( oxstdurl like {$sQtedStdUrl} ) as samestdurl,
720  oxseourl like {$sQtedSeoUrl} as sameseourl from oxseo where oxtype = {$sQtedType} and
721  oxobjectid = {$sQtedObjectId} and oxshopid = {$iQtedShopId} and oxlang = {$iLang} ";
722 
723  $sQ .= $sParams ? " and oxparams = {$sQtedParams} " : '';
724  $sQ .= "limit 1";
726  $oRs = $oDb->select( $sQ );
727  if ( $oRs && $oRs->recordCount() > 0 && !$oRs->EOF ) {
728  if ( $oRs->fields['samestdurl'] && $oRs->fields['sameseourl'] && $oRs->fields['oxexpired'] ) {
729  // fixed state change
730  $sFixed = isset( $blFixed ) ? ", oxfixed = " . ( (int) $blFixed ) . " " : '';
731  // nothing was changed - setting expired status back to 0
732  $sSql = "update oxseo set oxexpired = 0 {$sFixed} where oxtype = {$sQtedType} and
733  oxobjectid = {$sQtedObjectId} and oxshopid = {$iQtedShopId} and oxlang = {$iLang} ";
734  $sSql .= $sParams ? " and oxparams = {$sQtedParams} " : '';
735  $sSql .= " limit 1";
736 
737  return $oDb->execute( $sSql );
738  } elseif ( $oRs->fields['oxexpired'] ) {
739  // copy to history
740  $this->_copyToHistory( $sObjectId, $iShopId, $iLang, $sType );
741  }
742  }
743 
744  // inserting new or updating
745  $sParams = $sParams ? $oDb->quote( $sParams ) :'""';
746  $blFixed = (int) $blFixed;
747 
748  $sQ = "insert into oxseo
749  (oxobjectid, oxident, oxshopid, oxlang, oxstdurl, oxseourl, oxtype, oxfixed, oxexpired, oxparams)
750  values
751  ( {$sQtedObjectId}, {$sQtedIdent}, {$iQtedShopId}, {$iLang}, {$sQtedStdUrl}, {$sQtedSeoUrl}, {$sQtedType}, '$blFixed', '0', {$sParams} )
752  on duplicate key update
753  oxident = {$sQtedIdent}, oxstdurl = {$sQtedStdUrl}, oxseourl = {$sQtedSeoUrl}, oxfixed = '$blFixed', oxexpired = '0'";
754 
755  return $oDb->execute( $sQ );
756  }
757 
768  protected function _trimUrl( $sUrl, $iLang = null )
769  {
770  $myConfig = $this->getConfig();
771  $oStr = getStr();
772  $sUrl = str_replace( array( $myConfig->getShopUrl( $iLang, false ), $myConfig->getSslShopUrl( $iLang ) ), '', $sUrl );
773  $sUrl = $oStr->preg_replace( '/(\?|&(amp;)?)(force_)?(admin_)?sid=[a-z0-9\.]+&?(amp;)?/i', '\1', $sUrl );
774  $sUrl = $oStr->preg_replace( '/(\?|&(amp;)?)shp=[0-9]+&?(amp;)?/i', '\1', $sUrl );
775  $sUrl = $oStr->preg_replace( '/(\?|&(amp;)?)lang=[0-9]+&?(amp;)?/i', '\1', $sUrl );
776  $sUrl = $oStr->preg_replace( '/(\?|&(amp;)?)cur=[0-9]+&?(amp;)?/i', '\1', $sUrl );
777  $sUrl = $oStr->preg_replace( '/(\?|&(amp;)?)stoken=[a-z0-9]+&?(amp;)?/i', '\1', $sUrl );
778  $sUrl = $oStr->preg_replace( '/(\?|&(amp;)?)&(amp;)?/i', '\1', $sUrl );
779  $sUrl = $oStr->preg_replace( '/(\?|&(amp;)?)+$/i', '', $sUrl );
780  $sUrl = trim( $sUrl );
781 
782  // max length <= $this->_iMaxUrlLength
783  $iLength = $this->_getMaxUrlLength();
784  if ( $oStr->strlen( $sUrl ) > $iLength ) {
785  $sUrl = $oStr->substr( $sUrl, 0, $iLength );
786  }
787 
788  return $sUrl;
789  }
790 
796  protected function _getMaxUrlLength()
797  {
798  if ( $this->_iMaxUrlLength === null ) {
799  // max length <= 2048 / custom
800  $this->_iMaxUrlLength = $this->getConfig()->getConfigParam( "iMaxSeoUrlLength" ) ;
801  if ( !$this->_iMaxUrlLength ) {
802  $this->_iMaxUrlLength = 2048;
803  }
804  }
805  return $this->_iMaxUrlLength;
806  }
807 
817  public function encodeString( $sString, $blReplaceChars = true, $iLang = false )
818  {
819  // decoding entities
820  $sString = getStr()->html_entity_decode( $sString );
821 
822  if ( $blReplaceChars ) {
823  if ($iLang === false || !is_numeric($iLang)) {
824  $iLang = oxRegistry::getLang()->getEditLanguage();
825  }
826 
827  if ( $aReplaceChars = oxRegistry::getLang()->getSeoReplaceChars($iLang) ) {
828  $sString = str_replace( array_keys( $aReplaceChars ), array_values( $aReplaceChars ), $sString );
829  }
830  }
831 
832 
833  // special chars
834  $aReplaceWhat = array( '&amp;', '&quot;', '&#039;', '&lt;', '&gt;' );
835  return str_replace( $aReplaceWhat, '', $sString );
836  }
837 
845  public function setSeparator( $sSeparator = null )
846  {
847  self::$_sSeparator = $sSeparator;
848  if ( !self::$_sSeparator ) {
849  self::$_sSeparator = '-';
850  }
851  }
852 
860  public function setPrefix( $sPrefix )
861  {
862  if ($sPrefix) {
863  self::$_sPrefix = $sPrefix;
864  } else {
865  self::$_sPrefix = 'oxid';
866  }
867  }
868 
876  public function setIdLength( $iIdlength = null )
877  {
878  if ( isset( $iIdlength ) ) {
879  $this->_iIdLength = $iIdlength;
880  }
881  }
882 
891  public function setReservedWords( $aReservedWords )
892  {
893  self::$_aReservedWords = array_merge( self::$_aReservedWords, $aReservedWords );
894  }
895 
896 
908  public function markAsExpired( $sId, $iShopId = null, $iExpStat = 1, $iLang = null, $sParams = null )
909  {
910  $oDb = oxDb::getDb();
911  $sWhere = $sId ? "where oxobjectid = " . $oDb->quote( $sId ) : '';
912  $sWhere .= isset( $iShopId ) ? ( $sWhere ? " and oxshopid = ". $oDb->quote( $iShopId ) : "where oxshopid = ". $oDb->quote( $iShopId ) ) : '';
913  $sWhere .= !is_null($iLang) ? ( $sWhere ? " and oxlang = '{$iLang}'" : "where oxlang = '{$iLang}'" ) : '';
914  $sWhere .= $sParams ? ( $sWhere ? " and {$sParams}" : "where {$sParams}" ) : '';
915 
916  $sQ = "update oxseo set oxexpired = " . $oDb->quote( $iExpStat ) . " $sWhere ";
917  $oDb->execute( $sQ );
918  }
919 
933  protected function _getPageUri( $oObject, $sType, $sStdUrl, $sSeoUrl, $sParams, $iLang = null, $blFixed = false )
934  {
935  if ( !isset( $iLang ) ) {
936  $iLang = $oObject->getLanguage();
937  }
938  $iShopId = $this->getConfig()->getShopId();
939 
940  //load page link from DB
941  $sOldSeoUrl = $this->_loadFromDb( $sType, $oObject->getId(), $iLang, $iShopId, $sParams );
942  if ( !$sOldSeoUrl ) {
943  // generating new..
944  $sSeoUrl = $this->_processSeoUrl( $sSeoUrl, $oObject->getId(), $iLang );
945  $this->_saveToDb( $sType, $oObject->getId(), $sStdUrl, $sSeoUrl, $iLang, $iShopId, (int) $blFixed, $sParams );
946  } else {
947  // using old
948  $sSeoUrl = $sOldSeoUrl;
949  }
950  return $sSeoUrl;
951  }
952 
961  protected function _getStaticObjectId( $iShopId, $sStdUrl )
962  {
963  return md5( strtolower ( $iShopId . $this->_trimUrl( $sStdUrl ) ) );
964  }
965 
975  public function encodeStaticUrls( $aStaticUrl, $iShopId, $iLang )
976  {
977  $oDb = oxDb::getDb();
978  $sValues = '';
979  $sOldObjectId = null;
980 
981  // standard url
982  $sStdUrl = $this->_trimUrl( trim( $aStaticUrl['oxseo__oxstdurl'] ) );
983  $sObjectId = $aStaticUrl['oxseo__oxobjectid'];
984 
985  if ( !$sObjectId || $sObjectId == '-1' ) {
986  $sObjectId = $this->_getStaticObjectId( $iShopId, $sStdUrl );
987  } else {
988  // marking entry as needs to move to history
989  $sOldObjectId = $sObjectId;
990 
991  // if std url does not match old
992  if ( $this->_getStaticObjectId( $iShopId, $sStdUrl ) != $sObjectId ) {
993  $sObjectId = $this->_getStaticObjectId( $iShopId, $sStdUrl );
994  }
995  }
996 
997  foreach ( $aStaticUrl['oxseo__oxseourl'] as $iLang => $sSeoUrl ) {
998 
999  $iLang = (int) $iLang;
1000 
1001  // generating seo url
1002  $sSeoUrl = $this->_trimUrl( $sSeoUrl );
1003  if ( $sSeoUrl ) {
1004  $sSeoUrl = $this->_processSeoUrl( $sSeoUrl, $sObjectId, $iLang );
1005  }
1006 
1007 
1008  if ( $sOldObjectId ) {
1009  // move changed records to history
1010  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) ) {
1011  $this->_copyToHistory( $sOldObjectId, $iShopId, $iLang, 'static', $sObjectId );
1012  }
1013  }
1014 
1015  if ( !$sSeoUrl || !$sStdUrl ) {
1016  continue;
1017  }
1018 
1019  $sIdent = $this->_getSeoIdent( $sSeoUrl );
1020 
1021  if ( $sValues ) {
1022  $sValues .= ', ';
1023  }
1024 
1025  $sValues .= "( " . $oDb->quote( $sObjectId ) . ", " . $oDb->quote( $sIdent ) . ", " . $oDb->quote( $iShopId ).", '{$iLang}', " . $oDb->quote( $sStdUrl ) . ", " . $oDb->quote( $sSeoUrl ) . ", 'static' )";
1026  }
1027 
1028  // must delete old before insert/update
1029  if ( $sOldObjectId ) {
1030  $oDb->execute( "delete from oxseo where oxobjectid in ( " . $oDb->quote( $sOldObjectId ) . ", " . $oDb->quote( $sObjectId ) . " )" );
1031  }
1032 
1033  // (re)inserting
1034  if ( $sValues ) {
1035 
1036  $sQ = "insert into oxseo ( oxobjectid, oxident, oxshopid, oxlang, oxstdurl, oxseourl, oxtype ) values {$sValues} ";
1037  $oDb->execute( $sQ );
1038  }
1039 
1040  return $sObjectId;
1041  }
1042 
1050  public function copyStaticUrls( $iShopId )
1051  {
1052  $iBaseShopId = $this->getConfig()->getBaseShopId();
1053  if ( $iShopId != $iBaseShopId ) {
1054  $oDb = oxDb::getDb();
1055  foreach (array_keys(oxRegistry::getLang()->getLanguageIds()) as $iLang) {
1056  $sQ = "insert into oxseo ( oxobjectid, oxident, oxshopid, oxlang, oxstdurl, oxseourl, oxtype )
1057  select MD5( LOWER( CONCAT( " . $oDb->quote( $iShopId ) . ", oxstdurl ) ) ), MD5( LOWER( oxseourl ) ),
1058  " . $oDb->quote( $iShopId ) . ", oxlang, oxstdurl, oxseourl, oxtype from oxseo where oxshopid = '{$iBaseShopId}' and oxtype = 'static' and oxlang='$iLang' ";
1059  $oDb->execute( $sQ );
1060  }
1061  }
1062  }
1063 
1073  public function getStaticUrl( $sStdUrl, $iLang = null, $iShopId = null )
1074  {
1075  if (!isset($iShopId)) {
1076  $iShopId = $this->getConfig()->getShopId();
1077  }
1078  if (!isset($iLang)) {
1079  $iLang = oxRegistry::getLang()->getEditLanguage();
1080  }
1081 
1082  if ( isset($this->_aStaticUrlCache[$sStdUrl][$iLang][$iShopId])) {
1083  return $this->_aStaticUrlCache[$sStdUrl][$iLang][$iShopId];
1084  }
1085 
1086  $sFullUrl = '';
1087  if ( ( $sSeoUrl = $this->_getStaticUri( $sStdUrl, $iShopId, $iLang ) ) ) {
1088  $sFullUrl = $this->_getFullUrl( $sSeoUrl, $iLang, strpos( $sStdUrl, "https:" ) === 0 );
1089  }
1090 
1091 
1092  $this->_aStaticUrlCache[$sStdUrl][$iLang][$iShopId] = $sFullUrl;
1093 
1094  return $sFullUrl;
1095  }
1096 
1115  public function addSeoEntry( $sObjectId, $iShopId, $iLang, $sStdUrl, $sSeoUrl, $sType, $blFixed = 1, $sKeywords = '', $sDescription = '', $sParams = '', $blExclude = false, $sAltObjectId = null )
1116  {
1117  $sSeoUrl = $this->_processSeoUrl( $this->_trimUrl( $sSeoUrl ? $sSeoUrl : $this->_getAltUri( $sAltObjectId ? $sAltObjectId : $sObjectId, $iLang ) ), $sObjectId, $iLang, $blExclude );
1118  if ( $this->_saveToDb( $sType, $sObjectId, $sStdUrl, $sSeoUrl, $iLang, $iShopId, $blFixed, $sParams ) ) {
1119 
1120  $oDb = oxDb::getDb();
1121 
1122  //
1123  $sQtedObjectId = $oDb->quote( $sAltObjectId ? $sAltObjectId : $sObjectId );
1124  $iQtedShopId = $oDb->quote( $iShopId );
1125 
1126  $oStr = getStr();
1127  if ( $sKeywords !== false ) {
1128  $sKeywords = $oDb->quote( $oStr->htmlspecialchars( $this->encodeString( $oStr->strip_tags( $sKeywords ), false, $iLang ) ) );
1129  }
1130 
1131  if ( $sDescription !== false ) {
1132  $sDescription = $oDb->quote( $oStr->htmlspecialchars( $oStr->strip_tags( $sDescription ) ) );
1133  }
1134 
1135  $sQ = "insert into oxobject2seodata
1136  ( oxobjectid, oxshopid, oxlang, oxkeywords, oxdescription )
1137  values
1138  ( {$sQtedObjectId}, {$iQtedShopId}, {$iLang}, ".( $sKeywords ? $sKeywords : "''" ).", ".( $sDescription ? $sDescription : "''" )." )
1139  on duplicate key update
1140  oxkeywords = ".( $sKeywords ? $sKeywords : "oxkeywords" ).", oxdescription = ".( $sDescription ? $sDescription : "oxdescription" );
1141  $oDb->execute( $sQ );
1142  }
1143  }
1144 
1153  protected function _getAltUri( $sObjectId, $iLang )
1154  {
1155  }
1156 
1167  public function deleteSeoEntry( $sObjectId, $iShopId, $iLang, $sType )
1168  {
1169  $oDb = oxDb::getDb();
1170  $sQ = "delete from oxseo where oxobjectid = " . $oDb->quote( $sObjectId ) . " and oxshopid = " . $oDb->quote( $iShopId ) . " and oxlang = " . $oDb->quote( $iLang ) . " and oxtype = " . $oDb->quote( $sType ) . " ";
1171  $oDb->execute( $sQ );
1172  }
1173 
1184  public function getMetaData( $sObjectId, $sMetaType, $iShopId = null, $iLang = null )
1185  {
1186  $oDb = oxDb::getDb();
1187 
1188  $iShopId = ( !isset( $iShopId ) ) ? $this->getConfig()->getShopId():$iShopId;
1189  $iLang = ( !isset( $iLang ) ) ? oxRegistry::getLang()->getObjectTplLanguage():((int) $iLang);
1190  return $oDb->getOne( "select {$sMetaType} from oxobject2seodata where oxobjectid = " . $oDb->quote( $sObjectId ) . " and oxshopid = " . $oDb->quote( $iShopId )." and oxlang = '{$iLang}'" );
1191  }
1192 
1206  public function getDynamicUrl( $sStdUrl, $sSeoUrl, $iLang )
1207  {
1208  startProfile("getDynamicUrl");
1209  $sDynUrl = $this->_getFullUrl( $this->_getDynamicUri( $sStdUrl, $sSeoUrl, $iLang ), $iLang, strpos( $sStdUrl, "https:" ) === 0 );
1210  stopProfile("getDynamicUrl");
1211  return $sDynUrl;
1212  }
1213 
1222  public function fetchSeoUrl( $sStdUrl, $iLanguage = null )
1223  {
1225  $iLanguage = isset( $iLanguage ) ? ( (int) $iLanguage ) : oxRegistry::getLang()->getBaseLanguage();
1226  $sSeoUrl = false;
1227 
1228  $sShopId = $this->getConfig()->getShopId();
1229 
1230  $sQ = "SELECT `oxseourl`, `oxlang` FROM `oxseo` WHERE `oxstdurl` = " . $oDb->quote( $sStdUrl ) . " AND `oxlang` = '$iLanguage' AND `oxshopid` = '$sShopId' LIMIT 1";
1231 
1233  $oRs = $oDb->select( $sQ );
1234 
1235  if ( !$oRs->EOF ) {
1236  $sSeoUrl = $oRs->fields['oxseourl'];
1237  }
1238 
1239  return $sSeoUrl;
1240  }
1241 }