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 8use Tiki\File\PDFHelper; 9 10class Tracker_Field_Files extends Tracker_Field_Abstract implements Tracker_Field_Exportable 11{ 12 public static function getTypes() 13 { 14 global $prefs; 15 16 $options = [ 17 'FG' => [ 18 'name' => tr('Files'), 19 'description' => tr('Attached and upload files stored in the file galleries to the tracker item.'), 20 'prefs' => ['trackerfield_files', 'feature_file_galleries'], 21 'tags' => ['advanced'], 22 'help' => 'Files Tracker Field', 23 'default' => 'y', 24 'params' => [ 25 'galleryId' => [ 26 'name' => tr('Gallery ID'), 27 'description' => tr('File gallery to upload new files into.'), 28 'filter' => 'int', 29 'legacy_index' => 0, 30 'profile_reference' => 'file_gallery', 31 ], 32 'filter' => [ 33 'name' => tr('MIME Type Filter'), 34 'description' => tr('Mask for accepted MIME types in the field'), 35 'filter' => 'text', 36 'legacy_index' => 1, 37 ], 38 'count' => [ 39 'name' => tr('File Count'), 40 'description' => tr('Maximum number of files to be attached on the field.'), 41 'filter' => 'int', 42 'legacy_index' => 2, 43 ], 44 'displayMode' => [ 45 'name' => tr('Display Mode'), 46 'description' => tr('Show files as object links or via a wiki plugin (img so far)'), 47 'filter' => 'word', 48 'options' => [ 49 '' => tr('Links'), 50 'barelink' => tr('Bare Links'), 51 'table' => tr('Table'), 52 'img' => tr('Images'), 53 'vimeo' => tr('Vimeo'), 54 'googleviewer' => tr('Google Viewer'), 55 'moodlescorm' => tr('Moodle Scorm Viewer'), 56 ], 57 ], 58 'displayParams' => [ 59 'name' => tr('Display parameters'), 60 'description' => tr('URL-encoded parameters used such as in the {img} plugin, for example,.') . ' "max=400&desc=namedesc&stylebox=block"', 61 'filter' => 'text', 62 ], 63 'displayParamsForLists' => [ 64 'name' => tr('Display parameters for lists'), 65 'description' => tr('URL-encoded parameters used such as in the {img} plugin, for example,.') . ' "thumb=box&max=60"', 66 'filter' => 'text', 67 ], 68 'displayOrder' => [ 69 'name' => tr('Display Order'), 70 'description' => tr('Sort order for the files'), 71 'filter' => 'word', 72 'options' => [ 73 '' => tr('Default (order added to tracker item)'), 74 'name_asc' => tr('Name (A - Z)'), 75 'name_desc' => tr('Name (Z - A)'), 76 'filename_asc' => tr('Filename (A - Z)'), 77 'filename_desc' => tr('Filename (Z - A)'), 78 'created_asc' => tr('Created date (old - new)'), 79 'created_desc' => tr('Created date (new - old)'), 80 'lastModif_asc' => tr('Last modified date (old - new)'), 81 'lastModif_desc' => tr('Last modified date (new - old)'), 82 'filesize_asc' => tr('File size (small - large)'), 83 'filesize_desc' => tr('File size (large - small)'), 84 'hits_asc' => tr('Hits (low - high)'), 85 'hits_desc' => tr('Hits (high - low)'), 86 ], 87 ], 88 'deepGallerySearch' => [ 89 'name' => tr('Include Child Galleries'), 90 'description' => tr('Use files from child galleries as well.'), 91 'filter' => 'int', 92 'options' => [ 93 0 => tr('No'), 94 1 => tr('Yes'), 95 ], 96 ], 97 'replace' => [ 98 'name' => tr('Replace Existing File'), 99 'description' => tr('Replace the existing file, if any, instead of uploading a new one.'), 100 'filter' => 'alpha', 101 'default' => 'n', 102 'options' => [ 103 'n' => tr('No'), 104 'y' => tr('Yes'), 105 ], 106 ], 107 'browseGalleryId' => [ 108 'name' => tr('Browse Gallery ID'), 109 'description' => tr('File gallery browse files. Use 0 for root file gallery. (requires elFinder feature - experimental)') . '. ' . tr('Restrict permissions to view the file gallery to hide the button.') , 110 'filter' => 'int', 111 'profile_reference' => 'file_gallery', 112 ], 113 'duplicateGalleryId' => [ 114 'name' => tr('Duplicate Gallery ID'), 115 'description' => tr('File gallery to duplicate files into when copying the tracker item. 0 or empty means do not duplicate (default).'), 116 'filter' => 'int', 117 'profile_reference' => 'file_gallery', 118 ], 119 'indexGeometry' => [ 120 'name' => tr('Index As Map Layer'), 121 'description' => tr('Index the files in a specific format for use in map searchlayers to display trails and features.'), 122 'filter' => 'text', 123 'default' => '', 124 'options' => [ 125 '' => tr('No'), 126 'geojson' => tr('GeoJSON'), 127 'gpx' => tr('GPX'), 128 ], 129 ], 130 'uploadInModal' => [ 131 'name' => tr('Upload In Modal'), 132 'description' => tr('Upload files in a new modal window.'), 133 'filter' => 'alpha', 134 'default' => 'y', 135 'options' => [ 136 'n' => tr('No'), 137 'y' => tr('Yes'), 138 ], 139 ], 140 'image_x' => [ 141 'name' => tr('Maximum image width'), 142 'description' => tr('Leave blank to use selected gallery default setting or enter value in pixels to override gallery settings'), 143 'filter' => 'text', 144 'default' => '', 145 ], 146 'image_y' => [ 147 'name' => tr('Maximum image height'), 148 'description' => tr('Leave blank to use selected gallery default settings or enter value in pixels to override gallery settings'), 149 'filter' => 'text', 150 ], 151 'addDecriptionOnUpload' => [ 152 'name' => tr('Add Descriptions'), 153 'description' => tr('Add descriptions on uploaded files.'), 154 'filter' => 'alpha', 155 'default' => 'n', 156 'options' => [ 157 'n' => tr('No'), 158 'y' => tr('Yes'), 159 ], 160 ], 161 'requireTitle' => [ 162 'name' => tr('Require file title'), 163 'description' => tr('Require a file title which will be saved as the name of the file in the file gallery in addition to the filename. Upload In Modal required.'), 164 'filter' => 'alpha', 165 'default' => 'n', 166 'options' => [ 167 'n' => tr('No'), 168 'y' => tr('Yes'), 169 ], 170 ] 171 ], 172 ], 173 ]; 174 if (isset($prefs['vimeo_upload']) && $prefs['vimeo_upload'] === 'y') { 175 $options['FG']['params']['displayMode']['description'] = tr('Show files as object links or via a wiki plugin (img, Vimeo)'); 176 $options['FG']['params']['displayMode']['options']['vimeo'] = tr('Vimeo'); 177 } 178 return $options; 179 } 180 181 function getFieldData(array $requestData = []) 182 { 183 global $prefs; 184 $filegallib = TikiLib::lib('filegal'); 185 186 $galleryId = (int) $this->getOption('galleryId'); 187 $count = (int) $this->getOption('count'); 188 $deepGallerySearch = (boolean) $this->getOption('deepGallerySearch'); 189 190 // to use the user's userfiles gallery enter the fgal_root_user_id which is often (but not always) 2 191 $galleryId = $filegallib->check_user_file_gallery($galleryId); 192 193 $value = ''; 194 $ins_id = $this->getInsertId(); 195 if (isset($requestData[$ins_id])) { 196 // Incoming data from form 197 198 // Get the list of selected file IDs from the text field 199 $value = $requestData[$ins_id]; 200 $fileIds = explode(',', $value); 201 202 // Remove missed uploads 203 $fileIds = array_filter($fileIds); 204 205 // Obtain the info for display and filter by type if specified 206 $fileInfo = $this->getFileInfo($fileIds); 207 $fileInfo = array_filter($fileInfo, [$this, 'filterFile']); 208 209 // Rebuild the database value, but preserve the order the files have been attached to the item 210 foreach ($fileIds as & $fileId) { 211 if (! isset($fileInfo[$fileId])) { 212 $fileId = 0; 213 } 214 } 215 216 // Keep only the last files if a limit is applied 217 if ($count) { 218 $fileIds = array_filter($fileIds); 219 $fileIds = array_slice($fileIds, -$count); 220 $value = implode(',', $fileIds); 221 } else { 222 $value = implode(',', array_filter($fileIds)); 223 } 224 } else { 225 $value = $this->getValue(); 226 227 // Obtain the information from the database for display 228 $fileIds = array_filter(explode(',', $value)); 229 $fileInfo = $this->getFileInfo($fileIds); 230 } 231 232 if ($deepGallerySearch) { 233 $gallery_list = null; 234 $filegallib->getGalleryIds($gallery_list, $galleryId, 'list'); 235 $gallery_list = implode(' or ', $gallery_list); 236 } else { 237 $gallery_list = $galleryId; 238 } 239 240 if ($this->getOption('displayMode') == 'img' && $fileIds) { 241 $firstfile = $fileIds[0]; 242 } else { 243 $firstfile = 0; 244 } 245 246 $galinfo = $filegallib->get_file_gallery($galleryId); 247 if (! $galinfo) { 248 Feedback::error(tr('Files field: Gallery #%0 not found', $galleryId)); 249 return []; 250 } 251 if ($prefs['feature_use_fgal_for_user_files'] !== 'y' || $galinfo['type'] !== 'user') { 252 $perms = Perms::get('file gallery', $galleryId); 253 $canUpload = $perms->upload_files; 254 } else { 255 global $user; 256 $perms = TikiLib::lib('tiki')->get_local_perms($user, $galleryId, 'file gallery', $galinfo, false); //get_perm_object($galleryId, 'file gallery', $galinfo); 257 $canUpload = $perms['tiki_p_upload_files'] === 'y'; 258 } 259 260 $image_x = $this->getOption('image_x'); 261 $image_y = $this->getOption('image_y'); 262 263 //checking if image_x and image_y are set 264 if (! $image_x) { 265 $image_x = $galinfo['image_max_size_x']; 266 } 267 268 if (! $image_y) { 269 $image_y = $galinfo['image_max_size_y']; 270 } 271 272 273 274 return [ 275 'galleryId' => $galleryId, 276 'canUpload' => $canUpload, 277 'limit' => $count, 278 'files' => $fileInfo, 279 'firstfile' => $firstfile, 280 'value' => $value, 281 'filter' => $this->getOption('filter'), 282 'image_x' => $image_x, 283 'image_y' => $image_y, 284 'gallerySearch' => $gallery_list, 285 'requireTitle' => $this->getOption('requireTitle'), 286 ]; 287 } 288 289 function renderInput($context = []) 290 { 291 global $prefs; 292 293 $context['canBrowse'] = false; 294 295 if ($prefs['fgal_tracker_existing_search'] == 'y') { 296 if ($this->getOption('browseGalleryId')) { 297 $defaultGalleryId = $this->getOption('browseGalleryId'); 298 } elseif ($this->getOption('galleryId')) { 299 $defaultGalleryId = $this->getOption('galleryId'); 300 } else { 301 $defaultGalleryId = 0; 302 } 303 $deepGallerySearch = $this->getOption('deepGallerySearch'); 304 //in case $deepGallerySearch is false 305 $deepGallerySearch = $deepGallerySearch == 1 ? 1 : 0; 306 $image_x = $this->getOption('image_x'); 307 $image_y = $this->getOption('image_y'); 308 309 if ($prefs['fgal_elfinder_feature'] == 'y') { 310 $smarty = TikiLib::lib('smarty'); 311 $smarty->loadPlugin('smarty_function_ticket'); 312 $context['onclick'] = 'return openElFinderDialog(this, { 313 defaultGalleryId:' . $defaultGalleryId . ', 314 deepGallerySearch: ' . $deepGallerySearch . ', 315 ticket: \'' . smarty_function_ticket(['mode' => 'get'], $smarty) . '\', 316 getFileCallback: function(file,elfinder){ window.handleFinderFile(file,elfinder); }, 317 eventOrigin:this 318});'; 319 } 320 $context['galleryId'] = $defaultGalleryId; 321 $context['canBrowse'] = Perms::get(['type' => 'file gallery', 'object' => $defaultGalleryId])->view_file_gallery; 322 } 323 324 return $this->renderTemplate('trackerinput/files.tpl', $context, [ 325 'replaceFile' => 'y' == $this->getOption('replace', 'n'), 326 'addDecriptionOnUpload' => $this->getOption('addDecriptionOnUpload') === 'y' ? 1 : 0, 327 ]); 328 } 329 330 function renderOutput($context = []) 331 { 332 global $prefs; 333 global $mimetypes; 334 include('lib/mime/mimetypes.php'); 335 $galleryId = (int)$this->getOption('galleryId'); 336 337 if (! isset($context['list_mode'])) { 338 $context['list_mode'] = 'n'; 339 } 340 if (! $this->getOption('displayOrder')) { 341 $value = $this->getValue(); 342 } else { 343 $value = $this->getConfiguration('files'); 344 $value = implode(',', array_keys($value)); 345 } 346 347 if ($context['list_mode'] === 'csv') { 348 return $value; 349 } 350 351 if ($context['list_mode'] === 'text') { 352 return implode( 353 "\n", 354 array_map( 355 function ($file) { 356 return $file['name']; 357 }, 358 $this->getConfiguration('files') 359 ) 360 ); 361 } 362 363 $ret = ''; 364 if (empty($value)) { 365 $ret = ' '; 366 } else { 367 if ($this->getOption('displayMode')) { // images etc 368 $params = [ 369 'fileId' => $value, 370 ]; 371 if ($context['list_mode'] === 'y') { 372 $otherParams = $this->getOption('displayParamsForLists'); 373 } else { 374 $otherParams = $this->getOption('displayParams'); 375 } 376 if ($otherParams) { 377 parse_str($otherParams, $otherParams); 378 $params = array_merge($params, $otherParams); 379 } 380 $params['fromFieldId'] = $this->getConfiguration('fieldId'); 381 $params['fromItemId'] = $this->getItemId(); 382 $item = Tracker_Item::fromInfo($this->getItemData()); 383 $params['checkItemPerms'] = $item->canModify() ? 'n' : 'y'; 384 385 if ($this->getOption('displayMode') == 'img') { // img 386 if ($context['list_mode'] === 'y') { 387 $params['thumb'] = $context['list_mode']; 388 $params['rel'] = 'box[' . $this->getInsertId() . ']'; 389 } 390 include_once('lib/wiki-plugins/wikiplugin_img.php'); 391 $ret = wikiplugin_img('', $params); 392 } elseif ($this->getOption('displayMode') == 'vimeo') { // Vimeo videos stored as filegal REMOTEs 393 include_once('lib/wiki-plugins/wikiplugin_vimeo.php'); 394 $ret = wikiplugin_vimeo('', $params); 395 } elseif ($this->getOption('displayMode') == 'moodlescorm') { 396 include_once('lib/wiki-plugins/wikiplugin_playscorm.php'); 397 foreach ($this->getConfiguration('files') as $fileId => $file) { 398 $params['fileId'] = $fileId; 399 $ret .= wikiplugin_playscorm('', $params); 400 } 401 } elseif ($this->getOption('displayMode') == 'googleviewer') { 402 if ($prefs['auth_token_access'] != 'y') { 403 $ret = tra('Token access needs to be enabled for Google viewer to be used'); 404 } else { 405 $files = []; 406 foreach ($this->getConfiguration('files') as $fileId => $file) { 407 global $base_url, $tikiroot, $https_mode; 408 if ($https_mode) { 409 $scheme = 'https'; 410 } else { 411 $scheme = 'http'; 412 } 413 $googleurl = $scheme . "://docs.google.com/viewer?url="; 414 if ($prefs['feature_sefurl'] === 'y') { 415 $fileurl = urlencode($base_url . "dl" . $fileId); 416 } else { 417 $fileurl = urlencode($base_url . "tiki-download_file.php?fileId=" . $fileId); 418 } 419 require_once 'lib/auth/tokens.php'; 420 $tokenlib = AuthTokens::build($prefs); 421 if ($prefs['feature_sefurl'] === 'y') { 422 $token = $tokenlib->createToken( 423 $tikiroot . "dl" . $fileId, 424 [], 425 ['Registered'], 426 ['timeout' => 600, 'hits' => 6] 427 ); 428 $fileurl .= urlencode("?TOKEN=" . $token); 429 } else { 430 $token = $tokenlib->createToken( 431 $tikiroot . "tiki-download_file.php", 432 ['fileId' => $fileId], 433 ['Registered'], 434 ['timeout' => 600, 'hits' => 6] 435 ); 436 $fileurl .= urlencode("&TOKEN=" . $token); 437 } 438 $url = $googleurl . $fileurl . '&embedded=true'; 439 $title = $file['name']; 440 $files[] = ['url' => $url, 'title' => $title, 'id' => $fileId]; 441 } 442 $smarty = TikiLib::lib('smarty'); 443 $smarty->assign('files', $files); 444 $ret = $smarty->fetch('trackeroutput/files_googleviewer.tpl'); 445 } 446 } elseif ($this->getOption('displayMode') == 'barelink') { 447 $smarty = TikiLib::lib('smarty'); 448 $smarty->loadPlugin('smarty_function_object_link'); 449 $smarty->loadPlugin('smarty_modifier_sefurl'); 450 foreach ($this->getConfiguration('files') as $fileId => $file) { 451 $ret .= smarty_modifier_sefurl($file['fileId'], 'file'); 452 } 453 } elseif ($this->getOption('displayMode') == 'table') { 454 $ret = $this->renderTemplate('trackeroutput/files_table.tpl', $context, [ 455 'files' => $this->getConfiguration('files') 456 ]); 457 } 458 $ret = preg_replace('/~\/?np~/', '', $ret); 459 } else { 460 $smarty = TikiLib::lib('smarty'); 461 $smarty->loadPlugin('smarty_function_object_link'); 462 $smarty->loadPlugin('smarty_modifier_iconify'); 463 $ret = '<ol class="tracker-item-files">'; 464 465 foreach ($this->getConfiguration('files') as $fileId => $file) { 466 $ret .= '<li class="m-1">'; 467 if ($prefs['vimeo_upload'] == 'y' && $this->getOption('displayMode') == 'vimeo') { 468 $ret .= smarty_function_icon(['name' => 'vimeo'], $smarty->getEmptyInternalTemplate()); 469 } else { 470 $ret .= smarty_modifier_iconify('tiki-download_file.php?fileId=' . $fileId, $file['filetype'], $fileId, 2); 471 } 472 473 $ret .= smarty_function_object_link(['type' => 'file', 'id' => $fileId, 'title' => $file['name']], $smarty->getEmptyInternalTemplate()); 474 475 $globalperms = Perms::get([ 'type' => 'file gallery', 'object' => $galleryId ]); 476 477 if ($prefs['feature_draw'] == 'y' && 478 $globalperms->upload_files == 'y' && 479 ($file['filetype'] == $mimetypes["svg"] || 480 $file['filetype'] == $mimetypes["gif"] || 481 $file['filetype'] == $mimetypes["jpg"] || 482 $file['filetype'] == $mimetypes["png"] || 483 $file['filetype'] == $mimetypes["tiff"]) 484 ) { 485 $smarty->loadPlugin('smarty_function_icon'); 486 $editicon = smarty_function_icon(['name' => 'edit'], $smarty->getEmptyInternalTemplate()); 487 $ret .= " <a href='tiki-edit_draw.php?fileId=" . $file['fileId'] 488 . "' onclick='return $(this).ajaxEditDraw();' class='tips' title='Edit: " . $file['name'] 489 . "' data-fileid='" . $file['fileId'] . "' data-galleryid='" . $galleryId . "'> 490 $editicon 491 </a>"; 492 } 493 494 $smarty->loadPlugin('smarty_function_icon'); 495 $viewicon = smarty_function_icon(['name' => 'view'], $smarty->getEmptyInternalTemplate()); 496 497 if ($prefs['fgal_pdfjs_feature'] == 'y' && 498 ($file['filetype'] == $mimetypes["pdf"] || (PDFHelper::canConvertToPDF($file['filetype']) && $prefs['fgal_convert_documents_pdf'] == 'y')) 499 ) { 500 $ret .= " <a href='tiki-display.php?fileId=" . $file['fileId'] 501 . "' target='_blank' class='tips' title='Preview: " . $file['filename'] . "'> 502 $viewicon 503 </a>"; 504 } elseif (strpos($file['filetype'], 'video/') === 0) { 505 $src = smarty_modifier_sefurl($file['fileId'], 'display'); 506 507 $ret .= " <a href='$src' target='_blank' class='tips' title='Preview: " . $file['filename'] . "' data-box='box-type=video'> 508 $viewicon 509 </a>"; 510 } else { 511 $dataAttributes = []; 512 513 if ($file['filetype'] === 'text/plain') { 514 $dataAttributes[] = 'data-is-text="1"'; 515 } 516 517 $src = smarty_modifier_sefurl($file['fileId'], 'display'); 518 $ret .= " <a href='" . $src . "' target='_blank' class='tips' title='Preview: " . $file['filename'] . "' data-box='box-" . $this->getConfiguration('fieldId') . "' " . implode(' ', $dataAttributes) . "> 519 $viewicon 520 </a>"; 521 } 522 523 $ret .= '</li>'; 524 } 525 $ret .= '</ol>'; 526 } 527 } 528 return $ret; 529 } 530 531 function handleSave($value, $oldValue) 532 { 533 $new = array_diff(explode(',', $value), explode(',', $oldValue)); 534 $remove = array_diff(explode(',', $oldValue), explode(',', $value)); 535 536 $itemId = $this->getItemId(); 537 538 if ($itemId) { 539 $relationlib = TikiLib::lib('relation'); 540 $relations = $relationlib->get_relations_from('trackeritem', $itemId, 'tiki.file.attach'); 541 foreach ($relations as $existing) { 542 if ($existing['type'] != 'file') { 543 continue; 544 } 545 546 if (in_array($existing['itemId'], $remove)) { 547 $relationlib->remove_relation($existing['relationId']); 548 } 549 } 550 551 foreach ($new as $fileId) { 552 if (! empty($fileId)) { 553 $relationlib->add_relation('tiki.file.attach', 'trackeritem', $itemId, 'file', $fileId); 554 } 555 } 556 } 557 558 return [ 559 'value' => $value, 560 ]; 561 } 562 563 /** 564 * called from action_clone_item and duplicates the related files if option duplicateGalleryID is set 565 */ 566 function handleClone() 567 { 568 global $prefs; 569 570 $oldValue = $this->getValue(); 571 if ($galleryId = $this->getOption('duplicateGalleryId')) { 572 $filegallib = TikiLib::lib('filegal'); 573 574 // to use the user's userfiles gallery enter the fgal_root_user_id which is often (but not always) 2 575 $galleryId = $filegallib->check_user_file_gallery($galleryId); 576 577 $newIds = []; 578 579 foreach (array_filter(explode(',', $oldValue)) as $fileId) { 580 $newIds[] = $filegallib->duplicate_file($fileId, $galleryId); 581 } 582 583 return $this->handleSave(implode(',', $newIds), $oldValue); 584 } 585 return [ 586 'value' => $oldValue, 587 ]; 588 } 589 590 function watchCompare($old, $new) 591 { 592 $name = $this->getConfiguration('name'); 593 $isVisible = $this->getConfiguration('isHidden', 'n') == 'n'; 594 595 if (! $isVisible) { 596 return; 597 } 598 599 $filegallib = TikiLib::lib('filegal'); 600 601 $oldFileIds = explode(',', $old); 602 $newFileIds = explode(',', $new); 603 604 $oldFileInfos = empty($oldFileIds) ? [] : $filegallib->get_files_info(null, $oldFileIds); 605 $newFileInfos = empty($newFileIds) ? [] : $filegallib->get_files_info(null, $newFileIds); 606 607 $oldValueLines = ''; 608 foreach ($oldFileInfos as $info) { 609 $oldValueLines .= '> ' . $info['filename']; 610 } 611 $newValueLines = ''; 612 foreach ($newFileInfos as $info) { 613 $newValueLines .= '> ' . $info['filename']; 614 } 615 616 return "[-[$name]-]:\n--[Old]--:\n$oldValueLines\n\n*-[New]-*:\n$newValueLines"; 617 } 618 619 public function renderDiff($context = []) 620 { 621 $smarty = TikiLib::lib('smarty'); 622 $smarty->loadPlugin('smarty_modifier_sefurl'); 623 $smarty->loadPlugin('smarty_modifier_escape'); 624 $smarty->loadPlugin('smarty_modifier_iconify'); 625 626 if ($context['oldValue']) { 627 $old = $context['oldValue']; 628 } else { 629 $old = ''; 630 } 631 if ($context['value']) { 632 $new = $context['value']; 633 } else { 634 $new = $this->getValue(''); 635 } 636 if (empty($context['diff_style'])) { 637 $context['diff_style'] = 'sidediff'; 638 } 639 640 $filegallib = TikiLib::lib('filegal'); 641 642 $oldFileIds = explode(',', $old); 643 $newFileIds = explode(',', $new); 644 645 $filesRemoved = array_diff($oldFileIds, $newFileIds); 646 $filesAdded = array_diff($newFileIds, $oldFileIds); 647 648 $addedFileInfos = empty($filesAdded) ? [] : $filegallib->get_files_info(null, $filesAdded); 649 $removedFileInfos = empty($filesRemoved) ? [] : $filegallib->get_files_info(null, $filesRemoved); 650 651 $result = '<table class="table"><tr><td class="diffdeleted">-</td><td class="diffdeleted"><del class="diffchar deleted">'; 652 653 foreach ($removedFileInfos as $file) { 654 $url = smarty_modifier_sefurl($file['fileId'], 'file'); 655 $result .= smarty_modifier_iconify($url, $file['filetype'], $file['fileId'], 1); 656 $result .= ' <a href="' . $url . '">' . smarty_modifier_escape($file['name']) . '</a><br>'; 657 } 658 659 $result .= '</del></td><td class="diffadded">+</td><td class="diffadded"><ins class="diffchar inserted">'; 660 661 foreach ($addedFileInfos as $file) { 662 $url = smarty_modifier_sefurl($file['fileId'], 'file'); 663 $result .= smarty_modifier_iconify($url, $file['filetype'], $file['fileId'], 1); 664 $result .= ' <a href="' . $url . '">' . smarty_modifier_escape($file['name']) . '</a><br>'; 665 } 666 667 $result .= '</ins></td></tr></table>'; 668 669 return $result; 670 } 671 672 function filterFile($info) 673 { 674 $filter = $this->getOption('filter'); 675 676 if (! $filter) { 677 return true; 678 } 679 680 $parts = explode('*', $filter); 681 $parts = array_map('preg_quote', $parts, array_fill(0, count($parts), '/')); 682 683 $body = implode('[\w-]*', $parts); 684 685 // Force begin, ignore end which may contain charsets or other attributes 686 return preg_match("/^$body/", $info['filetype']); 687 } 688 689 private function getFileInfo($ids) 690 { 691 $db = TikiDb::get(); 692 $table = $db->table('tiki_files'); 693 694 $sortOrder = $this->getOption('displayOrder'); 695 696 $data = $table->fetchAll( 697 [ 698 'fileId', 699 'name', 700 'filename', 701 'filetype', 702 'archiveId', 703 'lastModif', 704 'description' 705 ], 706 [ 707 'fileId' => $table->in($ids), 708 ], 709 -1, 710 -1, 711 $table->sortMode($sortOrder) 712 ); 713 714 $out = []; 715 foreach ($data as $info) { 716 $out[$info['fileId']] = $info; 717 } 718 719 if (! $sortOrder) { // re-order result into order they were attached 720 $out2 = []; 721 foreach ($ids as $id) { 722 if (isset($out[$id])) { 723 $out2["$id"] = $out[$id]; 724 } else { 725 $itemId = $this->getItemId(); 726 if ($itemId) { 727 $smarty = TikiLib::lib('smarty'); 728 $smarty->loadPlugin('smarty_function_object_link'); 729 730 Feedback::warning( 731 tr( 732 'File #%0 missing (was attached to trackerfield "%1" on item %2)', 733 $id, 734 $this->getConfiguration('permName'), 735 smarty_function_object_link( 736 [ 737 'id' => $itemId, 738 'type' => 'trackeritem', 739 ], 740 $smarty 741 ) 742 ) 743 ); 744 } 745 } 746 } 747 $out = $out2; 748 } elseif (strstr($sortOrder, 'name')) { 749 $sep = strrpos($sortOrder, '_'); 750 $field = substr($sortOrder, 0, $sep); 751 $dir = substr($sortOrder, $sep + 1); 752 $sortArray = array_map(function ($file) use ($field) { 753 return isset($file[$field]) ? $file[$field] : ''; 754 }, $out); 755 natsort($sortArray); 756 if ($dir == 'desc') { 757 $sortArray = array_reverse($sortArray, true); 758 } 759 $sorted = []; 760 foreach ($sortArray as $key => $_) { 761 $sorted[$key] = $out[$key]; 762 } 763 $out = $sorted; 764 } 765 766 return $out; 767 } 768 769 private function handleUpload($galleryId, $file) 770 { 771 if (empty($file['tmp_name'])) { 772 // Not an actual file upload attempt, just skip 773 return false; 774 } 775 776 if (! is_uploaded_file($file['tmp_name'])) { 777 Feedback::error(tr('Problem with uploaded file: "%0"', $file['name'])); 778 return false; 779 } 780 781 $filegallib = TikiLib::lib('filegal'); 782 $gal_info = $filegallib->get_file_gallery_info($galleryId); 783 784 if (! $gal_info) { 785 Feedback::error(tr('No gallery for uploaded file, galleryId=%0', $galleryId)); 786 return false; 787 } 788 789 $perms = Perms::get('file gallery', $galleryId); 790 if (! $perms->upload_files) { 791 Feedback::error(tr('You don\'t have permission to upload a file to gallery "%0"', $gal_info['name'])); 792 return false; 793 } 794 795 $fileIds = $this->getConfiguration('files'); 796 797 if ($this->getOption('displayMode') == 'img' && is_array($fileIds) && count($fileIds) > 0) { 798 return $filegallib->update_single_file($gal_info, $file['name'], $file['size'], $file['type'], file_get_contents($file['tmp_name']), $fileIds[0]); 799 } else { 800 return $filegallib->upload_single_file($gal_info, $file['name'], $file['size'], $file['type'], file_get_contents($file['tmp_name'])); 801 } 802 } 803 804 function getDocumentPart(Search_Type_Factory_Interface $typeFactory) 805 { 806 if ($this->getOption('indexGeometry') && $this->getValue()) { 807 TikiLib::lib('smarty')->loadPlugin('smarty_modifier_sefurl'); 808 $urls = []; 809 810 foreach (explode(',', $this->getValue()) as $value) { 811 $urls[] = smarty_modifier_sefurl($value, 'file'); 812 } 813 return [ 814 'geo_located' => $typeFactory->identifier('y'), 815 'geo_file' => $typeFactory->identifier(implode(',', $urls)), 816 'geo_file_format' => $typeFactory->identifier($this->getOption('indexGeometry')), 817 ]; 818 } else { 819 return parent::getDocumentPart($typeFactory); 820 } 821 } 822 823 function getProvidedFields() 824 { 825 if ($this->getOption('indexGeometry') && $this->getValue()) { 826 return ['geo_located', 'geo_file', 'geo_file_format']; 827 } else { 828 return parent::getProvidedFields(); 829 } 830 } 831 832 function getGlobalFields() 833 { 834 if ($this->getOption('indexGeometry') && $this->getValue()) { 835 return []; 836 } else { 837 return parent::getGlobalFields(); 838 } 839 } 840 841 function getTabularSchema() 842 { 843 $schema = new Tracker\Tabular\Schema($this->getTrackerDefinition()); 844 845 $permName = $this->getConfiguration('permName'); 846 $name = $this->getConfiguration('name'); 847 848 $schema->addNew($permName, 'default') 849 ->setLabel($name) 850 ->setRenderTransform(function ($value) { 851 return $value; 852 }) 853 ->setParseIntoTransform(function (&$info, $value) use ($permName) { 854 $info['fields'][$permName] = $value; 855 }); 856 857 return $schema; 858 } 859} 860