1<?php
2
3/**
4 * DokuWiki Plugin prosemirror (Action Component)
5 *
6 * @license GPL 2 http://www.gnu.org/licenses/gpl-2.0.html
7 * @author  Andreas Gohr <gohr@cosmocode.de>
8 */
9
10use dokuwiki\Extension\ActionPlugin;
11use dokuwiki\Extension\EventHandler;
12use dokuwiki\Extension\Event;
13use dokuwiki\plugin\prosemirror\parser\ImageNode;
14use dokuwiki\plugin\prosemirror\parser\RSSNode;
15use dokuwiki\plugin\prosemirror\parser\LocalLinkNode;
16use dokuwiki\plugin\prosemirror\parser\InternalLinkNode;
17use dokuwiki\plugin\prosemirror\parser\LinkNode;
18
19class action_plugin_prosemirror_ajax extends ActionPlugin
20{
21    /**
22     * Registers a callback function for a given event
23     *
24     * @param EventHandler $controller DokuWiki's event controller object
25     *
26     * @return void
27     */
28    public function register(EventHandler $controller)
29    {
30        $controller->register_hook('AJAX_CALL_UNKNOWN', 'BEFORE', $this, 'handleAjax');
31        $controller->register_hook('AJAX_CALL_UNKNOWN', 'BEFORE', $this, 'switchEditors');
32    }
33
34    /**
35     * [Custom event handler which performs action]
36     *
37     * Event: AJAX_CALL_UNKNOWN
38     *
39     * @param Event $event event object by reference
40     *
41     * @return void
42     */
43    public function handleAjax(Event $event)
44    {
45        if ($event->data !== 'plugin_prosemirror') {
46            return;
47        }
48        $event->preventDefault();
49        $event->stopPropagation();
50
51        global $INPUT, $ID;
52        $ID = $INPUT->str('id');
53        $responseData = [];
54        foreach ($INPUT->arr('actions') as $action) {
55            switch ($action) {
56                case 'resolveInternalLink':
57                    $inner = $INPUT->str('inner');
58                    $responseData[$action] = $this->resolveInternalLink($inner, $ID);
59                    break;
60                case 'resolveInterWikiLink':
61                    $inner = $INPUT->str('inner');
62                    [$shortcut, $reference] = explode('>', $inner);
63                    $responseData[$action] = $this->resolveInterWikiLink($shortcut, $reference);
64                    break;
65                case 'resolveMedia':
66                    $attrs = $INPUT->arr('attrs');
67                    $responseData[$action] = [
68                        'data-resolvedHtml' => ImageNode::resolveMedia(
69                            $attrs['id'],
70                            $attrs['title'],
71                            $attrs['align'],
72                            $attrs['width'],
73                            $attrs['height'],
74                            $attrs['cache'],
75                            $attrs['linking']
76                        )
77                    ];
78                    break;
79                case 'resolveImageTitle':
80                    $image = $INPUT->arr('image');
81                    $responseData[$action] = [];
82                    $responseData[$action]['data-resolvedImage'] = LinkNode::resolveImageTitle(
83                        $ID,
84                        $image['id'],
85                        $image['title'],
86                        $image['align'],
87                        $image['width'],
88                        $image['height'],
89                        $image['cache']
90                    );
91                    break;
92                case 'resolveRSS':
93                    $attrs = json_decode($INPUT->str('attrs'), true);
94                    $responseData[$action] = RSSNode::renderAttrsToHTML($attrs);
95                    break;
96                default:
97                    dokuwiki\Logger::getInstance(dokuwiki\Logger::LOG_DEBUG)->log(
98                        __FILE__ . ': ' . __LINE__,
99                        'Unknown action: ' . $action
100                    );
101                    http_status(400, 'unknown action');
102                    return;
103            }
104        }
105
106        header('Content-Type: application/json');
107        echo json_encode($responseData);
108    }
109
110    protected function resolveInterWikiLink($shortcut, $reference)
111    {
112        $xhtml_renderer = p_get_renderer('xhtml');
113        $xhtml_renderer->interwiki = getInterwiki();
114        $url = $xhtml_renderer->_resolveInterWiki($shortcut, $reference, $exits);
115        return [
116            'url' => $url,
117            'resolvedClass' => 'interwikilink interwiki iw_' . $shortcut,
118        ];
119    }
120
121    protected function resolveInternalLink($inner, $curId)
122    {
123        if ($inner[0] === '#') {
124            return LocalLinkNode::resolveLocalLink($inner, $curId);
125        }
126        return InternalLinkNode::resolveLink($inner, $curId);
127    }
128
129    /**
130     * [Custom event handler which performs action]
131     *
132     * Event: AJAX_CALL_UNKNOWN
133     *
134     * @param Event $event event object by reference
135     *
136     * @return void
137     */
138    public function switchEditors(Event $event)
139    {
140        if ($event->data !== 'plugin_prosemirror_switch_editors') {
141            return;
142        }
143        $event->preventDefault();
144        $event->stopPropagation();
145
146        global $INPUT, $ID;
147        $ID = $INPUT->str('id');
148
149        if ($INPUT->bool('getJSON')) {
150            $text = $INPUT->str('data');
151            $instructions = p_get_instructions($text);
152            try {
153                $prosemirrorJSON = p_render('prosemirror', $instructions, $info);
154            } catch (Throwable $e) {
155                $errorMsg = 'Rendering the page\'s syntax for the WYSIWYG editor failed: ';
156                $errorMsg .= $e->getMessage();
157
158                /** @var \helper_plugin_prosemirror $helper */
159                $helper = plugin_load('helper', 'prosemirror');
160                if ($helper->tryToLogErrorToSentry($e, ['text' => $text])) {
161                    $errorMsg .= ' -- The error has been logged to Sentry.';
162                } else {
163                    $errorMsg .= '<code>' . $e->getFile() . ':' . $e->getLine() . '</code>';
164                    $errorMsg .= '<pre>' . $e->getTraceAsString() . '</pre>';
165                }
166
167                http_status(500);
168                header('Content-Type: application/json');
169                echo json_encode(['error' => $errorMsg]);
170                return;
171            }
172            $responseData = [
173                'json' => $prosemirrorJSON,
174            ];
175        } else {
176            /** @var \helper_plugin_prosemirror $helper */
177            $helper = plugin_load('helper', 'prosemirror');
178            $json = $INPUT->str('data');
179            try {
180                $syntax = $helper->getSyntaxFromProsemirrorData($json);
181            } catch (Throwable $e) {
182                $errorMsg = 'Parsing the data generated by Prosemirror failed with message: "';
183                $errorMsg .= $e->getMessage();
184                $errorMsg .= '"';
185
186                if ($helper->tryToLogErrorToSentry($e, ['json' => $json])) {
187                    $errorMsg .= ' -- The error has been logged to Sentry.';
188                }
189
190                http_status(500);
191                header('Content-Type: application/json');
192                echo json_encode(['error' => $errorMsg]);
193                return;
194            }
195            $responseData = [
196                'text' => $syntax,
197            ];
198        }
199
200        header('Content-Type: application/json');
201        echo json_encode($responseData);
202    }
203}
204