helper === null) { $this->helper = plugin_load('helper', 'extranet'); } return $this->helper; } public function register(Doku_Event_Handler $controller) { $controller->register_hook('DOKUWIKI_STARTED', 'AFTER', $this, 'injectJsInfo'); $controller->register_hook('TPL_METAHEADER_OUTPUT', 'BEFORE', $this, 'addProsemirrorPolyfillAsset'); $controller->register_hook('PROSEMIRROR_RENDER_PLUGIN', 'BEFORE', $this, 'handleRenderForProsemirror'); $controller->register_hook('ACTION_ACT_PREPROCESS', 'AFTER', $this, 'syncTextFromProsemirrorState'); $controller->register_hook('IO_WIKIPAGE_WRITE', 'BEFORE', $this, 'syncMacroFromProsemirrorStateBeforeWrite'); $controller->register_hook('AJAX_CALL_UNKNOWN', 'BEFORE', $this, 'handleProsemirrorSwitchToText'); $helper = $this->getHelper(); if ($helper && $helper->isExtranetRequest()) { $controller->register_hook('ACTION_ACT_PREPROCESS', 'BEFORE', $this, 'blockConfiguredActions'); $controller->register_hook('AUTH_LOGIN_CHECK', 'AFTER', $this, 'disableActions'); $controller->register_hook('PARSER_CACHE_USE', 'BEFORE', $this, 'createExtranetCache'); $controller->register_hook('IO_WIKIPAGE_READ', 'AFTER', $this, 'displayHideMessageIfRestricted'); $controller->register_hook('FETCH_MEDIA_STATUS', 'BEFORE', $this, 'hideMediaIfRestricted'); } } public function handleRenderForProsemirror(Doku_Event $event, $param): void { $data = $event->data; $name = strtolower(trim((string)($data['name'] ?? ''))); if ($name !== 'extranet') return; $match = trim((string)($data['match'] ?? '')); if (!preg_match('/^~~\s*(NOEXTRANET|EXTRANET)\s*~~$/i', $match, $matches)) return; $renderer = $data['renderer'] ?? null; if (!is_object($renderer) || !isset($renderer->nodestack) || !method_exists($renderer->nodestack, 'getDocNode')) { return; } $macro = strtoupper($matches[1]); $docNode = $renderer->nodestack->getDocNode(); if ($macro === 'NOEXTRANET') { $docNode->attr('noextranet', true); $docNode->attr('extranet', false); } else { $docNode->attr('extranet', true); $docNode->attr('noextranet', false); } $event->preventDefault(); $event->stopPropagation(); } public function addProsemirrorPolyfillAsset(Doku_Event $event, $param): void { global $ACT; if (!in_array((string)$ACT, ['edit', 'preview'], true)) return; if (defined('DOKUWIKI_PM_FILE_STATE_POLYFILL_INCLUDED')) return; if (empty($event->data) || !is_array($event->data)) return; define('DOKUWIKI_PM_FILE_STATE_POLYFILL_INCLUDED', 1); $path = DOKU_INC . 'lib/plugins/extranet/script/prosemirror_file_state_polyfill.js'; $version = @filemtime($path) ?: time(); $event->data['script'][] = [ 'type' => 'text/javascript', 'src' => DOKU_BASE . 'lib/plugins/extranet/script/prosemirror_file_state_polyfill.js?v=' . rawurlencode((string)$version), '_data' => '', 'defer' => 'defer', ]; } public function handleProsemirrorSwitchToText(Doku_Event $event, $param): void { global $INPUT, $ID; if ($event->data !== 'plugin_prosemirror_switch_editors') return; if ($INPUT->bool('getJSON')) return; $json = (string)$INPUT->str('data'); if ($json === '') return; $ID = $INPUT->str('id'); /** @var helper_plugin_prosemirror $helper */ $helper = plugin_load('helper', 'prosemirror'); if (!$helper) return; try { $syntax = $helper->getSyntaxFromProsemirrorData($json); } catch (Throwable $e) { return; } $macroState = $this->extractMacroStateFromJson($json); if ($macroState === null) { $macroState = $this->extractMacroStateFromSyntax($syntax); } $syntax = $this->applyMacroStateToSyntax($syntax, $macroState); $event->preventDefault(); $event->stopPropagation(); header('Content-Type: application/json'); echo json_encode(['text' => $syntax]); } public function syncTextFromProsemirrorState(Doku_Event $event, $param): void { global $INPUT, $TEXT; if ($INPUT->server->str('REQUEST_METHOD') !== 'POST') return; if (!in_array((string)$event->data, ['save', 'preview'], true)) return; if (!$INPUT->post->has('prosemirror_json')) return; if (!get_doku_pref('plugin_prosemirror_useWYSIWYG', false)) return; $macroState = $this->extractMacroStateFromJson((string)$INPUT->post->str('prosemirror_json')); if ($macroState === null) { $macroState = $this->extractMacroStateFromSyntax((string)$TEXT); } $TEXT = $this->applyMacroStateToSyntax((string)$TEXT, $macroState); } public function syncMacroFromProsemirrorStateBeforeWrite(Doku_Event $event, $param): void { global $INPUT; if (!$INPUT->post->has('prosemirror_json')) return; $macroState = $this->extractMacroStateFromJson((string)$INPUT->post->str('prosemirror_json')); $helper = $this->getHelper(); $defaultPolicy = $helper ? $helper->getDefaultPolicy() : 'allow'; if ($macroState === null && !in_array($defaultPolicy, ['force_allow', 'force_block'], true)) return; if (!isset($event->data[0]) || !is_array($event->data[0]) || !isset($event->data[0][1])) return; $event->data[0][1] = $this->applyMacroStateToSyntax((string)$event->data[0][1], $macroState); } protected function extractMacroStateFromJson(string $json): ?array { if ($json === '') return null; $data = json_decode($json, true); if (!is_array($data)) return null; $attrs = $data['attrs'] ?? null; if (!is_array($attrs)) return null; $hasNoExtranetAttr = array_key_exists('noextranet', $attrs); $hasExtranetAttr = array_key_exists('extranet', $attrs); if (!$hasNoExtranetAttr && !$hasExtranetAttr) return null; return [ 'noextranet' => $hasNoExtranetAttr ? (bool)$attrs['noextranet'] : false, 'extranet' => $hasExtranetAttr ? (bool)$attrs['extranet'] : false, ]; } protected function applyMacroStateToSyntax(string $content, ?array $macroState): string { $content = preg_replace('/^\h*~~\h*(NOEXTRANET|EXTRANET)\h*~~\h*$(\R)?/imu', '', $content); $content = rtrim((string)$content); $helper = $this->getHelper(); $defaultPolicy = $helper ? $helper->getDefaultPolicy() : 'allow'; if ($macroState === null || in_array($defaultPolicy, ['force_allow', 'force_block'], true)) { return $content !== '' ? $content . "\n" : ''; } if (!empty($macroState['noextranet'])) { $content .= "\n\n~~NOEXTRANET~~\n"; } elseif (!empty($macroState['extranet'])) { $content .= "\n\n~~EXTRANET~~\n"; } elseif ($content !== '') { $content .= "\n"; } return $content; } protected function extractMacroStateFromSyntax(string $content): ?array { $hasNoExtranet = (bool)preg_match('/~~\s*NOEXTRANET\s*~~/i', $content); $hasExtranet = (bool)preg_match('/~~\s*EXTRANET\s*~~/i', $content); if (!$hasNoExtranet && !$hasExtranet) { return null; } return [ 'noextranet' => $hasNoExtranet, 'extranet' => $hasExtranet, ]; } public function injectJsInfo(Doku_Event $event, $param): void { global $JSINFO; $helper = $this->getHelper(); $mode = $helper ? $helper->getDefaultPolicy() : 'allow'; $JSINFO['plugin_extranet_default_mode'] = $mode; $JSINFO['plugin_extranet_label_noextranet'] = (string)$this->getLang('label_noextranet'); $JSINFO['plugin_extranet_label_extranet'] = (string)$this->getLang('label_extranet'); } protected function isConfiguredActionDisabled(string $actionName): bool { $actionName = strtolower(trim($actionName)); if ($actionName === '') return false; $helper = $this->getHelper(); $actions = $helper ? $helper->parseRuleList($this->getConf('disable_actions')) : []; $actions = array_map(static function ($action) { return strtolower(trim((string)$action)); }, $actions); return in_array($actionName, $actions, true); } protected function isRestrictedPageActionDisabled(string $actionName): bool { $actionName = strtolower(trim($actionName)); if ($actionName === '') return false; $helper = $this->getHelper(); $actions = $helper ? $helper->parseRuleList($this->getConf('restricted_disable_actions')) : []; $actions = array_map(static function ($action) { return strtolower(trim((string)$action)); }, $actions); if (!in_array($actionName, $actions, true)) return false; global $ID; return $helper && !$helper->isPageAllowed((string)$ID); } public function blockConfiguredActions(Doku_Event $event, $param): void { $actionName = (string)$event->data; if ($this->isConfiguredActionDisabled($actionName) || $this->isRestrictedPageActionDisabled($actionName)) { throw new ActionDisabledException(); } } protected function isContentRestrictedFromExtranet(string $content): bool { global $ID; $helper = $this->getHelper(); if (!$helper) return false; return !$helper->isPageVisibleFromExtranet((string)$ID, $content); } protected function isMediaRestrictedFromExtranet(string $media): bool { $helper = $this->getHelper(); if (!$helper) return false; return !$helper->isMediaAllowed($media); } public function disableActions(Doku_Event $event, $param): void { if (!empty($this->getConf('disable_actions'))) { global $conf; $conf['disableactions'] = (!empty($conf['disableactions']) ? $conf['disableactions'] . ',' : '') . $this->getConf('disable_actions'); } } public function createExtranetCache(Doku_Event $event, $param): void { $cache = $event->data; $cache->key .= '#extranet'; $cache->cache = getCacheName($cache->key, $cache->ext); } public function displayHideMessageIfRestricted(Doku_Event $event, $param): void { if (!$this->isContentRestrictedFromExtranet((string)$event->result)) return; $result = ''; if ($this->getConf('preserve_first_title')) { $titlePattern = '/(?:^|\v)(={2,6}.+={2,})(?:\v|$)/'; preg_match($titlePattern, $event->result, $matches); if (!empty($matches[0])) { $result .= $matches[0] . "\r\n"; } } $result .= $this->getConf('message_prefix') . $this->getLang('hidden_message') . $this->getConf('message_suffix'); $event->result = $result; } public function hideMediaIfRestricted(Doku_Event $event, $param): void { $hideFilesMode = $this->getHideFilesMode(); if ($hideFilesMode === 'none') return; $mediaID = (string)($event->data['media'] ?? ''); if ($mediaID === '') return; if ($hideFilesMode === 'except_pageicons' && $this->isPagesIconMedia($mediaID)) return; if (!$this->isMediaRestrictedFromExtranet($mediaID)) return; $event->data['file'] = dirname(__FILE__) . '/images/restricted.png'; $event->data['orig'] = $event->data['file']; $event->data['status'] = 200; $event->data['statusmessage'] = 'OK'; $event->data['mime'] = 'image/png'; $event->data['download'] = false; $event->data['cache'] = false; $event->data['ispublic'] = false; } protected function getHideFilesMode(): string { $value = $this->getConf('hide_files'); if ($value === true || $value === 1 || $value === '1') return 'all'; if ($value === false || $value === 0 || $value === '0' || $value === '') return 'none'; $value = strtolower(trim((string)$value)); if (!in_array($value, ['all', 'except_pageicons', 'none'], true)) { return 'none'; } return $value; } protected function isPagesIconMedia(string $mediaID): bool { $mediaID = cleanID($mediaID); if ($mediaID === '') return false; /** @var helper_plugin_pagesicon|null $helper */ $helper = plugin_load('helper', 'pagesicon'); if (!$helper || !method_exists($helper, 'isPageIconMedia')) return false; return (bool)$helper->isPageIconMedia($mediaID); } }