|
|
@@ -40,8 +40,9 @@ class FreshRSS_Export_Service {
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * Generate OPML file content.
|
|
|
- * @return array{0:string,1:string} First item is the filename, second item is the content
|
|
|
+ * 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();
|
|
|
@@ -52,12 +53,12 @@ class FreshRSS_Export_Service {
|
|
|
|
|
|
return [
|
|
|
"feeds_{$day}.opml.xml",
|
|
|
- $view->helperToString('export/opml')
|
|
|
+ self::helperToTempFile($view, 'export/opml'),
|
|
|
];
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * Generate the starred and labelled entries file content.
|
|
|
+ * 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.
|
|
|
@@ -67,7 +68,8 @@ class FreshRSS_Export_Service {
|
|
|
* '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 content
|
|
|
+ * @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();
|
|
|
@@ -85,14 +87,15 @@ class FreshRSS_Export_Service {
|
|
|
|
|
|
return [
|
|
|
"starred_{$day}.json",
|
|
|
- $view->helperToString('export/articles')
|
|
|
+ self::helperToTempFile($view, 'export/articles'),
|
|
|
];
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * Generate the entries file content for the given feed.
|
|
|
- * @return array{0:string,1:string}|null First item is the filename, second item is the content.
|
|
|
+ * 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();
|
|
|
@@ -120,13 +123,14 @@ class FreshRSS_Export_Service {
|
|
|
|
|
|
return [
|
|
|
$filename,
|
|
|
- $view->helperToString('export/articles')
|
|
|
+ self::helperToTempFile($view, 'export/articles'),
|
|
|
];
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * Generate the entries file content for all the feeds.
|
|
|
- * @return array<string,string> Keys are filenames and values are contents.
|
|
|
+ * Generate the entries files for all the feeds.
|
|
|
+ * @return array<string,string> 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();
|
|
|
@@ -138,8 +142,8 @@ class FreshRSS_Export_Service {
|
|
|
continue;
|
|
|
}
|
|
|
|
|
|
- [$filename, $content] = $result;
|
|
|
- $exported_files[$filename] = $content;
|
|
|
+ [$filename, $path] = $result;
|
|
|
+ $exported_files[$filename] = $path;
|
|
|
}
|
|
|
|
|
|
return $exported_files;
|
|
|
@@ -147,34 +151,49 @@ class FreshRSS_Export_Service {
|
|
|
|
|
|
/**
|
|
|
* Compress several files in a Zip file.
|
|
|
- * @param array<string,string> $files where the key is the filename, the value is the content
|
|
|
- * @return array{0:string,1:string|false} First item is the zip filename, second item is the zip content
|
|
|
+ * @param array<string,string> $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';
|
|
|
|
|
|
- // From https://stackoverflow.com/questions/1061710/php-zip-files-on-the-fly
|
|
|
- $zip_file = tempnam(TMP_PATH, 'zip');
|
|
|
- if ($zip_file === false) {
|
|
|
- return [$zip_filename, false];
|
|
|
- }
|
|
|
+ $zip_file = self::tempFile();
|
|
|
$zip_archive = new ZipArchive();
|
|
|
$zip_archive->open($zip_file, ZipArchive::OVERWRITE);
|
|
|
|
|
|
- foreach ($files as $filename => $content) {
|
|
|
- $zip_archive->addFromString($filename, $content);
|
|
|
+ foreach ($files as $filename => $path) {
|
|
|
+ $zip_archive->addFile($path, $filename);
|
|
|
}
|
|
|
|
|
|
- $zip_archive->close();
|
|
|
-
|
|
|
- $content = file_get_contents($zip_file);
|
|
|
-
|
|
|
- unlink($zip_file);
|
|
|
-
|
|
|
return [
|
|
|
$zip_filename,
|
|
|
- $content,
|
|
|
+ $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;
|
|
|
+ }
|
|
|
}
|