BxDolPermalinks.php 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319
  1. <?php defined('BX_DOL') or die('hack attempt');
  2. /**
  3. * Copyright (c) UNA, Inc - https://una.io
  4. * MIT License - https://opensource.org/licenses/MIT
  5. *
  6. * @defgroup UnaCore UNA Core
  7. * @{
  8. */
  9. /**
  10. * Permalinks for any content.
  11. *
  12. * An object of the class allows to check whether permalink is enabled
  13. * and get it for specified standard URI.
  14. *
  15. *
  16. * All permalinks must match the whole URL, to be automatially determined.
  17. * There are only 2 permlinks which are replaced by prefix:
  18. * - modules/?r= - for module URLs
  19. * - page.php?i= - for pages URLs
  20. *
  21. *
  22. * @section example Example of usage:
  23. *
  24. * 1. Register permalink in database by adding necessary info in the `sys_permalinks` table.
  25. * 2. Create an object and process the URI
  26. * @code
  27. * $oPermalinks = new BxDolPermalinks();
  28. * $oPermalinks->permalink('modules/?r=news/home');
  29. * @endcode
  30. *
  31. *
  32. * @section acl Memberships/ACL:
  33. * Doesn't depend on user's membership.
  34. *
  35. *
  36. * @section alerts Alerts:
  37. *
  38. * The following alert can be used when permalink call is performed:
  39. * - $sUnit - system
  40. * - $sAction - permalink | unpermalink
  41. * - $iObjectId - 0 (not used)
  42. * - $iSenderId - 0 (not used)
  43. * - $aExtra['link'] - the link to check permalink for
  44. * - $aExtra['return_data'] - it is possible to override default behavior, by setting 'return_data' to non NULL value.
  45. *
  46. */
  47. class BxDolPermalinks extends BxDolDb implements iBxDolSingleton
  48. {
  49. protected $sCacheFile;
  50. protected $aLinksStandard;
  51. protected $aLinksPermalink;
  52. protected $aPrefixesStandard;
  53. protected $aPrefixesPermalink;
  54. protected function __construct()
  55. {
  56. if (isset($GLOBALS['bxDolClasses'][get_class($this)]))
  57. trigger_error ('Multiple instances are not allowed for the class: ' . get_class($this), E_USER_ERROR);
  58. parent::__construct();
  59. if (getParam('sys_db_cache_enable')) {
  60. $oCache = $this->getDbCacheObject();
  61. $sCacheKey = $this->genDbCacheKey('sys_permalinks');
  62. $aPermalinksData = $oCache->getData($sCacheKey);
  63. if (null === $aPermalinksData) {
  64. $aPermalinksData = $this->getPermalinksData();
  65. $oCache->setData ($sCacheKey, $aPermalinksData);
  66. }
  67. } else {
  68. $aPermalinksData = $this->getPermalinksData();
  69. }
  70. $this->aLinksStandard = $aPermalinksData['standard'];
  71. $this->aLinksPermalink = $aPermalinksData['permalink'];
  72. $this->aPrefixesStandard = $aPermalinksData['prefixes_standard'];
  73. $this->aPrefixesPermalink = $aPermalinksData['prefixes_permalink'];
  74. }
  75. public function cacheInvalidate()
  76. {
  77. return BxDolDb::getInstance()->cleanCache ('sys_permalinks');
  78. }
  79. /**
  80. * Prevent cloning the instance
  81. */
  82. public function __clone()
  83. {
  84. if (isset($GLOBALS['bxDolClasses'][get_class($this)]))
  85. trigger_error('Clone is not allowed for the class: ' . get_class($this), E_USER_ERROR);
  86. }
  87. /**
  88. * Get singleton instance of the class
  89. */
  90. public static function getInstance()
  91. {
  92. if(!isset($GLOBALS['bxDolClasses'][__CLASS__]))
  93. $GLOBALS['bxDolClasses'][__CLASS__] = new BxDolPermalinks();
  94. return $GLOBALS['bxDolClasses'][__CLASS__];
  95. }
  96. function getPermalinksData()
  97. {
  98. $aLinksStandard = $this->getAll("SELECT * FROM `sys_permalinks`");
  99. $aResult = array(
  100. 'standard' => array(),
  101. 'permalink' => array(),
  102. 'prefixes_standard' => array(),
  103. 'prefixes_permalink' => array(),
  104. );
  105. foreach ($aLinksStandard as $aLink) {
  106. $a = array(
  107. 'permalink' => $aLink['permalink'],
  108. 'standard' => $aLink['standard'],
  109. 'check' => $aLink['check'],
  110. 'enabled' => getParam($aLink['check']) == 'on',
  111. );
  112. $aResult['standard'][$aLink['standard']] = $a;
  113. $aResult['permalink'][$aLink['permalink']] = $a;
  114. if ($aLink['compare_by_prefix']) {
  115. $aResult['prefixes_standard'][$aLink['standard']] = strlen($aLink['standard']);
  116. $aResult['prefixes_permalink'][$aLink['permalink']] = strlen($aLink['permalink']);
  117. }
  118. }
  119. return $aResult;
  120. }
  121. /**
  122. * Get page name (changeable page of URL) from the URL.
  123. * @param $sLink - relative UNpermalinked, it is better to use unpermalink method to make sure that correct url is passed here
  124. * @param $iLength - position of the page name, or false to detect automatically
  125. * @return page name (changeable part of URL) or empty string if page name is not detectable.
  126. */
  127. function getPageNameFromLink($sLink, $iLength = false)
  128. {
  129. if (false == $iLength) {
  130. $sLink = $this->_fixUrl($sLink);
  131. foreach ($this->aPrefixesStandard as $sKey => $iLen) {
  132. if (strncmp($sLink, $sKey, $iLen) === 0) {
  133. $iLength = $iLen;
  134. break;
  135. }
  136. }
  137. }
  138. return false === $iLength ? '' : substr($sLink, $iLength);
  139. }
  140. /**
  141. * Make permalink from link.
  142. * @param $sLink - relative URL.
  143. * @param $aParams - params to add to the url.
  144. * @return - relative permalinked URL if it was detected and permalinks are ON or unchanged URL otherwise.
  145. */
  146. function permalink($sLink, $aParams = array())
  147. {
  148. $sRet = null;
  149. /**
  150. * @hooks
  151. * @hookdef hook-system-permalink 'system', 'permalink' - hook to override permalink during link to permalink conversion
  152. * - $unit_name - equals `system`
  153. * - $action - equals `permalink`
  154. * - $object_id - not used
  155. * - $sender_id - not used
  156. * - $extra_params - array of additional params with the following array keys:
  157. * - `link` - [string] input link
  158. * - `params` - [array] by ref, additional params array as key&value pairs, can be overridden in hook processing
  159. * - `return_data` - [string] by ref, output permalink, can be overridden in hook processing
  160. * @hook @ref hook-system-permalink
  161. */
  162. bx_alert('system', 'permalink', 0, 0, [
  163. 'link' => $sLink,
  164. 'params' => &$aParams,
  165. 'params_ref' => &$aParams,
  166. 'return_data' => &$sRet
  167. ]);
  168. if (null !== $sRet)
  169. return $sRet;
  170. $sPrefix = '';
  171. if(strncmp($sLink, BX_DOL_URL_ROOT, strlen(BX_DOL_URL_ROOT)) === 0) {
  172. $sPrefix = BX_DOL_URL_ROOT;
  173. $sLink = substr($sLink, strlen($sPrefix));
  174. }
  175. $sLink = $this->_fixUrl($sLink);
  176. if ($sSeoLink = BxDolPage::transformSeoLink ($sLink, $sPrefix, $aParams))
  177. return $sSeoLink;
  178. foreach ($this->aPrefixesStandard as $sKey => $iLength) {
  179. if (strncmp($sLink, $sKey, $iLength) !== 0)
  180. continue;
  181. $sPage = $this->getPageNameFromLink($sLink, $iLength);
  182. if (!$this->_isEnabled($sKey))
  183. return bx_append_url_params($sLink, $aParams);
  184. $sUrl = $this->_fixUrlAmpersand($this->aLinksStandard[$sKey]['permalink'] . $sPage);
  185. return $sPrefix . bx_append_url_params($sUrl, $aParams);
  186. }
  187. return $sPrefix . bx_append_url_params($this->_isEnabled($sLink) ? $this->aLinksStandard[$sLink]['permalink'] : $sLink, $aParams);
  188. }
  189. /**
  190. * Remove permalink from link.
  191. * @param $sLink - relative or absoulte URL.
  192. * @param $isStripBaseUrl - strip site prefix (absolute URL) automatically (enabled by default)
  193. * @return - relative UNpermalinked URL if it was detected or relative URL if URL withing the site or unchanged URL otherwise.
  194. */
  195. function unpermalink($sLink, $isStripBaseUrl = true)
  196. {
  197. $sRet = null;
  198. /**
  199. * @hooks
  200. * @hookdef hook-system-unpermalink 'system', 'unpermalink' - hook to override link during permalink to link conversion
  201. * - $unit_name - equals `system`
  202. * - $action - equals `unpermalink`
  203. * - $object_id - not used
  204. * - $sender_id - not used
  205. * - $extra_params - array of additional params with the following array keys:
  206. * - `link` - [string] input permalink
  207. * - `strip_base_url` - [boolean] strip base URL from output link
  208. * - `return_data` - [string] by ref, output link, can be overridden in hook processing
  209. * @hook @ref hook-system-unpermalink
  210. */
  211. bx_alert('system', 'unpermalink', 0, 0, [
  212. 'link' => $sLink,
  213. 'strip_base_url' => $isStripBaseUrl,
  214. 'return_data' => &$sRet
  215. ]);
  216. if (null !== $sRet)
  217. return $sRet;
  218. $sPrefix = '';
  219. if(!$isStripBaseUrl)
  220. $sPrefix = BX_DOL_URL_ROOT;
  221. if(strncmp($sLink, BX_DOL_URL_ROOT, strlen(BX_DOL_URL_ROOT)) === 0)
  222. $sLink = substr($sLink, strlen(BX_DOL_URL_ROOT));
  223. if ($sUnSeoLink = BxDolPage::untransformSeoLink ($sLink, $sPrefix))
  224. return $sUnSeoLink;
  225. foreach ($this->aPrefixesPermalink as $sKey => $iLength) {
  226. if (strncmp($sLink, $sKey, $iLength) !== 0)
  227. continue;
  228. $sPage = substr($sLink, $iLength);
  229. return $sPrefix . $this->aLinksPermalink[$sKey]['standard'] . str_replace('?', '&', $sPage);
  230. }
  231. return $sPrefix . (isset($this->aLinksPermalink[$sLink]) ? $this->aLinksPermalink[$sLink]['standard'] : $sLink);
  232. }
  233. function _isEnabled($sLink)
  234. {
  235. return !empty($sLink) && array_key_exists($sLink, $this->aLinksStandard) && $this->aLinksStandard[$sLink]['enabled'];
  236. }
  237. /**
  238. * redirect to the correct url after switching skin or language
  239. * only correct modules urls are supported
  240. */
  241. function redirectIfNecessary ($aSkip = array())
  242. {
  243. $sCurrentUrl = $_SERVER['PHP_SELF'] . '?' . bx_encode_url_params($_GET, $aSkip);
  244. if (!preg_match('/modules\/index.php\?r=(\w+)(.*)/', $sCurrentUrl, $m))
  245. return false;
  246. $sStandardLink = 'modules/?r=' . $m[1] . '/';
  247. $sPermalink = $this->permalink ($sStandardLink);
  248. if (false !== strpos($sCurrentUrl, $sPermalink))
  249. return false;
  250. header("HTTP/1.1 301 Moved Permanently");
  251. header ('Location:' . BX_DOL_URL_ROOT . $sPermalink . rtrim(trim(urldecode($m[2]), '/'), '&'));
  252. return true;
  253. }
  254. protected function _fixUrlAmpersand($sLink)
  255. {
  256. $iPosQuestion = strpos($sLink, '?');
  257. if (false === $iPosQuestion && false !== ($iPosAmpersand = strpos($sLink, '&')))
  258. $sLink = substr_replace($sLink, '?', $iPosAmpersand, 1);
  259. return $sLink;
  260. }
  261. protected function _fixUrl($sLink)
  262. {
  263. if (strncmp($sLink, 'modules/index.php?r=', 20) === 0)
  264. $sLink = str_replace('modules/index.php?r=', 'modules/?r=', $sLink);
  265. return $sLink;
  266. }
  267. }
  268. /** @} */