1<?php
2
3namespace dokuwiki\plugin\imagemapping;
4
5use dokuwiki\Parsing\Handler\CallWriterInterface;
6
7/**
8 * Custom CallWriter for the imagemapping plugin
9 */
10class ImageMapHandler implements CallWriterInterface
11{
12    /** @var CallWriterInterface the parent call writer */
13    public $CallWriter;
14
15    /** @var array the local call stack */
16    protected $calls = [];
17
18    /** @var string Name of this imagemap FIXME currenlty unsused?*/
19    private $mapname;
20
21    /**
22     * Constructor
23     *
24     * @param CallWriterInterface $CallWriter the "parent" call writer
25     */
26    public function __construct($mapname, CallWriterInterface $CallWriter)
27    {
28        $this->mapname = $mapname;
29        $this->CallWriter = $CallWriter;
30    }
31
32    /** @inheritdoc */
33    public function writeCall($call)
34    {
35        $this->calls[] = $call;
36    }
37
38    /** @inheritdoc */
39    public function writeCalls($calls)
40    {
41        $this->calls = array_merge($this->calls, $calls);
42    }
43
44    /** @inheritdoc */
45    public function finalise()
46    {
47        $last_call = end($this->calls);
48        $this->process();
49        $this->addPluginCall([DOKU_LEXER_EXIT], $last_call[2]);
50        $this->CallWriter->finalise(); // FIXME do we really need to call it?
51    }
52
53    /**
54     * Get the parent call writer
55     *
56     * @return CallWriterInterface
57     */
58    public function getCallWriter()
59    {
60        return $this->CallWriter;
61    }
62
63    /**
64     * Process the local call stack
65     *
66     * @return void
67     */
68    public function process()
69    {
70        $last_call = end($this->calls);
71        $first_call = array_shift($this->calls);
72
73        $this->CallWriter->writeCall($first_call);
74        $this->processLinks($first_call[2]);
75
76        if (!empty($this->calls)) {
77            $this->addPluginCall([DOKU_LEXER_MATCHED, 'divstart'], $first_call[2]);
78            //Force a new paragraph
79            $this->CallWriter->writeCall(['eol', [], $this->calls[0][2]]);
80            $this->CallWriter->writeCalls($this->calls);
81            $this->addPluginCall([DOKU_LEXER_MATCHED, 'divend'], $last_call[2]);
82        }
83    }
84
85    /**
86     * Adds a call to the imagemap plugin with the given data
87     *
88     * The syntax component will be called with the given data and handle the various sub modes
89     *
90     * @param array $args [DOKU_LEXER_*, 'submode', params...]
91     * @param int $pos
92     * @return void
93     */
94    protected function addPluginCall($args, $pos)
95    {
96        $this->CallWriter->writeCall(['plugin', ['imagemapping', $args, $args[0]], $pos]);
97    }
98
99    /**
100     * Add a new area call to the call stack
101     *
102     * @param int $pos The position in the source
103     * @param string $type The type of the link (internallink, externallink, ...)
104     * @param string $title The link title including the coordinates
105     * @param string $url The link part of the link
106     * @param string $wiki The interwiki identifier for interwiki links
107     * @return string The title without the coordinates
108     */
109    protected function addArea($pos, $type, $title, $url, $wiki = null)
110    {
111        if (preg_match('/^(.*)@([^@]+)$/u', $title, $match)) {
112            $coords = explode(',', $match[2]);
113            if (count($coords) == 3) {
114                $shape = 'circle';
115            } elseif (count($coords) == 4) {
116                $shape = 'rect';
117            } elseif (count($coords) >= 6) {
118                $shape = 'poly';
119            } else {
120                return $title;
121            }
122            $coords = array_map('trim', $coords);
123            $title = trim($match[1]);
124
125            $coords = join(',', $coords);
126            $coords = trim($coords);
127
128            $this->addPluginCall(
129                [DOKU_LEXER_MATCHED, 'area', $shape, $coords, $type, $title, $url, $wiki],
130                $pos
131            );
132        }
133        return $title;
134    }
135
136    /**
137     * Walk through the call stack and process all links
138     *
139     * This will add the imagemap areas to the call stack and remove the coordinates from the link titles
140     *
141     * @todo simplify more and add tests
142     * @param int $pos The source position
143     * @return void
144     */
145    protected function processLinks($pos)
146    {
147        for ($n = 0; $n < count($this->calls); $n++) {
148            $data =& $this->calls[$n][1];
149            $type = $this->calls[$n][0];
150            switch ($type) {
151                case 'plugin':
152                    // support for other plugins that use the imagemap syntax (e.g. the popupviewer plugin)
153                    $plugin = plugin_load('syntax', $data[0]);
154                    if ($plugin != null && method_exists($plugin, 'convertToImageMapArea')) {
155                        $plugin->convertToImageMapArea($this, $data[1], $pos);
156                    }
157                    break;
158                case 'internallink':
159                case 'locallink':
160                case 'externallink':
161                case 'emaillink':
162                case 'windowssharelink':
163                    if (is_array($data[1])) {
164                        $title = $data[1]['title'] ?? '';
165                    } else {
166                        $title = $data[1];
167                    }
168                    $title = $this->addArea($pos, $type, $title, $data[0]);
169                    if (is_array($data[1])) {
170                        $data[1]['title'] = $title;
171                    } else {
172                        $data[1] = $title;
173                    }
174                    break;
175                case 'interwikilink':
176                    if (is_array($data[1])) {
177                        $title = $data[1]['title'];
178                    } else {
179                        $title = $data[1];
180                    }
181                    $title = $this->addArea($pos, $type, $title, $data[3], $data[2]);
182                    if (is_array($data[1])) {
183                        $data[1]['title'] = $title;
184                    } else {
185                        $data[1] = $title;
186                    }
187                    break;
188            }
189        }
190    }
191
192}
193