1<?php 2// (c) Copyright by authors of the Tiki Wiki CMS Groupware Project 3// 4// All Rights Reserved. See copyright.txt for details and a complete list of authors. 5// Licensed under the GNU LESSER GENERAL PUBLIC LICENSE. See license.txt for details. 6// $Id$ 7 8include_once "vendor_bundled/vendor/studio-42/elfinder/php/elFinderConnector.class.php"; 9include_once "vendor_bundled/vendor/studio-42/elfinder/php/elFinder.class.php"; 10include_once "vendor_bundled/vendor/studio-42/elfinder/php/elFinderVolumeDriver.class.php"; 11 12include_once 'lib/jquery_tiki/elfinder/elFinderVolumeTikiFiles.class.php'; 13include_once 'lib/jquery_tiki/elfinder/tikiElFinder.php'; 14 15class Services_File_FinderController 16{ 17 private $fileController; 18 19 private $parentIds; 20 21 function setUp() 22 { 23 global $prefs; 24 25 if ($prefs['feature_file_galleries'] != 'y') { 26 throw new Services_Exception_Disabled('feature_file_galleries'); 27 } 28 if ($prefs['fgal_elfinder_feature'] != 'y') { 29 throw new Services_Exception_Disabled('fgal_elfinder_feature'); 30 } 31 $this->fileController = new Services_File_Controller(); 32 $this->fileController->setUp(); 33 34 $this->parentIds = null; 35 } 36 37 /********************** 38 * elFinder functions * 39 *********************/ 40 41 /*** 42 * Main "connector" to handle all requests from elfinder 43 * 44 * @param $input 45 * 46 * @return array 47 * @throws Exception 48 */ 49 50 public function action_finder($input) 51 { 52 global $prefs, $user; 53 54 55 if ($this->parentIds === null) { 56 $ids = TikiLib::lib('filegal')->getGalleriesParentIds(); 57 $this->parentIds = [ 'galleries' => [], 'files' => [] ]; 58 foreach ($ids as $id) { 59 if ($id['parentId'] > 0) { 60 $this->parentIds['galleries'][(int) $id['galleryId']] = (int) $id['parentId']; 61 } 62 } 63 $tiki_files = TikiDb::get()->table('tiki_files'); 64 $this->parentIds['files'] = array_map('intval', $tiki_files->fetchMap('fileId', 'galleryId', [])); 65 } 66 67 // turn off some elfinder commands here too (stops the back-end methods being accessible) 68 $disabled = ['mkfile', 'edit', 'archive', 'resize']; 69 // done so far: 'rename', 'rm', 'duplicate', 'upload', 'copy', 'cut', 'paste', 'mkdir', 'extract', 70 71 // check for a "userfiles" gallery - currently although elFinder can support more than one root, it always starts in the first one 72 $opts = [ 73 'debug' => ($prefs['fgal_elfinder_debug'] === 'y'), 74 'roots' => [], 75 'bind' => [ 76 //check csrf prior to executing state-changing actions 77 'duplicate.pre mkdir.pre paste.pre rename.pre rm.pre upload.pre' => [ 78 [$this, 'csrfCheck'] 79 ] 80 ] 81 ]; 82 83 $rootDefaults = [ 84 'driver' => 'TikiFiles', // driver for accessing file system (REQUIRED) 85// 'path' => $rootId, // tiki root filegal - path to files (REQUIRED) - to be filled in later 86 'disabled' => $disabled, 87// 'URL' => // URL to files (seems not to be REQUIRED) 88 'accessControl' => [$this, 'elFinderAccess'], // obey tiki perms 89 'uploadMaxSize' => ini_get('upload_max_filesize'), 90 'accessControlData' => [ 91 'deepGallerySearch' => $input->deepGallerySearch->int(), 92 'parentIds' => $this->parentIds, 93 ], 94 'alias' => tr('Default Root Gallery'), // just in case 95 ]; 96 97 // gallery to start in 98 $startGallery = $input->defaultGalleryId->int(); 99 100 if ($startGallery) { 101 $gal_info = TikiLib::lib('filegal')->get_file_gallery_info($startGallery); 102 if (! $gal_info) { 103 Feedback::error(tr('Gallery ID %0 not found', $startGallery)); 104 $startGallery = $prefs['fgal_root_id']; 105 } 106 } 107 108 // 'startPath' not functioning with multiple roots as yet (https://github.com/Studio-42/elFinder/issues/351) 109 // so work around it for now with startRoot 110 111 $opts['roots'][] = array_merge( 112 // normal file gals 113 $rootDefaults, 114 [ 115 'path' => $prefs['fgal_root_id'], // should be a function? 116 'alias' => tr('File Galleries'), 117 ] 118 ); 119 $startRoot = 0; 120 121 if (! empty($user) && $prefs['feature_userfiles'] == 'y' && $prefs['feature_use_fgal_for_user_files'] == 'y') { 122 if ($startGallery && $startGallery == $prefs['fgal_root_user_id'] && ! Perms::get('file gallery', $startGallery)->admin_file_galleries) { 123 $startGallery = (int) TikiLib::lib('filegal')->get_user_file_gallery(); 124 } 125 $userRootId = $prefs['fgal_root_user_id']; 126 127 if ($startGallery != $userRootId) { 128 $gal_info = TikiLib::lib('filegal')->get_file_gallery_info($startGallery); 129 if ($gal_info['type'] == 'user') { 130 $startRoot = count($opts['roots']); 131 } 132 } else { 133 $startRoot = count($opts['roots']); 134 } 135 $opts['roots'][] = array_merge( 136 $rootDefaults, 137 [ 138 'path' => $userRootId, // should be $prefs['fgal_root_id']? 139 'alias' => tr('Users File Galleries'), 140 ] 141 ); 142 } 143 144 if ($prefs['feature_wiki_attachments'] == 'y' && $prefs['feature_use_fgal_for_wiki_attachments'] === 'y') { 145 if ($startGallery && $startGallery == $prefs['fgal_root_wiki_attachments_id']) { 146 $startRoot = count($opts['roots']); 147 } 148 $opts['roots'][] = array_merge( 149 $rootDefaults, 150 [ 151 'path' => $prefs['fgal_root_wiki_attachments_id'], // should be $prefs['fgal_root_id']? 152 'alias' => tr('Wiki Attachments'), 153 ] 154 ); 155 } 156 157 if ($startGallery) { 158 $opts['startRoot'] = $startRoot; 159 $d = $opts['roots'][$startRoot]['path'] == $startGallery ? '' : 'd_'; // needs to be the cached name in elfinder (with 'd_' in front) unless it's the root id 160 $opts['roots'][$startRoot]['startPath'] = $d . $startGallery; 161 } 162 163/* thumb size not working due to css issues - tried this in setup/javascript.php but needs extensive css overhaul to get looking right 164 if ($prefs['fgal_elfinder_feature'] === 'y') { 165 $tmbSize = (int) $prefs['fgal_thumb_max_size'] / 2; 166 TikiLib::lib('header')->add_css(".elfinder-cwd-icon {width:{$tmbSize}px; height:{$tmbSize}px;}"); // def 48 167 $tmbSize += 4; // def 52 168 TikiLib::lib('header')->add_css(".elfinder-cwd-view-icons .elfinder-cwd-file-wrapper {width:{$tmbSize}px; height:{$tmbSize}px;}"); 169 $tmbSize += 28; $tmbSizeW = $tmbSize + 40; // def 120 x 80 170 TikiLib::lib('header')->add_css(".elfinder-cwd-view-icons .elfinder-cwd-file {width: {$tmbSizeW}px;height: {$tmbSize}px;}"); 171 } 172*/ 173 // run elFinder 174 175 session_write_close(); 176 177 $elFinder = new tikiElFinder($opts); 178 $connector = new elFinderConnector($elFinder); 179 180 $filegallib = TikiLib::lib('filegal'); 181 if ($input->cmd->text() === 'tikiFileFromHash') { // intercept tiki only commands 182 $fileId = $elFinder->realpath($input->hash->text()); 183 if (strpos($fileId, 'f_') !== false) { 184 $info = $filegallib->get_file(str_replace('f_', '', $fileId)); 185 } else { 186 $info = $filegallib->get_file_gallery(str_replace('d_', '', $fileId)); 187 } 188 $params = []; 189 if ($input->filegals_manager->text()) { 190 $params['filegals_manager'] = $input->filegals_manager->text(); 191 } 192 if ($input->insertion_syntax->text()) { 193 $params['insertion_syntax'] = $input->insertion_syntax->text(); 194 } 195 $info['wiki_syntax'] = $filegallib->getWikiSyntax($info['galleryId'], empty($info['fileId']) ? [] : $info, $params); 196 $info['data'] = ''; // binary data makes JSON fall over 197 return $info; 198 } elseif ($input->cmd->text() === 'file') { 199 // intercept download command and use tiki-download_file so the mime type and extension is correct 200 $fileId = $elFinder->realpath($input->target->text()); 201 if (strpos($fileId, 'f_') !== false) { 202 global $base_url; 203 204 $fileId = str_replace('f_', '', $fileId); 205 $display = ''; 206 207 $url = $base_url . 'tiki-download_file.php?fileId=' . $fileId; 208 209 if (! $input->download->int()) { // images can be displayed 210 $info = $filegallib->get_file($fileId); 211 212 if (strpos($info['filetype'], 'image/') !== false) { 213 $url .= '&display'; 214 } elseif ($prefs['fgal_viewerjs_feature'] === 'y' && 215 ($info['filetype'] === 'application/pdf' or 216 strpos($info['filetype'], 'application/vnd.oasis.opendocument.') !== false)) { 217 $url = TikiLib::lib('access')->absoluteUrl($prefs['fgal_viewerjs_uri'] . '#' . $url); 218 } 219 } 220 221 TikiLib::lib('access')->redirect($url); 222 return []; 223 } 224 } 225 226 // elfinder needs "raw" $_GET or $_POST 227 if ($_SERVER["REQUEST_METHOD"] == 'POST') { 228 $_POST = $input->asArray(); 229 } else { 230 $_GET = $input->asArray(); 231 TikiLib::lib('access')->setTicket(); 232 } 233 234 $connector->run(); 235 // deals with response 236 237 return []; 238 } 239 240 /** 241 * elFinderAccess "accessControl" callback. 242 * 243 * @param string $attr attribute name (read|write|locked|hidden) 244 * @param string $path file path relative to volume root directory started with directory separator 245 * @param $data 246 * @param $volume 247 * 248 * @return bool|null 249 * @throws Exception 250 */ 251 function elFinderAccess($attr, $path, $data, $volume) 252 { 253 global $prefs; 254 255 $ar = explode('_', $path); 256 $visible = true; // for now 257 if (count($ar) === 2) { 258 $isgal = $ar[0] === 'd'; 259 $id = $ar[1]; 260 if ($isgal) { 261 $visible = $this->isVisible($id, $data, $isgal); 262 } else { 263 $visible = $this->isVisible($this->parentIds['files'][$id], $data, $isgal); 264 } 265 } else { 266 $isgal = true; 267 $id = $path; 268 } 269 270 if ($isgal) { 271 $perms = TikiLib::lib('tiki')->get_perm_object($id, 'file gallery', TikiLib::lib('filegal')->get_file_gallery_info($id)); 272 } else { 273 $perms = TikiLib::lib('tiki')->get_perm_object($id, 'file', TikiLib::lib('filegal')->get_file($id)); 274 } 275 276 $perms = array_merge([ 277 'tiki_p_admin_file_galleries' => 'n', 278 'tiki_p_download_files' => 'n', 279 'tiki_p_upload_files' => 'n', 280 'tiki_p_view_file_gallery' => 'n', 281 'tiki_p_remove_files' => 'n', 282 'tiki_p_create_file_galleries' => 'n', 283 'tiki_p_edit_gallery_file' => 'n', 284 'tiki_p_list_file_galleries' => 'n', 285 'tiki_p_assign_perm_file_gallery' => 'n', 286 'tiki_p_batch_upload_file_dir' => 'n', 287 'tiki_p_batch_upload_files' => 'n', 288 'tiki_p_view_fgal_explorer' => 'n', 289 'tiki_p_view_fgal_path' => 'n', 290 'tiki_p_upload_javascript' => 'n', 291 'tiki_p_upload_svg' => 'n', 292 ], $perms); 293 294 switch ($attr) { 295 case 'read': 296 if ($isgal) { 297 return $visible && ($perms['tiki_p_view_file_gallery'] === 'y' || $id == $prefs['fgal_root_id']); 298 } else { 299 return $visible && $perms['tiki_p_download_files'] === 'y'; 300 } 301 case 'write': 302 if ($isgal) { 303 return $visible && ($perms['tiki_p_admin_file_galleries'] === 'y' || $perms['tiki_p_upload_files'] === 'y'); 304 } else { 305 return $visible && ($perms['tiki_p_edit_gallery_file'] === 'y' || $perms['tiki_p_remove_files'] === 'y'); 306 } 307 case 'locked': 308 case 'hidden': 309 return ! $visible; 310 default: 311 return false; 312 } 313 } 314 315 private function isVisible($id, $data, $isgal) 316 { 317 $visible = true; 318 319 if (! empty($data['startPath'])) { 320 if ($data['startPath'] == $id) { // is startPath 321 $visible = true; 322 return $visible; 323 } else { 324 $isParentOf = $this->isParentOf($id, $data['startPath'], $this->parentIds['galleries']); 325 326 if (isset($data['deepGallerySearch']) && $data['deepGallerySearch'] == 0) { // not startPath and not deep 327 if ($isParentOf && $isgal) { 328 $visible = true; 329 return $visible; 330 } else { 331 $visible = false; 332 return $visible; 333 } 334 } else { 335 if ($isParentOf && $isgal) { 336 $visible = true; 337 } else { 338 $visible = false; 339 } 340 $pid = $this->parentIds['galleries'][$id]; 341 while ($pid) { 342 if ($pid == $data['startPath']) { 343 $visible = true; 344 break; 345 } 346 $pid = $this->parentIds['galleries'][$pid]; 347 } 348 return $visible; 349 } 350 } 351 } 352 return $visible; 353 } 354 355 private function isParentOf($id, $child, $parentIds) 356 { 357 if (! isset($parentIds[$child])) { 358 return false; 359 } elseif ($parentIds[$child] == $id) { 360 return true; 361 } else { 362 return $this->isParentOf($child, $parentIds[$child], $parentIds); 363 } 364 } 365 366 /** 367 * Anti-CSRF check. To be run pre-execution of commands that change the database 368 * 369 * @param $cmd 370 * @param $args 371 * @param $elfinder tikiElFinder 372 * @param $volume elFinderVolumeTikiFiles 373 * 374 * @return mixed $results array 375 * @throws Services_Exception 376 */ 377 public function csrfCheck($cmd, &$args, $elfinder, $volume) 378 { 379 $access = TikiLib::lib('access'); 380 //don't unset ticket since multiple actions may be performed without refreshing the page 381 if ($access->checkCsrf('none', false)) { 382 $access->setTicket(); 383 $elfinder->setCustomData('ticket', $access->getTicket()); 384 } else { 385 return [ 386 'preventexec' => true, 387 'results' => [ 388 'error' => tr('Potential cross-site request forgery (CSRF) detected. Operation blocked. Reloading the page may help.') 389 ] 390 ]; 391 } 392 } 393}