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