BxDolWiki.php 37 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977
  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. * WIKI object.
  11. *
  12. * It's possble to create different WIKI object which will different URLs, Menus and permissions.
  13. * For example it's possible to create
  14. * http://example.com/wiki/somepageshere and http://example.com/docs/anotherpageshere
  15. *
  16. * @section wiki_create Creating the WIKI object:
  17. *
  18. *
  19. * Add record to 'sys_objects_wiki' table:
  20. *
  21. * - object: name of the WIKI object, this name will be user in URL as well,
  22. * for example, 'wiki' will have URLs like this: http://example.com/wiki/somepageshere
  23. * - uri: wiki module URI
  24. * - title: title of the WIKI, for example, documentation, help, tutorial
  25. * - module: module name WIKI object belongs to
  26. * - override_class_name: user defined class name
  27. * - override_class_file: the location of the user defined class, leave it empty if class is located in system folders.
  28. *
  29. * Add record to 'sys_rewrite_rules' table:
  30. * - preg - regular expression which matches some URL
  31. * - service - service method to call if abbe regular expression matches
  32. * - active - active flag
  33. *
  34. * Add record to 'sys_permalinks' table:
  35. * - standard - how link should look like when permalinks are off
  36. * - permalink - how link should look like when permalinks are on,
  37. * some special server configuration may be required to make permalink to work,
  38. * such as 'mod_rewrite' and .htaccess file for Apache
  39. * - check - option name which enables/disables permalinks
  40. * - compare_by_prefix - compare by prefix
  41. */
  42. class BxDolWiki extends BxDolFactory implements iBxDolFactoryObject
  43. {
  44. protected $_bIsApi;
  45. protected $_sObject;
  46. protected $_aObject;
  47. protected $_oQuery;
  48. protected $_sLangCateg = 'Wiki';
  49. protected $_sACLprefix = 'wiki ';
  50. protected $_bProcessMarkdown = true;
  51. /**
  52. * Constructor
  53. * @param $aObject array of WIKI options
  54. */
  55. protected function __construct($aObject)
  56. {
  57. parent::__construct();
  58. $this->_bIsApi = bx_is_api();
  59. $this->_sObject = $aObject['object'];
  60. $this->_aObject = $aObject;
  61. $this->_oQuery = new BxDolWikiQuery($aObject);
  62. }
  63. /**
  64. * Get WIKI object instance by object URI
  65. * @param $sObject object name
  66. * @return object instance or false on error
  67. */
  68. static public function getObjectInstanceByUri($sUri, $oTemplate = false)
  69. {
  70. $aObject = BxDolWikiQuery::getWikiObjectByUri($sUri);
  71. if (!$aObject || !is_array($aObject))
  72. return false;
  73. return self::getObjectInstance($aObject['object'], $oTemplate);
  74. }
  75. /**
  76. * Get WIKI object instance by object name
  77. * @param $sObject object name
  78. * @return object instance or false on error
  79. */
  80. static public function getObjectInstance($sObject, $oTemplate = false)
  81. {
  82. if (isset($GLOBALS['bxDolClasses']['BxDolWiki!'.$sObject]))
  83. return $GLOBALS['bxDolClasses']['BxDolWiki!'.$sObject];
  84. $aObject = BxDolWikiQuery::getWikiObject($sObject);
  85. if (!$aObject || !is_array($aObject))
  86. $aObject = BxDolWikiQuery::getWikiObject('system');
  87. $sClass = empty($aObject['override_class_name']) ? 'BxDolWiki' : $aObject['override_class_name'];
  88. if (!empty($aObject['override_class_file']))
  89. require_once(BX_DIRECTORY_PATH_ROOT . $aObject['override_class_file']);
  90. $o = new $sClass($aObject, $oTemplate);
  91. return ($GLOBALS['bxDolClasses']['BxDolWiki!'.$sObject] = $o);
  92. }
  93. static public function onModuleUninstall($sModule)
  94. {
  95. $aBlockIds = BxDolWikiQuery::getBlocks ($sModule);
  96. if ($aBlockIds && is_array($aBlockIds))
  97. self::onBlockDelete($aBlockIds);
  98. }
  99. static public function onBlockDelete($mixedBlockIds)
  100. {
  101. BxDolWikiQuery::deleteAllRevisions($mixedBlockIds);
  102. $aBlockIds = is_array($mixedBlockIds) ? $mixedBlockIds : [$mixedBlockIds];
  103. $oStorage = BxDolStorage::getObjectInstance('sys_wiki_files');
  104. if ($oStorage) {
  105. foreach ($aBlockIds as $iBlockId) {
  106. $aFiles = $oStorage->getGhosts(0, $iBlockId, false, true);
  107. $oStorage->queueFiles($aFiles);
  108. }
  109. }
  110. }
  111. /**
  112. * Get object name
  113. */
  114. public function getObjectName ()
  115. {
  116. return $this->_sObject;
  117. }
  118. /**
  119. * Get URI
  120. */
  121. public function getUri ()
  122. {
  123. return $this->_aObject['uri'];
  124. }
  125. /**
  126. * Get WIKI block content
  127. * @param $iBlockId block ID
  128. * @param $sLang optional language name
  129. * @return block content
  130. */
  131. public function getBlockContent ($iBlockId, $sLang = false, $iRevision = false)
  132. {
  133. if (!$sLang)
  134. $sLang = bx_lang_name();
  135. $s = '';
  136. $sControls = '';
  137. $aWikiVer = $this->_oQuery->getBlockContent ($iBlockId, $sLang, $iRevision);
  138. $aWikiLatest = $this->_oQuery->getBlockContent ($iBlockId, $sLang);
  139. if ($aWikiVer) {
  140. if ($this->_bProcessMarkdown) {
  141. $oParsedown = new BxDolParsedown();
  142. $oParsedown->setSafeMode($aWikiVer['unsafe'] ? false : true);
  143. $s = $oParsedown->text($aWikiVer['content']);
  144. }
  145. else {
  146. $s = $aWikiVer['content'];
  147. }
  148. }
  149. if (!$aWikiVer && $aWikiLatest) {
  150. return _t('_sys_wiki_error_no_rev', $iRevision ? $iRevision : 0, $sLang);
  151. }
  152. if (!$aWikiVer && !$aWikiLatest && $this->isAllowed('edit')) {
  153. $s = _t('_sys_wiki_error_no_revs');
  154. }
  155. $sControls = '';
  156. if (!$this->_bIsApi && ($aWikiVer || $this->isAllowed('edit'))) {
  157. $sControls = BxDolService::call('system', 'wiki_controls', array($this, $aWikiVer, $aWikiLatest, $iBlockId), 'TemplServiceWiki');
  158. }
  159. return $s . $sControls;
  160. }
  161. /**
  162. * Generate wiki contents - list of all wiki pages
  163. */
  164. public function getContents ($sAllExceptSpecified = '', $sOnlySpecified = '')
  165. {
  166. $aAllExceptSpecified = $sAllExceptSpecified ? explode(',', $sAllExceptSpecified) : array();
  167. $aAllExceptSpecified = array_merge($aAllExceptSpecified, ['wiki-pages-list', 'wiki-page-contents']);
  168. $aOnlySpecified = $sOnlySpecified ? explode(',', $sOnlySpecified) : array();
  169. if (!($a = $this->_oQuery->getPages($aAllExceptSpecified, $aOnlySpecified)))
  170. return '';
  171. $aVars = array('bx_repeat:pages' => array());
  172. foreach ($a as $r) {
  173. $oPage = BxDolPage::getObjectInstance($r['object']);
  174. if (!$oPage->isVisiblePage())
  175. continue;
  176. $sUrl = $this->getPageUrl($r['uri']);
  177. $sTitle = _t($r['title']);
  178. $aVars['bx_repeat:pages'][] = array(
  179. 'url' => $sUrl,
  180. 'title' => $sTitle ? $sTitle : _t('_Empty'),
  181. );
  182. }
  183. usort($aVars['bx_repeat:pages'], function ($r1, $r2) {
  184. return strcmp($r1['title'], $r2['title']);
  185. });
  186. return BxDolTemplate::getInstance()->parseHtmlByName('wiki_contents.html', $aVars);
  187. }
  188. /**
  189. * Generate list of wiki blocks with missing translations for the given language
  190. */
  191. public function getMissingTranslations ($sLang = false)
  192. {
  193. if (!$sLang)
  194. $sLang = bx_lang_name();
  195. if (!$this->isAllowed('edit') && !$this->isAllowed('translate') || !preg_match("/^[a-z]+$/", $sLang))
  196. return _t('_sys_txt_access_denied');
  197. if (!BxDolLanguages::getInstance()->getLangId($sLang))
  198. return _t('_sys_wiki_error_no_such_lang');
  199. $a = $this->_oQuery->getBlocksWithMissingTranslations($sLang);
  200. if (!$a)
  201. return _t('_sys_wiki_no_missing_translations', $sLang);
  202. $aVars = array(
  203. 'title_title' => _t('_sys_wiki_title_title'),
  204. 'title_lang' => _t('_sys_wiki_title_lang_missing'),
  205. 'title_author' => _t('_sys_wiki_title_author', _t('_sys_wiki_orig')),
  206. 'title_content' => _t('_sys_wiki_title_content', _t('_sys_wiki_orig')),
  207. 'title_time' => _t('_sys_wiki_title_time', _t('_sys_wiki_orig')),
  208. 'bx_repeat:blocks' => array()
  209. );
  210. foreach ($a as $iBlockId) {
  211. $aWikiVerMain = $this->_oQuery->getBlockContent ($iBlockId, false);
  212. $aPage = $this->_oQuery->getPageByBlockId($iBlockId);
  213. $sUrl = $this->getPageUrl($aPage['uri']) . '#bx-page-wiki-container-' . $iBlockId;
  214. $oProfile = BxDolProfile::getInstanceMagic($aWikiVerMain['profile_id']);
  215. $aVars['bx_repeat:blocks'][] = array(
  216. 'author_url' => $oProfile->getUrl(),
  217. 'author_name' => $oProfile->getDisplayName(),
  218. 'url' => $sUrl,
  219. 'lang' => $sLang,
  220. 'title' => _t($aPage['title']),
  221. 'content' => strmaxtextlen($aWikiVerMain['content']),
  222. 'timejs' => bx_time_js($aWikiVerMain['added']),
  223. );
  224. }
  225. return BxDolTemplate::getInstance()->parseHtmlByName('wiki_translations_missing.html', $aVars);
  226. }
  227. /**
  228. * Generate list of wiki blocks with outdated translations for the given language
  229. */
  230. public function getOutdatedTranslations ($sLang = false)
  231. {
  232. if (!$sLang)
  233. $sLang = bx_lang_name();
  234. if (!$this->isAllowed('edit') && !$this->isAllowed('translate') || !preg_match("/^[a-z]+$/", $sLang))
  235. return _t('_sys_txt_access_denied');
  236. if (!BxDolLanguages::getInstance()->getLangId($sLang))
  237. return _t('_sys_wiki_error_no_such_lang');
  238. $aVars = array(
  239. 'title_title' => _t('_sys_wiki_title_title'),
  240. 'title_lang' => _t('_sys_wiki_title_lang_outated'),
  241. 'title_author' => _t('_sys_wiki_title_author', _t('_sys_wiki_orig')),
  242. 'title_content' => _t('_sys_wiki_title_content', _t('_sys_wiki_orig')),
  243. 'title_time_main' => _t('_sys_wiki_title_time', _t('_sys_wiki_orig')),
  244. 'title_time' => _t('_sys_wiki_title_time', '"'.$sLang.'"'),
  245. 'bx_repeat:blocks' => array()
  246. );
  247. $a = $this->_oQuery->getBlocksWithOutdatedTranslations($sLang);
  248. if (!$a)
  249. return _t('_sys_wiki_all_translations_up_to_date', $sLang);
  250. foreach ($a as $iBlockId) {
  251. $aWikiVer = $this->_oQuery->getBlockContent ($iBlockId, $sLang, false, false);
  252. $aWikiVerMain = $this->_oQuery->getBlockContent ($iBlockId, false);
  253. $aPage = $this->_oQuery->getPageByBlockId($iBlockId);
  254. $sUrl = $this->getPageUrl($aPage['uri']) . '#bx-page-wiki-container-' . $iBlockId;
  255. $oProfile = BxDolProfile::getInstanceMagic($aWikiVerMain['profile_id']);
  256. $aVars['bx_repeat:blocks'][] = array(
  257. 'author_url' => $oProfile->getUrl(),
  258. 'author_name' => $oProfile->getDisplayName(),
  259. 'url' => $sUrl,
  260. 'lang' => $sLang,
  261. 'title' => _t($aPage['title']),
  262. 'content' => strmaxtextlen($aWikiVerMain['content']),
  263. 'timejs_main' => bx_time_js($aWikiVerMain['added']),
  264. 'timejs' => bx_time_js($aWikiVer['added']),
  265. );
  266. }
  267. return BxDolTemplate::getInstance()->parseHtmlByName('wiki_translations_outdated.html', $aVars);
  268. }
  269. /**
  270. * Check if partucular action is allowed
  271. * @param $sType action type: add, edit, delete, translate
  272. * @param $sProfileId profile to check, if not provided then current profile is used
  273. * @return true if action is allowed, false otherwise
  274. */
  275. public function isAllowed ($sType, $iProfileId = false)
  276. {
  277. if ('convert-links' == $sType) {
  278. return isLogged();
  279. }
  280. // translate isn't allowed when only one language on the site
  281. if ('translate' == $sType) {
  282. $aLangs = BxDolLanguages::getInstance()->getLanguages(false, true);
  283. if (count($aLangs) < 2)
  284. return false;
  285. }
  286. // add page isn't implemented for the system
  287. if ('add-page' == $sType && 'system' == $this->_aObject['module'])
  288. return false;
  289. // any action is allowed for admin(operator)
  290. if (isAdmin())
  291. return true;
  292. // map 'action' to 'acl action'
  293. $aTypes = array(
  294. 'add-page' => 'add page',
  295. 'add' => 'add block',
  296. 'edit' => 'edit block',
  297. 'translate' => 'translate block',
  298. 'delete-version' => 'delete version',
  299. 'delete-block' => 'delete block',
  300. 'get-traaslation' => 'translate block',
  301. 'history' => 'history',
  302. 'unsafe' => 'unsafe',
  303. );
  304. // not listed actions aren't allowed
  305. if (!isset($aTypes[$sType]))
  306. return false;
  307. // hardcoded actions (not implemented for now)
  308. if (true === $aTypes[$sType] || false === $aTypes[$sType])
  309. return $aTypes[$sType];
  310. // all system actions starts with special prefix
  311. $sAction = ('system' == $this->_aObject['module'] ? $this->_sACLprefix : '') . $aTypes[$sType];
  312. if (!$iProfileId)
  313. $iProfileId = bx_get_logged_profile_id();
  314. $aCheck = checkActionModule($iProfileId, $sAction, $this->_aObject['module']);
  315. return $aCheck[CHECK_ACTION_RESULT] === CHECK_ACTION_RESULT_ALLOWED;
  316. }
  317. public function addPage ($sPageUri, $sTitleLangKey)
  318. {
  319. $sUrl = $this->getPageUrl($sPageUri, false, false);
  320. return $this->_oQuery->insertPage ($sPageUri, $sUrl, $sTitleLangKey);
  321. }
  322. public function actionGetTranslation ()
  323. {
  324. $aWikiVer = $this->_oQuery->getBlockContent ((int)bx_get('block_id'), bx_get('language'), false, false);
  325. if (!$aWikiVer)
  326. return array('code' => 1, 'actions' => 'ShowMsg', 'msg' => 'no translation was found');
  327. else
  328. return array('code' => 0, 'language' => $aWikiVer['language'], 'content' => $aWikiVer['content'], 'block_id' => $aWikiVer['block_id']);
  329. }
  330. public function actionDeleteVersion ()
  331. {
  332. $iBlockId = (int)bx_get('block_id');
  333. $sLang = bx_lang_name();
  334. $oLang = BxDolLanguages::getInstance();
  335. $aVars = $this->getVarsForHistory($iBlockId, $sLang);
  336. $aVars['select_all'] = _t('_Select_all');
  337. if (!$aVars['bx_repeat:revisions'])
  338. return BxDolTemplate::getInstance()->parseHtmlByName('wiki_msg.html', array(
  339. 'close' => _t('_sys_close'),
  340. 'msg' => _t('_sys_wiki_error_no_revs'),
  341. ));
  342. $aForm = array(
  343. 'form_attrs' => array(
  344. 'name' => 'bx-wiki-del-rev',
  345. 'method' => 'post',
  346. ),
  347. 'params' => array (
  348. 'db' => array(
  349. 'submit_name' => 'do_submit',
  350. ),
  351. ),
  352. 'inputs' => array(
  353. 'block_id' => array(
  354. 'type' => 'hidden',
  355. 'name' => 'block_id',
  356. 'value' => $iBlockId,
  357. ),
  358. 'language' => array(
  359. 'type' => 'hidden',
  360. 'name' => 'language',
  361. 'value' => $sLang,
  362. ),
  363. 'revision' => array(
  364. 'type' => 'custom',
  365. 'name' => 'revision',
  366. 'caption' => '',
  367. 'content' => BxDolTemplate::getInstance()->parseHtmlByName('wiki_delete_version.html', $aVars),
  368. ),
  369. 'buttons' => array(
  370. 'type' => 'input_set',
  371. array(
  372. 'type' => 'submit',
  373. 'name' => 'do_submit',
  374. 'value' => _t('_Submit'),
  375. ),
  376. array(
  377. 'type' => 'reset',
  378. 'name' => 'close',
  379. 'value' => _t('_sys_close'),
  380. 'attrs' => array(
  381. 'onclick' => "$('.bx-popup-applied:visible').dolPopupHide();",
  382. 'class' => "bx-def-margin-sec-left",
  383. ),
  384. ),
  385. ),
  386. ),
  387. );
  388. $oForm = new BxTemplFormView ($aForm);
  389. $oForm->initChecker();
  390. if ($oForm->isSubmittedAndValid ()) {
  391. $i = $this->_oQuery->deleteRevisions ($iBlockId, $sLang, bx_get('revision'));
  392. return array('code' => 0, 'actions' => array('Reload', 'ClosePopup', 'ShowMsg'), 'block_id' => $iBlockId, 'msg' => _t('_sys_wiki_revisions_deleted', $i));
  393. }
  394. else {
  395. return BxDolTemplate::getInstance()->parseHtmlByName('wiki_form.html', array(
  396. 'form' => $oForm->getCode(),
  397. 'block_id' => $iBlockId,
  398. 'wiki_action_uri' => $this->getUri(),
  399. 'action' => 'delete-version',
  400. 'txt_open_editor' => bx_js_string(_t('_sys_wiki_open_in_editor')),
  401. ));
  402. }
  403. }
  404. public function actionAddPage ()
  405. {
  406. // validate page URI
  407. $sPageUri = strtolower(bx_get('page'));
  408. if (!preg_match("/^[a-z0-9_-]+$/", $sPageUri))
  409. return _t('_sys_wiki_error_incorrect_page_uri', bx_process_output($sPageUri));
  410. // check if such page exist
  411. $oPage1 = BxDolPage::getObjectInstanceByUri($sPageUri);
  412. $oPage2 = BxDolPage::getObjectInstance($this->_aObject['module'] . '_' . str_replace('-', '_', $sPageUri));
  413. if ($oPage1 || $oPage2)
  414. return _t('_sys_wiki_error_page_exists', bx_process_output($sPageUri));
  415. // get lang category where translations will be added
  416. $oLang = BxDolStudioLanguagesUtils::getInstance();
  417. $iLangCat = $oLang->getLanguageCategory($this->_sLangCateg);
  418. if (!$iLangCat)
  419. return _t('_sys_wiki_error_occured', 9);
  420. // set default values for the form
  421. $aValues = array();
  422. $aLangs = $oLang->getLanguages(false, true);
  423. foreach ($aLangs as $sKey => $sLang)
  424. $aValues[$sKey] = ucfirst($sPageUri);
  425. // init form
  426. $aForm = array(
  427. 'form_attrs' => array(
  428. 'name' => 'bx-wiki-create-page',
  429. 'method' => 'post',
  430. ),
  431. 'params' => array (
  432. 'db' => array(
  433. 'submit_name' => 'do_submit',
  434. ),
  435. ),
  436. 'inputs' => array(
  437. 'page' => array(
  438. 'type' => 'hidden',
  439. 'name' => 'page',
  440. 'value' => $sPageUri,
  441. ),
  442. 'title' => array(
  443. 'type' => 'text_translatable',
  444. 'name' => 'title',
  445. 'caption' => _t('_adm_bp_txt_page_title'),
  446. 'info' => _t('_adm_bp_dsc_page_title'),
  447. 'value' => '',
  448. 'values' => $aValues,
  449. 'required' => '1',
  450. 'db' => array (
  451. 'pass' => 'Xss',
  452. ),
  453. 'checker' => array (
  454. 'func' => 'LengthTranslatable',
  455. 'params' => array(3,100, 'title'),
  456. 'error' => _t('_adm_bp_err_page_title'),
  457. ),
  458. ),
  459. 'buttons' => array(
  460. 'type' => 'input_set',
  461. array(
  462. 'type' => 'submit',
  463. 'name' => 'do_submit',
  464. 'value' => _t('_Submit'),
  465. ),
  466. array(
  467. 'type' => 'reset',
  468. 'name' => 'close',
  469. 'value' => _t('_sys_close'),
  470. 'attrs' => array(
  471. 'onclick' => "$('.bx-popup-applied:visible').dolPopupHide();",
  472. 'class' => "bx-def-margin-sec-left",
  473. ),
  474. ),
  475. ),
  476. ),
  477. );
  478. $oForm = new BxTemplStudioFormView ($aForm);
  479. $oForm->initChecker();
  480. if ($oForm->isSubmittedAndValid ()) {
  481. // insert page
  482. $sLangKey = '_' . $this->_aObject['module'] . '_' . str_replace('-', '_', $sPageUri) . '_' . time();
  483. $iPageId = $this->addPage ($sPageUri, $sLangKey);
  484. if (!$iPageId)
  485. return array('code' => 10, 'actions' => array('ShowMsg'), 'msg' => _t('_sys_wiki_error_occured', 10));
  486. // insert translations
  487. $aLangs = $oLang->getLanguages(true, true);
  488. foreach ($aLangs as $iLangId => $sLangTitle) {
  489. $sLang = $oLang->getLangName($iLangId);
  490. if (!($sVal = $oForm->getCleanValue('title-' . $sLang)))
  491. continue;
  492. $oLang->addLanguageString($sLangKey, $sVal, $iLangId, $iLangCat);
  493. }
  494. $sUrl = $this->getPageUrl($sPageUri, false, false);
  495. return array('code' => 0, 'url' => BxDolPermalinks::getInstance()->permalink($sUrl));
  496. }
  497. else {
  498. // display form
  499. return BxDolTemplate::getInstance()->parseHtmlByName('wiki_create_page_form.html', array(
  500. 'form' => $oForm->getCode(),
  501. 'wiki_action_uri' => $this->getUri(),
  502. 'action' => 'add-page',
  503. ));
  504. }
  505. }
  506. public function actionAdd ()
  507. {
  508. $iCellId = (int)bx_get('cell_id');
  509. $sPage = bx_get('page');
  510. $oPage = BxDolPage::getObjectInstance($sPage);
  511. if (!$oPage)
  512. return array('code' => 4, 'actions' => array('ShowMsg'), 'msg' => _t('_sys_wiki_error_occured', 4));
  513. if ($this->_aObject['module'] != $oPage->getModule())
  514. return array('code' => 5, 'actions' => array('ShowMsg'), 'msg' => _t('_sys_txt_access_denied'));
  515. $iDesignBox = getParam($this->_aObject['module'] . '_design_box');
  516. $aBlock = array(
  517. 'object' => $sPage,
  518. 'cell_id' => $iCellId,
  519. 'module' => $this->_aObject['module'],
  520. 'title_system' => '',
  521. 'title' => '',
  522. 'designbox_id' => false === $iDesignBox ? 0 : $iDesignBox,
  523. 'visible_for_levels' => 2147483647,
  524. 'type' => 'wiki',
  525. 'deletable' => 1,
  526. 'copyable' => 0,
  527. 'active' => 1,
  528. );
  529. $oQueryPageBuilder = new BxDolStudioBuilderPageQuery();
  530. if (!($iBlockId = $oQueryPageBuilder->insertBlock($aBlock)))
  531. return array('code' => 6, 'actions' => array('ShowMsg'), 'msg' => _t('_sys_wiki_error_occured', 6));
  532. return array('code' => 0, 'block_id' => $iBlockId, 'cell_id' => $iCellId);
  533. }
  534. public function actionDeleteBlock ()
  535. {
  536. $iBlockId = (int)bx_get('block_id');
  537. $oQueryPageBuilder = new BxDolStudioBuilderPageQuery();
  538. if (!$oQueryPageBuilder->deleteBlocks(array('type' => 'by_id', 'value' => $iBlockId)))
  539. return array('code' => 3, 'actions' => array('ShowMsg'), 'block_id' => $iBlockId, 'msg' => _t('_sys_wiki_error_occured', 3));
  540. self::onBlockDelete($iBlockId);
  541. return array('code' => 0, 'actions' => array('DeleteBlock', 'ShowMsg'), 'block_id' => $iBlockId, 'msg' => _t('_sys_wiki_block_deleted'));
  542. }
  543. public function actionHistory ()
  544. {
  545. $iBlockId = (int)bx_get('block_id');
  546. $sLang = bx_lang_name();
  547. $oLang = BxDolLanguages::getInstance();
  548. $aVars = $this->getVarsForHistory($iBlockId, $sLang);
  549. $aVars['language'] = $oLang->getLangTitle($oLang->getLangId($sLang));
  550. $aVars['close'] = _t('_sys_close');
  551. $aVars['msg'] = $aVars['bx_repeat:revisions'] ? '' : _t('_sys_wiki_error_no_revs');
  552. return BxDolTemplate::getInstance()->parseHtmlByName('wiki_history.html', $aVars);
  553. }
  554. public function actionTranslate ()
  555. {
  556. return $this->actionEdit (true);
  557. }
  558. public function actionEdit ($bTranslate = false)
  559. {
  560. $iBlockId = (int)bx_get('block_id');
  561. $sMainLangLabel = '';
  562. $aWikiVerMain = array();
  563. $sLangForTranslate = '';
  564. $aLangsForInput = $this->getLangsForInput ($iBlockId, $bTranslate, $sMainLangLabel, $aWikiVerMain, $sLangForTranslate);
  565. // don't allow to translate empty blocks
  566. if (!$aWikiVerMain && $bTranslate)
  567. return BxDolTemplate::getInstance()->parseHtmlByName('wiki_msg.html', array(
  568. 'close' => _t('_sys_close'),
  569. 'msg' => _t('_sys_wiki_error_no_revs'),
  570. ));
  571. // get latest revision for block with current lang
  572. if ($bTranslate) {
  573. $aWikiVer = $this->_oQuery->getBlockContent ($iBlockId, $sLangForTranslate, false, false);
  574. }
  575. else {
  576. $aWikiVer = $this->_oQuery->getBlockContent ($iBlockId, bx_lang_name());
  577. }
  578. if ($aWikiVer) // unset notes since we need this field empty in the form
  579. unset($aWikiVer['notes']);
  580. if (!$aWikiVer) { // check for new block, so initialize with default values
  581. $aWikiVer = array('block_id' => $iBlockId);
  582. if ($bTranslate)
  583. $aWikiVer['language'] = $sLangForTranslate;
  584. else
  585. $aWikiVer['language'] = bx_lang_name();
  586. }
  587. // init form object
  588. $oForm = BxDolForm::getObjectInstance('sys_wiki', $bTranslate ? 'sys_wiki_translate' : 'sys_wiki_edit');
  589. if (!$oForm)
  590. return _t('_sys_wiki_error_occured', 12);
  591. if (isset($oForm->aInputs['language']))
  592. $oForm->aInputs['language']['values'] = $aLangsForInput;
  593. if (isset($oForm->aInputs['content_main']) && $sMainLangLabel) {
  594. $oForm->aInputs['content_main']['caption'] = $sMainLangLabel;
  595. $oForm->aInputs['content_main']['content'] = $aWikiVerMain ? BxDolTemplate::getInstance()->parseHtmlByName('wiki_content.html', array('content' => $aWikiVerMain['content'])) : '';
  596. }
  597. $oForm->initChecker($aWikiVer);
  598. if (!$oForm->isSubmittedAndValid()) {
  599. // display form
  600. return BxDolTemplate::getInstance()->parseHtmlByName('wiki_form.html', array(
  601. 'form' => $oForm->getCode(),
  602. 'block_id' => $iBlockId,
  603. 'wiki_action_uri' => $this->getUri(),
  604. 'action' => $bTranslate ? 'translate' : 'edit',
  605. 'txt_open_editor' => bx_js_string(_t('_sys_wiki_open_in_editor')),
  606. ));
  607. }
  608. else {
  609. $sLang = $oForm->getCleanValue('language');
  610. // get previous revision with priority for current language
  611. $aWikiVer = $this->_oQuery->getBlockContent ($iBlockId, $sLang);
  612. $bMainLang = $this->getFieldMainLangFlag ($oForm, $sLang, $aWikiVer);
  613. $sRev = $this->getFieldRev ($oForm, $sLang, $aWikiVer);
  614. $bUnsafe = $this->getFieldUnsafeFlag ($oForm, $sLang, $aWikiVer);
  615. // insert new revision (main lang is NOT allowed for translations)
  616. $iTime = time();
  617. if ((!$bMainLang || ($bMainLang && $this->isAllowed('edit'))) && $this->isContentChanged($iBlockId, $sLang, $oForm->getCleanValue('content'))) {
  618. $sId = $oForm->insert(array(
  619. 'added' => $iTime,
  620. 'revision' => $sRev,
  621. 'main_lang' => $bMainLang,
  622. 'profile_id' => bx_get_logged_profile_id(),
  623. 'unsafe' => $bUnsafe,
  624. ));
  625. }
  626. // process translations if available
  627. if (($sTranslations = bx_get('translations')) && ($aTranslations = json_decode($sTranslations, true))) {
  628. foreach ($aTranslations as $sLang => $sContent) {
  629. if ($sLang == $oForm->getCleanValue('language'))
  630. continue;
  631. // get previous revision with priority for current language
  632. $aWikiVer = $this->_oQuery->getBlockContent ($iBlockId, $sLang);
  633. $bMainLang = $this->getFieldMainLangFlag ($oForm, $sLang, $aWikiVer);
  634. $sRev = $this->getFieldRev ($oForm, $sLang, $aWikiVer);
  635. $bUnsafe = $this->getFieldUnsafeFlag ($oForm, $sLang, $aWikiVer);
  636. // insert new revision (main lang is NOT allowed for translations)
  637. if ((!$bMainLang || ($bMainLang && $this->isAllowed('edit'))) && $this->isContentChanged($iBlockId, $sLang, $sContent)) {
  638. $sId = $oForm->insert(array(
  639. 'added' => $iTime,
  640. 'revision' => $sRev,
  641. 'main_lang' => $bMainLang,
  642. 'profile_id' => bx_get_logged_profile_id(),
  643. 'unsafe' => $bUnsafe,
  644. 'language' => $sLang,
  645. 'content' => $sContent,
  646. ));
  647. }
  648. }
  649. }
  650. // update indexing data
  651. $this->updateBlockIndexingData($iBlockId);
  652. return array('code' => 0, 'actions' => array('Reload', 'ClosePopup'), 'block_id' => $iBlockId);
  653. }
  654. }
  655. public function actionConvertLinks ()
  656. {
  657. $s = bx_get('s');
  658. if (!$s)
  659. return ['s' => ''];
  660. // convert links to be viewable in external editor
  661. $s = preg_replace_callback('#\((([a-zA-Z0-9_]+)/([a-zA-Z0-9]+))\)#', function ($aMatches) {
  662. $oStorage = BxDolStorage::getObjectInstance($aMatches[2]);
  663. if ($oStorage) {
  664. $sUrl = $oStorage->getFileUrlByRemoteId($aMatches[3]);
  665. if ($sUrl)
  666. return '(' . $sUrl . ')';
  667. }
  668. return '(' . $aMatches[0] . ')';
  669. }, $s);
  670. // add references to existing pages, so short references will work in external editor
  671. $s .= "\n<!-- " . _t("_sys_wiki_external_editor_references_comment") . " -->\n";
  672. $this->_aPages = $this->_oQuery->getAllPages ();
  673. if ($this->_aPages) {
  674. foreach ($this->_aPages as $sUri => $r)
  675. $s .= "[{$sUri}]: " . BX_DOL_URL_ROOT . BxDolPermalinks::getInstance()->permalink($r['url']) . "\n";
  676. }
  677. return ['s' => $s];
  678. }
  679. public function updateBlockIndexingData($iBlockId)
  680. {
  681. if (!($aLangs = $this->_oQuery->getBlockLangs ($iBlockId)))
  682. return false;
  683. $sText = '';
  684. foreach ($aLangs as $sLang) {
  685. $aWikiVer = $this->_oQuery->getBlockContent ($iBlockId, $sLang, false, false);
  686. $sText .= trim(strip_tags($aWikiVer['content'])) . ' ';
  687. }
  688. return $this->_oQuery->updateBlockIndexingData ($iBlockId, $sText);
  689. }
  690. protected function getPageUrl ($sPageUri, $bPermalink = true, $bAddRootUrl = true)
  691. {
  692. $sUrl = ($bAddRootUrl ? BX_DOL_URL_ROOT : '') . 'r.php?_q=' . $this->_aObject['uri'] . '/' . $sPageUri;
  693. return $bPermalink ? BxDolPermalinks::getInstance()->permalink($sUrl) : $sUrl;
  694. }
  695. protected function isContentChanged ($iBlockId, $sLang, $sContent)
  696. {
  697. if (!$sContent) // don't allow revisions with empty content
  698. return false;
  699. $aWikiVer = $this->_oQuery->getBlockContent ($iBlockId, $sLang, false, false);
  700. if (!$aWikiVer) // when no previous revision available
  701. return true;
  702. return $sContent != $aWikiVer['content'];
  703. }
  704. protected function getFieldRev ($oForm, $sLang, $aWikiVer) {
  705. // increase revision for particular language or start with first revision
  706. return $aWikiVer && $sLang == $aWikiVer['language'] ? $aWikiVer['revision'] + 1 : 1;
  707. }
  708. protected function getFieldMainLangFlag ($oForm, $sLang, $aWikiVer) {
  709. // detect main language flag
  710. if ($aWikiVer && $sLang == $aWikiVer['language'])
  711. return $aWikiVer['main_lang']; // when revision is increased (language matches) - copy this flag from previous revision
  712. elseif (!$aWikiVer)
  713. return 1; // when it's first revision and no translations - start as main language
  714. else
  715. return 0;
  716. }
  717. protected function getFieldUnsafeFlag ($oForm, $sLang, $aWikiVer) {
  718. if ($aWikiVer && $sLang == $aWikiVer['language'] && !$aWikiVer['unsafe'])
  719. return 0; // copy 'unsafe' from previous revision only when unsafe = 0
  720. else
  721. return $this->isAllowed('unsafe') ? 1 : 0; // in other cases - check permissions
  722. }
  723. protected function getLangsForInput ($iBlockId, $bTranslateForm, &$sMainLangLabel, &$aWikiVerMain, &$sLangForTranslate)
  724. {
  725. // get main lang update time
  726. $aWikiVerMain = $this->_oQuery->getBlockContent ($iBlockId, 'neverhood');
  727. $iUpdatedMainLang = $aWikiVerMain ? $aWikiVerMain['added'] : 0;
  728. // generate values for radio set
  729. $aLangs = BxDolLanguages::getInstance()->getLanguages(false, true);
  730. foreach ($aLangs as $sKey => $sLang) {
  731. $aWikiVer = $this->_oQuery->getBlockContent ($iBlockId, $sKey, false, false);
  732. $bMainLang = $aWikiVer && $aWikiVer['main_lang'];
  733. $sMainLang = $bMainLang ? '★' : '';
  734. $sComment = !$aWikiVer ? _t('_sys_wiki_lang_missing') : bx_time_js($aWikiVer['added']);
  735. if(!$aWikiVer || $iUpdatedMainLang > $aWikiVer['added'])
  736. $aLangs[$sKey] = _t('_sys_wiki_lang_mask_warn', $sLang, $sMainLang, $sComment);
  737. else
  738. $aLangs[$sKey] = _t('_sys_wiki_lang_mask', $sLang, $sMainLang, $sComment);
  739. if($bTranslateForm) {
  740. if(!$bMainLang && !$sLangForTranslate)
  741. $sLangForTranslate = $sKey;
  742. if($bMainLang) {
  743. $sMainLangLabel = $aLangs[$sKey];
  744. unset($aLangs[$sKey]);
  745. continue;
  746. }
  747. }
  748. }
  749. return $aLangs;
  750. }
  751. public function getVarsForHistory ($iBlockId, $sLang)
  752. {
  753. $a = $this->_oQuery->getBlockHistory($iBlockId, $sLang);
  754. $aVars = array(
  755. 'bx_repeat:revisions' => array()
  756. );
  757. foreach ($a as $r) {
  758. $oProfile = BxDolProfile::getInstanceMagic($r['profile_id']);
  759. list($sPageLink, $aPageParams) = bx_get_base_url_popup(array($r['block_id'].'rev' => $r['revision']));
  760. $r['author_url'] = $oProfile->getUrl();
  761. $r['author_name'] = $oProfile->getDisplayName();
  762. $r['timejs'] = bx_time_js($r['added']);
  763. $r['rev_url'] = BxDolPermalinks::getInstance()->permalink(bx_append_url_params($sPageLink, $aPageParams));
  764. $aVars['bx_repeat:revisions'][] = $r;
  765. }
  766. return $aVars;
  767. }
  768. }
  769. class BxDolParsedown extends Parsedown
  770. {
  771. protected $_aPages;
  772. protected function textElements($text)
  773. {
  774. $res = parent::textElements($text);
  775. // add all available pages as references
  776. $this->_aPages = BxDolWikiQuery::getAllPages ();
  777. if ($this->_aPages) {
  778. foreach ($this->_aPages as $sUri => $r) {
  779. $aData = array(
  780. // real link URL is set later when link is referenced
  781. 'url' => 'bx-internal-page://' . $sUri,
  782. 'title' => !empty($r['title']) ? _t($r['title']) : null,
  783. );
  784. $this->DefinitionData['Reference'][$sUri] = $aData;
  785. }
  786. }
  787. return $res;
  788. }
  789. protected function inlineLink($Excerpt)
  790. {
  791. $a = parent::inlineLink($Excerpt);
  792. if ($a && @isset($a['element']['attributes']['href']) && 0 === strncmp($a['element']['attributes']['href'], 'bx-internal-page://', 19)) {
  793. $sUri = strtolower(substr($a['element']['attributes']['href'], 19));
  794. if (isset($this->_aPages[$sUri])) {
  795. $sHref = BX_DOL_URL_ROOT . BxDolPermalinks::getInstance()->permalink($this->_aPages[$sUri]['url']);
  796. $a['element']['attributes']['href'] = $sHref;
  797. }
  798. else {
  799. $a['element']['attributes']['href'] = $sUri;
  800. }
  801. }
  802. if ($a && @isset($a['element']['attributes']['title']) && '#' === $a['element']['attributes']['title']) {
  803. $a['element']['attributes']['class'] = 'bx-def-font-grayed';
  804. }
  805. return $a;
  806. }
  807. protected function inlineImage($Excerpt)
  808. {
  809. $a = parent::inlineImage($Excerpt);
  810. if (@isset($a['element']['attributes']['src']) && preg_match('#^([a-zA-Z0-9_]+)/([a-zA-Z0-9]+)$#', $a['element']['attributes']['src'], $aMatches) && is_array($aMatches) && 3 == count($aMatches)) {
  811. $oStorage = BxDolStorage::getObjectInstance($aMatches[1]);
  812. if ($oStorage) {
  813. $sUrl = $oStorage->getFileUrlByRemoteId($aMatches[2]);
  814. if ($sUrl)
  815. $a['element']['attributes']['src'] = $sUrl;
  816. }
  817. }
  818. return $a;
  819. }
  820. protected function blockHeader($Excerpt) {
  821. $a = parent::blockHeader($Excerpt);
  822. if (isset($a)) {
  823. $sText = trim($a['element']['handler']['argument'], '#');
  824. $sText = trim($sText, ' ');
  825. $sLink = preg_replace('/[^\p{L}\p{N}\p{M}-]+/u', '', mb_strtolower(mb_ereg_replace(' ','-', $sText)));
  826. $aAttr = array();
  827. if (!empty($sLink)) {
  828. $aAttr = isset($a['element']['attributes']) ? $a['element']['attributes'] : [];
  829. $aAttr['id'] = $sLink;
  830. $aHandler = array(
  831. 'function' => 'lineElements',
  832. 'argument' => $sText . (isset($_SERVER['REQUEST_URI']) ? ' [](' . bx_get_self_url() . '#' . $sLink . ' "#")' : ''),
  833. 'destination' => 'elements',
  834. );
  835. $a['element']['attributes'] = $aAttr;
  836. $a['element']['handler'] = $aHandler;
  837. }
  838. }
  839. else {
  840. $a = null;
  841. }
  842. return $a;
  843. }
  844. }
  845. /** @} */