BxDolPage.php 35 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922
  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. * Pages.
  11. *
  12. * It allows to display pages which are built from studio.
  13. *
  14. * The new system has the following page features:
  15. * - Layouts: page can have any structure, not just columns.
  16. * - SEO: page can have own SEO options, like meta tags and meta keywords, as well as instructions for search bots.
  17. * - Cache: page can be cached on the server.
  18. * - Access control: page access can be controlled using member levels.
  19. *
  20. *
  21. *
  22. * @section page_create Creating the Page object:
  23. *
  24. * 1. add record to 'sys_objects_page' table:
  25. *
  26. * - object: name of the page object, in the format: vendor prefix, underscore, module prefix, underscore, internal identifier or nothing; for example: bx_profiles_view - profile view page.
  27. * - title: name to display as page title.
  28. * - module: the module this page belongs to.
  29. * - layout_id: page layout to use, this is id of the record from 'sys_pages_layouts' table.
  30. * - visible_for_levels: bit field with set of member level ids. To use member level id in bit field - the level id minus 1 as power of 2 is used, for example:
  31. * - user level id = 1 -> 2^(1-1) = 1
  32. * - user level id = 2 -> 2^(2-1) = 2
  33. * - user level id = 3 -> 2^(3-1) = 4
  34. * - user level id = 4 -> 2^(4-1) = 8
  35. * - visible_for_levels_editable: it determines if 'visible_for_levels' field is editable from page builder, visibility options can be overriden by custom class and shouldn't be editable in this case.
  36. * - url: the page url, if it is static page.
  37. * - content_info: content info object name related to the content on the page,
  38. * if different from module name
  39. * - meta_description: meta description of the page.
  40. * - meta_keywords: meta keywords of the page.
  41. * - meta_robots: instructions for search bots.
  42. * - cache_lifetime: number of seconds to store cache for.
  43. * - cache_editable: it determines if cache can be edited from page builder.
  44. * - deletable: it determines if page can be deleted from page builder.
  45. * - override_class_name: user defined class name which is derived from BxTemplPage.
  46. * - override_class_file: the location of the user defined class, leave it empty if class is located in system folders.
  47. *
  48. * Page can select appropriate menu automatically if 'module' and 'object' fields in 'sys_objects_page' table are matched with 'module' and 'name' fields in 'sys_menu_items' table.
  49. *
  50. *
  51. * Page layout are stored in 'sys_pages_layouts' table:
  52. * - name: inner unique layout name.
  53. * - icon: layout icon to display in page builder, it should represent basic view of the layout to help studio operator determine the layout structure.
  54. * - title: layout name to display in page builder.
  55. * - template: template name to use to display page with certain layout.
  56. * - cells_number: number of areas in the layout, page blocks can be places into this areas(cells).
  57. * To define areas in the layout they should be named as '__cell_N__', where N is cell number, starting from 1.
  58. *
  59. *
  60. * 2. Add page blocks to 'sys_pages_blocks' table:
  61. * - object: page object name this block belongs to.
  62. * - cell_id: cell number in page layout to place block to.
  63. * - module: module name this block belongs to.
  64. * - title: block title.
  65. * - designbox_id: design box to use to diplay page block, it is id of the record from 'sys_pages_design_boxes' table.
  66. * - visible_for_levels: bit field with set of member level ids. To use member level id in bit field - the level id minus 1 as power of 2 is used, for example:
  67. * - user level id = 1 -> 2^(1-1) = 1
  68. * - user level id = 2 -> 2^(2-1) = 2
  69. * - user level id = 3 -> 2^(3-1) = 4
  70. * - user level id = 4 -> 2^(4-1) = 8
  71. * - type: block type
  72. * - raw: HTML block, displayed in page builder as HTML textarea.
  73. * - html: HTML block, displayed in page builder as visual editor, like TinyMCE.
  74. * - lang: translatable language string, displayed in page builder as editable language key.
  75. * - image: just an image, displayed in page builder as HTML upload form.
  76. * - rss: RSS block, displayed in page builder as editable URL to RSS resource, along with number of displayed items.
  77. * - menu: menu block, displayed as menu selector.
  78. * - service: to display block content, the provided service method is used.
  79. * - content: depending on 'type' field:
  80. * - raw: HTML string.
  81. * - html: HTML string.
  82. * - lang: language key.
  83. * - image: image id in the storage and alignment (left, center, right) for example: 36\#center
  84. * - rss: URL to RSS with number of displayed items, for example: http://www.example.com/rss#4
  85. * - menu: menu object name.
  86. * - service: serialized array of service call parameters: module - module name, method - service method name, params - array of parameters.
  87. * - deletable: is block deletable from page builder
  88. * - copyable: is block can be copied to any other page from page builder.
  89. * - order: block order in particular cell.
  90. *
  91. * Block design boxes are stored in 'sys_pages_design_boxes' table:
  92. * - id: consistent id, there are the following defines can be used in the code for each system block style:
  93. * - 0 - BX_DB_CONTENT_ONLY: design box with content only - no borders, no background, no caption.
  94. * - 1 - BX_DB_DEF: default design box with content, borders and caption.
  95. * - 2 - BX_DB_EMPTY: just empty design box, without anything.
  96. * - 3 - BX_DB_NO_CAPTION: design box with content, like BX_DB_DEF but without caption.
  97. * - 10 - BX_DB_PADDING_CONTENT_ONLY: design box with content only wrapped with default padding - no borders, no background, no caption; it can be used to just wrap content with default padding.
  98. * - 11 - BX_DB_PADDING_DEF: default design box with content wrapped with default padding, borders and caption.
  99. * - 13 - BX_DB_PADDING_NO_CAPTION: design box with content wrapped with default padding, like BX_DB_DEF but without caption.
  100. * - title: block name which is displayed in studio, describes block styles.
  101. * - template: template name to use to display page block.
  102. *
  103. *
  104. * 3. Display Page.
  105. * Use the following sample code to display page:
  106. * @code
  107. * $oPage = BxDolPage::getObjectInstance('sample'); // 'sample' is 'object' field from 'sys_objects_page' table, it automatically creates instance of default or custom class by object name
  108. * if ($oPage)
  109. * echo $oPage->getCode(); // print page
  110. * @endcode
  111. *
  112. */
  113. class BxDolPage extends BxDolFactory implements iBxDolFactoryObject, iBxDolReplaceable
  114. {
  115. protected $_sObject;
  116. protected $_aObject;
  117. protected $_oQuery;
  118. protected $_aMarkers = array ();
  119. /**
  120. * Constructor
  121. * @param $aObject array of page options
  122. */
  123. protected function __construct($aObject)
  124. {
  125. parent::__construct();
  126. $this->_sObject = $aObject['object'];
  127. $this->_aObject = $aObject;
  128. $this->_oQuery = new BxDolPageQuery($this->_aObject);
  129. }
  130. /**
  131. * Get page object instance by Module name and page URI
  132. * @param $sModule module name
  133. * @param $sURI unique page URI
  134. * @return object instance or false on error
  135. */
  136. static public function getObjectInstanceByModuleAndURI($sModule, $sURI = '', $oTemplate = false)
  137. {
  138. if(empty($sURI) && bx_get('i') !== false)
  139. $sURI = bx_process_input(bx_get('i'));
  140. if(empty($sURI))
  141. return false;
  142. $sObject = BxDolPageQuery::getPageObjectNameByURI($sURI, $sModule);
  143. return $sObject ? self::getObjectInstance($sObject, $oTemplate) : false;
  144. }
  145. /**
  146. * Get page object instance by page URI
  147. * @param $sURI unique page URI
  148. * @return object instance or false on error
  149. */
  150. static public function getObjectInstanceByURI($sURI = '', $oTemplate = false, $bRedirectCheck = false)
  151. {
  152. if(empty($sURI) && bx_get('i') !== false)
  153. $sURI = bx_process_input(bx_get('i'));
  154. if(empty($sURI))
  155. return false;
  156. $sObject = BxDolPageQuery::getPageObjectNameByURI($sURI);
  157. if ($bRedirectCheck && !$sObject && '/' == substr($sURI, -1)) {
  158. header("HTTP/1.1 301 Moved Permanently");
  159. unset($_GET['i']);
  160. header ('Location:' . bx_append_url_params(bx_absolute_url(BxDolPermalinks::getInstance()->permalink('page.php?i=' . trim($sURI, '/'))), $_GET));
  161. exit;
  162. }
  163. return $sObject ? self::getObjectInstance($sObject, $oTemplate) : false;
  164. }
  165. /**
  166. * Get page object instance by object name
  167. * @param $sObject object name
  168. * @return object instance or false on error
  169. */
  170. static public function getObjectInstance($sObject, $oTemplate = false)
  171. {
  172. if (isset($GLOBALS['bxDolClasses']['BxDolPage!'.$sObject]))
  173. return $GLOBALS['bxDolClasses']['BxDolPage!'.$sObject];
  174. $aObject = BxDolPageQuery::getPageObject($sObject);
  175. if (!$aObject || !is_array($aObject))
  176. return false;
  177. $sClass = 'BxTemplPage';
  178. if (!empty($aObject['override_class_name'])) {
  179. $sClass = $aObject['override_class_name'];
  180. if (!empty($aObject['override_class_file']))
  181. require_once(BX_DIRECTORY_PATH_ROOT . $aObject['override_class_file']);
  182. }
  183. $o = new $sClass($aObject, $oTemplate);
  184. return ($GLOBALS['bxDolClasses']['BxDolPage!'.$sObject] = $o);
  185. }
  186. /**
  187. * Process page triggers.
  188. * Page triggers allow to automatically add page blocks to modules with no different if dependant module was install before or after the module page block belongs to.
  189. * For example module "Notes" adds page blocks to all profiles modules (Persons, Organizations, etc)
  190. * with no difference if persons module was installed before or after "Notes" module was installed.
  191. * @param $sPageTriggerName trigger name to process, usually specified in module installer class - @see BxBaseModGeneralInstaller
  192. * @return always true, always success
  193. */
  194. static public function processPageTrigger ($sPageTriggerName)
  195. {
  196. // get list of active modules
  197. $aModules = BxDolModuleQuery::getInstance()->getModulesBy(array(
  198. 'type' => 'modules',
  199. 'active' => 1,
  200. ));
  201. // get list of page block triggers
  202. $aPageBlocks = BxDolPageQuery::getPageTriggers($sPageTriggerName);
  203. // check each page block trigger for all modules
  204. foreach ($aPageBlocks as $aPageBlock) {
  205. foreach ($aModules as $aModule) {
  206. if (!BxDolRequest::serviceExists($aModule['name'], 'get_page_object_for_page_trigger'))
  207. continue;
  208. $mixedPageObject = BxDolService::call($aModule['name'], 'get_page_object_for_page_trigger', array($sPageTriggerName));
  209. if (!$mixedPageObject)
  210. continue;
  211. $aPageBlockRow = $aPageBlock;
  212. if (is_array($mixedPageObject))
  213. $aPageBlockRow = array_merge($aPageBlockRow, $mixedPageObject);
  214. else
  215. $aPageBlockRow['object'] = $mixedPageObject;
  216. BxDolPageQuery::addPageBlockToPage($aPageBlockRow);
  217. }
  218. }
  219. return true;
  220. }
  221. static public function multisiteLinkCheck ($sLink, $sPageUri = '', $sPageModule = '', $aQueryParams = [])
  222. {
  223. $sMultisiteDomain = self::getMultisite($sLink, $sPageUri, $sPageModule, $aQueryParams);
  224. if ($sMultisiteDomain) {
  225. if (parse_url($sMultisiteDomain, PHP_URL_HOST) !== $_SERVER['HTTP_HOST'])
  226. return $sMultisiteDomain . $sLink;
  227. }
  228. if (!$sMultisiteDomain && parse_url(BX_MULTISITE_URL_MAIN, PHP_URL_HOST) !== $_SERVER['HTTP_HOST']) {
  229. return BX_MULTISITE_URL_MAIN . $sLink;
  230. }
  231. return false;
  232. }
  233. /**
  234. * Get multisite link
  235. * @param $sLink link to check
  236. * @return return multisite link if provided link is multisite, or false is other case
  237. */
  238. static public function getMultisite ($sLink, $sPageUri = '', $sPageModule = '', $aQueryParams = [])
  239. {
  240. $sMultisiteDomain = '';
  241. if (!$sPageModule || !$aQueryParams) {
  242. $sQuery = parse_url($sLink, PHP_URL_QUERY);
  243. if ($sQuery) {
  244. $aQueryParams = bx_parse_str($sQuery);
  245. $sPageUri = !empty($aQueryParams['i']) ? $aQueryParams['i'] : false;
  246. if ($sPageUri) {
  247. $sPageName = BxDolPageQuery::getPageObjectNameByURI($sPageUri);
  248. $aPage = $sPageName ? BxDolPageQuery::getPageObject($sPageName) : false;
  249. $sPageModule = $aPage ? $aPage['module'] : '';
  250. }
  251. }
  252. }
  253. $sMultisiteDomain = '';
  254. if (!empty($aQueryParams['profile_id'])) {
  255. $oProfile = BxDolProfile::getInstance($aQueryParams['profile_id']);
  256. if (BX_MULTISITE_MODULE == $oProfile->getModule())
  257. $sMultisiteDomain = $oProfile->getDisplayName();
  258. }
  259. elseif (!empty($aQueryParams['id']) && $sPageModule) {
  260. $sContentInfo = !empty($aPage['content_info']) ? $aPage['content_info'] : $sPageModule;
  261. $oContentInfo = BxDolContentInfo::getObjectInstance($sContentInfo);
  262. if ($oContentInfo && BX_MULTISITE_MODULE == $sPageModule) {
  263. $sMultisiteDomain = $oContentInfo->getContentTitle($aQueryParams['id']);
  264. }
  265. elseif ($oContentInfo && BX_MULTISITE_MODULE != $sPageModule) {
  266. $iPrivacy = $oContentInfo->getContentPrivacy($aQueryParams['id']);
  267. if (0 > $iPrivacy) {
  268. $oProfile = BxDolProfile::getInstance(abs($iPrivacy));
  269. if ($oProfile && BX_MULTISITE_MODULE == $oProfile->getModule())
  270. $sMultisiteDomain = $oProfile->getDisplayName();
  271. }
  272. }
  273. }
  274. if ($sMultisiteDomain)
  275. return str_replace('{domain}', mb_strtolower($sMultisiteDomain), BX_MULTISITE_URL_PATTERN);
  276. return false;
  277. }
  278. /**
  279. * Delete SEO link
  280. * @param $sModule module name
  281. * @param $sContentInfoObject content info object
  282. * @param $sId content id
  283. * @return number of affected rows
  284. */
  285. static public function deleteSeoLink ($sModule, $sContentInfoObject, $sId)
  286. {
  287. return BxDolPageQuery::deleteSeoLink($sModule, $sContentInfoObject, $sId);
  288. }
  289. /**
  290. * Delete SEO links by param
  291. * @param $sParamName GET param name
  292. * @param $sId content id
  293. * @return number of affected rows
  294. */
  295. static public function deleteSeoLinkByParam ($sParamName, $sId)
  296. {
  297. return BxDolPageQuery::deleteSeoLinkByParam($sParamName, $sId);
  298. }
  299. /**
  300. * Delete SEO links by module
  301. * @param $sModule module name
  302. * @return number of affected rows
  303. */
  304. static public function deleteSeoLinkByModule ($sModule)
  305. {
  306. return BxDolPageQuery::deleteSeoLinkByModule($sModule);
  307. }
  308. /**
  309. * Get page object by SEO link. It takes request part from SEO link and returns page object
  310. * @param $sRequest request URI with SEO link
  311. * @return false - if page wasn't found,
  312. * page object - on success,
  313. * string URL - if permanent redirect is needed
  314. */
  315. static public function getPageBySeoLink ($sRequest)
  316. {
  317. if (!$sRequest || '/' === $sRequest || !getParam('permalinks_seo_links'))
  318. return false;
  319. // redirect to the correct URL
  320. if ('/' === $sRequest[-1] || $sRequest != mb_strtolower($sRequest)) {
  321. unset($_GET['_q']);
  322. $sUrl = BX_DOL_URL_ROOT . bx_append_url_params(mb_strtolower(trim($sRequest, '/')), $_GET);
  323. return $sUrl;
  324. }
  325. // parse URL
  326. $a = explode('/', $sRequest);
  327. if (!$a || empty($a[0]))
  328. return false;
  329. // check page URI rewrite redirect
  330. $aSeoUriRewrites = BxDolPageQuery::getSeoUriRewrites();
  331. if (isset($aSeoUriRewrites[$a[0]])) {
  332. unset($_GET['_q']);
  333. $a[0] = $aSeoUriRewrites[$a[0]];
  334. $sUrl = BX_DOL_URL_ROOT . bx_append_url_params(mb_strtolower(implode('/', $a)), $_GET);
  335. return $sUrl;
  336. }
  337. // check page URI rewrite
  338. $aSeoUriRewritesInv = array_flip($aSeoUriRewrites);
  339. if (isset($aSeoUriRewritesInv[$a[0]]))
  340. $a[0] = $aSeoUriRewritesInv[$a[0]];
  341. // get page
  342. $sPageName = BxDolPageQuery::getPageObjectNameByURI($a[0]);
  343. $aPage = $sPageName ? BxDolPageQuery::getPageObject($sPageName) : false;
  344. if (!$aPage)
  345. return false;
  346. // page with params
  347. if (!empty($a[1])) {
  348. $r = BxDolPageQuery::getSeoLink($aPage['module'], $a[0], ['uri' => $a[1]]);
  349. if ($r)
  350. $_GET[$r['param_name']] = $_REQUEST[$r['param_name']] = $r['param_value'];
  351. }
  352. // multisite redirect
  353. if (defined('BX_MULTISITE_MODULE')) {
  354. list($sPageLink, $aPageParams) = bx_get_base_url_inline();
  355. unset($aPageParams['_q']);
  356. $sLink = bx_append_url_params($sPageLink, $aPageParams);
  357. if (0 === strpos($sLink, BX_DOL_URL_ROOT))
  358. $sLink = substr($sLink, strlen(BX_DOL_URL_ROOT));
  359. $s = BxDolPage::multisiteLinkCheck ('', $a[0], $aPage['module'], $_GET);
  360. if (false !== $s)
  361. return $s . $sLink;
  362. }
  363. // display page
  364. $_REQUEST['i'] = $_GET['i'] = $a[0];
  365. $oPage = BxDolPage::getObjectInstanceByURI($a[0], false, true);
  366. return $oPage;
  367. }
  368. /**
  369. * Process SEO links. It takes request part from SEO link and process it
  370. * to make it work as regular page link
  371. * @param $sRequest request URI with SEO link
  372. * @return true - if page was found and processed correctly, false - if page wasn't found
  373. */
  374. static public function processSeoLink ($sRequest)
  375. {
  376. $mixed = self::getPageBySeoLink($sRequest);
  377. if (($sUrl = $mixed) && is_string($sUrl)) {
  378. header('Location:' . $sUrl, true, 301);
  379. exit;
  380. }
  381. elseif (($oPage = $mixed) && is_object($oPage)) {
  382. $oPage->displayPage();
  383. return true;
  384. }
  385. else {
  386. return false;
  387. }
  388. }
  389. /**
  390. * Transform regular link to SEO link. It takes regular list as param and return SEO link.
  391. * @param $sLink regular link
  392. * @param $sPrefix prefix to add to the final URL, usually BX_DOL_URL_ROOT
  393. * @param $aParams additional GET params
  394. * @return SEO link string on success, false if transform failed or not necessary
  395. */
  396. static public function transformSeoLink ($sLink, $sPrefix, $aParams = array())
  397. {
  398. if (!getParam('permalinks_seo_links'))
  399. return false;
  400. if (0 !== strncmp('page.php', $sLink, 8)) // only page.php links are supported
  401. return false;
  402. $sQuery = parse_url($sLink, PHP_URL_QUERY);
  403. if (false === $sQuery)
  404. return false;
  405. if (function_exists('bx_parse_str'))
  406. $aQueryParams = bx_parse_str($sQuery);
  407. else
  408. parse_str($sQuery, $aQueryParams);
  409. $sPageUri = !empty($aQueryParams['i']) ? $aQueryParams['i'] : false;
  410. unset($aQueryParams['i']);
  411. if (!$sPageUri) // page URI wasn't found
  412. return false;
  413. $sPageModule = '';
  414. $aSeoParams = array('id', 'profile_id'); // supported SEO params which are transformed to SEO strings
  415. $sSeoParamName = '';
  416. $sSeoParamValue = '';
  417. foreach ($aSeoParams as $k) {
  418. if (empty($aQueryParams[$k]) && empty($aParams[$k]))
  419. continue;
  420. $sSeoParamName = $k;
  421. $sSeoParamValue = !empty($aParams[$k]) ? $aParams[$k] : $aQueryParams[$k];
  422. unset($aQueryParams[$k]);
  423. unset($aParams[$k]);
  424. break; // only 1 SEO param will be transformed
  425. }
  426. if (!empty($sSeoParamValue) && 0 === strpos($sSeoParamValue, '{'))
  427. return false;
  428. $sSeoPageUri = $sPageUri;
  429. $aSeoUriRewrites = BxDolPageQuery::getSeoUriRewrites();
  430. if (isset($aSeoUriRewrites[$sSeoPageUri]))
  431. $sSeoPageUri = $aSeoUriRewrites[$sSeoPageUri];
  432. if ($sSeoParamName && $sSeoParamValue) { // process page with SEO param
  433. $sPageName = BxDolPageQuery::getPageObjectNameByURI($sPageUri);
  434. $aPage = $sPageName ? BxDolPageQuery::getPageObject($sPageName) : false;
  435. if ($aPage) {
  436. $sPageModule = $aPage['module'];
  437. $sContentInfo = !empty($aPage['content_info']) ? $aPage['content_info'] : $aPage['module'];
  438. if ('id' == $sSeoParamName) {
  439. $oContentInfo = BxDolContentInfo::getObjectInstance($sContentInfo);
  440. $sSeoTitle = $oContentInfo ? $oContentInfo->getContentTitle($sSeoParamValue) : '';
  441. if (!$sSeoTitle)
  442. $sSeoTitle = self::getSeoHash($sSeoParamValue);
  443. }
  444. elseif ('profile_id' == $sSeoParamName) {
  445. $oProfile = BxDolProfile::getInstance($sSeoParamValue);
  446. $sSeoTitle = $oProfile ? $oProfile->getDisplayName() : self::getSeoHash($sSeoParamValue);
  447. }
  448. $r = BxDolPageQuery::getSeoLink($sPageModule, $sPageUri, ['param_value' => $sSeoParamValue]);
  449. if (!$r) {
  450. $sSeoTitleLimited = BxTemplFunctions::getInstance()->getStringWithLimitedLength($sSeoTitle, 45);
  451. $sUri = uriGenerate ($sSeoTitleLimited, 'sys_seo_links', 'uri', ['cond' => ['module' => $sPageModule, 'page_uri' => $sPageUri]]);
  452. /**
  453. * @hooks
  454. * @hookdef hook-system-uri_generate 'system', 'uri_generate' - hook to override SEO URI generated for a page
  455. * - $unit_name - equals `system`
  456. * - $action - equals `uri_generate`
  457. * - $object_id - not used
  458. * - $sender_id - not used
  459. * - $extra_params - array of additional params with the following array keys:
  460. * - `module` - [string] module name
  461. * - `page_uri` - [string] page default URI
  462. * - `param_name` - [string] param name
  463. * - `param_value` - [string] param value
  464. * - `title` - [string] SEO page title
  465. * - `uri` - [string] by ref, generated SEO URI, can be overridden in hook processing
  466. * @hook @ref hook-system-uri_generate
  467. */
  468. bx_alert('system', 'uri_generate', 0, false, [
  469. 'module' => $sPageModule,
  470. 'page_uri' =>$sPageUri,
  471. 'param_name' => $sSeoParamName,
  472. 'param_value' => $sSeoParamValue,
  473. 'title' => $sSeoTitle,
  474. 'uri' => &$sUri,
  475. ]);
  476. BxDolPageQuery::insertSeoLink($sPageModule, $sPageUri, $sSeoParamName, $sSeoParamValue, $sUri);
  477. }
  478. elseif ($r['param_name'] == $sSeoParamName) {
  479. $sUri = $r['uri'];
  480. }
  481. else {
  482. $sUri = false;
  483. }
  484. if ($sUri)
  485. $sSeoPageUri .= '/' . $sUri;
  486. else
  487. $sSeoPageUri = false;
  488. }
  489. }
  490. if (!$sSeoPageUri)
  491. return false;
  492. $sLink = $sPrefix . bx_append_url_params($sSeoPageUri, array_merge($aQueryParams, $aParams), false);
  493. if (defined('BX_MULTISITE_MODULE')) {
  494. $s = BxDolPage::multisiteLinkCheck ($sLink, $sPageUri, $sPageModule, [$sSeoParamName => $sSeoParamValue]);
  495. if (false !== $s)
  496. return $s;
  497. }
  498. return $sLink;
  499. }
  500. /**
  501. * Transform SEO regular link into regular link with permalinks off.
  502. * @param $sSeoLink SEO link
  503. * @param $sPrefix prefix to add to the final URL, usually BX_DOL_URL_ROOT
  504. * @return unSEO link string on success, false if no transform is needed
  505. */
  506. static public function untransformSeoLink ($sSeoLink, $sPrefix)
  507. {
  508. // check for standard links first
  509. if (preg_match('/^(page\/|s\/|m\/|modules\/|page\.php|storage\.php)/', $sSeoLink))
  510. return false;
  511. // parse link
  512. $aParts = parse_url($sSeoLink);
  513. if (!$aParts || empty($aParts['path']))
  514. return false;
  515. $aUris = explode('/', trim($aParts['path'], '/'));
  516. // check if link starts with page URI and page with this URI exists
  517. if (!$aUris || empty($aUris[0]) || !($sPageName = BxDolPageQuery::getPageObjectNameByURI($aUris[0], false, true)))
  518. return false;
  519. $aPage = BxDolPageQuery::getPageObject ($sPageName);
  520. // make final URL
  521. $s = 'page.php?i=' . $aPage['uri'];
  522. // add params
  523. if (!empty($aUris[1])) {
  524. if ($aPage) {
  525. $r = BxDolPageQuery::getSeoLink($aPage['module'], $aPage['uri'], ['uri' => urldecode($aUris[1])]);
  526. if (!$r)
  527. return false;
  528. $s .= '&' . $r['param_name'] . '=' . $r['param_value'];
  529. }
  530. }
  531. return $s . (!empty($aParts['query']) ? '&' . $aParts['query'] : '');
  532. }
  533. /**
  534. * Perform SEO redorect from regular pages if needed
  535. * @param $sSeoLink SEO link
  536. * @param $sPrefix prefix to add to the final URL, usually BX_DOL_URL_ROOT
  537. * @return unSEO link string on success, false if no transform is needed
  538. */
  539. static public function seoRedirect ()
  540. {
  541. if (!getParam('permalinks_seo_links'))
  542. return;
  543. list($sPageLink, $aPageParams) = bx_get_base_url_inline();
  544. $sLink = bx_append_url_params($sPageLink, $aPageParams);
  545. if (0 === strpos($sLink, BX_DOL_URL_ROOT))
  546. $sLink = substr($sLink, strlen(BX_DOL_URL_ROOT));
  547. if (defined('BX_MULTISITE_MODULE')) {
  548. $s = BxDolPage::multisiteLinkCheck ($sLink);
  549. if (false !== $s) {
  550. header('Location:' . $s, true, 301);
  551. exit;
  552. }
  553. }
  554. if ($sSeoLink = self::transformSeoLink ($sLink, BX_DOL_URL_ROOT)) {
  555. header("Location:{$sSeoLink}", true, 301);
  556. exit;
  557. }
  558. }
  559. static public function getSeoHash($s)
  560. {
  561. return base_convert(substr(md5($s), -8), 16, 36);
  562. }
  563. /**
  564. * Static method to Get embed code
  565. * @return string.
  566. */
  567. static public function getEmbedData ($sUrl)
  568. {
  569. $sUrl = urldecode($sUrl);
  570. $aParams = [];
  571. $aUrl = bx_get_base_url($sUrl);
  572. if (!isset($aUrl[1]['i'])){
  573. return [];
  574. }
  575. $sUri = $aUrl[1]['i'];
  576. unset($aUrl[1]['i']);
  577. $aParams = $aUrl[1];
  578. $sAuthorName = $sAuthorUrl = $sThumb = '';
  579. if (isset($aParams['id'])){
  580. $sContentInfoObject = BxDolPageQuery::getContentInfoObjectNameByURI($sUri);
  581. $oContentInfo = BxDolContentInfo::getObjectInstance($sContentInfoObject);
  582. $sTitle = $oContentInfo->getContentTitle($aParams['id']);
  583. $iAuthor = $oContentInfo->getContentAuthor($aParams['id']);
  584. $sAuthorName = BxDolProfile::getInstance($iAuthor)->getDisplayName();
  585. $sAuthorUrl = BxDolProfile::getInstance($iAuthor)->getUrl();
  586. $sThumb = $oContentInfo->getContentThumb($aParams['id']);
  587. $sHtml = $oContentInfo->getContentEmbed($aParams['id']);
  588. }
  589. else{
  590. $oPage = BxDolPage::getObjectInstanceByURI($sUri, false, true);
  591. $aPage = $oPage->getObject();
  592. $sTitle = $oPage->_getPageTitle();
  593. $sHtml = BxDolTemplate::getInstance()->parseHtmlByName('embed.html', [
  594. 'title' => $sTitle,
  595. 'url' => BX_DOL_URL_ROOT . 'page.php?a=embed&o=' . $oPage->getName()
  596. ]);
  597. }
  598. return ['url' => $sUrl, 'title' => $sTitle, 'author_name' => $sAuthorName, 'author_url' => $sAuthorUrl, 'thumbnail_url' => $sThumb, 'html' => $sHtml];
  599. }
  600. static public function getPageBlockData($iBlockId, $iContentId = 0, $sContentModule = '')
  601. {
  602. $oDb = new BxDolPageQuery([]);
  603. $sData = $oDb->getPageBlockData($iBlockId, $iContentId, $sContentModule);
  604. $aData = !empty($sData) ? json_decode($sData, true) : [];
  605. $oEmbed = BxDolEmbed::getObjectInstance('sys_system');
  606. foreach($aData as &$j) {
  607. foreach($j as &$k) {
  608. if($k['type'] == 'link') {
  609. $k['content_data'] = $oEmbed->getData($k['content'], '');
  610. }
  611. }
  612. }
  613. return $aData;
  614. }
  615. static public function setPageBlockData($iBlockId, $iContentId = 0, $sContentModule = '', $mixedData = '')
  616. {
  617. $oDb = new BxDolPageQuery([]);
  618. $sData = is_array($mixedData) ? json_encode($mixedData) : $mixedData;
  619. return $oDb->setPageBlockData($iBlockId, $iContentId, $sContentModule, $sData);
  620. }
  621. /**
  622. * Display complete page
  623. */
  624. public function displayPage ($oTemplate = null)
  625. {
  626. $sSessionKey = 'sys_entrance_url';
  627. $oSession = BxDolSession::getInstance();
  628. if(!$oSession->getValue($sSessionKey)) {
  629. list($sPageLink, $aPageParams) = bx_get_base_url_inline();
  630. $oSession->setValue($sSessionKey, bx_append_url_params($sPageLink, array_intersect_key($aPageParams, array_flip(['i', 'id', 'profile_id']))));
  631. }
  632. if(isLogged())
  633. BxDolAccount::getInstance()->isNeedChangePassword();
  634. if ($this->isLockedFromUnauthenticated ($this->_aObject['uri'])) {
  635. $this->redirectToLoginForm();
  636. exit;
  637. }
  638. if(!$oTemplate)
  639. $oTemplate = BxDolTemplate::getInstance();
  640. $oTemplate->setPageNameIndex(BX_PAGE_DEFAULT);
  641. if(($sHxTarget = bx_get_htmx_target()) !== false)
  642. $oTemplate->setPageNameIndexByTarget($sHxTarget);
  643. $oTemplate->setPageUrl($this->_aObject['url']);
  644. $oTemplate->setPageType($this->getType());
  645. $oTemplate->setPageInjections($this->getInjections());
  646. if($oTemplate->getPageNameIndex() == BX_PAGE_CONTENT_PRELOAD)
  647. $oTemplate->setPageContent('page_main_code', $this->getIncludes());
  648. else
  649. $oTemplate->setPageContent('page_main_code', $this->getCode());
  650. $oTemplate->getPageCode();
  651. }
  652. public function getId ()
  653. {
  654. return (int)$this->_aObject['id'];
  655. }
  656. public function getType ()
  657. {
  658. if (defined('BX_MULTISITE_FORCE_PAGETYPE') && defined('BX_MULTISITE_MODULE')) {
  659. list($sPageLink, $aPageParams) = bx_get_base_url_inline();
  660. $sLink = bx_append_url_params($sPageLink, $aPageParams);
  661. if (0 === strpos($sLink, BX_DOL_URL_ROOT))
  662. $sLink = substr($sLink, strlen(BX_DOL_URL_ROOT));
  663. if (BxDolPage::getMultisite ($sLink))
  664. return BX_MULTISITE_FORCE_PAGETYPE;
  665. }
  666. return (int)$this->_aObject['type_id'];
  667. }
  668. public function getModule ()
  669. {
  670. return $this->_aObject['module'];
  671. }
  672. public function getSubMenu ()
  673. {
  674. return $this->_aObject['submenu'];
  675. }
  676. public function getMetaDescription ()
  677. {
  678. return $this->_aObject['meta_description'];
  679. }
  680. public function getName ()
  681. {
  682. return $this->_sObject;
  683. }
  684. public function getObject ()
  685. {
  686. return $this->_aObject;
  687. }
  688. public function getInjections()
  689. {
  690. $aResult = array();
  691. foreach($this->_aObject as $sKey => $sValue)
  692. if(strpos($sKey, 'inj_') === 0)
  693. $aResult[substr($sKey, 4)] = $sValue;
  694. return $aResult;
  695. }
  696. public function setTitle ($sTitle)
  697. {
  698. return $this->_aObject['title'] = $sTitle;
  699. }
  700. /**
  701. * Add replace markers. Markers are replaced in raw, html, lang blocks and page title, description, keywords and block titles.
  702. * @param $a array of markers as key => value
  703. * @return true on success or false on error
  704. */
  705. public function addMarkers ($a)
  706. {
  707. if (empty($a) || !is_array($a))
  708. return false;
  709. $this->_aMarkers = array_merge ($this->_aMarkers, $a);
  710. return true;
  711. }
  712. public function isAvailablePage ()
  713. {
  714. return $this->_isAvailablePage($this->_aObject);
  715. }
  716. public function isVisiblePage ()
  717. {
  718. return $this->_isVisiblePage($this->_aObject);
  719. }
  720. /**
  721. * Check if page can be seen by viewer.
  722. * @param array $a - page object.
  723. * @return type boolean (true or false).
  724. */
  725. public function isPage()
  726. {
  727. return $this->_isAvailablePage($this->_aObject) === true && $this->_isVisiblePage($this->_aObject) === true;
  728. }
  729. public function isVisiblePageSubmenu ()
  730. {
  731. return $this->_aObject['submenu'] != 'disabled';
  732. }
  733. public function isEditAllowed ()
  734. {
  735. return false;
  736. }
  737. public function isDeleteAllowed ()
  738. {
  739. return false;
  740. }
  741. /**
  742. * Replace provided markers in a string
  743. * @param $mixed string or array to replace markers in
  744. * @return string where all occured markers are replaced
  745. */
  746. protected function _replaceMarkers ($mixed, $aAdditionalMarkers = array())
  747. {
  748. return bx_replace_markers($mixed, array_merge($this->_aMarkers, $aAdditionalMarkers));
  749. }
  750. /**
  751. * Check if page block is visible.
  752. */
  753. protected function _isVisibleBlock ($a)
  754. {
  755. return BxDolAcl::getInstance()->isMemberLevelInSet($a['visible_for_levels']);
  756. }
  757. /**
  758. * Check if page is available.
  759. * @param type $a - page object.
  760. * @return boolean true if available, false or a string with error message otherwise.
  761. */
  762. protected function _isAvailablePage ($a)
  763. {
  764. return true;
  765. }
  766. /**
  767. * Check if page is visible.
  768. * @param array $a - page object.
  769. * @return boolean true if visible, false or a string with error message otherwise.
  770. */
  771. protected function _isVisiblePage ($a)
  772. {
  773. return isAdmin() || BxDolAcl::getInstance()->isMemberLevelInSet($a['visible_for_levels']);
  774. }
  775. public static function isLockedFromUnauthenticated ($sUri)
  776. {
  777. if (!isLogged() && getParam('sys_lock_from_unauthenticated') && !defined('BX_DOL_CRON_EXECUTE')) {
  778. $aURIs = explode(',', getParam('sys_lock_from_unauthenticated_exceptions'));
  779. array_walk($aURIs, function (&$sVal) {
  780. $sVal = trim($sVal);
  781. });
  782. $aI = array_combine($aURIs, array_fill(0, count($aURIs), 1));
  783. if (!preg_match('/\/oauth2\//', $_SERVER['REQUEST_URI']) /*&& !preg_match('/searchKeyword.php$/', $_SERVER['PHP_SELF'])*/ && !preg_match('/member.php$/', $_SERVER['PHP_SELF']) && !isset($aI[$sUri])) {
  784. return true;
  785. }
  786. }
  787. return false;
  788. }
  789. public static function redirectToLoginForm ()
  790. {
  791. header("Location: " . bx_append_url_params(bx_absolute_url(BxDolPermalinks::getInstance()->permalink('page.php?i=login')), ['relocate' => bx_get_self_url()]));
  792. exit;
  793. }
  794. }
  795. /** @} */