1<?php
2/**
3* Wikindx Citation Module
4* Derived from: Refworks Plugin by Daniel Terry, Amazon Plugin by Andreas Gohr
5*
6* @license    GPL 2 (http://www.gnu.org/licenses/gpl.html)
7* @author     Andreas Wagner <Andreas.Wagner@em.uni-frankfurt.de>
8* @author     Stéphane Aulery <lkppo@users.sourceforge.net>
9*
10* Online Resources:
11*   https://www.dokuwiki.org/plugin:wikindx
12*   https://wikindx.sourceforge.io/web/trunk/interfacing/cms/
13*   http://www.commontology.de/polphil/literatur/einbindung_der_literaturdatenbank (broken link)
14*
15* CHANGELOG:
16*
17*    2024-01-27: Finish v2 of the plugin for WIKINDX 6.6.0 and higher
18*                Compatible with:
19*                 - Jack Jackrum+
20*                 - Igor
21*                 - Hogfather
22*
23*    2021-10-12: Finish v1 of the plugin for WIKINDX 6.6.0 and higher
24*                Compatible with:
25*                 - Hogfather
26*
27*    2013-01-16: Update for dokuwiki Release 2012-10-13 "Adora Belle" and
28*                  wikindx v4.1.
29*                  (wikindx4's index.php provides the following actions out
30*                     of the box: getResource, getCategory, getSubcategory,
31*                     getKeyword, getCreator, getPublisher, getCollection,
32*                     getRecent.
33*                   Optional parameters are:
34*                     limit, days, order, sqlMethod(=and), bibstyle)
35*                New functionality: I have added these actions:
36*                   getAbstract, getNotes, getQuote, getParaphr, getMusing.
37*
38*    2013-01-21: Update to take advantage of wikindx4 svn changes rather than
39*                  patch it myself (patch hence no longer needed).
40*
41* TODO:
42*     + Provide switch to not insert a citation into the tracking array
43*     + Allow to put bibliography before all the citations?
44*
45*
46*/
47
48use dokuwiki\HTTP\DokuHTTPClient;
49
50/* have an array ready to keep track of all cited works on the page */
51global $WKX_USED_IDS;
52
53/**
54* All DokuWiki plugins to extend the parser/rendering mechanism
55* need to inherit from this class
56*/
57class syntax_plugin_wikindx extends \dokuwiki\Extension\SyntaxPlugin
58{
59    /**
60     * Syntax Type
61     *
62     * Needs to return one of the mode types defined in $PARSER_MODES in Parser.php
63     *
64     * @return string
65     */
66    public function getType()
67    {
68        return "container";
69    }
70
71    /**
72     * Where to sort in?
73     */
74    public function getSort()
75    {
76        // Just after internal link mode
77        return 301;
78    }
79
80    /**
81     * Connect pattern to lexer
82     */
83    function connectTo($mode)
84    {
85        // Grab everything between {{wxcite> and }}  (resp. grab {{wxbib}})
86        $this->Lexer->addSpecialPattern(
87            '\{\{(?:' .
88                'wxbib|' .
89                'wxabstract>[^}]*?|' .
90                'wxcite>[^}]*?|' .
91                'wxmusing>[^}]*?|' .
92                'wxnotes>[^}]*?|' .
93                'wxparaphrase>[^}]*?|' .
94                'wxquote>[^}]*?|' .
95            ')\}\}',
96            $mode,
97            'plugin_wikindx'
98        );
99    }
100
101    /**
102     * Allowed Mode Types
103     *
104     * Defines the mode types for other dokuwiki markup that maybe nested within the
105     * plugin's own markup. Needs to return an array of one or more of the mode types
106     * defined in $PARSER_MODES in Parser.php
107     *
108     * @return array
109     */
110     function getAllowedTypes()
111     {
112        return [
113            "container",
114            "formatting",
115            "substition",
116        ];
117     }
118
119    /**
120     * Handler to prepare matched data for the rendering process
121     *
122     * This function can only pass data to render() via its return value - render()
123     * may be not be run during the object's current life.
124     *
125     * Usually you should only need the $match param.
126     *
127     * @param   string $match The text matched by the patterns
128     * @param   int $state The lexer state for the match
129     * @param   int $pos The character position of the matched text
130     * @param   Doku_Handler $handler The Doku_Handler object
131     * @return  bool|array Return an array with all data you want to use in render, false don't add an instruction
132     */
133    public function handle($match, $state, $pos, Doku_Handler $handler)
134    {
135        global $WKX_USED_IDS;
136          if ($match == '{{wxbib}}')
137          {
138                $callingmodus = "wxbib";
139                $ids = array_unique($WKX_USED_IDS);
140          }
141          else
142          {
143                // Remove {{ from start
144                // Remove }} from end
145                // E.g. {{wxcite>128}} => wxcite>128
146                $match_without_bracket = substr($match, strlen("{{"), -1 * strlen("}}"));
147
148                // Cut on first ">" character into callingmodus and data parts
149                $match_parts = explode(">", $match_without_bracket);
150                $callingmodus = $match_parts[0]; // E.g. wxcite, wxmusing ...
151                $data = trim($match_parts[1]);
152              $data = trim($data, ";"); //remove trailing ;
153
154              // Extract ids from data
155              $ids = explode(";", $data);
156          }
157
158
159        //Lookup data
160        $http = new DokuHTTPClient();
161        foreach ($ids as $id)
162        {
163            $iex = explode(':', $id);
164            $resId = $iex[0] ?? NULL;
165            $page = $iex[1] ?? NULL;
166
167            if ($callingmodus != "wxbib")
168            {
169                $WKX_USED_IDS[] = $resId;
170            }
171
172            $callmode[] = $callingmodus;
173            $resourceId[] = $resId;
174            $resourceHtml = [];
175
176            switch ($callingmodus)
177            {
178                case 'wxbib':
179                    global $WKX_USED_IDS;
180
181                    $citetext = "";
182                    foreach ($WKX_USED_IDS as $rid)
183                    {
184                        $citetext .= "[cite]" . $resId . "[/cite]\n";
185                    }
186
187                    $aResponse = $this->WikindxparseText($resId, $this->getConf('url'), $citetext);
188
189                    // Search for the bibliograpy which is the last block
190                    $cite_blocks = $this->mb_explode("<br><br>", $aResponse["text"] ?? "");
191                    if (count($cite_blocks) == 3)
192                        $bibliography = $cite_blocks[2];
193                    elseif (count($cite_blocks) == 2)
194                        $bibliography = $cite_blocks[1];
195                    else
196                        $bibliography = "";
197
198                    $resourceHtml[] = $bibliography;
199                break;
200                case 'wxabstract':
201                    $queryUrl = rtrim($this->getConf('url'), "/") . "/cmsprint.php?action=getAbstract&bibStyle=" . $this->getConf('bibStyle') . "&id=" . $resId;
202                    $aResponse = $this->_CMSPHPResponse2Array($http->get($queryUrl));
203                    foreach ($aResponse as $rid => $item)
204                    {
205                        $resourceHtml[] = $item;
206                    }
207                break;
208                case 'wxcite':
209                default:
210                   $aResponse = $this->WikindxparseText($resId, $this->getConf('url'), "[cite]" . rtrim($resId . "|" . $page, "|") . "[/cite]");
211
212                    // Search for the citation which is the first block in regulat style,
213                    // and the second block for footnote style
214                    $cite_blocks = $this->mb_explode("<br><br>", $aResponse["text"] ?? "");
215                    if (count($cite_blocks) == 3)
216                    {
217                        $citation = $cite_blocks[1];
218                    }
219                    elseif (count($cite_blocks) == 2)
220                    {
221                        $citation = '<a href=' . rtrim($this->getConf('url'), "/") . "/index.php?action=cms_CMS_CORE&method=resource_RESOURCEVIEW_CORE&id=" . $resId . '">';
222                        $citation .= trim($cite_blocks[0]);
223                        $citation .= '</a>';
224                    }
225                    else
226                    {
227                        $citation = "";
228                    }
229
230                    $resourceHtml[] = $citation;
231                break;
232                case 'wxmusing':
233                   $queryUrl = rtrim($this->getConf('url'), "/") . "/cmsprint.php?action=getMusing&bibStyle=" . $this->getConf('bibStyle') . "&id=" . $resId;
234                   $aResponse = $this->_CMSPHPResponse2Array($http->get($queryUrl));
235
236                   if (!$page)
237                       $page = 1;
238                   else
239                       $page = $page;
240
241                    if (array_key_exists(intval($resId), $aResponse))
242                    {
243                        $musings = $aResponse[intval($resId)];
244                        $n = 0;
245                        foreach ($musings as $key => $musing)
246                        {
247                            $n++;
248                            if ($page == $n)
249                            {
250                                $resourceHtml[] = $musing;
251                                break;
252                            }
253                        }
254                    }
255                break;
256                case 'wxnotes':
257                    $queryUrl = rtrim($this->getConf('url'), "/") . "/cmsprint.php?action=getNotes&bibStyle=" . $this->getConf('bibStyle') . "&id=" . $resId;
258                    $aResponse = $this->_CMSPHPResponse2Array($http->get($queryUrl));
259                    foreach ($aResponse as $rid => $item)
260                    {
261                        $resourceHtml[] = $item;
262                    }
263                break;
264                case 'wxparaphrase':
265                   $queryUrl = rtrim($this->getConf('url'), "/") . "/cmsprint.php?action=getParaphrase&bibStyle=" . $this->getConf('bibStyle') . "&id=" . $resId;
266                   $aResponse = $this->_CMSPHPResponse2Array($http->get($queryUrl));
267
268                   if (!$page)
269                       $page = 1;
270                   else
271                       $page = $page;
272
273                    if (array_key_exists(intval($resId), $aResponse))
274                    {
275                        $paraphrases = $aResponse[intval($resId)];
276                        $n = 0;
277                        foreach ($paraphrases as $key => $paraphrase)
278                        {
279                            $n++;
280                            if ($page == $n)
281                            {
282                                $resourceHtml[] = $paraphrase;
283                                break;
284                            }
285                        }
286                    }
287                break;
288                case 'wxquote':
289                   $queryUrl = rtrim($this->getConf('url'), "/") . "/cmsprint.php?action=getQuote&bibStyle=" . $this->getConf('bibStyle') . "&id=" . $resId;
290                   $aResponse = $this->_CMSPHPResponse2Array($http->get($queryUrl));
291
292                   if (!$page)
293                       $page = 1;
294                   else
295                       $page = $page;
296
297                    if (array_key_exists(intval($resId), $aResponse))
298                    {
299                        $quotes = $aResponse[intval($resId)];
300                        $n = 0;
301                        foreach ($quotes as $key => $quote)
302                        {
303                            $n++;
304                            if ($page == $n)
305                            {
306                                $resourceHtml[] = $quote;
307                                break;
308                            }
309                        }
310                    }
311                break;
312            }
313        }
314
315        return [$resourceId, $resourceHtml, $callmode];
316    }
317
318    /**
319     * Handles the actual output creation.
320     *
321     * The function must not assume any other of the classes methods have been run
322     * during the object's current life. The only reliable data it receives are its
323     * parameters.
324     *
325     * The function should always check for the given output format and return false
326     * when a format isn't supported.
327     *
328     * $renderer contains a reference to the renderer object which is
329     * currently handling the rendering. You need to use it for writing
330     * the output. How this is done depends on the renderer used (specified
331     * by $format
332     *
333     * The contents of the $data array depends on what the handler() function above
334     * created
335     *
336     * @param string $format output format being rendered
337     * @param Doku_Renderer $renderer the current renderer object
338     * @param array $data data created by handler()
339     * @return  boolean                 rendered correctly? (however, returned value is not used at the moment)
340     */
341    public function render($format, Doku_Renderer $renderer, $data)
342    {
343        if ($format != 'xhtml')
344        {
345            return false;
346        }
347
348        if (is_array($data[1]))
349        {
350            $output = "";
351
352            for ($i = 0; $i < count($data[1]); $i++)
353            {
354                $output .= $data[1][$i];
355            }
356
357            $renderer->doc .= $output;
358        }
359        return true;
360    }
361
362    function WikindxparseText($resId, $baseurl, $text)
363    {
364        static $mem_text = [];
365
366        if (!array_key_exists($text, $mem_text))
367        {
368            $queryUrl = rtrim($baseurl, "/") . "/cmsprint.php?action=parseText&bibStyle=" . $this->getConf('bibStyle');
369
370            $http = new DokuHTTPClient();
371            $resp = $http->post($queryUrl, ["text" => $text]);
372            $memresId[$resId] = $this->_CMSPHPResponse2Array($resp, true);
373        }
374
375        return $memresId[$resId];
376    }
377
378    /**
379     * Decode an object or an array serialized with PHPH serialize(), and other data type
380     *
381     * Return a human-readable string representing $encodedData. If the decoding fails $encodedData is returned.
382     *
383     * @param mixed $EncodedResponse
384     *
385     * @return array
386     */
387    private function _CMSPHPResponse2Array($EncodedResponse)
388    {
389        if ($EncodedResponse !== FALSE)
390        {
391            $array_serialized_pattern = '/^a:\d+:{.+/u';
392            $object_serialized_pattern = '/^O:\d+:".+/u';
393
394            if (preg_match($array_serialized_pattern, $EncodedResponse) > 0 || preg_match($object_serialized_pattern, $EncodedResponse) > 0)
395            {
396                $tmp = @unserialize($EncodedResponse);
397                if ($tmp !== FALSE)
398                {
399                    return $tmp;
400                }
401            }
402        }
403
404        // Fallback on error
405        return [];
406    }
407
408    /**
409     * Simulate explode() for multibytes strings (as documented for PHP 7.0)
410     *
411     * @param string $delimiter
412     * @param string $string
413     * @param int $limit Default is PHP_INT_MAX.
414     *
415     * @return string
416     */
417    function mb_explode($delimiter, $string, $limit = PHP_INT_MAX)
418    {
419        if ($delimiter == '')
420        {
421            return FALSE;
422        }
423
424        if ($limit === NULL)
425        {
426            PHP_INT_MAX;
427        }
428        if ($limit == 0)
429        {
430            $limit = 1;
431        }
432
433        $pattern = '/' . preg_quote($delimiter, '/') . '/u';
434
435        $aString = preg_split($pattern, $string, $limit);
436
437        if ($limit < 0 && count($aString) == 1)
438        {
439            return [];
440        }
441        elseif ($limit < 0 && count($aString) > 1)
442        {
443            $length = count($aString) - abs($limit);
444            if ($length <= 0)
445            {
446                return [];
447            }
448            else
449            {
450                return array_slice($aString, 0, $length, TRUE);
451            }
452        }
453        else
454        {
455            return $aString;
456        }
457    }
458}
459
460//Setup VIM: ex: et ts=4 enc=utf-8 :