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 
10 use dokuwiki\Extension\ActionPlugin;
11 use dokuwiki\Extension\EventHandler;
12 use dokuwiki\Extension\Event;
13 use dokuwiki\plugin\prosemirror\parser\ImageNode;
14 use dokuwiki\plugin\prosemirror\parser\RSSNode;
15 use dokuwiki\plugin\prosemirror\parser\LocalLinkNode;
16 use dokuwiki\plugin\prosemirror\parser\InternalLinkNode;
17 use dokuwiki\plugin\prosemirror\parser\LinkNode;
18 
19 class 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