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