BxDolPage.php 38 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008
  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 static $_aBlockProcessing = [];
  116. protected $_bIsApi;
  117. protected $_sObject;
  118. protected $_aObject;
  119. protected $_oQuery;
  120. protected $_aMarkers = [];
  121. protected $_sForceRedirect = false;
  122. /**
  123. * Constructor
  124. * @param $aObject array of page options
  125. */
  126. protected function __construct($aObject)
  127. {
  128. parent::__construct();
  129. $this->_bIsApi = bx_is_api();
  130. $this->_sObject = $aObject['object'];
  131. $this->_aObject = $aObject;
  132. $this->_oQuery = new BxDolPageQuery($this->_aObject);
  133. }
  134. /**
  135. * Get page object instance by Module name and page URI
  136. * @param $sModule module name
  137. * @param $sURI unique page URI
  138. * @return object instance or false on error
  139. */
  140. static public function getObjectInstanceByModuleAndURI($sModule, $sURI = '', $oTemplate = false)
  141. {
  142. if(empty($sURI) && bx_get('i') !== false)
  143. $sURI = bx_process_input(bx_get('i'));
  144. if(empty($sURI))
  145. return false;
  146. $sObject = BxDolPageQuery::getPageObjectNameByURI($sURI, $sModule);
  147. return $sObject ? self::getObjectInstance($sObject, $oTemplate) : false;
  148. }
  149. /**
  150. * Get page object instance by page URI
  151. * @param $sURI unique page URI
  152. * @return object instance or false on error
  153. */
  154. static public function getObjectInstanceByURI($sURI = '', $oTemplate = false, $bRedirectCheck = false)
  155. {
  156. if(empty($sURI) && bx_get('i') !== false)
  157. $sURI = bx_process_input(bx_get('i'));
  158. if(empty($sURI))
  159. return false;
  160. $sObject = BxDolPageQuery::getPageObjectNameByURI($sURI);
  161. if($bRedirectCheck && !$sObject && '/' == substr($sURI, -1)) {
  162. unset($_GET['i']);
  163. header("HTTP/1.1 301 Moved Permanently");
  164. header('Location:' . bx_append_url_params(bx_absolute_url(BxDolPermalinks::getInstance()->permalink('page.php?i=' . trim($sURI, '/'))), $_GET));
  165. exit;
  166. }
  167. if(!$sObject)
  168. return false;
  169. $oObject = self::getObjectInstance($sObject, $oTemplate);
  170. if(($sRedirectUrl = $oObject->getForceRedirect()) !== false) {
  171. if(!bx_is_api()) {
  172. header("Location: " . $sRedirectUrl);
  173. exit;
  174. }
  175. else
  176. $oObject = bx_api_get_relative_url($sRedirectUrl);
  177. }
  178. return $oObject;
  179. }
  180. /**
  181. * Get page object instance by object name
  182. * @param $sObject object name
  183. * @return object instance or false on error
  184. */
  185. static public function getObjectInstance($sObject, $oTemplate = false)
  186. {
  187. if (isset($GLOBALS['bxDolClasses']['BxDolPage!'.$sObject]))
  188. return $GLOBALS['bxDolClasses']['BxDolPage!'.$sObject];
  189. $aObject = BxDolPageQuery::getPageObject($sObject);
  190. if (!$aObject || !is_array($aObject))
  191. return false;
  192. $sClass = 'BxTemplPage';
  193. if (!empty($aObject['override_class_name'])) {
  194. $sClass = $aObject['override_class_name'];
  195. if (!empty($aObject['override_class_file']))
  196. require_once(BX_DIRECTORY_PATH_ROOT . $aObject['override_class_file']);
  197. }
  198. $o = new $sClass($aObject, $oTemplate);
  199. return ($GLOBALS['bxDolClasses']['BxDolPage!'.$sObject] = $o);
  200. }
  201. /**
  202. * Process page triggers.
  203. * 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.
  204. * For example module "Notes" adds page blocks to all profiles modules (Persons, Organizations, etc)
  205. * with no difference if persons module was installed before or after "Notes" module was installed.
  206. * @param $sPageTriggerName trigger name to process, usually specified in module installer class - @see BxBaseModGeneralInstaller
  207. * @return always true, always success
  208. */
  209. static public function processPageTrigger ($sPageTriggerName)
  210. {
  211. // get list of active modules
  212. $aModules = BxDolModuleQuery::getInstance()->getModulesBy(array(
  213. 'type' => 'modules',
  214. 'active' => 1,
  215. ));
  216. // get list of page block triggers
  217. $aPageBlocks = BxDolPageQuery::getPageTriggers($sPageTriggerName);
  218. // check each page block trigger for all modules
  219. foreach ($aPageBlocks as $aPageBlock) {
  220. foreach ($aModules as $aModule) {
  221. if (!BxDolRequest::serviceExists($aModule['name'], 'get_page_object_for_page_trigger'))
  222. continue;
  223. $mixedPageObject = BxDolService::call($aModule['name'], 'get_page_object_for_page_trigger', array($sPageTriggerName));
  224. if (!$mixedPageObject)
  225. continue;
  226. $aPageBlockRow = $aPageBlock;
  227. if (is_array($mixedPageObject))
  228. $aPageBlockRow = array_merge($aPageBlockRow, $mixedPageObject);
  229. else
  230. $aPageBlockRow['object'] = $mixedPageObject;
  231. BxDolPageQuery::addPageBlockToPage($aPageBlockRow);
  232. }
  233. }
  234. return true;
  235. }
  236. static public function multisiteLinkCheck ($sLink, $sPageUri = '', $sPageModule = '', $aQueryParams = [])
  237. {
  238. $sMultisiteDomain = self::getMultisite($sLink, $sPageUri, $sPageModule, $aQueryParams);
  239. if ($sMultisiteDomain) {
  240. if (parse_url($sMultisiteDomain, PHP_URL_HOST) !== $_SERVER['HTTP_HOST'])
  241. return $sMultisiteDomain . $sLink;
  242. }
  243. if (!$sMultisiteDomain && parse_url(BX_MULTISITE_URL_MAIN, PHP_URL_HOST) !== $_SERVER['HTTP_HOST']) {
  244. return BX_MULTISITE_URL_MAIN . $sLink;
  245. }
  246. return false;
  247. }
  248. /**
  249. * Get multisite link
  250. * @param $sLink link to check
  251. * @return return multisite link if provided link is multisite, or false is other case
  252. */
  253. static public function getMultisite ($sLink, $sPageUri = '', $sPageModule = '', $aQueryParams = [])
  254. {
  255. $sMultisiteDomain = '';
  256. if (!$sPageModule || !$aQueryParams) {
  257. $sQuery = parse_url($sLink, PHP_URL_QUERY);
  258. if ($sQuery) {
  259. $aQueryParams = bx_parse_str($sQuery);
  260. $sPageUri = !empty($aQueryParams['i']) ? $aQueryParams['i'] : false;
  261. if ($sPageUri) {
  262. $sPageName = BxDolPageQuery::getPageObjectNameByURI($sPageUri);
  263. $aPage = $sPageName ? BxDolPageQuery::getPageObject($sPageName) : false;
  264. $sPageModule = $aPage ? $aPage['module'] : '';
  265. }
  266. }
  267. }
  268. $sMultisiteDomain = '';
  269. if (!empty($aQueryParams['profile_id'])) {
  270. $oProfile = BxDolProfile::getInstance($aQueryParams['profile_id']);
  271. if (BX_MULTISITE_MODULE == $oProfile->getModule())
  272. $sMultisiteDomain = $oProfile->getDisplayName();
  273. }
  274. elseif (!empty($aQueryParams['id']) && $sPageModule) {
  275. $sContentInfo = !empty($aPage['content_info']) ? $aPage['content_info'] : $sPageModule;
  276. $oContentInfo = BxDolContentInfo::getObjectInstance($sContentInfo);
  277. if ($oContentInfo && BX_MULTISITE_MODULE == $sPageModule) {
  278. $sMultisiteDomain = $oContentInfo->getContentTitle($aQueryParams['id']);
  279. }
  280. elseif ($oContentInfo && BX_MULTISITE_MODULE != $sPageModule) {
  281. $iPrivacy = $oContentInfo->getContentPrivacy($aQueryParams['id']);
  282. if (0 > $iPrivacy) {
  283. $oProfile = BxDolProfile::getInstance(abs($iPrivacy));
  284. if ($oProfile && BX_MULTISITE_MODULE == $oProfile->getModule())
  285. $sMultisiteDomain = $oProfile->getDisplayName();
  286. }
  287. }
  288. }
  289. if ($sMultisiteDomain)
  290. return str_replace('{domain}', mb_strtolower($sMultisiteDomain), BX_MULTISITE_URL_PATTERN);
  291. return false;
  292. }
  293. /**
  294. * Delete SEO link
  295. * @param $sModule module name
  296. * @param $sContentInfoObject content info object
  297. * @param $sId content id
  298. * @return number of affected rows
  299. */
  300. static public function deleteSeoLink ($sModule, $sContentInfoObject, $sId)
  301. {
  302. return BxDolPageQuery::deleteSeoLink($sModule, $sContentInfoObject, $sId);
  303. }
  304. /**
  305. * Delete SEO links by param
  306. * @param $sParamName GET param name
  307. * @param $sId content id
  308. * @return number of affected rows
  309. */
  310. static public function deleteSeoLinkByParam ($sParamName, $sId)
  311. {
  312. return BxDolPageQuery::deleteSeoLinkByParam($sParamName, $sId);
  313. }
  314. /**
  315. * Delete SEO links by module
  316. * @param $sModule module name
  317. * @return number of affected rows
  318. */
  319. static public function deleteSeoLinkByModule ($sModule)
  320. {
  321. return BxDolPageQuery::deleteSeoLinkByModule($sModule);
  322. }
  323. /**
  324. * Get page object by SEO link. It takes request part from SEO link and returns page object
  325. * @param $sRequest request URI with SEO link
  326. * @return false - if page wasn't found,
  327. * page object - on success,
  328. * string URL - if permanent redirect is needed
  329. */
  330. static public function getPageBySeoLink ($sRequest)
  331. {
  332. if (!$sRequest || '/' === $sRequest || !getParam('permalinks_seo_links'))
  333. return false;
  334. // redirect to the correct URL
  335. if ('/' === $sRequest[-1] || $sRequest != mb_strtolower($sRequest)) {
  336. unset($_GET['_q']);
  337. $sUrl = BX_DOL_URL_ROOT . bx_append_url_params(mb_strtolower(trim($sRequest, '/')), $_GET);
  338. return $sUrl;
  339. }
  340. // parse URL
  341. $a = explode('/', $sRequest);
  342. if (!$a || empty($a[0]))
  343. return false;
  344. // check page URI rewrite redirect
  345. $aSeoUriRewrites = BxDolPageQuery::getSeoUriRewrites();
  346. if (isset($aSeoUriRewrites[$a[0]])) {
  347. unset($_GET['_q']);
  348. $a[0] = $aSeoUriRewrites[$a[0]];
  349. $sUrl = BX_DOL_URL_ROOT . bx_append_url_params(mb_strtolower(implode('/', $a)), $_GET);
  350. return $sUrl;
  351. }
  352. // check page URI rewrite
  353. $aSeoUriRewritesInv = array_flip($aSeoUriRewrites);
  354. if (isset($aSeoUriRewritesInv[$a[0]]))
  355. $a[0] = $aSeoUriRewritesInv[$a[0]];
  356. // get page
  357. $sPageName = BxDolPageQuery::getPageObjectNameByURI($a[0]);
  358. $aPage = $sPageName ? BxDolPageQuery::getPageObject($sPageName) : false;
  359. if (!$aPage)
  360. return false;
  361. // page with params
  362. if (!empty($a[1])) {
  363. $r = BxDolPageQuery::getSeoLink($aPage['module'], $a[0], ['uri' => $a[1]]);
  364. if ($r)
  365. $_GET[$r['param_name']] = $_REQUEST[$r['param_name']] = $r['param_value'];
  366. }
  367. // multisite redirect
  368. if (defined('BX_MULTISITE_MODULE')) {
  369. list($sPageLink, $aPageParams) = bx_get_base_url_inline();
  370. unset($aPageParams['_q']);
  371. $sLink = bx_append_url_params($sPageLink, $aPageParams);
  372. if (0 === strpos($sLink, BX_DOL_URL_ROOT))
  373. $sLink = substr($sLink, strlen(BX_DOL_URL_ROOT));
  374. $s = BxDolPage::multisiteLinkCheck ('', $a[0], $aPage['module'], $_GET);
  375. if (false !== $s)
  376. return $s . $sLink;
  377. }
  378. // display page
  379. $_REQUEST['i'] = $_GET['i'] = $a[0];
  380. $oPage = BxDolPage::getObjectInstanceByURI($a[0], false, true);
  381. return $oPage;
  382. }
  383. /**
  384. * Process SEO links. It takes request part from SEO link and process it
  385. * to make it work as regular page link
  386. * @param $sRequest request URI with SEO link
  387. * @return true - if page was found and processed correctly, false - if page wasn't found
  388. */
  389. static public function processSeoLink ($sRequest)
  390. {
  391. $mixed = self::getPageBySeoLink($sRequest);
  392. if (($sUrl = $mixed) && is_string($sUrl)) {
  393. header('Location:' . $sUrl, true, 301);
  394. exit;
  395. }
  396. elseif (($oPage = $mixed) && is_object($oPage)) {
  397. $oPage->displayPage();
  398. return true;
  399. }
  400. else {
  401. return false;
  402. }
  403. }
  404. /**
  405. * Transform regular link to SEO link. It takes regular list as param and return SEO link.
  406. * @param $sLink regular link
  407. * @param $sPrefix prefix to add to the final URL, usually BX_DOL_URL_ROOT
  408. * @param $aParams additional GET params
  409. * @return SEO link string on success, false if transform failed or not necessary
  410. */
  411. static public function transformSeoLink ($sLink, $sPrefix, $aParams = array())
  412. {
  413. static $aMemoryUrls = [];
  414. $sMemoryUrlCacheKey = $sLink . serialize($aParams);
  415. if (isset($aMemoryUrls[$sMemoryUrlCacheKey]))
  416. return $aMemoryUrls[$sMemoryUrlCacheKey];
  417. if (!getParam('permalinks_seo_links'))
  418. return false;
  419. if (0 !== strncmp('page.php', $sLink, 8)) // only page.php links are supported
  420. return false;
  421. $sQuery = parse_url($sLink, PHP_URL_QUERY);
  422. if (false === $sQuery)
  423. return false;
  424. if (function_exists('bx_parse_str'))
  425. $aQueryParams = bx_parse_str($sQuery);
  426. else
  427. parse_str($sQuery, $aQueryParams);
  428. $sPageUri = !empty($aQueryParams['i']) ? $aQueryParams['i'] : false;
  429. unset($aQueryParams['i']);
  430. if (!$sPageUri) // page URI wasn't found
  431. return false;
  432. $sPageModule = '';
  433. $aSeoParams = array('id', 'profile_id'); // supported SEO params which are transformed to SEO strings
  434. $sSeoParamName = '';
  435. $sSeoParamValue = '';
  436. $sSeoTitle = '';
  437. if ('cmts-view' != $sPageUri) {
  438. foreach ($aSeoParams as $k) {
  439. if (empty($aQueryParams[$k]) && empty($aParams[$k]))
  440. continue;
  441. $sSeoParamName = $k;
  442. $sSeoParamValue = !empty($aParams[$k]) ? $aParams[$k] : $aQueryParams[$k];
  443. unset($aQueryParams[$k]);
  444. unset($aParams[$k]);
  445. break; // only 1 SEO param will be transformed
  446. }
  447. }
  448. if (!empty($sSeoParamValue) && 0 === strpos($sSeoParamValue, '{'))
  449. return false;
  450. $aSeoUriRewrites = BxDolPageQuery::getSeoUriRewrites();
  451. if (isset($aSeoUriRewrites[$sPageUri])) {
  452. $sSeoPageUri = $aSeoUriRewrites[$sPageUri];
  453. } else {
  454. $sSeoPageUri = $sPageUri;
  455. }
  456. if ($sSeoParamName && $sSeoParamValue) { // process page with SEO param
  457. $sPageName = BxDolPageQuery::getPageObjectNameByURI($sPageUri);
  458. $aPage = $sPageName ? BxDolPageQuery::getPageObject($sPageName) : false;
  459. if ($aPage) {
  460. $sPageModule = $aPage['module'];
  461. $sContentInfo = !empty($aPage['content_info']) ? $aPage['content_info'] : $aPage['module'];
  462. $bSeoUrlFound = true;
  463. if ('id' == $sSeoParamName) {
  464. $oContentInfo = BxDolContentInfo::getObjectInstance($sContentInfo);
  465. if (!$oContentInfo) {
  466. $bSeoUrlFound = false;
  467. } else {
  468. $sSeoTitle = $oContentInfo->getContentTitle($sSeoParamValue);
  469. }
  470. if (empty($sSeoTitle))
  471. $sSeoTitle = base_convert((string)sprintf('%u', crc32($sSeoParamValue . BX_DOL_SECRET)), 10, 36);
  472. }
  473. elseif ('profile_id' == $sSeoParamName) {
  474. $oProfile = BxDolProfile::getInstance($sSeoParamValue);
  475. if (!$oProfile) {
  476. $bSeoUrlFound = false;
  477. } else {
  478. $sSeoTitle = $oProfile->getDisplayName();
  479. }
  480. }
  481. $r = false;
  482. if (!$bSeoUrlFound) {
  483. $sSeoPageUri = false;
  484. } else {
  485. $r = BxDolPageQuery::getSeoLink($sPageModule, $sPageUri, ['param_value' => $sSeoParamValue]);
  486. }
  487. if ($bSeoUrlFound && !$r) {
  488. $sSeoTitleLimited = BxTemplFunctions::getInstance()->getStringWithLimitedLength($sSeoTitle, 45);
  489. $sUri = uriGenerate ($sSeoTitleLimited, 'sys_seo_links', 'uri', ['cond' => ['module' => $sPageModule, 'page_uri' => $sPageUri]]);
  490. /**
  491. * @hooks
  492. * @hookdef hook-system-uri_generate 'system', 'uri_generate' - hook to override SEO URI generated for a page
  493. * - $unit_name - equals `system`
  494. * - $action - equals `uri_generate`
  495. * - $object_id - not used
  496. * - $sender_id - not used
  497. * - $extra_params - array of additional params with the following array keys:
  498. * - `module` - [string] module name
  499. * - `page_uri` - [string] page default URI
  500. * - `param_name` - [string] param name
  501. * - `param_value` - [string] param value
  502. * - `title` - [string] SEO page title
  503. * - `uri` - [string] by ref, generated SEO URI, can be overridden in hook processing
  504. * @hook @ref hook-system-uri_generate
  505. */
  506. bx_alert('system', 'uri_generate', 0, false, [
  507. 'module' => $sPageModule,
  508. 'page_uri' =>$sPageUri,
  509. 'param_name' => $sSeoParamName,
  510. 'param_value' => $sSeoParamValue,
  511. 'title' => $sSeoTitle,
  512. 'uri' => &$sUri,
  513. 'uri_ref' => &$sUri,
  514. ]);
  515. BxDolPageQuery::insertSeoLink($sPageModule, $sPageUri, $sSeoParamName, $sSeoParamValue, $sUri);
  516. }
  517. elseif ($r && $r['param_name'] == $sSeoParamName) {
  518. $sUri = $r['uri'];
  519. }
  520. else {
  521. $sUri = false;
  522. }
  523. if ($sUri)
  524. $sSeoPageUri .= '/' . $sUri;
  525. else
  526. $sSeoPageUri = false;
  527. }
  528. }
  529. $sRet = false;
  530. if ($sSeoPageUri) {
  531. $sLinkTransformed = $sPrefix . bx_append_url_params($sSeoPageUri, array_merge($aQueryParams, $aParams), false);
  532. if (defined('BX_MULTISITE_MODULE')) {
  533. $s = BxDolPage::multisiteLinkCheck ($sLinkTransformed, $sPageUri, $sPageModule, [$sSeoParamName => $sSeoParamValue]);
  534. if (false !== $s)
  535. $sRet = $s;
  536. }
  537. else {
  538. $sRet = $sLinkTransformed;
  539. }
  540. }
  541. $aMemoryUrls[$sMemoryUrlCacheKey] = $sRet;
  542. return $sRet;
  543. }
  544. /**
  545. * Transform SEO regular link into regular link with permalinks off.
  546. * @param $sSeoLink SEO link
  547. * @param $sPrefix prefix to add to the final URL, usually BX_DOL_URL_ROOT
  548. * @return unSEO link string on success, false if no transform is needed
  549. */
  550. static public function untransformSeoLink ($sSeoLink, $sPrefix)
  551. {
  552. // check for standard links first
  553. if (preg_match('/^(page\/|s\/|m\/|modules\/|page\.php|storage\.php)/', $sSeoLink))
  554. return false;
  555. // parse link
  556. $aParts = parse_url($sSeoLink);
  557. if (!$aParts || empty($aParts['path']))
  558. return false;
  559. $aUris = explode('/', trim($aParts['path'], '/'));
  560. // check if link starts with page URI and page with this URI exists
  561. if (!$aUris || empty($aUris[0]) || !($sPageName = BxDolPageQuery::getPageObjectNameByURI($aUris[0], false, true)))
  562. return false;
  563. $aPage = BxDolPageQuery::getPageObject ($sPageName);
  564. // make final URL
  565. $s = 'page.php?i=' . $aPage['uri'];
  566. // add params
  567. if (!empty($aUris[1])) {
  568. if ($aPage) {
  569. $r = BxDolPageQuery::getSeoLink($aPage['module'], $aPage['uri'], ['uri' => urldecode($aUris[1])]);
  570. if (!$r)
  571. return false;
  572. $s .= '&' . $r['param_name'] . '=' . $r['param_value'];
  573. }
  574. }
  575. return $s . (!empty($aParts['query']) ? '&' . $aParts['query'] : '');
  576. }
  577. /**
  578. * Perform SEO redorect from regular pages if needed
  579. * @param $sSeoLink SEO link
  580. * @param $sPrefix prefix to add to the final URL, usually BX_DOL_URL_ROOT
  581. * @return unSEO link string on success, false if no transform is needed
  582. */
  583. static public function seoRedirect ()
  584. {
  585. if (!getParam('permalinks_seo_links'))
  586. return;
  587. list($sPageLink, $aPageParams) = bx_get_base_url_inline();
  588. $sLink = bx_append_url_params($sPageLink, $aPageParams);
  589. if (0 === strpos($sLink, BX_DOL_URL_ROOT))
  590. $sLink = substr($sLink, strlen(BX_DOL_URL_ROOT));
  591. if (defined('BX_MULTISITE_MODULE')) {
  592. $s = BxDolPage::multisiteLinkCheck ($sLink);
  593. if (false !== $s) {
  594. header('Location:' . $s, true, 301);
  595. exit;
  596. }
  597. }
  598. if ($sSeoLink = self::transformSeoLink ($sLink, BX_DOL_URL_ROOT)) {
  599. header("Location:{$sSeoLink}", true, 301);
  600. exit;
  601. }
  602. }
  603. static public function getSeoHash($s)
  604. {
  605. return base_convert(substr(md5($s), -8), 16, 36);
  606. }
  607. /**
  608. * Static method to Get embed code
  609. * @return string.
  610. */
  611. static public function getEmbedData ($sUrl)
  612. {
  613. $sUrl = urldecode($sUrl);
  614. $aParams = [];
  615. $aUrl = bx_get_base_url($sUrl);
  616. if (!isset($aUrl[1]['i'])){
  617. return [];
  618. }
  619. $sUri = $aUrl[1]['i'];
  620. unset($aUrl[1]['i']);
  621. $aParams = $aUrl[1];
  622. $sTitle = $sAuthorName = $sAuthorUrl = $sThumb = $sHtml = '';
  623. if (isset($aParams['id'])) {
  624. $sContentInfo = BxDolPageQuery::getContentInfoObjectNameByURI($sUri);
  625. if(($oContentInfo = BxDolContentInfo::getObjectInstance($sContentInfo))) {
  626. $sTitle = $oContentInfo->getContentTitle($aParams['id']);
  627. $iAuthor = $oContentInfo->getContentAuthor($aParams['id']);
  628. if(($oAuthor = BxDolProfile::getInstance($iAuthor)) !== false) {
  629. $sAuthorName = $oAuthor->getDisplayName();
  630. $sAuthorUrl = $oAuthor->getUrl();
  631. }
  632. $sThumb = $oContentInfo->getContentThumb($aParams['id']);
  633. $sHtml = $oContentInfo->getContentEmbed($aParams['id']);
  634. }
  635. }
  636. else {
  637. $oPage = BxDolPage::getObjectInstanceByURI($sUri, false, true);
  638. $aPage = $oPage->getObject();
  639. $sTitle = $oPage->_getPageTitle();
  640. $sHtml = BxDolTemplate::getInstance()->parseHtmlByName('embed.html', [
  641. 'title' => $sTitle,
  642. 'url' => BX_DOL_URL_ROOT . 'page.php?a=embed&o=' . $oPage->getName()
  643. ]);
  644. }
  645. return ['url' => $sUrl, 'title' => $sTitle, 'author_name' => $sAuthorName, 'author_url' => $sAuthorUrl, 'thumbnail_url' => $sThumb, 'html' => $sHtml];
  646. }
  647. static public function getPageBlockData($iBlockId, $iContentId = 0, $sContentModule = '')
  648. {
  649. $oDb = new BxDolPageQuery([]);
  650. $sData = $oDb->getPageBlockData($iBlockId, $iContentId, $sContentModule);
  651. $aData = !empty($sData) ? json_decode($sData, true) : [];
  652. foreach($aData as &$j) {
  653. foreach($j as &$k) {
  654. switch($k['type']) {
  655. case 'image':
  656. $aImage = bx_api_get_image(['sys_images_editor', 'sys_images_editor'], (int)$k['content']);
  657. if($aImage && is_array($aImage))
  658. $k['content'] = $aImage;
  659. break;
  660. case 'link':
  661. if(($oEmbed = BxDolEmbed::getObjectInstance('sys_system')) !== false)
  662. $k['content_data'] = $oEmbed->getData($k['content'], '');
  663. break;
  664. }
  665. }
  666. }
  667. return $aData;
  668. }
  669. static public function setPageBlockData($iBlockId, $iContentId = 0, $sContentModule = '', $mixedData = '')
  670. {
  671. $oDb = new BxDolPageQuery([]);
  672. $sData = is_array($mixedData) ? json_encode($mixedData) : $mixedData;
  673. return $oDb->setPageBlockData($iBlockId, $iContentId, $sContentModule, $sData);
  674. }
  675. static public function getBlockProcessing()
  676. {
  677. return self::$_aBlockProcessing;
  678. }
  679. static public function setBlockProcessing($aBlock)
  680. {
  681. self::$_aBlockProcessing = $aBlock && is_array($aBlock) ? $aBlock : [];
  682. }
  683. static public function unsetBlockProcessing()
  684. {
  685. self::$_aBlockProcessing = [];
  686. }
  687. /**
  688. * Display complete page
  689. */
  690. public function displayPage ($oTemplate = null)
  691. {
  692. $sSessionKey = 'sys_entrance_url';
  693. $oSession = BxDolSession::getInstance();
  694. if(!$oSession->getValue($sSessionKey)) {
  695. list($sPageLink, $aPageParams) = bx_get_base_url_inline();
  696. $oSession->setValue($sSessionKey, bx_append_url_params($sPageLink, array_intersect_key($aPageParams, array_flip(['i', 'id', 'profile_id']))));
  697. }
  698. if(isLogged())
  699. BxDolAccount::getInstance()->isNeedChangePassword();
  700. if ($this->isLockedFromUnauthenticated ($this->_aObject['uri'])) {
  701. $this->redirectToLoginForm();
  702. exit;
  703. }
  704. if(!$oTemplate)
  705. $oTemplate = BxDolTemplate::getInstance();
  706. $oTemplate->setPageNameIndex(BX_PAGE_DEFAULT);
  707. if(($sHxTarget = bx_get_htmx_target()) !== false)
  708. $oTemplate->setPageNameIndexByTarget($sHxTarget);
  709. $oTemplate->setPageUrl($this->_aObject['url']);
  710. $oTemplate->setPageType($this->getType());
  711. $oTemplate->setPageInjections($this->getInjections());
  712. if($oTemplate->getPageNameIndex() == BX_PAGE_CONTENT_PRELOAD)
  713. $oTemplate->setPageContent('page_main_code', $this->getIncludes());
  714. else
  715. $oTemplate->setPageContent('page_main_code', $this->getCode());
  716. $oTemplate->getPageCode();
  717. }
  718. public function getId ()
  719. {
  720. return (int)$this->_aObject['id'];
  721. }
  722. public function getType ()
  723. {
  724. if (defined('BX_MULTISITE_FORCE_PAGETYPE') && defined('BX_MULTISITE_MODULE')) {
  725. list($sPageLink, $aPageParams) = bx_get_base_url_inline();
  726. $sLink = bx_append_url_params($sPageLink, $aPageParams);
  727. if (0 === strpos($sLink, BX_DOL_URL_ROOT))
  728. $sLink = substr($sLink, strlen(BX_DOL_URL_ROOT));
  729. if (BxDolPage::getMultisite ($sLink))
  730. return BX_MULTISITE_FORCE_PAGETYPE;
  731. }
  732. return (int)$this->_aObject['type_id'];
  733. }
  734. public function getModule ()
  735. {
  736. return $this->_aObject['module'];
  737. }
  738. public function getSubMenu ()
  739. {
  740. return $this->_aObject['submenu'];
  741. }
  742. public function getMetaDescription ()
  743. {
  744. return $this->_aObject['meta_description'];
  745. }
  746. public function getName ()
  747. {
  748. return $this->_sObject;
  749. }
  750. public function getObject ()
  751. {
  752. return $this->_aObject;
  753. }
  754. public function getInjections ()
  755. {
  756. $aResult = array();
  757. foreach($this->_aObject as $sKey => $sValue)
  758. if(strpos($sKey, 'inj_') === 0)
  759. $aResult[substr($sKey, 4)] = $sValue;
  760. return $aResult;
  761. }
  762. public function getForceRedirect ()
  763. {
  764. return $this->_sForceRedirect ?: false;
  765. }
  766. public function setForceRedirect ($sUrl)
  767. {
  768. $this->_sForceRedirect = $sUrl;
  769. }
  770. public function setTitle ($sTitle)
  771. {
  772. return $this->_aObject['title'] = $sTitle;
  773. }
  774. /**
  775. * Add replace markers. Markers are replaced in raw, html, lang blocks and page title, description, keywords and block titles.
  776. * @param $a array of markers as key => value
  777. * @return true on success or false on error
  778. */
  779. public function addMarkers ($a)
  780. {
  781. if (empty($a) || !is_array($a))
  782. return false;
  783. $this->_aMarkers = array_merge ($this->_aMarkers, $a);
  784. return true;
  785. }
  786. public function isAvailablePage ()
  787. {
  788. return $this->_isAvailablePage($this->_aObject);
  789. }
  790. public function isVisiblePage ()
  791. {
  792. return $this->_isVisiblePage($this->_aObject);
  793. }
  794. /**
  795. * Check if page can be seen by viewer.
  796. * @param array $a - page object.
  797. * @return type boolean (true or false).
  798. */
  799. public function isPage()
  800. {
  801. return $this->_isAvailablePage($this->_aObject) === true && $this->_isVisiblePage($this->_aObject) === true;
  802. }
  803. public function isVisiblePageSubmenu ()
  804. {
  805. return $this->_aObject['submenu'] != 'disabled';
  806. }
  807. public function isEditAllowed ()
  808. {
  809. return false;
  810. }
  811. public function isDeleteAllowed ()
  812. {
  813. return false;
  814. }
  815. /**
  816. * Replace provided markers in a string
  817. * @param $mixed string or array to replace markers in
  818. * @return string where all occured markers are replaced
  819. */
  820. protected function _replaceMarkers ($mixed, $aAdditionalMarkers = array())
  821. {
  822. return bx_replace_markers($mixed, array_merge($this->_aMarkers, $aAdditionalMarkers));
  823. }
  824. /**
  825. * Check if page block is visible.
  826. */
  827. protected function _isVisibleBlock ($a)
  828. {
  829. return BxDolAcl::getInstance()->isMemberLevelInSet($a['visible_for_levels']);
  830. }
  831. /**
  832. * Check if page is available.
  833. * @param type $a - page object.
  834. * @return boolean true if available, false or a string with error message otherwise.
  835. */
  836. protected function _isAvailablePage ($a)
  837. {
  838. return true;
  839. }
  840. /**
  841. * Check if page is visible.
  842. * @param array $a - page object.
  843. * @return boolean true if visible, false or a string with error message otherwise.
  844. */
  845. protected function _isVisiblePage ($a)
  846. {
  847. return isAdmin() || BxDolAcl::getInstance()->isMemberLevelInSet($a['visible_for_levels']);
  848. }
  849. public static function isLockedFromUnauthenticated ($sUri)
  850. {
  851. if (!isLogged() && getParam('sys_lock_from_unauthenticated') && !defined('BX_DOL_CRON_EXECUTE')) {
  852. $aURIs = explode(',', getParam('sys_lock_from_unauthenticated_exceptions'));
  853. array_walk($aURIs, function (&$sVal) {
  854. $sVal = trim($sVal);
  855. });
  856. $aI = array_combine($aURIs, array_fill(0, count($aURIs), 1));
  857. if (!preg_match('/\/oauth2\//', $_SERVER['REQUEST_URI']) /*&& !preg_match('/searchKeyword.php$/', $_SERVER['PHP_SELF'])*/ && !preg_match('/member.php$/', $_SERVER['PHP_SELF']) && !isset($aI[$sUri])) {
  858. return true;
  859. }
  860. }
  861. return false;
  862. }
  863. public static function redirectToLoginForm ()
  864. {
  865. header("Location: " . bx_append_url_params(bx_absolute_url(BxDolPermalinks::getInstance()->permalink('page.php?i=login')), ['relocate' => bx_get_self_url()]));
  866. exit;
  867. }
  868. }
  869. /** @} */