* @author Stéphane Aulery * * Online Resources: * https://www.dokuwiki.org/plugin:wikindx * https://wikindx.sourceforge.io/web/trunk/interfacing/cms/ * http://www.commontology.de/polphil/literatur/einbindung_der_literaturdatenbank (broken link) * * CHANGELOG: * * 2024-01-27: Finish v2 of the plugin for WIKINDX 6.6.0 and higher * Compatible with: * - Jack Jackrum+ * - Igor * - Hogfather * * 2021-10-12: Finish v1 of the plugin for WIKINDX 6.6.0 and higher * Compatible with: * - Hogfather * * 2013-01-16: Update for dokuwiki Release 2012-10-13 "Adora Belle" and * wikindx v4.1. * (wikindx4's index.php provides the following actions out * of the box: getResource, getCategory, getSubcategory, * getKeyword, getCreator, getPublisher, getCollection, * getRecent. * Optional parameters are: * limit, days, order, sqlMethod(=and), bibstyle) * New functionality: I have added these actions: * getAbstract, getNotes, getQuote, getParaphr, getMusing. * * 2013-01-21: Update to take advantage of wikindx4 svn changes rather than * patch it myself (patch hence no longer needed). * * TODO: * + Provide switch to not insert a citation into the tracking array * + Allow to put bibliography before all the citations? * * */ use dokuwiki\HTTP\DokuHTTPClient; /* have an array ready to keep track of all cited works on the page */ global $WKX_USED_IDS; /** * All DokuWiki plugins to extend the parser/rendering mechanism * need to inherit from this class */ class syntax_plugin_wikindx extends \dokuwiki\Extension\SyntaxPlugin { /** * Syntax Type * * Needs to return one of the mode types defined in $PARSER_MODES in Parser.php * * @return string */ public function getType() { return "container"; } /** * Where to sort in? */ public function getSort() { // Just after internal link mode return 301; } /** * Connect pattern to lexer */ function connectTo($mode) { // Grab everything between {{wxcite> and }} (resp. grab {{wxbib}}) $this->Lexer->addSpecialPattern( '\{\{(?:' . 'wxbib|' . 'wxabstract>[^}]*?|' . 'wxcite>[^}]*?|' . 'wxmusing>[^}]*?|' . 'wxnotes>[^}]*?|' . 'wxparaphrase>[^}]*?|' . 'wxquote>[^}]*?|' . ')\}\}', $mode, 'plugin_wikindx' ); } /** * Allowed Mode Types * * Defines the mode types for other dokuwiki markup that maybe nested within the * plugin's own markup. Needs to return an array of one or more of the mode types * defined in $PARSER_MODES in Parser.php * * @return array */ function getAllowedTypes() { return [ "container", "formatting", "substition", ]; } /** * Handler to prepare matched data for the rendering process * * This function can only pass data to render() via its return value - render() * may be not be run during the object's current life. * * Usually you should only need the $match param. * * @param string $match The text matched by the patterns * @param int $state The lexer state for the match * @param int $pos The character position of the matched text * @param Doku_Handler $handler The Doku_Handler object * @return bool|array Return an array with all data you want to use in render, false don't add an instruction */ public function handle($match, $state, $pos, Doku_Handler $handler) { global $WKX_USED_IDS; if ($match == '{{wxbib}}') { $callingmodus = "wxbib"; $ids = array_unique($WKX_USED_IDS); } else { // Remove {{ from start // Remove }} from end // E.g. {{wxcite>128}} => wxcite>128 $match_without_bracket = substr($match, strlen("{{"), -1 * strlen("}}")); // Cut on first ">" character into callingmodus and data parts $match_parts = explode(">", $match_without_bracket); $callingmodus = $match_parts[0]; // E.g. wxcite, wxmusing ... $data = trim($match_parts[1]); $data = trim($data, ";"); //remove trailing ; // Extract ids from data $ids = explode(";", $data); } //Lookup data $http = new DokuHTTPClient(); foreach ($ids as $id) { $iex = explode(':', $id); $resId = $iex[0] ?? NULL; $page = $iex[1] ?? NULL; if ($callingmodus != "wxbib") { $WKX_USED_IDS[] = $resId; } $callmode[] = $callingmodus; $resourceId[] = $resId; $resourceHtml = []; switch ($callingmodus) { case 'wxbib': global $WKX_USED_IDS; $citetext = ""; foreach ($WKX_USED_IDS as $rid) { $citetext .= "[cite]" . $resId . "[/cite]\n"; } $aResponse = $this->WikindxparseText($resId, $this->getConf('url'), $citetext); // Search for the bibliograpy which is the last block $cite_blocks = $this->mb_explode("

", $aResponse["text"] ?? ""); if (count($cite_blocks) == 3) $bibliography = $cite_blocks[2]; elseif (count($cite_blocks) == 2) $bibliography = $cite_blocks[1]; else $bibliography = ""; $resourceHtml[] = $bibliography; break; case 'wxabstract': $queryUrl = rtrim($this->getConf('url'), "/") . "/cmsprint.php?action=getAbstract&bibStyle=" . $this->getConf('bibStyle') . "&id=" . $resId; $aResponse = $this->_CMSPHPResponse2Array($http->get($queryUrl)); foreach ($aResponse as $rid => $item) { $resourceHtml[] = $item; } break; case 'wxcite': default: $aResponse = $this->WikindxparseText($resId, $this->getConf('url'), "[cite]" . rtrim($resId . "|" . $page, "|") . "[/cite]"); // Search for the citation which is the first block in regulat style, // and the second block for footnote style $cite_blocks = $this->mb_explode("

