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