1<?php
2/**
3 * DokuWiki Syntax Plugin Web Component.
4 *
5 */
6if (!defined('DOKU_INC')) {
7    die();
8}
9
10if (!defined('DOKU_PLUGIN')) {
11    define('DOKU_PLUGIN', DOKU_INC . 'lib/plugins/');
12}
13
14require_once(__DIR__ . '/../webcomponent.php');
15
16/**
17 * All DokuWiki plugins to extend the parser/rendering mechanism
18 * need to inherit from this class
19 *
20 * The name of the class must follow a pattern (don't change it)
21 * ie:
22 *    syntax_plugin_PluginName_ComponentName
23 */
24class syntax_plugin_webcomponent_card extends DokuWiki_Syntax_Plugin
25{
26
27    // Pattern that we expect in a card (teaser)
28    const HEADER_PATTERN = '[ \t]*={2,}[^\n]+={2,}[ \t]*(?=\n)';
29    const IMAGE_PATTERN = "\{\{(?:[^\}]|(?:\}[^\}]))+\}\}";
30
31
32    // The elements of a teaser
33    // because they are assembled at the end
34    private $startElement;
35    private $text;
36    private $header;
37    private $image;
38
39
40    /**
41     * Syntax Type.
42     *
43     * Needs to return one of the mode types defined in $PARSER_MODES in parser.php
44     * @see DokuWiki_Syntax_Plugin::getType()
45     */
46    function getType()
47    {
48        return 'protected';
49    }
50
51    /**
52     * @return array
53     * Allow which kind of plugin inside
54     *
55     * No one of array('container', 'formatting', 'substition', 'protected', 'disabled', 'paragraphs')
56     * because we manage self the content and we call self the parser
57     */
58    public function getAllowedTypes()
59    {
60        return array();
61    }
62
63    /**
64     * How Dokuwiki will add P element
65     *
66     * * 'normal' - The plugin can be used inside paragraphs
67     *  * 'block'  - Open paragraphs need to be closed before plugin output - block should not be inside paragraphs
68     *  * 'stack'  - Special case. Plugin wraps other paragraphs. - Stacks can contain paragraphs
69     *
70     * @see DokuWiki_Syntax_Plugin::getPType()
71     */
72    function getPType()
73    {
74        return 'block';
75    }
76
77    /**
78     * @see Doku_Parser_Mode::getSort()
79     * Higher number than the teaser-columns
80     * because the mode with the lowest sort number will win out
81     */
82    function getSort()
83    {
84        return 200;
85    }
86
87    /**
88     * Create a pattern that will called this plugin
89     *
90     * @see Doku_Parser_Mode::connectTo()
91     * @param string $mode
92     */
93    function connectTo($mode)
94    {
95
96        foreach (self::getTags() as $tag) {
97
98            $pattern = webcomponent::getLookAheadPattern($tag);
99            $this->Lexer->addEntryPattern($pattern, $mode, 'plugin_' . webcomponent::PLUGIN_NAME . '_' . $this->getPluginComponent());
100        }
101
102
103
104    }
105
106    public function postConnect()
107    {
108
109        foreach (self::getTags() as $tag) {
110            $this->Lexer->addExitPattern('</' . $tag . '>', 'plugin_' . webcomponent::PLUGIN_NAME . '_' . $this->getPluginComponent());
111        }
112
113        // Header
114        $this->Lexer->addPattern(self::HEADER_PATTERN, 'plugin_' . webcomponent::PLUGIN_NAME . '_' . $this->getPluginComponent());
115
116        // Image
117        $this->Lexer->addPattern(self::IMAGE_PATTERN, 'plugin_' . webcomponent::PLUGIN_NAME . '_' . $this->getPluginComponent());
118
119    }
120
121    /**
122     *
123     * The handle function goal is to parse the matched syntax through the pattern function
124     * and to return the result for use in the renderer
125     * This result is always cached until the page is modified.
126     * @see DokuWiki_Syntax_Plugin::handle()
127     *
128     * @param string $match
129     * @param int $state
130     * @param int $pos
131     * @param Doku_Handler $handler
132     * @return array|bool
133     */
134    function handle($match, $state, $pos, Doku_Handler $handler)
135    {
136
137        switch ($state) {
138
139            case DOKU_LEXER_ENTER:
140
141                // Suppress the component name
142                // Suppress the <>
143                $match = utf8_substr($match, 1, -1);
144                // Suppress the tag name
145                foreach (self::getTags() as $tag) {
146                    $match = str_replace( $tag, "",$match);
147                }
148                $parameters = webcomponent::parseMatch($match);
149                return array($state, $parameters);
150
151            case DOKU_LEXER_UNMATCHED :
152
153                return array($state, $match);
154
155            case DOKU_LEXER_MATCHED :
156
157                $parameters = array();
158                if (preg_match('/' . self::HEADER_PATTERN . '/msSi', $match . DOKU_LF)) {
159                    // We have a header
160                    $title = trim($match);
161                    $level = 7 - strspn($title, '=');
162                    if ($level < 1) $level = 1;
163                    $title = trim($title, '=');
164                    $title = trim($title);
165                    $parameters['header']['title'] = $title;
166                    $parameters['header']['level'] = $level;
167                }
168
169                if (preg_match('/' . self::IMAGE_PATTERN . '/msSi', $match . DOKU_LF)) {
170                    // We have an image, we parse it (Doku_Handler_Parse_Media in handler.php)
171                    $parameters['image'] = Doku_Handler_Parse_Media($match);
172                }
173
174                return array($state, $parameters);
175
176            case DOKU_LEXER_EXIT :
177
178                return array($state, '');
179
180
181        }
182
183        return array();
184
185    }
186
187    /**
188     * Render the output
189     * @see DokuWiki_Syntax_Plugin::render()
190     *
191     * @param string $mode
192     * @param Doku_Renderer $renderer
193     * @param array $data - what the function handle() return'ed
194     * @return bool
195     */
196    function render($mode, Doku_Renderer $renderer, $data)
197    {
198
199        if ($mode == 'xhtml') {
200
201            /** @var Doku_Renderer_xhtml $renderer */
202            list($state, $parameters) = $data;
203            switch ($state) {
204
205                case DOKU_LEXER_ENTER :
206                    $this->startElement .= '<div class="card"';
207                    foreach ($parameters as $key => $value) {
208                        $this->startElement .= ' ' . $key . '="' . $value . '"';
209                    }
210                    $this->startElement .= '>';
211                    break;
212
213                case DOKU_LEXER_UNMATCHED :
214                    $instructions = p_get_instructions($parameters);
215                    $lastPBlockPosition = sizeof($instructions) - 2;
216                    if ($instructions[1][0] == 'p_open') {
217                        unset($instructions[1]);
218                    }
219                    if ($instructions[$lastPBlockPosition][0] == 'p_close') {
220                        unset($instructions[$lastPBlockPosition]);
221                    }
222                    $this->text .= p_render('xhtml', $instructions, $info);
223                    break;
224
225                case DOKU_LEXER_MATCHED:
226
227                    if (array_key_exists('header', $parameters)) {
228                        $title = $parameters['header']['title'];
229                        $level = $parameters['header']['level'];
230                        $this->header .= '<h' . $level . ' class="card-title">';
231                        $this->header .= $renderer->_xmlEntities($title);
232                        $this->header .= "</h$level>";
233                    }
234
235                    if (array_key_exists('image', $parameters)) {
236
237                        $this->image = $parameters['image'];
238
239                    }
240                    break;
241
242                case DOKU_LEXER_EXIT :
243
244                    $renderer->doc .= $this->startElement . DOKU_LF;
245
246                    if ($this->header == "" and $this->text == "" and $this->image != ""){
247                        // An image card without any content
248                        $src = $this->image['src'];
249                        $width = $this->image['width'];
250                        $height = $this->image['height'];
251                        $title = $this->image['title'];
252                        //Snippet taken from $renderer->doc .= $renderer->internalmedia($src, $linking = 'nolink');
253                        $renderer->doc .= '<img class="card-img" src="' . ml($src, array('w' => $width, 'h' => $height, 'cache' => true)) . '" alt="' . $title . '" width="' . $width . '">';
254                    } else {
255                        // A real teaser
256                        if ($this->image != "") {
257                            $src = $this->image['src'];
258                            $width = $this->image['width'];
259                            $height = $this->image['height'];
260                            $title = $this->image['title'];
261                            //Snippet taken from $renderer->doc .= $renderer->internalmedia($src, $linking = 'nolink');
262                            $renderer->doc .= DOKU_TAB . '<img class="card-img-top" src="' . ml($src, array('w' => $width, 'h' => $height, 'cache' => true)) . '" alt="' . $title . '" width="' . $width . '">' .DOKU_LF;
263                        }
264                        $renderer->doc .= DOKU_TAB . '<div class="card-body">' . DOKU_LF;
265                        if ($this->header != "") {
266                            $renderer->doc .= DOKU_TAB . DOKU_TAB . $this->header . DOKU_LF;
267                        }
268                        if ($this->text != "") {
269                            $renderer->doc .= DOKU_TAB . DOKU_TAB . '<p class="card-text">' . $this->text . '</p>' . DOKU_LF;
270                        }
271                        $renderer->doc .= DOKU_TAB . '</div>' . DOKU_LF;
272                    }
273
274                    $renderer->doc .= "</div>" . DOKU_LF;
275
276                    $this->startElement = "";
277                    $this->image = "";
278                    $this->header = "";
279                    $this->text = "";
280                    break;
281            }
282            return true;
283        }
284        return false;
285    }
286
287
288    public static function getTag()
289    {
290        list(/* $t */, /* $p */, /* $n */, $c) = explode('_', get_called_class(), 4);
291        return (isset($c) ? $c : '');
292    }
293
294    public
295    static function getTags()
296    {
297        $elements[] = webcomponent::getTagName(get_called_class());
298        $elements[] = 'teaser';
299        return $elements;
300    }
301
302
303}
304