summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.htaccess11
-rw-r--r--apps/dav/lib/BackgroundJob/UserStatusAutomation.php17
-rw-r--r--apps/dav/tests/unit/BackgroundJob/UserStatusAutomationTest.php204
-rw-r--r--apps/files_sharing/l10n/de_DE.js4
-rw-r--r--apps/files_sharing/l10n/de_DE.json4
-rw-r--r--apps/files_sharing/l10n/pl.js1
-rw-r--r--apps/files_sharing/l10n/pl.json1
-rw-r--r--apps/settings/l10n/el.js18
-rw-r--r--apps/settings/l10n/el.json18
-rw-r--r--apps/settings/l10n/fr.js2
-rw-r--r--apps/settings/l10n/fr.json2
-rw-r--r--lib/composer/composer/autoload_classmap.php3
-rw-r--r--lib/composer/composer/autoload_static.php3
-rw-r--r--lib/private/App/AppManager.php29
-rw-r--r--lib/private/Server.php1
-rw-r--r--lib/private/SystemTag/SystemTagObjectMapper.php16
-rw-r--r--lib/private/legacy/OC_App.php6
-rw-r--r--lib/public/App/Events/AppDisableEvent.php51
-rw-r--r--lib/public/App/Events/AppEnableEvent.php62
-rw-r--r--lib/public/App/Events/AppUpdateEvent.php51
-rw-r--r--tests/lib/App/AppManagerTest.php28
-rw-r--r--tests/lib/AppTest.php4
-rw-r--r--tests/lib/SystemTag/SystemTagObjectMapperTest.php21
23 files changed, 513 insertions, 44 deletions
diff --git a/.htaccess b/.htaccess
index dd0fce231e8..956e29ea7c4 100644
--- a/.htaccess
+++ b/.htaccess
@@ -41,11 +41,12 @@
# Add cache control for static resources
<FilesMatch "\.(css|js|svg|gif|png|jpg|ico|wasm|tflite)$">
- Header set Cache-Control "max-age=15778463"
- </FilesMatch>
-
- <FilesMatch "\.(css|js|svg|gif|png|jpg|ico|wasm|tflite)(\?v=.*)?$">
- Header set Cache-Control "max-age=15778463, immutable"
+ <If "%{QUERY_STRING} =~ /(^|&)v=/">
+ Header set Cache-Control "max-age=15778463, immutable"
+ </If>
+ <Else>
+ Header set Cache-Control "max-age=15778463"
+ </Else>
</FilesMatch>
# Let browsers cache WOFF files for a week
diff --git a/apps/dav/lib/BackgroundJob/UserStatusAutomation.php b/apps/dav/lib/BackgroundJob/UserStatusAutomation.php
index 8edc28c9a69..43fccbf233e 100644
--- a/apps/dav/lib/BackgroundJob/UserStatusAutomation.php
+++ b/apps/dav/lib/BackgroundJob/UserStatusAutomation.php
@@ -92,7 +92,7 @@ class UserStatusAutomation extends TimedJob {
$isCurrentlyAvailable = false;
$nextPotentialToggles = [];
- $now = new \DateTime('now');
+ $now = $this->time->getDateTime();
$lastMidnight = (clone $now)->setTime(0, 0);
$vObject = Reader::read($property);
@@ -105,9 +105,16 @@ class UserStatusAutomation extends TimedJob {
foreach ($availables as $available) {
/** @var Available $available */
if ($available->name === 'AVAILABLE') {
- /** @var \DateTimeInterface $effectiveStart */
- /** @var \DateTimeInterface $effectiveEnd */
- [$effectiveStart, $effectiveEnd] = $available->getEffectiveStartEnd();
+ /** @var \DateTimeImmutable $originalStart */
+ /** @var \DateTimeImmutable $originalEnd */
+ [$originalStart, $originalEnd] = $available->getEffectiveStartEnd();
+
+ // Little shenanigans to fix the automation on the day the rules were adjusted
+ // Otherwise the $originalStart would match rules for Thursdays on a Friday, etc.
+ // So we simply wind back a week and then fastForward to the next occurrence
+ // since today's midnight, which then also accounts for the week days.
+ $effectiveStart = \DateTime::createFromImmutable($originalStart)->sub(new \DateInterval('P7D'));
+ $effectiveEnd = \DateTime::createFromImmutable($originalEnd)->sub(new \DateInterval('P7D'));
try {
$it = new RRuleIterator((string) $available->RRULE, $effectiveStart);
@@ -150,8 +157,10 @@ class UserStatusAutomation extends TimedJob {
$this->setLastRunToNextToggleTime($userId, $nextAutomaticToggle - 1);
if ($isCurrentlyAvailable) {
+ $this->logger->debug('User is currently available, reverting DND status if applicable');
$this->manager->revertUserStatus($userId, IUserStatus::MESSAGE_AVAILABILITY, IUserStatus::DND);
} else {
+ $this->logger->debug('User is currently NOT available, reverting call status if applicable and then setting DND');
// The DND status automation is more important than the "Away - In call" so we also restore that one if it exists.
$this->manager->revertUserStatus($userId, IUserStatus::MESSAGE_CALL, IUserStatus::AWAY);
$this->manager->setUserStatus($userId, IUserStatus::MESSAGE_AVAILABILITY, IUserStatus::DND, true);
diff --git a/apps/dav/tests/unit/BackgroundJob/UserStatusAutomationTest.php b/apps/dav/tests/unit/BackgroundJob/UserStatusAutomationTest.php
new file mode 100644
index 00000000000..59438c7cd28
--- /dev/null
+++ b/apps/dav/tests/unit/BackgroundJob/UserStatusAutomationTest.php
@@ -0,0 +1,204 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * @copyright Copyright (c) 2023 Joas Schilling <coding@schilljs.com>
+ *
+ * @author Joas Schilling <coding@schilljs.com>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace OCA\DAV\Tests\unit\BackgroundJob;
+
+use OCA\DAV\BackgroundJob\UserStatusAutomation;
+use OCP\AppFramework\Utility\ITimeFactory;
+use OCP\BackgroundJob\IJobList;
+use OCP\IConfig;
+use OCP\UserStatus\IManager;
+use OCP\UserStatus\IUserStatus;
+use PHPUnit\Framework\MockObject\MockObject;
+use Psr\Log\LoggerInterface;
+use Test\TestCase;
+
+/**
+ * @group DB
+ */
+class UserStatusAutomationTest extends TestCase {
+
+ protected MockObject|ITimeFactory $time;
+ protected MockObject|IJobList $jobList;
+ protected MockObject|LoggerInterface $logger;
+ protected MockObject|IManager $statusManager;
+ protected MockObject|IConfig $config;
+
+ protected function setUp(): void {
+ parent::setUp();
+
+ $this->time = $this->createMock(ITimeFactory::class);
+ $this->jobList = $this->createMock(IJobList::class);
+ $this->logger = $this->createMock(LoggerInterface::class);
+ $this->statusManager = $this->createMock(IManager::class);
+ $this->config = $this->createMock(IConfig::class);
+
+ }
+
+ protected function getAutomationMock(array $methods): MockObject|UserStatusAutomation {
+ if (empty($methods)) {
+ return new UserStatusAutomation(
+ $this->time,
+ \OC::$server->getDatabaseConnection(),
+ $this->jobList,
+ $this->logger,
+ $this->statusManager,
+ $this->config,
+ );
+ }
+
+ return $this->getMockBuilder(UserStatusAutomation::class)
+ ->setConstructorArgs([
+ $this->time,
+ \OC::$server->getDatabaseConnection(),
+ $this->jobList,
+ $this->logger,
+ $this->statusManager,
+ $this->config,
+ ])
+ ->setMethods($methods)
+ ->getMock();
+ }
+
+ public function dataRun(): array {
+ return [
+ ['20230217', '2023-02-24 10:49:36.613834', true],
+ ['20230224', '2023-02-24 10:49:36.613834', true],
+ ['20230217', '2023-02-24 13:58:24.479357', false],
+ ['20230224', '2023-02-24 13:58:24.479357', false],
+ ];
+ }
+
+ /**
+ * @dataProvider dataRun
+ */
+ public function testRun(string $ruleDay, string $currentTime, bool $isAvailable): void {
+ $this->config->method('getUserValue')
+ ->with('user', 'dav', 'user_status_automation', 'no')
+ ->willReturn('yes');
+
+ $this->time->method('getDateTime')
+ ->willReturn(new \DateTime($currentTime, new \DateTimeZone('UTC')));
+
+ $automation = $this->getAutomationMock(['getAvailabilityFromPropertiesTable']);
+ $automation->method('getAvailabilityFromPropertiesTable')
+ ->with('user')
+ ->willReturn('BEGIN:VCALENDAR
+PRODID:Nextcloud DAV app
+BEGIN:VTIMEZONE
+TZID:Europe/Berlin
+BEGIN:STANDARD
+TZNAME:CET
+TZOFFSETFROM:+0200
+TZOFFSETTO:+0100
+DTSTART:19701025T030000
+RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU
+END:STANDARD
+BEGIN:DAYLIGHT
+TZNAME:CEST
+TZOFFSETFROM:+0100
+TZOFFSETTO:+0200
+DTSTART:19700329T020000
+RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU
+END:DAYLIGHT
+END:VTIMEZONE
+BEGIN:VAVAILABILITY
+BEGIN:AVAILABLE
+DTSTART;TZID=Europe/Berlin:' . $ruleDay . 'T090000
+DTEND;TZID=Europe/Berlin:' . $ruleDay . 'T170000
+UID:3e6feeec-8e00-4265-b822-b73174e8b39f
+RRULE:FREQ=WEEKLY;BYDAY=TH
+END:AVAILABLE
+BEGIN:AVAILABLE
+DTSTART;TZID=Europe/Berlin:' . $ruleDay . 'T090000
+DTEND;TZID=Europe/Berlin:' . $ruleDay . 'T120000
+UID:8a634e99-07cf-443b-b480-005a0e1db323
+RRULE:FREQ=WEEKLY;BYDAY=FR
+END:AVAILABLE
+END:VAVAILABILITY
+END:VCALENDAR');
+
+ if ($isAvailable) {
+ $this->statusManager->expects($this->once())
+ ->method('revertUserStatus')
+ ->with('user', IUserStatus::MESSAGE_AVAILABILITY, IUserStatus::DND);
+ } else {
+ $this->statusManager->expects($this->once())
+ ->method('revertUserStatus')
+ ->with('user', IUserStatus::MESSAGE_CALL, IUserStatus::AWAY);
+ $this->statusManager->expects($this->once())
+ ->method('setUserStatus')
+ ->with('user', IUserStatus::MESSAGE_AVAILABILITY, IUserStatus::DND, true);
+ }
+
+ self::invokePrivate($automation, 'run', [['userId' => 'user']]);
+ }
+
+ public function testRunNoMoreAvailabilityDefined(): void {
+ $this->config->method('getUserValue')
+ ->with('user', 'dav', 'user_status_automation', 'no')
+ ->willReturn('yes');
+
+ $this->time->method('getDateTime')
+ ->willReturn(new \DateTime('2023-02-24 13:58:24.479357', new \DateTimeZone('UTC')));
+
+ $automation = $this->getAutomationMock(['getAvailabilityFromPropertiesTable']);
+ $automation->method('getAvailabilityFromPropertiesTable')
+ ->with('user')
+ ->willReturn('BEGIN:VCALENDAR
+PRODID:Nextcloud DAV app
+BEGIN:VTIMEZONE
+TZID:Europe/Berlin
+BEGIN:STANDARD
+TZNAME:CET
+TZOFFSETFROM:+0200
+TZOFFSETTO:+0100
+DTSTART:19701025T030000
+RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU
+END:STANDARD
+BEGIN:DAYLIGHT
+TZNAME:CEST
+TZOFFSETFROM:+0100
+TZOFFSETTO:+0200
+DTSTART:19700329T020000
+RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU
+END:DAYLIGHT
+END:VTIMEZONE
+BEGIN:VAVAILABILITY
+END:VAVAILABILITY
+END:VCALENDAR');
+
+ $this->statusManager->expects($this->once())
+ ->method('revertUserStatus')
+ ->with('user', IUserStatus::MESSAGE_AVAILABILITY, IUserStatus::DND);
+
+ $this->jobList->expects($this->once())
+ ->method('remove')
+ ->with(UserStatusAutomation::class, ['userId' => 'user']);
+
+ self::invokePrivate($automation, 'run', [['userId' => 'user']]);
+ }
+}
diff --git a/apps/files_sharing/l10n/de_DE.js b/apps/files_sharing/l10n/de_DE.js
index c6fb385ca29..81ac2ce193e 100644
--- a/apps/files_sharing/l10n/de_DE.js
+++ b/apps/files_sharing/l10n/de_DE.js
@@ -120,7 +120,7 @@ OC.L10N.register(
"You cannot share to a Circle if the app is not enabled" : "Sie können nichts mit einem Kreis teilen, wenn die App nicht aktiviert ist",
"Please specify a valid circle" : "Bitte einen gültigen Kreis angeben",
"Sharing %s failed because the back end does not support room shares" : "Freigabe von %s fehlgeschlagen, da das Backend die Freigabe von Räumen nicht unterstützt",
- "Sharing %s failed because the back end does not support sciencemesh shares" : "Freigabe von %s fehlgeschlagen, da das Backend keine Science-Mesh-Freigaben unterstützt",
+ "Sharing %s failed because the back end does not support sciencemesh shares" : "Freigabe von %s fehlgeschlagen, da das Backend keine ScienceMesh-Freigaben unterstützt",
"Unknown share type" : "Unbekannter Freigabetyp",
"Not a directory" : "Kein Verzeichnis",
"Could not lock node" : "Knotenpunkt konnte nicht gesperrt werden",
@@ -226,7 +226,7 @@ OC.L10N.register(
"Circle" : "Kreis",
"Talk conversation" : "Talk-Unterhaltung",
"Deck board" : "Deck-Board",
- "Science Mesh" : "Science-Mesh",
+ "Science Mesh" : "ScienceMesh",
"on {server}" : "auf {server}",
"Others with access" : "Andere mit Zugriff",
"No other users with access found" : "Keine anderen Benutzer mit Zugriff gefunden",
diff --git a/apps/files_sharing/l10n/de_DE.json b/apps/files_sharing/l10n/de_DE.json
index ec68771cc99..4d323d38d2e 100644
--- a/apps/files_sharing/l10n/de_DE.json
+++ b/apps/files_sharing/l10n/de_DE.json
@@ -118,7 +118,7 @@
"You cannot share to a Circle if the app is not enabled" : "Sie können nichts mit einem Kreis teilen, wenn die App nicht aktiviert ist",
"Please specify a valid circle" : "Bitte einen gültigen Kreis angeben",
"Sharing %s failed because the back end does not support room shares" : "Freigabe von %s fehlgeschlagen, da das Backend die Freigabe von Räumen nicht unterstützt",
- "Sharing %s failed because the back end does not support sciencemesh shares" : "Freigabe von %s fehlgeschlagen, da das Backend keine Science-Mesh-Freigaben unterstützt",
+ "Sharing %s failed because the back end does not support sciencemesh shares" : "Freigabe von %s fehlgeschlagen, da das Backend keine ScienceMesh-Freigaben unterstützt",
"Unknown share type" : "Unbekannter Freigabetyp",
"Not a directory" : "Kein Verzeichnis",
"Could not lock node" : "Knotenpunkt konnte nicht gesperrt werden",
@@ -224,7 +224,7 @@
"Circle" : "Kreis",
"Talk conversation" : "Talk-Unterhaltung",
"Deck board" : "Deck-Board",
- "Science Mesh" : "Science-Mesh",
+ "Science Mesh" : "ScienceMesh",
"on {server}" : "auf {server}",
"Others with access" : "Andere mit Zugriff",
"No other users with access found" : "Keine anderen Benutzer mit Zugriff gefunden",
diff --git a/apps/files_sharing/l10n/pl.js b/apps/files_sharing/l10n/pl.js
index d2aaeba4005..791acd7ba63 100644
--- a/apps/files_sharing/l10n/pl.js
+++ b/apps/files_sharing/l10n/pl.js
@@ -120,6 +120,7 @@ OC.L10N.register(
"You cannot share to a Circle if the app is not enabled" : "Nie możesz udostępnić w Kręgach, jeśli aplikacja jest wyłączona",
"Please specify a valid circle" : "Podaj prawidłowy krąg",
"Sharing %s failed because the back end does not support room shares" : "Udostępnienie %s nie powiodło się, ponieważ zaplecze nie obsługuje udostępnień pokoju",
+ "Sharing %s failed because the back end does not support sciencemesh shares" : "Udostępnienie %s nie powiodło się, ponieważ oprogramowanie nie obsługuje udostępnień Science Mesh",
"Unknown share type" : "Nieznany typ udostępnienia",
"Not a directory" : "Nie jest katalogiem",
"Could not lock node" : "Nie można zablokować powiązania",
diff --git a/apps/files_sharing/l10n/pl.json b/apps/files_sharing/l10n/pl.json
index 55d27ccb005..a4c2836ada9 100644
--- a/apps/files_sharing/l10n/pl.json
+++ b/apps/files_sharing/l10n/pl.json
@@ -118,6 +118,7 @@
"You cannot share to a Circle if the app is not enabled" : "Nie możesz udostępnić w Kręgach, jeśli aplikacja jest wyłączona",
"Please specify a valid circle" : "Podaj prawidłowy krąg",
"Sharing %s failed because the back end does not support room shares" : "Udostępnienie %s nie powiodło się, ponieważ zaplecze nie obsługuje udostępnień pokoju",
+ "Sharing %s failed because the back end does not support sciencemesh shares" : "Udostępnienie %s nie powiodło się, ponieważ oprogramowanie nie obsługuje udostępnień Science Mesh",
"Unknown share type" : "Nieznany typ udostępnienia",
"Not a directory" : "Nie jest katalogiem",
"Could not lock node" : "Nie można zablokować powiązania",
diff --git a/apps/settings/l10n/el.js b/apps/settings/l10n/el.js
index b713c3a7552..1294118eaeb 100644
--- a/apps/settings/l10n/el.js
+++ b/apps/settings/l10n/el.js
@@ -49,7 +49,10 @@ OC.L10N.register(
"Remote wipe has finished on %1$s" : "Η απομακρυσμένη εκκαθάριση τελείωσε στις %1$s",
"Your <strong>password</strong> or <strong>email</strong> was modified" : "Ο <strong>κωδικός</strong> ή το <strong>email</strong> σας, τροποποιήθηκαν",
"Apps" : "Εφαρμογές",
+ "Could not remove app." : "Αδυναμία αφαίρεσης εφαρμογής.",
+ "Could not update app." : "Αδυναμία ενημέρωσης εφαρμογής.",
"Wrong password" : "Λάθος κωδικός πρόσβασης",
+ "Unable to change personal password" : "Αδυναμία αλλαγής προσωπικού κωδικού πρόσβασης",
"Saved" : "Αποθηκεύτηκαν",
"No user supplied" : "Δεν εισήχθη χρήστης",
"Authentication error" : "Σφάλμα πιστοποίησης",
@@ -164,6 +167,7 @@ OC.L10N.register(
"Disable all" : "Απενεργοποίηση όλων",
"Enable all" : "Ενεργοποίηση όλων",
"_%n app has an update available_::_%n apps have an update available_" : ["%n εφαρμογή έχει διαθέσιμη αναβάθμιση","%n εφαρμογές έχουν διαθέσιμη αναβάθμιση"],
+ "_Update_::_Update all_" : ["Ενημέρωση","Ενημέρωση όλων"],
"Marked for remote wipe" : "Επισημάνθηκε για απομακρυσμένη εκκαθάριση",
"Device settings" : "Ρυθμίσεις συσκευής",
"Allow filesystem access" : "Επιτρέπεται η πρόσβαση στο σύστημα αρχείων",
@@ -209,7 +213,10 @@ OC.L10N.register(
"Could not copy app password. Please copy it manually." : "Ο κωδικός εφαρμογής δεν μπορεί να αντιγραφεί. Παρακαλώ αντιγράψτε χειροκίνητα.",
"For the server to work properly, it's important to configure background jobs correctly. Cron is the recommended setting. Please see the documentation for more information." : "Για να λειτουργεί σωστά ο διακομιστής, είναι σημαντικό να διαμορφώσετε σωστά τις εργασίες παρασκηνίου. Το Cron είναι η προτεινόμενη ρύθμιση. Δείτε την τεκμηρίωση για περισσότερες πληροφορίες.",
"Last job execution ran {time}. Something seems wrong." : "Η τελευταία εργασία εκτελέστηκε {time}. Κάτι φαίνεται λάθος.",
+ "Background job did not run yet!" : "Η εργασία παρασκηνίου δεν εκτελέστηκε ακόμη!",
+ "AJAX" : "AJAX",
"Execute one task with each page loaded. Use case: Single user instance." : "Εκτέλεση μιας εργασίας με κάθε σελίδα που φορτώνεται. Περίπτωση χρήσης: Εγκατάσταση με έναν χρήστη.",
+ "Webcron" : "Webcron",
"cron.php is registered at a webcron service to call cron.php every 5 minutes over HTTP. Use case: Very small instance (1–5 users depending on the usage)." : "Το cron.php είναι εγγεγραμμένο σε μια υπηρεσία webcron για να καλεί το cron.php κάθε 5 λεπτά μέσω HTTP. Περίπτωση χρήσης: Πολύ μικρή εγκατάσταση (1–5 χρήστες ανάλογα με τη χρήση).",
"Cron (Recommended)" : "Cron (Συνιστάται)",
"To run this you need the PHP POSIX extension. See {linkstart}PHP documentation{linkend} for more details." : "Για να τρέξετε αυτό χρειάζεστε την επέκταση PHP POSIX. Δείτε {linkstart} PHP τεκμηρίωση {linked} για περισσότερες λεπτομέρειες.",
@@ -236,10 +243,21 @@ OC.L10N.register(
"Current password" : "Τρέχον συνθηματικό",
"New password" : "Νέο συνθηματικό",
"Change password" : "Αλλαγή συνθηματικού",
+ "Your profile picture" : "Η εικόνα του προφίλ σας",
+ "Upload profile picture" : "Μεταφόρτωση εικόνας προφίλ",
+ "Choose profile picture from files" : "Επιλογή εικόνας προφίλ από το αρχείο",
+ "Remove profile picture" : "Αφαίρεση εικόνας προφίλ",
"png or jpg, max. 20 MB" : "png ή jpg, μεγ. 20 MB",
"Picture provided by original account" : "Φωτογραφία που παρέχεται από τον πρωτότυπο λογαριασμό",
"Cancel" : "Άκυρο",
+ "Set as profile picture" : "Ορισμός ως εικόνας προφίλ",
"Please note that it can take up to 24 hours for your profile picture to be updated everywhere." : "Λάβετε υπόψη ότι μπορεί να χρειαστούν έως και 24 ώρες για να ενημερωθεί παντού η εικόνα του προφίλ σας.",
+ "Choose your profile picture" : "Επιλέξτε την εικόνα προφίλ σας",
+ "Please select a valid png or jpg file" : "Παρακαλούμε επιλέξτε συμβατό αρχείο png ή jpg",
+ "Error setting profile picture" : "Σφάλμα ορισμού εικόνας προφίλ",
+ "Error cropping profile picture" : "Σφάλμα περικοπής εικόνας προφίλ",
+ "Error saving profile picture" : "Σφάλμα αποθήκευσης εικόνας προφίλ",
+ "Error removing profile picture" : "Σφάλμα αφαίρεσης εικόνας προφίλ",
"Your biography" : "Το βιογραφικό σας",
"Details" : "Λεπτομέρειες",
"You are a member of the following groups:" : "Είστε μέλος των ακόλουθων ομάδων:",
diff --git a/apps/settings/l10n/el.json b/apps/settings/l10n/el.json
index 9c191490f42..c6953027c2a 100644
--- a/apps/settings/l10n/el.json
+++ b/apps/settings/l10n/el.json
@@ -47,7 +47,10 @@
"Remote wipe has finished on %1$s" : "Η απομακρυσμένη εκκαθάριση τελείωσε στις %1$s",
"Your <strong>password</strong> or <strong>email</strong> was modified" : "Ο <strong>κωδικός</strong> ή το <strong>email</strong> σας, τροποποιήθηκαν",
"Apps" : "Εφαρμογές",
+ "Could not remove app." : "Αδυναμία αφαίρεσης εφαρμογής.",
+ "Could not update app." : "Αδυναμία ενημέρωσης εφαρμογής.",
"Wrong password" : "Λάθος κωδικός πρόσβασης",
+ "Unable to change personal password" : "Αδυναμία αλλαγής προσωπικού κωδικού πρόσβασης",
"Saved" : "Αποθηκεύτηκαν",
"No user supplied" : "Δεν εισήχθη χρήστης",
"Authentication error" : "Σφάλμα πιστοποίησης",
@@ -162,6 +165,7 @@
"Disable all" : "Απενεργοποίηση όλων",
"Enable all" : "Ενεργοποίηση όλων",
"_%n app has an update available_::_%n apps have an update available_" : ["%n εφαρμογή έχει διαθέσιμη αναβάθμιση","%n εφαρμογές έχουν διαθέσιμη αναβάθμιση"],
+ "_Update_::_Update all_" : ["Ενημέρωση","Ενημέρωση όλων"],
"Marked for remote wipe" : "Επισημάνθηκε για απομακρυσμένη εκκαθάριση",
"Device settings" : "Ρυθμίσεις συσκευής",
"Allow filesystem access" : "Επιτρέπεται η πρόσβαση στο σύστημα αρχείων",
@@ -207,7 +211,10 @@
"Could not copy app password. Please copy it manually." : "Ο κωδικός εφαρμογής δεν μπορεί να αντιγραφεί. Παρακαλώ αντιγράψτε χειροκίνητα.",
"For the server to work properly, it's important to configure background jobs correctly. Cron is the recommended setting. Please see the documentation for more information." : "Για να λειτουργεί σωστά ο διακομιστής, είναι σημαντικό να διαμορφώσετε σωστά τις εργασίες παρασκηνίου. Το Cron είναι η προτεινόμενη ρύθμιση. Δείτε την τεκμηρίωση για περισσότερες πληροφορίες.",
"Last job execution ran {time}. Something seems wrong." : "Η τελευταία εργασία εκτελέστηκε {time}. Κάτι φαίνεται λάθος.",
+ "Background job did not run yet!" : "Η εργασία παρασκηνίου δεν εκτελέστηκε ακόμη!",
+ "AJAX" : "AJAX",
"Execute one task with each page loaded. Use case: Single user instance." : "Εκτέλεση μιας εργασίας με κάθε σελίδα που φορτώνεται. Περίπτωση χρήσης: Εγκατάσταση με έναν χρήστη.",
+ "Webcron" : "Webcron",
"cron.php is registered at a webcron service to call cron.php every 5 minutes over HTTP. Use case: Very small instance (1–5 users depending on the usage)." : "Το cron.php είναι εγγεγραμμένο σε μια υπηρεσία webcron για να καλεί το cron.php κάθε 5 λεπτά μέσω HTTP. Περίπτωση χρήσης: Πολύ μικρή εγκατάσταση (1–5 χρήστες ανάλογα με τη χρήση).",
"Cron (Recommended)" : "Cron (Συνιστάται)",
"To run this you need the PHP POSIX extension. See {linkstart}PHP documentation{linkend} for more details." : "Για να τρέξετε αυτό χρειάζεστε την επέκταση PHP POSIX. Δείτε {linkstart} PHP τεκμηρίωση {linked} για περισσότερες λεπτομέρειες.",
@@ -234,10 +241,21 @@
"Current password" : "Τρέχον συνθηματικό",
"New password" : "Νέο συνθηματικό",
"Change password" : "Αλλαγή συνθηματικού",
+ "Your profile picture" : "Η εικόνα του προφίλ σας",
+ "Upload profile picture" : "Μεταφόρτωση εικόνας προφίλ",
+ "Choose profile picture from files" : "Επιλογή εικόνας προφίλ από το αρχείο",
+ "Remove profile picture" : "Αφαίρεση εικόνας προφίλ",
"png or jpg, max. 20 MB" : "png ή jpg, μεγ. 20 MB",
"Picture provided by original account" : "Φωτογραφία που παρέχεται από τον πρωτότυπο λογαριασμό",
"Cancel" : "Άκυρο",
+ "Set as profile picture" : "Ορισμός ως εικόνας προφίλ",
"Please note that it can take up to 24 hours for your profile picture to be updated everywhere." : "Λάβετε υπόψη ότι μπορεί να χρειαστούν έως και 24 ώρες για να ενημερωθεί παντού η εικόνα του προφίλ σας.",
+ "Choose your profile picture" : "Επιλέξτε την εικόνα προφίλ σας",
+ "Please select a valid png or jpg file" : "Παρακαλούμε επιλέξτε συμβατό αρχείο png ή jpg",
+ "Error setting profile picture" : "Σφάλμα ορισμού εικόνας προφίλ",
+ "Error cropping profile picture" : "Σφάλμα περικοπής εικόνας προφίλ",
+ "Error saving profile picture" : "Σφάλμα αποθήκευσης εικόνας προφίλ",
+ "Error removing profile picture" : "Σφάλμα αφαίρεσης εικόνας προφίλ",
"Your biography" : "Το βιογραφικό σας",
"Details" : "Λεπτομέρειες",
"You are a member of the following groups:" : "Είστε μέλος των ακόλουθων ομάδων:",
diff --git a/apps/settings/l10n/fr.js b/apps/settings/l10n/fr.js
index 30c84b02811..4eaa5e48c71 100644
--- a/apps/settings/l10n/fr.js
+++ b/apps/settings/l10n/fr.js
@@ -160,7 +160,7 @@ OC.L10N.register(
"Enforced groups" : "Groupes forcés",
"Two-factor authentication is not enforced for members of the following groups." : "L'authentification à deux facteurs n'est pas forcée pour les membres des groupes suivants : ",
"Excluded groups" : "Groupes exclus",
- "When groups are selected/excluded, they use the following logic to determine if a user has 2FA enforced: If no groups are selected, 2FA is enabled for everyone except members of the excluded groups. If groups are selected, 2FA is enabled for all members of these. If a user is both in a selected and excluded group, the selected takes precedence and 2FA is enforced." : "Lorsque des groupes sont forcés/exclus, la logique suivante est utilisée pour déterminer si l'authentification à double facteur (2FA) est imposée à un utilisateur. Si aucun groupe n'est forcé, l'authentification à double facteur est activée pour tous sauf pour les membres des groupes exclus. Si des groupes sont forcés, l'authentification à double facteur est exigée pour tous les membres de ces groupes. Si un utilisateur est à la fois dans un groupe forcé et exclu, c'est le groupe forcé qui prime et l'authentification double facteur est imposée.",
+ "When groups are selected/excluded, they use the following logic to determine if a user has 2FA enforced: If no groups are selected, 2FA is enabled for everyone except members of the excluded groups. If groups are selected, 2FA is enabled for all members of these. If a user is both in a selected and excluded group, the selected takes precedence and 2FA is enforced." : "Lorsque des groupes sont forcés/exclus, la logique suivante est utilisée pour déterminer si l'authentification à double facteur (A2F) est imposée à un utilisateur. Si aucun groupe n'est forcé, l'authentification à double facteur est activée pour tous sauf pour les membres des groupes exclus. Si des groupes sont forcés, l'authentification à double facteur est exigée pour tous les membres de ces groupes. Si un utilisateur est à la fois dans un groupe forcé et exclu, c'est le groupe forcé qui prime et l'authentification double facteur est imposée.",
"Save changes" : "Enregistrer les modifications",
"All" : "Tous",
"Limit app usage to groups" : "Limiter l'utilisation de l'application aux groupes",
diff --git a/apps/settings/l10n/fr.json b/apps/settings/l10n/fr.json
index 2b220cbecb6..ea234cec4e4 100644
--- a/apps/settings/l10n/fr.json
+++ b/apps/settings/l10n/fr.json
@@ -158,7 +158,7 @@
"Enforced groups" : "Groupes forcés",
"Two-factor authentication is not enforced for members of the following groups." : "L'authentification à deux facteurs n'est pas forcée pour les membres des groupes suivants : ",
"Excluded groups" : "Groupes exclus",
- "When groups are selected/excluded, they use the following logic to determine if a user has 2FA enforced: If no groups are selected, 2FA is enabled for everyone except members of the excluded groups. If groups are selected, 2FA is enabled for all members of these. If a user is both in a selected and excluded group, the selected takes precedence and 2FA is enforced." : "Lorsque des groupes sont forcés/exclus, la logique suivante est utilisée pour déterminer si l'authentification à double facteur (2FA) est imposée à un utilisateur. Si aucun groupe n'est forcé, l'authentification à double facteur est activée pour tous sauf pour les membres des groupes exclus. Si des groupes sont forcés, l'authentification à double facteur est exigée pour tous les membres de ces groupes. Si un utilisateur est à la fois dans un groupe forcé et exclu, c'est le groupe forcé qui prime et l'authentification double facteur est imposée.",
+ "When groups are selected/excluded, they use the following logic to determine if a user has 2FA enforced: If no groups are selected, 2FA is enabled for everyone except members of the excluded groups. If groups are selected, 2FA is enabled for all members of these. If a user is both in a selected and excluded group, the selected takes precedence and 2FA is enforced." : "Lorsque des groupes sont forcés/exclus, la logique suivante est utilisée pour déterminer si l'authentification à double facteur (A2F) est imposée à un utilisateur. Si aucun groupe n'est forcé, l'authentification à double facteur est activée pour tous sauf pour les membres des groupes exclus. Si des groupes sont forcés, l'authentification à double facteur est exigée pour tous les membres de ces groupes. Si un utilisateur est à la fois dans un groupe forcé et exclu, c'est le groupe forcé qui prime et l'authentification double facteur est imposée.",
"Save changes" : "Enregistrer les modifications",
"All" : "Tous",
"Limit app usage to groups" : "Limiter l'utilisation de l'application aux groupes",
diff --git a/lib/composer/composer/autoload_classmap.php b/lib/composer/composer/autoload_classmap.php
index ee6117f9b73..1ecd8152c0f 100644
--- a/lib/composer/composer/autoload_classmap.php
+++ b/lib/composer/composer/autoload_classmap.php
@@ -82,6 +82,9 @@ return array(
'OCP\\AppFramework\\Utility\\IControllerMethodReflector' => $baseDir . '/lib/public/AppFramework/Utility/IControllerMethodReflector.php',
'OCP\\AppFramework\\Utility\\ITimeFactory' => $baseDir . '/lib/public/AppFramework/Utility/ITimeFactory.php',
'OCP\\App\\AppPathNotFoundException' => $baseDir . '/lib/public/App/AppPathNotFoundException.php',
+ 'OCP\\App\\Events\\AppDisableEvent' => $baseDir . '/lib/public/App/Events/AppDisableEvent.php',
+ 'OCP\\App\\Events\\AppEnableEvent' => $baseDir . '/lib/public/App/Events/AppEnableEvent.php',
+ 'OCP\\App\\Events\\AppUpdateEvent' => $baseDir . '/lib/public/App/Events/AppUpdateEvent.php',
'OCP\\App\\IAppManager' => $baseDir . '/lib/public/App/IAppManager.php',
'OCP\\App\\ManagerEvent' => $baseDir . '/lib/public/App/ManagerEvent.php',
'OCP\\Authentication\\Events\\AnyLoginFailedEvent' => $baseDir . '/lib/public/Authentication/Events/AnyLoginFailedEvent.php',
diff --git a/lib/composer/composer/autoload_static.php b/lib/composer/composer/autoload_static.php
index 253b9ddbada..2127835e514 100644
--- a/lib/composer/composer/autoload_static.php
+++ b/lib/composer/composer/autoload_static.php
@@ -115,6 +115,9 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2
'OCP\\AppFramework\\Utility\\IControllerMethodReflector' => __DIR__ . '/../../..' . '/lib/public/AppFramework/Utility/IControllerMethodReflector.php',
'OCP\\AppFramework\\Utility\\ITimeFactory' => __DIR__ . '/../../..' . '/lib/public/AppFramework/Utility/ITimeFactory.php',
'OCP\\App\\AppPathNotFoundException' => __DIR__ . '/../../..' . '/lib/public/App/AppPathNotFoundException.php',
+ 'OCP\\App\\Events\\AppDisableEvent' => __DIR__ . '/../../..' . '/lib/public/App/Events/AppDisableEvent.php',
+ 'OCP\\App\\Events\\AppEnableEvent' => __DIR__ . '/../../..' . '/lib/public/App/Events/AppEnableEvent.php',
+ 'OCP\\App\\Events\\AppUpdateEvent' => __DIR__ . '/../../..' . '/lib/public/App/Events/AppUpdateEvent.php',
'OCP\\App\\IAppManager' => __DIR__ . '/../../..' . '/lib/public/App/IAppManager.php',
'OCP\\App\\ManagerEvent' => __DIR__ . '/../../..' . '/lib/public/App/ManagerEvent.php',
'OCP\\Authentication\\Events\\AnyLoginFailedEvent' => __DIR__ . '/../../..' . '/lib/public/Authentication/Events/AnyLoginFailedEvent.php',
diff --git a/lib/private/App/AppManager.php b/lib/private/App/AppManager.php
index d14f0a2644e..0a89711f178 100644
--- a/lib/private/App/AppManager.php
+++ b/lib/private/App/AppManager.php
@@ -40,8 +40,11 @@ namespace OC\App;
use OC\AppConfig;
use OCP\App\AppPathNotFoundException;
+use OCP\App\Events\AppDisableEvent;
+use OCP\App\Events\AppEnableEvent;
use OCP\App\IAppManager;
use OCP\App\ManagerEvent;
+use OCP\EventDispatcher\IEventDispatcher;
use OCP\ICacheFactory;
use OCP\IConfig;
use OCP\IGroup;
@@ -80,7 +83,9 @@ class AppManager implements IAppManager {
private $memCacheFactory;
/** @var EventDispatcherInterface */
- private $dispatcher;
+ private $legacyDispatcher;
+
+ private IEventDispatcher $dispatcher;
/** @var LoggerInterface */
private $logger;
@@ -108,13 +113,15 @@ class AppManager implements IAppManager {
AppConfig $appConfig,
IGroupManager $groupManager,
ICacheFactory $memCacheFactory,
- EventDispatcherInterface $dispatcher,
+ EventDispatcherInterface $legacyDispatcher,
+ IEventDispatcher $dispatcher,
LoggerInterface $logger) {
$this->userSession = $userSession;
$this->config = $config;
$this->appConfig = $appConfig;
$this->groupManager = $groupManager;
$this->memCacheFactory = $memCacheFactory;
+ $this->legacyDispatcher = $legacyDispatcher;
$this->dispatcher = $dispatcher;
$this->logger = $logger;
}
@@ -163,7 +170,7 @@ class AppManager implements IAppManager {
}
/**
- * @param \OCP\IGroup $group
+ * @param IGroup $group
* @return array
*/
public function getEnabledAppsForGroup(IGroup $group): array {
@@ -287,7 +294,7 @@ class AppManager implements IAppManager {
* Notice: This actually checks if the app is enabled and not only if it is installed.
*
* @param string $appId
- * @param \OCP\IGroup[]|String[] $groups
+ * @param IGroup[]|String[] $groups
* @return bool
*/
public function isInstalled($appId) {
@@ -320,7 +327,8 @@ class AppManager implements IAppManager {
$this->installedAppsCache[$appId] = 'yes';
$this->appConfig->setValue($appId, 'enabled', 'yes');
- $this->dispatcher->dispatch(ManagerEvent::EVENT_APP_ENABLE, new ManagerEvent(
+ $this->dispatcher->dispatchTyped(new AppEnableEvent($appId));
+ $this->legacyDispatcher->dispatch(ManagerEvent::EVENT_APP_ENABLE, new ManagerEvent(
ManagerEvent::EVENT_APP_ENABLE, $appId
));
$this->clearAppsCache();
@@ -345,7 +353,7 @@ class AppManager implements IAppManager {
* Enable an app only for specific groups
*
* @param string $appId
- * @param \OCP\IGroup[] $groups
+ * @param IGroup[] $groups
* @param bool $forceEnable
* @throws \InvalidArgumentException if app can't be enabled for groups
* @throws AppPathNotFoundException
@@ -363,8 +371,9 @@ class AppManager implements IAppManager {
$this->ignoreNextcloudRequirementForApp($appId);
}
+ /** @var string[] $groupIds */
$groupIds = array_map(function ($group) {
- /** @var \OCP\IGroup $group */
+ /** @var IGroup $group */
return ($group instanceof IGroup)
? $group->getGID()
: $group;
@@ -372,7 +381,8 @@ class AppManager implements IAppManager {
$this->installedAppsCache[$appId] = json_encode($groupIds);
$this->appConfig->setValue($appId, 'enabled', json_encode($groupIds));
- $this->dispatcher->dispatch(ManagerEvent::EVENT_APP_ENABLE_FOR_GROUPS, new ManagerEvent(
+ $this->dispatcher->dispatchTyped(new AppEnableEvent($appId, $groupIds));
+ $this->legacyDispatcher->dispatch(ManagerEvent::EVENT_APP_ENABLE_FOR_GROUPS, new ManagerEvent(
ManagerEvent::EVENT_APP_ENABLE_FOR_GROUPS, $appId, $groups
));
$this->clearAppsCache();
@@ -407,7 +417,8 @@ class AppManager implements IAppManager {
\OC_App::executeRepairSteps($appId, $appData['repair-steps']['uninstall']);
}
- $this->dispatcher->dispatch(ManagerEvent::EVENT_APP_DISABLE, new ManagerEvent(
+ $this->dispatcher->dispatchTyped(new AppDisableEvent($appId));
+ $this->legacyDispatcher->dispatch(ManagerEvent::EVENT_APP_DISABLE, new ManagerEvent(
ManagerEvent::EVENT_APP_DISABLE, $appId
));
$this->clearAppsCache();
diff --git a/lib/private/Server.php b/lib/private/Server.php
index f9fc585e74d..9a4ee0da198 100644
--- a/lib/private/Server.php
+++ b/lib/private/Server.php
@@ -928,6 +928,7 @@ class Server extends ServerContainer implements IServerContainer {
$c->get(IGroupManager::class),
$c->get(ICacheFactory::class),
$c->get(SymfonyAdapter::class),
+ $c->get(IEventDispatcher::class),
$c->get(LoggerInterface::class)
);
});
diff --git a/lib/private/SystemTag/SystemTagObjectMapper.php b/lib/private/SystemTag/SystemTagObjectMapper.php
index 5a09a1754f2..b61a81a1fa7 100644
--- a/lib/private/SystemTag/SystemTagObjectMapper.php
+++ b/lib/private/SystemTag/SystemTagObjectMapper.php
@@ -77,23 +77,25 @@ class SystemTagObjectMapper implements ISystemTagObjectMapper {
->from(self::RELATION_TABLE)
->where($query->expr()->in('objectid', $query->createParameter('objectids')))
->andWhere($query->expr()->eq('objecttype', $query->createParameter('objecttype')))
- ->setParameter('objectids', $objIds, IQueryBuilder::PARAM_STR_ARRAY)
->setParameter('objecttype', $objectType)
->addOrderBy('objectid', 'ASC')
->addOrderBy('systemtagid', 'ASC');
-
+ $chunks = array_chunk($objIds, 900, false);
$mapping = [];
foreach ($objIds as $objId) {
$mapping[$objId] = [];
}
+ foreach ($chunks as $chunk) {
+ $query->setParameter('objectids', $chunk, IQueryBuilder::PARAM_STR_ARRAY);
+ $result = $query->executeQuery();
+ while ($row = $result->fetch()) {
+ $objectId = $row['objectid'];
+ $mapping[$objectId][] = $row['systemtagid'];
+ }
- $result = $query->execute();
- while ($row = $result->fetch()) {
- $objectId = $row['objectid'];
- $mapping[$objectId][] = $row['systemtagid'];
+ $result->closeCursor();
}
- $result->closeCursor();
return $mapping;
}
diff --git a/lib/private/legacy/OC_App.php b/lib/private/legacy/OC_App.php
index 7f51d81d21b..a7887d2bed7 100644
--- a/lib/private/legacy/OC_App.php
+++ b/lib/private/legacy/OC_App.php
@@ -50,9 +50,12 @@ declare(strict_types=1);
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
*/
+
+use OCP\App\Events\AppUpdateEvent;
use OCP\AppFramework\QueryException;
use OCP\App\ManagerEvent;
use OCP\Authentication\IAlternativeLogin;
+use OCP\EventDispatcher\IEventDispatcher;
use OCP\ILogger;
use OCP\Settings\IManager as ISettingsManager;
use OC\AppFramework\Bootstrap\Coordinator;
@@ -1042,6 +1045,7 @@ class OC_App {
$version = \OC_App::getAppVersion($appId);
\OC::$server->getConfig()->setAppValue($appId, 'installed_version', $version);
+ \OC::$server->get(IEventDispatcher::class)->dispatchTyped(new AppUpdateEvent($appId));
\OC::$server->getEventDispatcher()->dispatch(ManagerEvent::EVENT_APP_UPDATE, new ManagerEvent(
ManagerEvent::EVENT_APP_UPDATE, $appId
));
@@ -1061,7 +1065,7 @@ class OC_App {
// load the app
self::loadApp($appId);
- $dispatcher = \OC::$server->get(\OCP\EventDispatcher\IEventDispatcher::class);
+ $dispatcher = \OC::$server->get(IEventDispatcher::class);
// load the steps
$r = new Repair([], $dispatcher, \OC::$server->get(LoggerInterface::class));
diff --git a/lib/public/App/Events/AppDisableEvent.php b/lib/public/App/Events/AppDisableEvent.php
new file mode 100644
index 00000000000..5a50587446f
--- /dev/null
+++ b/lib/public/App/Events/AppDisableEvent.php
@@ -0,0 +1,51 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * @copyright Copyright (c) 2022, Thomas Citharel <nextcloud@tcit.fr>
+ *
+ * @author Thomas Citharel <nextcloud@tcit.fr>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+namespace OCP\App\Events;
+
+use OCP\EventDispatcher\Event;
+
+/**
+ * @since 27.0.0
+ */
+class AppDisableEvent extends Event {
+ private string $appId;
+
+ /**
+ * @since 27.0.0
+ */
+ public function __construct(string $appId) {
+ parent::__construct();
+
+ $this->appId = $appId;
+ }
+
+ /**
+ * @since 27.0.0
+ */
+ public function getAppId(): string {
+ return $this->appId;
+ }
+}
diff --git a/lib/public/App/Events/AppEnableEvent.php b/lib/public/App/Events/AppEnableEvent.php
new file mode 100644
index 00000000000..1aff3630f86
--- /dev/null
+++ b/lib/public/App/Events/AppEnableEvent.php
@@ -0,0 +1,62 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * @copyright Copyright (c) 2022, Thomas Citharel <nextcloud@tcit.fr>
+ *
+ * @author Thomas Citharel <nextcloud@tcit.fr>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+namespace OCP\App\Events;
+
+use OCP\EventDispatcher\Event;
+
+/**
+ * @since 27.0.0
+ */
+class AppEnableEvent extends Event {
+ private string $appId;
+ /** @var string[] */
+ private array $groupIds;
+
+ /**
+ * @param string[] $groupIds
+ * @since 27.0.0
+ */
+ public function __construct(string $appId, array $groupIds = []) {
+ parent::__construct();
+
+ $this->appId = $appId;
+ $this->groupIds = $groupIds;
+ }
+
+ /**
+ * @since 27.0.0
+ */
+ public function getAppId(): string {
+ return $this->appId;
+ }
+
+ /**
+ * @since 27.0.0
+ */
+ public function getGroupIds(): array {
+ return $this->groupIds;
+ }
+}
diff --git a/lib/public/App/Events/AppUpdateEvent.php b/lib/public/App/Events/AppUpdateEvent.php
new file mode 100644
index 00000000000..92f1f8f9b16
--- /dev/null
+++ b/lib/public/App/Events/AppUpdateEvent.php
@@ -0,0 +1,51 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * @copyright Copyright (c) 2022, Thomas Citharel <nextcloud@tcit.fr>
+ *
+ * @author Thomas Citharel <nextcloud@tcit.fr>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+namespace OCP\App\Events;
+
+use OCP\EventDispatcher\Event;
+
+/**
+ * @since 27.0.0
+ */
+class AppUpdateEvent extends Event {
+ private string $appId;
+
+ /**
+ * @since 27.0.0
+ */
+ public function __construct(string $appId) {
+ parent::__construct();
+
+ $this->appId = $appId;
+ }
+
+ /**
+ * @since 27.0.0
+ */
+ public function getAppId(): string {
+ return $this->appId;
+ }
+}
diff --git a/tests/lib/App/AppManagerTest.php b/tests/lib/App/AppManagerTest.php
index de515837406..bf9592ac6a6 100644
--- a/tests/lib/App/AppManagerTest.php
+++ b/tests/lib/App/AppManagerTest.php
@@ -14,7 +14,10 @@ namespace Test\App;
use OC\App\AppManager;
use OC\AppConfig;
use OCP\App\AppPathNotFoundException;
+use OCP\App\Events\AppDisableEvent;
+use OCP\App\Events\AppEnableEvent;
use OCP\App\IAppManager;
+use OCP\EventDispatcher\IEventDispatcher;
use OCP\ICache;
use OCP\ICacheFactory;
use OCP\IConfig;
@@ -91,6 +94,9 @@ class AppManagerTest extends TestCase {
protected $cacheFactory;
/** @var EventDispatcherInterface|MockObject */
+ protected $legacyEventDispatcher;
+
+ /** @var IEventDispatcher|MockObject */
protected $eventDispatcher;
/** @var LoggerInterface|MockObject */
@@ -108,7 +114,8 @@ class AppManagerTest extends TestCase {
$this->appConfig = $this->getAppConfig();
$this->cacheFactory = $this->createMock(ICacheFactory::class);
$this->cache = $this->createMock(ICache::class);
- $this->eventDispatcher = $this->createMock(EventDispatcherInterface::class);
+ $this->legacyEventDispatcher = $this->createMock(EventDispatcherInterface::class);
+ $this->eventDispatcher = $this->createMock(IEventDispatcher::class);
$this->logger = $this->createMock(LoggerInterface::class);
$this->cacheFactory->expects($this->any())
->method('createDistributed')
@@ -120,6 +127,7 @@ class AppManagerTest extends TestCase {
$this->appConfig,
$this->groupManager,
$this->cacheFactory,
+ $this->legacyEventDispatcher,
$this->eventDispatcher,
$this->logger
);
@@ -137,12 +145,14 @@ class AppManagerTest extends TestCase {
if ($this->manager->isEnabledForUser('files_trashbin')) {
$this->manager->disableApp('files_trashbin');
}
+ $this->eventDispatcher->expects($this->once())->method('dispatchTyped')->with(new AppEnableEvent('files_trashbin'));
$this->manager->enableApp('files_trashbin');
$this->assertEquals('yes', $this->appConfig->getValue('files_trashbin', 'enabled', 'no'));
}
public function testDisableApp() {
$this->expectClearCache();
+ $this->eventDispatcher->expects($this->once())->method('dispatchTyped')->with(new AppDisableEvent('files_trashbin'));
$this->manager->disableApp('files_trashbin');
$this->assertEquals('no', $this->appConfig->getValue('files_trashbin', 'enabled', 'no'));
}
@@ -175,7 +185,7 @@ class AppManagerTest extends TestCase {
/** @var AppManager|MockObject $manager */
$manager = $this->getMockBuilder(AppManager::class)
->setConstructorArgs([
- $this->userSession, $this->config, $this->appConfig, $this->groupManager, $this->cacheFactory, $this->eventDispatcher, $this->logger
+ $this->userSession, $this->config, $this->appConfig, $this->groupManager, $this->cacheFactory, $this->legacyEventDispatcher, $this->eventDispatcher, $this->logger
])
->setMethods([
'getAppPath',
@@ -187,6 +197,8 @@ class AppManagerTest extends TestCase {
->with('test')
->willReturn('apps/test');
+ $this->eventDispatcher->expects($this->once())->method('dispatchTyped')->with(new AppEnableEvent('test', ['group1', 'group2']));
+
$manager->enableAppForGroups('test', $groups);
$this->assertEquals('["group1","group2"]', $this->appConfig->getValue('test', 'enabled', 'no'));
}
@@ -222,7 +234,7 @@ class AppManagerTest extends TestCase {
/** @var AppManager|MockObject $manager */
$manager = $this->getMockBuilder(AppManager::class)
->setConstructorArgs([
- $this->userSession, $this->config, $this->appConfig, $this->groupManager, $this->cacheFactory, $this->eventDispatcher, $this->logger
+ $this->userSession, $this->config, $this->appConfig, $this->groupManager, $this->cacheFactory, $this->legacyEventDispatcher, $this->eventDispatcher, $this->logger
])
->setMethods([
'getAppPath',
@@ -240,6 +252,8 @@ class AppManagerTest extends TestCase {
->with('test')
->willReturn($appInfo);
+ $this->eventDispatcher->expects($this->once())->method('dispatchTyped')->with(new AppEnableEvent('test', ['group1', 'group2']));
+
$manager->enableAppForGroups('test', $groups);
$this->assertEquals('["group1","group2"]', $this->appConfig->getValue('test', 'enabled', 'no'));
}
@@ -276,7 +290,7 @@ class AppManagerTest extends TestCase {
/** @var AppManager|MockObject $manager */
$manager = $this->getMockBuilder(AppManager::class)
->setConstructorArgs([
- $this->userSession, $this->config, $this->appConfig, $this->groupManager, $this->cacheFactory, $this->eventDispatcher, $this->logger
+ $this->userSession, $this->config, $this->appConfig, $this->groupManager, $this->cacheFactory, $this->legacyEventDispatcher, $this->eventDispatcher, $this->logger
])
->setMethods([
'getAppPath',
@@ -296,6 +310,8 @@ class AppManagerTest extends TestCase {
'types' => [$type],
]);
+ $this->eventDispatcher->expects($this->never())->method('dispatchTyped')->with(new AppEnableEvent('test', ['group1', 'group2']));
+
$manager->enableAppForGroups('test', $groups);
}
@@ -470,7 +486,7 @@ class AppManagerTest extends TestCase {
public function testGetAppsNeedingUpgrade() {
/** @var AppManager|MockObject $manager */
$manager = $this->getMockBuilder(AppManager::class)
- ->setConstructorArgs([$this->userSession, $this->config, $this->appConfig, $this->groupManager, $this->cacheFactory, $this->eventDispatcher, $this->logger])
+ ->setConstructorArgs([$this->userSession, $this->config, $this->appConfig, $this->groupManager, $this->cacheFactory, $this->legacyEventDispatcher, $this->eventDispatcher, $this->logger])
->setMethods(['getAppInfo'])
->getMock();
@@ -521,7 +537,7 @@ class AppManagerTest extends TestCase {
public function testGetIncompatibleApps() {
/** @var AppManager|MockObject $manager */
$manager = $this->getMockBuilder(AppManager::class)
- ->setConstructorArgs([$this->userSession, $this->config, $this->appConfig, $this->groupManager, $this->cacheFactory, $this->eventDispatcher, $this->logger])
+ ->setConstructorArgs([$this->userSession, $this->config, $this->appConfig, $this->groupManager, $this->cacheFactory, $this->legacyEventDispatcher, $this->eventDispatcher, $this->logger])
->setMethods(['getAppInfo'])
->getMock();
diff --git a/tests/lib/AppTest.php b/tests/lib/AppTest.php
index 4b2619a3761..5cdee5e1200 100644
--- a/tests/lib/AppTest.php
+++ b/tests/lib/AppTest.php
@@ -12,6 +12,7 @@ namespace Test;
use OC\App\AppManager;
use OC\App\InfoParser;
use OC\AppConfig;
+use OCP\EventDispatcher\IEventDispatcher;
use OCP\IAppConfig;
use Psr\Log\LoggerInterface;
@@ -553,13 +554,14 @@ class AppTest extends \Test\TestCase {
*/
private function registerAppConfig(AppConfig $appConfig) {
$this->overwriteService(AppConfig::class, $appConfig);
- $this->overwriteService(AppManager::class, new \OC\App\AppManager(
+ $this->overwriteService(AppManager::class, new AppManager(
\OC::$server->getUserSession(),
\OC::$server->getConfig(),
$appConfig,
\OC::$server->getGroupManager(),
\OC::$server->getMemCacheFactory(),
\OC::$server->getEventDispatcher(),
+ \OC::$server->get(IEventDispatcher::class),
\OC::$server->get(LoggerInterface::class)
));
}
diff --git a/tests/lib/SystemTag/SystemTagObjectMapperTest.php b/tests/lib/SystemTag/SystemTagObjectMapperTest.php
index c988db69c30..e77709c781f 100644
--- a/tests/lib/SystemTag/SystemTagObjectMapperTest.php
+++ b/tests/lib/SystemTag/SystemTagObjectMapperTest.php
@@ -141,6 +141,17 @@ class SystemTagObjectMapperTest extends TestCase {
$this->assertEquals([], $tagIdMapping);
}
+ public function testGetTagIdsForALotOfObjects() {
+ $ids = range(1, 10500);
+ $tagIdMapping = $this->tagMapper->getTagIdsForObjects(
+ $ids,
+ 'testtype'
+ );
+
+ $this->assertEquals(10500, count($tagIdMapping));
+ $this->assertEquals([$this->tag1->getId(), $this->tag2->getId()], $tagIdMapping[1]);
+ }
+
public function testGetObjectsForTags() {
$objectIds = $this->tagMapper->getObjectIdsForTags(
[$this->tag1->getId(), $this->tag2->getId(), $this->tag3->getId()],
@@ -165,7 +176,7 @@ class SystemTagObjectMapperTest extends TestCase {
], $objectIds);
}
-
+
public function testGetObjectsForTagsLimitWithMultipleTags() {
$this->expectException(\InvalidArgumentException::class);
@@ -189,7 +200,7 @@ class SystemTagObjectMapperTest extends TestCase {
], $objectIds);
}
-
+
public function testGetObjectsForNonExistingTag() {
$this->expectException(\OCP\SystemTag\TagNotFoundException::class);
@@ -227,7 +238,7 @@ class SystemTagObjectMapperTest extends TestCase {
$this->assertTrue(true, 'No error when reassigning/unassigning');
}
-
+
public function testAssignNonExistingTags() {
$this->expectException(\OCP\SystemTag\TagNotFoundException::class);
@@ -254,7 +265,7 @@ class SystemTagObjectMapperTest extends TestCase {
], $tagIdMapping, 'None of the tags got assigned');
}
-
+
public function testUnassignNonExistingTags() {
$this->expectException(\OCP\SystemTag\TagNotFoundException::class);
@@ -385,7 +396,7 @@ class SystemTagObjectMapperTest extends TestCase {
);
}
-
+
public function testHaveTagNonExisting() {
$this->expectException(\OCP\SystemTag\TagNotFoundException::class);