", $aResponse["text"] ?? ""); if (count($cite_blocks) == 3) { $citation = $cite_blocks[1]; } elseif (count($cite_blocks) == 2) { $citation = ''; $citation .= trim($cite_blocks[0]); $citation .= ''; } else { $citation = ""; } $resourceHtml[] = $citation; break; case 'wxmusing': $queryUrl = rtrim($this->getConf('url'), "/") . "/cmsprint.php?action=getMusing&bibStyle=" . $this->getConf('bibStyle') . "&id=" . $resId; $aResponse = $this->_CMSPHPResponse2Array($http->get($queryUrl)); if (!$page) $page = 1; else $page = $page; if (array_key_exists(intval($resId), $aResponse)) { $musings = $aResponse[intval($resId)]; $n = 0; foreach ($musings as $key => $musing) { $n++; if ($page == $n) { $resourceHtml[] = $musing; break; } } } break; case 'wxnotes': $queryUrl = rtrim($this->getConf('url'), "/") . "/cmsprint.php?action=getNotes&bibStyle=" . $this->getConf('bibStyle') . "&id=" . $resId; $aResponse = $this->_CMSPHPResponse2Array($http->get($queryUrl)); foreach ($aResponse as $rid => $item) { $resourceHtml[] = $item; } break; case 'wxparaphrase': $queryUrl = rtrim($this->getConf('url'), "/") . "/cmsprint.php?action=getParaphrase&bibStyle=" . $this->getConf('bibStyle') . "&id=" . $resId; $aResponse = $this->_CMSPHPResponse2Array($http->get($queryUrl)); if (!$page) $page = 1; else $page = $page; if (array_key_exists(intval($resId), $aResponse)) { $paraphrases = $aResponse[intval($resId)]; $n = 0; foreach ($paraphrases as $key => $paraphrase) { $n++; if ($page == $n) { $resourceHtml[] = $paraphrase; break; } } } break; case 'wxquote': $queryUrl = rtrim($this->getConf('url'), "/") . "/cmsprint.php?action=getQuote&bibStyle=" . $this->getConf('bibStyle') . "&id=" . $resId; $aResponse = $this->_CMSPHPResponse2Array($http->get($queryUrl)); if (!$page) $page = 1; else $page = $page; if (array_key_exists(intval($resId), $aResponse)) { $quotes = $aResponse[intval($resId)]; $n = 0; foreach ($quotes as $key => $quote) { $n++; if ($page == $n) { $resourceHtml[] = $quote; break; } } } break; } } return [$resourceId, $resourceHtml, $callmode]; } /** * Handles the actual output creation. * * The function must not assume any other of the classes methods have been run * during the object's current life. The only reliable data it receives are its * parameters. * * The function should always check for the given output format and return false * when a format isn't supported. * * $renderer contains a reference to the renderer object which is * currently handling the rendering. You need to use it for writing * the output. How this is done depends on the renderer used (specified * by $format * * The contents of the $data array depends on what the handler() function above * created * * @param string $format output format being rendered * @param Doku_Renderer $renderer the current renderer object * @param array $data data created by handler() * @return boolean rendered correctly? (however, returned value is not used at the moment) */ public function render($format, Doku_Renderer $renderer, $data) { if ($format != 'xhtml') { return false; } if (is_array($data[1])) { $output = ""; for ($i = 0; $i < count($data[1]); $i++) { $output .= $data[1][$i]; } $renderer->doc .= $output; } return true; } function WikindxparseText($resId, $baseurl, $text) { static $mem_text = []; if (!array_key_exists($text, $mem_text)) { $queryUrl = rtrim($baseurl, "/") . "/cmsprint.php?action=parseText&bibStyle=" . $this->getConf('bibStyle'); $http = new DokuHTTPClient(); $resp = $http->post($queryUrl, ["text" => $text]); $memresId[$resId] = $this->_CMSPHPResponse2Array($resp, true); } return $memresId[$resId]; } /** * Decode an object or an array serialized with PHPH serialize(), and other data type * * Return a human-readable string representing $encodedData. If the decoding fails $encodedData is returned. * * @param mixed $EncodedResponse * * @return array */ private function _CMSPHPResponse2Array($EncodedResponse) { if ($EncodedResponse !== FALSE) { $array_serialized_pattern = '/^a:\d+:{.+/u'; $object_serialized_pattern = '/^O:\d+:".+/u'; if (preg_match($array_serialized_pattern, $EncodedResponse) > 0 || preg_match($object_serialized_pattern, $EncodedResponse) > 0) { $tmp = @unserialize($EncodedResponse); if ($tmp !== FALSE) { return $tmp; } } } // Fallback on error return []; } /** * Simulate explode() for multibytes strings (as documented for PHP 7.0) * * @param string $delimiter * @param string $string * @param int $limit Default is PHP_INT_MAX. * * @return string */ function mb_explode($delimiter, $string, $limit = PHP_INT_MAX) { if ($delimiter == '') { return FALSE; } if ($limit === NULL) { PHP_INT_MAX; } if ($limit == 0) { $limit = 1; } $pattern = '/' . preg_quote($delimiter, '/') . '/u'; $aString = preg_split($pattern, $string, $limit); if ($limit < 0 && count($aString) == 1) { return []; } elseif ($limit < 0 && count($aString) > 1) { $length = count($aString) - abs($limit); if ($length <= 0) { return []; } else { return array_slice($aString, 0, $length, TRUE); } } else { return $aString; } } } //Setup VIM: ex: et ts=4 enc=utf-8 :