1<?php
2
3use dokuwiki\Extension\SyntaxPlugin;
4use dokuwiki\File\PageResolver;
5
6/**
7 * DokuWiki Plugin bb4dw (Syntax Component)
8 *
9 * @license GPL 2 http://www.gnu.org/licenses/gpl-2.0.html
10 * @author Hans-Nikolai Viessmann <hans@viess.mn>
11 *
12 * This Syntax plugin is inspired by the deprecated publistf Dokuwiki plugin, and
13 * tries to recreate the same output using a BibBrowser
14 * (https://github.com/monperrus/bibtexbrowser) Bibtex processing script.
15 *
16 * Templating is handled through the BB4DWTemplating class.
17 */
18
19/**
20 * BibBrowser Configurations
21 */
22$_GET['library'] = 1; // cause BibBrowser to run in 'library' mode
23define('BIBTEXBROWSER_BIBTEX_LINKS', false); // disable links back to bibtex
24define('USE_FIRST_THEN_LAST', true); // ensure that author names are consistently ordered
25
26class syntax_plugin_bb4dw extends SyntaxPlugin
27{
28    /** @inheritDoc */
29    public function getType()
30    {
31        return 'substition';
32    }
33
34    /** @inheritDoc */
35    public function getPType()
36    {
37        return 'block';
38    }
39
40    /** @inheritDoc */
41    public function getSort()
42    {
43        return 105;
44    }
45
46    /** @inheritDoc */
47    public function connectTo($mode)
48    {
49        $this->Lexer->addSpecialPattern('\[bb4dw\|.+?\]', $mode, 'plugin_bb4dw');
50    }
51
52    /** @inheritDoc */
53    public function handle($match, $state, $pos, Doku_Handler $handler)
54    {
55        $data = [
56            'error' => false,
57            'groups' => [],
58            'bibtex' => [],
59            'template' => [],
60            'raw' => [],
61            'config' => ['target' => 'dokuwiki', # XXX likely we won't be able to support anything else!
62                         'usegroup' => true,
63                         'groupby' => 'year', # call also be 'none', 'author', or 'title'
64                         'order' => 'newest',
65                         'globalkey' => 'all',
66                         'filter' => []], # or 'descending'
67        ];
68
69        // parse the bb4dw plugin pattern
70        $matchs = [];
71        $pattern = '/\[bb4dw(?:\|bibtex=(dokuwiki|url):(.+?))(?:\|template=(dokuwiki|url):(.+?))(?:\|(.+?(?:\|.+?)*))?\]/';
72        if (preg_match($pattern, $match, $matches) === 0) {
73            msg('Not valid bb4dw syntax: '.$match, -1);
74            $data['error'] = true;
75        } else {
76            // capture matches in config
77            $data['bibtex'] = ['type' => $matches[1], 'ref' => $matches[2]];
78            $data['template'] = ['type' => $matches[3], 'ref' => $matches[4]];
79
80            if (!empty($matches[5])) {
81               $matches = explode('|', $matches[5]);
82               foreach ( $matches as $opt ) {
83                 $optparts = array();
84                 if (preg_match('/(.+?)=(.+)/', $opt, $optparts) ) {
85                    $optparts[2] = explode(';', $optparts[2]);
86                    $option = array();
87                    foreach ($optparts[2] as $single) {
88                        $single = explode(':', $single);
89                        if (count($single) == 1 && count($optparts[2]) == 1) {
90                            $option = $single[0];
91                        }
92                        else {
93                            $option[$single[0]] = str_replace(',', '|', $single[1]);
94                        }
95                    }
96                    $data['config'][$optparts[1]] = $option;
97                 }
98               }
99            }
100
101            // init BibBrowser library
102            require_once(dirname(__FILE__).'/bibtexbrowser.php');
103            global $db;
104            $db = new BibDataBase();
105
106            // Load Bibtex into db structure
107            $db->load($this->retrieve_resource($data['bibtex']['type'], $data['bibtex']['ref'], true));
108
109            // get all entries and sort (internally the default is by year)
110            $data['raw'] = $db->getEntries();
111            //uasort($data['raw'], 'compare_bib_entries');
112
113            foreach ($data['raw'] as $entry) {
114                // we decouple the read in fields from the bibbrowser library
115                // we format authors into consistent state
116                $_tmp_entry = $entry->getFields();
117                $_tmp_entry['niceauthor'] = $entry->getFormattedAuthorsString();
118                $_tmp_entry['bibtex'] = $entry->getText();
119
120                $keep = true;
121                foreach ($data['config']['filter'] as $field => $regexp) {
122                    if (!empty($_tmp_entry[$field])) {
123                        $val = $field === 'author'
124                            ? $_tmp_entry['niceauthor']
125                            : $_tmp_entry[$field];
126
127                        $keep = $keep && preg_match('/'.$regexp.'/i', $val);
128                    }
129                    else
130                        $keep = false;
131
132                }
133                if (!$keep) {
134                    unset($_tmp_entry);
135                    continue;
136                }
137
138                switch($data['config']['groupby']) {
139                    case 'none':
140                        $groupby = 'none';
141                        break;
142                    case 'author':
143                        $_authors = $entry->getRawAuthors();
144                        $groupby = mb_substr($entry->getLastName($_authors[0]), 0, 1);
145                        break;
146                    case 'title':
147                        $groupby = mb_substr($entry->getTitle(), 0, 1);
148                        break;
149                    case 'year':
150                        $groupby = $entry->getYear();
151                        break;
152                    default:
153                        msg('Unknown groupby `'.$data['config']['groupby'].'` passed!', -1);
154                        $data['error'] = true;
155                        break 2;
156                }
157
158                // ensure that we don't append to null array
159                if (empty($data['groups'][$groupby]))
160                    $data['groups'][$groupby] = [$_tmp_entry];
161                else
162                    $data['groups'][$groupby][] = $_tmp_entry;
163            }
164
165            // ensure that the groups are sorted and in the specified order
166            ksort($data['groups']);
167            if ($data['config']['order'] == 'newest' || $data['config']['order'] == 'descending')
168                $data['groups'] = array_reverse($data['groups'], true);
169        }
170
171        return $data;
172    }
173
174    /** @inheritDoc */
175    public function render($mode, Doku_Renderer $renderer, $data)
176    {
177        if ($data['error']) return false;
178        if ($mode !== 'xhtml') return false;
179
180        // activate caching of results
181        $renderer->info['cache'] = $this->getConf('cache');
182
183        $tpl = $this->retrieve_resource($data['template']['type'], $data['template']['ref']);
184
185        require_once(dirname(__FILE__).'/templating.php');
186        $bb4dw_tpl_class = new BB4DWTemplating();
187        $proc_tpl = $bb4dw_tpl_class->process_template($tpl, $data);
188
189        if ($data['config']['target'] == 'dokuwiki') {
190            $proc_tpl = p_render($mode, p_get_instructions($proc_tpl), $info);
191        }
192
193        $renderer->doc .= $proc_tpl;
194
195        return true;
196    }
197
198    /**
199     * Retrieve resource from one of file, URL, or dokuwiki page. The result of this
200     * function with either be the verbatim content of the resource, or an absolute path.
201     *
202     * @param string $type
203     * @param string $ref
204     * @param bool $path
205     * @return string Content of or path to resource
206     */
207    private function retrieve_resource(string $type, string $ref, bool $path = false): string
208    {
209        $res = '';
210
211        switch ($type)
212        {
213            case 'url':
214                if ($path)
215                    $res = $ref;
216                else
217                    $res = file_get_contents($ref);
218                break;
219            case 'dokuwiki':
220                $resolver = new PageResolver($ID);
221                $mid = $resolver->resolveId($ref);
222                if(page_exists($mid)) {
223                    if ($path)
224                        $res = wikiFN($mid);
225                    else
226                        $res = rawWiki($mid);
227                }
228                break;
229            default:
230                msg('Unknown type '.$type.', unable to process!', -1);
231                break;
232        }
233
234        return $res;
235    }
236}
237