diff --git a/.github/workflows/codeception-min-version.yml b/.github/workflows/codeception-min-version.yml index 257febcd..8a178933 100644 --- a/.github/workflows/codeception-min-version.yml +++ b/.github/workflows/codeception-min-version.yml @@ -13,3 +13,4 @@ jobs: with: module-id: cfiles use-rest-module: true + rest-module-branch: develop diff --git a/Events.php b/Events.php index d6eb07e6..aea7bffd 100644 --- a/Events.php +++ b/Events.php @@ -2,6 +2,8 @@ namespace humhub\modules\cfiles; +use humhub\commands\IntegrityController; +use humhub\helpers\ControllerHelper; use humhub\modules\cfiles\extensions\custom_pages\elements\FileElement; use humhub\modules\cfiles\extensions\custom_pages\elements\FilesElement; use humhub\modules\cfiles\extensions\custom_pages\elements\FolderElement; @@ -14,7 +16,10 @@ use humhub\modules\file\actions\DownloadAction; use humhub\modules\file\models\File as BaseFile; use humhub\modules\space\models\Space; +use humhub\modules\space\widgets\Menu; +use humhub\modules\ui\menu\MenuLink; use humhub\modules\user\models\User; +use humhub\modules\user\widgets\ProfileMenu; use Yii; use yii\base\Event; @@ -27,15 +32,16 @@ class Events { public static function onSpaceMenuInit($event) { + /* @var Menu $menu */ + $menu = $event->sender; - if ($event->sender->space !== null && $event->sender->space->moduleManager->isEnabled('cfiles')) { - $event->sender->addItem([ + if ($menu->space !== null && $menu->space->moduleManager->isEnabled('cfiles')) { + $menu->addEntry(new MenuLink([ 'label' => Yii::t('CfilesModule.base', 'Files'), - 'group' => 'modules', 'url' => $event->sender->space->createUrl('/cfiles/browse'), - 'icon' => '', - 'isActive' => (Yii::$app->controller->module && Yii::$app->controller->module->id == 'cfiles'), - ]); + 'icon' => 'files-o', + 'isActive' => ControllerHelper::isActivePath('cfiles'), + ])); } } @@ -46,6 +52,7 @@ public static function onSpaceMenuInit($event) */ public static function onIntegrityCheck($event) { + /* @var IntegrityController $integrityController */ $integrityController = $event->sender; $integrityController->showTestHeadline("CFile Module (" . File::find()->count() . " entries)"); @@ -76,13 +83,15 @@ public static function onIntegrityCheck($event) public static function onProfileMenuInit($event) { - if ($event->sender->user !== null && $event->sender->user->moduleManager->isEnabled('cfiles')) { - $event->sender->addItem([ + /* @var ProfileMenu $menu */ + $menu = $event->sender; + if ($menu->user !== null && $menu->user->moduleManager->isEnabled('cfiles')) { + $menu->addEntry(new MenuLink([ 'label' => Yii::t('CfilesModule.base', 'Files'), 'url' => $event->sender->user->createUrl('/cfiles/browse'), - 'icon' => '', - 'isActive' => (Yii::$app->controller->module && Yii::$app->controller->module->id == 'cfiles'), - ]); + 'icon' => 'files-o', + 'isActive' => ControllerHelper::isActivePath('cfiles'), + ])); } } diff --git a/Module.php b/Module.php index 64db8279..fe86f4c0 100644 --- a/Module.php +++ b/Module.php @@ -2,7 +2,6 @@ namespace humhub\modules\cfiles; -use humhub\components\console\Application as ConsoleApplication; use humhub\modules\cfiles\models\ConfigureContainerForm; use humhub\modules\cfiles\models\rows\FileSystemItemRow; use humhub\modules\space\models\Space; @@ -16,6 +15,16 @@ class Module extends ContentContainerModule { + /** + * @var int Files uploaded into the same folder by the same user are announced by a single + * notification, once no further file was uploaded for this many minutes. + * 0 announces every upload request on its own. + * + * @see \humhub\modules\cfiles\libs\FileUploadBatch + * @since 0.19 + */ + public int $uploadNotificationDelay = 10; + /** * @var string sort name as 'name', 'size', 'updated_at' * @see FileSystemItemRow::ORDER_MAPPING @@ -30,19 +39,6 @@ class Module extends ContentContainerModule public $defaultPostedFilesSort = FileSystemItemRow::ORDER_TYPE_UPDATED_AT; public $defaultPostedFilesOrder = SORT_ASC; - /** - * @inheritdoc - */ - public function init() - { - parent::init(); - - if (Yii::$app instanceof ConsoleApplication) { - // Prevents the Yii HelpCommand from crawling all web controllers and possibly throwing errors at REST endpoints if the REST module is not available. - $this->controllerNamespace = 'cfiles/commands'; - } - } - /** * @inheritdoc */ diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index ae51fb01..d704de0c 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -1,6 +1,26 @@ Changelog ========= +0.19.0 - Unreleased +------------------- +- Enh #288: Send a single notification to announce all files uploaded within a short period of time, rather than sending one notification per file (humhub/humhub#5334) + +0.18.3 - July 22, 2026 +---------------------- +- Fix #287: Fix PHP error from deprecated search event/constant removed in HumHub 1.19 + +0.18.2 - July 20, 2026 +---------------------- +- Fix #272: Fix Content linking with Comment + +0.18.1 - July 8, 2026 +--------------------- +- Enh #286: Add aria-label attribute for icon-only buttons + +0.18.0 - June 5, 2026 +--------------------- +- Enh #272: Update for HumHub 1.19 + 0.17.4 - May 5, 2026 -------------------- - Fix #278: Improved download count accuracy diff --git a/jobs/SendFileUploadNotification.php b/jobs/SendFileUploadNotification.php new file mode 100644 index 00000000..b96b1819 --- /dev/null +++ b/jobs/SendFileUploadNotification.php @@ -0,0 +1,74 @@ +folderId, (int)$this->userId); + + if ($batch->isEmpty()) { + // Already announced, or the batch was lost through a cache flush + return; + } + + $remainingDelay = $batch->getRemainingDelay(); + + if ($remainingDelay > 0 && $this->attempt < self::MAX_ATTEMPTS) { + // Uploads continued after this job was queued, wait for them to settle + Yii::$app->queue->delay($remainingDelay)->push(new self([ + 'folderId' => $this->folderId, + 'userId' => $this->userId, + 'attempt' => $this->attempt + 1, + ])); + return; + } + + $batch->notify(); + } +} diff --git a/libs/FileUploadBatch.php b/libs/FileUploadBatch.php new file mode 100644 index 00000000..751819e3 --- /dev/null +++ b/libs/FileUploadBatch.php @@ -0,0 +1,216 @@ +cache`, which the web request and the queue worker share. + * A batch lost through a cache flush simply means that upload is not announced. + * + * @since 0.19 + */ +final class FileUploadBatch +{ + /** + * @var int how many quiet periods ongoing uploads may postpone a batch, counted from the + * first uploaded file. Prevents a continuously uploading user (or a large archive + * import) from deferring the notification indefinitely. + */ + public const MAX_POSTPONE_FACTOR = 6; + + private const CACHE_KEY_PREFIX = 'cfiles.fileUploadBatch.'; + + /** + * @var int uploaded files collected so far + */ + public int $count = 0; + + /** + * @var int timestamp of the first uploaded file + */ + public int $firstAt = 0; + + /** + * @var int timestamp of the most recently uploaded file + */ + public int $lastAt = 0; + + public function __construct( + public readonly int $folderId, + public readonly int $userId, + ) { + } + + /** + * Counts the given file into the open batch of its folder and uploader. + * + * The first file of a batch also schedules the delayed notification job. Every following + * file only bumps the counter and restarts the quiet period, so a single job (which + * re-queues itself while uploads keep coming in) is enough for the whole batch. + */ + public static function add(File $file): void + { + $content = $file->content; + + if ($content === null || !$content->getStateService()->isPublished()) { + // Not published content is announced by Content::processNewContent() once it gets published + return; + } + + $folderId = (int)$file->parent_folder_id; + $userId = (int)$content->created_by; + + if ($folderId === 0 || $userId === 0) { + return; + } + + $batch = static::load($folderId, $userId); + $isFirstFile = $batch->isEmpty(); + $now = time(); + + $batch->count++; + $batch->lastAt = $now; + + if ($isFirstFile) { + $batch->firstAt = $now; + } + + $batch->save(); + + if ($isFirstFile) { + Yii::$app->queue->delay(static::getDelay())->push(new SendFileUploadNotification([ + 'folderId' => $folderId, + 'userId' => $userId, + ])); + } + } + + /** + * Returns the open batch of the given folder and uploader, or an empty one. + */ + public static function load(int $folderId, int $userId): self + { + $batch = new self($folderId, $userId); + $cached = Yii::$app->cache->get($batch->getCacheKey()); + + if (is_array($cached)) { + $batch->count = (int)($cached['count'] ?? 0); + $batch->firstAt = (int)($cached['firstAt'] ?? 0); + $batch->lastAt = (int)($cached['lastAt'] ?? 0); + } + + return $batch; + } + + public function isEmpty(): bool + { + return $this->count < 1; + } + + public function save(): void + { + // The batch must outlive the longest possible postponing + $duration = max(3600, static::getDelay() * (self::MAX_POSTPONE_FACTOR + 1)); + + Yii::$app->cache->set($this->getCacheKey(), [ + 'count' => $this->count, + 'firstAt' => $this->firstAt, + 'lastAt' => $this->lastAt, + ], $duration); + } + + public function forget(): void + { + Yii::$app->cache->delete($this->getCacheKey()); + } + + /** + * @return int seconds left until this batch may be announced, 0 if it is due + */ + public function getRemainingDelay(): int + { + $delay = static::getDelay(); + + $due = min( + // The quiet period restarts with every uploaded file... + $this->lastAt + $delay, + // ...but a user uploading continuously must not defer the notification forever. + $this->firstAt + $delay * self::MAX_POSTPONE_FACTOR, + ); + + return max(0, $due - time()); + } + + /** + * Announces this batch with a single notification and closes it. + * + * The batch is always dropped, even when nothing could be sent, so a broken batch cannot + * block notifications for later uploads into the same folder. + */ + public function notify(): void + { + $fileCount = $this->count; + + $this->forget(); + + if ($fileCount < 1) { + return; + } + + $folder = Folder::findOne(['id' => $this->folderId]); + $user = User::findOne(['id' => $this->userId]); + + if ($folder === null || $user === null) { + return; + } + + $content = $folder->content; + + if ($content === null || !$content->getStateService()->isPublished()) { + return; + } + + FilesUploaded::instance() + ->from($user) + ->about($folder) + ->fileCount($fileCount) + ->sendBulk(Yii::$app->notification->getFollowers($content)); + } + + /** + * @return int seconds of upload inactivity before a batch is announced + */ + public static function getDelay(): int + { + /** @var Module $module */ + $module = Yii::$app->getModule('cfiles'); + + return max(0, $module->uploadNotificationDelay) * 60; + } + + private function getCacheKey(): string + { + return self::CACHE_KEY_PREFIX . $this->folderId . '.' . $this->userId; + } +} diff --git a/libs/ZIPCreator.php b/libs/ZIPCreator.php index 734ee9d6..a080f34f 100644 --- a/libs/ZIPCreator.php +++ b/libs/ZIPCreator.php @@ -101,7 +101,7 @@ public function addFile($file, $path = '', $fileName = null) $file = $file->baseFile; } - if (!$file || !$file->canView()) { + if (!$file || !$file->canView() || !$file->store->has()) { return; } @@ -113,10 +113,7 @@ public function addFile($file, $path = '', $fileName = null) $filePath = $this->fixPath($filePath); - $realFilePath = $file->store->get(); - if (is_file($realFilePath)) { - $this->archive->addFile($realFilePath, $filePath); - } + $this->archive->addFromString($filePath, $file->store->getContent()); } /** diff --git a/messages/am/base.php b/messages/am/base.php index 6ca6fd2f..0a231a22 100644 --- a/messages/am/base.php +++ b/messages/am/base.php @@ -116,4 +116,7 @@ 'You cannot move the folder "{name}"!' => '', 'ZIP selected' => '', 'ZIP support is not enabled.' => '', + '{displayName} added {n,plural,=1{a file} other{# files}} to the folder "{folderTitle}".' => '', + '{originator} added {n,plural,=1{a file} other{# files}} to the folder "{folderTitle}"' => '', + '{originator} added {n,plural,=1{a file} other{# files}} to the folder "{folderTitle}" in Space {space}' => '', ]; diff --git a/messages/an/base.php b/messages/an/base.php index 91bc09a9..e3b2feb5 100644 --- a/messages/an/base.php +++ b/messages/an/base.php @@ -116,4 +116,7 @@ 'You cannot move the folder "{name}"!' => '', 'ZIP selected' => '', 'ZIP support is not enabled.' => '', + '{displayName} added {n,plural,=1{a file} other{# files}} to the folder "{folderTitle}".' => '', + '{originator} added {n,plural,=1{a file} other{# files}} to the folder "{folderTitle}"' => '', + '{originator} added {n,plural,=1{a file} other{# files}} to the folder "{folderTitle}" in Space {space}' => '', ]; diff --git a/messages/ar/base.php b/messages/ar/base.php index e00e5c90..679f449c 100644 --- a/messages/ar/base.php +++ b/messages/ar/base.php @@ -116,4 +116,7 @@ 'Files (Module)' => '', 'Folder content ID' => '', 'Folders' => '', + '{displayName} added {n,plural,=1{a file} other{# files}} to the folder "{folderTitle}".' => '', + '{originator} added {n,plural,=1{a file} other{# files}} to the folder "{folderTitle}"' => '', + '{originator} added {n,plural,=1{a file} other{# files}} to the folder "{folderTitle}" in Space {space}' => '', ]; diff --git a/messages/bg/base.php b/messages/bg/base.php index 16725b4d..1e6c9418 100644 --- a/messages/bg/base.php +++ b/messages/bg/base.php @@ -116,4 +116,7 @@ 'Wrong target folder!' => '', 'You cannot move the file "{name}"!' => '', 'You cannot move the folder "{name}"!' => '', + '{displayName} added {n,plural,=1{a file} other{# files}} to the folder "{folderTitle}".' => '', + '{originator} added {n,plural,=1{a file} other{# files}} to the folder "{folderTitle}"' => '', + '{originator} added {n,plural,=1{a file} other{# files}} to the folder "{folderTitle}" in Space {space}' => '', ]; diff --git a/messages/br/base.php b/messages/br/base.php index 0508561f..ff3a7b35 100644 --- a/messages/br/base.php +++ b/messages/br/base.php @@ -116,4 +116,7 @@ 'You cannot move the folder "{name}"!' => '', 'ZIP selected' => '', 'ZIP support is not enabled.' => '', + '{displayName} added {n,plural,=1{a file} other{# files}} to the folder "{folderTitle}".' => '', + '{originator} added {n,plural,=1{a file} other{# files}} to the folder "{folderTitle}"' => '', + '{originator} added {n,plural,=1{a file} other{# files}} to the folder "{folderTitle}" in Space {space}' => '', ]; diff --git a/messages/ca/base.php b/messages/ca/base.php index c6968797..7ab18b28 100644 --- a/messages/ca/base.php +++ b/messages/ca/base.php @@ -116,4 +116,7 @@ 'You cannot move the folder "{name}"!' => '', 'ZIP selected' => '', 'ZIP support is not enabled.' => '', + '{displayName} added {n,plural,=1{a file} other{# files}} to the folder "{folderTitle}".' => '', + '{originator} added {n,plural,=1{a file} other{# files}} to the folder "{folderTitle}"' => '', + '{originator} added {n,plural,=1{a file} other{# files}} to the folder "{folderTitle}" in Space {space}' => '', ]; diff --git a/messages/cs/base.php b/messages/cs/base.php index a898f4e5..102355d5 100644 --- a/messages/cs/base.php +++ b/messages/cs/base.php @@ -116,4 +116,7 @@ 'You cannot move the folder "{name}"!' => '', 'ZIP selected' => '', 'ZIP support is not enabled.' => '', + '{displayName} added {n,plural,=1{a file} other{# files}} to the folder "{folderTitle}".' => '', + '{originator} added {n,plural,=1{a file} other{# files}} to the folder "{folderTitle}"' => '', + '{originator} added {n,plural,=1{a file} other{# files}} to the folder "{folderTitle}" in Space {space}' => '', ]; diff --git a/messages/cy/base.php b/messages/cy/base.php index c9b1c838..8f0cc1cf 100644 --- a/messages/cy/base.php +++ b/messages/cy/base.php @@ -116,4 +116,7 @@ 'You cannot move the folder "{name}"!' => '', 'ZIP selected' => '', 'ZIP support is not enabled.' => '', + '{displayName} added {n,plural,=1{a file} other{# files}} to the folder "{folderTitle}".' => '', + '{originator} added {n,plural,=1{a file} other{# files}} to the folder "{folderTitle}"' => '', + '{originator} added {n,plural,=1{a file} other{# files}} to the folder "{folderTitle}" in Space {space}' => '', ]; diff --git a/messages/da/base.php b/messages/da/base.php index 5d222a98..b4da4188 100644 --- a/messages/da/base.php +++ b/messages/da/base.php @@ -116,4 +116,7 @@ 'You cannot move the folder "{name}"!' => '', 'ZIP selected' => '', 'ZIP support is not enabled.' => '', + '{displayName} added {n,plural,=1{a file} other{# files}} to the folder "{folderTitle}".' => '', + '{originator} added {n,plural,=1{a file} other{# files}} to the folder "{folderTitle}"' => '', + '{originator} added {n,plural,=1{a file} other{# files}} to the folder "{folderTitle}" in Space {space}' => '', ]; diff --git a/messages/de/base.php b/messages/de/base.php index 087f24d6..268b000a 100644 --- a/messages/de/base.php +++ b/messages/de/base.php @@ -115,4 +115,7 @@ 'You cannot move the folder "{name}"!' => 'Du kannst den Ordner "{name} nicht verschieben"!', 'ZIP selected' => 'Ausgewählte komprimieren (ZIP)', 'ZIP support is not enabled.' => 'ZIP-Unterstützung ist nicht aktiviert.', + '{displayName} added {n,plural,=1{a file} other{# files}} to the folder "{folderTitle}".' => '{displayName} hat {n,plural,=1{eine Datei} other{# Dateien}} zum Ordner "{folderTitle}" hinzugefügt.', + '{originator} added {n,plural,=1{a file} other{# files}} to the folder "{folderTitle}"' => '{originator} hat {n,plural,=1{eine Datei} other{# Dateien}} zum Ordner "{folderTitle}" hinzugefügt.', + '{originator} added {n,plural,=1{a file} other{# files}} to the folder "{folderTitle}" in Space {space}' => '{originator} hat {n,plural,=1{eine Datei} other{# Dateien}} zum Ordner "{folderTitle}" im Space {space} hinzugefügt.', ]; diff --git a/messages/el/base.php b/messages/el/base.php index 30fbf736..e6a3ebc0 100644 --- a/messages/el/base.php +++ b/messages/el/base.php @@ -116,4 +116,7 @@ 'Wrong target folder!' => '', 'You cannot move the file "{name}"!' => '', 'You cannot move the folder "{name}"!' => '', + '{displayName} added {n,plural,=1{a file} other{# files}} to the folder "{folderTitle}".' => '', + '{originator} added {n,plural,=1{a file} other{# files}} to the folder "{folderTitle}"' => '', + '{originator} added {n,plural,=1{a file} other{# files}} to the folder "{folderTitle}" in Space {space}' => '', ]; diff --git a/messages/es-419/base.php b/messages/es-419/base.php index c9b1c838..8f0cc1cf 100644 --- a/messages/es-419/base.php +++ b/messages/es-419/base.php @@ -116,4 +116,7 @@ 'You cannot move the folder "{name}"!' => '', 'ZIP selected' => '', 'ZIP support is not enabled.' => '', + '{displayName} added {n,plural,=1{a file} other{# files}} to the folder "{folderTitle}".' => '', + '{originator} added {n,plural,=1{a file} other{# files}} to the folder "{folderTitle}"' => '', + '{originator} added {n,plural,=1{a file} other{# files}} to the folder "{folderTitle}" in Space {space}' => '', ]; diff --git a/messages/es/base.php b/messages/es/base.php index 2fa75e61..7af191ef 100644 --- a/messages/es/base.php +++ b/messages/es/base.php @@ -116,4 +116,7 @@ 'Files (Module)' => '', 'Folder content ID' => '', 'Folders' => '', + '{displayName} added {n,plural,=1{a file} other{# files}} to the folder "{folderTitle}".' => '', + '{originator} added {n,plural,=1{a file} other{# files}} to the folder "{folderTitle}"' => '', + '{originator} added {n,plural,=1{a file} other{# files}} to the folder "{folderTitle}" in Space {space}' => '', ]; diff --git a/messages/et/base.php b/messages/et/base.php index c9b1c838..8f0cc1cf 100644 --- a/messages/et/base.php +++ b/messages/et/base.php @@ -116,4 +116,7 @@ 'You cannot move the folder "{name}"!' => '', 'ZIP selected' => '', 'ZIP support is not enabled.' => '', + '{displayName} added {n,plural,=1{a file} other{# files}} to the folder "{folderTitle}".' => '', + '{originator} added {n,plural,=1{a file} other{# files}} to the folder "{folderTitle}"' => '', + '{originator} added {n,plural,=1{a file} other{# files}} to the folder "{folderTitle}" in Space {space}' => '', ]; diff --git a/messages/eu/base.php b/messages/eu/base.php index 09dd3dee..1d299743 100644 --- a/messages/eu/base.php +++ b/messages/eu/base.php @@ -116,4 +116,7 @@ 'You cannot move the folder "{name}"!' => '', 'ZIP selected' => '', 'ZIP support is not enabled.' => '', + '{displayName} added {n,plural,=1{a file} other{# files}} to the folder "{folderTitle}".' => '', + '{originator} added {n,plural,=1{a file} other{# files}} to the folder "{folderTitle}"' => '', + '{originator} added {n,plural,=1{a file} other{# files}} to the folder "{folderTitle}" in Space {space}' => '', ]; diff --git a/messages/fa-IR/base.php b/messages/fa-IR/base.php index fd673689..547822de 100644 --- a/messages/fa-IR/base.php +++ b/messages/fa-IR/base.php @@ -116,4 +116,7 @@ 'You cannot move the file "{name}"!' => '', 'You cannot move the folder "{name}"!' => '', 'ZIP support is not enabled.' => '', + '{displayName} added {n,plural,=1{a file} other{# files}} to the folder "{folderTitle}".' => '', + '{originator} added {n,plural,=1{a file} other{# files}} to the folder "{folderTitle}"' => '', + '{originator} added {n,plural,=1{a file} other{# files}} to the folder "{folderTitle}" in Space {space}' => '', ]; diff --git a/messages/fi/base.php b/messages/fi/base.php index 5c6dd46f..caeb1d80 100644 --- a/messages/fi/base.php +++ b/messages/fi/base.php @@ -116,4 +116,7 @@ 'Wrong target folder!' => '', 'You cannot move the file "{name}"!' => '', 'You cannot move the folder "{name}"!' => '', + '{displayName} added {n,plural,=1{a file} other{# files}} to the folder "{folderTitle}".' => '', + '{originator} added {n,plural,=1{a file} other{# files}} to the folder "{folderTitle}"' => '', + '{originator} added {n,plural,=1{a file} other{# files}} to the folder "{folderTitle}" in Space {space}' => '', ]; diff --git a/messages/fr/base.php b/messages/fr/base.php index 516d6dcc..7477b362 100644 --- a/messages/fr/base.php +++ b/messages/fr/base.php @@ -116,4 +116,7 @@ 'You cannot move the folder "{name}"!' => 'Vous ne pouvez pas déplacer le dossier "{name}".', 'ZIP selected' => 'archive (.zip) sélectionnée', 'ZIP support is not enabled.' => 'Le support d\'archive (.zip) n\'est pas activé.', + '{displayName} added {n,plural,=1{a file} other{# files}} to the folder "{folderTitle}".' => '', + '{originator} added {n,plural,=1{a file} other{# files}} to the folder "{folderTitle}"' => '', + '{originator} added {n,plural,=1{a file} other{# files}} to the folder "{folderTitle}" in Space {space}' => '', ]; diff --git a/messages/he/base.php b/messages/he/base.php index c4b724d0..8ec75f7c 100644 --- a/messages/he/base.php +++ b/messages/he/base.php @@ -116,4 +116,7 @@ 'You cannot move the folder "{name}"!' => '', 'ZIP selected' => '', 'ZIP support is not enabled.' => '', + '{displayName} added {n,plural,=1{a file} other{# files}} to the folder "{folderTitle}".' => '', + '{originator} added {n,plural,=1{a file} other{# files}} to the folder "{folderTitle}"' => '', + '{originator} added {n,plural,=1{a file} other{# files}} to the folder "{folderTitle}" in Space {space}' => '', ]; diff --git a/messages/hr/base.php b/messages/hr/base.php index 4c822d13..c62a63d9 100644 --- a/messages/hr/base.php +++ b/messages/hr/base.php @@ -116,4 +116,7 @@ 'Wrong target folder!' => '', 'You cannot move the file "{name}"!' => '', 'You cannot move the folder "{name}"!' => '', + '{displayName} added {n,plural,=1{a file} other{# files}} to the folder "{folderTitle}".' => '', + '{originator} added {n,plural,=1{a file} other{# files}} to the folder "{folderTitle}"' => '', + '{originator} added {n,plural,=1{a file} other{# files}} to the folder "{folderTitle}" in Space {space}' => '', ]; diff --git a/messages/ht/base.php b/messages/ht/base.php index cb77b943..4548a285 100644 --- a/messages/ht/base.php +++ b/messages/ht/base.php @@ -116,4 +116,7 @@ 'You cannot move the folder "{name}"!' => '', 'ZIP selected' => '', 'ZIP support is not enabled.' => '', + '{displayName} added {n,plural,=1{a file} other{# files}} to the folder "{folderTitle}".' => '', + '{originator} added {n,plural,=1{a file} other{# files}} to the folder "{folderTitle}"' => '', + '{originator} added {n,plural,=1{a file} other{# files}} to the folder "{folderTitle}" in Space {space}' => '', ]; diff --git a/messages/hu/base.php b/messages/hu/base.php index e6b514a1..af295814 100644 --- a/messages/hu/base.php +++ b/messages/hu/base.php @@ -116,4 +116,7 @@ 'Files (Module)' => '', 'Folder content ID' => '', 'Folders' => '', + '{displayName} added {n,plural,=1{a file} other{# files}} to the folder "{folderTitle}".' => '', + '{originator} added {n,plural,=1{a file} other{# files}} to the folder "{folderTitle}"' => '', + '{originator} added {n,plural,=1{a file} other{# files}} to the folder "{folderTitle}" in Space {space}' => '', ]; diff --git a/messages/id/base.php b/messages/id/base.php index 397c145f..1398be8e 100644 --- a/messages/id/base.php +++ b/messages/id/base.php @@ -116,4 +116,7 @@ 'You cannot move the folder "{name}"!' => '', 'ZIP selected' => '', 'ZIP support is not enabled.' => '', + '{displayName} added {n,plural,=1{a file} other{# files}} to the folder "{folderTitle}".' => '', + '{originator} added {n,plural,=1{a file} other{# files}} to the folder "{folderTitle}"' => '', + '{originator} added {n,plural,=1{a file} other{# files}} to the folder "{folderTitle}" in Space {space}' => '', ]; diff --git a/messages/it/base.php b/messages/it/base.php index 16827a95..215b7a47 100644 --- a/messages/it/base.php +++ b/messages/it/base.php @@ -116,4 +116,7 @@ 'Files (Module)' => '', 'Folder content ID' => '', 'Folders' => '', + '{displayName} added {n,plural,=1{a file} other{# files}} to the folder "{folderTitle}".' => '', + '{originator} added {n,plural,=1{a file} other{# files}} to the folder "{folderTitle}"' => '', + '{originator} added {n,plural,=1{a file} other{# files}} to the folder "{folderTitle}" in Space {space}' => '', ]; diff --git a/messages/ja/base.php b/messages/ja/base.php index 704642ae..5698bc0d 100644 --- a/messages/ja/base.php +++ b/messages/ja/base.php @@ -116,4 +116,7 @@ 'Files (Module)' => '', 'Folder content ID' => '', 'Folders' => '', + '{displayName} added {n,plural,=1{a file} other{# files}} to the folder "{folderTitle}".' => '', + '{originator} added {n,plural,=1{a file} other{# files}} to the folder "{folderTitle}"' => '', + '{originator} added {n,plural,=1{a file} other{# files}} to the folder "{folderTitle}" in Space {space}' => '', ]; diff --git a/messages/ko/base.php b/messages/ko/base.php index 0b5fd949..847fd37f 100644 --- a/messages/ko/base.php +++ b/messages/ko/base.php @@ -116,4 +116,7 @@ 'You cannot move the folder "{name}"!' => '', 'ZIP selected' => '', 'ZIP support is not enabled.' => '', + '{displayName} added {n,plural,=1{a file} other{# files}} to the folder "{folderTitle}".' => '', + '{originator} added {n,plural,=1{a file} other{# files}} to the folder "{folderTitle}"' => '', + '{originator} added {n,plural,=1{a file} other{# files}} to the folder "{folderTitle}" in Space {space}' => '', ]; diff --git a/messages/lt/base.php b/messages/lt/base.php index 99ae9bc5..e6e071f6 100644 --- a/messages/lt/base.php +++ b/messages/lt/base.php @@ -116,4 +116,7 @@ 'Files (Module)' => '', 'Folder content ID' => '', 'Folders' => '', + '{displayName} added {n,plural,=1{a file} other{# files}} to the folder "{folderTitle}".' => '', + '{originator} added {n,plural,=1{a file} other{# files}} to the folder "{folderTitle}"' => '', + '{originator} added {n,plural,=1{a file} other{# files}} to the folder "{folderTitle}" in Space {space}' => '', ]; diff --git a/messages/lv/base.php b/messages/lv/base.php index 9e9d4b37..5089fd9d 100644 --- a/messages/lv/base.php +++ b/messages/lv/base.php @@ -116,4 +116,7 @@ 'You cannot move the folder "{name}"!' => '', 'ZIP selected' => '', 'ZIP support is not enabled.' => '', + '{displayName} added {n,plural,=1{a file} other{# files}} to the folder "{folderTitle}".' => '', + '{originator} added {n,plural,=1{a file} other{# files}} to the folder "{folderTitle}"' => '', + '{originator} added {n,plural,=1{a file} other{# files}} to the folder "{folderTitle}" in Space {space}' => '', ]; diff --git a/messages/nb-NO/base.php b/messages/nb-NO/base.php index 432566cc..dd79cb0f 100644 --- a/messages/nb-NO/base.php +++ b/messages/nb-NO/base.php @@ -116,4 +116,7 @@ 'Wrong target folder!' => '', 'You cannot move the file "{name}"!' => '', 'You cannot move the folder "{name}"!' => '', + '{displayName} added {n,plural,=1{a file} other{# files}} to the folder "{folderTitle}".' => '', + '{originator} added {n,plural,=1{a file} other{# files}} to the folder "{folderTitle}"' => '', + '{originator} added {n,plural,=1{a file} other{# files}} to the folder "{folderTitle}" in Space {space}' => '', ]; diff --git a/messages/nl/base.php b/messages/nl/base.php index a62aa933..53a3ae3e 100644 --- a/messages/nl/base.php +++ b/messages/nl/base.php @@ -116,4 +116,7 @@ 'You cannot move the folder "{name}"!' => 'U kunt de map "{name}" niet verplaatsen!', 'ZIP selected' => 'ZIPpen', 'ZIP support is not enabled.' => 'ZIP-ondersteuning is niet ingeschakeld.', + '{displayName} added {n,plural,=1{a file} other{# files}} to the folder "{folderTitle}".' => '', + '{originator} added {n,plural,=1{a file} other{# files}} to the folder "{folderTitle}"' => '', + '{originator} added {n,plural,=1{a file} other{# files}} to the folder "{folderTitle}" in Space {space}' => '', ]; diff --git a/messages/nn-NO/base.php b/messages/nn-NO/base.php index abb36dce..19db66d6 100644 --- a/messages/nn-NO/base.php +++ b/messages/nn-NO/base.php @@ -116,4 +116,7 @@ 'You cannot move the folder "{name}"!' => '', 'ZIP selected' => '', 'ZIP support is not enabled.' => '', + '{displayName} added {n,plural,=1{a file} other{# files}} to the folder "{folderTitle}".' => '', + '{originator} added {n,plural,=1{a file} other{# files}} to the folder "{folderTitle}"' => '', + '{originator} added {n,plural,=1{a file} other{# files}} to the folder "{folderTitle}" in Space {space}' => '', ]; diff --git a/messages/pl/base.php b/messages/pl/base.php index 6835db5e..b1b5bc19 100644 --- a/messages/pl/base.php +++ b/messages/pl/base.php @@ -1,118 +1,122 @@ '%filename% ma nieprawidłowe rozszerzenie i został pominięty.', - '/ (root)' => '/ (root)', - 'Confirm delete file' => 'Potwierdź usunięcie pliku', - 'Create folder' => 'Utwórz nowy folder', - 'Edit file' => 'Edytuj plik', - 'Edit folder' => 'Edytuj katalog', - 'File download url' => 'Adres do pobraniaPliku', - 'File url' => 'adres Pliku', - 'File versions' => 'Pliki wersje', - 'Files module configuration' => 'moduł konfiguracji Plików', - 'Folder url' => 'adres Folderu', - 'Move files' => 'Przenieś pliki', - 'A file with that name already exists in this folder.' => 'Plik o tej nazwie już istnieje w tym folderze.', - 'Actions' => 'Akcje', - 'Add directory' => 'Dodaj katalog', - 'Add file(s)' => 'Dodaj Plik(i)', - 'Add files' => 'Dodaj pliki', - 'Adds files module to this space.' => 'Dodaj moduł plików do tej strefy.', - 'Adds files module to your profile.' => 'Dodaje moduł plików do twojego profilu', - 'Allows the user to modify or delete any files.' => 'Zezwól użytkownikom na modyfikacje i usuwanie plików.', - 'Allows the user to upload new files and create folders' => 'Zezwól użytkownikom na wgrywanie nowych plików i tworzenie folderów', - 'An error occurred while creating folder {folder}.' => 'Wystąpił błąd w trakcie tworzenia folderu {folder}.', - 'An error occurred while unpacking {filename}.' => 'Wystąpił błąd w trakcie wypakowywania {filename}.', - 'Archive %filename% could not be extracted.' => 'Nie można rozpakować %filename%.', - 'Are you really sure to delete this version?' => 'Czy aby na pewno chcesz skasować wydanie?', - 'Author' => 'Autor', - 'Cannot edit non existing file.' => 'Nie można edytować nie istniejącego pliku.', - 'Close' => 'Zamknij', - 'Could not find folder with id: %id%' => 'Nie można znaleźć folderu o id: %id%', - 'Could not import file with guid {guid}. File not found' => 'Nie udało się zaimportować pliku z guid {guid}. Nie odnaleziono pliku', - 'Could not move the item!' => 'Nie można przenieść elementu!', - 'Creator' => 'Twórca', - 'Delete' => 'Usuń', - 'Delete this version!' => 'Skasuj tą wersję!', - 'Description' => 'Opis', - 'Destination folder not found!' => 'Folder docelowy nieodnaleziony!', - 'Disable archive (ZIP) support' => 'Wyłącz wsparcie archiwów (ZIP)', - 'Display Url' => 'Wyświetl adres', - 'Display a download count column' => 'Wyświetlaj kolumnę z licznikiem pobrań', - 'Do you really want to delete this {number} item(s) with all subcontent?' => 'Czy na pewno chcesz usunąć {number} rzeczy z całą zawartością?', - 'Download' => 'Pobierz', - 'Download ZIP' => 'Pobierz ZIP', - 'Downloads' => 'Pobrane', - 'Edit' => 'Edycja', - 'Edit directory' => 'Edytuj katalog', - 'File' => 'Plik', - 'File "{movedItemName}" has been moved into the folder "{targetFolderName}".' => 'Plik "{movedItemName}" został przeniesiony do folderu "{targetFolderName}".', - 'File (Module)' => 'Plik (moduł)', - 'File content ID' => 'ID zawartości pliku', - 'File {fileName} has been reverted to version from {fileDateTime}' => 'Plik {fileName} został przywrócony do stanu z dnia {fileDateTime}', - 'Files' => 'Pliki', - 'Files (Module)' => 'Pliki (moduł)', - 'Files from the stream' => 'Pliki z tego strumienia', - 'Folder' => 'Katalog', - 'Folder "{movedItemName}" has been moved into the folder "{targetFolderName}".' => 'Folder "{movedItemName}" został przeniesiony w nowe miejsce "{targetFolderName}".', - 'Folder ID' => 'ID Foldera', - 'Folder content ID' => 'ID zawartości folderu', - 'Folder should not start or end with blank space.' => 'Folder nie może zaczynać lub kończyć się pustym znakiem.', - 'Folder {name} can\'t be moved to itself!' => 'Folder {name} nie może zostać przeniesiony do samego siebie!', - 'Folder {name} given folder is not editable!' => 'Folder {name} jest nieedytowalny!', - 'Folders' => 'Foldery', - 'Hide in Stream' => 'Ukryj w strumieniu', - 'Import Zip' => 'Importuj Zip', - 'Is Public' => 'Jest Publiczny', - 'Likes/Comments' => 'Polubienia/Komentarze', - 'Make Private' => 'Zmień na Prywatny', - 'Make Public' => 'Zmień na Publiczny', - 'Manage files' => 'Zarządzaj plikami', - 'Move' => 'Przenieś', - 'Moving to the same folder is not valid.' => 'Przenoszenie do tego samego folderu jest nieprawidłowe.', - 'Moving to this folder is invalid.' => 'Przenoszenie do tego folderu jest nieprawidłowe.', - 'Name' => 'Nazwa', - 'No file found!' => 'Nie odnaleziono pliku', - 'Note: Changes of the folders visibility, will be inherited by all contained files and folders.' => 'Notatka: Zmiany widoczności folderów, będą dziedziczone przez wszystkie zawarte pliki i foldery.', - 'Open' => 'Otwórz', - 'Open file folder' => 'Otwórz folder pliku', - 'Opening archive failed with error code %code%.' => 'Otwarcie archiwum spowodowało błąd. Kod błędu: %code%.', - 'Parent Folder ID' => 'ID Foldera Nadrzeędnego', - 'Please select a valid destination folder for %title%.' => 'Wybierz poprawne miejsce docelowe dla %title%.', - 'Revert to this version' => 'Przywróć do tej wersji', - 'Root' => 'Root', - 'Select what file version you want to switch.' => 'Wybierz wersję, którą chcesz przywrócić.', - 'Selected items...' => 'Zaznaczone obiekty...', - 'Show Post' => 'Pokaż Post', - 'Show older versions' => 'Pokaż starsze wersje', - 'Size' => 'Rozmiar', - 'Size: {size}' => 'Rozmiar: {size}', - 'Some files could not be imported: ' => 'Niektóre pliki nie mogły zostać zaimportowane:', - 'Some files could not be moved: ' => 'Niektóre pliki nie mogły zostać przeniesione:', - 'The root folder is the entry point that contains all available files.' => 'Folder źródłowy jest miejscem zawierającym wszystkie możliwe pliki.', - 'The version "{versionDate}" could not be deleted!' => 'Wydanie "{versionDate}" nie może zostać skasowane!', - 'The version "{versionDate}" has been deleted.' => 'Wydanie "{versionDate}" zostało skasowane.', - 'This file is only visible for you and your friends.' => 'To jest widoczne tlyko dla Ciebie i twoich znajomych.', - 'This file is private.' => 'Ten plik jest prywatny,', - 'This file is protected.' => 'Ten plik jest chroniony.', - 'This file is public.' => 'Ten plik jet publiczny.', - 'This folder is empty.' => 'Katalog jest pusty.', - 'This folder is only visible for you and your friends.' => 'Ten folder jest widoczny tylko dla Ciebie i twoich znajomych.', - 'This folder is private.' => 'Ten folder jest prywatny.', - 'This folder is protected.' => 'Ten folder jest chroniony.', - 'This folder is public.' => 'Ten folder jest publiczny.', - 'Time' => 'Czas', - 'Title' => 'Tytuł', - 'Unfortunately you have no permission to upload/edit files.' => 'Niestety nie masz uprawnień do przesyłania/edycji plików.', - 'Updated' => 'Zaktualizowane', - 'Upload files or create a subfolder with the buttons on the top.' => 'Prześlij pliku lub utwórz podkatalog za pomocą przycisków na górze.', - 'Upload files to the stream to fill this folder.' => 'Prześlij pliki na strumień aby wypełnić katalog zawartością.', - 'Versions' => 'Wersje', - 'Wrong moved item!' => 'Błędny przenoszony element!', - 'Wrong target folder!' => 'Błędny folder docelowy!', - 'You can find all files that have been posted to this stream here.' => 'Tutaj możesz znaleźć wszystkie pliki dodane do tego strumienia.', - 'You cannot move the file "{name}"!' => 'Nie możesz przenieść pliku "{name}"!', - 'You cannot move the folder "{name}"!' => 'Nie możesz przenieść folderu "{name}"!', - 'ZIP selected' => 'Wybrany ZIP', - 'ZIP support is not enabled.' => 'Wsparcie ZIP jest wyłączone.', + '%filename% has invalid extension and was skipped.' => '%filename% ma nieprawidłowe rozszerzenie i został pominięty.', + '/ (root)' => '/ (root)', + 'Confirm delete file' => 'Potwierdź usunięcie pliku', + 'Create folder' => 'Utwórz nowy folder', + 'Edit file' => 'Edytuj plik', + 'Edit folder' => 'Edytuj katalog', + 'File download url' => 'Adres do pobraniaPliku', + 'File url' => 'adres Pliku', + 'File versions' => 'Pliki wersje', + 'Files module configuration' => 'moduł konfiguracji Plików', + 'Folder url' => 'adres Folderu', + 'Move files' => 'Przenieś pliki', + 'A file with that name already exists in this folder.' => 'Plik o tej nazwie już istnieje w tym folderze.', + 'Actions' => 'Akcje', + 'Add directory' => 'Dodaj katalog', + 'Add file(s)' => 'Dodaj Plik(i)', + 'Add files' => 'Dodaj pliki', + 'Adds files module to this space.' => 'Dodaj moduł plików do tej strefy.', + 'Adds files module to your profile.' => 'Dodaje moduł plików do twojego profilu', + 'Allows the user to modify or delete any files.' => 'Zezwól użytkownikom na modyfikacje i usuwanie plików.', + 'Allows the user to upload new files and create folders' => 'Zezwól użytkownikom na wgrywanie nowych plików i tworzenie folderów', + 'An error occurred while creating folder {folder}.' => 'Wystąpił błąd w trakcie tworzenia folderu {folder}.', + 'An error occurred while unpacking {filename}.' => 'Wystąpił błąd w trakcie wypakowywania {filename}.', + 'Archive %filename% could not be extracted.' => 'Nie można rozpakować %filename%.', + 'Are you really sure to delete this version?' => 'Czy aby na pewno chcesz skasować wydanie?', + 'Author' => 'Autor', + 'Cannot edit non existing file.' => 'Nie można edytować nie istniejącego pliku.', + 'Close' => 'Zamknij', + 'Could not find folder with id: %id%' => 'Nie można znaleźć folderu o id: %id%', + 'Could not import file with guid {guid}. File not found' => 'Nie udało się zaimportować pliku z guid {guid}. Nie odnaleziono pliku', + 'Could not move the item!' => 'Nie można przenieść elementu!', + 'Creator' => 'Twórca', + 'Delete' => 'Usuń', + 'Delete this version!' => 'Skasuj tą wersję!', + 'Description' => 'Opis', + 'Destination folder not found!' => 'Folder docelowy nieodnaleziony!', + 'Disable archive (ZIP) support' => 'Wyłącz wsparcie archiwów (ZIP)', + 'Display Url' => 'Wyświetl adres', + 'Display a download count column' => 'Wyświetlaj kolumnę z licznikiem pobrań', + 'Do you really want to delete this {number} item(s) with all subcontent?' => 'Czy na pewno chcesz usunąć {number} rzeczy z całą zawartością?', + 'Download' => 'Pobierz', + 'Download ZIP' => 'Pobierz ZIP', + 'Downloads' => 'Pobrane', + 'Edit' => 'Edycja', + 'Edit directory' => 'Edytuj katalog', + 'File' => 'Plik', + 'File "{movedItemName}" has been moved into the folder "{targetFolderName}".' => 'Plik "{movedItemName}" został przeniesiony do folderu "{targetFolderName}".', + 'File (Module)' => 'Plik (moduł)', + 'File content ID' => 'ID zawartości pliku', + 'File {fileName} has been reverted to version from {fileDateTime}' => 'Plik {fileName} został przywrócony do stanu z dnia {fileDateTime}', + 'Files' => 'Pliki', + 'Files (Module)' => 'Pliki (moduł)', + 'Files from the stream' => 'Pliki z tego strumienia', + 'Folder' => 'Katalog', + 'Folder "{movedItemName}" has been moved into the folder "{targetFolderName}".' => 'Folder "{movedItemName}" został przeniesiony w nowe miejsce "{targetFolderName}".', + 'Folder ID' => 'ID Foldera', + 'Folder content ID' => 'ID zawartości folderu', + 'Folder should not start or end with blank space.' => 'Folder nie może zaczynać lub kończyć się pustym znakiem.', + 'Folder {name} can\'t be moved to itself!' => 'Folder {name} nie może zostać przeniesiony do samego siebie!', + 'Folder {name} given folder is not editable!' => 'Folder {name} jest nieedytowalny!', + 'Folders' => 'Foldery', + 'Hide in Stream' => 'Ukryj w strumieniu', + 'Import Zip' => 'Importuj Zip', + 'Is Public' => 'Jest Publiczny', + 'Likes/Comments' => 'Polubienia/Komentarze', + 'Make Private' => 'Zmień na Prywatny', + 'Make Public' => 'Zmień na Publiczny', + 'Manage files' => 'Zarządzaj plikami', + 'Move' => 'Przenieś', + 'Moving to the same folder is not valid.' => 'Przenoszenie do tego samego folderu jest nieprawidłowe.', + 'Moving to this folder is invalid.' => 'Przenoszenie do tego folderu jest nieprawidłowe.', + 'Name' => 'Nazwa', + 'No file found!' => 'Nie odnaleziono pliku', + 'Note: Changes of the folders visibility, will be inherited by all contained files and folders.' => 'Notatka: Zmiany widoczności folderów, będą dziedziczone przez wszystkie zawarte pliki i foldery.', + 'Open' => 'Otwórz', + 'Open file folder' => 'Otwórz folder pliku', + 'Opening archive failed with error code %code%.' => 'Otwarcie archiwum spowodowało błąd. Kod błędu: %code%.', + 'Parent Folder ID' => 'ID Foldera Nadrzeędnego', + 'Please select a valid destination folder for %title%.' => 'Wybierz poprawne miejsce docelowe dla %title%.', + 'Revert to this version' => 'Przywróć do tej wersji', + 'Root' => 'Root', + 'Select what file version you want to switch.' => 'Wybierz wersję, którą chcesz przywrócić.', + 'Selected items...' => 'Zaznaczone obiekty...', + 'Show Post' => 'Pokaż Post', + 'Show older versions' => 'Pokaż starsze wersje', + 'Size' => 'Rozmiar', + 'Size: {size}' => 'Rozmiar: {size}', + 'Some files could not be imported: ' => 'Niektóre pliki nie mogły zostać zaimportowane:', + 'Some files could not be moved: ' => 'Niektóre pliki nie mogły zostać przeniesione:', + 'The root folder is the entry point that contains all available files.' => 'Folder źródłowy jest miejscem zawierającym wszystkie możliwe pliki.', + 'The version "{versionDate}" could not be deleted!' => 'Wydanie "{versionDate}" nie może zostać skasowane!', + 'The version "{versionDate}" has been deleted.' => 'Wydanie "{versionDate}" zostało skasowane.', + 'This file is only visible for you and your friends.' => 'To jest widoczne tlyko dla Ciebie i twoich znajomych.', + 'This file is private.' => 'Ten plik jest prywatny,', + 'This file is protected.' => 'Ten plik jest chroniony.', + 'This file is public.' => 'Ten plik jet publiczny.', + 'This folder is empty.' => 'Katalog jest pusty.', + 'This folder is only visible for you and your friends.' => 'Ten folder jest widoczny tylko dla Ciebie i twoich znajomych.', + 'This folder is private.' => 'Ten folder jest prywatny.', + 'This folder is protected.' => 'Ten folder jest chroniony.', + 'This folder is public.' => 'Ten folder jest publiczny.', + 'Time' => 'Czas', + 'Title' => 'Tytuł', + 'Unfortunately you have no permission to upload/edit files.' => 'Niestety nie masz uprawnień do przesyłania/edycji plików.', + 'Updated' => 'Zaktualizowane', + 'Upload files or create a subfolder with the buttons on the top.' => 'Prześlij pliku lub utwórz podkatalog za pomocą przycisków na górze.', + 'Upload files to the stream to fill this folder.' => 'Prześlij pliki na strumień aby wypełnić katalog zawartością.', + 'Versions' => 'Wersje', + 'Wrong moved item!' => 'Błędny przenoszony element!', + 'Wrong target folder!' => 'Błędny folder docelowy!', + 'You can find all files that have been posted to this stream here.' => 'Tutaj możesz znaleźć wszystkie pliki dodane do tego strumienia.', + 'You cannot move the file "{name}"!' => 'Nie możesz przenieść pliku "{name}"!', + 'You cannot move the folder "{name}"!' => 'Nie możesz przenieść folderu "{name}"!', + 'ZIP selected' => 'Wybrany ZIP', + 'ZIP support is not enabled.' => 'Wsparcie ZIP jest wyłączone.', + '{displayName} added {n,plural,=1{a file} other{# files}} to the folder "{folderTitle}".' => '', + '{originator} added {n,plural,=1{a file} other{# files}} to the folder "{folderTitle}"' => '', + '{originator} added {n,plural,=1{a file} other{# files}} to the folder "{folderTitle}" in Space {space}' => '', ]; diff --git a/messages/pt-BR/base.php b/messages/pt-BR/base.php index 3701b151..f4a190c9 100644 --- a/messages/pt-BR/base.php +++ b/messages/pt-BR/base.php @@ -116,4 +116,7 @@ 'You cannot move the folder "{name}"!' => 'Você não pode mover a pasta "{name}"!', 'ZIP selected' => 'ZIP selecionado', 'ZIP support is not enabled.' => 'Suporte para arquivos ZIP não habilitado.', + '{displayName} added {n,plural,=1{a file} other{# files}} to the folder "{folderTitle}".' => '', + '{originator} added {n,plural,=1{a file} other{# files}} to the folder "{folderTitle}"' => '', + '{originator} added {n,plural,=1{a file} other{# files}} to the folder "{folderTitle}" in Space {space}' => '', ]; diff --git a/messages/pt/base.php b/messages/pt/base.php index c728df7a..70c68d72 100644 --- a/messages/pt/base.php +++ b/messages/pt/base.php @@ -116,4 +116,7 @@ 'Folder content ID' => '', 'Folders' => '', 'Hide in Stream' => '', + '{displayName} added {n,plural,=1{a file} other{# files}} to the folder "{folderTitle}".' => '', + '{originator} added {n,plural,=1{a file} other{# files}} to the folder "{folderTitle}"' => '', + '{originator} added {n,plural,=1{a file} other{# files}} to the folder "{folderTitle}" in Space {space}' => '', ]; diff --git a/messages/ro/base.php b/messages/ro/base.php index cbbdce2f..5181f25f 100644 --- a/messages/ro/base.php +++ b/messages/ro/base.php @@ -116,4 +116,7 @@ 'Wrong target folder!' => '', 'You cannot move the file "{name}"!' => '', 'You cannot move the folder "{name}"!' => '', + '{displayName} added {n,plural,=1{a file} other{# files}} to the folder "{folderTitle}".' => '', + '{originator} added {n,plural,=1{a file} other{# files}} to the folder "{folderTitle}"' => '', + '{originator} added {n,plural,=1{a file} other{# files}} to the folder "{folderTitle}" in Space {space}' => '', ]; diff --git a/messages/ru/base.php b/messages/ru/base.php index 9e2cc2b5..ad20f943 100644 --- a/messages/ru/base.php +++ b/messages/ru/base.php @@ -116,4 +116,7 @@ 'Wrong moved item!' => '', 'Wrong target folder!' => '', 'You can find all files that have been posted to this stream here.' => '', + '{displayName} added {n,plural,=1{a file} other{# files}} to the folder "{folderTitle}".' => '', + '{originator} added {n,plural,=1{a file} other{# files}} to the folder "{folderTitle}"' => '', + '{originator} added {n,plural,=1{a file} other{# files}} to the folder "{folderTitle}" in Space {space}' => '', ]; diff --git a/messages/sk/base.php b/messages/sk/base.php index a19e0e00..707630f5 100644 --- a/messages/sk/base.php +++ b/messages/sk/base.php @@ -116,4 +116,7 @@ 'Files (Module)' => '', 'Folder content ID' => '', 'Folders' => '', + '{displayName} added {n,plural,=1{a file} other{# files}} to the folder "{folderTitle}".' => '', + '{originator} added {n,plural,=1{a file} other{# files}} to the folder "{folderTitle}"' => '', + '{originator} added {n,plural,=1{a file} other{# files}} to the folder "{folderTitle}" in Space {space}' => '', ]; diff --git a/messages/sl/base.php b/messages/sl/base.php index 4cf00158..2500638a 100644 --- a/messages/sl/base.php +++ b/messages/sl/base.php @@ -116,4 +116,7 @@ 'You cannot move the folder "{name}"!' => '', 'ZIP selected' => '', 'ZIP support is not enabled.' => '', + '{displayName} added {n,plural,=1{a file} other{# files}} to the folder "{folderTitle}".' => '', + '{originator} added {n,plural,=1{a file} other{# files}} to the folder "{folderTitle}"' => '', + '{originator} added {n,plural,=1{a file} other{# files}} to the folder "{folderTitle}" in Space {space}' => '', ]; diff --git a/messages/sq/base.php b/messages/sq/base.php index c4f77778..3faa08d1 100644 --- a/messages/sq/base.php +++ b/messages/sq/base.php @@ -116,4 +116,7 @@ 'You cannot move the folder "{name}"!' => '', 'ZIP selected' => '', 'ZIP support is not enabled.' => '', + '{displayName} added {n,plural,=1{a file} other{# files}} to the folder "{folderTitle}".' => '', + '{originator} added {n,plural,=1{a file} other{# files}} to the folder "{folderTitle}"' => '', + '{originator} added {n,plural,=1{a file} other{# files}} to the folder "{folderTitle}" in Space {space}' => '', ]; diff --git a/messages/sr/base.php b/messages/sr/base.php index e799c41b..519419c1 100644 --- a/messages/sr/base.php +++ b/messages/sr/base.php @@ -116,4 +116,7 @@ 'Wrong target folder!' => '', 'You cannot move the file "{name}"!' => '', 'You cannot move the folder "{name}"!' => '', + '{displayName} added {n,plural,=1{a file} other{# files}} to the folder "{folderTitle}".' => '', + '{originator} added {n,plural,=1{a file} other{# files}} to the folder "{folderTitle}"' => '', + '{originator} added {n,plural,=1{a file} other{# files}} to the folder "{folderTitle}" in Space {space}' => '', ]; diff --git a/messages/sv/base.php b/messages/sv/base.php index 0522e989..1f6ea626 100644 --- a/messages/sv/base.php +++ b/messages/sv/base.php @@ -116,4 +116,7 @@ 'Files (Module)' => '', 'Folder content ID' => '', 'Folders' => '', + '{displayName} added {n,plural,=1{a file} other{# files}} to the folder "{folderTitle}".' => '', + '{originator} added {n,plural,=1{a file} other{# files}} to the folder "{folderTitle}"' => '', + '{originator} added {n,plural,=1{a file} other{# files}} to the folder "{folderTitle}" in Space {space}' => '', ]; diff --git a/messages/sw/base.php b/messages/sw/base.php index c9b1c838..8f0cc1cf 100644 --- a/messages/sw/base.php +++ b/messages/sw/base.php @@ -116,4 +116,7 @@ 'You cannot move the folder "{name}"!' => '', 'ZIP selected' => '', 'ZIP support is not enabled.' => '', + '{displayName} added {n,plural,=1{a file} other{# files}} to the folder "{folderTitle}".' => '', + '{originator} added {n,plural,=1{a file} other{# files}} to the folder "{folderTitle}"' => '', + '{originator} added {n,plural,=1{a file} other{# files}} to the folder "{folderTitle}" in Space {space}' => '', ]; diff --git a/messages/th/base.php b/messages/th/base.php index abfb863d..ed3aae0d 100644 --- a/messages/th/base.php +++ b/messages/th/base.php @@ -116,4 +116,7 @@ 'Wrong target folder!' => '', 'You cannot move the file "{name}"!' => '', 'You cannot move the folder "{name}"!' => '', + '{displayName} added {n,plural,=1{a file} other{# files}} to the folder "{folderTitle}".' => '', + '{originator} added {n,plural,=1{a file} other{# files}} to the folder "{folderTitle}"' => '', + '{originator} added {n,plural,=1{a file} other{# files}} to the folder "{folderTitle}" in Space {space}' => '', ]; diff --git a/messages/tr/base.php b/messages/tr/base.php index 1310d7b0..86200736 100644 --- a/messages/tr/base.php +++ b/messages/tr/base.php @@ -116,4 +116,7 @@ 'You can find all files that have been posted to this stream here.' => '', 'ZIP selected' => '', 'ZIP support is not enabled.' => '', + '{displayName} added {n,plural,=1{a file} other{# files}} to the folder "{folderTitle}".' => '', + '{originator} added {n,plural,=1{a file} other{# files}} to the folder "{folderTitle}"' => '', + '{originator} added {n,plural,=1{a file} other{# files}} to the folder "{folderTitle}" in Space {space}' => '', ]; diff --git a/messages/uk/base.php b/messages/uk/base.php index e7dfbfce..8a43fbfa 100644 --- a/messages/uk/base.php +++ b/messages/uk/base.php @@ -116,4 +116,7 @@ 'You cannot move the folder "{name}"!' => '', 'ZIP selected' => '', 'ZIP support is not enabled.' => '', + '{displayName} added {n,plural,=1{a file} other{# files}} to the folder "{folderTitle}".' => '', + '{originator} added {n,plural,=1{a file} other{# files}} to the folder "{folderTitle}"' => '', + '{originator} added {n,plural,=1{a file} other{# files}} to the folder "{folderTitle}" in Space {space}' => '', ]; diff --git a/messages/uz/base.php b/messages/uz/base.php index 3fbdada0..b137b738 100644 --- a/messages/uz/base.php +++ b/messages/uz/base.php @@ -116,4 +116,7 @@ 'You cannot move the folder "{name}"!' => '', 'ZIP selected' => '', 'ZIP support is not enabled.' => '', + '{displayName} added {n,plural,=1{a file} other{# files}} to the folder "{folderTitle}".' => '', + '{originator} added {n,plural,=1{a file} other{# files}} to the folder "{folderTitle}"' => '', + '{originator} added {n,plural,=1{a file} other{# files}} to the folder "{folderTitle}" in Space {space}' => '', ]; diff --git a/messages/vi/base.php b/messages/vi/base.php index 07270926..82840d0a 100644 --- a/messages/vi/base.php +++ b/messages/vi/base.php @@ -116,4 +116,7 @@ 'Wrong target folder!' => '', 'You cannot move the file "{name}"!' => '', 'You cannot move the folder "{name}"!' => '', + '{displayName} added {n,plural,=1{a file} other{# files}} to the folder "{folderTitle}".' => '', + '{originator} added {n,plural,=1{a file} other{# files}} to the folder "{folderTitle}"' => '', + '{originator} added {n,plural,=1{a file} other{# files}} to the folder "{folderTitle}" in Space {space}' => '', ]; diff --git a/messages/zh-CN/base.php b/messages/zh-CN/base.php index 9c3a9a1c..a95b64a4 100644 --- a/messages/zh-CN/base.php +++ b/messages/zh-CN/base.php @@ -116,4 +116,7 @@ 'You cannot move the folder "{name}"!' => '', 'ZIP selected' => '', 'ZIP support is not enabled.' => '', + '{displayName} added {n,plural,=1{a file} other{# files}} to the folder "{folderTitle}".' => '', + '{originator} added {n,plural,=1{a file} other{# files}} to the folder "{folderTitle}"' => '', + '{originator} added {n,plural,=1{a file} other{# files}} to the folder "{folderTitle}" in Space {space}' => '', ]; diff --git a/messages/zh-TW/base.php b/messages/zh-TW/base.php index 73fd1425..d3a9c628 100644 --- a/messages/zh-TW/base.php +++ b/messages/zh-TW/base.php @@ -116,4 +116,7 @@ 'You cannot move the folder "{name}"!' => '', 'ZIP selected' => '', 'ZIP support is not enabled.' => '', + '{displayName} added {n,plural,=1{a file} other{# files}} to the folder "{folderTitle}".' => '', + '{originator} added {n,plural,=1{a file} other{# files}} to the folder "{folderTitle}"' => '', + '{originator} added {n,plural,=1{a file} other{# files}} to the folder "{folderTitle}" in Space {space}' => '', ]; diff --git a/models/File.php b/models/File.php index e29655f7..a93da959 100644 --- a/models/File.php +++ b/models/File.php @@ -2,6 +2,7 @@ namespace humhub\modules\cfiles\models; +use humhub\modules\cfiles\libs\FileUploadBatch; use humhub\modules\cfiles\libs\FileUtils; use humhub\modules\comment\models\Comment; use humhub\modules\content\components\ContentContainerActiveRecord; @@ -11,7 +12,6 @@ use humhub\modules\file\models\File as BaseFile; use humhub\modules\file\models\FileUpload; use humhub\modules\post\models\Post; -use humhub\modules\search\events\SearchAddEvent; use humhub\modules\topic\models\Topic; use humhub\modules\user\models\User; use Yii; @@ -51,6 +51,16 @@ class File extends FileSystemItem */ public $fileManagerEnableHistory = true; + /** + * @inheritdoc + * + * Uploading a set of files would otherwise create one notification and one e-mail per + * file. The whole upload is announced by a single FilesUploaded notification instead. + * + * @see FileUploadBatch + */ + public $silentContentCreation = true; + /** * @inheritdoc */ @@ -135,7 +145,6 @@ public function getSearchAttributes() if ($this->baseFile) { $attributes['name'] = $this->getTitle(); } - $this->trigger(self::EVENT_SEARCH_ADD, new SearchAddEvent($attributes)); return $attributes; } @@ -198,6 +207,10 @@ public function afterSave($insert, $changedAttributes) parent::afterSave($insert, $changedAttributes); RichText::postProcess($this->description, $this); + + if ($insert) { + FileUploadBatch::add($this); + } } public function updateVisibility($visibility) @@ -322,24 +335,26 @@ public function getEditUrl() } /** - * Get the post related to the given file file. + * Get the related Content record to the given file. */ - public static function getBasePost(?BaseFile $file = null) + public static function getBasePost(?BaseFile $file = null): ?Content { if ($file === null) { return null; } - $searchItem = $file; - // if the item is connected to a Comment, we have to search for the corresponding Post + // If the File is linked to a Comment if ($file->object_model === Comment::class) { - $searchItem = Comment::findOne($file->object_id); + return Content::find() + ->innerJoin('comment', 'comment.content_id = content.id') + ->where(['comment.id' => $file->object_id]) + ->one(); } - return Content::find()->where([ - 'content.object_id' => $searchItem->object_id, - 'content.object_model' => $searchItem->object_model, - ])->one(); + return Content::findOne([ + 'content.object_id' => $file->object_id, + 'content.object_model' => $file->object_model, + ]); } public function getBaseFile() @@ -396,8 +411,8 @@ public static function getPostedFiles($contentContainer, $filesOrder = ['file.up ->where(['content.object_model' => Post::class]), Comment::class => Content::find() ->select('comment.id') - ->innerJoin('comment', 'comment.object_model = content.object_model AND comment.object_id = content.object_id') - ->where(['comment.object_model' => Post::class]), + ->innerJoin('comment', 'comment.content_id = content.id') + ->where(['content.object_model' => Post::class]), ]; $query = BaseFile::find(); diff --git a/models/FileSystemItem.php b/models/FileSystemItem.php index 98f19188..3bdb88ce 100644 --- a/models/FileSystemItem.php +++ b/models/FileSystemItem.php @@ -8,8 +8,6 @@ use humhub\modules\content\components\ContentContainerActiveRecord; use humhub\modules\content\components\ContentActiveRecord; use humhub\modules\content\models\Content; -use humhub\modules\user\models\User; -use humhub\modules\search\interfaces\Searchable; use Yii; /** @@ -21,7 +19,7 @@ * * @property-read Folder|null $parentFolder */ -abstract class FileSystemItem extends ContentActiveRecord implements ItemInterface, Searchable +abstract class FileSystemItem extends ContentActiveRecord implements ItemInterface { /** * @var int used for edit form diff --git a/models/Folder.php b/models/Folder.php index 90d22720..987ac6d7 100644 --- a/models/Folder.php +++ b/models/Folder.php @@ -8,7 +8,6 @@ use humhub\modules\file\models\FileContent; use humhub\modules\file\libs\FileHelper; use humhub\modules\user\models\User; -use humhub\modules\search\events\SearchAddEvent; use humhub\modules\space\models\Space; use Yii; use yii\db\ActiveQuery; @@ -162,7 +161,6 @@ public function getSearchAttributes() $attributes['editor'] = $this->getEditor()->getDisplayName(); } } - $this->trigger(self::EVENT_SEARCH_ADD, new SearchAddEvent($attributes)); return $attributes; } diff --git a/module.json b/module.json index cf0d03ca..d2d4092b 100644 --- a/module.json +++ b/module.json @@ -9,10 +9,9 @@ "organisation", "sharing" ], - "version": "0.17.4", + "version": "0.19.0", "humhub": { - "minVersion": "1.18.1", - "maxVersion": "1.18" + "minVersion": "1.19" }, "homepage": "https://github.com/humhub/cfiles", "authors": [ diff --git a/notifications/FilesUploaded.php b/notifications/FilesUploaded.php new file mode 100644 index 00000000..e82aef97 --- /dev/null +++ b/notifications/FilesUploaded.php @@ -0,0 +1,137 @@ +payload(['fileCount' => $fileCount]); + } + + /** + * @return int number of uploaded files this notification announces + */ + public function getFileCount(): int + { + return max(1, (int)($this->payload['fileCount'] ?? 1)); + } + + /** + * The folder title instead of `getContentInfo()`, since `Folder::getContentDescription()` + * returns the raw title, which is an untranslated placeholder for the root and the posted + * files folder. + */ + protected function getFolderTitle(): string + { + return $this->source instanceof Folder ? $this->source->getTitle() : ''; + } + + /** + * @inheritdoc + */ + public function html() + { + return Yii::t('CfilesModule.base', '{displayName} added {n,plural,=1{a file} other{# files}} to the folder "{folderTitle}".', [ + 'displayName' => Html::tag('strong', Html::encode($this->originator->displayName)), + 'folderTitle' => Html::encode($this->getFolderTitle()), + 'n' => $this->getFileCount(), + ]); + } + + /** + * @inheritdoc + */ + public function getMailSubject() + { + $space = $this->getSpace(); + + if ($space) { + return Yii::t('CfilesModule.base', '{originator} added {n,plural,=1{a file} other{# files}} to the folder "{folderTitle}" in Space {space}', [ + 'originator' => $this->originator->displayName, + 'folderTitle' => $this->getFolderTitle(), + 'space' => $space->displayName, + 'n' => $this->getFileCount(), + ]); + } + + return Yii::t('CfilesModule.base', '{originator} added {n,plural,=1{a file} other{# files}} to the folder "{folderTitle}"', [ + 'originator' => $this->originator->displayName, + 'folderTitle' => $this->getFolderTitle(), + 'n' => $this->getFileCount(), + ]); + } + + /** + * @inheritdoc + * + * The base implementation only keeps source and originator, but the file count has to + * survive the queue, since it is only persisted when the notification records are created. + */ + public function __serialize(): array + { + $data = parent::__serialize(); + $data['fileCount'] = $this->getFileCount(); + + return $data; + } + + /** + * @inheritdoc + */ + public function __unserialize($unserializedArr) + { + parent::__unserialize($unserializedArr); + + if (isset($unserializedArr['fileCount'])) { + $this->fileCount((int)$unserializedArr['fileCount']); + } + } +} diff --git a/tests/codeception/unit/FileUploadBatchTest.php b/tests/codeception/unit/FileUploadBatchTest.php new file mode 100644 index 00000000..13e14c08 --- /dev/null +++ b/tests/codeception/unit/FileUploadBatchTest.php @@ -0,0 +1,319 @@ +db->createCommand()->truncateTable('queue')->execute(); + + // The folders are created while nothing is executed, so that their own content created + // notifications cannot interfere with the assertions below. + $this->useDelayingQueue(); + + $space = Space::findOne(self::SPACE_ID); + $this->folder = Folder::initRoot($space); + $this->assertInstanceOf(Folder::class, $this->folder); + + $this->uploaderId = $this->becomeUser(self::UPLOADER)->id; + } + + /** + * The default test driver is Instant, which runs jobs right away and ignores delay(). The + * MySQL driver stores them instead, which is what a real installation uses and what lets a + * whole upload be collected before the announcement job runs. + */ + private function useDelayingQueue(): void + { + Yii::$app->set('queue', ['class' => MySQL::class]); + } + + private function useInstantQueue(): void + { + Yii::$app->set('queue', ['class' => Instant::class]); + } + + /** + * @return File[] + */ + private function upload(int $count, ?Folder $folder = null): array + { + $folder ??= $this->folder; + $files = []; + + for ($i = 1; $i <= $count; $i++) { + // The same entry point the upload action uses + $file = $folder->addUploadedFile(new UploadedFile([ + 'name' => 'batch-test-' . $i . '-' . uniqid('', true) . '.txt', + 'size' => 1024, + 'type' => 'text/plain', + ])); + + $this->assertFalse($file->hasErrors(), 'File ' . $i . ' could not be saved'); + $this->assertFalse($file->isNewRecord, 'File ' . $i . ' was not persisted'); + + $files[] = $file; + } + + return $files; + } + + private function batch(?Folder $folder = null, ?int $userId = null): FileUploadBatch + { + return FileUploadBatch::load(($folder ?? $this->folder)->id, $userId ?? $this->uploaderId); + } + + private function countQueuedBatchJobs(): int + { + return (int)Yii::$app->db + ->createCommand('SELECT COUNT(*) FROM queue WHERE job LIKE :job', [':job' => '%SendFileUploadNotification%']) + ->queryScalar(); + } + + private function countNotifications(): int + { + return (int)Notification::find()->where(['class' => FilesUploaded::class])->count(); + } + + /** + * Lets the quiet period expire without waiting for it. + */ + private function expireQuietPeriod(): void + { + $batch = $this->batch(); + $batch->firstAt = time() - 100000; + $batch->lastAt = time() - 100000; + $batch->save(); + + $this->assertSame(0, $this->batch()->getRemainingDelay()); + } + + private function runBatchJob(int $attempt = 0): void + { + (new SendFileUploadNotification([ + 'folderId' => $this->folder->id, + 'userId' => $this->uploaderId, + 'attempt' => $attempt, + ]))->run(); + } + + private function cfilesModule(): Module + { + return Yii::$app->getModule('cfiles'); + } + + public function testUploadedFileDoesNotNotifyOnItsOwn() + { + $files = $this->upload(3); + + foreach ($files as $file) { + $this->assertHasNoNotification(ContentCreated::class, $file); + } + + $this->assertSame(0, $this->countNotifications(), 'Nothing may be announced before the quiet period expired'); + } + + public function testUploadsAreCollectedInASingleBatch() + { + $this->upload(5); + + $batch = $this->batch(); + $this->assertFalse($batch->isEmpty()); + $this->assertSame(5, $batch->count); + $this->assertSame($this->folder->id, $batch->folderId); + $this->assertSame($this->uploaderId, $batch->userId); + } + + public function testOnlyTheFirstUploadSchedulesAJob() + { + $this->upload(5); + + $this->assertSame(1, $this->countQueuedBatchJobs(), '5 uploads must not queue 5 jobs'); + + $delay = (int)Yii::$app->db + ->createCommand('SELECT delay FROM queue WHERE job LIKE :job', [':job' => '%SendFileUploadNotification%']) + ->queryScalar(); + $this->assertSame(600, $delay, 'The job must be delayed by the configured 10 minutes'); + } + + public function testWholeUploadIsAnnouncedByOneNotificationAndOneMail() + { + $this->upload(5); + $this->expireQuietPeriod(); + + // From here on the notification targets have to run, as they would in a queue worker + $this->useInstantQueue(); + $this->runBatchJob(); + + $this->assertSame(1, $this->countNotifications(), '5 uploaded files must result in exactly one notification'); + $this->assertMailSent(1); + + $notification = Notification::find()->where(['class' => FilesUploaded::class])->one(); + $this->assertSame('{"fileCount":5}', $notification->payload); + $this->assertSame(User::findOne(['username' => self::UPLOADER])->id, $notification->originator_user_id); + + $this->assertTrue($this->batch()->isEmpty(), 'The batch must be closed after being announced'); + } + + public function testAnnouncedCountIsKeptWhenTheNotificationIsRenderedAgain() + { + $this->upload(4); + $this->expireQuietPeriod(); + $this->useInstantQueue(); + $this->runBatchJob(); + + // The count only survives in the stored payload, the notification list re-renders from it + $notification = Notification::find()->where(['class' => FilesUploaded::class])->one(); + $rendered = $notification->getBaseModel(); + $rendered->getViewParams(); + + $this->assertSame(4, $rendered->getFileCount()); + $this->assertStringContainsString('4 files', $rendered->html()); + } + + public function testASingleUploadIsAnnouncedInSingular() + { + $this->upload(1); + $this->expireQuietPeriod(); + $this->useInstantQueue(); + $this->runBatchJob(); + + $notification = Notification::find()->where(['class' => FilesUploaded::class])->one(); + $rendered = $notification->getBaseModel(); + $rendered->getViewParams(); + + $html = $rendered->html(); + $this->assertStringContainsString('a file', $html); + $this->assertStringNotContainsString('1 files', $html); + } + + public function testEveryUploadRestartsTheQuietPeriod() + { + $this->upload(1); + + $batch = $this->batch(); + $batch->lastAt = time() - 550; + $batch->save(); + $this->assertLessThanOrEqual(50, $this->batch()->getRemainingDelay()); + + $this->upload(1); + + $this->assertGreaterThan(500, $this->batch()->getRemainingDelay(), 'A further upload must restart the quiet period'); + } + + public function testOngoingUploadsCannotPostponeTheNotificationForever() + { + $this->upload(1); + + $batch = $this->batch(); + // Still being uploaded into, but running since longer than the hard limit + $batch->firstAt = time() - (FileUploadBatch::getDelay() * FileUploadBatch::MAX_POSTPONE_FACTOR) - 10; + $batch->lastAt = time(); + $batch->save(); + + $this->assertSame(0, $this->batch()->getRemainingDelay()); + } + + public function testJobRequeuesItselfWhileTheBatchIsNotDue() + { + $this->upload(2); + $this->assertSame(1, $this->countQueuedBatchJobs()); + + $this->runBatchJob(); + + $this->assertSame(2, $this->countQueuedBatchJobs(), 'A job running too early must queue a new one'); + $this->assertSame(0, $this->countNotifications()); + $this->assertFalse($this->batch()->isEmpty(), 'The batch must stay open'); + } + + public function testJobDoesNothingWithoutAnOpenBatch() + { + $this->assertTrue($this->batch()->isEmpty()); + + $this->useInstantQueue(); + $this->runBatchJob(); + + $this->assertSame(0, $this->countNotifications()); + $this->assertMailSent(0); + } + + public function testQuietPeriodIsTakenFromTheModuleConfiguration() + { + $this->assertSame(10, $this->cfilesModule()->uploadNotificationDelay); + $this->assertSame(600, FileUploadBatch::getDelay()); + + $this->cfilesModule()->uploadNotificationDelay = 30; + $this->assertSame(1800, FileUploadBatch::getDelay()); + + $this->cfilesModule()->uploadNotificationDelay = 0; + $this->assertSame(0, FileUploadBatch::getDelay()); + + $this->cfilesModule()->uploadNotificationDelay = 10; + } + + public function testBatchesOfDifferentFoldersAreIndependent() + { + $other = $this->folder->newFolder('Other', 'Other folder'); + $this->assertTrue($other->save()); + + $this->upload(3); + $this->upload(2, $other); + + $this->assertSame(3, $this->batch()->count); + $this->assertSame(2, $this->batch($other)->count); + $this->assertSame(2, $this->countQueuedBatchJobs(), 'Each folder gets its own job'); + } + + public function testBatchesOfDifferentUsersAreIndependent() + { + $this->upload(3); + + $otherId = $this->becomeUser('Admin')->id; + $this->upload(1); + + $this->assertSame(3, $this->batch(null, $this->uploaderId)->count); + $this->assertSame(1, $this->batch(null, $otherId)->count); + } +} diff --git a/widgets/views/fileListMenu.php b/widgets/views/fileListMenu.php index 28f451cf..addfcb5f 100644 --- a/widgets/views/fileListMenu.php +++ b/widgets/views/fileListMenu.php @@ -8,6 +8,7 @@ use humhub\modules\file\widgets\UploadButton; use humhub\modules\file\widgets\UploadInput; use humhub\widgets\bootstrap\Button; +use humhub\widgets\bootstrap\Link; use humhub\widgets\modal\ModalButton; /* @var $folder Folder */ @@ -31,8 +32,8 @@ 'contentContainer' => $contentContainer, ]);?> - parentFolder) : ?> - parentFolder->getUrl())->left()->setText(''); ?> + parentFolder) : ?> + parentFolder->getUrl(), '')->left() ?> @@ -48,11 +49,11 @@ ->icon('fa-folder') ?> isRoot()): ?>