summaryrefslogtreecommitdiff
path: root/platform/www/lib/plugins/refnotes/action.php
diff options
context:
space:
mode:
Diffstat (limited to 'platform/www/lib/plugins/refnotes/action.php')
-rw-r--r--platform/www/lib/plugins/refnotes/action.php634
1 files changed, 634 insertions, 0 deletions
diff --git a/platform/www/lib/plugins/refnotes/action.php b/platform/www/lib/plugins/refnotes/action.php
new file mode 100644
index 0000000..6f6af89
--- /dev/null
+++ b/platform/www/lib/plugins/refnotes/action.php
@@ -0,0 +1,634 @@
+<?php
+
+/**
+ * Plugin RefNotes: Event handler
+ *
+ * @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
+ * @author Mykola Ostrovskyy <dwpforge@gmail.com>
+ */
+
+require_once(DOKU_PLUGIN . 'refnotes/core.php');
+require_once(DOKU_PLUGIN . 'refnotes/instructions.php');
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+class action_plugin_refnotes extends DokuWiki_Action_Plugin {
+ use refnotes_localization_plugin;
+
+ private $afterParserHandlerDone;
+ private $beforeAjaxCallUnknown;
+ private $beforeParserCacheUse;
+ private $beforeParserWikitextPreprocess;
+ private $beforeTplMetaheaderOutput;
+
+ /**
+ * Constructor
+ */
+ public function __construct() {
+ refnotes_localization::initialize($this);
+
+ $this->afterParserHandlerDone = new refnotes_after_parser_handler_done();
+ $this->beforeAjaxCallUnknown = new refnotes_before_ajax_call_unknown();
+ $this->beforeParserCacheUse = new refnotes_before_parser_cache_use();
+ $this->beforeParserWikitextPreprocess = new refnotes_before_parser_wikitext_preprocess();
+ $this->beforeTplMetaheaderOutput = new refnotes_before_tpl_metaheader_output();
+ }
+
+ /**
+ * Register callbacks
+ */
+ public function register(Doku_Event_Handler $controller) {
+ $this->afterParserHandlerDone->register($controller);
+ $this->beforeAjaxCallUnknown->register($controller);
+ $this->beforeParserCacheUse->register($controller);
+ $this->beforeParserWikitextPreprocess->register($controller);
+ $this->beforeTplMetaheaderOutput->register($controller);
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+class refnotes_after_parser_handler_done {
+
+ /**
+ * Register callback
+ */
+ public function register($controller) {
+ $controller->register_hook('PARSER_HANDLER_DONE', 'AFTER', $this, 'handle');
+ }
+
+ /**
+ *
+ */
+ public function handle($event, $param) {
+ refnotes_parser_core::getInstance()->exitParsingContext($event->data);
+
+ /* We need a new instance of mangler for each event because we can trigger it recursively
+ * by loading reference database or by parsing structured notes.
+ */
+ $mangler = new refnotes_instruction_mangler($event);
+
+ $mangler->process();
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+class refnotes_instruction_mangler {
+
+ private $core;
+ private $calls;
+ private $paragraphReferences;
+ private $referenceGroup;
+ private $hidden;
+ private $inReference;
+
+ /**
+ * Constructor
+ */
+ public function __construct($event) {
+ $this->core = new refnotes_action_core();
+ $this->calls = new refnotes_instruction_list($event);
+ $this->paragraphReferences = array();
+ $this->referenceGroup = array();
+ $this->hidden = true;
+ $this->inReference = false;
+ }
+
+ /**
+ *
+ */
+ public function process() {
+ $this->scanInstructions();
+
+ if ($this->core->getNamespaceCount() > 0) {
+ $this->insertNotesInstructions($this->core->getStyles(), 'refnotes_notes_style_instruction');
+ $this->insertNotesInstructions($this->core->getMappings(), 'refnotes_notes_map_instruction');
+ $this->renderLeftovers();
+
+ $this->calls->applyChanges();
+
+ $this->renderStructuredNotes();
+
+ $this->calls->applyChanges();
+ }
+ }
+
+ /**
+ *
+ */
+ private function scanInstructions() {
+ foreach ($this->calls as $call) {
+ $this->markHiddenReferences($call);
+ $this->markReferenceGroups($call);
+ $this->markScopeLimits($call);
+ $this->extractStyles($call);
+ $this->extractMappings($call);
+ }
+ }
+
+ /**
+ *
+ */
+ private function markHiddenReferences($call) {
+ switch ($call->getName()) {
+ case 'p_open':
+ $this->paragraphReferences = array();
+ $this->hidden = true;
+ break;
+
+ case 'p_close':
+ if ($this->hidden) {
+ foreach ($this->paragraphReferences as $call) {
+ $call->setRefnotesAttribute('hidden', true);
+ }
+ }
+ break;
+
+ case 'cdata':
+ if (!$this->inReference && !empty(trim($call->getData(0)))) {
+ $this->hidden = false;
+ }
+ break;
+
+ case 'plugin_refnotes_references':
+ switch ($call->getPluginData(0)) {
+ case 'start':
+ $this->inReference = true;
+ break;
+
+ case 'render':
+ $this->inReference = false;
+ $this->paragraphReferences[] = $call;
+ break;
+ }
+ break;
+
+ default:
+ if (!$this->inReference) {
+ $this->hidden = false;
+ }
+ break;
+ }
+ }
+
+ /**
+ *
+ */
+ private function markReferenceGroups($call) {
+ if (($call->getName() == 'plugin_refnotes_references') && ($call->getPluginData(0) == 'render')) {
+ if (!empty($this->referenceGroup)) {
+ $groupNamespace = $this->referenceGroup[0]->getRefnotesAttribute('ns');
+
+ if ($call->getRefnotesAttribute('ns') != $groupNamespace) {
+ $this->closeReferenceGroup();
+ }
+ }
+
+ $this->referenceGroup[] = $call;
+ }
+ elseif (!$this->inReference && !empty($this->referenceGroup)) {
+ // Allow whitespace "cdata" istructions between references in a group
+ if ($call->getName() == 'cdata' && empty(trim($call->getData(0)))) {
+ return;
+ }
+
+ $this->closeReferenceGroup();
+ }
+ }
+
+ /**
+ *
+ */
+ private function closeReferenceGroup() {
+ $count = count($this->referenceGroup);
+
+ if ($count > 1) {
+ $this->referenceGroup[0]->setRefnotesAttribute('group', 'open');
+
+ for ($i = 1; $i < $count - 1; $i++) {
+ $this->referenceGroup[$i]->setRefnotesAttribute('group', 'hold');
+ }
+
+ $this->referenceGroup[$count - 1]->setRefnotesAttribute('group', 'close');
+ }
+
+ $this->referenceGroup = array();
+ }
+
+ /**
+ *
+ */
+ private function markScopeLimits($call) {
+ switch ($call->getName()) {
+ case 'plugin_refnotes_references':
+ if ($call->getPluginData(0) == 'render') {
+ $this->core->markScopeStart($call->getRefnotesAttribute('ns'), $call->getIndex());
+ }
+ break;
+
+ case 'plugin_refnotes_notes':
+ $this->core->markScopeEnd($call->getRefnotesAttribute('ns'), $call->getIndex());
+ break;
+ }
+ }
+
+ /**
+ * Extract style data and replace "split" instructions with "render"
+ */
+ private function extractStyles($call) {
+ if (($call->getName() == 'plugin_refnotes_notes') && ($call->getPluginData(0) == 'split')) {
+ $this->core->addStyle($call->getRefnotesAttribute('ns'), $call->getPluginData(2));
+
+ $call->setPluginData(0, 'render');
+ $call->unsetPluginData(2);
+ }
+ }
+
+ /**
+ * Extract namespace mapping info
+ */
+ private function extractMappings($call) {
+ if ($call->getName() == 'plugin_refnotes_notes') {
+ $map = $call->getRefnotesAttribute('map');
+
+ if (!empty($map)) {
+ $this->core->addMapping($call->getRefnotesAttribute('ns'), $map);
+ $call->unsetRefnotesAttribute('map');
+ }
+ }
+ }
+
+ /**
+ *
+ */
+ private function insertNotesInstructions($stash, $instruction) {
+ if ($stash->getCount() == 0) {
+ return;
+ }
+
+ $stash->sort();
+
+ foreach ($stash->getIndex() as $index) {
+ foreach ($stash->getAt($index) as $data) {
+ $this->calls->insert($index, new $instruction($data->getNamespace(), $data->getData()));
+ }
+ }
+ }
+
+ /**
+ * Insert render call at the very bottom of the page
+ */
+ private function renderLeftovers() {
+ $this->calls->append(new refnotes_notes_render_instruction('*'));
+ }
+
+ /**
+ *
+ */
+ private function renderStructuredNotes() {
+ $this->core->reset();
+
+ foreach ($this->calls as $call) {
+ $this->styleNamespaces($call);
+ $this->setNamespaceMappings($call);
+ $this->addReferences($call);
+ $this->rewriteReferences($call);
+ }
+ }
+
+ /**
+ *
+ */
+ private function styleNamespaces($call) {
+ if (($call->getName() == 'plugin_refnotes_notes') && ($call->getPluginData(0) == 'style')) {
+ $this->core->styleNamespace($call->getRefnotesAttribute('ns'), $call->getPluginData(2));
+ }
+ }
+
+ /**
+ *
+ */
+ private function setNamespaceMappings($call) {
+ if (($call->getName() == 'plugin_refnotes_notes') && ($call->getPluginData(0) == 'map')) {
+ $this->core->setNamespaceMapping($call->getRefnotesAttribute('ns'), $call->getPluginData(2));
+ }
+ }
+
+ /**
+ *
+ */
+ private function addReferences($call) {
+ if (($call->getName() == 'plugin_refnotes_references') && ($call->getPluginData(0) == 'render')) {
+ $attributes = $call->getPluginData(1);
+ $data = (count($call->getData(1)) > 2) ? $call->getPluginData(2) : array();
+ $reference = $this->core->addReference($attributes, $data, $call);
+
+ if ($call->getPrevious()->getName() != 'plugin_refnotes_references') {
+ $reference->getNote()->setText('defined');
+ }
+ }
+ }
+
+ /**
+ *
+ */
+ private function rewriteReferences($call) {
+ if (($call->getName() == 'plugin_refnotes_notes') && ($call->getPluginData(0) == 'render')) {
+ $this->core->rewriteReferences($call->getRefnotesAttribute('ns'), $call->getRefnotesAttribute('limit'));
+ }
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+class refnotes_before_ajax_call_unknown {
+
+ /**
+ * Register callback
+ */
+ public function register($controller) {
+ $controller->register_hook('AJAX_CALL_UNKNOWN', 'BEFORE', $this, 'handle');
+ }
+
+ /**
+ *
+ */
+ public function handle($event, $param) {
+ global $conf;
+
+ if ($event->data == 'refnotes-admin') {
+ $event->preventDefault();
+ $event->stopPropagation();
+
+ /* Check admin rights */
+ if (auth_quickaclcheck($conf['start']) < AUTH_ADMIN) {
+ die('access denied');
+ }
+
+ switch ($_POST['action']) {
+ case 'load-settings':
+ $this->sendConfig();
+ break;
+
+ case 'save-settings':
+ $this->saveConfig($_POST['settings']);
+ break;
+ }
+ }
+ }
+
+ /**
+ *
+ */
+ private function sendResponse($contentType, $data) {
+ static $cookie = '{B27067E9-3DDA-4E31-9768-E66F23D18F4A}';
+
+ header('Content-Type: ' . $contentType);
+ print($cookie . $data . $cookie);
+ }
+
+ /**
+ *
+ */
+ private function sendConfig() {
+ $namespace = refnotes_configuration::load('namespaces');
+ $namespace = $this->translateStyles($namespace, 'dw', 'js');
+
+ $config['general'] = refnotes_configuration::load('general');
+ $config['namespaces'] = $namespace;
+ $config['notes'] = refnotes_configuration::load('notes');
+
+ $this->sendResponse('application/x-suggestions+json', json_encode($config));
+ }
+
+ /**
+ *
+ */
+ private function saveConfig($config) {
+ global $config_cascade;
+
+ $config = json_decode($config, true);
+
+ $namespace = $config['namespaces'];
+ $namespace = $this->translateStyles($namespace, 'js', 'dw');
+
+ $saved = refnotes_configuration::save('general', $config['general']);
+ $saved = $saved && refnotes_configuration::save('namespaces', $namespace);
+ $saved = $saved && refnotes_configuration::save('notes', $config['notes']);
+
+ if ($config['general']['reference-db-enable']) {
+ $saved = $saved && $this->setupReferenceDatabase($config['general']['reference-db-namespace']);
+ }
+
+ /* Touch local config file to expire the cache */
+ $saved = $saved && touch(reset($config_cascade['main']['local']));
+
+ $this->sendResponse('text/plain', $saved ? 'saved' : 'failed');
+ }
+
+ /**
+ *
+ */
+ private function translateStyles($namespace, $from, $to) {
+ foreach ($namespace as &$ns) {
+ foreach ($ns as $styleName => &$style) {
+ $style = $this->translateStyle($styleName, $style, $from, $to);
+ }
+ }
+
+ return $namespace;
+ }
+
+ /**
+ *
+ */
+ private function translateStyle($styleName, $style, $from, $to) {
+ static $dictionary = array(
+ 'refnote-id' => array(
+ 'dw' => array('1' , 'a' , 'A' , 'i' , 'I' , '*' , 'name' ),
+ 'js' => array('numeric', 'latin-lower', 'latin-upper', 'roman-lower', 'roman-upper', 'stars', 'note-name')
+ ),
+ 'reference-base' => array(
+ 'dw' => array('sup' , 'text' ),
+ 'js' => array('super', 'normal-text')
+ ),
+ 'reference-format' => array(
+ 'dw' => array(')' , '()' , ']' , '[]' ),
+ 'js' => array('right-parent', 'parents', 'right-bracket', 'brackets')
+ ),
+ 'reference-group' => array(
+ 'dw' => array('none' , ',' , 's' ),
+ 'js' => array('group-none', 'group-comma', 'group-semicolon')
+ ),
+ 'multi-ref-id' => array(
+ 'dw' => array('ref' , 'note' ),
+ 'js' => array('ref-counter', 'note-counter')
+ ),
+ 'note-id-base' => array(
+ 'dw' => array('sup' , 'text' ),
+ 'js' => array('super', 'normal-text')
+ ),
+ 'note-id-format' => array(
+ 'dw' => array(')' , '()' , ']' , '[]' , '.' ),
+ 'js' => array('right-parent', 'parents', 'right-bracket', 'brackets', 'dot')
+ ),
+ 'back-ref-base' => array(
+ 'dw' => array('sup' , 'text' ),
+ 'js' => array('super', 'normal-text')
+ ),
+ 'back-ref-format' => array(
+ 'dw' => array('1' , 'a' , 'note' ),
+ 'js' => array('numeric', 'latin', 'note-id')
+ ),
+ 'back-ref-separator' => array(
+ 'dw' => array(',' ),
+ 'js' => array('comma')
+ ),
+ 'struct-refs' => array(
+ 'dw' => array('off' , 'on' ),
+ 'js' => array('disable', 'enable')
+ )
+ );
+
+ if (array_key_exists($styleName, $dictionary)) {
+ $key = array_search($style, $dictionary[$styleName][$from]);
+
+ if ($key !== false) {
+ $style = $dictionary[$styleName][$to][$key];
+ }
+ }
+
+ return $style;
+ }
+
+ /**
+ *
+ */
+ private function setupReferenceDatabase($namespace) {
+ $success = true;
+ $source = refnotes_localization::getInstance()->getFileName('__template');
+ $destination = wikiFN(cleanID($namespace . ':template'));
+ $destination = preg_replace('/template.txt$/', '__template.txt', $destination);
+
+ if (@filemtime($destination) < @filemtime($source)) {
+ if (!file_exists(dirname($destination))) {
+ @mkdir(dirname($destination), 0755, true);
+ }
+
+ $success = copy($source, $destination);
+
+ touch($destination, filemtime($source));
+ }
+
+ return $success;
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+class refnotes_before_parser_cache_use {
+
+ /**
+ * Register callback
+ */
+ public function register($controller) {
+ $controller->register_hook('PARSER_CACHE_USE', 'BEFORE', $this, 'handle');
+ }
+
+ /**
+ *
+ */
+ public function handle($event, $param) {
+ global $ID;
+
+ $cache = $event->data;
+
+ if (isset($cache->page) && ($cache->page == $ID)) {
+ if (isset($cache->mode) && (($cache->mode == 'xhtml') || ($cache->mode == 'i'))) {
+ $meta = p_get_metadata($ID, 'plugin refnotes');
+
+ if (!empty($meta) && isset($meta['dbref'])) {
+ $this->addDependencies($cache, array_keys($meta['dbref']));
+ }
+ }
+ }
+ }
+
+ /**
+ * Add extra dependencies to the cache
+ */
+ private function addDependencies($cache, $depends) {
+ foreach ($depends as $file) {
+ if (!in_array($file, $cache->depends['files']) && file_exists($file)) {
+ $cache->depends['files'][] = $file;
+ }
+ }
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+class refnotes_before_parser_wikitext_preprocess {
+
+ /**
+ * Register callback
+ */
+ public function register($controller) {
+ $controller->register_hook('PARSER_WIKITEXT_PREPROCESS', 'BEFORE', $this, 'handle');
+ }
+
+ /**
+ *
+ */
+ public function handle($event, $param) {
+ refnotes_parser_core::getInstance()->enterParsingContext();
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+class refnotes_before_tpl_metaheader_output {
+
+ /**
+ * Register callback
+ */
+ public function register($controller) {
+ $controller->register_hook('TPL_METAHEADER_OUTPUT', 'BEFORE', $this, 'handle');
+ }
+
+ /**
+ *
+ */
+ public function handle($event, $param) {
+ if (!empty($_REQUEST['do']) && $_REQUEST['do'] == 'admin' &&
+ !empty($_REQUEST['page']) && $_REQUEST['page'] == 'refnotes') {
+ $this->addAdminIncludes($event);
+ }
+ }
+
+ /**
+ *
+ */
+ private function addAdminIncludes($event) {
+ $this->addTemplateHeaderInclude($event, 'admin.js');
+ $this->addTemplateHeaderInclude($event, 'admin.css');
+ }
+
+ /**
+ *
+ */
+ private function addTemplateHeaderInclude($event, $fileName) {
+ $type = '';
+ $fileName = DOKU_BASE . 'lib/plugins/refnotes/' . $fileName;
+
+ switch (pathinfo($fileName, PATHINFO_EXTENSION)) {
+ case 'js':
+ $type = 'script';
+ $data = array('type' => 'text/javascript', 'charset' => 'utf-8', 'src' => $fileName, '_data' => '', 'defer' => 'defer');
+ break;
+
+ case 'css':
+ $type = 'link';
+ $data = array('type' => 'text/css', 'rel' => 'stylesheet', 'href' => $fileName);
+ break;
+ }
+
+ if ($type != '') {
+ $event->data[$type][] = $data;
+ }
+ }
+}