register_hook( 'PLUGIN_USERSETTINGS_REGISTER', 'BEFORE', $this, 'handleSettingsRegister' ); // Inject annotation stats + user preference into JSINFO. $controller->register_hook( 'TPL_METAHEADER_OUTPUT', 'BEFORE', $this, 'handleMetaHeader' ); // Handle the AJAX call. $controller->register_hook( 'AJAX_CALL_UNKNOWN', 'BEFORE', $this, 'handleAjax' ); } // ------------------------------------------------------------------ // 1. usersettings toggle registration // ------------------------------------------------------------------ /** * Append the annotations_enabled toggle definition to the event data. * * The event data is an array that the usersettings helper fires with * createAndTrigger(); every handler appends its definition(s). * * @param Doku_Event $event PLUGIN_USERSETTINGS_REGISTER * @param mixed $param */ public function handleSettingsRegister(Doku_Event $event, $param) { $event->data[] = [ 'key' => 'annotations_enabled', 'label' => $this->getLang('toggle_label'), 'desc' => $this->getLang('toggle_desc'), 'type' => 'checkbox', 'default' => true, 'plugin' => 'annotations', ]; } // ------------------------------------------------------------------ // 2. Inject into JSINFO // ------------------------------------------------------------------ /** * Add annotation stats and the user preference to JSINFO so script.js * does not need an extra round-trip on page load. * * IMPORTANT: tpl_metaheaders() calls jsinfo() and then immediately * JSON-encodes $JSINFO into an inline " would otherwise close the script element and // inject arbitrary HTML — a stored XSS reachable by anyone who can // annotate. HEX_TAG neutralises every tag-based breakout. $payload = json_encode($data, JSON_HEX_TAG | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE); // The inline script block containing "var JSINFO = ...;" is in // $event->data['script']. Find it and append our assignment so it // runs in the same scope after JSINFO is already declared. if (!empty($event->data['script'])) { foreach ($event->data['script'] as &$scriptTag) { if ( isset($scriptTag['_data']) && strpos($scriptTag['_data'], 'var JSINFO') !== false ) { $scriptTag['_data'] .= 'JSINFO.annotations=' . $payload . ';'; break; } } unset($scriptTag); } } /** * Append a