OXID eShop CE  4.9.7
 All Classes Files Functions Variables Pages
oxseoencoder.php
Go to the documentation of this file.
1 <?php
2 
7 class oxSeoEncoder extends oxSuperCfg
8 {
9 
16  protected static $_aReservedWords = array('admin');
17 
23  protected static $_aReservedEntryKeys = null;
24 
30  protected static $_sSeparator = null;
31 
37  protected $_iIdLength = 255;
38 
44  protected static $_sPrefix = null;
45 
51  protected $_sAddParams = null;
52 
58  protected static $_aFixedCache = array();
59 
65  protected static $_sCacheKey = null;
66 
72  protected static $_aCache = array();
73 
79  protected $_iMaxUrlLength = null;
80 
89  public function addLanguageParam($sSeoUrl, $iLang)
90  {
91  $iLang = (int) $iLang;
92  $iDefLang = (int) $this->getConfig()->getConfigParam('iDefSeoLang');
93  $aLangIds = oxRegistry::getLang()->getLanguageIds();
94 
95  if ($iLang != $iDefLang && isset($aLangIds[$iLang]) && getStr()->strpos($sSeoUrl, $aLangIds[$iLang] . '/') !== 0) {
96  $sSeoUrl = $aLangIds[$iLang] . '/' . $sSeoUrl;
97  }
98 
99  return $sSeoUrl;
100  }
101 
114  protected function _processSeoUrl($sSeoUrl, $sObjectId = null, $iLang = null, $blExclude = false)
115  {
116  if (!$blExclude) {
117  $sSeoUrl = $this->addLanguageParam($sSeoUrl, $iLang);
118  }
119 
120  return $this->_getUniqueSeoUrl($sSeoUrl, $sObjectId, $iLang);
121  }
122 
126  public function __construct()
127  {
128  $myConfig = $this->getConfig();
129  if (!self::$_sSeparator) {
130  $this->setSeparator($myConfig->getConfigParam('sSEOSeparator'));
131  }
132  if (!self::$_sPrefix) {
133  $this->setPrefix($myConfig->getConfigParam('sSEOuprefix'));
134  }
135  $this->setReservedWords($myConfig->getConfigParam('aSEOReservedWords'));
136  }
137 
147  protected function _copyToHistory($sId, $iShopId, $iLang, $sType = null, $sNewId = null)
148  {
149  $oDb = oxDb::getDb();
150  $sObjectid = $sNewId ? $oDb->quote($sNewId) : 'oxobjectid';
151  $sType = $sType ? "oxtype =" . $oDb->quote($sType) . " and" : '';
152  $iLang = (int) $iLang;
153 
154  // moving
155  $sSub = "select $sObjectid, MD5( LOWER( oxseourl ) ), oxshopid, oxlang, now() from oxseo
156  where {$sType} oxobjectid = " . $oDb->quote($sId) . " and oxshopid = " . $oDb->quote($iShopId) . " and
157  oxlang = {$iLang} and oxexpired = '1'";
158  $sQ = "replace oxseohistory ( oxobjectid, oxident, oxshopid, oxlang, oxinsert ) {$sSub}";
159  $oDb->execute($sQ);
160  }
161 
170  public function getDynamicObjectId($iShopId, $sStdUrl)
171  {
172  return $this->_getStaticObjectId($iShopId, $sStdUrl);
173  }
174 
184  protected function _getDynamicUri($sStdUrl, $sSeoUrl, $iLang)
185  {
186  $iShopId = $this->getConfig()->getShopId();
187 
188  $sStdUrl = $this->_trimUrl($sStdUrl);
189  $sObjectId = $this->getDynamicObjectId($iShopId, $sStdUrl);
190  $sSeoUrl = $this->_prepareUri($this->addLanguageParam($sSeoUrl, $iLang), $iLang);
191 
192  //load details link from DB
193  $sOldSeoUrl = $this->_loadFromDb('dynamic', $sObjectId, $iLang);
194  if ($sOldSeoUrl === $sSeoUrl) {
195  $sSeoUrl = $sOldSeoUrl;
196  } else {
197 
198  if ($sOldSeoUrl) {
199  // old must be transferred to history
200  $this->_copyToHistory($sObjectId, $iShopId, $iLang, 'dynamic');
201  }
202 
203  // creating unique
204  $sSeoUrl = $this->_processSeoUrl($sSeoUrl, $sObjectId, $iLang);
205 
206  // inserting
207  $this->_saveToDb('dynamic', $sObjectId, $sStdUrl, $sSeoUrl, $iLang, $iShopId);
208  }
209 
210  return $sSeoUrl;
211  }
212 
222  protected function _getFullUrl($sSeoUrl, $iLang = null, $blSsl = false)
223  {
224  if ($sSeoUrl) {
225  $sFullUrl = ($blSsl ? $this->getConfig()->getSslShopUrl($iLang) : $this->getConfig()->getShopUrl($iLang, false)) . $sSeoUrl;
226 
227  return oxRegistry::get("oxUtilsUrl")->processSeoUrl($sFullUrl);
228  }
229 
230  return false;
231  }
232 
242  protected function _getSeoIdent($sSeoUrl)
243  {
244  return md5(strtolower($sSeoUrl));
245  }
246 
256  protected function _getStaticUri($sStdUrl, $iShopId, $iLang)
257  {
258  $sStdUrl = $this->_trimUrl($sStdUrl, $iLang);
259 
260  return $this->_loadFromDb('static', $this->_getStaticObjectId($iShopId, $sStdUrl), $iLang);
261  }
262 
268  protected function _getUrlExtension()
269  {
270  return;
271  }
272 
285  protected function _getUniqueSeoUrl($sSeoUrl, $sObjectId = null, $iObjectLang = null)
286  {
287  $sSeoUrl = $this->_prepareUri($sSeoUrl, $iObjectLang);
288  $oStr = getStr();
289  $sExt = '';
290  if ($oStr->preg_match('/(\.html?|\/)$/i', $sSeoUrl, $aMatched)) {
291  $sExt = $aMatched[0];
292  }
293  $sBaseSeoUrl = $sSeoUrl;
294  if ($sExt && $oStr->substr($sSeoUrl, 0 - $oStr->strlen($sExt)) == $sExt) {
295  $sBaseSeoUrl = $oStr->substr($sSeoUrl, 0, $oStr->strlen($sSeoUrl) - $oStr->strlen($sExt));
296  }
297 
298  $iShopId = $this->getConfig()->getShopId();
299  $iCnt = 0;
300  $sCheckSeoUrl = $this->_trimUrl($sSeoUrl);
301  $sQ = "select 1 from oxseo where oxshopid = '{$iShopId}'";
302 
303  $oDb = oxDb::getDb();
304  // skipping self
305  if ($sObjectId && isset($iObjectLang)) {
306  $iObjectLang = (int) $iObjectLang;
307  $sQ .= " and not (oxobjectid = " . $oDb->quote($sObjectId) . " and oxlang = $iObjectLang)";
308  }
309 
310  while ($oDb->getOne($sQ . " and oxident= " . $oDb->quote($this->_getSeoIdent($sCheckSeoUrl)))) {
311  $sAdd = '';
312  if (self::$_sPrefix) {
313  $sAdd = self::$_sSeparator . self::$_sPrefix;
314  }
315  if ($iCnt) {
316  $sAdd .= self::$_sSeparator . $iCnt;
317  }
318  ++$iCnt;
319 
320  $sSeoUrl = $sBaseSeoUrl . $sAdd . $sExt;
321  $sCheckSeoUrl = $this->_trimUrl($sSeoUrl);
322  }
323 
324  return $sSeoUrl;
325  }
326 
341  protected function _isFixed($sType, $sId, $iLang, $iShopId = null, $sParams = null, $blStrictParamsCheck = true)
342  {
343  if ($iShopId === null) {
344  $iShopId = $this->getConfig()->getShopId();
345  }
346  $iLang = (int) $iLang;
347 
348  if (!isset(self::$_aFixedCache[$sType][$sShopId][$sId][$iLang])) {
349  $oDb = oxDb::getDb();
350 
351  $sQ = "SELECT `oxfixed`
352  FROM `oxseo`
353  WHERE `oxtype` = " . $oDb->quote($sType) . "
354  AND `oxobjectid` = " . $oDb->quote($sId) . "
355  AND `oxshopid` = " . $oDb->quote($iShopId) . "
356  AND `oxlang` = '{$iLang}'";
357 
358  $sParams = $sParams ? $oDb->quote($sParams) : "''";
359  if ($sParams && $blStrictParamsCheck) {
360  $sQ .= " AND `oxparams` = {$sParams}";
361  } else {
362  $sQ .= " ORDER BY `oxparams` ASC";
363  }
364  $sQ .= " LIMIT 1";
365 
366  self::$_aFixedCache[$sType][$sShopId][$sId][$iLang] = (bool) $oDb->getOne($sQ);
367  }
368 
369  return self::$_aFixedCache[$sType][$sShopId][$sId][$iLang];
370  }
371 
382  protected function _getCacheKey($sType, $iLang = null, $iShopId = null, $sParams = null)
383  {
384  $blAdmin = $this->isAdmin();
385  if (!$blAdmin && $sType !== "oxarticle") {
386  return $sType . ((int) $iLang) . ((int) $iShopId) . "seo";
387  }
388 
389  // use cache in non admin mode
390  if (self::$_sCacheKey === null) {
391  self::$_sCacheKey = false;
392  if (!$blAdmin && ($oView = $this->getConfig()->getActiveView())) {
393  self::$_sCacheKey = md5($oView->getViewId()) . "seo";
394  }
395  }
396 
397  return self::$_sCacheKey;
398  }
399 
411  protected function _loadFromCache($sCacheIdent, $sType, $iLang = null, $iShopId = null, $sParams = null)
412  {
413  if (!$this->getConfig()->getConfigParam('blEnableSeoCache')) {
414  return false;
415  }
416 
417  startProfile("seoencoder_loadFromCache");
418 
419  $sCacheKey = $this->_getCacheKey($sType, $iLang, $iShopId, $sParams);
420  $sCache = false;
421 
422  if ($sCacheKey && !isset(self::$_aCache[$sCacheKey])) {
423  self::$_aCache[$sCacheKey] = oxRegistry::getUtils()->fromFileCache($sCacheKey);
424  }
425 
426  if (isset(self::$_aCache[$sCacheKey]) && isset(self::$_aCache[$sCacheKey][$sCacheIdent])) {
427  $sCache = self::$_aCache[$sCacheKey][$sCacheIdent];
428  }
429 
430  stopProfile("seoencoder_loadFromCache");
431 
432  return $sCache;
433  }
434 
447  protected function _saveInCache($sCacheIdent, $sCache, $sType, $iLang = null, $iShopId = null, $sParams = null)
448  {
449  if (!$this->getConfig()->getConfigParam('blEnableSeoCache')) {
450  return false;
451  }
452 
453  startProfile("seoencoder_saveInCache");
454 
455  $blSaved = false;
456  if ($sCache && ($sCacheKey = $this->_getCacheKey($sType, $iLang, $iShopId, $sParams)) !== false) {
457  self::$_aCache[$sCacheKey][$sCacheIdent] = $sCache;
458  $blSaved = oxRegistry::getUtils()->toFileCache($sCacheKey, self::$_aCache[$sCacheKey]);
459  }
460 
461  stopProfile("seoencoder_saveInCache");
462 
463  return $blSaved;
464  }
465 
481  protected function _loadFromDb($sType, $sId, $iLang, $iShopId = null, $sParams = null, $blStrictParamsCheck = true)
482  {
483 
484  if ($iShopId === null) {
485  $iShopId = $this->getConfig()->getShopId();
486  }
487 
488  $iLang = (int) $iLang;
490 
491  $sQ = "
492  SELECT
493  `oxfixed`,
494  `oxseourl`,
495  `oxexpired`,
496  `oxtype`
497  FROM `oxseo`
498  WHERE `oxtype` = " . $oDb->quote($sType) . "
499  AND `oxobjectid` = " . $oDb->quote($sId) . "
500  AND `oxshopid` = " . $oDb->quote($iShopId) . "
501  AND `oxlang` = '{$iLang}'";
502 
503  $sParams = $sParams ? $sParams : '';
504  if ($sParams && $blStrictParamsCheck) {
505  $sQ .= " AND `oxparams` = '{$sParams}'";
506  } else {
507  $sQ .= " ORDER BY `oxparams` ASC";
508  }
509  $sQ .= " LIMIT 1";
510 
511 
512  // caching to avoid same queries..
513  $sIdent = md5($sQ);
514 
515  // looking in cache
516  if (($sSeoUrl = $this->_loadFromCache($sIdent, $sType, $iLang, $iShopId, $sParams)) === false) {
517  $oRs = $oDb->select($sQ);
518 
519  if ($oRs && $oRs->recordCount() > 0 && !$oRs->EOF) {
520  // moving expired static urls to history ..
521  if ($oRs->fields['oxexpired'] && ($oRs->fields['oxtype'] == 'static' || $oRs->fields['oxtype'] == 'dynamic')) {
522  // if expired - copying to history, marking as not expired
523  $this->_copyToHistory($sId, $iShopId, $iLang);
524  $oDb->execute("update oxseo set oxexpired = 0 where oxobjectid = " . $oDb->quote($sId) . " and oxlang = '{$iLang}'");
525  $sSeoUrl = $oRs->fields['oxseourl'];
526  } elseif (!$oRs->fields['oxexpired'] || $oRs->fields['oxfixed']) {
527  // if seo url is available and is valid
528  $sSeoUrl = $oRs->fields['oxseourl'];
529  }
530 
531  // storing in cache
532  $this->_saveInCache($sIdent, $sSeoUrl, $sType, $iLang, $iShopId, $sParams);
533  }
534  }
535 
536  return $sSeoUrl;
537  }
538 
545  protected function _getReservedEntryKeys()
546  {
547  if (!isset(self::$_aReservedEntryKeys) || !is_array(self::$_aReservedEntryKeys)) {
548  $sDir = getShopBasePath();
549  self::$_aReservedEntryKeys = array_map('preg_quote', self::$_aReservedWords, array('#'));
550  $oStr = getStr();
551  foreach (glob("$sDir/*") as $sFile) {
552  if ($oStr->preg_match('/^(.+)\.php[0-9]*$/i', basename($sFile), $aMatches)) {
553  self::$_aReservedEntryKeys[] = preg_quote($aMatches[0], '#');
554  self::$_aReservedEntryKeys[] = preg_quote($aMatches[1], '#');
555  } elseif (is_dir($sFile)) {
556  self::$_aReservedEntryKeys[] = preg_quote(basename($sFile), '#');
557  }
558  }
559  self::$_aReservedEntryKeys = array_unique(self::$_aReservedEntryKeys);
560  }
561 
563  }
564 
573  protected function _prepareUri($sUri, $iLang = false)
574  {
575  // decoding entities
576  $sUri = $this->encodeString($sUri, true, $iLang);
577 
578  // basic string preparation
579  $oStr = getStr();
580  $sUri = $oStr->strip_tags($sUri);
581 
582  // if found ".html" or "/" at the end - removing it temporary
583  $sExt = $this->_getUrlExtension();
584  if ($sExt === null) {
585  $aMatched = array();
586  if ($oStr->preg_match('/(\.html?|\/)$/i', $sUri, $aMatched)) {
587  $sExt = $aMatched[0];
588  } else {
589  $sExt = '/';
590  }
591  }
592  if ($sExt && $oStr->substr($sUri, 0 - $oStr->strlen($sExt)) == $sExt) {
593  $sUri = $oStr->substr($sUri, 0, $oStr->strlen($sUri) - $oStr->strlen($sExt));
594  }
595 
596  // removing any special characters
597  // #0004282 bugfix, php <5.3 does not escape - char, so we do it manually
598  $sQuotedPrefix = preg_quote(self::$_sSeparator . self::$_sPrefix, '/');
599  if (phpversion() < '5.3') {
600  $sQuotedPrefix = str_replace('-', '\-', $sQuotedPrefix);
601  }
602  $sRegExp = '/[^A-Za-z0-9' . $sQuotedPrefix . '\/]+/';
603  $sUri = $oStr->preg_replace(array("/\W*\/\W*/", $sRegExp), array("/", self::$_sSeparator), $sUri);
604 
605  // SEO id is empty ?
606  if (!$sUri && self::$_sPrefix) {
607  $sUri = $this->_prepareUri(self::$_sPrefix, $iLang);
608  }
609 
610  $sAdd = '';
611  if ('/' != self::$_sSeparator) {
612  $sAdd = self::$_sSeparator . self::$_sPrefix;
613  $sUri = trim($sUri, self::$_sSeparator);
614  } else {
615  $sAdd = '_' . self::$_sPrefix;
616  }
617 
618  // binding the ending back
619  $sUri .= $sExt;
620 
621  // fix for not having url, which executes through /other/ script then seo decoder
622  $sUri = $oStr->preg_replace("#^(/*)(" . implode('|', $this->_getReservedEntryKeys()) . ")(/|$)#i", "\$1\$2$sAdd\$3", $sUri);
623 
624  // cleaning
625  // #0004282 bugfix, php < 5.3 does not escape - char, so we do it manually\
626  $sQuotedSeparator = preg_quote(self::$_sSeparator, '/');
627  if (phpversion() < '5.3') {
628  $sQuotedSeparator = str_replace('-', '\-', $sQuotedSeparator);
629  }
630 
631  return $oStr->preg_replace(
632  array('|//+|', '/' . $sQuotedSeparator . $sQuotedSeparator . '+/'),
633  array('/', self::$_sSeparator), $sUri
634  );
635  }
636 
637 
647  protected function _prepareTitle($sTitle, $blSkipTruncate = false, $iLang = false)
648  {
649  $sTitle = $this->encodeString($sTitle, true, $iLang);
650  $sSep = self::$_sSeparator;
651  if (!$sSep || ('/' == $sSep)) {
652  $sSep = '_';
653  }
654 
655  $sRegExp = '/[^A-Za-z0-9\/' . preg_quote(self::$_sPrefix, '/') . preg_quote($sSep, '/') . ']+/';
656  $sTitle = preg_replace(array("#/+#", $sRegExp, "# +#", "#(" . preg_quote($sSep, '/') . ")+#"), $sSep, $sTitle);
657 
658  $oStr = getStr();
659  // smart truncate
660  if (!$blSkipTruncate && $oStr->strlen($sTitle) > $this->_iIdLength) {
661  $iFirstSpace = $oStr->strpos($sTitle, $sSep, $this->_iIdLength);
662  if ($iFirstSpace !== false) {
663  $sTitle = $oStr->substr($sTitle, 0, $iFirstSpace);
664  }
665  }
666 
667  $sTitle = trim($sTitle, $sSep);
668 
669  if (!$sTitle) {
670  return self::$_sPrefix;
671  }
672 
673  // cleaning
674  return $sTitle;
675  }
676 
677 
694  protected function _saveToDb($sType, $sObjectId, $sStdUrl, $sSeoUrl, $iLang, $iShopId = null, $blFixed = null, $sParams = null)
695  {
697  if ($iShopId === null) {
698  $iShopId = $this->getConfig()->getShopId();
699  }
700 
701  $iLang = (int) $iLang;
702 
703  $sStdUrl = $this->_trimUrl($sStdUrl);
704  $sSeoUrl = $this->_trimUrl($sSeoUrl);
705  $sIdent = $this->_getSeoIdent($sSeoUrl);
706 
707  // transferring old url, thus current url will be regenerated
708  $sQtedObjectId = $oDb->quote($sObjectId);
709  $iQtedShopId = $oDb->quote($iShopId);
710  $sQtedType = $oDb->quote($sType);
711  $sQtedSeoUrl = $oDb->quote($sSeoUrl);
712  $sQtedStdUrl = $oDb->quote($sStdUrl);
713  $sQtedParams = $oDb->quote($sParams);
714  $sQtedIdent = $oDb->quote($sIdent);
715 
716  // transferring old url, thus current url will be regenerated
717  $sQ = "select oxfixed, oxexpired, ( oxstdurl like {$sQtedStdUrl} ) as samestdurl,
718  oxseourl like {$sQtedSeoUrl} as sameseourl from oxseo where oxtype = {$sQtedType} and
719  oxobjectid = {$sQtedObjectId} and oxshopid = {$iQtedShopId} and oxlang = {$iLang} ";
720 
721  $sQ .= $sParams ? " and oxparams = {$sQtedParams} " : '';
722  $sQ .= "limit 1";
724  $oRs = $oDb->select($sQ);
725  if ($oRs && $oRs->recordCount() > 0 && !$oRs->EOF) {
726  if ($oRs->fields['samestdurl'] && $oRs->fields['sameseourl'] && $oRs->fields['oxexpired']) {
727  // fixed state change
728  $sFixed = isset($blFixed) ? ", oxfixed = " . ((int) $blFixed) . " " : '';
729  // nothing was changed - setting expired status back to 0
730  $sSql = "update oxseo set oxexpired = 0 {$sFixed} where oxtype = {$sQtedType} and
731  oxobjectid = {$sQtedObjectId} and oxshopid = {$iQtedShopId} and oxlang = {$iLang} ";
732  $sSql .= $sParams ? " and oxparams = {$sQtedParams} " : '';
733  $sSql .= " limit 1";
734 
735  return $oDb->execute($sSql);
736  } elseif ($oRs->fields['oxexpired']) {
737  // copy to history
738  $this->_copyToHistory($sObjectId, $iShopId, $iLang, $sType);
739  }
740  }
741 
742  // inserting new or updating
743  $sParams = $sParams ? $oDb->quote($sParams) : '""';
744  $blFixed = (int) $blFixed;
745 
746  $sQ = "insert into oxseo
747  (oxobjectid, oxident, oxshopid, oxlang, oxstdurl, oxseourl, oxtype, oxfixed, oxexpired, oxparams)
748  values
749  ( {$sQtedObjectId}, {$sQtedIdent}, {$iQtedShopId}, {$iLang}, {$sQtedStdUrl}, {$sQtedSeoUrl}, {$sQtedType}, '$blFixed', '0', {$sParams} )
750  on duplicate key update
751  oxident = {$sQtedIdent}, oxstdurl = {$sQtedStdUrl}, oxseourl = {$sQtedSeoUrl}, oxfixed = '$blFixed', oxexpired = '0'";
752 
753  return $oDb->execute($sQ);
754  }
755 
766  protected function _trimUrl($sUrl, $iLang = null)
767  {
768  $myConfig = $this->getConfig();
769  $oStr = getStr();
770  $sUrl = str_replace(array($myConfig->getShopUrl($iLang, false), $myConfig->getSslShopUrl($iLang)), '', $sUrl);
771  $sUrl = $oStr->preg_replace('/(\?|&(amp;)?)(force_)?(admin_)?sid=[a-z0-9\.]+&?(amp;)?/i', '\1', $sUrl);
772  $sUrl = $oStr->preg_replace('/(\?|&(amp;)?)shp=[0-9]+&?(amp;)?/i', '\1', $sUrl);
773  $sUrl = $oStr->preg_replace('/(\?|&(amp;)?)lang=[0-9]+&?(amp;)?/i', '\1', $sUrl);
774  $sUrl = $oStr->preg_replace('/(\?|&(amp;)?)cur=[0-9]+&?(amp;)?/i', '\1', $sUrl);
775  $sUrl = $oStr->preg_replace('/(\?|&(amp;)?)stoken=[a-z0-9]+&?(amp;)?/i', '\1', $sUrl);
776  $sUrl = $oStr->preg_replace('/(\?|&(amp;)?)&(amp;)?/i', '\1', $sUrl);
777  $sUrl = $oStr->preg_replace('/(\?|&(amp;)?)+$/i', '', $sUrl);
778  $sUrl = trim($sUrl);
779 
780  // max length <= $this->_iMaxUrlLength
781  $iLength = $this->_getMaxUrlLength();
782  if ($oStr->strlen($sUrl) > $iLength) {
783  $sUrl = $oStr->substr($sUrl, 0, $iLength);
784  }
785 
786  return $sUrl;
787  }
788 
794  protected function _getMaxUrlLength()
795  {
796  if ($this->_iMaxUrlLength === null) {
797  // max length <= 2048 / custom
798  $this->_iMaxUrlLength = $this->getConfig()->getConfigParam("iMaxSeoUrlLength");
799  if (!$this->_iMaxUrlLength) {
800  $this->_iMaxUrlLength = 2048;
801  }
802  }
803 
804  return $this->_iMaxUrlLength;
805  }
806 
816  public function encodeString($sString, $blReplaceChars = true, $iLang = false)
817  {
818  // decoding entities
819  $sString = getStr()->html_entity_decode($sString);
820 
821  if ($blReplaceChars) {
822  if ($iLang === false || !is_numeric($iLang)) {
823  $iLang = oxRegistry::getLang()->getEditLanguage();
824  }
825 
826  if ($aReplaceChars = oxRegistry::getLang()->getSeoReplaceChars($iLang)) {
827  $sString = str_replace(array_keys($aReplaceChars), array_values($aReplaceChars), $sString);
828  }
829  }
830 
831 
832  // special chars
833  $aReplaceWhat = array('&amp;', '&quot;', '&#039;', '&lt;', '&gt;');
834 
835  return str_replace($aReplaceWhat, '', $sString);
836  }
837 
843  public function setSeparator($sSeparator = null)
844  {
845  self::$_sSeparator = $sSeparator;
846  if (!self::$_sSeparator) {
847  self::$_sSeparator = '-';
848  }
849  }
850 
856  public function setPrefix($sPrefix)
857  {
858  if ($sPrefix) {
859  self::$_sPrefix = $sPrefix;
860  } else {
861  self::$_sPrefix = 'oxid';
862  }
863  }
864 
870  public function setIdLength($iIdlength = null)
871  {
872  if (isset($iIdlength)) {
873  $this->_iIdLength = $iIdlength;
874  }
875  }
876 
883  public function setReservedWords($aReservedWords)
884  {
885  self::$_aReservedWords = array_merge(self::$_aReservedWords, $aReservedWords);
886  }
887 
888 
898  public function markAsExpired($sId, $iShopId = null, $iExpStat = 1, $iLang = null, $sParams = null)
899  {
900  $oDb = oxDb::getDb();
901  $sWhere = $sId ? "where oxobjectid = " . $oDb->quote($sId) : '';
902  $sWhere .= isset($iShopId) ? ($sWhere ? " and oxshopid = " . $oDb->quote($iShopId) : "where oxshopid = " . $oDb->quote($iShopId)) : '';
903  $sWhere .= !is_null($iLang) ? ($sWhere ? " and oxlang = '{$iLang}'" : "where oxlang = '{$iLang}'") : '';
904  $sWhere .= $sParams ? ($sWhere ? " and {$sParams}" : "where {$sParams}") : '';
905 
906  $sQ = "update oxseo set oxexpired = " . $oDb->quote($iExpStat) . " $sWhere ";
907  $oDb->execute($sQ);
908  }
909 
923  protected function _getPageUri($oObject, $sType, $sStdUrl, $sSeoUrl, $sParams, $iLang = null, $blFixed = false)
924  {
925  if (!isset($iLang)) {
926  $iLang = $oObject->getLanguage();
927  }
928  $iShopId = $this->getConfig()->getShopId();
929 
930  //load page link from DB
931  $sOldSeoUrl = $this->_loadFromDb($sType, $oObject->getId(), $iLang, $iShopId, $sParams);
932  if (!$sOldSeoUrl) {
933  // generating new..
934  $sSeoUrl = $this->_processSeoUrl($sSeoUrl, $oObject->getId(), $iLang);
935  $this->_saveToDb($sType, $oObject->getId(), $sStdUrl, $sSeoUrl, $iLang, $iShopId, (int) $blFixed, $sParams);
936  } else {
937  // using old
938  $sSeoUrl = $sOldSeoUrl;
939  }
940 
941  return $sSeoUrl;
942  }
943 
952  protected function _getStaticObjectId($iShopId, $sStdUrl)
953  {
954  return md5(strtolower($iShopId . $this->_trimUrl($sStdUrl)));
955  }
956 
966  public function encodeStaticUrls($aStaticUrl, $iShopId, $iLang)
967  {
968  $oDb = oxDb::getDb();
969  $sValues = '';
970  $sOldObjectId = null;
971 
972  // standard url
973  $sStdUrl = $this->_trimUrl(trim($aStaticUrl['oxseo__oxstdurl']));
974  $sObjectId = $aStaticUrl['oxseo__oxobjectid'];
975 
976  if (!$sObjectId || $sObjectId == '-1') {
977  $sObjectId = $this->_getStaticObjectId($iShopId, $sStdUrl);
978  } else {
979  // marking entry as needs to move to history
980  $sOldObjectId = $sObjectId;
981 
982  // if std url does not match old
983  if ($this->_getStaticObjectId($iShopId, $sStdUrl) != $sObjectId) {
984  $sObjectId = $this->_getStaticObjectId($iShopId, $sStdUrl);
985  }
986  }
987 
988  foreach ($aStaticUrl['oxseo__oxseourl'] as $iLang => $sSeoUrl) {
989 
990  $iLang = (int) $iLang;
991 
992  // generating seo url
993  $sSeoUrl = $this->_trimUrl($sSeoUrl);
994  if ($sSeoUrl) {
995  $sSeoUrl = $this->_processSeoUrl($sSeoUrl, $sObjectId, $iLang);
996  }
997 
998 
999  if ($sOldObjectId) {
1000  // move changed records to history
1001  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)) {
1002  $this->_copyToHistory($sOldObjectId, $iShopId, $iLang, 'static', $sObjectId);
1003  }
1004  }
1005 
1006  if (!$sSeoUrl || !$sStdUrl) {
1007  continue;
1008  }
1009 
1010  $sIdent = $this->_getSeoIdent($sSeoUrl);
1011 
1012  if ($sValues) {
1013  $sValues .= ', ';
1014  }
1015 
1016  $sValues .= "( " . $oDb->quote($sObjectId) . ", " . $oDb->quote($sIdent) . ", " . $oDb->quote($iShopId) . ", '{$iLang}', " . $oDb->quote($sStdUrl) . ", " . $oDb->quote($sSeoUrl) . ", 'static' )";
1017  }
1018 
1019  // must delete old before insert/update
1020  if ($sOldObjectId) {
1021  $oDb->execute("delete from oxseo where oxobjectid in ( " . $oDb->quote($sOldObjectId) . ", " . $oDb->quote($sObjectId) . " )");
1022  }
1023 
1024  // (re)inserting
1025  if ($sValues) {
1026 
1027  $sQ = "insert into oxseo ( oxobjectid, oxident, oxshopid, oxlang, oxstdurl, oxseourl, oxtype ) values {$sValues} ";
1028  $oDb->execute($sQ);
1029  }
1030 
1031  return $sObjectId;
1032  }
1033 
1039  public function copyStaticUrls($iShopId)
1040  {
1041  $iBaseShopId = $this->getConfig()->getBaseShopId();
1042  if ($iShopId != $iBaseShopId) {
1043  $oDb = oxDb::getDb();
1044  foreach (array_keys(oxRegistry::getLang()->getLanguageIds()) as $iLang) {
1045  $sQ = "insert into oxseo ( oxobjectid, oxident, oxshopid, oxlang, oxstdurl, oxseourl, oxtype )
1046  select MD5( LOWER( CONCAT( " . $oDb->quote($iShopId) . ", oxstdurl ) ) ), MD5( LOWER( oxseourl ) ),
1047  " . $oDb->quote($iShopId) . ", oxlang, oxstdurl, oxseourl, oxtype from oxseo where oxshopid = '{$iBaseShopId}' and oxtype = 'static' and oxlang='$iLang' ";
1048  $oDb->execute($sQ);
1049  }
1050  }
1051  }
1052 
1062  public function getStaticUrl($sStdUrl, $iLang = null, $iShopId = null)
1063  {
1064  if (!isset($iShopId)) {
1065  $iShopId = $this->getConfig()->getShopId();
1066  }
1067  if (!isset($iLang)) {
1068  $iLang = oxRegistry::getLang()->getEditLanguage();
1069  }
1070 
1071  if (isset($this->_aStaticUrlCache[$sStdUrl][$iLang][$iShopId])) {
1072  return $this->_aStaticUrlCache[$sStdUrl][$iLang][$iShopId];
1073  }
1074 
1075  $sFullUrl = '';
1076  if (($sSeoUrl = $this->_getStaticUri($sStdUrl, $iShopId, $iLang))) {
1077  $sFullUrl = $this->_getFullUrl($sSeoUrl, $iLang, strpos($sStdUrl, "https:") === 0);
1078  }
1079 
1080 
1081  $this->_aStaticUrlCache[$sStdUrl][$iLang][$iShopId] = $sFullUrl;
1082 
1083  return $sFullUrl;
1084  }
1085 
1102  public function addSeoEntry($sObjectId, $iShopId, $iLang, $sStdUrl, $sSeoUrl, $sType, $blFixed = 1, $sKeywords = '', $sDescription = '', $sParams = '', $blExclude = false, $sAltObjectId = null)
1103  {
1104  $sSeoUrl = $this->_processSeoUrl($this->_trimUrl($sSeoUrl ? $sSeoUrl : $this->_getAltUri($sAltObjectId ? $sAltObjectId : $sObjectId, $iLang)), $sObjectId, $iLang, $blExclude);
1105  if ($this->_saveToDb($sType, $sObjectId, $sStdUrl, $sSeoUrl, $iLang, $iShopId, $blFixed, $sParams)) {
1106 
1107  $oDb = oxDb::getDb();
1108 
1109  //
1110  $sQtedObjectId = $oDb->quote($sAltObjectId ? $sAltObjectId : $sObjectId);
1111  $iQtedShopId = $oDb->quote($iShopId);
1112 
1113  $oStr = getStr();
1114  if ($sKeywords !== false) {
1115  $sKeywords = $oDb->quote($oStr->htmlspecialchars($this->encodeString($oStr->strip_tags($sKeywords), false, $iLang)));
1116  }
1117 
1118  if ($sDescription !== false) {
1119  $sDescription = $oDb->quote($oStr->htmlspecialchars($oStr->strip_tags($sDescription)));
1120  }
1121 
1122  $sQ = "insert into oxobject2seodata
1123  ( oxobjectid, oxshopid, oxlang, oxkeywords, oxdescription )
1124  values
1125  ( {$sQtedObjectId}, {$iQtedShopId}, {$iLang}, " . ($sKeywords ? $sKeywords : "''") . ", " . ($sDescription ? $sDescription : "''") . " )
1126  on duplicate key update
1127  oxkeywords = " . ($sKeywords ? $sKeywords : "oxkeywords") . ", oxdescription = " . ($sDescription ? $sDescription : "oxdescription");
1128  $oDb->execute($sQ);
1129  }
1130  }
1131 
1138  protected function _getAltUri($sObjectId, $iLang)
1139  {
1140  }
1141 
1150  public function deleteSeoEntry($sObjectId, $iShopId, $iLang, $sType)
1151  {
1152  $oDb = oxDb::getDb();
1153  $sQ = "delete from oxseo where oxobjectid = " . $oDb->quote($sObjectId) . " and oxshopid = " . $oDb->quote($iShopId) . " and oxlang = " . $oDb->quote($iLang) . " and oxtype = " . $oDb->quote($sType) . " ";
1154  $oDb->execute($sQ);
1155  }
1156 
1167  public function getMetaData($sObjectId, $sMetaType, $iShopId = null, $iLang = null)
1168  {
1169  $oDb = oxDb::getDb();
1170 
1171  $iShopId = (!isset($iShopId)) ? $this->getConfig()->getShopId() : $iShopId;
1172  $iLang = (!isset($iLang)) ? oxRegistry::getLang()->getObjectTplLanguage() : ((int) $iLang);
1173 
1174  return $oDb->getOne("select {$sMetaType} from oxobject2seodata where oxobjectid = " . $oDb->quote($sObjectId) . " and oxshopid = " . $oDb->quote($iShopId) . " and oxlang = '{$iLang}'");
1175  }
1176 
1190  public function getDynamicUrl($sStdUrl, $sSeoUrl, $iLang)
1191  {
1192  startProfile("getDynamicUrl");
1193  $sDynUrl = $this->_getFullUrl($this->_getDynamicUri($sStdUrl, $sSeoUrl, $iLang), $iLang, strpos($sStdUrl, "https:") === 0);
1194  stopProfile("getDynamicUrl");
1195 
1196  return $sDynUrl;
1197  }
1198 
1207  public function fetchSeoUrl($sStdUrl, $iLanguage = null)
1208  {
1210  $iLanguage = isset($iLanguage) ? ((int) $iLanguage) : oxRegistry::getLang()->getBaseLanguage();
1211  $sSeoUrl = false;
1212 
1213  $sShopId = $this->getConfig()->getShopId();
1214 
1215  $sQ = "SELECT `oxseourl`, `oxlang` FROM `oxseo` WHERE `oxstdurl` = " . $oDb->quote($sStdUrl) . " AND `oxlang` = '$iLanguage' AND `oxshopid` = '$sShopId' LIMIT 1";
1216 
1218  $oRs = $oDb->select($sQ);
1219 
1220  if (!$oRs->EOF) {
1221  $sSeoUrl = $oRs->fields['oxseourl'];
1222  }
1223 
1224  return $sSeoUrl;
1225  }
1226 
1227 }