<?php
/**
* Wikindx Citation Module
* Derived from: Refworks Plugin by Daniel Terry, Amazon Plugin by Andreas Gohr
*
* @license    GPL 2 (http://www.gnu.org/licenses/gpl.html)
* @author     Andreas Wagner <Andreas.Wagner@em.uni-frankfurt.de>
* @author     Stéphane Aulery <lkppo@users.sourceforge.net>
*
* 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("<br><br>", $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("<br><br>", $aResponse["text"] ?? "");
                    if (count($cite_blocks) == 3)
                    {
                        $citation = $cite_blocks[1];
                    }
                    elseif (count($cite_blocks) == 2)
                    {
                        $citation = '<a href=' . rtrim($this->getConf('url'), "/") . "/index.php?action=cms_CMS_CORE&method=resource_RESOURCEVIEW_CORE&id=" . $resId . '">';
                        $citation .= trim($cite_blocks[0]);
                        $citation .= '</a>';
                    }
                    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 :