1<?php 2/** 3 * DokuWiki Plugin dropfiles (Action Component) 4 * 5 * @license GPL 2 http://www.gnu.org/licenses/gpl-2.0.html 6 * @author Michael Große <dokuwiki@cosmocode.de> 7 */ 8 9// must be run within Dokuwiki 10if (!defined('DOKU_INC')) { 11 die(); 12} 13 14class action_plugin_dropfiles_ajax extends DokuWiki_Action_Plugin 15{ 16 17 protected $NS = ''; 18 19 20 /** 21 * Registers a callback function for a given event 22 * 23 * @param Doku_Event_Handler $controller DokuWiki's event controller object 24 * 25 * @return void 26 */ 27 public function register(Doku_Event_Handler $controller) 28 { 29 $controller->register_hook('AJAX_CALL_UNKNOWN', 'BEFORE', $this, 'handleAjaxCallUnknown'); 30 } 31 32 /** 33 * [Custom event handler which performs action] 34 * 35 * @param Doku_Event $event event object by reference 36 * @param mixed $param [the parameters passed as fifth argument to register_hook() when this 37 * handler was registered] 38 * 39 * @return void 40 */ 41 42 public function handleAjaxCallUnknown(Doku_Event $event, $param) 43 { 44 if (strpos($event->data,'dropfiles') !== 0) { 45 return; 46 } 47 48 $event->preventDefault(); 49 $event->stopPropagation(); 50 $action = substr($event->data, strlen('dropfiles_')); 51 52 if ($action === 'checkfiles') { 53 echo json_encode($this->checkFiles()); 54 } 55 56 if ($action === 'mediaupload') { 57 global $INPUT; 58 $this->callMediaupload(); 59 } 60 } 61 62 /** 63 * this is an adjusted version of @see ajax_mediaupload 64 * 65 * This version also provides a consistent error key, instead of only giving a localized error message 66 */ 67 protected function callMediaupload() 68 { 69 global $NS, $MSG, $INPUT; 70 71 $id = ''; 72 if ($_FILES['qqfile']['tmp_name']) { 73 $id = $INPUT->post->str('mediaid', $_FILES['qqfile']['name']); 74 } elseif ($INPUT->get->has('qqfile')) { 75 $id = $INPUT->get->str('qqfile'); 76 } 77 78 $id = cleanID($id); 79 80 $NS = $INPUT->str('ns'); 81 $ns = $NS . ':' . getNS($id); 82 83 $AUTH = auth_quickaclcheck("$ns:*"); 84 if ($AUTH >= AUTH_UPLOAD) { 85 io_createNamespace("$ns:xxx", 'media'); 86 } 87 88 if ($_FILES['qqfile']['error']) { 89 unset($_FILES['qqfile']); 90 } 91 92 $res = false; 93 if ($_FILES['qqfile']['tmp_name']) { 94 $res = media_upload($NS, $AUTH, $_FILES['qqfile']); 95 } 96 if ($INPUT->get->has('qqfile')) { 97 $res = media_upload_xhr($NS, $AUTH); 98 } 99 100 if ($res) { 101 $result = array( 102 'success' => true, 103 'link' => media_managerURL(array('ns' => $ns, 'image' => $NS . ':' . $id), '&'), 104 'id' => $NS . ':' . $id, 105 'ns' => $NS, 106 ); 107 } else { 108 $error = ''; 109 if (count($MSG)) { 110 foreach ($MSG as $msg) { 111 $error .= $msg['msg']; 112 } 113 } 114 $result = array( 115 'error' => $error, 116 'errorType' => $this->determineErrorCause($id, $ns), 117 'ns' => $NS, 118 ); 119 } 120 header('Content-Type: application/json'); 121 echo json_encode($result); 122 } 123 124 125 /** 126 * @return array 127 */ 128 protected function checkFiles() { 129 global $INPUT; 130 $this->NS = $INPUT->str('ns'); 131 132 // loop over files 133 $filelist = $INPUT->post->arr('filenames'); 134 return array_reduce($filelist, [$this, 'checkFileCallback'], []); 135 } 136 137 /** 138 * check a list of filenames for existing files and other errors that might prevent upload 139 * 140 * Note: some checks can only be done after the file has actually been uploaded 141 * 142 * @param array $carry 143 * @param string $filename 144 * @return array 145 */ 146 protected function checkFileCallback($carry, $filename){ 147 $id = cleanID($filename); 148 $ns = $this->NS . ':' . getNS($id); 149 $error = $this->determineErrorCause($id, $ns, false); 150 $carry[$filename] = $error; 151 return $carry; 152 } 153 154 155 /** 156 * Try to determine WHY the upload failed 157 * 158 * This replicates code from several places in dokuwiki core 159 * 160 * @param string $id the name of the new file on the filesystem 161 * @param string $ns the namespace where the new file would have been saved 162 * @param bool $contentAvailable flag if the file hasn't been uploaded yet 163 * 164 * @return string 165 */ 166 protected function determineErrorCause($id, $ns, $contentAvailable = true) 167 { 168 global $conf; 169 170 if (!checkSecurityToken()) { 171 return 'security token failed'; 172 } 173 $AUTH = auth_quickaclcheck("$ns:*"); 174 if ($AUTH < AUTH_UPLOAD) { 175 return 'missing permissions'; 176 } 177 $fullID = $ns . ':' . $id; 178 $fn = mediaFN($fullID); 179 180 // get filetype regexp 181 $types = array_keys(getMimeTypes()); 182 $types = array_map( 183 function ($q) { 184 return preg_quote($q, '/'); 185 }, 186 $types 187 ); 188 $regex = implode('|', $types); 189 if (!preg_match('/\.(' . $regex . ')$/i', $fn)) { 190 return 'bad file type'; 191 } 192 193 $exists = file_exists($fn); 194 if ($exists) { 195 if (!$conf['mediarevisions'] && $AUTH < AUTH_DELETE) { 196 return 'file exists and no delete permissions'; 197 } 198 199 return 'file exists'; 200 } 201 202 if ($contentAvailable) { 203 list(, $mime) = mimetype($id); 204 $ok = media_contentcheck($fn, $mime); 205 if ($ok === -1) { 206 return 'file does not match extension'; 207 } 208 if ($ok === -2) { 209 return 'spam'; 210 } 211 if ($ok === -3) { 212 return 'xss'; 213 } 214 } 215 216 return ''; 217 } 218} 219 220// vim:ts=4:sw=4:et: 221