12670d39fSLORTET<?php 22670d39fSLORTET 32670d39fSLORTETif (!defined('DOKU_INC')) die(); 42670d39fSLORTET 52670d39fSLORTETuse dokuwiki\Action\Exception\ActionDisabledException; 62670d39fSLORTET 72670d39fSLORTETclass action_plugin_extranet extends DokuWiki_Action_Plugin 82670d39fSLORTET{ 9*ef5bb1f3SLORTET /** @var helper_plugin_extranet|null */ 10*ef5bb1f3SLORTET private $helper = null; 11*ef5bb1f3SLORTET 12*ef5bb1f3SLORTET protected function getHelper(): ?helper_plugin_extranet 13*ef5bb1f3SLORTET { 14*ef5bb1f3SLORTET if ($this->helper === null) { 15*ef5bb1f3SLORTET $this->helper = plugin_load('helper', 'extranet'); 16*ef5bb1f3SLORTET } 17*ef5bb1f3SLORTET return $this->helper; 18*ef5bb1f3SLORTET } 19*ef5bb1f3SLORTET 202670d39fSLORTET public function register(Doku_Event_Handler $controller) 212670d39fSLORTET { 22*ef5bb1f3SLORTET $controller->register_hook('DOKUWIKI_STARTED', 'AFTER', $this, 'injectJsInfo'); 232670d39fSLORTET $controller->register_hook('TPL_METAHEADER_OUTPUT', 'BEFORE', $this, 'addProsemirrorPolyfillAsset'); 242670d39fSLORTET $controller->register_hook('PROSEMIRROR_RENDER_PLUGIN', 'BEFORE', $this, 'handleRenderForProsemirror'); 252670d39fSLORTET $controller->register_hook('ACTION_ACT_PREPROCESS', 'AFTER', $this, 'syncTextFromProsemirrorState'); 262670d39fSLORTET $controller->register_hook('IO_WIKIPAGE_WRITE', 'BEFORE', $this, 'syncMacroFromProsemirrorStateBeforeWrite'); 272670d39fSLORTET $controller->register_hook('AJAX_CALL_UNKNOWN', 'BEFORE', $this, 'handleProsemirrorSwitchToText'); 282670d39fSLORTET 29*ef5bb1f3SLORTET $helper = $this->getHelper(); 30*ef5bb1f3SLORTET if ($helper && $helper->isExtranetRequest()) { 312670d39fSLORTET $controller->register_hook('ACTION_ACT_PREPROCESS', 'BEFORE', $this, 'blockConfiguredActions'); 32*ef5bb1f3SLORTET $controller->register_hook('AUTH_LOGIN_CHECK', 'AFTER', $this, 'disableActions'); 33*ef5bb1f3SLORTET $controller->register_hook('PARSER_CACHE_USE', 'BEFORE', $this, 'createExtranetCache'); 34*ef5bb1f3SLORTET $controller->register_hook('IO_WIKIPAGE_READ', 'AFTER', $this, 'displayHideMessageIfRestricted'); 35*ef5bb1f3SLORTET $controller->register_hook('FETCH_MEDIA_STATUS', 'BEFORE', $this, 'hideMediaIfRestricted'); 362670d39fSLORTET } 372670d39fSLORTET } 382670d39fSLORTET 392670d39fSLORTET public function handleRenderForProsemirror(Doku_Event $event, $param): void 402670d39fSLORTET { 412670d39fSLORTET $data = $event->data; 422670d39fSLORTET $name = strtolower(trim((string)($data['name'] ?? ''))); 432670d39fSLORTET if ($name !== 'extranet') return; 442670d39fSLORTET 452670d39fSLORTET $match = trim((string)($data['match'] ?? '')); 462670d39fSLORTET if (!preg_match('/^~~\s*(NOEXTRANET|EXTRANET)\s*~~$/i', $match, $matches)) return; 472670d39fSLORTET 482670d39fSLORTET $renderer = $data['renderer'] ?? null; 492670d39fSLORTET if (!is_object($renderer) || !isset($renderer->nodestack) || !method_exists($renderer->nodestack, 'getDocNode')) { 502670d39fSLORTET return; 512670d39fSLORTET } 522670d39fSLORTET 532670d39fSLORTET $macro = strtoupper($matches[1]); 542670d39fSLORTET $docNode = $renderer->nodestack->getDocNode(); 552670d39fSLORTET if ($macro === 'NOEXTRANET') { 562670d39fSLORTET $docNode->attr('noextranet', true); 572670d39fSLORTET $docNode->attr('extranet', false); 582670d39fSLORTET } else { 592670d39fSLORTET $docNode->attr('extranet', true); 602670d39fSLORTET $docNode->attr('noextranet', false); 612670d39fSLORTET } 622670d39fSLORTET 632670d39fSLORTET $event->preventDefault(); 642670d39fSLORTET $event->stopPropagation(); 652670d39fSLORTET } 662670d39fSLORTET 672670d39fSLORTET public function addProsemirrorPolyfillAsset(Doku_Event $event, $param): void 682670d39fSLORTET { 692670d39fSLORTET global $ACT; 702670d39fSLORTET 712670d39fSLORTET if (!in_array((string)$ACT, ['edit', 'preview'], true)) return; 722670d39fSLORTET if (defined('DOKUWIKI_PM_FILE_STATE_POLYFILL_INCLUDED')) return; 732670d39fSLORTET if (empty($event->data) || !is_array($event->data)) return; 742670d39fSLORTET 752670d39fSLORTET define('DOKUWIKI_PM_FILE_STATE_POLYFILL_INCLUDED', 1); 762670d39fSLORTET $path = DOKU_INC . 'lib/plugins/extranet/script/prosemirror_file_state_polyfill.js'; 772670d39fSLORTET $version = @filemtime($path) ?: time(); 782670d39fSLORTET 792670d39fSLORTET $event->data['script'][] = [ 802670d39fSLORTET 'type' => 'text/javascript', 812670d39fSLORTET 'src' => DOKU_BASE . 'lib/plugins/extranet/script/prosemirror_file_state_polyfill.js?v=' . rawurlencode((string)$version), 822670d39fSLORTET '_data' => '', 832670d39fSLORTET 'defer' => 'defer', 842670d39fSLORTET ]; 852670d39fSLORTET } 862670d39fSLORTET 872670d39fSLORTET public function handleProsemirrorSwitchToText(Doku_Event $event, $param): void 882670d39fSLORTET { 892670d39fSLORTET global $INPUT, $ID; 902670d39fSLORTET 912670d39fSLORTET if ($event->data !== 'plugin_prosemirror_switch_editors') return; 922670d39fSLORTET if ($INPUT->bool('getJSON')) return; 932670d39fSLORTET 942670d39fSLORTET $json = (string)$INPUT->str('data'); 952670d39fSLORTET if ($json === '') return; 962670d39fSLORTET 972670d39fSLORTET $ID = $INPUT->str('id'); 982670d39fSLORTET 992670d39fSLORTET /** @var helper_plugin_prosemirror $helper */ 1002670d39fSLORTET $helper = plugin_load('helper', 'prosemirror'); 1012670d39fSLORTET if (!$helper) return; 1022670d39fSLORTET 1032670d39fSLORTET try { 1042670d39fSLORTET $syntax = $helper->getSyntaxFromProsemirrorData($json); 1052670d39fSLORTET } catch (Throwable $e) { 1062670d39fSLORTET return; 1072670d39fSLORTET } 1082670d39fSLORTET 1092670d39fSLORTET $macroState = $this->extractMacroStateFromJson($json); 1102670d39fSLORTET if ($macroState === null) { 1112670d39fSLORTET $macroState = $this->extractMacroStateFromSyntax($syntax); 1122670d39fSLORTET } 1132670d39fSLORTET $syntax = $this->applyMacroStateToSyntax($syntax, $macroState); 1142670d39fSLORTET 1152670d39fSLORTET $event->preventDefault(); 1162670d39fSLORTET $event->stopPropagation(); 1172670d39fSLORTET header('Content-Type: application/json'); 1182670d39fSLORTET echo json_encode(['text' => $syntax]); 1192670d39fSLORTET } 1202670d39fSLORTET 1212670d39fSLORTET public function syncTextFromProsemirrorState(Doku_Event $event, $param): void 1222670d39fSLORTET { 1232670d39fSLORTET global $INPUT, $TEXT; 1242670d39fSLORTET 1252670d39fSLORTET if ($INPUT->server->str('REQUEST_METHOD') !== 'POST') return; 1262670d39fSLORTET if (!in_array((string)$event->data, ['save', 'preview'], true)) return; 1272670d39fSLORTET if (!$INPUT->post->has('prosemirror_json')) return; 1282670d39fSLORTET if (!get_doku_pref('plugin_prosemirror_useWYSIWYG', false)) return; 1292670d39fSLORTET 1302670d39fSLORTET $macroState = $this->extractMacroStateFromJson((string)$INPUT->post->str('prosemirror_json')); 1312670d39fSLORTET if ($macroState === null) { 1322670d39fSLORTET $macroState = $this->extractMacroStateFromSyntax((string)$TEXT); 1332670d39fSLORTET } 1342670d39fSLORTET 1352670d39fSLORTET $TEXT = $this->applyMacroStateToSyntax((string)$TEXT, $macroState); 1362670d39fSLORTET } 1372670d39fSLORTET 1382670d39fSLORTET public function syncMacroFromProsemirrorStateBeforeWrite(Doku_Event $event, $param): void 1392670d39fSLORTET { 140*ef5bb1f3SLORTET global $INPUT; 141*ef5bb1f3SLORTET 142*ef5bb1f3SLORTET if (!$INPUT->post->has('prosemirror_json')) return; 143*ef5bb1f3SLORTET $macroState = $this->extractMacroStateFromJson((string)$INPUT->post->str('prosemirror_json')); 144*ef5bb1f3SLORTET 145*ef5bb1f3SLORTET $helper = $this->getHelper(); 146*ef5bb1f3SLORTET $defaultPolicy = $helper ? $helper->getDefaultPolicy() : 'allow'; 147*ef5bb1f3SLORTET if ($macroState === null && !in_array($defaultPolicy, ['force_allow', 'force_block'], true)) return; 1482670d39fSLORTET 1492670d39fSLORTET if (!isset($event->data[0]) || !is_array($event->data[0]) || !isset($event->data[0][1])) return; 1502670d39fSLORTET $event->data[0][1] = $this->applyMacroStateToSyntax((string)$event->data[0][1], $macroState); 1512670d39fSLORTET } 1522670d39fSLORTET 1532670d39fSLORTET protected function extractMacroStateFromJson(string $json): ?array 1542670d39fSLORTET { 1552670d39fSLORTET if ($json === '') return null; 1562670d39fSLORTET $data = json_decode($json, true); 1572670d39fSLORTET if (!is_array($data)) return null; 1582670d39fSLORTET $attrs = $data['attrs'] ?? null; 1592670d39fSLORTET if (!is_array($attrs)) return null; 1602670d39fSLORTET 1612670d39fSLORTET $hasNoExtranetAttr = array_key_exists('noextranet', $attrs); 1622670d39fSLORTET $hasExtranetAttr = array_key_exists('extranet', $attrs); 1632670d39fSLORTET if (!$hasNoExtranetAttr && !$hasExtranetAttr) return null; 1642670d39fSLORTET 1652670d39fSLORTET return [ 1662670d39fSLORTET 'noextranet' => $hasNoExtranetAttr ? (bool)$attrs['noextranet'] : false, 1672670d39fSLORTET 'extranet' => $hasExtranetAttr ? (bool)$attrs['extranet'] : false, 1682670d39fSLORTET ]; 1692670d39fSLORTET } 1702670d39fSLORTET 1712670d39fSLORTET protected function applyMacroStateToSyntax(string $content, ?array $macroState): string 1722670d39fSLORTET { 1732670d39fSLORTET $content = preg_replace('/^\h*~~\h*(NOEXTRANET|EXTRANET)\h*~~\h*$(\R)?/imu', '', $content); 1742670d39fSLORTET $content = rtrim((string)$content); 1752670d39fSLORTET 176*ef5bb1f3SLORTET $helper = $this->getHelper(); 177*ef5bb1f3SLORTET $defaultPolicy = $helper ? $helper->getDefaultPolicy() : 'allow'; 178*ef5bb1f3SLORTET 179*ef5bb1f3SLORTET if ($macroState === null || in_array($defaultPolicy, ['force_allow', 'force_block'], true)) { 180*ef5bb1f3SLORTET return $content !== '' ? $content . "\n" : ''; 181*ef5bb1f3SLORTET } 182*ef5bb1f3SLORTET 1832670d39fSLORTET if (!empty($macroState['noextranet'])) { 1842670d39fSLORTET $content .= "\n\n~~NOEXTRANET~~\n"; 1852670d39fSLORTET } elseif (!empty($macroState['extranet'])) { 1862670d39fSLORTET $content .= "\n\n~~EXTRANET~~\n"; 1872670d39fSLORTET } elseif ($content !== '') { 1882670d39fSLORTET $content .= "\n"; 1892670d39fSLORTET } 1902670d39fSLORTET 1912670d39fSLORTET return $content; 1922670d39fSLORTET } 1932670d39fSLORTET 1942670d39fSLORTET protected function extractMacroStateFromSyntax(string $content): ?array 1952670d39fSLORTET { 1962670d39fSLORTET $hasNoExtranet = (bool)preg_match('/~~\s*NOEXTRANET\s*~~/i', $content); 1972670d39fSLORTET $hasExtranet = (bool)preg_match('/~~\s*EXTRANET\s*~~/i', $content); 1982670d39fSLORTET 1992670d39fSLORTET if (!$hasNoExtranet && !$hasExtranet) { 2002670d39fSLORTET return null; 2012670d39fSLORTET } 2022670d39fSLORTET 2032670d39fSLORTET return [ 2042670d39fSLORTET 'noextranet' => $hasNoExtranet, 2052670d39fSLORTET 'extranet' => $hasExtranet, 2062670d39fSLORTET ]; 2072670d39fSLORTET } 2082670d39fSLORTET 209*ef5bb1f3SLORTET public function injectJsInfo(Doku_Event $event, $param): void 2102670d39fSLORTET { 2112670d39fSLORTET global $JSINFO; 212*ef5bb1f3SLORTET 213*ef5bb1f3SLORTET $helper = $this->getHelper(); 214*ef5bb1f3SLORTET $mode = $helper ? $helper->getDefaultPolicy() : 'allow'; 2152670d39fSLORTET 2162670d39fSLORTET $JSINFO['plugin_extranet_default_mode'] = $mode; 2172670d39fSLORTET $JSINFO['plugin_extranet_label_noextranet'] = (string)$this->getLang('label_noextranet'); 2182670d39fSLORTET $JSINFO['plugin_extranet_label_extranet'] = (string)$this->getLang('label_extranet'); 2192670d39fSLORTET } 2202670d39fSLORTET 221*ef5bb1f3SLORTET protected function isConfiguredActionDisabled(string $actionName): bool 2222670d39fSLORTET { 2232670d39fSLORTET $actionName = strtolower(trim($actionName)); 2242670d39fSLORTET if ($actionName === '') return false; 2252670d39fSLORTET 226*ef5bb1f3SLORTET $helper = $this->getHelper(); 227*ef5bb1f3SLORTET $actions = $helper ? $helper->parseRuleList($this->getConf('disable_actions')) : []; 2282670d39fSLORTET $actions = array_map(static function ($action) { 2292670d39fSLORTET return strtolower(trim((string)$action)); 2302670d39fSLORTET }, $actions); 2312670d39fSLORTET 2322670d39fSLORTET return in_array($actionName, $actions, true); 2332670d39fSLORTET } 2342670d39fSLORTET 235*ef5bb1f3SLORTET protected function isRestrictedPageActionDisabled(string $actionName): bool 236*ef5bb1f3SLORTET { 237*ef5bb1f3SLORTET $actionName = strtolower(trim($actionName)); 238*ef5bb1f3SLORTET if ($actionName === '') return false; 239*ef5bb1f3SLORTET 240*ef5bb1f3SLORTET $helper = $this->getHelper(); 241*ef5bb1f3SLORTET $actions = $helper ? $helper->parseRuleList($this->getConf('restricted_disable_actions')) : []; 242*ef5bb1f3SLORTET $actions = array_map(static function ($action) { 243*ef5bb1f3SLORTET return strtolower(trim((string)$action)); 244*ef5bb1f3SLORTET }, $actions); 245*ef5bb1f3SLORTET 246*ef5bb1f3SLORTET if (!in_array($actionName, $actions, true)) return false; 247*ef5bb1f3SLORTET 248*ef5bb1f3SLORTET global $ID; 249*ef5bb1f3SLORTET return $helper && !$helper->isPageAllowed((string)$ID); 250*ef5bb1f3SLORTET } 251*ef5bb1f3SLORTET 2522670d39fSLORTET public function blockConfiguredActions(Doku_Event $event, $param): void 2532670d39fSLORTET { 2542670d39fSLORTET $actionName = (string)$event->data; 255*ef5bb1f3SLORTET if ($this->isConfiguredActionDisabled($actionName) || $this->isRestrictedPageActionDisabled($actionName)) { 2562670d39fSLORTET throw new ActionDisabledException(); 2572670d39fSLORTET } 2582670d39fSLORTET } 2592670d39fSLORTET 260*ef5bb1f3SLORTET protected function isContentRestrictedFromExtranet(string $content): bool 2612670d39fSLORTET { 2622670d39fSLORTET global $ID; 263*ef5bb1f3SLORTET $helper = $this->getHelper(); 264*ef5bb1f3SLORTET if (!$helper) return false; 265*ef5bb1f3SLORTET return !$helper->isPageVisibleFromExtranet((string)$ID, $content); 2662670d39fSLORTET } 2672670d39fSLORTET 268*ef5bb1f3SLORTET protected function isMediaRestrictedFromExtranet(string $media): bool 2692670d39fSLORTET { 270*ef5bb1f3SLORTET $helper = $this->getHelper(); 271*ef5bb1f3SLORTET if (!$helper) return false; 272*ef5bb1f3SLORTET return !$helper->isMediaAllowed($media); 2732670d39fSLORTET } 2742670d39fSLORTET 275*ef5bb1f3SLORTET public function disableActions(Doku_Event $event, $param): void 2762670d39fSLORTET { 2772670d39fSLORTET if (!empty($this->getConf('disable_actions'))) { 2782670d39fSLORTET global $conf; 2792670d39fSLORTET $conf['disableactions'] = (!empty($conf['disableactions']) ? $conf['disableactions'] . ',' : '') . $this->getConf('disable_actions'); 2802670d39fSLORTET } 2812670d39fSLORTET } 2822670d39fSLORTET 283*ef5bb1f3SLORTET public function createExtranetCache(Doku_Event $event, $param): void 2842670d39fSLORTET { 2852670d39fSLORTET $cache = $event->data; 2862670d39fSLORTET $cache->key .= '#extranet'; 2872670d39fSLORTET $cache->cache = getCacheName($cache->key, $cache->ext); 2882670d39fSLORTET } 2892670d39fSLORTET 290*ef5bb1f3SLORTET public function displayHideMessageIfRestricted(Doku_Event $event, $param): void 2912670d39fSLORTET { 292*ef5bb1f3SLORTET if (!$this->isContentRestrictedFromExtranet((string)$event->result)) return; 2932670d39fSLORTET 2942670d39fSLORTET $result = ''; 2952670d39fSLORTET 2962670d39fSLORTET if ($this->getConf('preserve_first_title')) { 2972670d39fSLORTET $titlePattern = '/(?:^|\v)(={2,6}.+={2,})(?:\v|$)/'; 2982670d39fSLORTET preg_match($titlePattern, $event->result, $matches); 2992670d39fSLORTET 3002670d39fSLORTET if (!empty($matches[0])) { 3012670d39fSLORTET $result .= $matches[0] . "\r\n"; 3022670d39fSLORTET } 3032670d39fSLORTET } 3042670d39fSLORTET $result .= $this->getConf('message_prefix') . $this->getLang('hidden_message') . $this->getConf('message_suffix'); 3052670d39fSLORTET 3062670d39fSLORTET $event->result = $result; 3072670d39fSLORTET } 3082670d39fSLORTET 309*ef5bb1f3SLORTET public function hideMediaIfRestricted(Doku_Event $event, $param): void 3102670d39fSLORTET { 311*ef5bb1f3SLORTET $hideFilesMode = $this->getHideFilesMode(); 3122670d39fSLORTET if ($hideFilesMode === 'none') return; 3132670d39fSLORTET 3142670d39fSLORTET $mediaID = (string)($event->data['media'] ?? ''); 3152670d39fSLORTET if ($mediaID === '') return; 316*ef5bb1f3SLORTET if ($hideFilesMode === 'except_pageicons' && $this->isPagesIconMedia($mediaID)) return; 317*ef5bb1f3SLORTET if (!$this->isMediaRestrictedFromExtranet($mediaID)) return; 3182670d39fSLORTET 3192670d39fSLORTET $event->data['file'] = dirname(__FILE__) . '/images/restricted.png'; 320*ef5bb1f3SLORTET $event->data['orig'] = $event->data['file']; 321*ef5bb1f3SLORTET $event->data['status'] = 200; 322*ef5bb1f3SLORTET $event->data['statusmessage'] = 'OK'; 3232670d39fSLORTET $event->data['mime'] = 'image/png'; 3242670d39fSLORTET $event->data['download'] = false; 325*ef5bb1f3SLORTET $event->data['cache'] = false; 326*ef5bb1f3SLORTET $event->data['ispublic'] = false; 3272670d39fSLORTET } 3282670d39fSLORTET 329*ef5bb1f3SLORTET protected function getHideFilesMode(): string 3302670d39fSLORTET { 3312670d39fSLORTET $value = $this->getConf('hide_files'); 3322670d39fSLORTET 3332670d39fSLORTET if ($value === true || $value === 1 || $value === '1') return 'all'; 3342670d39fSLORTET if ($value === false || $value === 0 || $value === '0' || $value === '') return 'none'; 3352670d39fSLORTET 3362670d39fSLORTET $value = strtolower(trim((string)$value)); 3372670d39fSLORTET if (!in_array($value, ['all', 'except_pageicons', 'none'], true)) { 3382670d39fSLORTET return 'none'; 3392670d39fSLORTET } 3402670d39fSLORTET 3412670d39fSLORTET return $value; 3422670d39fSLORTET } 3432670d39fSLORTET 344*ef5bb1f3SLORTET protected function isPagesIconMedia(string $mediaID): bool 3452670d39fSLORTET { 3462670d39fSLORTET $mediaID = cleanID($mediaID); 3472670d39fSLORTET if ($mediaID === '') return false; 3482670d39fSLORTET 3492670d39fSLORTET /** @var helper_plugin_pagesicon|null $helper */ 3502670d39fSLORTET $helper = plugin_load('helper', 'pagesicon'); 3512670d39fSLORTET if (!$helper || !method_exists($helper, 'isPageIconMedia')) return false; 3522670d39fSLORTET 3532670d39fSLORTET return (bool)$helper->isPageIconMedia($mediaID); 3542670d39fSLORTET } 3552670d39fSLORTET} 356