category_dao = FreshRSS_Factory::createCategoryDao($this->username); $this->feed_dao = FreshRSS_Factory::createFeedDao($this->username); $this->entry_dao = FreshRSS_Factory::createEntryDao($this->username); $this->tag_dao = FreshRSS_Factory::createTagDao(); } /** * Generate OPML file. * @return array{0:string,1:string} First item is the filename, second item is the path of a temporary file with the content * @throws Minz_PermissionDeniedException */ public function generateOpml(): array { $view = new FreshRSS_View(); $day = date('Y-m-d'); $view->categories = $this->category_dao->listSortedCategories(prePopulateFeeds: true, details: true); $view->excludeMutedFeeds = false; $view->includeSensitiveCurlParams = true; return [ "feeds_{$day}.opml.xml", self::helperToTempFile($view, 'export/opml'), ]; } /** * Generate the starred and labelled entries file. * * Both starred and labelled entries are put into a "starred" file, that’s * why there is only one method for both. * * @phpstan-param 'S'|'T'|'ST' $type * @param string $type must be one of: * 'S' (starred/favourite), * 'T' (taggued/labelled), * 'ST' (starred or labelled) * @return array{0:string,1:string} First item is the filename, second item is the path of a temporary file with the content * @throws Minz_PermissionDeniedException */ public function generateStarredEntries(string $type): array { $view = new FreshRSS_View(); $view->categories = $this->category_dao->listCategories(prePopulateFeeds: true); $day = date('Y-m-d'); $view->list_title = _t('sub.import_export.starred_list'); $view->type = 'starred'; $entriesId = $this->entry_dao->listIdsWhere($type, 0, FreshRSS_Entry::STATE_ALL, order: 'ASC', limit: -1) ?? []; $view->entryIdsTagNames = $this->tag_dao->getEntryIdsTagNames($entriesId); // The following is a streamable query, i.e. must be last $view->entries = $this->entry_dao->listWhere( $type, 0, FreshRSS_Entry::STATE_ALL, order: 'ASC', limit: -1 ); return [ "starred_{$day}.json", self::helperToTempFile($view, 'export/articles'), ]; } /** * Generate the entries file for the given feed. * @return array{0:string,1:string}|null First item is the filename, second item is the path of a temporary file with the content. * It also can return null if the feed doesn’t exist. * @throws Minz_PermissionDeniedException */ public function generateFeedEntries(int $feed_id, int $max_number_entries): ?array { $view = new FreshRSS_View(); $view->categories = $this->category_dao->listCategories(prePopulateFeeds: true); $feed = FreshRSS_Category::findFeed($view->categories, $feed_id); if ($feed === null) { return null; } $view->feed = $feed; $day = date('Y-m-d'); $filename = "feed_{$day}_" . $feed->categoryId() . '_' . $feed->id() . '.json'; $view->list_title = _t('sub.import_export.feed_list', $feed->name()); $view->type = 'feed/' . $feed->id(); $entriesId = $this->entry_dao->listIdsWhere( 'f', $feed->id(), FreshRSS_Entry::STATE_ALL, order: 'ASC', limit: $max_number_entries ) ?? []; $view->entryIdsTagNames = $this->tag_dao->getEntryIdsTagNames($entriesId); // The following is a streamable query, i.e. must be last $view->entries = $this->entry_dao->listWhere( 'f', $feed->id(), FreshRSS_Entry::STATE_ALL, order: 'ASC', limit: $max_number_entries ); return [ $filename, self::helperToTempFile($view, 'export/articles'), ]; } /** * Generate the entries files for all the feeds. * @return array Keys are filenames and values are paths of temporary files with the contents. * @throws Minz_PermissionDeniedException */ public function generateAllFeedEntries(int $max_number_entries): array { $feed_ids = $this->feed_dao->listFeedsIds(); $exported_files = []; foreach ($feed_ids as $feed_id) { $result = $this->generateFeedEntries($feed_id, $max_number_entries); if ($result === null) { continue; } [$filename, $path] = $result; $exported_files[$filename] = $path; } return $exported_files; } /** * Compress several files in a Zip file. * @param array $files where the key is the filename, the value is the path of the file * @return array{0:string,1:string|false} First item is the zip filename, second item is the path of a temporary zip file, or false in case of error * @throws Minz_PermissionDeniedException */ public function zip(array $files): array { $day = date('Y-m-d'); $zip_filename = 'freshrss_' . $this->username . '_' . $day . '_export.zip'; $zip_file = self::tempFile(); $zip_archive = new ZipArchive(); $zip_archive->open($zip_file, ZipArchive::OVERWRITE); foreach ($files as $filename => $path) { $zip_archive->addFile($path, $filename); } return [ $zip_filename, $zip_archive->close() ? $zip_file : false, ]; } /** * Create a temporary file, which is deleted at the end of the script. * @throws Minz_PermissionDeniedException */ private static function tempFile(): string { $filename = tempnam(TMP_PATH, 'export'); if ($filename === false) { throw new Minz_PermissionDeniedException(TMP_PATH); } register_shutdown_function(static fn() => @unlink($filename)); return $filename; } /** * Render a view helper into a temporary file, instead of keeping the whole content in memory. * @return string The path of the temporary file * @throws Minz_PermissionDeniedException */ private static function helperToTempFile(FreshRSS_View $view, string $helper): string { $filename = self::tempFile(); $view->helperToFile($helper, $filename); return $filename; } }