get_file_gallery_info((int)$galleryId); } if (isset($gal_info['type']) && in_array($gal_info['type'], ['podcast', 'vidcast'])) { return true; } else { return false; } } public function is_default_gallery_writable() { $definition = new GalleryDefinition($this->default_file_gallery()); return $definition->isWritable(); } public function get_attachment_gallery($objectId, $objectType, $create = false) { switch ($objectType) { case 'wiki page': return $this->get_wiki_attachment_gallery($objectId, $create); } return false; } public function get_wiki_attachment_gallery($pageName, $create = false) { global $prefs; $return = $this->getGalleryId($pageName, $prefs['fgal_root_wiki_attachments_id']); // Get the Wiki Attachment Gallery for this wiki page or create it if it does not exist if ($create && ! $return) { // Create the attachment gallery only if the wiki page really exists if ($this->get_page_id_from_name($pageName) > 0) { $return = $this->replace_file_gallery( [ 'name' => $pageName, 'user' => 'admin', 'type' => 'attachments', 'public' => 'y', 'visible' => 'y', 'parentId' => $prefs['fgal_root_wiki_attachments_id'] ] ); } } return $return; } /** * Looks for and returns a user's file gallery, depending on the various prefs * * @return bool|int false if none found, id of user's filegal otherwise */ public function get_user_file_gallery($auser = '') { global $user, $prefs; $tikilib = TikiLib::lib('tiki'); if (empty($auser)) { $auser = $user; } // Feature check + Anonymous don't have their own Users File Gallery if (empty($auser) || $prefs['feature_use_fgal_for_user_files'] == 'n' || $prefs['feature_userfiles'] == 'n' || ( $userId = $tikilib->get_user_id($auser) ) <= 0) { return false; } $conditions = [ 'type' => 'user', 'name' => $userId, 'user' => $auser, 'parentId' => $prefs['fgal_root_user_id'] ]; if ($idGallery = $this->table('tiki_file_galleries')->fetchOne('galleryId', $conditions)) { // upgrades from very old tikis may have multiple user filegals per user, so merge them into one here unset($conditions['name']); $conditions['galleryId'] = $this->table('tiki_file_galleries')->not($idGallery); $rows = $this->table('tiki_file_galleries')->fetchAll(['galleryId'], $conditions); foreach ($rows as $row) { $this->table('tiki_files')->updateMultiple( ['galleryId' => $idGallery], // set gallery to the proper one (name eq userId) ['galleryId' => $row['galleryId']] // where gallery is this one ); $this->remove_file_gallery($row['galleryId']); } return $idGallery; } $fgal_info = $conditions; $fgal_info['public'] = 'n'; $fgal_info['visible'] = $prefs['userfiles_private'] === 'y' || $prefs['userfiles_hidden'] === 'y' ? 'n' : 'y'; $fgal_info['quota'] = $prefs['userfiles_quota']; // Create the user gallery if it does not exist yet $idGallery = $this->replace_file_gallery($fgal_info); return $idGallery; } /** * Functionality to migrate files from image galleries to file galleries */ final public function migrateFilesFromImageGalleries(): int { global $prefs; $attributelib = TikiLib::lib('attribute'); // the tables should have been renamed in installer/schema/20211020_mark_image_gallery_tables_as_unused_tiki.sql but let's check $tables = TikiDb::get()->listTables(); if (! in_array('tiki_galleries', $tables) && in_array('zzz_unused_tiki_galleries', $tables)) { $zzz = "zzz_unused_"; } else { $zzz = ''; } $tikiGalleries = TikiDb::get()->table("{$zzz}tiki_galleries"); $tikiGalleriesScales = TikiDb::get()->table("{$zzz}tiki_galleries_scales"); $tikiImages = TikiDb::get()->table("{$zzz}tiki_images"); $tikiImagesData = TikiDb::get()->table("{$zzz}tiki_images_data"); $galleryIdMap = []; $rootFileGalleryId = 0; if ($tikiImages->fetchCount([])) { $rootFileGalleryId = $this->replace_file_gallery([ 'name' => tra('Migrated Image Galleries'), 'description' => tra('Converted from image gallery'), ]); foreach ($tikiGalleries->fetchAll() as $gallery) { $gallery['sort_mode'] = $gallery['sortorder'] . '_' . $gallery['sortdirection']; $oldGalleryId = $gallery['galleryId']; $gallery['galleryId'] = 0; // we want a new one if ($gallery['parentgallery'] < 0 || empty($galleryIdMap[$gallery['parentgallery']])) { $gallery['parentId'] = $rootFileGalleryId; } else { $gallery['parentId'] = $galleryIdMap[$gallery['parentgallery']]; } $gallery['show_name'] = $gallery['showname']; $gallery['show_id'] = $gallery['showimageid']; $gallery['show_description'] = $gallery['showdescription']; $gallery['show_author'] = $gallery['showuser']; // TODO something about creator? $gallery['show_hits'] = $gallery['showhits']; if ($gallery['show_name'] === 'y' && $gallery['showfilename'] === 'y') { $gallery['show_name'] = 'a'; } elseif ($gallery['showfilename'] === 'y') { $gallery['show_name'] = 'f'; } else { $gallery['show_name'] = 'n'; } unset( $gallery['geographic'], $gallery['theme'], $gallery['rowImages'], $gallery['thumbSizeX'], $gallery['thumbSizeY'], $gallery['sortorder'], $gallery['sortdirection'], $gallery['galleryimage'], // TODO something? $gallery['parentgallery'], $gallery['showname'], $gallery['showimageid'], $gallery['showdescription'], $gallery['showcreated'], $gallery['showuser'], $gallery['showhits'], $gallery['showxysize'], $gallery['showfilesize'], $gallery['showname'], $gallery['showfilename'], $gallery['defaultscale'], // TODO something? $gallery['showcategories'] ); $fileGalleryId = $this->replace_file_gallery($gallery); $gallery['galleryId'] = $fileGalleryId; $galleryIdMap[$oldGalleryId] = $fileGalleryId; $images = $tikiImages->fetchAll([], ['galleryId' => $oldGalleryId]); foreach ($images as $image) { $imageData = $tikiImagesData->fetchAll([], [ 'type' => 'o', // not thumbnails 'imageId' => $image['imageId'], ]); $image = array_merge($imageData[0], $image); if (strlen($image['data']) < 3 && ! empty($image['path'])) { // read from disk if (file_exists($prefs['gal_use_dir'] . $image["path"])) { $image['data'] = file_get_contents($prefs['gal_use_dir'] . $image["path"]); } $image['path'] = ''; } $image['galleryId'] = $fileGalleryId; $file = new TikiFile([ 'galleryId' => $image['galleryId'], 'description' => $image['description'], 'user' => $image['user'], 'author' => $image['user'], 'created' => $image['created'], ]); $fileId = $file->replace($image['data'], $image['filetype'], $image['name'], $image['filename'], $image['xsize'], $image['ysize']); TikiLib::lib('geo')->set_coordinates( 'file', $fileId, [ 'lon' => $image['lon'], 'lat' => $image['lat'], ] ); // add the old imageId as an attribute for future use in the img plugin $attributelib->set_attribute('file', $fileId, 'tiki.file.imageid', $image['imageId']); } } } return $rootFileGalleryId; } /** * Calculate gallery name for user galleries * * @param array $gal_info gallery info array * @param string $auser optional user (global used if not supplied) * @return string name of gallery modified if a "top level" user galley */ public function get_user_gallery_name($gal_info, $auser = null) { global $user, $prefs; if ($auser === null) { $auser = $user; } $name = $gal_info['name']; if (! empty($auser) && $prefs['feature_use_fgal_for_user_files'] == 'y') { if ($gal_info['type'] === 'user' && $gal_info['parentId'] == $prefs['fgal_root_user_id']) { if ($gal_info['user'] === $auser) { $name = tra('My Files'); } else { $name = tr('Files of %0', TikiLib::lib('user')->clean_user($gal_info['user'])); } } } return $name; } /** * Checks if a galleryId is the user filegal root and converts it to the correct user gallery for that user * Otherwise just passes through * * @param $galleryId gallery id to check and change if necessary * @return int user's gallery id if applicable */ public function check_user_file_gallery($galleryId) { global $prefs; if ($prefs['feature_use_fgal_for_user_files'] === 'y' && $galleryId == $prefs['fgal_root_user_id']) { $galleryId = $this->get_user_file_gallery(); } return (int) $galleryId; } /** * @param $fileInfo * @param string $galInfo * @param bool $disable_notifications * @return bool|TikiDb_Adodb_Result|TikiDb_Pdo_Result * @throws Exception */ public function remove_file($fileInfo, $galInfo = '', $disable_notifications = false) { global $prefs, $user; if (empty($fileInfo['fileId'])) { return false; } $fileId = $fileInfo['fileId']; if ($prefs['vimeo_upload'] === 'y' && $prefs['vimeo_delete'] === 'y' && $fileInfo['filetype'] === 'video/vimeo') { $attributes = TikiLib::lib('attribute')->get_attributes('file', $fileId); if ($url = $attributes['tiki.content.url']) { $video_id = substr($url, strrpos($url, '/') + 1); // not ideal, but video_id not stored elsewhere (yet) TikiLib::lib('vimeo')->deleteVideo($video_id); } } $definition = $this->getGalleryDefinition($fileInfo['galleryId']); $this->deleteBacklinks(null, $fileId); $definition->delete(new TikiFile($fileInfo)); $archives = $this->get_archives($fileId); foreach ($archives['data'] as $archive) { $definition->delete(new TikiFile($archive)); $this->remove_object('file', $archive['fileId']); } $files = $this->table('tiki_files'); $result = $files->delete(['fileId' => $fileId]); $files->deleteMultiple(['archiveId' => $fileId]); $this->remove_draft($fileId); $this->remove_object('file', $fileId); //Watches if (! $disable_notifications) { $this->notify($fileInfo['galleryId'], $fileInfo['name'], $fileInfo['filename'], '', 'remove file', $user); } if ($prefs['feature_actionlog'] == 'y') { $logslib = TikiLib::lib('logs'); $logslib->add_action('Removed', $fileId . '/' . $fileInfo['filename'], 'file', ''); } TikiLib::events()->trigger( 'tiki.file.delete', [ 'type' => 'file', 'object' => $fileId, 'galleryId' => $fileInfo['galleryId'], 'filetype' => $fileInfo['filetype'], 'user' => $GLOBALS['user'], ] ); return $result; } /** * Remove all drafts of a file * * @param int $fileId * @param string $user * @return TikiDb_Adodb_Result|TikiDb_Pdo_Result */ public function remove_draft($fileId, $user = null) { $fileDraftsTable = $this->table('tiki_file_drafts'); if (isset($user)) { return $fileDraftsTable->delete(['fileId' => (int) $fileId, 'user' => $user]); } else { return $fileDraftsTable->deleteMultiple(['fileId' => (int) $fileId]); } } /** * Validate draft and replace real file * * @param int $fileId * @return bool|TikiDb_Adodb_Result|TikiDb_Pdo_Result * @throws Exception * @global string $user */ public function validate_draft($fileId) { global $prefs, $user; $fileDraftsTable = $this->table('tiki_file_drafts'); $galleriesTable = $this->table('tiki_file_galleries'); $filesTable = $this->table('tiki_files'); if ($prefs['feature_file_galleries_save_draft'] == 'y') { if (! $draft = $fileDraftsTable->fetchFullRow(['fileId' => (int) $fileId, 'user' => $user])) { return false; } $draft = TikiFileDraft::fromFileDraft($draft); $file = TikiFile::id($fileId); $file->validateDraft($draft); return $this->remove_draft($fileId, $user); } } /** * @param $file * @param $gallery * @return TikiDb_Adodb_Result|TikiDb_Pdo_Result */ public function set_file_gallery($file, $gallery) { $files = $this->table('tiki_files'); $result = $files->updateMultiple( ['galleryId' => $gallery], ['anyOf' => $files->expr('(`fileId` = ? OR `archiveId` = ?)', [$file, $file])] ); require_once('lib/search/refresh-functions.php'); refresh_index('files', $file); return $result; } /** * @param int $id ID of gallery to be removed or file in the gallery to be removed * @param int $galleryId The parent gallery of the gallery to be removed * @param bool $recurse * @return bool|TikiDb_Adodb_Result|TikiDb_Pdo_Result * @throws Exception */ public function remove_file_gallery($id, $galleryId = 0, $recurse = true) { global $prefs; $fileGalleries = $this->table('tiki_file_galleries'); $id = (int)$id; if ($id == $prefs['fgal_root_id']) { return false; } if (empty($galleryId)) { $info = $this->get_file_info($id); $galleryId = $info['galleryId']; } $cachelib = TikiLib::lib('cache'); $cachelib->empty_type_cache('fgals_perms_' . $id . "_"); if (isset($info['galleryId'])) { $cachelib->empty_type_cache('fgals_perms_' . $info['galleryId'] . "_"); } $cachelib->empty_type_cache($this->get_all_galleries_cache_type()); $result = $fileGalleries->delete(['galleryId' => $id]); $this->remove_object('file gallery', $id); if ($filesInfo = $this->get_files_info_from_gallery_id($id, false, false)) { foreach ($filesInfo as $fileInfo) { $this->remove_file($fileInfo, '', true); } } TikiLib::events()->trigger('tiki.filegallery.delete', [ 'type' => 'file gallery', 'object' => $galleryId, 'user' => $GLOBALS['user'], ]); // If $recurse, also recursively remove children galleries if ($recurse) { $galleries = $fileGalleries->fetchColumn( 'galleryId', ['parentId' => $id, 'galleryId' => $fileGalleries->greaterThan(0)] ); foreach ($galleries as $galleryId) { $this->remove_file_gallery($galleryId, $id, true); } } return $result; } /** * Fetch a complete set of file gallery information from the database * @param $id int Gallery Id to fetch information of. * * @return mixed */ public function get_file_gallery_info($id) { return $this->table('tiki_file_galleries')->fetchFullRow(['galleryId' => (int) $id]); } /** * @param $galleryId * @param $new_parent_id * @return bool|TikiDb_Adodb_Result|TikiDb_Pdo_Result * @throws Exception */ public function move_file_gallery($galleryId, $new_parent_id) { if ((int)$galleryId <= 0 || (int)$new_parent_id == 0 || $galleryId == $new_parent_id) { return false; } $cachelib = TikiLib::lib('cache'); $cachelib->empty_type_cache($this->get_all_galleries_cache_type()); return $this->table('tiki_file_galleries')->updateMultiple( ['parentId' => (int) $new_parent_id], ['galleryId' => (int) $galleryId] ); } public function default_file_gallery() { global $prefs; return [ 'name' => '', 'description' => '', 'visible' => 'y', 'public' => 'y', 'type' => 'default', 'parentId' => $prefs['fgal_root_id'], 'lockable' => 'n', 'archives' => 0, 'quota' => $prefs['fgal_quota_default'], 'image_max_size_x' => $prefs['fgal_image_max_size_x'], 'image_max_size_y' => $prefs['fgal_image_max_size_y'], 'backlinkPerms' => 'n', 'show_backlinks' => 'n', 'show_deleteAfter' => $prefs['fgal_list_deleteAfter'], 'show_lastDownload' => 'n', 'sort_mode' => $prefs['fgal_sortField'] . '_' . $prefs['fgal_sortDirection'], 'maxRows' => (int)$prefs['maxRowsGalleries'], 'max_desc' => 0, 'subgal_conf' => '', 'show_id' => $prefs['fgal_list_id'], 'show_icon' => $prefs['fgal_list_type'], 'show_name' => $prefs['fgal_list_name'], 'show_description' => $prefs['fgal_list_description'], 'show_size' => $prefs['fgal_list_size'], 'show_created' => $prefs['fgal_list_created'], 'show_modified' => $prefs['fgal_list_lastModif'], 'show_creator' => $prefs['fgal_list_creator'], 'show_author' => $prefs['fgal_list_author'], 'show_last_user' => $prefs['fgal_list_last_user'], 'show_comment' => $prefs['fgal_list_comment'], 'show_files' => $prefs['fgal_list_files'], 'show_hits' => $prefs['fgal_list_hits'], 'show_lockedby' => $prefs['fgal_list_lockedby'], 'show_checked' => ! empty($prefs['fgal_checked']) ? $prefs['fgal_checked'] : 'y' , 'show_share' => $prefs['fgal_list_share'], 'show_explorer' => $prefs['fgal_show_explorer'], 'show_path' => $prefs['fgal_show_path'], 'show_slideshow' => $prefs['fgal_show_slideshow'], 'show_source' => 'o', 'wiki_syntax' => '', 'show_ocr_state' => $prefs['fgal_show_ocr_state'], 'default_view' => $prefs['fgal_default_view'], 'template' => null, 'icon_fileId' => ! empty($prefs['fgal_icon_fileId']) ? $prefs['fgal_icon_fileId'] : null, 'ocr_lang' => '', ]; } public function replace_file_gallery($fgal_info) { global $prefs; $galleriesTable = $this->table('tiki_file_galleries'); $objectsTable = $this->table('tiki_objects'); $fgal_info = array_merge($this->default_file_gallery(), $fgal_info); // ensure gallery name is userId for root user gallery if ( $prefs['feature_use_fgal_for_user_files'] === 'y' && $fgal_info['type'] === 'user' && $fgal_info['parentId'] == $prefs['fgal_root_user_id'] ) { $userId = TikiLib::lib('user')->get_user_id($fgal_info['user']); if ($userId) { $fgal_info['name'] = $userId; } } // if the user is admin or the user is the same user and the gallery exists // then replace if not then create the gallary if the name is unused. $fgal_info['name'] = strip_tags($fgal_info['name']); $fgal_info['description'] = strip_tags($fgal_info['description']); if ($fgal_info['sort_mode'] == 'created_desc') { $fgal_info['sort_mode'] = null; } if (! empty($fgal_info['galleryId']) && $fgal_info['galleryId'] > 0) { $fgal_info['lastModif'] = $this->now; $galleryId = (int) $fgal_info['galleryId']; $galleriesTable->update($fgal_info, ['galleryId' => $galleryId]); $objectsTable->update( ['name' => $fgal_info['name'], 'description' => $fgal_info['description']], ['type' => 'file gallery', 'itemId' => $galleryId] ); $finalEvent = 'tiki.filegallery.update'; } else { unset($fgal_info['galleryId']); $fgal_info['created'] = $this->now; $fgal_info['lastModif'] = $this->now; $galleryId = $galleriesTable->insert($fgal_info); $finalEvent = 'tiki.filegallery.create'; } $cachelib = TikiLib::lib('cache'); $cachelib->empty_type_cache($this->get_all_galleries_cache_type()); TikiLib::events()->trigger($finalEvent, [ 'type' => 'file gallery', 'object' => $galleryId, 'user' => $GLOBALS['user'], ]); // event_handler($action,$object_type,$object_id,$options); return $galleryId; } public function get_all_galleries_cache_name($user) { $tikilib = TikiLib::lib('tiki'); $categlib = TikiLib::lib('categ'); $gs = $tikilib->get_user_groups($user); $tmp = ""; if (is_array($gs)) { $tmp .= implode("\n", $gs); } $tmp .= '----'; if ($jail = $categlib->get_jail()) { $tmp .= implode("\n", $jail); } return md5($tmp); } public function get_all_galleries_cache_type() { return 'fgals_'; } public function process_batch_file_upload($galleryId, $file, $user, $description, &$errors) { $extract_dir = 'temp/' . basename($file) . '/'; mkdir($extract_dir); $archive = new PclZip($file); $archive->extract(PCLZIP_OPT_PATH, $extract_dir, PCLZIP_OPT_REMOVE_ALL_PATH); unlink($file); $h = opendir($extract_dir); // check filters $upl = 1; $errors = []; while (($file = readdir($h)) !== false) { if ($file != '.' && $file != '..' && is_file($extract_dir . '/' . $file)) { if (! $this->is_filename_valid($file)) { $errors[] = tra('Invalid filename (using filters for filenames)') . ': ' . $file; $upl = 0; } try { $this->assertUploadedFileIsSafe($file); } catch (Exception $e) { $errors[] = $e->getMessage(); $upl = 0; } if (! $this->checkQuota(filesize($extract_dir . $file), $galleryId, $error)) { $errors[] = $error; $upl = 0; } } } if (! $upl) { return false; } rewinddir($h); while (($file = readdir($h)) !== false) { if ($file != '.' && $file != '..' && is_file($extract_dir . '/' . $file)) { if (false === $data = @file_get_contents($extract_dir . $file)) { $errors[] = tra('Cannot open this file:') . "temp/$file"; return false; } $tikiFile = new TikiFile([ 'galleryId' => $galleryId, 'description' => $description, 'user' => $user, ]); $type = TikiLib::lib('mime')->from_path($file, $extract_dir . $file); $fileId = $tikiFile->replace($data, $type, $file, $file); unlink($extract_dir . $file); } } closedir($h); rmdir($extract_dir); return true; } public function get_file_info($fileId, $include_search_data = true, $include_data = true, $use_draft = false) { global $prefs, $user; $return = $this->get_files_info(null, (int)$fileId, $include_search_data, $include_data, 1); if (! $return) { return false; } $file = $return[0]; if ($use_draft && $prefs['feature_file_galleries_save_draft'] == 'y') { $draft = $this->table('tiki_file_drafts')->fetchRow( ['filename', 'filesize', 'filetype', 'data', 'user', 'path', 'hash', 'lastModif', 'lockedby'], ['fileId' => (int) $fileId, 'user' => $user] ); if ($draft) { $file = array_merge($file, $draft); } } return $file; } public function get_file_label($fileId) { $info = $this->get_file_info($fileId, false, false, false); $arr = array_filter([$info['name'], $info['filename']]); return reset($arr); } public function get_files_info_from_gallery_id($galleryId, $include_search_data = false, $include_data = false) { return $this->get_files_info((int)$galleryId, null, $include_search_data, $include_data); } public function get_files_info($galleryIds = null, $fileIds = null, $include_search_data = false, $include_data = false, $numrows = -1) { $files = $this->table('tiki_files'); if ($include_search_data && $include_data) { $fields = $files->all(); } else { $fields = ['fileId', 'galleryId', 'name', 'description', 'created', 'filename', 'filesize', 'filetype', 'user', 'author', 'hits', 'votes', 'points', 'path', 'reference_url', 'is_reference', 'hash', 'lastModif', 'lastModifUser', 'lockedby', 'comment', 'archiveId','ocr_state']; if ($include_search_data) { $fields[] = 'search_data'; $fields[] = 'ocr_data'; } if ($include_data) { $fields[] = 'data'; } } $conditions = []; if (! empty($fileIds)) { $conditions['fileId'] = $files->in((array) $fileIds); } if (! empty($galleryIds)) { $conditions['galleryId'] = $files->in((array) $galleryIds); } return $files->fetchAll($fields, $conditions, $numrows); } /** * @param $id * @param $params * @return TikiDb_Adodb_Result|TikiDb_Pdo_Result */ public function update_file($id, $params) { if (isset($params['name'])) { $params['name'] = strip_tags($params['name']); } if (isset($params['description'])) { $params['name'] = strip_tags($params['description']); } $params['lastModif'] = $this->now; $files = $this->table('tiki_files'); $result = $files->update($params, ['fileId' => $id]); $galleryId = $files->fetchOne('galleryId', ['fileId' => $id]); if ($galleryId >= 0) { $this->table('tiki_file_galleries')->update(['lastModif' => $this->now], ['galleryId' => $galleryId]); } require_once('lib/search/refresh-functions.php'); refresh_index('files', $id); return $result; } public function duplicate_file($id, $galleryId = null, $newName = false) { global $user; $origFile = TikiFile::id($id); $file = $origFile->clone(); $file->setParam('user', $user); if (! empty($galleryId)) { $file->setParam('galleryId', $galleryId); } $newName = ($newName ? $newName : $origFile->name . tra(' copy')); $id = $file->replace($origFile->getContents(), $origFile->filetype, $newName, $file->filename); $attributes = TikiLib::lib('attribute')->get_attributes('file', $origFile->fileId); if ($url = $attributes['tiki.content.url']) { $this->attach_file_source($id, $url, $file->getParams(), true); } return $id; } public function change_file_handler($mime_type, $cmd) { $handlers = $this->table('tiki_file_handlers'); $mime_type = trim($mime_type); $handlers->delete(['mime_type' => $mime_type]); $handlers->insert(['mime_type' => $mime_type, 'cmd' => $cmd]); return true; } public function delete_file_handler($mime_type) { $handlers = $this->table('tiki_file_handlers'); return (bool) $handlers->delete(['mime_type' => $mime_type]); } public function get_native_handler($type) { switch ($type) { case 'text/plain': return function (FileWrapper $wrapper) { return $wrapper->getContents(); }; case 'application/pdf': return function (FileWrapper $wrapper) { include_once "vendor_bundled/vendor/christian-vigh-phpclasses/PdfToText/PdfToText.phpclass"; ob_start(); $pdf = new PdfToText($wrapper->getReadableFile()); ob_end_clean(); return $pdf->Text; }; } } public function get_file_handlers($for_execution = false) { $cachelib = TikiLib::lib('cache'); if ($for_execution && ! $default = $cachelib->getSerialized('file_handlers')) { // n.b. this array is partially duplicated in tiki-check.php for standalone mode checks $possibilities = [ 'application/ms-excel' => ['xls2csv %1'], 'application/msexcel' => ['xls2csv %1'], 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' => ['xlsx2csv.py %1'], 'application/ms-powerpoint' => ['catppt %1'], 'application/mspowerpoint' => ['catppt %1'], 'application/vnd.openxmlformats-officedocument.presentationml.presentation' => ['pptx2txt.pl %1 -'], 'application/msword' => ['catdoc %1', 'strings %1'], 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' => ['docx2txt.pl %1 -'], 'application/pdf' => ['pstotext %1', 'pdftotext %1 -'], 'application/postscript' => ['pstotext %1'], 'application/ps' => ['pstotext %1'], 'application/rtf' => ['catdoc %1'], 'application/sgml' => ['col -b %1', 'strings %1'], 'application/vnd.ms-excel' => ['xls2csv %1'], 'application/vnd.ms-powerpoint' => ['catppt %1'], 'application/x-msexcel' => ['xls2csv %1'], 'application/x-pdf' => ['pstotext %1', 'pdftotext %1 -'], 'application/x-troff-man' => ['man -l %1'], 'application/zip' => ['unzip -l %1'], 'text/enriched' => ['col -b %1', 'strings %1'], 'text/html' => ['elinks -dump -no-home %1'], 'text/richtext' => ['col -b %1', 'strings %1'], 'text/sgml' => ['col -b %1', 'strings %1'], 'text/tab-separated-values' => ['col -b %1', 'strings %1'], ]; $default = []; $executables = []; foreach ($possibilities as $type => $options) { foreach ($options as $opt) { $optArray = explode(' ', $opt, 2); $exec = reset($optArray); if (! isset($executables[$exec])) { $executables[$exec] = (bool) `which $exec`; } if ($executables[$exec]) { $default[$type] = $opt; break; } } } $cachelib->cacheItem('file_handlers', serialize($default)); } elseif (! $for_execution) { $default = []; } $handlers = $this->table('tiki_file_handlers'); $database = $handlers->fetchMap('mime_type', 'cmd', []); return array_merge($default, $database); } public function reindex_all_files_for_search_text() { @ini_set('memory_limit', -1); $files = $this->table('tiki_files'); $reindexFilesCount = 0; for ($offset = 0, $maxRecords = 10;; $offset += $maxRecords) { $rows = $files->fetchAll(['fileId', 'filename', 'filesize', 'filetype', 'data', 'path', 'galleryId'], ['archiveId' => 0], $maxRecords, $offset); if (empty($rows)) { break; } foreach ($rows as $row) { $file = new TikiFile($row); $search_text = $this->get_search_text_for_data($file); if ($search_text !== false) { if ($files->update(['search_data' => $search_text], ['fileId' => $row['fileId']])) { $reindexFilesCount++; } } } } include_once("lib/search/refresh-functions.php"); refresh_index('files'); return $reindexFilesCount; } public function get_parse_app($type, $skipDefault = true) { static $fileParseApps; $partial = $type; if (false !== $p = strpos($partial, ';')) { $partial = substr($partial, 0, $p); } if ($handler = $this->get_native_handler($type)) { return $handler; } if ($handler = $this->get_native_handler($partial)) { return $handler; } if (! $fileParseApps) { $fileParseApps = $this->get_file_handlers(true); } if (isset($fileParseApps[$type])) { return $this->shellExecuteCallback($fileParseApps[$type]); } elseif (isset($fileParseApps[$partial])) { return $this->shellExecuteCallback($fileParseApps[$partial]); } elseif (! $skipDefault && isset($fileParseApps['default'])) { return $this->shellExecuteCallback($fileParseApps['default']); } } private function shellExecuteCallback($command) { if (! $command) { return function () { return ''; }; } return function (FileWrapper $wrapper) use ($command) { $tmpfname = $wrapper->getReadableFile(); $cmd = str_replace('%1', escapeshellarg($tmpfname), $command); $handle = popen($cmd, "r"); if ($handle !== false) { $contents = stream_get_contents($handle); fclose($handle); return $contents; } return false; }; } public function get_search_text_for_data($file) { $parseApp = $this->get_parse_app($file->filetype); if (empty($parseApp)) { return ''; } $wrapper = $file->getWrapper(); try { $content = $parseApp($wrapper); // clean out any chars not suitable for storing in tiki_file.search_data which is a LONGTEXT column $content = filter_var($content, FILTER_UNSAFE_RAW, FILTER_FLAG_STRIP_HIGH | FILTER_FLAG_STRIP_LOW); } catch (Exception $e) { Feedback::error(tr('Processing search text from a "%0" file in gallery #%1', $file->filetype, $file->galleryId) . '
' . $e->getMessage()); $content = ''; } return $content; } public function fix_vnd_ms_files() { $files = $this->table('tiki_files'); $files->update( ['filetype' => $files->expr("REPLACE(`filetype`, 'application/vnd.ms-', 'application/ms')")], ['filetype' => $files->like('application/vnd.ms-%')] ); } public function getGalleryDefinition($galleryId) { if (! isset($this->loadedGalleryDefinitions[$galleryId])) { $info = $this->get_file_gallery_info($galleryId); $this->loadedGalleryDefinitions[$galleryId] = new GalleryDefinition($info); } return $this->loadedGalleryDefinitions[$galleryId]; } public function clearLoadedGalleryDefinitions() { $this->loadedGalleryDefinitions = []; } public function notify($galleryId, $name, $filename, $description, $action, $user, $fileId = false) { global $prefs; if ($prefs['feature_user_watches'] == 'y') { // Deal with mail notifications. include_once(__DIR__ . '/../notifications/notificationemaillib.php'); $galleryName = $this->table('tiki_file_galleries')->fetchOne('name', ['galleryId' => $galleryId]); sendFileGalleryEmailNotification('file_gallery_changed', $galleryId, $galleryName, $name, $filename, $description, $action, $user, $fileId); } } /** * Lock a file * * @param $fileId * @param $user * @return TikiDb_Adodb_Result|TikiDb_Pdo_Result */ public function lock_file($fileId, $user) { $result = $this->table('tiki_files')->update(['lockedby' => $user], ['fileId' => $fileId]); return $result; } /** * Unlock a file * * @param $fileId * @return TikiDb_Adodb_Result|TikiDb_Pdo_Result */ public function unlock_file($fileId) { return $this->lock_file($fileId, null); } /* get archives of a file */ public function get_archives($fileId, $offset = 0, $maxRecords = -1, $sort_mode = 'created_desc', $find = '') { return $this->get_files($offset, $maxRecords, $sort_mode, $find, $fileId, true, false, false, true, false, false, false, false, '', false, true); } public function duplicate_file_gallery($galleryId, $name, $description = '') { global $user; $info = $this->get_file_gallery_info($galleryId); $info['user'] = $user; $info['galleryId'] = 0; $info['description'] = $description; $info['name'] = $name; $newGalleryId = $this->replace_file_gallery($info); return $newGalleryId; } public function get_download_limit($fileId) { return $this->table('tiki_files')->fetchOne('maxhits', ['fileId' => $fileId]); } public function set_download_limit($fileId, $limit) { $this->table('tiki_files')->update(['maxhits' => (int) $limit], ['fileId' => (int) $fileId]); } // not the best optimisation as using a library using files and not content public function zip($fileIds, &$error, $zipName = '') { global $tiki_p_admin_file_galleries, $prefs, $user; $userlib = TikiLib::lib('user'); $list = []; $temp = '/' . md5(\Laminas\Math\Rand::getBytes(10)) . '/'; if (! mkdir(sys_get_temp_dir() . $temp)) { $temp = sys_get_temp_dir() . $temp; } elseif (mkdir('temp' . $temp)) { $temp = 'temp' . $temp; } else { $error = "Can not create directory $temp"; return false; } $fileIds = array_unique($fileIds); Perms::bulk(['type' => 'file'], 'object', $fileIds); foreach ($fileIds as $fileId) { $file = TikiFile::id($fileId); if ($tiki_p_admin_file_galleries == 'y' || $userlib->user_has_perm_on_object($user, $file->fileId, 'file', 'tiki_p_download_files')) { if (empty($zipName)) { $zipName = $file->galleryId; } $tmp = $temp . $file->filename; if (! copy($file->getWrapper()->getReadableFile(), $tmp)) { $error = "Can not copy to $tmp"; return false; } $list[] = $tmp; $info = $file->getParams(); } } if (empty($list)) { $error = "No permission"; return null; } $info['filename'] = "$zipName.zip"; $zip = $temp . $info['filename']; define(PCZLIB_SEPARATOR, '\001'); if (! $archive = new PclZip($zip)) { $error = $archive->errorInfo(true); return false; } if (! ($v_list = $archive->create($list, PCLZIP_OPT_REMOVE_PATH, $temp))) { $error = $archive->errorInfo(true); return false; } $info['data'] = file_get_contents($zip); $info['path'] = ''; $info['filetype'] = 'application/x-zip-compressed'; foreach ($list as $tmp) { unlink($tmp); } unlink($zip); rmdir($temp); return $info; } /** * Return a flat list with the gallery id and the parent id, keeping a cached version * * @return array */ public function getGalleriesParentIds() { if (self::$getGalleriesParentIdsCache === null) { self::$getGalleriesParentIdsCache = $this->table('tiki_file_galleries')->fetchAll(['galleryId', 'parentId'], []); } return self::$getGalleriesParentIdsCache; } /** * Enables to clear the cache for the gallery parent ids * * @return void */ public function cleanGalleriesParentIdsCache() { self::$getGalleriesParentIdsCache = null; } /** * Recursively returns all ids of the children of the specifified parent gallery * as a linear array (list). * * @param Array $allIds All ids of the Gallery * @param Array &$subtree Output - The children Ids are appended * @param int $parentId The parent whichs children are to be listed */ public function _getGalleryChildrenIdsList($allIds, &$subtree, $parentId) { if (empty($allIds[$parentId])) { return; } foreach ($allIds[$parentId] as $child) { $galleryId = $child; $subtree[] = (int)$galleryId; $this->_getGalleryChildrenIdsList($allIds, $subtree, $galleryId); } } /** * Recursively returns all Ids of the Children of the specifified parent gallery * as a tree-array (sub-galleries are array as an element of the parent array). * Thus the structure of the child galleries are preserved. * * @param Array $allIds All ids of the Gallery * @param Array &$subtree Output - The children Ids are appended * @param int $parentId The parent whichs children are to be listed */ public function _getGalleryChildrenIdsTree($allIds, &$subtree, $parentId) { if (empty($allIds[$parentId])) { return; } foreach ($allIds[$parentId] as $child) { $galleryId = $child; $subtree[ (int)$galleryId ] = []; $this->_getGalleryChildrenIdsTree($allIds, $subtree[$galleryId], $galleryId); } } // Get a tree or a list of a gallery children ids, optionnally under a specific parentId // To avoid a query to the database for each node, this function retrieves all gallery ids and recursively build the tree using this info public function getGalleryChildrenIds(&$subtree, $parentId = -1, $format = 'tree') { $allIds = $this->getGalleriesParentIds(); $allChildIds = []; foreach ($allIds as $v) { $allChildIds[$v['parentId']][] = $v['galleryId']; } switch ($format) { case 'list': $this->_getGalleryChildrenIdsList($allChildIds, $subtree, $parentId); break; case 'tree': default: $this->_getGalleryChildrenIdsTree($allChildIds, $subtree, $parentId); } } // Get a tree or a list of ids of the specified gallery and its children public function getGalleryIds(&$subtree, $parentId = -1, $format = 'tree') { switch ($format) { case 'list': $subtree[] = $parentId; $childSubtree =& $subtree; break; case 'tree': default: $subtree[$parentId] = []; $childSubtree =& $subtree[$parentId]; } return $this->getGalleryChildrenIds($childSubtree, $parentId, $format); } /* Get the subgalleries of a gallery, the one identified by $parentId if $wholeSpecialGallery is false, or the special gallery containing the gallery identified by $parentId if $wholeSpecialGallery is true. * * @param int $parentId Identifier of a gallery * @param bool $wholeSpecialGallery If true, will return the subgalleries of the special gallery (User File Galleries, Wiki Attachment Galleries, File Galleries, ...) that contains the $parentId gallery * @param string $permission If set, will limit the list of subgalleries to those having this permission for the current user */ public function getSubGalleries($parentId = 0, $wholeSpecialGallery = true, $permission = 'view_file_gallery') { // Use the special File Galleries root if no other special gallery root id is specified if ($parentId == 0) { global $prefs; $parentId = $prefs['fgal_root_id']; } // If needed, get the id of the special gallery that contains the $parentId gallery if ($wholeSpecialGallery) { $parentId = $this->getGallerySpecialRoot($parentId); $useCache = true; } global $user; $cachelib = TikiLib::lib('cache'); if ($useCache) { $cacheName = 'pid' . $parentId . '_' . $this->get_all_galleries_cache_name($user); $cacheType = $this->get_all_galleries_cache_type(); } if (! $useCache || ! $return = $cachelib->getSerialized($cacheName, $cacheType)) { $return = $this->list_file_galleries(0, -1, 'name_asc', $user, '', $parentId, false, true, false, false, false, true, false); if (is_array($return)) { $return['parentId'] = $parentId; } if ($useCache) { $cachelib->cacheItem($cacheName, serialize($return), $cacheType); } } if ($permission != '') { if (! is_array($return['data'])) { $return['data'] = []; } $return['data'] = Perms::filter(['type' => 'file gallery'], 'object', $return['data'], ['object' => 'id'], $permission); } return $return; } /** * Get the Id of the gallery special root, which will be a gallery of type 'special' with the parentId '-1' * (i.e. 'File Galleries', 'Users File Galleries', ...) * * @param int $galleryId The id of the gallery * @return The special root gallery Id */ // WARNING: Semi-private function. "Public callers" should only pass the galleryId parameter. public function getGallerySpecialRoot($galleryId, $treeParentId = null, &$tree = null /* Pass by reference for performance */) { global $prefs; if (( $treeParentId === null xor $tree === null ) || $galleryId <= 0) { // If parameters are not valid, return false (they should be null at first call and not empty when recursively called) return false; } elseif ($treeParentId === null) { // Initialize the full tree and the top root of all galleries $tree = []; $treeParentId = -1; $this->getGalleryChildrenIds($tree, $treeParentId, 'tree'); } elseif ($treeParentId == $galleryId) { // If the searched gallery is the same as the current tree parent id, then return tree (we found the right branch of the tree) return true; } if (! empty($tree)) { foreach ($tree as $subGalleryId => $childs) { if ($result = $this->getGallerySpecialRoot($galleryId, $subGalleryId, $childs)) { if (is_integer($result)) { return $result; } elseif ($treeParentId == -1) { // // If the parent is : // - either the User File Gallery, stop here to keep only the user gallery instead of all users galleries // - or already the top root of all galleries, it means that the gallery is a special gallery root // return (int)$subGalleryId; } else { return true; } } } } return false; } // Get the tree of 'Wiki Attachment File Galleries' filegal of the specified wiki page public function getWikiAttachmentFilegalsIdsTree($pageName) { $return = []; $this->getGalleryIds($return, $this->get_wiki_attachment_gallery($pageName), 'tree'); return $return; } // Get the tree of 'Users File Galleries' filegal of the current user public function getUserFilegalsIdsTree() { $return = []; $this->getGalleryIds($return, $this->get_user_file_gallery(), 'tree'); return $return; } // Get the tree of 'File Galleries' filegal public function getFilegalsIdsTree() { global $prefs; $return = []; $this->getGalleryIds($return, $prefs['fgal_root_id'], 'tree'); return $return; } // Return HTML code to display the complete file galleries tree for the special root containing the given gallery. // If $galleryIdentifier is not given, default to the "default" / normal / "File Galleries" file galleries. public function getTreeHTML($galleryIdentifier = null) { global $prefs; $smarty = TikiLib::lib('smarty'); require_once('lib/tree/BrowseTreeMaker.php'); $galleryIdentifier = is_null($galleryIdentifier) ? $prefs['fgal_root_id'] : $galleryIdentifier; $subGalleries = $this->getSubGalleries($galleryIdentifier); $smarty->loadPlugin('smarty_function_icon'); $icon = ' ' . smarty_function_icon(['name' => 'file-archive-open'], $smarty->getEmptyInternalTemplate()) . ' '; $smarty->loadPlugin('smarty_block_self_link'); $linkParameters = ['_script' => 'tiki-list_file_gallery.php', '_class' => 'fgalname']; if (! empty($_REQUEST['filegals_manager'])) { $linkParameters['filegals_manager'] = $_REQUEST['filegals_manager']; } $nodes = []; foreach ($subGalleries['data'] as $subGallery) { $linkParameters['galleryId'] = $subGallery['id']; $nodes[] = [ 'id' => $subGallery['id'], 'parent' => $subGallery['parentId'], 'data' => smarty_block_self_link($linkParameters, $icon . htmlspecialchars($subGallery['name']), $smarty), ]; } $browseTreeMaker = new BrowseTreeMaker('Galleries'); return $browseTreeMaker->make_tree($this->getGallerySpecialRoot($galleryIdentifier), $nodes); } // Return the given gallery's path relative to its special root. The path starts with a constant component, File Galleries for default galleries. // It would be File Galleries > Foo for a root default file gallery named "Foo". Other constant components are "User File Galleries" and "Wiki Attachment File Galleries". // Returns an array with 2 elements, "Array" and "HTML". // Array is a numerically-indexed array with one element per path component. Each value is the name of the component (usually a file gallery name). Keys are file gallery OIDs. // HTML is a string of HTML code to display the path. public function getPath($galleryIdentifier) { global $prefs, $user; $rootIdentifier = $this->getGallerySpecialRoot($galleryIdentifier); $root = $this->get_file_gallery_info($galleryIdentifier); if ($user != '' && $prefs['feature_use_fgal_for_user_files'] == 'y') { $userGallery = $this->get_user_file_gallery(); if ($userGallery == $prefs['fgal_root_user_id']) { $rootIdentifier = $userGallery; } } $path = []; for ($node = $this->get_file_gallery_info($galleryIdentifier); $node && $node['galleryId'] != $rootIdentifier; $node = $this->get_file_gallery_info($node['parentId'])) { $path[$node['galleryId']] = $node['name']; } if (isset($userGallery) && $rootIdentifier == $prefs['fgal_root_user_id']) { $path[$rootIdentifier] = tra('User File Galleries'); } elseif ($rootIdentifier == $prefs['fgal_root_wiki_attachments_id']) { $path[$rootIdentifier] = tra('Wiki Attachment File Galleries'); } else { $path[$rootIdentifier] = tra('File Galleries'); } $path = array_reverse($path, true); $pathHtml = ''; foreach ($path as $identifier => $name) { if ($pathHtml != '') { $pathHtml .= '  > '; } $pathHtml .= '' . htmlspecialchars($name) . ''; } return [ 'HTML' => $pathHtml, 'Array' => $path ]; } // get the size in k used in a fgal and its children public function getUsedSize($galleryId = 0) { $files = $this->table('tiki_files'); $conditions = []; if (! empty($galleryId)) { $galleryIds = []; $this->getGalleryIds($galleryIds, $galleryId, 'list'); $conditions['galleryId'] = $files->in($galleryIds); } return $files->fetchOne($files->sum('filesize'), $conditions); } // get the min quota in M of a fgal and its parents public function getQuota($galleryId = 0) { global $prefs; if (empty($galleryId) || $prefs['fgal_quota_per_fgal'] == 'n') { return $prefs['fgal_quota']; } $list = $this->getGalleryParentsColumns($galleryId, ['galleryId', 'quota']); $quota = $prefs['fgal_quota']; foreach ($list as $fgal) { if (empty($fgal['quota'])) { continue; } $quota = min($quota, $fgal['quota']); } return $quota; } /** * get the max quota in MB of the children of a fgal, * or total contents size where no quota is set * * @param int $galleryId * @return float */ public function getMaxQuotaDescendants($galleryId = 0) { if (empty($galleryId)) { return 0; } $this->getGalleryChildrenIds($subtree, $galleryId, 'list'); if (is_array($subtree) && ! empty($subtree)) { $files = $this->table('tiki_files'); $gals = $this->table('tiki_file_galleries'); $size = 0; foreach ($subtree as $subGalleryId) { $quota = $gals->fetchOne('quota', ['galleryId' => $subGalleryId]); if ($quota) { $size += $quota; } else { $size += $files->fetchOne($files->sum('filesize'), ['galleryId' => $subGalleryId]) / (1024 * 1024); } } return $size; } else { return 0.0; } } // check quota is smaller than parent quotas and bigger than children quotas // return -1: too small, 0: ok, +1: too big public function checkQuotaSetting($quota, $galleryId = 0, $parentId = 0) { if (empty($quota)) { return 0; } $limit = $this->getQuota($parentId); if (! empty($limit) && $quota > $limit) { return 1;// too big } if (! empty($galleryId)) { $limit = $this->getMaxQuotaDescendants($galleryId); if (! empty($limit) && $quota < $limit) { return -1;//too small } } return 0; } // get specific columns for a gallery and its parents public function getGalleryParentsColumns($galleryId, $columns) { $cols = array_diff($columns, ['size', 'galleryId', 'parentId']); $cols[] = 'galleryId'; $cols[] = 'parentId'; $all = $this->table('tiki_file_galleries')->fetchAll($cols, []); $list = []; $this->_getGalleryParentsColumns($all, $list, $galleryId, $columns); return $list; } public function _getGalleryParentsColumns($all, &$list, $galleryId, $columns = []) { foreach ($all as $fgal) { if ($fgal['galleryId'] == $galleryId) { if (in_array('size', $columns)) { // to be optimized $fgal['size'] = $this->getUsedSize($galleryId); } $list[] = $fgal; $this->_getGalleryParentsColumns($all, $list, $fgal['parentId'], $columns); return; } } } // check a size in K can be added to a gallery return false if problem public function checkQuota($size, $galleryId, &$error) { global $prefs; $smarty = TikiLib::lib('smarty'); $error = ''; if (! empty($prefs['fgal_quota'])) { $use = $this->getUsedSize(); if ($use + $size > $prefs['fgal_quota'] * 1024 * 1024) { $error = tra('The upload was not completed.') . ' ' . tra('Reason: The global quota has been reached'); $diff = $use + $size - $prefs['fgal_quota'] * 1024 * 1024; } } if (empty($error) && $prefs['fgal_quota_per_fgal'] == 'y') { $list = $this->getGalleryParentsColumns($galleryId, ['galleryId', 'quota', 'size', 'name']); //echo '
';print_r($list);echo '
'; foreach ($list as $fgal) { if (! empty($fgal['quota']) && $fgal['size'] + $size > $fgal['quota'] * 1024 * 1024) { $error = tra('The upload was not completed.') . ' ' . sprintf(tra('Reason: The quota has been reached in "%s"'), $fgal['name']); $smarty->assign('mail_fgal', $fgal); $diff = $fgal['size'] + $size - $fgal['quota'] * 1024 * 1024; break; } } } if (! empty($error)) { global $tikilib; $nots = $tikilib->get_event_watches('fgal_quota_exceeded', '*'); if (! empty($nots)) { include_once('lib/webmail/tikimaillib.php'); $mail = new TikiMail(); $foo = parse_url($_SERVER["REQUEST_URI"]); $machine = $tikilib->httpPrefix(true) . dirname($foo["path"]); $machine = preg_replace("!/$!", "", $machine); // just incase $smarty->assign('mail_machine', $machine); $smarty->assign('mail_diff', $diff); foreach ($nots as $not) { $lg = $tikilib->get_user_preference($not['user'], 'language', $prefs['site_language']); $mail->setSubject(tra('File gallery quota exceeded', $lg)); $mail->setText($smarty->fetchLang($lg, 'mail/fgal_quota_exceeded.tpl')); $mail->send([$not['email']]); } } return false; } return true; } // update backlinks of an object public function replaceBacklinks($context, $fileIds = []) { $objectlib = TikiLib::lib('object'); $objectId = $objectlib->get_object_id($context['type'], $context['object']); if (empty($objectId) && ! empty($fileIds)) { $context = array_merge($context, [ 'description' => null, 'name' => null, 'href' => null, ]); $objectId = $objectlib->add_object($context['type'], $context['object'], false, $context['description'], $context['name'], $context['href']); } if (! empty($objectId)) { $this->_replaceBacklinks($objectId, $fileIds); } //echo 'REPLACEBACKLINK'; print_r($context);print_r($fileIds);echo '
'; debug_print_backtrace(); echo '
';die; } public function _replaceBacklinks($objectId, $fileIds = []) { $backlinks = $this->table('tiki_file_backlinks'); $this->_deleteBacklinks($objectId); foreach ($fileIds as $fileId) { $backlinks->insert(['objectId' => (int) $objectId, 'fileId' => (int) $fileId]); } } // delete backlinks associated to an object public function deleteBacklinks($context, $fileId = null) { if (empty($fileId)) { $objectlib = TikiLib::lib('object'); $objectId = $objectlib->get_object_id($context['type'], $context['object']); if (! empty($objectId)) { $this->_deleteBacklinks($objectId); } } else { $this->_deleteBacklinks(null, $fileId); } } public function _deleteBacklinks($objectId, $fileId = null) { $backlinks = $this->table('tiki_file_backlinks'); if (empty($fileId)) { $backlinks->delete(['objectId' => (int) $objectId]); } else { $backlinks->delete(['fileId' => (int) $fileId]); } } // get the backlinks of an object public function getFileBacklinks($fileId, $sort_mode = 'type_asc') { $query = 'select tob.* from `tiki_file_backlinks` tfb left join `tiki_objects` tob on (tob.`objectId`=tfb.`objectId`) where `fileId`=? order by ' . $this->convertSortMode($sort_mode); return $this->fetchAll($query, [(int)$fileId]); } /** * "can not see a file if all its backlinks are not viewable" * * Checks if a file is used in various object types and all the uses of it are "private" * * @param int $fileId numeric id of the file in question * * @return bool true: if all the uses of a file are _not_ visible to the current user, * false: if any objects using of the file are visible or the file is not used * * @throws Exception */ public function hasOnlyPrivateBacklinks($fileId) { $objects = $this->getFileBacklinks($fileId); if (empty($objects)) { return false; } $pobjects = []; foreach ($objects as $object) { $pobjects[$object['type']][] = $object; } TikiLib::lib('object'); $map = ObjectLib::map_object_type_to_permission(); foreach ($pobjects as $type => $list) { if ($type == 'blog post') { $this->parentObjects($list, 'tiki_blog_posts', 'postId', 'blogId'); $filtered = Perms::filter(['type' => 'blog'], 'object', $list, ['object' => 'blogId'], str_replace('tiki_p_', '', $map['blog'])); } elseif (strstr($type, 'comment')) { $this->parentObjects($list, 'tiki_comments', 'threadId', 'object'); $t = str_replace(' comment', '', $type); $filtered = Perms::filter(['type' => $t], 'object', $list, ['object' => 'object'], str_replace('tiki_p_', '', $map[$t])); } elseif ($type == 'forum post') { $this->parentObjects($list, 'tiki_comments', 'threadId', 'object'); $filtered = Perms::filter(['type' => 'forum'], 'object', $list, ['object' => 'object'], str_replace('tiki_p_', '', $map['forum'])); } elseif ($type == 'trackeritem') { foreach ($list as $object) { $item = Tracker_Item::fromId($object['itemId']); if ($item->canView()) { return false; } } } else { $filtered = Perms::filter(['type' => $type], 'object', $list, ['object' => 'itemId'], str_replace('tiki_p_', '', $map[$type])); } if (! empty($filtered)) { // some objects linkling to this file are visible return false; } } return true; } /** * Finds out of a file is backlinked from a tracker item that is viewable by the current user. * This is useful if user doesn't have permissions to download files in the corresponding file * gallery but still can view the file through the tracker item. */ public function isBacklinkedFromAViewableTrackerItem($fileId) { $objects = $this->getFileBacklinks($fileId); foreach ($objects as $object) { if ($object['type'] == 'trackeritem') { $item = Tracker_Item::fromId($object['itemId']); if ($item->canView()) { return true; } } } return false; } // sync the backlinks used by a text of an object public function syncFileBacklinks($data, $context) { $fileIds = []; $parserlib = TikiLib::lib('parser'); $plugins = $parserlib->getPlugins($data, ['IMG', 'FILE']); foreach ($plugins as $plugin) { if (! empty($plugin['arguments']['fileId'])) { $fileIds[] = $plugin['arguments']['fileId']; } if (! empty($plugin['arguments']['src']) && $fileId = $this->getLinkFileId($plugin['arguments']['src'])) { $fileIds[] = $fileId; } } if (preg_match_all('/\[(.+)\]/Umi', $data, $matches, PREG_PATTERN_ORDER)) { foreach ($matches[1] as $match) { if ($fileId = $this->getLinkFileId($match)) { $fileIds[] = $fileId; } } } if (preg_match_all('/]*href=(\'|\")?([^>*])/Umi', $data, $matches, PREG_PATTERN_ORDER)) { foreach ($matches[2] as $match) { if ($fileId = $this->getLinkFileId($match)) { $fileIds[] = $fileId; } } } if ($context['type'] == 'trackeritem') { $relationlib = TikiLib::lib('relation'); $relations = $relationlib->get_relations_from('trackeritem', $context['object'], 'tiki.file.attach'); foreach ($relations as $relation) { if ($relation['type'] === 'file') { $fileIds[] = $relation['itemId']; } } } $fileIds = array_unique($fileIds); //if (!empty($fileIds)) {echo '
'; print_r($context); print_r($fileIds); echo '
';} $this->replaceBacklinks($context, $fileIds); return $fileIds; } public function save_sync_file_backlinks($args) { $content = []; if (isset($args['values'])) { $content = $args['values']; } if (isset($args['data'])) { $content[] = $args['data']; } if (! isset($args['values']) && ! isset($args['data'])) { // skip events that don't provide enough content return; } $content = implode(' ', $content); $this->syncFileBacklinks($content, $args); } public function getLinkFileId($url) { if (preg_match('/^tiki-download_file.php\?.*fileId=([0-9]+)/', $url, $matches)) { return $matches[1]; } if (preg_match('/^(dl|preview|thumbnail|thumb||display)([0-9]+)/', $url, $matches)) { return $matches[2]; } } private function syncParsedText($data, $context) { // Compatbility function $this->object_post_save($context, [ 'content' => $data ]); } public function refreshBacklinks() { $result = $this->table('tiki_pages')->fetchAll(['data', 'description', 'pageName'], []); foreach ($result as $res) { $this->syncParsedText($res['data'], ['type' => 'wiki page', 'object' => $res['pageName'], 'description' => $res['description'], 'name' => $res['pageName'], 'href' => 'tiki-index.php?page=' . $res['pageName']]); } $result = $this->table('tiki_articles')->fetchAll(['heading', 'body', 'articleId', 'title'], []); foreach ($result as $res) { $this->syncParsedText($res['body'] . ' ' . $res['heading'], ['type' => 'article', 'object' => $res['articleId'], 'description' => substr($res['heading'], 0, 200), 'name' => $res['title'], 'href' => 'tiki-read_article.php?articleId=' . $res['articleId']]); } $result = $this->table('tiki_submissions')->fetchAll(['heading', 'body', 'subId', 'title'], []); foreach ($result as $res) { $this->syncParsedText($res['heading'] . ' ' . $res['body'], ['type' => 'submission', 'object' => $res['subId'], 'description' => substr($res['heading'], 0, 200), 'name' => $res['title'], 'href' => 'tiki-edit_submission.php?subId=' . $res['subId']]); } $result = $this->table('tiki_blogs')->fetchAll(['blogId', 'heading', 'description', 'title'], []); foreach ($result as $res) { $this->syncParsedText($res['heading'], ['type' => 'blog', 'object' => $res['blogId'], 'description' => $res['description'], 'name' => $res['title'], 'href' => 'tiki-view_blog.php?blogId=' . $res['blogId']]); } $result = $this->table('tiki_blog_posts')->fetchAll(['blogId', 'data', 'postId', 'title'], []); foreach ($result as $res) { $this->syncParsedText($res['data'], ['type' => 'blog post', 'object' => $res['postId'], 'description' => substr($res['data'], 0, 200), 'name' => $res['title'], 'href' => 'tiki-view_blog_post.php?postId=' . $res['postId']]); } $result = $this->table('tiki_comments')->fetchAll(['objectType', 'object', 'threadId', 'title', 'data'], []); $commentslib = TikiLib::lib('comments'); foreach ($result as $res) { if ($res['objectType'] == 'forum') { $type = 'forum post'; } else { $type = $res['objectType'] . ' comment'; } $this->syncParsedText($res['data'], ['type' => $type, 'object' => $res['threadId'], 'description' => '', 'name' => $res['title'], 'href' => $commentslib->getHref($res['objectType'], $res['object'], $res['threadId'])]); } $result = $this->table('tiki_trackers')->fetchAll(['description', 'name', 'trackerId'], ['descriptionIsParsed' => 'y']); foreach ($result as $res) { $this->syncParsedText($res['description'], ['type' => 'tracker', 'object' => $res['trackerId'], 'description' => $res['description'], 'name' => $res['name'], 'href' => 'tiki-view_tracker.php?trackerId=' . $res['trackerId']]); } //TODO field description $query = 'select `value`, `itemId` from `tiki_tracker_item_fields` ttif left join `tiki_tracker_fields` ttf on (ttif.`fieldId`=ttf.`fieldId`) where ttf.`type`=?'; $result = $this->query($query, ['a']); while ($res = $result->fetchRow()) { //TODO: get the name of the item $this->syncParsedText($res['value'], ['type' => 'trackeritem', 'object' => $res['itemId'], 'description' => '', 'name' => '', 'href' => 'tiki-view_tracker_item.php?itemId=' . $res['itemId']]); } } /* move files to file system * return '' if ok otherwise error message */ public function moveFiles($to = 'to_fs', &$feedbacks = []) { $files = $this->table('tiki_files'); if ($to == 'to_db') { $result = $files->fetchColumn('fileId', ['path' => $files->not('')]); $msg = tra('Number of files transferred to the database:'); } else { $result = $files->fetchColumn('fileId', ['path' => '', 'filetype' => $files->not('image/svg+xml')]); $msg = tra('Number of files transferred to the file system:'); } $nb = 0; foreach ($result as $fileId) { if (($errors = $this->moveFile($fileId, $to)) != '') { $feedbacks[] = "$msg $nb"; return $errors; } ++$nb; } $feedbacks[] = "$msg $nb"; return ''; } public function moveFile($file_id, $to = 'to_fs') { global $prefs; $files = $this->table('tiki_files'); $file = TikiFile::id($file_id); $file->galleryDefinition()->fixFileLocation($file); $files->update($file->getParamsForDB(), ['fileId' => $file->fileId]); return ''; } // find the fileId in the pool of fileId archives files that is closer before the date public function getArchiveJustBefore($fileId, $date) { $files = $this->table('tiki_files'); $archiveId = $files->fetchOne('archiveId', ['fileId' => $fileId]); if (empty($archiveId)) { $archiveId = $fileId; } return $files->fetchOne( 'fileId', [ 'anyOf' => $files->expr('(`fileId`=? or `archiveId`=?)', [$archiveId, $archiveId]), 'created' => $files->lesserThan($date + 1) ], 1, 0, ['created' => 'DESC'] ); } public function get_objectid_from_virtual_path($path, $parentId = -1) { if (empty($path) || $path[0] != '/') { return false; } if ($path == '/') { // global $prefs; // return array('type' => 'filegal', 'id' => $prefs['fgal_root_id']); return ['type' => 'filegal', 'id' => -1]; } $pathParts = explode('/', $path, 3); $files = $this->table('tiki_files'); // Path detected as a file if (count($pathParts) < 3) { // If we ask for a previous version (name?version) if (preg_match('/^([^?]*)\?(\d*)$/', $pathParts[1], $matches)) { $result = $files->fetchAll( ['fileId'], ['filename' => $matches[1], 'galleryId' => (int) $parentId, 'archiveId' => $files->greaterThan(0)], 1, $matches[2], ['fileId' => 'ASC'] ); } else { $result = $files->fetchOne( 'fileId', ['filename' => $pathParts[1], 'galleryId' => (int) $parentId, 'archiveId' => 0], ['fileId' => 'DESC'] ); } if (is_array($result)) { $res = reset($result); if (! empty($res)) { return ['type' => 'file', 'id' => $res['fileId']]; } } elseif (! empty($result)) { return ['type' => 'file', 'id' => $result]; } } $galleryId = $this->table('tiki_file_galleries')->fetchOne('galleryId', ['name' => $pathParts[1], 'parentId' => (int) $parentId]); if ($galleryId) { // as a leaf if (empty($pathParts[2])) { return ['type' => 'filegal', 'id' => $galleryId]; } else { return $this->get_objectid_from_virtual_path('/' . $pathParts[2], $galleryId); } } return false; } /** * Only used in webdav context - gets the URI of the file or gallery * with default/root file gallery path removed. */ public function get_full_virtual_path($id, $type = 'file') { global $prefs; if (! $id > 0) { return false; } switch ($type) { case 'filegal': if ($id == -1) { return '/'; } $res = $this->table('tiki_file_galleries')->fetchRow(['galleryId', 'name', 'parentId'], ['galleryId' => (int) $id]); if ($res['galleryId'] == $prefs['fgal_root_id']) { $res['name'] = ''; } break; case 'file': default: $res = $this->table('tiki_files')->fetchRow(['filename', 'parentId' => 'galleryId'], ['fileId' => (int) $id]); $res['name'] = $res['filename']; } if ($res) { $parentPath = $this->get_full_virtual_path($res['parentId'], 'filegal'); if (empty($res['name'])) { return $parentPath; } else { return $parentPath . ( $parentPath == '/' ? '' : '/' ) . $res['name']; } } return false; } public function getFiletype($not = []) { if (empty($not)) { $query = 'select distinct(`filetype`) from `tiki_files` order by `filetype` asc'; } else { $query = 'select distinct(`filetype`) from `tiki_files` where `filetype` not in(' . implode(',', array_fill(0, count($not), '?')) . ')order by `filetype` asc'; } $result = $this->query($query, $not); $ret = []; while ($res = $result->fetchRow()) { $ret[] = $res['filetype']; } return $ret; } /** * Sets default options for file galleries from global preferences * @param $fgalIds * @return TikiDb_Adodb_Result|TikiDb_Pdo_Result */ public function setDefault($fgalIds) { global $prefs; $defaults = [ 'sort_mode' => $prefs['fgal_sortField'] . '_' . $prefs['fgal_sortDirection'], 'show_backlinks' => 'n', 'show_deleteAfter' => $prefs['fgal_list_deleteAfter'], 'show_lastDownload' => 'n', 'show_id' => $prefs['fgal_list_id'], 'show_icon' => $prefs['fgal_list_type'], 'show_name' => $prefs['fgal_list_name'], 'show_description' => $prefs['fgal_list_description'], 'show_size' => $prefs['fgal_list_size'], 'show_created' => $prefs['fgal_list_created'], 'show_modified' => $prefs['fgal_list_lastModif'], 'show_creator' => $prefs['fgal_list_creator'], 'show_author' => $prefs['fgal_list_author'], 'show_last_user' => $prefs['fgal_list_last_user'], 'show_comment' => $prefs['fgal_list_comment'], 'show_files' => $prefs['fgal_list_files'], 'show_hits' => $prefs['fgal_list_hits'], 'show_lockedby' => $prefs['fgal_list_lockedby'], 'show_checked' => $prefs['fgal_checked'], 'show_share' => $prefs['fgal_list_share'], 'show_explorer' => $prefs['fgal_show_explorer'], 'show_path' => $prefs['fgal_show_path'], 'show_slideshow' => $prefs['fgal_show_slideshow'], 'show_ocr_state' => $prefs['fgal_show_ocr_state'], 'default_view' => $prefs['fgal_default_view'], 'icon_fileId' => ! empty($prefs['fgal_icon_fileId']) ? $prefs['fgal_icon_fileId'] : null, 'show_source' => $prefs['fgal_list_source'], ]; $galleries = $this->table('tiki_file_galleries'); return $galleries->updateMultiple($defaults, ['galleryId' => $galleries->in($fgalIds)]); } public function getGalleryId($name, $parentId) { return $this->table('tiki_file_galleries')->fetchOne('galleryId', ['name' => $name, 'parentId' => $parentId]); } public function deleteOldFiles() { global $prefs; $smarty = TikiLib::lib('smarty'); include_once('lib/webmail/tikimaillib.php'); $query = 'select * from `tiki_files` where `deleteAfter` < ? - `lastModif` and `deleteAfter` is not NULL and `deleteAfter` != \'\' order by galleryId asc'; $files = $this->fetchAll($query, [$this->now]); foreach ($files as $fileInfo) { $definition = $this->getGalleryDefinition($fileInfo['galleryId']); $galInfo = $definition->getInfo(); if (! empty($prefs['fgal_delete_after_email'])) { $wrapper = $definition->getFileWrapper(new TikiFile($fileInfo)); $fileInfo['data'] = $wrapper->getContents(); $smarty->assign('fileInfo', $fileInfo); $smarty->assign('galInfo', $galInfo); $mail = new TikiMail(); $mail->setSubject(tra('Old File deleted:', $prefs['site_language']) . ' ' . $fileInfo['filename']); $mail->setText($smarty->fetchLang($prefs['site_language'], 'mail/fgal_old_file_deleted.tpl')); $mail->addAttachment($fileInfo['data'], $fileInfo['filename'], $fileInfo['filetype']); $to = preg_split('/ *, */', $prefs['fgal_delete_after_email']); $mail->send($to); } $this->remove_file($fileInfo, $galInfo, false); } } /** * get the wiki_syntax - use parent's if none * * @param int $galleryId gallery to get syntax from * @param array $fileinfo optional file info to process syntax on * @return string wiki markup */ public function getWikiSyntax($galleryId = 0, $fileinfo = null, $params = null) { if (! $params) { $params = $_REQUEST; } if (isset($params['insertion_syntax']) && $params['insertion_syntax'] == 'file') { // for use in 'Choose or Upload' toolbar item (tikifile) $syntax = '{file type="gallery" fileId="%fileId%" showicon="y"}'; } elseif (isset($params['filegals_manager'])) { // for use in plugin edit popup if ($params['filegals_manager'] === 'fgal_picker_id') { $syntax = '%fileId%'; // for use in plugin edit popup } elseif ($params['filegals_manager'] === 'fgal_picker') { $href = 'tiki-download_file.php?fileId=123&display'; // dummy id as sefurl expects a (/d+) pattern include_once('tiki-sefurl.php'); $href = filter_out_sefurl($href); $syntax = str_replace('123', '%fileId%', $href); } elseif (! empty($params['insertion_syntax'])) { // for use in prefs $syntax = $params['insertion_syntax']; } } if (empty($syntax)) { $syntax = $this->table('tiki_file_galleries')->fetchOne('wiki_syntax', ['galleryId' => $galleryId]); $list = $this->getGalleryParentsColumns($galleryId, ['wiki_syntax']); foreach ($list as $fgal) { if (! empty($fgal['wiki_syntax'])) { $syntax = $fgal['wiki_syntax']; break; } } } // and no syntax set, return default if (empty($syntax)) { $syntax = '{img fileId="%fileId%" thumb="box"}'; // should be a pref if (! empty($fileinfo['filetype']) && preg_match('/video\/*/', $fileinfo['filetype'])) { $syntax = '{mediaplayer src="display%fileId%"}'; } } if ($fileinfo) { // if fileinfo provided then process it now $syntax = $this->process_fgal_syntax($syntax, $fileinfo); } return $syntax; } public function add_file_hit($id) { global $prefs, $user; $files = $this->table('tiki_files'); if (StatsLib::is_stats_hit()) { // Enforce max download per file if ($prefs['fgal_limit_hits_per_file'] == 'y') { $limit = $this->get_download_limit($id); if ($limit > 0) { $count = $files->fetchCount(['fileId' => $id, 'hits' => $files->lesserThan($limit)]); if (! $count) { return false; } } } $files->update(['hits' => $files->increment(1), 'lastDownload' => $this->now], ['fileId' => (int) $id]); } else { $files->update(['lastDownload' => $this->now], ['fileId' => (int) $id]); } if ($prefs['feature_score'] == 'y' && $prefs['fgal_prevent_negative_score'] == 'y') { $score = TikiLib::lib('score')->get_user_score($user); if ($score < 0) { return false; } } $owner = $files->fetchOne('user', ['fileId' => (int) $id]); TikiLib::events()->trigger( 'tiki.file.download', [ 'type' => 'file', 'object' => $id, 'user' => $user, 'owner' => $owner, ] ); return true; } public function add_file_gallery_hit($id) { global $prefs, $user; if (StatsLib::is_stats_hit()) { $fileGalleries = $this->table('tiki_file_galleries'); $fileGalleries->update(['hits' => $fileGalleries->increment(1)], ['galleryId' => (int) $id]); } return true; } /** * Get a file by file id OR a random file from the given gallery * * @see get_file_info() for another way to select by file id */ public function get_file($id, $randomGalleryId = '') { if (empty($randomGalleryId)) { $where = '`fileId`=?'; $bindvars[] = (int)$id; } else { $where = 'tf.`galleryId`=? order by ' . $this->convertSortMode('random') . ' limit 1 '; $bindvars[] = (int)$randomGalleryId; } $query = "select tf.*, tfg.`backlinkPerms` from `tiki_files` tf left join `tiki_file_galleries` tfg on (tfg.`galleryId`=tf.`galleryId`) where $where"; $result = $this->query($query, $bindvars); return $result ? $result->fetchRow() : []; } /** * Retrieve file draft * * @param int $id */ public function get_file_draft($id) { global $user; $query = "select tfd.* from `tiki_file_drafts` tfd where `fileId`=? and `user`=?"; $result = $this->query($query, [(int)$id, $user]); return $result ? $result->fetchRow() : []; } public function get_file_by_name($galleryId, $name, $column = 'name') { $query = "select `fileId`,`path`,`galleryId`,`filename`,`filetype`,`data`,`filesize`,`name`,`description`, `created` from `tiki_files` where `galleryId`=? AND `$column`=? ORDER BY created DESC LIMIT 1"; $result = $this->query($query, [(int) $galleryId, $name]); $res = $result->fetchRow(); return $res; } public function list_files($offset = 0, $maxRecords = -1, $sort_mode = 'created_desc', $find = '') { global $prefs; return $this->get_files($offset, $maxRecords, $sort_mode, $find, $prefs['fgal_root_id'], false, false, true, true, false, false, true, true); } public function list_file_galleries($offset = 0, $maxRecords = -1, $sort_mode = 'name_desc', $user = '', $find = '', $parentId = -1, $with_archive = false, $with_subgals = true, $with_subgals_size = false, $with_files = false, $with_files_data = false, $with_parent_name = true, $with_files_count = true, $recursive = true) { return $this->get_files($offset, $maxRecords, $sort_mode, $find, $parentId, $with_archive, $with_subgals, $with_subgals_size, $with_files, $with_files_data, $with_parent_name, $with_files_count, $recursive, $user); } /** * Get files and/or subgals list with additional data from one or all file galleries * * @param int $offset * @param int $maxRecords * @param string $sort_mode * @param string $find * @param int $galleryId (-1 = all galleries (default)) * @param bool $with_archive give back the number of archives * @param bool $with_subgals include subgals in the listing * @param bool $with_subgals_size calculate the size of subgals * @param bool $with_files include files in the listing * @param bool $with_files_data include files data in the listing * @param bool $with_parent_name include parent names in the listing * @param bool $recursive include all subgals recursively (yet only implemented for galleryId == -1) * @param string $my_user use another user than the current one * @param bool $keep_subgals_together do not mix files and subgals when sorting (if true, subgals will always be at the top) * @param bool $parent_is_file use $galleryId param as $fileId (to return only archives of the file) * @param array filter: creator, categId, lastModif, lastDownload, fileId, created * @param string wiki_syntax: text to be inserted in editor onclick (from fgal manager) * @return array of found files and subgals */ public function get_files( $offset, $maxRecords, $sort_mode, $find = null, $galleryId = -1, $with_archive = false, $with_subgals = false, $with_subgals_size = true, $with_files = true, $with_files_data = false, $with_parent_name = false, $with_files_count = true, $recursive = false, $my_user = '', $keep_subgals_together = true, $parent_is_file = false, $with_backlink = false, $filter = '', $wiki_syntax = '' ) { global $user, $tiki_p_admin, $tiki_p_admin_file_galleries, $prefs; $f_jail_bind = []; $g_jail_bind = []; $f_where = ''; $bindvars = []; if (( ! $with_files && ! $with_subgals ) || ( $parent_is_file && $galleryId <= 0 )) { return []; } $fileId = -1; if ($parent_is_file) { $fileId = $galleryId; $galleryId = -2; } if ($recursive) { $idTree = []; if (is_array($galleryId)) { foreach ($galleryId as $galId) { $this->getGalleryIds($idTree, $galId, 'list'); } } else { $this->getGalleryIds($idTree, $galleryId, 'list'); } $galleryId =& $idTree; } $with_subgals_size = ( $with_subgals && $with_subgals_size ); if (empty($my_user)) { $my_user = $user; } $f_table = '`tiki_files` as tf'; $g_table = '`tiki_file_galleries` as tfg'; $f_group_by = ''; $orderby = $this->convertSortMode($sort_mode); // order by must handle "1", which is the convertSortMode error return if ($orderby == '1') { $orderby = ''; } $categlib = TikiLib::lib('categ'); $f2g_corresp = [ '0 as `isgal`' => '1 as `isgal`', 'tf.`fileId` as `id`' => 'tfg.`galleryId` as `id`', 'tf.`galleryId` as `parentId`' => 'tfg.`parentId`', 'tf.`name`' => 'tfg.`name`', 'tf.`description`' => 'tfg.`description`', 'tf.`filesize` as `size`' => "0 as `size`", 'tf.`created`' => 'tfg.`created`', 'tf.`filename`' => 'tfg.`name` as `filename`', 'tf.`filetype` as `type`' => "tfg.`type`", 'tf.`user` as `creator`' => 'tfg.`user` as `creator`', 'tf.`author`' => "'' as `author`", 'tf.`hits`' => "tfg.`hits`", 'tf.`lastDownload`' => "0 as `lastDownload`", 'tf.`votes`' => 'tfg.`votes`', 'tf.`points`' => 'tfg.`points`', 'tf.`path`' => "'' as `path`", 'tf.`reference_url`' => "'' as `reference_url`", 'tf.`is_reference`' => "'' as `is_reference`", 'tf.`hash`' => "'' as `hash`", 'tf.`search_data`' => 'tfg.`name` as `search_data`', 'tf.`metadata`' => "'' as `metadata`", 'tf.`lastModif` as `lastModif`' => 'tfg.`lastModif` as `lastModif`', 'tf.`lastModifUser` as `last_user`' => "'' as `last_user`", 'tf.`lockedby`' => "'' as `lockedby`", 'tf.`comment`' => "'' as `comment`", 'tf.`deleteAfter`' => "'' as `deleteAfter`", 'tf.`maxhits`' => "'' as `maxhits`", 'tf.`archiveId`' => '0 as `archiveId`', 'tf.`ocr_state`' => "'' as `ocr_state`", "'' as `visible`" => 'tfg.`visible`', "'' as `public`" => 'tfg.`public`', /// Below are obsolete fields that will be removed soon (they have their new equivalents above) 'tf.`fileId`' => 'tfg.`galleryId` as `fileId`', /// use 'id' instead 'tf.`galleryId`' => 'tfg.`parentId` as `galleryId`', /// use 'parentId' instead 'tf.`filesize`' => "0 as `filesize`", /// use 'size' instead 'tf.`filetype`' => "tfg.`type` as `filetype`", /// use 'type' instead 'tf.`user`' => 'tfg.`user`', /// use 'creator' instead 'tf.`lastModifUser`' => "'' as `lastModifUser`", /// use 'last_user' instead '0 as `icon_fileId`' => '`icon_fileId`' // icon for galleries in browse mode ]; if ($with_files_data) { $f2g_corresp['tf.`data`'] = "'' as `data`"; } if ($with_files_count) { $f2g_corresp["'' as `files`"] = 'count(distinct tfc.`fileId`) as `files`'; } if ($with_archive) { $f2g_corresp['count(tfh.`fileId`) as `nbArchives`'] = '0 as `nbArchives`'; $f_table .= ' LEFT JOIN `tiki_files` tfh ON (tf.`fileId` = tfh.`archiveId`)'; $f_group_by = ' GROUP BY tf.`fileId`'; } if ($with_files && $prefs['feature_file_galleries_save_draft'] == 'y') { $f2g_corresp['count(tfd.`fileId`) as `nbDraft`'] = '0 as `nbDraft`'; $f_table .= ' LEFT JOIN `tiki_file_drafts` tfd ON (tf.`fileId` = tfd.`fileId` and tfd.`user`=?)'; $f_group_by = ' GROUP BY tf.`fileId`'; $bindvars[] = $user; } if ($with_backlink) { $f2g_corresp['count(tfb.`fileId`) as `nbBacklinks`'] = '0 as `nbBacklinks`'; $f_table .= ' LEFT JOIN `tiki_file_backlinks` tfb ON (tf.`fileId` = tfb.`fileId`)'; $f_group_by = ' GROUP BY tf.`fileId`'; } if ($f_group_by) { $f_group_by .= ', tf.`fileId`, tf.`galleryId`, tf.`name`, tf.`description`, tf.`filesize`, tf.`created`, tf.`filename`, tf.`filetype`, tf.`user`, tf.`author`, tf.`hits`, tf.`lastDownload`, tf.`votes`, tf.`points`, tf.`path`, tf.`reference_url`, tf.`is_reference`, tf.`hash`, tf.`search_data`, tf.`metadata`, tf.`lastModif`, tf.`lastModifUser`, tf.`lockedby`, tf.`comment`, tf.`deleteAfter`, tf.`maxhits`, tf.`archiveId`, tf.`fileId`, tf.`galleryId`, tf.`filesize`, tf.`filetype`, tf.`user`, tf.`lastModifUser`'; } if (! empty($filter['orphan']) && $filter['orphan'] == 'y') { $f_where .= ' AND tfb.`objectId` IS NULL'; if (! $with_backlink) { $f_table .= 'LEFT JOIN `tiki_file_backlinks` tfb ON (tf.`fileId`=tfb.`fileId`)'; } } if (! empty($filter['categId'])) { $jail = $filter['categId']; } else { $jail = $categlib->get_jail(); } $f_jail_join = ''; $f_jail_where = ''; $f_jail_bind = []; if ($jail) { $categlib->getSqlJoin($jail, 'file', 'tf.`fileId`', $f_jail_join, $f_jail_where, $f_jail_bind); } if ($with_parent_name && ! $with_subgals) { $f2g_corresp['tfgp.`name` as `parentName`'] = ''; $f_table .= ' LEFT OUTER JOIN `tiki_file_galleries` tfgp ON (tf.`galleryId` = tfgp.`galleryId`)'; } $f_query = 'SELECT ' . implode(', ', array_keys($f2g_corresp)) . ' FROM ' . $f_table . $f_jail_join . ' WHERE tf.`archiveId`=' . ( $parent_is_file ? $fileId : '0' ) . $f_jail_where . $f_where; $mid = ''; $midvars = []; if (isset($find)) { $findesc = '%' . $find . '%'; $tab = $with_subgals ? 'tab' : 'tf'; $mid = " (upper($tab.`name`) LIKE upper(?) OR upper($tab.`description`) LIKE upper(?) OR upper($tab.`filename`) LIKE upper(?))"; $midvars = [$findesc, $findesc, $findesc]; } if (! empty($filter['creator'])) { $f_query .= ' AND tf.`user` = ? '; $bindvars[] = $filter['creator']; } if (! empty($filter['lastModif'])) { $f_query .= ' AND tf.`lastModif` < ? '; $bindvars[] = $filter['lastModif']; } if (! empty($filter['lastDownload'])) { $f_query .= ' AND (tf.`lastDownload` < ? or tf.`lastDownload` is NULL)'; $bindvars[] = $filter['lastDownload']; } if (! empty($filter['fileType'])) { $f_query .= ' AND (tf.`filetype` = ?)'; $bindvars[] = $filter['fileType']; } if (! empty($filter['created'])) { $f_query .= ' AND tf.`created` > ? '; $bindvars[] = $filter['created']; } if (! empty($filter['fileId'])) { $f_query .= ' AND tf.`fileId` in (' . implode(',', array_fill(0, count($filter['fileId']), '?')) . ')'; $bindvars = array_merge($bindvars, $filter['fileId']); } $galleryId_str = ''; if (is_array($galleryId)) { $galleryId_str = ' in (' . implode(',', array_fill(0, count($galleryId), '?')) . ')'; $bindvars = array_merge($bindvars, $galleryId); } elseif ($galleryId >= -1) { $galleryId_str = '=?'; $bindvars[] = $galleryId; } if ($galleryId_str != '') { $f_query .= ' AND tf.`galleryId`' . $galleryId_str; } if ($with_subgals) { $g_mid = ''; $g_join = ''; $g_group_by = ''; $join = ''; $select = 'tab.*'; if ($with_files_count) { $g_join = ' LEFT JOIN `tiki_files` tfc ON (tfg.`galleryId` = tfc.`galleryId`)'; $g_group_by = ' GROUP BY tfg.`galleryId`'; } $g_jail_join = ''; $g_jail_where = ''; $g_jail_bind = []; if ($jail) { $categlib->getSqlJoin($jail, 'file gallery', '`tfg`.`galleryId`', $g_jail_join, $g_jail_where, $g_jail_bind); } $g_query = 'SELECT ' . implode(', ', array_values($f2g_corresp)) . ' FROM ' . $g_table . $g_join . $g_jail_join; $g_query .= " WHERE 1=1 "; if ($galleryId_str != '') { $g_query .= ' AND tfg.`parentId`' . $galleryId_str; if ($with_files) { // f_query is not used if !with_files if (is_array($galleryId)) { $bindvars = array_merge($bindvars, $galleryId); } else { $bindvars[] = $galleryId; } } } // If $user is admin then get ALL galleries, if not only user galleries are shown // If the user is not admin then select it's own galleries or public galleries if ($tiki_p_admin !== 'y' && $tiki_p_admin_file_galleries !== 'y' && empty($parentId)) { $g_mid = " AND (tfg.`user`=? OR tfg.`visible`='y' OR tfg.`public`='y')"; $bindvars[] = $my_user; } $g_query .= $g_mid; $g_query .= $g_jail_where; $bindvars = array_merge($bindvars, $g_jail_bind); if ($with_parent_name) { $select .= ', tfgp.`name` as `parentName`'; $join .= ' LEFT OUTER JOIN `tiki_file_galleries` tfgp ON (tab.`parentId` = tfgp.`galleryId`)'; } if ($g_group_by) { $g_group_by .= ", tfg.`parentId`, tfg.`name`, tfg.`description`, tfg.`created`, tfg.`name`, tfg.`type`, tfg.`user`, tfg.`hits`, tfg.`votes`, tfg.`points`, tfg.`name`, tfg.`lastModif`, tfg.`visible`, tfg.`public`, tfg.`galleryId`, tfg.`parentId`, tfg.`type`, tfg.`user`, `icon_fileId` "; } if ($with_files) { $query = "SELECT $select FROM (($f_query $f_group_by) UNION ALL ($g_query $g_group_by)) as tab" . $join; $bindvars = array_merge($f_jail_bind, $bindvars); } else { $query = "SELECT $select FROM ($g_query $g_group_by) as tab" . $join; } if ($mid != '') { $query .= ' WHERE' . $mid; $bindvars = array_merge($bindvars, $midvars); } //ORDER BY RAND() can be slow on large databases if ($orderby != 'RAND()' && $orderby != '' && $orderby != '1') { $orderby = 'tab.' . $orderby; } } else { $query = $f_query; $bindvars = array_merge($f_jail_bind, $bindvars); if ($mid != '') { $query .= ' AND' . $mid; $bindvars = array_merge($bindvars, $midvars); } $query .= $f_group_by; } if ($keep_subgals_together) { $query .= ' ORDER BY `isgal` desc' . ($orderby == '' ? '' : ', ' . $orderby); } elseif ($orderby != '') { $query .= ' ORDER BY ' . $orderby; } $result = $this->fetchAll($query, $bindvars); $ret = []; $gal_size_order = []; $cant = 0; $need_everything = ( $with_subgals_size && ( $sort_mode == 'size_asc' || $sort_mode == 'filesize_asc' ) ); $numResults = count($result); if (! $need_everything) { $result = array_slice($result, $offset == -1 ? 0 : $offset, $maxRecords == -1 ? null : $maxRecords); } $galleryIds = array_map( function ($res) { return $res['id']; }, array_filter($result, function ($res) { return $res['isgal'] == 1; }) ); $fileIds = array_map( function ($res) { return $res['id']; }, array_filter($result, function ($res) { return $res['isgal'] != 1; }) ); Perms::bulk(['type' => 'file gallery'], 'object', $galleryIds); Perms::bulk(['type' => 'file'], 'object', $fileIds); foreach ($result as $res) { $object_type = ( $res['isgal'] == 1 ? 'file gallery' : 'file'); $galleryId = $res['isgal'] == 1 ? $res['id'] : $res['galleryId']; if ($prefs['fgal_upload_from_source'] == 'y' && $object_type == 'file') { $attributes = TikiLib::lib('attribute')->get_attributes('file', $res['id']); if (isset($attributes['tiki.content.source'])) { $res['source'] = $attributes['tiki.content.source']; } } // use permission subsystem to figure out if this file has its own permissions, category permisisons or file gallery permissions attached $res['perms'] = $this->get_perm_object($res['id'], $object_type, [], false); // If the current user is the file owner, then list the file (fix for the userfiles - wasn't listing even if trying to list own files) if ($my_user == $res['creator']) { $res['perms']['tiki_p_view_file_gallery'] = 'y'; } // Don't return the current item, if : // the user has no rights to view the file gallery AND no rights to list all galleries (in case it's a gallery) if ($res['perms']['tiki_p_view_file_gallery'] != 'y' && $res['perms']['tiki_p_list_file_galleries'] != 'y') { $numResults--; continue; } if (empty($backlinkPerms[$res['galleryId']])) { $info = $this->get_file_gallery_info($res['galleryId']); $backlinkPerms[$res['galleryId']] = $info['backlinkPerms']; } if ($backlinkPerms[$res['galleryId']] == 'y' && $this->hasOnlyPrivateBacklinks($res['id'])) { $numResults--; continue; } // add markup to be inserted onclick // add information for share column if is active if ($object_type === 'file') { $res['wiki_syntax'] = $this->process_fgal_syntax($wiki_syntax, $res); if ($prefs['auth_token_access'] == 'y') { $query = 'select email, sum((maxhits - hits)) as visit, sum(maxhits) as maxhits from tiki_auth_tokens where `parameters`=? group by email'; $share_result = $this->fetchAll($query, ['{"fileId":"' . $res['id'] . '"}']); if ($share_result) { $res['share']['data'] = $share_result; $tmp = []; if (is_array($res['share']['data'])) { foreach ($res['share']['data'] as $data) { $tmp[] = $data['email']; } } $string_share = implode(', ', $tmp); $res['share']['string'] = substr($string_share, 0, 40); if (strlen($string_share) > 40) { $res['share']['string'] .= '...'; } $res['share']['nb'] = count($share_result); } else { $res['share'] = null; } } } else { // a gallery $res['name'] = $this->get_user_gallery_name($res); } $ret[$cant] = $res; if ($with_subgals_size && $res['isgal'] == 1) { $ret[$cant]['size'] = (string)$this->getUsedSize($res['id']); $ret[$cant]['filesize'] = $ret[$cant]['size']; /// Obsolete if ($keep_subgals_together) { $gal_size_order[$cant] = $ret[$cant]['size']; } } if ($with_subgals_size && ! $keep_subgals_together) { $gal_size_order[$cant] = $ret[$cant]['size']; } // generate link for podcasts $ret[$cant]['podcast_filename'] = $res['path']; $cant++; } if (count($gal_size_order) > 0) { if ($sort_mode == 'size_asc' || $sort_mode == 'filesize_asc') { asort($gal_size_order, SORT_NUMERIC); } elseif ($sort_mode == 'size_desc' || $sort_mode == 'filesize_desc') { arsort($gal_size_order, SORT_NUMERIC); } $ret2 = []; foreach ($gal_size_order as $k => $v) { $ret2[] = $ret[$k]; unset($ret[$k]); } if (count($ret) > 0) { foreach ($ret as $k => $v) { $ret2[] = $v; } } unset($ret); $ret =& $ret2; } if ($need_everything && ( $offset > 0 || $maxRecords != -1 )) { if ($maxRecords == -1) { $ret = array_slice($ret, $offset); } else { $ret = array_slice($ret, $offset, $maxRecords); } } return ['data' => $ret, 'cant' => $numResults]; } /** * Get a file with additional data * * @param int $fileId * @return array An array representing a file, formatted like get_files() * @throws Exception if file does not exist */ public function get_file_additional($fileId) { $file = $this->get_file_info($fileId); $files = $this->get_files(-1, -1, false, null, $file['galleryId'])['data']; $files = array_filter($files, function ($file) use ($fileId) { return $file['fileId'] == $fileId; }); if (! $files) { throw new Exception('File not found'); } return current($files); } /** * No longer used (12.x) - was only called from listfgal_pref() in /lib/prefs/home.php * * @param int $offset * @param $maxRecords * @param string $sort_mode * @param string $user * @param null $find * @return array */ public function list_visible_file_galleries($offset = 0, $maxRecords = -1, $sort_mode = 'name_desc', $user = '', $find = null) { // If $user is admin then get ALL galleries, if not only user galleries are shown $fileGalleries = $this->table('tiki_file_galleries'); $conditions = [ 'visible' => 'y', ]; // If the user is not admin then select `it` 's own galleries or public galleries global $tiki_p_admin_files_galleries; if ($tiki_p_admin_files_galleries != 'y') { $conditions['nonAdmin'] = $fileGalleries->expr('(`user`=? or `public`=?)', [$user, 'y']); } if (! empty($find)) { $findesc = '%' . $find . '%'; $conditions['search'] = $fileGalleries->expr('(`name` like ? or `description` like ?)', [$findesc, $findesc]); } $sort = $this->convertSortMode($sort_mode); return [ "data" => $fileGalleries->fetchAll($fileGalleries->all(), $conditions, $maxRecords, $offset, $fileGalleries->expr($sort)), "cant" => $fileGalleries->fetchCount($conditions), ]; } // beware: this method does not return false/empty array if file gallery is missing even when defaultsFallback is off public function get_file_gallery($id = -1, $defaultsFallback = true) { static $defaultValues = null; if ($defaultValues === null && $defaultsFallback) { $defaultValues = $this->default_file_gallery(); } if ($id > 0) { $res = $this->table('tiki_file_galleries')->fetchFullRow(['galleryId' => (int) $id]); } else { $res = []; } if ($res !== false) { // Use default values if some values are not specified if ($defaultsFallback) { foreach ($defaultValues as $k => $v) { if (! isset($res[$k]) || $res[$k] === null) { $res[$k] = $v; } } } $res['name'] = $this->get_user_gallery_name($res); } return $res; } public function can_upload_to($gal_info) { global $user, $prefs; if ($prefs['feature_use_fgal_for_user_files'] !== 'y' || $gal_info['type'] !== 'user') { $perms = Perms::get('file gallery', $gal_info['galleryId']); return $perms->upload_files; } else { $perms = TikiLib::lib('tiki')->get_local_perms($user, $gal_info['galleryId'], 'file gallery', $gal_info, false); //get_perm_object($galleryId, 'file gallery', $galinfo); return $perms['tiki_p_upload_files'] === 'y'; } } /** * convert markup to be inserted onclick - replace: %fileId%, %name%, %description% etc * also will convert attributes, e.g. %tiki.content.url% will be replaced with the remote url * * @param $syntax string template syntax * @param $file array file info * @return string wiki syntax for that file */ private function process_fgal_syntax($syntax, $file) { $replace_keys = ['fileId', 'name', 'filename', 'description', 'hits', 'author', 'filesize', 'filetype']; foreach ($replace_keys as $k) { if (isset($file[$k])) { $syntax = preg_replace("/%$k%/", $file[$k], $syntax); } } $attributes = TikiLib::lib('attribute')->get_attributes('file', $file['fileId']); foreach ($attributes as $k => $v) { $syntax = preg_replace("/%$k%/", $v, $syntax); } return $syntax; } private function print_msg($msg, $htmlEntities = false) { global $prefs; if ($htmlEntities) { $msg = htmlentities($msg, ENT_QUOTES, 'UTF-8'); } if ($prefs['javascript_enabled'] == 'y') { echo $msg; } } /*shared*/ public function actionHandler($action, $params) { $method_name = '_actionHandler_' . $action; if (! is_callable([ $this, $method_name ])) { return false; } return call_user_func([ $this, $method_name ], $params); } /** * @param $params * @return bool|TikiDb_Adodb_Result|TikiDb_Pdo_Result * @throws Exception */ private function _actionHandler_removeFile($params) { // mandatory params: int fileId // optional params: boolean draft, array gal_info if (! empty($params) && isset($params['fileId'])) { // To remove an image the user must be the owner or the file or the gallery or admin if (! isset($params['draft'])) { $params['draft'] = false; } $smarty = TikiLib::lib('smarty'); if (! $info = $this->get_file_info($params['fileId'])) { $smarty->assign('msg', tra('Incorrect param')); $smarty->display('error.tpl'); die; } if (empty($params['gal_info']) || ! isset($params['gal_info']['user'])) { if (isset($info['galleryId'])) { $params['gal_info'] = $this->get_file_gallery_info($info['galleryId']); } else { $params['gal_info'] = ['user' => '']; } } global $tiki_p_admin_file_galleries, $user; if ($tiki_p_admin_file_galleries != 'y' && ( ! $user || $user != $params['gal_info']['user'] )) { if ($user != $info['user']) { $smarty->assign('errortype', 401); $smarty->assign('msg', tra('You do not have permission to remove files from this gallery')); $smarty->display('error.tpl'); die; } } $backlinks = $this->getFileBacklinks($params['fileId']); if (isset($_POST['ticket']) && ! empty($backlinks)) { $smarty->assign_by_ref('backlinks', $backlinks); $smarty->assign('file_backlinks_title', 'Warning: The file is used in:');//get_strings tra('Warning: The file is used in:') $smarty->assign('confirm_detail', $smarty->fetch('file_backlinks.tpl')); ///FIXME } if ($params['draft']) { return $this->remove_draft($info['fileId'], $user); } else { return $this->remove_file($info, $params['gal_info']); } } } //Note that file edits are handled here along with uploads private function _actionHandler_uploadFile($params) { global $user, $prefs, $tiki_p_admin, $tiki_p_batch_upload_files; $logslib = TikiLib::lib('logs'); $smarty = TikiLib::lib('smarty'); $tikilib = TikiLib::lib('tiki'); $batch_job = false; $didFileReplace = false; $aux = []; $errors = []; $uploads = []; $foo = parse_url($_SERVER["REQUEST_URI"]); $foo1 = str_replace("tiki-upload_file", "tiki-download_file", $foo["path"]); $smarty->assign('url_browse', $tikilib->httpPrefix() . $foo1); $url_browse = $tikilib->httpPrefix() . $foo1; // create direct download path for podcasts $gal_info = null; if (! empty($params['galleryId'][0])) { $gal_info = $this->get_file_gallery((int) $params['galleryId'][0]); $podCastGallery = $this->isPodCastGallery((int) $params["galleryId"][0], $gal_info); } $podcast_url = str_replace("tiki-upload_file.php", "", $foo["path"]); $podcast_url = $tikilib->httpPrefix() . $podcast_url . $prefs['fgal_podcast_dir']; if (isset($params['fileId'])) { $editFile = true; $editFileId = $params['fileId']; if ($gal_info !== null && ( $nb_files = count($params['galleryId']) ) != 1) { for ($i = $nb_files - 1; $i >= 0; $i--) { if (! isset($params['galleryId'][$i]) || $params['galleryId'][$i] != $params['galleryId'][0]) { $smarty->assign('errortype', 401); $smarty->assign('msg', tra('You are trying to edit multiple files in different galleries, which is not supported yet')); $smarty->display('error.tpl'); die; } } } if (isset($params['fileInfo'])) { $fileInfo = $params['fileInfo']; $fileInfo['fileId'] = $editFileId; } else { if (! ( $fileInfo = $this->get_file_info($editFileId) )) { $smarty->assign('msg', tra('The specified file does not exist')); $smarty->display('error.tpl'); die; } } if (! empty($params['name'][0])) { $fileInfo['name'] = $params['name'][0]; } if (! empty($params['description'][0])) { $fileInfo['description'] = $params['description'][0]; } if (! empty($params['galleryId'][0])) { $fileInfo['galleryId'] = $params['galleryId'][0]; } if (! empty($params['user'][0])) { $fileInfo['user'] = $params['user'][0]; } if (! empty($params['author'][0])) { $fileInfo['author'] = $params['author'][0]; } if (! empty($params['ocr_lang'][0])) { $fileInfo['ocr_lang'] = json_encode($params['ocr_lang']); } if (isset($params['ocr_state'])) { $fileInfo['ocr_state'] = $params['ocr_state'][0]; } if (! empty($params['filetype'][0])) { if (isset($fileInfo['fileId']) && $fileInfo['filetype'] != $params['filetype'][0] && substr($params['filetype'][0], 0, 9) == 'image/svg') { try { // use a dummy.svg filename just so content checker knows this is being interpreted as svg $this->assertUploadedContentIsSafe($fileInfo['data'], 'dummy.svg'); } catch (Exception $e) { $smarty->assign('msg', tra("Forcing a filetype of image/svg+xml is blocked for security reasons")); $smarty->display('error.tpl'); die; } } $fileInfo['filetype'] = $params['filetype'][0]; } if (! empty($params['comment'][0])) { $fileInfo['comment'] = $params['comment'][0]; } } else { $editFileId = 0; $editFile = false; $fileInfo = null; } if (! empty($_FILES['userfile'])) { $feedback_message = ''; $aFiles['userfile'] = $_FILES['userfile']; foreach ($aFiles["userfile"]["error"] as $key => $error) { if (empty($params['galleryId'][$key])) { continue; } if (! isset($params['comment'][$key])) { $params['comment'][$key] = ''; } // Before feature_use_fgal_for_wiki_attachments, the wiki page attachments had a comment field which was stored and displayed. // With feature_use_fgal_for_wiki_attachments, wiki page attachments are normal file gallery files which allow showing and editing the description field, but not the comment field. // The comment field should probably be deprecated but the upload interface still asks for a comment field, so we put it as a description if there is none as well as as a comment. if (isset($prefs['feature_use_fgal_for_wiki_attachments']) && $prefs['feature_use_fgal_for_wiki_attachments'] = 'y') { if (! isset($params['description'][$key])) { $params['description'][$key] = $params['comment'][$key]; } } // We process here file uploads if (! empty($aFiles["userfile"]["name"][$key])) { // Were there any problems with the upload? If so, report here. if (! is_uploaded_file($aFiles["userfile"]["tmp_name"][$key])) { $errors[] = $aFiles['userfile']['name'][$key] . ': ' . tra('Upload was not successful') . ': ' . $this->uploaded_file_error($error); continue; } // Check the name if (! $this->is_filename_valid($aFiles['userfile']['name'][$key])) { $errors[] = tra('Invalid filename (using filters for filenames)') . ': ' . $aFiles["userfile"]["name"][$key]; continue; } $name = $aFiles["userfile"]["name"][$key]; if (isset($params["isbatch"][$key]) && $params["isbatch"][$key] == 'on' && strtolower(substr($name, strlen($name) - 3)) == 'zip') { if ($tiki_p_batch_upload_files == 'y') { try { $this->process_batch_file_upload($params["galleryId"][$key], $aFiles["userfile"]['tmp_name'][$key], $user, isset($params["description"][$key]) ? $params["description"][$key] : '', $errors); } catch (Exception $e) { $errors[] = tra('An exception occurred:') . ' ' . $e->getMessage(); } if (! empty($errors)) { continue; } $batch_job = true; $batch_job_galleryId = $params["galleryId"][$key]; $this->print_msg(tra('Batch file processed') . " $name", true); continue; } else { $errors[] = tra('You don\'t have permission to upload zipped file packages'); continue; } } if (! $this->checkQuota($aFiles['userfile']['size'][$key], $params['galleryId'][$key], $error)) { $errors[] = $error; continue; } $size = $aFiles["userfile"]['size'][$key]; $type = $aFiles["userfile"]['type'][$key]; $name = stripslashes($aFiles["userfile"]['name'][$key]); $file_name = $aFiles["userfile"]["name"][$key]; $file_tmp_name = $aFiles["userfile"]["tmp_name"][$key]; $tmp_dest = $prefs['tmpDir'] . "/" . $file_name . ".tmp"; // Handle per gallery image size limits $ratio = 0; list(,$subtype) = explode('/', strtolower($type)); // supress ie non-mime type (ie sends non standard mime-type for jpeg) if ($subtype == "pjpeg") { $subtype = "jpeg"; $type = "image/jpeg"; } // No resizing if (! move_uploaded_file($file_tmp_name, $tmp_dest)) { if ($tiki_p_admin == 'y') { $errors[] = tra('Errors detected') . '. ' . tra('Check that these paths exist and are writable by the web server') . ': ' . $file_tmp_name . ' ' . $tmp_dest; continue; } else { $errors[] = tra('Errors detected'); continue; } $logslib->add_log('file_gallery', tra('Errors detected') . '. ' . tra('Check that these paths exist and are writable by the web server') . ': ' . $file_tmp_name . ' ' . $tmp_dest); } else { $logslib->add_log('file_gallery', tra('File added: ') . $tmp_dest . ' ' . tra('by') . ' ' . $user); } if (false === $data = file_get_contents($tmp_dest)) { $errors[] = tra('Cannot read the file:') . ' ' . $tmp_dest; } try { $this->assertUploadedContentIsSafe($data, $file_name, $params['galleryId']); } catch (Exception $e) { $errors[] = $e->getMessage(); } //Add metadata $filemeta = $this->extractMetadataJson($tmp_dest); @unlink($tmp_dest); if (! $size) { $errors[] = tra('Warning: Empty file:') . ' ' . $name . '. ' . tra('Please re-upload the file'); } if (count($errors)) { continue; } if (preg_match('/.flv$/', $name)) { $type = "video/x-flv"; } if (empty($params['name'][$key])) { $params['name'][$key] = $this->getTitleFromFilename($name); } if (empty($params['deleteAfter'][$key]) || empty($params['deleteAfter_unit'][$key])) { $deleteAfter = null; } else { $deleteAfter = $params['deleteAfter'][$key] * $params['deleteAfter_unit'][$key]; } if (is_array($fileInfo)) { $fileInfo['filename'] = $file_name; } $file = TikiFile::id($editFileId); if (! $file->exists()) { $file->setParam('galleryId', $params["galleryId"][$key]); } $file->init([ 'description' => $params["description"][$key] ?? '', 'user' => $params['user'][$key] ?? $user, 'author' => $params['author'][$key] ?? $user, 'deleteAfter' => $deleteAfter, 'metadata' => $filemeta, ]); if (! empty($params['comment'][$key])) { $file->setParam('comment', $params['comment'][$key]); } $didFileReplace = true; if ($editFile) { $fileId = $file->replace($data, $type, $params["name"][$key], $name, null, null, true); } else { $title = $params["name"][$key]; if (! $params['imagesize'][$key]) { $image_x = $params["image_max_size_x"]; $image_y = $params["image_max_size_y"]; } else { $image_x = $gal_info["image_max_size_x"]; $image_y = $gal_info["image_max_size_y"]; } $fileId = $file->replace($data, $type, $title, $name, $image_x, $image_y, true); } if (! $fileId) { $errors[] = tra('The upload was not successful due to duplicate file content') . ': ' . $name; $file->galleryDefinition()->delete($file); } else { $_SESSION['allowed'][$fileId] = true; } if ($prefs['fgal_limit_hits_per_file'] == 'y' && isset($params['hit_limit'][$key])) { $this->set_download_limit($fileId, $params['hit_limit'][$key]); } if (count($errors) == 0) { $aux['name'] = $name; $aux['size'] = $size; $aux['fileId'] = $fileId; if ($podCastGallery) { $aux['dllink'] = $podcast_url . $file->path . '&thumbnail=y'; } else { $aux['dllink'] = $url_browse . "?fileId=" . $fileId; if ($prefs['javascript_enabled'] == 'y') { if (! empty($_POST['totalSubmissions']) && (int) $_POST['totalSubmissions'] > 1) { if ((int) $_POST['submission'] === (int) $_POST['totalSubmissions']) { Feedback::success(tr('Files uploaded'), true); } $feedback_message = tr('File "%0" uploaded', $file_name); } else { Feedback::success(tr('File "%0" uploaded', $file_name), true); } } else { Feedback::success(tr('File "%0" uploaded', $file_name)); } } $uploads[] = $aux; $cat_type = 'file'; $cat_objid = $fileId; $cat_desc = substr($params["description"][$key], 0, 200); $cat_name = empty($params['name'][$key]) ? $name : $params['name'][$key]; $cat_href = $aux['dllink']; if ($prefs['feature_groupalert'] == 'y' && isset($params['listtoalert'])) { $groupalertlib = TikiLib::lib('groupalert'); $groupalertlib->Notify($params['listtoalert'], "tiki-download_file.php?fileId=" . $fileId); } include_once('categorize.php'); // Print progress if (empty($params['returnUrl']) && $prefs['javascript_enabled'] == 'y' && empty($params['fileId'])) { $smarty->assign("name", $aux['name']); $smarty->assign("size", $aux['size']); $smarty->assign("fileId", $aux['fileId']); $smarty->assign("dllink", $aux['dllink']); $smarty->assign("feedback_message", $feedback_message); $syntax = $this->getWikiSyntax($params["galleryId"][$key]); $syntax = $this->process_fgal_syntax($syntax, $aux); $smarty->assign('syntax', $syntax); if (! empty($_REQUEST['filegals_manager'])) { $smarty->assign('filegals_manager', $_REQUEST['filegals_manager']); } $smarty->display("tiki-upload_file_progress.tpl"); } } } } } if (empty($params['returnUrl'])) { if (count($errors)) { $errors = implode('. ', $errors); Feedback::error($errors); $errors = []; } } if ($editFile && ! $didFileReplace) { // edit properties without upload if (empty($params['deleteAfter']) || empty($params['deleteAfter_unit'])) { $deleteAfter = null; } else { $deleteAfter = $params['deleteAfter'] * $params['deleteAfter_unit']; } $file = TikiFile::id($editFileId); $file->init([ 'description' => $fileInfo['description'], 'galleryId' => $fileInfo['galleryId'], 'user' => $fileInfo['user'], 'author' => $fileInfo['author'], 'comment' => $fileInfo['comment'], 'deleteAfter' => $deleteAfter, 'metadata' => $fileInfo['metadata'], 'lastModif' => $fileInfo['lastModif'], 'lockedby' => $fileInfo['lockedby'], 'ocr_lang' => $fileInfo['ocr_lang'], 'ocr_state' => $fileInfo['ocr_state'] ]); $fileInfo['fileId'] = $file->replace($fileInfo['data'], $fileInfo['filetype'], $fileInfo['name'], $fileInfo['filename']); $fileChangedMessage = tra('File update was successful') . ': ' . $params['name']; $smarty->assign('fileChangedMessage', $fileChangedMessage); $cat_type = 'file'; $cat_objid = $editFileId; $cat_desc = substr($params["description"][0], 0, 200); $cat_name = empty($fileInfo['name']) ? $fileInfo['filename'] : $fileInfo['name']; $cat_href = $podCastGallery ? $podcast_url . $fhash : "$url_browse?fileId=" . $editFileId; if ($prefs['fgal_limit_hits_per_file'] == 'y') { $this->set_download_limit($editFileId, $params['hit_limit'][0]); } include_once('categorize.php'); if (count($errors) == 0) { Feedback::success('Upload or modification made'); header("location: tiki-list_file_gallery.php?galleryId=" . $params["galleryId"][0]); die; } } if ($errors) { Feedback::error(['mes' => $errors]); } $smarty->assign('uploads', $uploads); if (! empty($params['returnUrl'])) { if (! empty($errors)) { $smarty->assign('msg', implode('
', $errors)); $smarty->display('error.tpl'); die; } header('location: ' . $params['returnUrl']); die; } // Returns fileInfo of the new file if only one file has been edited / uploaded return $fileInfo; } public function upload_single_file($gal_info, $name, $size, $type, $data, $asuser = null, $image_x = null, $image_y = null, $description = '', $created = '', $title = '') { global $user; if (empty($asuser) || ! Perms::get()->admin) { $asuser = $user; } $this->assertUploadedContentIsSafe($data, $name, $gal_info['galleryId']); if (! $title) { $title = $name; } $tx = $this->begin(); $file = new TikiFile([ 'galleryId' => $gal_info['galleryId'], 'description' => $description, 'user' => $asuser, 'created' => $created ]); $ret = $file->replace($data, $type, $title, $name, $image_x, $image_y); $tx->commit(); return $ret; } public function update_single_file($gal_info, $name, $size, $type, $data, $id, $asuser = null, $title = '') { global $user; if (empty($asuser)) { $asuser = $user; } $this->assertUploadedContentIsSafe($data, $name, $gal_info['galleryId']); if (! $title) { $title = $name; } $tx = $this->begin(); $file = TikiFile::id($id); $file->setParam('user', $asuser); $ret = $file->replace($data, $type, $title, $name); $tx->commit(); return $ret; } public static function getTitleFromFilename($title) { if (strpos($title, '.zip') !== strlen($title) - 4) { $title = preg_replace('/\.[^\.]*$/', '', $title); // remove extension $title = preg_replace('/[\-_]+/', ' ', $title); // turn _ etc into spaces $title = ucwords($title); } if (strlen($title) > 200) { // trim to length of name column in database $title = substr($title, 0, 200); } return $title; } public function fileContentIsSVG(&$data) { $finfo = new finfo(FILEINFO_MIME); $type = $finfo->buffer($data) . "\n"; if ( substr($type, 0, 18) == 'application/x-gzip' || substr($type, 0, 16) == 'application/gzip' ) { $data = gzdecode($data); $finfo = new finfo(FILEINFO_MIME); $type = $finfo->buffer($data); } return substr($type, 0, 9) == 'image/svg'; } public function assertUploadedFileIsSafe($path, $filename = null, $galleryId = null) { global $prefs; if ($filename === null) { $filename = $path; } $data = file_get_contents($path); return $this->assertUploadedContentIsSafe($data, $filename, $galleryId); } public function assertUploadedContentIsSafe(&$data, $filename = null, $galleryId = null) { global $prefs; $safe = true; if ($filename !== null) { $mimelib = TikiLib::lib('mime'); if (substr($mimelib->from_filename($filename), 0, 9) == 'image/svg') { $dom = new DOMDocument(); if (! $dom->loadXML($data, LIBXML_NOERROR | LIBXML_NOWARNING | LIBXML_NONET)) { throw new FileIsNotSafeException("You are trying to upload a file as SVG, but content can't be parsed as XML. This is a security risk."); } $safe = false; } } $safe = $safe && ! $this->fileContentIsSVG($data); $svgErrorMsg = tra("SVG files are not safe and cannot be uploaded"); if (! $safe) { if ($prefs['fgal_allow_svg'] !== 'y') { throw new FileIsNotSafeException($svgErrorMsg); } $perms = Perms::get([ 'file gallery', $galleryId ]); if (! $perms->upload_svg) { throw new FileIsNotSafeException($svgErrorMsg); } } return true; } /** * Sanitize XML based files * * @param string $data Image data * @param int $galleryId * @return string */ public function clean_xml($data, $galleryId) { global $prefs; if (empty($data)) { return ''; } $perms = Perms::get([ 'file gallery', $galleryId ]); if ($prefs['fgal_clean_xml_always'] === 'y' || ! $perms->upload_javascript) { $dom = new DOMDocument(); if ($dom->loadXML($data, LIBXML_NOERROR | LIBXML_NOWARNING | LIBXML_NONET)) { $elements = []; /** @var DOMElement $element */ foreach ($dom->getElementsByTagName('*') as $element) { $elements[] = $element; } foreach ($elements as $element) { if (in_array($element->tagName, ['script', 'embed', 'object', 'applet', 'iframe', 'frame'])) { $element->parentNode->removeChild($element); } else { foreach ($element->attributes as $name => $node) { if (stripos($name, 'on') === 0) { $element->removeAttribute($name); } } } } $data = $dom->saveXML(); } } return $data; } public function get_info_from_url($url, $lastCheck = false, $eTag = false) { if (! $url) { return false; } $data = parse_url($url); switch ($data['scheme']) { case 'http': case 'https': return $this->get_info_from_http($url, $lastCheck, $eTag); default: return false; } } private function get_info_from_http($url, $lastCheck, $eTag) { $action = $lastCheck ? 'Refresh' : 'Fetch'; try { $client = TikiLib::lib('tiki')->get_http_client($url); $http_headers = []; if ($lastCheck) { $http_headers['If-Modified-Since'] = gmdate('D, d M Y H:i:s T', $lastCheck); } if ($eTag) { $http_headers['If-None-Match'] = $eTag; } if (count($http_headers)) { $client->setHeaders($http_headers); } $response = TikiLib::lib('tiki')->http_perform_request($client); if ($response->isClientError()) { TikiLib::lib('logs')->add_action($action, $url, 'url', 'error=' . $response->getStatusCode()); return false; } // 300 code, likely not modified or other non-critical error if (! $response->isSuccess()) { return false; } $name = basename($client->getUri()->getPath()); $expiryDate = time(); $result = $response->getBody(); if ($disposition = $response->getHeaders()->get('Content-Disposition')) { $disposition = method_exists($disposition, 'toString') ? $disposition->toString() : $disposition; if (preg_match('/filename=[\'"]?([^;\'"]+)[\'"]?/i', $disposition, $parts)) { $name = $parts[1]; } } $name = rawurldecode($name); // Check expires if ($expires = $response->getHeaders()->get('Expires')) { $potential = strtotime($expires); $expiryDate = max($expiryDate, $potential); } // Check cache-control for max-age, which has priority if ($cacheControl = $response->getHeaders()->get('Cache-Control')) { $cacheControl = method_exists($cacheControl, 'toString') ? $cacheControl->toString() : $cacheControl; if (preg_match('/max-age=(\d+)/', $cacheControl, $parts)) { $expiryDate = time() + $parts[1]; } } $mimelib = TikiLib::lib('mime'); $type = $mimelib->from_content($name, $result); $size = function_exists('mb_strlen') ? mb_strlen($result, '8bit') : strlen($result); if (empty($name)) { $name = tr('unknown'); } if ($etag = $response->getHeaders()->get('Etag')) { $etag = method_exists($etag, 'toString') ? $etag->toString() : $etag; } TikiLib::lib('logs')->add_action($action, $url, 'url', 'success=' . $response->getStatusCode()); return [ 'data' => $result, 'size' => $size, 'type' => $type, 'name' => $name, 'expires' => $expiryDate, 'etag' => $etag, ]; } catch (Laminas\Http\Exception\ExceptionInterface $e) { TikiLib::lib('logs')->add_action($action, $url, 'url', 'error=' . $e->getMessage()); return false; } } public function attach_file_source($fileId, $url, $info, $isReference = false) { $attributelib = TikiLib::lib('attribute'); $attributelib->set_attribute('file', $fileId, 'tiki.content.source', $url); $attributelib->set_attribute('file', $fileId, 'tiki.content.lastcheck', time()); $attributelib->set_attribute('file', $fileId, 'tiki.content.expires', $info['expires']); if ($info['etag']) { $attributelib->set_attribute('file', $fileId, 'tiki.content.etag', $info['etag']); } if ($isReference) { $attributelib->set_attribute('file', $fileId, 'tiki.content.url', $url); } } public function lookup_source($url) { $attributelib = TikiLib::lib('attribute'); $objects = $attributelib->find_objects_with('tiki.content.source', $url); foreach ($objects as $object) { if ($object['type'] == 'file') { return $this->table('tiki_files')->fetchRow( [ 'fileId', 'size' => 'filesize', 'name', 'type' => 'filetype', 'galleryId', 'md5sum' => 'hash', ], ['fileId' => $object['itemId']] ); } } } public function refresh_file($fileId) { global $prefs; $attributelib = TikiLib::lib('attribute'); $attributes = $attributelib->get_attributes('file', $fileId); // Must have a source to begin with if (! isset($attributes['tiki.content.source'])) { return false; } $lastCheck = false; // Make sure not to flood the remote server with update requests if (isset($attributes['tiki.content.lastcheck'])) { if (empty($prefs['fgal_source_refresh_frequency'])) { // set this to 0 to disable return false; } $lastCheck = $attributes['tiki.content.lastcheck']; if ($lastCheck + $prefs['fgal_source_refresh_frequency'] > time()) { return false; } } // Respect cache headers too if (isset($attributes['tiki.content.expires']) && $attributes['tiki.content.expires'] > time()) { return false; } $files = $this->table('tiki_files'); $info = $files->fetchRow( ['galleryId', 'name', 'filename', 'description', 'hash'], ['fileId' => $fileId, 'archiveId' => 0] ); if (! $info) { // Either a missing file or an archive, in both cases, we don't process return false; } $eTag = false; if (isset($attributes['tiki.content.etag'])) { $eTag = $attributes['tiki.content.etag']; } // Record as a check before checking in the case the server is overloaded and times out $attributelib->set_attribute('file', $fileId, 'tiki.content.lastcheck', time()); $remote = $this->get_info_from_url($attributes['tiki.content.source'], $lastCheck, $eTag); $attributelib->set_attribute('file', $fileId, 'tiki.content.expires', $remote['expires']); if ($remote['etag']) { $attributelib->set_attribute('file', $fileId, 'tiki.content.etag', $remote['etag']); } if (! $remote) { return false; } $sum = md5($remote['data']); if ($sum === $info['hash']) { // Content is the same, no new version to create return false; } $name = $remote['name']; $data = $remote['data']; if ($info['name'] != $info['filename']) { // Name was changed on the file, preserve the manually entered one $name = $info['name']; } $file = TikiFile::id($id); $file->setParam('comment', tra('Automatic revision from source')); return $file->replace($data, $remote['type'], $name, $remote['name']); } private function is_filename_valid($filename) { global $prefs; if (! empty($prefs['fgal_match_regex'])) { if (! preg_match('/' . $prefs['fgal_match_regex'] . '/', $filename)) { return false; } } if (! empty($prefs['fgal_nmatch_regex'])) { if (preg_match('/' . $prefs['fgal_nmatch_regex'] . '/', $filename)) { return false; } } return true; } public function moveAllWikiUpToFgal($fgalId) { $tikilib = TikiLib::lib('tiki'); $maxRecords = 100; $pageback = []; // The outer loop attemps to limit memory usage by fetching pages gradually for ($offset = 0; $pages = $tikilib->list_pages($offset, $maxRecords), ! empty($pages['data']); $offset += $maxRecords) { foreach ($pages['data'] as $page) { $pageback[] = $this->moveWikiUpToFgal($page, $fgalId); } } $pageback = array_filter($pageback); if (! empty($pageback)) { foreach ($pageback as $res) { foreach ($res as $type => $array) { foreach ($array as $value) { $fb[$type][] = $value; } } } if (! empty($fb)) { $galinfo = $this->get_file_gallery_info($fgalId); $titles = [ 'success' => tr('Images successfully moved to file gallery %0', $galinfo['name']), 'error' => tr('Images could not be opened or uploaded'), ]; foreach ($fb as $type2 => $mes2) { Feedback::$type2(['mes' => $mes2, 'title' => $titles[$type2]]); } } } else { Feedback::note(['mes' => tr('No changes were made since no images in wiki_up were found in Wiki pages.'), 'title' => tr('No images found')]); } $this->wikiupMoved = []; } public function moveWikiUpToFgal($page_info, $fgalId) { global $user; $tikilib = TikiLib::lib('tiki'); $mimelib = TikiLib::lib('mime'); $argumentParser = new WikiParser_PluginArgumentParser(); $files = []; if (strpos($page_info['data'], 'img/wiki_up') === false) { return false; } $matches = WikiParser_PluginMatcher::match($page_info['data']); $feedback = []; foreach ($matches as $match) { $modif = false; $plugin_name = $match->getName(); if ($plugin_name == 'img') { $arguments = $argumentParser->parse($match->getArguments()); $newArgs = []; foreach ($arguments as $key => $val) { if ($key === 'src' && strpos($val, 'img/wiki_up') !== false) { //first time the wiki_up file is found if (! isset($this->wikiupMoved[$val])) { if (false === $data = @file_get_contents($val)) { $feedback['error'][] = tr('%0 on page %1 could not be opened', $val, $page_info['pageName']); continue; } $name = preg_replace('|.*/([^/]*)|', '$1', $val); $file = new TikiFile([ 'galleryId' => $fgalId, 'description' => 'Used in ' . $page_info['pageName'], 'user' => $user, 'comment' => 'wiki_up conversion', ]); $fileId = $file->replace($data, $mimelib->from_path($name, $val), $name, $name); if (empty($fileId)) { $feedback['error'][] = tr( '%0 on page %1 could not be uploaded into the file gallery', $val, $page_info['pageName'] ); continue; } else { $files[] = $val; $modif = true; $newArgs[] = 'fileId="' . $fileId . '"'; //save wiki_up file name and fileId pair in case there are more instances using this file $this->wikiupMoved[$val] = $fileId; $feedback['success'][] = tr( '%0 used on page %1 was moved and given the file ID %2.', $name, $page_info['pageName'], $fileId ); } //wiki_up file was already moved to file galleries } else { $name = preg_replace('|.*/([^/]*)|', '$1', $val); $files[] = $val; $modif = true; $fileId = $this->wikiupMoved[$val]; $newArgs[] = 'fileId="' . $fileId . '"'; $feedback['success'][] = tr( '%0 used on page %1 was moved and given the file ID %2.', $name, $page_info['pageName'], $fileId ); } } else { $newArgs[] = "$key=\"$val\""; } } if ($modif) { $match->replaceWith('{img ' . implode(' ', $newArgs) . '}'); } } } if (! empty($files)) { $tikilib->update_page( $page_info['pageName'], $matches->getText(), 'wiki_up conversion', $user, $tikilib->get_ip_address() ); $files = array_unique($files); foreach ($files as $file) { if (file_exists($file)) { unlink($file); } } } return $feedback; } public function fixMime($filetype, $filename) { global $prefs; if ($prefs['fgal_fix_mime_type'] != 'y') { return $filetype; } if ($filetype != "application/octet-stream") { return $filetype; } $mimelib = TikiLib::lib('mime'); return $mimelib->from_filename($filename); } /** * Get basic and extended metadata included in the file itself and return as JSON string * * @param string $file path to file or content of file * @param bool $ispath indicates whether $file is a path (true) or the file contents (false) * @param bool $extended indicates whether to retrieve extended metadata information * * @return string $filemeta JSON string of metadata */ public function extractMetadataJson($file, $ispath = true, $extended = true) { include_once __DIR__ . '/../metadata/metadatalib.php'; $metadata = new FileMetadata(); $filemeta = json_encode($metadata->getMetadata($file, $ispath, $extended)->typemeta['best']); return $filemeta; } /** * Perform actions with file metadata stored in the database * * @param numeric $fileId fileId of the file in the file gallery * @param string $action action to perform regarding metadata * * The following actions are handled: * 'get_array' Get file metadata from database column or, if that is empty, extract metadata from the * file, update the database and return an array of the data. * * 'refresh' Extract metadata from the file and update database * * @return array|TikiDb_Pdo_Result|TikiDb_Adodb_Result $metadata array of metadata is returned if * action is 'get_array', otherwise result class */ public function metadataAction($fileId, $action = 'get_array') { //get the tiki_files table $filesTable = $this->table('tiki_files'); if ($action == 'get_array') { //get metadata for the file from the database $metacol = $filesTable->fetchColumn('metadata', ['fileId' => $fileId]); } //if metadata field is empty, or if a refresh, extract from the file if (($action == 'get_array' && empty($metacol[0])) || $action == 'refresh') { //extract metadata $file = TikiFile::id($fileId); $metadata = $this->extractMetadataJson($file->getWrapper()->getReadableFile()); //update database for newly extracted metadata $result = $filesTable->update(['metadata' => $metadata], ['fileId' => $fileId]); // update search index require_once('lib/search/refresh-functions.php'); refresh_index('files', $fileId); } else { $metadata = $metacol[0]; } if ($action == 'get_array') { //return metadata as an array return json_decode($metadata, true); } else { return $result; } } /** * Attempts to create the directory structure speficied, along with an empty index.php file. * * @param $directory string The directory path that files will be stored at. */ public function setupDirectory($directory) { // if a directory structure selected is not present if (! file_exists($directory)) { // attempt to create directory structure mkdir($directory, 0777, true); } // if index.php file is not present in home directory, attempt to create it if (! file_exists($directory . 'index.php')) { file_put_contents($directory . 'index.php', ''); } } // Return HTML code to display the hierarchy of sub-galleries when the PluginDiagram {diagram} is used in a wiki page public function getNodes($nodes, $id, $sub = '') { $htmlnodes = ""; foreach ($nodes as $node) { if ($node['parentId'] == $id) { // If the current user has permission to access the gallery, then add gallery to the hierarchy. if ($node['perms']['tiki_p_view_file_gallery'] == 'y') { $htmlnodes .= "