OXID eShop CE  4.10.7
 All Classes Namespaces 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 &&
96  isset($aLangIds[$iLang]) &&
97  // #0006407 bugfix, we should not search for the string saved in the db but for the escaped string
98  getStr()->strpos($sSeoUrl, $this->_replaceSpecialChars($aLangIds[$iLang]) . '/') !== 0
99  ) {
100  $sSeoUrl = $aLangIds[$iLang] . '/' . $sSeoUrl;
101  }
102 
103  return $sSeoUrl;
104  }
105 
118  protected function _processSeoUrl($sSeoUrl, $sObjectId = null, $iLang = null, $blExclude = false)
119  {
120  if (!$blExclude) {
121  $sSeoUrl = $this->addLanguageParam($sSeoUrl, $iLang);
122  }
123 
124  return $this->_getUniqueSeoUrl($sSeoUrl, $sObjectId, $iLang);
125  }
126 
130  public function __construct()
131  {
132  $myConfig = $this->getConfig();
133  if (!self::$_sSeparator) {
134  $this->setSeparator($myConfig->getConfigParam('sSEOSeparator'));
135  }
136  if (!self::$_sPrefix) {
137  $this->setPrefix($myConfig->getConfigParam('sSEOuprefix'));
138  }
139  $this->setReservedWords($myConfig->getConfigParam('aSEOReservedWords'));
140  }
141 
151  protected function _copyToHistory($sId, $iShopId, $iLang, $sType = null, $sNewId = null)
152  {
153  $oDb = oxDb::getDb();
154  $sObjectid = $sNewId ? $oDb->quote($sNewId) : 'oxobjectid';
155  $sType = $sType ? "oxtype =" . $oDb->quote($sType) . " and" : '';
156  $iLang = (int) $iLang;
157 
158  // moving
159  $sSub = "select $sObjectid, MD5( LOWER( oxseourl ) ), oxshopid, oxlang, now() from oxseo
160  where {$sType} oxobjectid = " . $oDb->quote($sId) . " and oxshopid = " . $oDb->quote($iShopId) . " and
161  oxlang = {$iLang} and oxexpired = '1'";
162  $sQ = "replace oxseohistory ( oxobjectid, oxident, oxshopid, oxlang, oxinsert ) {$sSub}";
163  $oDb->execute($sQ);
164  }
165 
174  public function getDynamicObjectId($iShopId, $sStdUrl)
175  {
176  return $this->_getStaticObjectId($iShopId, $sStdUrl);
177  }
178 
188  protected function _getDynamicUri($sStdUrl, $sSeoUrl, $iLang)
189  {
190  $iShopId = $this->getConfig()->getShopId();
191 
192  $sStdUrl = $this->_trimUrl($sStdUrl);
193  $sObjectId = $this->getDynamicObjectId($iShopId, $sStdUrl);
194  $sSeoUrl = $this->_prepareUri($this->addLanguageParam($sSeoUrl, $iLang), $iLang);
195 
196  //load details link from DB
197  $sOldSeoUrl = $this->_loadFromDb('dynamic', $sObjectId, $iLang);
198  if ($sOldSeoUrl === $sSeoUrl) {
199  $sSeoUrl = $sOldSeoUrl;
200  } else {
201 
202  if ($sOldSeoUrl) {
203  // old must be transferred to history
204  $this->_copyToHistory($sObjectId, $iShopId, $iLang, 'dynamic');
205  }
206 
207  // creating unique
208  $sSeoUrl = $this->_processSeoUrl($sSeoUrl, $sObjectId, $iLang);
209 
210  // inserting
211  $this->_saveToDb('dynamic', $sObjectId, $sStdUrl, $sSeoUrl, $iLang, $iShopId);
212  }
213 
214  return $sSeoUrl;
215  }
216 
226  protected function _getFullUrl($sSeoUrl, $iLang = null, $blSsl = false)
227  {
228  if ($sSeoUrl) {
229  $sFullUrl = ($blSsl ? $this->getConfig()->getSslShopUrl($iLang) : $this->getConfig()->getShopUrl($iLang, false)) . $sSeoUrl;
230 
231  return oxRegistry::get("oxUtilsUrl")->processSeoUrl($sFullUrl);
232  }
233 
234  return false;
235  }
236 
246  protected function _getSeoIdent($sSeoUrl)
247  {
248  return md5(strtolower($sSeoUrl));
249  }
250 
260  protected function _getStaticUri($sStdUrl, $iShopId, $iLang)
261  {
262  $sStdUrl = $this->_trimUrl($sStdUrl, $iLang);
263 
264  return $this->_loadFromDb('static', $this->_getStaticObjectId($iShopId, $sStdUrl), $iLang);
265  }
266 
272  protected function _getUrlExtension()
273  {
274  return;
275  }
276 
289  protected function _getUniqueSeoUrl($sSeoUrl, $sObjectId = null, $iObjectLang = null)
290  {
291  $sSeoUrl = $this->_prepareUri($sSeoUrl, $iObjectLang);
292  $oStr = getStr();
293  $sExt = '';
294  if ($oStr->preg_match('/(\.html?|\/)$/i', $sSeoUrl, $aMatched)) {
295  $sExt = $aMatched[0];
296  }
297  $sBaseSeoUrl = $sSeoUrl;
298  if ($sExt && $oStr->substr($sSeoUrl, 0 - $oStr->strlen($sExt)) == $sExt) {
299  $sBaseSeoUrl = $oStr->substr($sSeoUrl, 0, $oStr->strlen($sSeoUrl) - $oStr->strlen($sExt));
300  }
301 
302  $iShopId = $this->getConfig()->getShopId();
303  $iCnt = 0;
304  $sCheckSeoUrl = $this->_trimUrl($sSeoUrl);
305  $sQ = "select 1 from oxseo where oxshopid = '{$iShopId}'";
306 
307  $oDb = oxDb::getDb();
308  // skipping self
309  if ($sObjectId && isset($iObjectLang)) {
310  $iObjectLang = (int) $iObjectLang;
311  $sQ .= " and not (oxobjectid = " . $oDb->quote($sObjectId) . " and oxlang = $iObjectLang)";
312  }
313 
314  while ($oDb->getOne($sQ . " and oxident= " . $oDb->quote($this->_getSeoIdent($sCheckSeoUrl)))) {
315  $sAdd = '';
316  if (self::$_sPrefix) {
317  $sAdd = self::$_sSeparator . self::$_sPrefix;
318  }
319  if ($iCnt) {
320  $sAdd .= self::$_sSeparator . $iCnt;
321  }
322  ++$iCnt;
323 
324  $sSeoUrl = $sBaseSeoUrl . $sAdd . $sExt;
325  $sCheckSeoUrl = $this->_trimUrl($sSeoUrl);
326  }
327 
328  return $sSeoUrl;
329  }
330 
345  protected function _isFixed($sType, $sId, $iLang, $iShopId = null, $sParams = null, $blStrictParamsCheck = true)
346  {
347  if ($iShopId === null) {
348  $iShopId = $this->getConfig()->getShopId();
349  }
350  $iLang = (int) $iLang;
351 
352  if (!isset(self::$_aFixedCache[$sType][$sShopId][$sId][$iLang])) {
353  $oDb = oxDb::getDb();
354 
355  $sQ = "SELECT `oxfixed`
356  FROM `oxseo`
357  WHERE `oxtype` = " . $oDb->quote($sType) . "
358  AND `oxobjectid` = " . $oDb->quote($sId) . "
359  AND `oxshopid` = " . $oDb->quote($iShopId) . "
360  AND `oxlang` = '{$iLang}'";
361 
362  $sParams = $sParams ? $oDb->quote($sParams) : "''";
363  if ($sParams && $blStrictParamsCheck) {
364  $sQ .= " AND `oxparams` = {$sParams}";
365  } else {
366  $sQ .= " ORDER BY `oxparams` ASC";
367  }
368  $sQ .= " LIMIT 1";
369 
370  self::$_aFixedCache[$sType][$sShopId][$sId][$iLang] = (bool) $oDb->getOne($sQ);
371  }
372 
373  return self::$_aFixedCache[$sType][$sShopId][$sId][$iLang];
374  }
375 
386  protected function _getCacheKey($sType, $iLang = null, $iShopId = null, $sParams = null)
387  {
388  $blAdmin = $this->isAdmin();
389  if (!$blAdmin && $sType !== "oxarticle") {
390  return $sType . ((int) $iLang) . ((int) $iShopId) . "seo";
391  }
392 
393  // use cache in non admin mode
394  if (self::$_sCacheKey === null) {
395  self::$_sCacheKey = false;
396  if (!$blAdmin && ($oView = $this->getConfig()->getActiveView())) {
397  self::$_sCacheKey = md5($oView->getViewId()) . "seo";
398  }
399  }
400 
401  return self::$_sCacheKey;
402  }
403 
415  protected function _loadFromCache($sCacheIdent, $sType, $iLang = null, $iShopId = null, $sParams = null)
416  {
417  if (!$this->getConfig()->getConfigParam('blEnableSeoCache')) {
418  return false;
419  }
420 
421  startProfile("seoencoder_loadFromCache");
422 
423  $sCacheKey = $this->_getCacheKey($sType, $iLang, $iShopId, $sParams);
424  $sCache = false;
425 
426  if ($sCacheKey && !isset(self::$_aCache[$sCacheKey])) {
427  self::$_aCache[$sCacheKey] = oxRegistry::getUtils()->fromFileCache($sCacheKey);
428  }
429 
430  if (isset(self::$_aCache[$sCacheKey]) && isset(self::$_aCache[$sCacheKey][$sCacheIdent])) {
431  $sCache = self::$_aCache[$sCacheKey][$sCacheIdent];
432  }
433 
434  stopProfile("seoencoder_loadFromCache");
435 
436  return $sCache;
437  }
438 
451  protected function _saveInCache($sCacheIdent, $sCache, $sType, $iLang = null, $iShopId = null, $sParams = null)
452  {
453  if (!$this->getConfig()->getConfigParam('blEnableSeoCache')) {
454  return false;
455  }
456 
457  startProfile("seoencoder_saveInCache");
458 
459  $blSaved = false;
460  if ($sCache && ($sCacheKey = $this->_getCacheKey($sType, $iLang, $iShopId, $sParams)) !== false) {
461  self::$_aCache[$sCacheKey][$sCacheIdent] = $sCache;
462  $blSaved = oxRegistry::getUtils()->toFileCache($sCacheKey, self::$_aCache[$sCacheKey]);
463  }
464 
465  stopProfile("seoencoder_saveInCache");
466 
467  return $blSaved;
468  }
469 
485  protected function _loadFromDb($sType, $sId, $iLang, $iShopId = null, $sParams = null, $blStrictParamsCheck = true)
486  {
487 
488  if ($iShopId === null) {
489  $iShopId = $this->getConfig()->getShopId();
490  }
491 
492  $iLang = (int) $iLang;
494 
495  $sQ = "
496  SELECT
497  `oxfixed`,
498  `oxseourl`,
499  `oxexpired`,
500  `oxtype`
501  FROM `oxseo`
502  WHERE `oxtype` = " . $oDb->quote($sType) . "
503  AND `oxobjectid` = " . $oDb->quote($sId) . "
504  AND `oxshopid` = " . $oDb->quote($iShopId) . "
505  AND `oxlang` = '{$iLang}'";
506 
507  $sParams = $sParams ? $sParams : '';
508  if ($sParams && $blStrictParamsCheck) {
509  $sQ .= " AND `oxparams` = '{$sParams}'";
510  } else {
511  $sQ .= " ORDER BY `oxparams` ASC";
512  }
513  $sQ .= " LIMIT 1";
514 
515 
516  // caching to avoid same queries..
517  $sIdent = md5($sQ);
518 
519  // looking in cache
520  if (($sSeoUrl = $this->_loadFromCache($sIdent, $sType, $iLang, $iShopId, $sParams)) === false) {
521  $oRs = $oDb->select($sQ);
522 
523  if ($oRs && $oRs->recordCount() > 0 && !$oRs->EOF) {
524  // moving expired static urls to history ..
525  if ($oRs->fields['oxexpired'] && ($oRs->fields['oxtype'] == 'static' || $oRs->fields['oxtype'] == 'dynamic')) {
526  // if expired - copying to history, marking as not expired
527  $this->_copyToHistory($sId, $iShopId, $iLang);
528  $oDb->execute("update oxseo set oxexpired = 0 where oxobjectid = " . $oDb->quote($sId) . " and oxlang = '{$iLang}'");
529  $sSeoUrl = $oRs->fields['oxseourl'];
530  } elseif (!$oRs->fields['oxexpired'] || $oRs->fields['oxfixed']) {
531  // if seo url is available and is valid
532  $sSeoUrl = $oRs->fields['oxseourl'];
533  }
534 
535  // storing in cache
536  $this->_saveInCache($sIdent, $sSeoUrl, $sType, $iLang, $iShopId, $sParams);
537  }
538  }
539 
540  return $sSeoUrl;
541  }
542 
549  protected function _getReservedEntryKeys()
550  {
551  if (!isset(self::$_aReservedEntryKeys) || !is_array(self::$_aReservedEntryKeys)) {
552  $sDir = getShopBasePath();
553  self::$_aReservedEntryKeys = array_map('preg_quote', self::$_aReservedWords, array('#'));
554  $oStr = getStr();
555  foreach (glob("$sDir/*") as $sFile) {
556  if ($oStr->preg_match('/^(.+)\.php[0-9]*$/i', basename($sFile), $aMatches)) {
557  self::$_aReservedEntryKeys[] = preg_quote($aMatches[0], '#');
558  self::$_aReservedEntryKeys[] = preg_quote($aMatches[1], '#');
559  } elseif (is_dir($sFile)) {
560  self::$_aReservedEntryKeys[] = preg_quote(basename($sFile), '#');
561  }
562  }
563  self::$_aReservedEntryKeys = array_unique(self::$_aReservedEntryKeys);
564  }
565 
567  }
568 
577  protected function _prepareUri($sUri, $iLang = false)
578  {
579  // decoding entities
580  $sUri = $this->encodeString($sUri, true, $iLang);
581 
582  // basic string preparation
583  $oStr = getStr();
584  $sUri = $oStr->strip_tags($sUri);
585 
586  // if found ".html" or "/" at the end - removing it temporary
587  $sExt = $this->_getUrlExtension();
588  if ($sExt === null) {
589  $aMatched = array();
590  if ($oStr->preg_match('/(\.html?|\/)$/i', $sUri, $aMatched)) {
591  $sExt = $aMatched[0];
592  } else {
593  $sExt = '/';
594  }
595  }
596  if ($sExt && $oStr->substr($sUri, 0 - $oStr->strlen($sExt)) == $sExt) {
597  $sUri = $oStr->substr($sUri, 0, $oStr->strlen($sUri) - $oStr->strlen($sExt));
598  }
599 
600  $sUri = $this->_replaceSpecialChars($sUri);
601 
602  // SEO id is empty ?
603  if (!$sUri && self::$_sPrefix) {
604  $sUri = $this->_prepareUri(self::$_sPrefix, $iLang);
605  }
606 
607  $sAdd = '';
608  if ('/' != self::$_sSeparator) {
609  $sAdd = self::$_sSeparator . self::$_sPrefix;
610  $sUri = trim($sUri, self::$_sSeparator);
611  } else {
612  $sAdd = '_' . self::$_sPrefix;
613  }
614 
615  // binding the ending back
616  $sUri .= $sExt;
617 
618  // fix for not having url, which executes through /other/ script then seo decoder
619  $sUri = $oStr->preg_replace("#^(/*)(" . implode('|', $this->_getReservedEntryKeys()) . ")(/|$)#i", "\$1\$2$sAdd\$3", $sUri);
620 
621  // cleaning
622  // #0004282 bugfix, php < 5.3 does not escape - char, so we do it manually\
623  $sQuotedSeparator = preg_quote(self::$_sSeparator, '/');
624  if (phpversion() < '5.3') {
625  $sQuotedSeparator = str_replace('-', '\-', $sQuotedSeparator);
626  }
627 
628  return $oStr->preg_replace(
629  array('|//+|', '/' . $sQuotedSeparator . $sQuotedSeparator . '+/'),
630  array('/', self::$_sSeparator), $sUri
631  );
632  }
633 
634 
644  protected function _prepareTitle($sTitle, $blSkipTruncate = false, $iLang = false)
645  {
646  $sTitle = $this->encodeString($sTitle, true, $iLang);
647  $sSep = self::$_sSeparator;
648  if (!$sSep || ('/' == $sSep)) {
649  $sSep = '_';
650  }
651 
652  $sRegExp = '/[^A-Za-z0-9\/' . preg_quote(self::$_sPrefix, '/') . preg_quote($sSep, '/') . ']+/';
653  $sTitle = preg_replace(array("#/+#", $sRegExp, "# +#", "#(" . preg_quote($sSep, '/') . ")+#"), $sSep, $sTitle);
654 
655  $oStr = getStr();
656  // smart truncate
657  if (!$blSkipTruncate && $oStr->strlen($sTitle) > $this->_iIdLength) {
658  $iFirstSpace = $oStr->strpos($sTitle, $sSep, $this->_iIdLength);
659  if ($iFirstSpace !== false) {
660  $sTitle = $oStr->substr($sTitle, 0, $iFirstSpace);
661  }
662  }
663 
664  $sTitle = trim($sTitle, $sSep);
665 
666  if (!$sTitle) {
667  return self::$_sPrefix;
668  }
669 
670  // cleaning
671  return $sTitle;
672  }
673 
674 
691  protected function _saveToDb($sType, $sObjectId, $sStdUrl, $sSeoUrl, $iLang, $iShopId = null, $blFixed = null, $sParams = null)
692  {
694  if ($iShopId === null) {
695  $iShopId = $this->getConfig()->getShopId();
696  }
697 
698  $iLang = (int) $iLang;
699 
700  $sStdUrl = $this->_trimUrl($sStdUrl);
701  $sSeoUrl = $this->_trimUrl($sSeoUrl);
702  $sIdent = $this->_getSeoIdent($sSeoUrl);
703 
704  // transferring old url, thus current url will be regenerated
705  $sQtedObjectId = $oDb->quote($sObjectId);
706  $iQtedShopId = $oDb->quote($iShopId);
707  $sQtedType = $oDb->quote($sType);
708  $sQtedSeoUrl = $oDb->quote($sSeoUrl);
709  $sQtedStdUrl = $oDb->quote($sStdUrl);
710  $sQtedParams = $oDb->quote($sParams);
711  $sQtedIdent = $oDb->quote($sIdent);
712 
713  // transferring old url, thus current url will be regenerated
714  $sQ = "select oxfixed, oxexpired, ( oxstdurl like {$sQtedStdUrl} ) as samestdurl,
715  oxseourl like {$sQtedSeoUrl} as sameseourl from oxseo where oxtype = {$sQtedType} and
716  oxobjectid = {$sQtedObjectId} and oxshopid = {$iQtedShopId} and oxlang = {$iLang} ";
717 
718  $sQ .= $sParams ? " and oxparams = {$sQtedParams} " : '';
719  $sQ .= "limit 1";
721  $oRs = $oDb->select($sQ);
722  if ($oRs && $oRs->recordCount() > 0 && !$oRs->EOF) {
723  if ($oRs->fields['samestdurl'] && $oRs->fields['sameseourl'] && $oRs->fields['oxexpired']) {
724  // fixed state change
725  $sFixed = isset($blFixed) ? ", oxfixed = " . ((int) $blFixed) . " " : '';
726  // nothing was changed - setting expired status back to 0
727  $sSql = "update oxseo set oxexpired = 0 {$sFixed} where oxtype = {$sQtedType} and
728  oxobjectid = {$sQtedObjectId} and oxshopid = {$iQtedShopId} and oxlang = {$iLang} ";
729  $sSql .= $sParams ? " and oxparams = {$sQtedParams} " : '';
730  $sSql .= " limit 1";
731 
732  return $oDb->execute($sSql);
733  } elseif ($oRs->fields['oxexpired']) {
734  // copy to history
735  $this->_copyToHistory($sObjectId, $iShopId, $iLang, $sType);
736  }
737  }
738 
739  // inserting new or updating
740  $sParams = $sParams ? $oDb->quote($sParams) : '""';
741  $blFixed = (int) $blFixed;
742 
743  $sQ = "insert into oxseo
744  (oxobjectid, oxident, oxshopid, oxlang, oxstdurl, oxseourl, oxtype, oxfixed, oxexpired, oxparams)
745  values
746  ( {$sQtedObjectId}, {$sQtedIdent}, {$iQtedShopId}, {$iLang}, {$sQtedStdUrl}, {$sQtedSeoUrl}, {$sQtedType}, '$blFixed', '0', {$sParams} )
747  on duplicate key update
748  oxident = {$sQtedIdent}, oxstdurl = {$sQtedStdUrl}, oxseourl = {$sQtedSeoUrl}, oxfixed = '$blFixed', oxexpired = '0'";
749 
750  return $oDb->execute($sQ);
751  }
752 
763  protected function _trimUrl($sUrl, $iLang = null)
764  {
765  $myConfig = $this->getConfig();
766  $oStr = getStr();
767  $sUrl = str_replace(array($myConfig->getShopUrl($iLang, false), $myConfig->getSslShopUrl($iLang)), '', $sUrl);
768  $sUrl = $oStr->preg_replace('/(\?|&(amp;)?)(force_)?(admin_)?sid=[a-z0-9\.]+&?(amp;)?/i', '\1', $sUrl);
769  $sUrl = $oStr->preg_replace('/(\?|&(amp;)?)shp=[0-9]+&?(amp;)?/i', '\1', $sUrl);
770  $sUrl = $oStr->preg_replace('/(\?|&(amp;)?)lang=[0-9]+&?(amp;)?/i', '\1', $sUrl);
771  $sUrl = $oStr->preg_replace('/(\?|&(amp;)?)cur=[0-9]+&?(amp;)?/i', '\1', $sUrl);
772  $sUrl = $oStr->preg_replace('/(\?|&(amp;)?)stoken=[a-z0-9]+&?(amp;)?/i', '\1', $sUrl);
773  $sUrl = $oStr->preg_replace('/(\?|&(amp;)?)&(amp;)?/i', '\1', $sUrl);
774  $sUrl = $oStr->preg_replace('/(\?|&(amp;)?)+$/i', '', $sUrl);
775  $sUrl = trim($sUrl);
776 
777  // max length <= $this->_iMaxUrlLength
778  $iLength = $this->_getMaxUrlLength();
779  if ($oStr->strlen($sUrl) > $iLength) {
780  $sUrl = $oStr->substr($sUrl, 0, $iLength);
781  }
782 
783  return $sUrl;
784  }
785 
791  protected function _getMaxUrlLength()
792  {
793  if ($this->_iMaxUrlLength === null) {
794  // max length <= 2048 / custom
795  $this->_iMaxUrlLength = $this->getConfig()->getConfigParam("iMaxSeoUrlLength");
796  if (!$this->_iMaxUrlLength) {
797  $this->_iMaxUrlLength = 2048;
798  }
799  }
800 
801  return $this->_iMaxUrlLength;
802  }
803 
813  public function encodeString($sString, $blReplaceChars = true, $iLang = false)
814  {
815  // decoding entities
816  $sString = getStr()->html_entity_decode($sString);
817 
818  if ($blReplaceChars) {
819  if ($iLang === false || !is_numeric($iLang)) {
820  $iLang = oxRegistry::getLang()->getEditLanguage();
821  }
822 
823  if ($aReplaceChars = oxRegistry::getLang()->getSeoReplaceChars($iLang)) {
824  $sString = str_replace(array_keys($aReplaceChars), array_values($aReplaceChars), $sString);
825  }
826  }
827 
828 
829  // special chars
830  $aReplaceWhat = array('&amp;', '&quot;', '&#039;', '&lt;', '&gt;');
831 
832  return str_replace($aReplaceWhat, '', $sString);
833  }
834 
840  public function setSeparator($sSeparator = null)
841  {
842  self::$_sSeparator = $sSeparator;
843  if (!self::$_sSeparator) {
844  self::$_sSeparator = '-';
845  }
846  }
847 
853  public function setPrefix($sPrefix)
854  {
855  if ($sPrefix) {
856  self::$_sPrefix = $sPrefix;
857  } else {
858  self::$_sPrefix = 'oxid';
859  }
860  }
861 
867  public function setIdLength($iIdlength = null)
868  {
869  if (isset($iIdlength)) {
870  $this->_iIdLength = $iIdlength;
871  }
872  }
873 
880  public function setReservedWords($aReservedWords)
881  {
882  self::$_aReservedWords = array_merge(self::$_aReservedWords, $aReservedWords);
883  }
884 
885 
895  public function markAsExpired($sId, $iShopId = null, $iExpStat = 1, $iLang = null, $sParams = null)
896  {
897  $oDb = oxDb::getDb();
898  $sWhere = $sId ? "where oxobjectid = " . $oDb->quote($sId) : '';
899  $sWhere .= isset($iShopId) ? ($sWhere ? " and oxshopid = " . $oDb->quote($iShopId) : "where oxshopid = " . $oDb->quote($iShopId)) : '';
900  $sWhere .= !is_null($iLang) ? ($sWhere ? " and oxlang = '{$iLang}'" : "where oxlang = '{$iLang}'") : '';
901  $sWhere .= $sParams ? ($sWhere ? " and {$sParams}" : "where {$sParams}") : '';
902 
903  $sQ = "update oxseo set oxexpired = " . $oDb->quote($iExpStat) . " $sWhere ";
904  $oDb->execute($sQ);
905  }
906 
920  protected function _getPageUri($oObject, $sType, $sStdUrl, $sSeoUrl, $sParams, $iLang = null, $blFixed = false)
921  {
922  if (!isset($iLang)) {
923  $iLang = $oObject->getLanguage();
924  }
925  $iShopId = $this->getConfig()->getShopId();
926 
927  //load page link from DB
928  $sOldSeoUrl = $this->_loadFromDb($sType, $oObject->getId(), $iLang, $iShopId, $sParams);
929  if (!$sOldSeoUrl) {
930  // generating new..
931  $sSeoUrl = $this->_processSeoUrl($sSeoUrl, $oObject->getId(), $iLang);
932  $this->_saveToDb($sType, $oObject->getId(), $sStdUrl, $sSeoUrl, $iLang, $iShopId, (int) $blFixed, $sParams);
933  } else {
934  // using old
935  $sSeoUrl = $sOldSeoUrl;
936  }
937 
938  return $sSeoUrl;
939  }
940 
949  protected function _getStaticObjectId($iShopId, $sStdUrl)
950  {
951  return md5(strtolower($iShopId . $this->_trimUrl($sStdUrl)));
952  }
953 
963  public function encodeStaticUrls($aStaticUrl, $iShopId, $iLang)
964  {
965  $oDb = oxDb::getDb();
966  $sValues = '';
967  $sOldObjectId = null;
968 
969  // standard url
970  $sStdUrl = $this->_trimUrl(trim($aStaticUrl['oxseo__oxstdurl']));
971  $sObjectId = $aStaticUrl['oxseo__oxobjectid'];
972 
973  if (!$sObjectId || $sObjectId == '-1') {
974  $sObjectId = $this->_getStaticObjectId($iShopId, $sStdUrl);
975  } else {
976  // marking entry as needs to move to history
977  $sOldObjectId = $sObjectId;
978 
979  // if std url does not match old
980  if ($this->_getStaticObjectId($iShopId, $sStdUrl) != $sObjectId) {
981  $sObjectId = $this->_getStaticObjectId($iShopId, $sStdUrl);
982  }
983  }
984 
985  foreach ($aStaticUrl['oxseo__oxseourl'] as $iLang => $sSeoUrl) {
986 
987  $iLang = (int) $iLang;
988 
989  // generating seo url
990  $sSeoUrl = $this->_trimUrl($sSeoUrl);
991  if ($sSeoUrl) {
992  $sSeoUrl = $this->_processSeoUrl($sSeoUrl, $sObjectId, $iLang);
993  }
994 
995 
996  if ($sOldObjectId) {
997  // move changed records to history
998  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)) {
999  $this->_copyToHistory($sOldObjectId, $iShopId, $iLang, 'static', $sObjectId);
1000  }
1001  }
1002 
1003  if (!$sSeoUrl || !$sStdUrl) {
1004  continue;
1005  }
1006 
1007  $sIdent = $this->_getSeoIdent($sSeoUrl);
1008 
1009  if ($sValues) {
1010  $sValues .= ', ';
1011  }
1012 
1013  $sValues .= "( " . $oDb->quote($sObjectId) . ", " . $oDb->quote($sIdent) . ", " . $oDb->quote($iShopId) . ", '{$iLang}', " . $oDb->quote($sStdUrl) . ", " . $oDb->quote($sSeoUrl) . ", 'static' )";
1014  }
1015 
1016  // must delete old before insert/update
1017  if ($sOldObjectId) {
1018  $oDb->execute("delete from oxseo where oxobjectid in ( " . $oDb->quote($sOldObjectId) . ", " . $oDb->quote($sObjectId) . " )");
1019  }
1020 
1021  // (re)inserting
1022  if ($sValues) {
1023 
1024  $sQ = "insert into oxseo ( oxobjectid, oxident, oxshopid, oxlang, oxstdurl, oxseourl, oxtype ) values {$sValues} ";
1025  $oDb->execute($sQ);
1026  }
1027 
1028  return $sObjectId;
1029  }
1030 
1036  public function copyStaticUrls($iShopId)
1037  {
1038  $iBaseShopId = $this->getConfig()->getBaseShopId();
1039  if ($iShopId != $iBaseShopId) {
1040  $oDb = oxDb::getDb();
1041  foreach (array_keys(oxRegistry::getLang()->getLanguageIds()) as $iLang) {
1042  $sQ = "insert into oxseo ( oxobjectid, oxident, oxshopid, oxlang, oxstdurl, oxseourl, oxtype )
1043  select MD5( LOWER( CONCAT( " . $oDb->quote($iShopId) . ", oxstdurl ) ) ), MD5( LOWER( oxseourl ) ),
1044  " . $oDb->quote($iShopId) . ", oxlang, oxstdurl, oxseourl, oxtype from oxseo where oxshopid = '{$iBaseShopId}' and oxtype = 'static' and oxlang='$iLang' ";
1045  $oDb->execute($sQ);
1046  }
1047  }
1048  }
1049 
1059  public function getStaticUrl($sStdUrl, $iLang = null, $iShopId = null)
1060  {
1061  if (!isset($iShopId)) {
1062  $iShopId = $this->getConfig()->getShopId();
1063  }
1064  if (!isset($iLang)) {
1065  $iLang = oxRegistry::getLang()->getEditLanguage();
1066  }
1067 
1068  if (isset($this->_aStaticUrlCache[$sStdUrl][$iLang][$iShopId])) {
1069  return $this->_aStaticUrlCache[$sStdUrl][$iLang][$iShopId];
1070  }
1071 
1072  $sFullUrl = '';
1073  if (($sSeoUrl = $this->_getStaticUri($sStdUrl, $iShopId, $iLang))) {
1074  $sFullUrl = $this->_getFullUrl($sSeoUrl, $iLang, strpos($sStdUrl, "https:") === 0);
1075  }
1076 
1077 
1078  $this->_aStaticUrlCache[$sStdUrl][$iLang][$iShopId] = $sFullUrl;
1079 
1080  return $sFullUrl;
1081  }
1082 
1099  public function addSeoEntry($sObjectId, $iShopId, $iLang, $sStdUrl, $sSeoUrl, $sType, $blFixed = 1, $sKeywords = '', $sDescription = '', $sParams = '', $blExclude = false, $sAltObjectId = null)
1100  {
1101  $sSeoUrl = $this->_processSeoUrl($this->_trimUrl($sSeoUrl ? $sSeoUrl : $this->_getAltUri($sAltObjectId ? $sAltObjectId : $sObjectId, $iLang)), $sObjectId, $iLang, $blExclude);
1102  if ($this->_saveToDb($sType, $sObjectId, $sStdUrl, $sSeoUrl, $iLang, $iShopId, $blFixed, $sParams)) {
1103 
1104  $oDb = oxDb::getDb();
1105 
1106  //
1107  $sQtedObjectId = $oDb->quote($sAltObjectId ? $sAltObjectId : $sObjectId);
1108  $iQtedShopId = $oDb->quote($iShopId);
1109 
1110  $oStr = getStr();
1111  if ($sKeywords !== false) {
1112  $sKeywords = $oDb->quote($oStr->htmlspecialchars($this->encodeString($oStr->strip_tags($sKeywords), false, $iLang)));
1113  }
1114 
1115  if ($sDescription !== false) {
1116  $sDescription = $oDb->quote($oStr->htmlspecialchars($oStr->strip_tags($sDescription)));
1117  }
1118 
1119  $sQ = "insert into oxobject2seodata
1120  ( oxobjectid, oxshopid, oxlang, oxkeywords, oxdescription )
1121  values
1122  ( {$sQtedObjectId}, {$iQtedShopId}, {$iLang}, " . ($sKeywords ? $sKeywords : "''") . ", " . ($sDescription ? $sDescription : "''") . " )
1123  on duplicate key update
1124  oxkeywords = " . ($sKeywords ? $sKeywords : "oxkeywords") . ", oxdescription = " . ($sDescription ? $sDescription : "oxdescription");
1125  $oDb->execute($sQ);
1126  }
1127  }
1128 
1135  protected function _getAltUri($sObjectId, $iLang)
1136  {
1137  }
1138 
1147  public function deleteSeoEntry($sObjectId, $iShopId, $iLang, $sType)
1148  {
1149  $oDb = oxDb::getDb();
1150  $sQ = "delete from oxseo where oxobjectid = " . $oDb->quote($sObjectId) . " and oxshopid = " . $oDb->quote($iShopId) . " and oxlang = " . $oDb->quote($iLang) . " and oxtype = " . $oDb->quote($sType) . " ";
1151  $oDb->execute($sQ);
1152  }
1153 
1164  public function getMetaData($sObjectId, $sMetaType, $iShopId = null, $iLang = null)
1165  {
1166  $oDb = oxDb::getDb();
1167 
1168  $iShopId = (!isset($iShopId)) ? $this->getConfig()->getShopId() : $iShopId;
1169  $iLang = (!isset($iLang)) ? oxRegistry::getLang()->getObjectTplLanguage() : ((int) $iLang);
1170 
1171  return $oDb->getOne("select {$sMetaType} from oxobject2seodata where oxobjectid = " . $oDb->quote($sObjectId) . " and oxshopid = " . $oDb->quote($iShopId) . " and oxlang = '{$iLang}'");
1172  }
1173 
1187  public function getDynamicUrl($sStdUrl, $sSeoUrl, $iLang)
1188  {
1189  startProfile("getDynamicUrl");
1190  $sDynUrl = $this->_getFullUrl($this->_getDynamicUri($sStdUrl, $sSeoUrl, $iLang), $iLang, strpos($sStdUrl, "https:") === 0);
1191  stopProfile("getDynamicUrl");
1192 
1193  return $sDynUrl;
1194  }
1195 
1204  public function fetchSeoUrl($sStdUrl, $iLanguage = null)
1205  {
1207  $iLanguage = isset($iLanguage) ? ((int) $iLanguage) : oxRegistry::getLang()->getBaseLanguage();
1208  $sSeoUrl = false;
1209 
1210  $sShopId = $this->getConfig()->getShopId();
1211 
1212  $sQ = "SELECT `oxseourl`, `oxlang` FROM `oxseo` WHERE `oxstdurl` = " . $oDb->quote($sStdUrl) . " AND `oxlang` = '$iLanguage' AND `oxshopid` = '$sShopId' LIMIT 1";
1213 
1215  $oRs = $oDb->select($sQ);
1216 
1217  if (!$oRs->EOF) {
1218  $sSeoUrl = $oRs->fields['oxseourl'];
1219  }
1220 
1221  return $sSeoUrl;
1222  }
1223 
1231  protected function _replaceSpecialChars($sStringWithSpecialChars)
1232  {
1233  if (!is_string($sStringWithSpecialChars)) {
1234  return "";
1235  }
1236  $oStr = oxStr::getStr();
1237  // #0004282 bugfix, php <5.3 does not escape - char, so we do it manually
1238  $sQuotedPrefix = preg_quote(self::$_sSeparator . self::$_sPrefix, '/');
1239  if (phpversion() < '5.3') {
1240  $sQuotedPrefix = str_replace('-', '\-', $sQuotedPrefix);
1241  }
1242  $sRegExp = '/[^A-Za-z0-9' . $sQuotedPrefix . '\/]+/';
1243  $sanitized = $oStr->preg_replace(
1244  array("/\W*\/\W*/", $sRegExp),
1245  array("/", self::$_sSeparator),
1246  $sStringWithSpecialChars
1247  );
1248 
1249  return $sanitized;
1250  }
1251 
1252 }