diff options
Diffstat (limited to 'apps')
60 files changed, 874 insertions, 229 deletions
diff --git a/apps/accessibility/l10n/uk.js b/apps/accessibility/l10n/uk.js index 97e59ce992a..5d8be748568 100644 --- a/apps/accessibility/l10n/uk.js +++ b/apps/accessibility/l10n/uk.js @@ -12,6 +12,8 @@ OC.L10N.register( "OpenDyslexic is a free typeface/font designed to mitigate some of the common reading errors caused by dyslexia." : "OpenDyslexic - це вільно доступний шрифт, що було розроблено для уникнення деяких загальних помилок, що спричинені дизлексією.", "Accessibility" : "Доступність", "Accessibility options for nextcloud" : "Опції доступності для nextcloud", + "Provides multiple accessibilities options to ease your use of Nextcloud" : "Надає кілька варіантів доступності, задля спрощення використання Nextcloud", + "Universal access is very important to us. We follow web standards and check to make everything usable also without mouse, and assistive software such as screenreaders. We aim to be compliant with the {guidelines}Web Content Accessibility Guidelines{linkend} 2.1 on AA level, with the high contrast theme even on AAA level." : "Для нас дуже важливо забезпечити доступ для всіх. Ми дотримуємось веб-стандартів і перевіряємо, щоб забезпечити зручність користування без комп'ютерної миші, а також за допомогую допоміжного програмного забезпечення, наприклад, програми зчитування з екрана. Ми прагнемо відповідати {guidelines} Правила доступу до веб-вмісту {linkend} 2.1 на рівні AA, а з темою високої контрастності навіть на рівні AAA.", "If you find any issues, don’t hesitate to report them on {issuetracker}our issue tracker{linkend}. And if you want to get involved, come join {designteam}our design team{linkend}!" : "Якщо ви знайшли будь-які проблеми, будь-ласка повідомте про них на {issuetracker} до нашого трекера задач{linkend}. Якщо ж ви бажаєте стати учасником - будь-ласка приєднуйтесь до {designteam} нашої команди{linkend}! ", "High contrast theme" : "Висококонтрастна тема", "A high contrast theme to ease your navigation. Visual quality will be reduced but clarity will be increased." : "Висококонтрастна тема для того, щоб полегшити користування. Візуальні ефекти буде погіршено, однак чіткість - покращено.", @@ -19,7 +21,7 @@ OC.L10N.register( "Web Content Accessibility Guidelines" : "Вказівки щодо доступності веб контенту", "our issue tracker" : "наш баґ-трекер", "our design team" : "наша команда дизайнерів", - "Universal access is very important to us. We follow web standards and check to make everything usable also without mouse, and assistive software such as screenreaders. We aim to be compliant with the {guidelines} 2.1 on AA level, with the high contrast theme even on AAA level." : "Доступ для всіх дуже важливий для нас. Ми намагаємося відповідати веб-стандартам і відслідковуємо, щоб всі функції були доступні також без використання миші, а також щоб було доступним допоміжне програмне забезпечення, наприклад читач екрану. Наша мета - відповідати {guidelines} 2.1 на рівні AA, а з висококонтрастною темою - на рівні AAA.", + "Universal access is very important to us. We follow web standards and check to make everything usable also without mouse, and assistive software such as screenreaders. We aim to be compliant with the {guidelines} 2.1 on AA level, with the high contrast theme even on AAA level." : "Для нас дуже важливо забезпечити доступ для всіх. Ми дотримуємось веб-стандартів і перевіряємо, щоб забезпечити зручність користування без комп'ютерної миші, а також за допомогую допоміжного програмного забезпечення, наприклад, програми зчитування з екрана. Ми прагнемо відповідати {guidelines} Правила доступу до веб-вмісту {linkend} 2.1 на рівні AA, а з темою високої контрастності навіть на рівні AAA.", "If you find any issues, don’t hesitate to report them on {issuetracker}. And if you want to get involved, come join {designteam}!" : "Якщо ви знайшли будь-які проблеми, будь-ласка повідомте про них на {issuetracker}. Якщо ж ви бажаєте стати учасником - будь-ласка приєднуйтесь до {designteam}!", "Enable" : "Увімкнути" }, diff --git a/apps/accessibility/l10n/uk.json b/apps/accessibility/l10n/uk.json index e73a0c06a7e..c66f3bceb53 100644 --- a/apps/accessibility/l10n/uk.json +++ b/apps/accessibility/l10n/uk.json @@ -10,6 +10,8 @@ "OpenDyslexic is a free typeface/font designed to mitigate some of the common reading errors caused by dyslexia." : "OpenDyslexic - це вільно доступний шрифт, що було розроблено для уникнення деяких загальних помилок, що спричинені дизлексією.", "Accessibility" : "Доступність", "Accessibility options for nextcloud" : "Опції доступності для nextcloud", + "Provides multiple accessibilities options to ease your use of Nextcloud" : "Надає кілька варіантів доступності, задля спрощення використання Nextcloud", + "Universal access is very important to us. We follow web standards and check to make everything usable also without mouse, and assistive software such as screenreaders. We aim to be compliant with the {guidelines}Web Content Accessibility Guidelines{linkend} 2.1 on AA level, with the high contrast theme even on AAA level." : "Для нас дуже важливо забезпечити доступ для всіх. Ми дотримуємось веб-стандартів і перевіряємо, щоб забезпечити зручність користування без комп'ютерної миші, а також за допомогую допоміжного програмного забезпечення, наприклад, програми зчитування з екрана. Ми прагнемо відповідати {guidelines} Правила доступу до веб-вмісту {linkend} 2.1 на рівні AA, а з темою високої контрастності навіть на рівні AAA.", "If you find any issues, don’t hesitate to report them on {issuetracker}our issue tracker{linkend}. And if you want to get involved, come join {designteam}our design team{linkend}!" : "Якщо ви знайшли будь-які проблеми, будь-ласка повідомте про них на {issuetracker} до нашого трекера задач{linkend}. Якщо ж ви бажаєте стати учасником - будь-ласка приєднуйтесь до {designteam} нашої команди{linkend}! ", "High contrast theme" : "Висококонтрастна тема", "A high contrast theme to ease your navigation. Visual quality will be reduced but clarity will be increased." : "Висококонтрастна тема для того, щоб полегшити користування. Візуальні ефекти буде погіршено, однак чіткість - покращено.", @@ -17,7 +19,7 @@ "Web Content Accessibility Guidelines" : "Вказівки щодо доступності веб контенту", "our issue tracker" : "наш баґ-трекер", "our design team" : "наша команда дизайнерів", - "Universal access is very important to us. We follow web standards and check to make everything usable also without mouse, and assistive software such as screenreaders. We aim to be compliant with the {guidelines} 2.1 on AA level, with the high contrast theme even on AAA level." : "Доступ для всіх дуже важливий для нас. Ми намагаємося відповідати веб-стандартам і відслідковуємо, щоб всі функції були доступні також без використання миші, а також щоб було доступним допоміжне програмне забезпечення, наприклад читач екрану. Наша мета - відповідати {guidelines} 2.1 на рівні AA, а з висококонтрастною темою - на рівні AAA.", + "Universal access is very important to us. We follow web standards and check to make everything usable also without mouse, and assistive software such as screenreaders. We aim to be compliant with the {guidelines} 2.1 on AA level, with the high contrast theme even on AAA level." : "Для нас дуже важливо забезпечити доступ для всіх. Ми дотримуємось веб-стандартів і перевіряємо, щоб забезпечити зручність користування без комп'ютерної миші, а також за допомогую допоміжного програмного забезпечення, наприклад, програми зчитування з екрана. Ми прагнемо відповідати {guidelines} Правила доступу до веб-вмісту {linkend} 2.1 на рівні AA, а з темою високої контрастності навіть на рівні AAA.", "If you find any issues, don’t hesitate to report them on {issuetracker}. And if you want to get involved, come join {designteam}!" : "Якщо ви знайшли будь-які проблеми, будь-ласка повідомте про них на {issuetracker}. Якщо ж ви бажаєте стати учасником - будь-ласка приєднуйтесь до {designteam}!", "Enable" : "Увімкнути" },"pluralForm" :"nplurals=4; plural=(n % 1 == 0 && n % 10 == 1 && n % 100 != 11 ? 0 : n % 1 == 0 && n % 10 >= 2 && n % 10 <= 4 && (n % 100 < 12 || n % 100 > 14) ? 1 : n % 1 == 0 && (n % 10 ==0 || (n % 10 >=5 && n % 10 <=9) || (n % 100 >=11 && n % 100 <=14 )) ? 2: 3);" diff --git a/apps/dav/composer/composer/autoload_classmap.php b/apps/dav/composer/composer/autoload_classmap.php index ab0c7ee63bd..9b6108a8052 100644 --- a/apps/dav/composer/composer/autoload_classmap.php +++ b/apps/dav/composer/composer/autoload_classmap.php @@ -223,6 +223,7 @@ return array( 'OCA\\DAV\\Upload\\CleanupService' => $baseDir . '/../lib/Upload/CleanupService.php', 'OCA\\DAV\\Upload\\FutureFile' => $baseDir . '/../lib/Upload/FutureFile.php', 'OCA\\DAV\\Upload\\RootCollection' => $baseDir . '/../lib/Upload/RootCollection.php', + 'OCA\\DAV\\Upload\\UploadFile' => $baseDir . '/../lib/Upload/UploadFile.php', 'OCA\\DAV\\Upload\\UploadFolder' => $baseDir . '/../lib/Upload/UploadFolder.php', 'OCA\\DAV\\Upload\\UploadHome' => $baseDir . '/../lib/Upload/UploadHome.php', ); diff --git a/apps/dav/composer/composer/autoload_static.php b/apps/dav/composer/composer/autoload_static.php index b142b93d793..fa57e9406e4 100644 --- a/apps/dav/composer/composer/autoload_static.php +++ b/apps/dav/composer/composer/autoload_static.php @@ -238,6 +238,7 @@ class ComposerStaticInitDAV 'OCA\\DAV\\Upload\\CleanupService' => __DIR__ . '/..' . '/../lib/Upload/CleanupService.php', 'OCA\\DAV\\Upload\\FutureFile' => __DIR__ . '/..' . '/../lib/Upload/FutureFile.php', 'OCA\\DAV\\Upload\\RootCollection' => __DIR__ . '/..' . '/../lib/Upload/RootCollection.php', + 'OCA\\DAV\\Upload\\UploadFile' => __DIR__ . '/..' . '/../lib/Upload/UploadFile.php', 'OCA\\DAV\\Upload\\UploadFolder' => __DIR__ . '/..' . '/../lib/Upload/UploadFolder.php', 'OCA\\DAV\\Upload\\UploadHome' => __DIR__ . '/..' . '/../lib/Upload/UploadHome.php', ); diff --git a/apps/dav/l10n/ca.js b/apps/dav/l10n/ca.js index 506a125cbd0..8dc962966ad 100644 --- a/apps/dav/l10n/ca.js +++ b/apps/dav/l10n/ca.js @@ -94,6 +94,7 @@ OC.L10N.register( "Birthday calendars will be generated by a background job." : "Els calendaris d'aniversari es generaran per un procés en segon pla..", "Hence they will not be available immediately after enabling but will show up after some time." : "Per tant, no estaran disponibles immediatament després d'habilitar-los, però apareixeran d'aquí una estona.", "Send notifications for events" : "Envia notificacions per als esdeveniments", + "Notifications are sent via background jobs, so these must occur often enough." : "Les notificacions s'envien per tasques funcionant en segon pla, així que això ha de succeir bastant sovint.", "Enable notifications for events via push" : "Habiliteu les notificacions per a esdeveniments mitjançant l'empenta.", "Technical details" : "Detalls tècnics", "Remote Address: %s" : "Adreça remota: %s", diff --git a/apps/dav/l10n/ca.json b/apps/dav/l10n/ca.json index 5b04b590ca9..18226a33df1 100644 --- a/apps/dav/l10n/ca.json +++ b/apps/dav/l10n/ca.json @@ -92,6 +92,7 @@ "Birthday calendars will be generated by a background job." : "Els calendaris d'aniversari es generaran per un procés en segon pla..", "Hence they will not be available immediately after enabling but will show up after some time." : "Per tant, no estaran disponibles immediatament després d'habilitar-los, però apareixeran d'aquí una estona.", "Send notifications for events" : "Envia notificacions per als esdeveniments", + "Notifications are sent via background jobs, so these must occur often enough." : "Les notificacions s'envien per tasques funcionant en segon pla, així que això ha de succeir bastant sovint.", "Enable notifications for events via push" : "Habiliteu les notificacions per a esdeveniments mitjançant l'empenta.", "Technical details" : "Detalls tècnics", "Remote Address: %s" : "Adreça remota: %s", diff --git a/apps/dav/lib/Upload/UploadFile.php b/apps/dav/lib/Upload/UploadFile.php new file mode 100644 index 00000000000..600cc24bdb7 --- /dev/null +++ b/apps/dav/lib/Upload/UploadFile.php @@ -0,0 +1,75 @@ +<?php + +declare(strict_types=1); +/** + * @copyright Copyright (c) 2020, Roeland Jago Douma <roeland@famdouma.nl> + * + * @author Roeland Jago Douma <roeland@famdouma.nl> + * + * @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\Upload; + +use OCA\DAV\Connector\Sabre\File; +use Sabre\DAV\IFile; + +class UploadFile implements IFile { + + /** @var File */ + private $file; + + public function __construct(File $file) { + $this->file = $file; + } + + public function put($data) { + return $this->file->put($data); + } + + public function get() { + return $this->file->get(); + } + + public function getContentType() { + return $this->file->getContentType(); + } + + public function getETag() { + return $this->file->getETag(); + } + + public function getSize() { + return $this->file->getSize(); + } + + public function delete() { + $this->file->delete(); + } + + public function getName() { + return $this->file->getName(); + } + + public function setName($name) { + $this->file->setName($name); + } + + public function getLastModified() { + return $this->file->getLastModified(); + } +} diff --git a/apps/dav/lib/Upload/UploadFolder.php b/apps/dav/lib/Upload/UploadFolder.php index d74154c6ac9..b3df7383ac1 100644 --- a/apps/dav/lib/Upload/UploadFolder.php +++ b/apps/dav/lib/Upload/UploadFolder.php @@ -53,12 +53,19 @@ class UploadFolder implements ICollection { if ($name === '.file') { return new FutureFile($this->node, '.file'); } - return $this->node->getChild($name); + return new UploadFile($this->node->getChild($name)); } public function getChildren() { - $children = $this->node->getChildren(); + $tmpChildren = $this->node->getChildren(); + + $children = []; $children[] = new FutureFile($this->node, '.file'); + + foreach ($tmpChildren as $child) { + $children[] = new UploadFile($child); + } + return $children; } diff --git a/apps/dav/tests/unit/Connector/Sabre/SharesPluginTest.php b/apps/dav/tests/unit/Connector/Sabre/SharesPluginTest.php index 153bc0cd93b..9144068a6cb 100644 --- a/apps/dav/tests/unit/Connector/Sabre/SharesPluginTest.php +++ b/apps/dav/tests/unit/Connector/Sabre/SharesPluginTest.php @@ -32,6 +32,7 @@ namespace OCA\DAV\Tests\unit\Connector\Sabre; use OCA\DAV\Connector\Sabre\Directory; use OCA\DAV\Connector\Sabre\File; use OCA\DAV\Connector\Sabre\Node; +use OCA\DAV\Upload\UploadFile; use OCP\Files\Folder; use OCP\IUser; use OCP\IUserSession; @@ -189,7 +190,7 @@ class SharesPluginTest extends \Test\TestCase { $this->userFolder->method('get') ->with('/subdir') ->willReturn($node); - + $dummyShares = array_map(function ($type) { $share = $this->getMockBuilder(IShare::class)->getMock(); $share->expects($this->any()) @@ -282,4 +283,24 @@ class SharesPluginTest extends \Test\TestCase { [[\OCP\Share::SHARE_TYPE_USER, \OCP\Share::SHARE_TYPE_REMOTE]], ]; } + + public function testGetPropertiesSkipChunks(): void { + $sabreNode = $this->getMockBuilder(UploadFile::class) + ->disableOriginalConstructor() + ->getMock(); + + $propFind = new \Sabre\DAV\PropFind( + '/dummyPath', + [self::SHARETYPES_PROPERTYNAME], + 0 + ); + + $this->plugin->handleGetProperties( + $propFind, + $sabreNode + ); + + $result = $propFind->getResultForMultiStatus(); + $this->assertCount(1, $result[404]); + } } diff --git a/apps/dav/tests/unit/Connector/Sabre/TagsPluginTest.php b/apps/dav/tests/unit/Connector/Sabre/TagsPluginTest.php index 210e732438c..95e567961d1 100644 --- a/apps/dav/tests/unit/Connector/Sabre/TagsPluginTest.php +++ b/apps/dav/tests/unit/Connector/Sabre/TagsPluginTest.php @@ -30,6 +30,7 @@ namespace OCA\DAV\Tests\unit\Connector\Sabre; use OCA\DAV\Connector\Sabre\Directory; use OCA\DAV\Connector\Sabre\File; use OCA\DAV\Connector\Sabre\Node; +use OCA\DAV\Upload\UploadFile; use OCP\ITagManager; use OCP\ITags; use Sabre\DAV\Tree; @@ -266,6 +267,26 @@ class TagsPluginTest extends \Test\TestCase { ]; } + public function testGetPropertiesSkipChunks(): void { + $sabreNode = $this->getMockBuilder(UploadFile::class) + ->disableOriginalConstructor() + ->getMock(); + + $propFind = new \Sabre\DAV\PropFind( + '/dummyPath', + [self::TAGS_PROPERTYNAME, self::TAG_FAVORITE], + 0 + ); + + $this->plugin->handleGetProperties( + $propFind, + $sabreNode + ); + + $result = $propFind->getResultForMultiStatus(); + $this->assertCount(2, $result[404]); + } + public function testUpdateTags() { // this test will replace the existing tags "tagremove" with "tag1" and "tag2" // and keep "tagkeep" diff --git a/apps/encryption/l10n/gl.js b/apps/encryption/l10n/gl.js index b20771acd53..284a7648300 100644 --- a/apps/encryption/l10n/gl.js +++ b/apps/encryption/l10n/gl.js @@ -33,10 +33,10 @@ OC.L10N.register( "Default encryption module" : "Módulo de cifrado predeterminado", "Default encryption module for server-side encryption" : "Módulo de cifrado predeterminado para o cifrado no lado do servidor", "In order to use this encryption module you need to enable server-side\n\t\tencryption in the admin settings. Once enabled this module will encrypt\n\t\tall your files transparently. The encryption is based on AES 256 keys.\n\t\tThe module won't touch existing files, only new files will be encrypted\n\t\tafter server-side encryption was enabled. It is also not possible to\n\t\tdisable the encryption again and switch back to a unencrypted system.\n\t\tPlease read the documentation to know all implications before you decide\n\t\tto enable server-side encryption." : "Para usar este módulo de cifrado é preciso activar o cifrado no lado\n\t\tdo servidor nos axustes do administrador. Una vez activado este módulo cifrará\n\t\ttodos os seus ficheiros de xeito transparente. O cifrado basease en chaves AES 256.\n\t\tO módulo non tocará os ficheiros existentes, só se cifran os ficheiros novos\n\t\tapós que se active o cifrado no lado do servidor. Tampouco é posíbel\n\t\tdesactivar o cifrado e volver a un sistema sen cifrar.\n\t\tLea a documentación para coñecer todas as implicacións antes de decidir\n\t\tactivar o cifrado no lado do servidor.", - "Hey there,\n\nthe admin enabled server-side-encryption. Your files were encrypted using the password '%s'.\n\nPlease login to the web interface, go to the section 'basic encryption module' of your personal settings and update your encryption password by entering this password into the 'old log-in password' field and your current login-password.\n\n" : "Ola.\n\nO administrador activou o cifrado de datos no servidor. Os seus ficheiros foron cifrados co contrasinal «%s».\n\nInicie a súa sesión dende a interface web, vaia á sección «Módulo de cifrado básico» dos seus axustes persoais e actualice o contrasinal de cifrado. Para iso, deberá inserir este contrasinal no campo «Contrasinal antigo de acceso» xunto co seu actual contrasinal de acceso.\n\n", + "Hey there,\n\nthe admin enabled server-side-encryption. Your files were encrypted using the password '%s'.\n\nPlease login to the web interface, go to the section 'basic encryption module' of your personal settings and update your encryption password by entering this password into the 'old log-in password' field and your current login-password.\n\n" : "Ola.\n\nO administrador activou o cifrado de datos no servidor. Os seus ficheiros foron cifrados co contrasinal «%s».\n\nInicie a súa sesión dende a interface web, vaia á sección «Módulo de cifrado básico» dos seus axustes persoais e actualice o contrasinal de cifrado. Para iso, deberá introducir este contrasinal no campo «Contrasinal antigo de acceso» xunto co seu actual contrasinal de acceso.\n\n", "The share will expire on %s." : "Este recurso compartido caduca o %s.", "Cheers!" : "Saúdos!", - "Hey there,<br><br>the admin enabled server-side-encryption. Your files were encrypted using the password <strong>%s</strong>.<br><br>Please login to the web interface, go to the section \"basic encryption module\" of your personal settings and update your encryption password by entering this password into the \"old log-in password\" field and your current login-password.<br><br>" : "Ola.<br><br>O administrador activou o cifrado de datos no servidor. Os seus ficheiros foron cifrados co contrasinal <strong>%s</strong>.<br><br>Inicie a súa sesión dende a interface web, vaia á sección «Módulo de cifrado básico» dos seus axustes persoais e actualice o contrasinal de cifrado. Para iso, deberá inserir este contrasinal no campo «Contrasinal antigo de acceso» xunto co seu actual contrasinal de acceso.<br><br>", + "Hey there,<br><br>the admin enabled server-side-encryption. Your files were encrypted using the password <strong>%s</strong>.<br><br>Please login to the web interface, go to the section \"basic encryption module\" of your personal settings and update your encryption password by entering this password into the \"old log-in password\" field and your current login-password.<br><br>" : "Ola.<br><br>O administrador activou o cifrado de datos no servidor. Os seus ficheiros foron cifrados co contrasinal <strong>%s</strong>.<br><br>Inicie a súa sesión dende a interface web, vaia á sección «Módulo de cifrado básico» dos seus axustes persoais e actualice o contrasinal de cifrado. Para iso, deberá introducir este contrasinal no campo «Contrasinal antigo de acceso» xunto co seu actual contrasinal de acceso.<br><br>", "Encryption app is enabled but your keys are not initialized, please log-out and log-in again" : "A aplicación de cifrado está activada, mais as chaves non foron preparadas, saia da sesión e volva a acceder de novo", "Encrypt the home storage" : "Cifrar o almacenamento persoal", "Enabling this option encrypts all files stored on the main storage, otherwise only files on external storage will be encrypted" : "Ao activar esta opción cífranse todos os ficheiros almacenados no almacenamento principal, senón só se cifran os ficheiros do almacenamento externo.", diff --git a/apps/encryption/l10n/gl.json b/apps/encryption/l10n/gl.json index efd4a70fc0e..b1e542ccbe0 100644 --- a/apps/encryption/l10n/gl.json +++ b/apps/encryption/l10n/gl.json @@ -31,10 +31,10 @@ "Default encryption module" : "Módulo de cifrado predeterminado", "Default encryption module for server-side encryption" : "Módulo de cifrado predeterminado para o cifrado no lado do servidor", "In order to use this encryption module you need to enable server-side\n\t\tencryption in the admin settings. Once enabled this module will encrypt\n\t\tall your files transparently. The encryption is based on AES 256 keys.\n\t\tThe module won't touch existing files, only new files will be encrypted\n\t\tafter server-side encryption was enabled. It is also not possible to\n\t\tdisable the encryption again and switch back to a unencrypted system.\n\t\tPlease read the documentation to know all implications before you decide\n\t\tto enable server-side encryption." : "Para usar este módulo de cifrado é preciso activar o cifrado no lado\n\t\tdo servidor nos axustes do administrador. Una vez activado este módulo cifrará\n\t\ttodos os seus ficheiros de xeito transparente. O cifrado basease en chaves AES 256.\n\t\tO módulo non tocará os ficheiros existentes, só se cifran os ficheiros novos\n\t\tapós que se active o cifrado no lado do servidor. Tampouco é posíbel\n\t\tdesactivar o cifrado e volver a un sistema sen cifrar.\n\t\tLea a documentación para coñecer todas as implicacións antes de decidir\n\t\tactivar o cifrado no lado do servidor.", - "Hey there,\n\nthe admin enabled server-side-encryption. Your files were encrypted using the password '%s'.\n\nPlease login to the web interface, go to the section 'basic encryption module' of your personal settings and update your encryption password by entering this password into the 'old log-in password' field and your current login-password.\n\n" : "Ola.\n\nO administrador activou o cifrado de datos no servidor. Os seus ficheiros foron cifrados co contrasinal «%s».\n\nInicie a súa sesión dende a interface web, vaia á sección «Módulo de cifrado básico» dos seus axustes persoais e actualice o contrasinal de cifrado. Para iso, deberá inserir este contrasinal no campo «Contrasinal antigo de acceso» xunto co seu actual contrasinal de acceso.\n\n", + "Hey there,\n\nthe admin enabled server-side-encryption. Your files were encrypted using the password '%s'.\n\nPlease login to the web interface, go to the section 'basic encryption module' of your personal settings and update your encryption password by entering this password into the 'old log-in password' field and your current login-password.\n\n" : "Ola.\n\nO administrador activou o cifrado de datos no servidor. Os seus ficheiros foron cifrados co contrasinal «%s».\n\nInicie a súa sesión dende a interface web, vaia á sección «Módulo de cifrado básico» dos seus axustes persoais e actualice o contrasinal de cifrado. Para iso, deberá introducir este contrasinal no campo «Contrasinal antigo de acceso» xunto co seu actual contrasinal de acceso.\n\n", "The share will expire on %s." : "Este recurso compartido caduca o %s.", "Cheers!" : "Saúdos!", - "Hey there,<br><br>the admin enabled server-side-encryption. Your files were encrypted using the password <strong>%s</strong>.<br><br>Please login to the web interface, go to the section \"basic encryption module\" of your personal settings and update your encryption password by entering this password into the \"old log-in password\" field and your current login-password.<br><br>" : "Ola.<br><br>O administrador activou o cifrado de datos no servidor. Os seus ficheiros foron cifrados co contrasinal <strong>%s</strong>.<br><br>Inicie a súa sesión dende a interface web, vaia á sección «Módulo de cifrado básico» dos seus axustes persoais e actualice o contrasinal de cifrado. Para iso, deberá inserir este contrasinal no campo «Contrasinal antigo de acceso» xunto co seu actual contrasinal de acceso.<br><br>", + "Hey there,<br><br>the admin enabled server-side-encryption. Your files were encrypted using the password <strong>%s</strong>.<br><br>Please login to the web interface, go to the section \"basic encryption module\" of your personal settings and update your encryption password by entering this password into the \"old log-in password\" field and your current login-password.<br><br>" : "Ola.<br><br>O administrador activou o cifrado de datos no servidor. Os seus ficheiros foron cifrados co contrasinal <strong>%s</strong>.<br><br>Inicie a súa sesión dende a interface web, vaia á sección «Módulo de cifrado básico» dos seus axustes persoais e actualice o contrasinal de cifrado. Para iso, deberá introducir este contrasinal no campo «Contrasinal antigo de acceso» xunto co seu actual contrasinal de acceso.<br><br>", "Encryption app is enabled but your keys are not initialized, please log-out and log-in again" : "A aplicación de cifrado está activada, mais as chaves non foron preparadas, saia da sesión e volva a acceder de novo", "Encrypt the home storage" : "Cifrar o almacenamento persoal", "Enabling this option encrypts all files stored on the main storage, otherwise only files on external storage will be encrypted" : "Ao activar esta opción cífranse todos os ficheiros almacenados no almacenamento principal, senón só se cifran os ficheiros do almacenamento externo.", diff --git a/apps/federatedfilesharing/l10n/ca.js b/apps/federatedfilesharing/l10n/ca.js index 962e4a01bff..8bc3663d50e 100644 --- a/apps/federatedfilesharing/l10n/ca.js +++ b/apps/federatedfilesharing/l10n/ca.js @@ -26,6 +26,7 @@ OC.L10N.register( "Federated sharing" : "Compartició federada", "You received \"%3$s\" as a remote share from %4$s (%1$s) (on behalf of %5$s (%2$s))" : "Has rebut \"%3$s\" com a compartició remota de %4$s (%1$s) (en nom de %5$s (%2$s))", "You received {share} as a remote share from {user} (on behalf of {behalf})" : "Heu rebut {share} com un recompte remot de {user} (en nom de {nom})", + "You received \"%3$s\" as a remote share from %4$s (%1$s)" : "Heu rebut \"%3$s\" com a compartició remota de %4$s (%1$s)", "You received {share} as a remote share from {user}" : "Heu rebut {share} com a recompte remot de {user}", "Accept" : "Accepta", "Decline" : "Denega", diff --git a/apps/federatedfilesharing/l10n/ca.json b/apps/federatedfilesharing/l10n/ca.json index 599f8100038..272d760f526 100644 --- a/apps/federatedfilesharing/l10n/ca.json +++ b/apps/federatedfilesharing/l10n/ca.json @@ -24,6 +24,7 @@ "Federated sharing" : "Compartició federada", "You received \"%3$s\" as a remote share from %4$s (%1$s) (on behalf of %5$s (%2$s))" : "Has rebut \"%3$s\" com a compartició remota de %4$s (%1$s) (en nom de %5$s (%2$s))", "You received {share} as a remote share from {user} (on behalf of {behalf})" : "Heu rebut {share} com un recompte remot de {user} (en nom de {nom})", + "You received \"%3$s\" as a remote share from %4$s (%1$s)" : "Heu rebut \"%3$s\" com a compartició remota de %4$s (%1$s)", "You received {share} as a remote share from {user}" : "Heu rebut {share} com a recompte remot de {user}", "Accept" : "Accepta", "Decline" : "Denega", diff --git a/apps/files/lib/Command/ScanAppData.php b/apps/files/lib/Command/ScanAppData.php index 358dd993291..0d34bc1e72c 100644 --- a/apps/files/lib/Command/ScanAppData.php +++ b/apps/files/lib/Command/ScanAppData.php @@ -38,6 +38,7 @@ use OCP\Files\StorageNotAvailableException; use OCP\IConfig; use OCP\IDBConnection; use Symfony\Component\Console\Helper\Table; +use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; @@ -67,6 +68,8 @@ class ScanAppData extends Base { $this ->setName('files:scan-app-data') ->setDescription('rescan the AppData folder'); + + $this->addArgument('folder', InputArgument::OPTIONAL, 'The appdata subfolder to scan', ''); } public function checkScanWarning($fullPath, OutputInterface $output) { @@ -78,7 +81,7 @@ class ScanAppData extends Base { } } - protected function scanFiles(OutputInterface $output) { + protected function scanFiles(OutputInterface $output, string $folder) { try { $appData = $this->getAppDataFolder(); } catch (NotFoundException $e) { @@ -86,6 +89,15 @@ class ScanAppData extends Base { return; } + if ($folder !== '') { + try { + $appData = $appData->get($folder); + } catch (NotFoundException $e) { + $output->writeln('Could not find folder: ' . $folder); + return; + } + } + $connection = $this->reconnectToDatabase($output); $scanner = new \OC\Files\Utils\Scanner(null, $connection, \OC::$server->query(IEventDispatcher::class), \OC::$server->getLogger()); @@ -139,9 +151,11 @@ class ScanAppData extends Base { $output->writeln("\nScanning AppData for files"); + $folder = $input->getArgument('folder'); + $this->initTools(); - $this->scanFiles($output); + $this->scanFiles($output, $folder); $this->presentStats($output); } diff --git a/apps/files_external/l10n/ca.js b/apps/files_external/l10n/ca.js index 478ac941b8e..a141102f9ef 100644 --- a/apps/files_external/l10n/ca.js +++ b/apps/files_external/l10n/ca.js @@ -105,6 +105,8 @@ OC.L10N.register( "SMB / CIFS" : "SMB / CIFS", "Share" : "Comparteix", "Show hidden files" : "Mostra els fitxers ocults", + "Verify ACL access when listing files" : "Verificar accés ACL quan es llistin fitxers", + "Check the ACL's of each file or folder inside a directory to filter out items where the user has no read permissions, comes with a performance penalty" : "Comprovar els ACLs de cada fitxer o carpeta a dins d'un directori per filtrar els elements pels que l'usuari no tingui permís, provoca una disminució del rendiment", "Timeout" : "Sense temps", "SMB / CIFS using OC login" : "SMB / CIFS fent servir acreditació OC", "Username as share" : "Nom d'usuari amb que es comparteix", diff --git a/apps/files_external/l10n/ca.json b/apps/files_external/l10n/ca.json index 64a80e7dbe8..1326e5f0356 100644 --- a/apps/files_external/l10n/ca.json +++ b/apps/files_external/l10n/ca.json @@ -103,6 +103,8 @@ "SMB / CIFS" : "SMB / CIFS", "Share" : "Comparteix", "Show hidden files" : "Mostra els fitxers ocults", + "Verify ACL access when listing files" : "Verificar accés ACL quan es llistin fitxers", + "Check the ACL's of each file or folder inside a directory to filter out items where the user has no read permissions, comes with a performance penalty" : "Comprovar els ACLs de cada fitxer o carpeta a dins d'un directori per filtrar els elements pels que l'usuari no tingui permís, provoca una disminució del rendiment", "Timeout" : "Sense temps", "SMB / CIFS using OC login" : "SMB / CIFS fent servir acreditació OC", "Username as share" : "Nom d'usuari amb que es comparteix", diff --git a/apps/files_external/l10n/de.js b/apps/files_external/l10n/de.js index 75f5bd3e4d2..6c38b9834dc 100644 --- a/apps/files_external/l10n/de.js +++ b/apps/files_external/l10n/de.js @@ -105,6 +105,8 @@ OC.L10N.register( "SMB / CIFS" : "SMB / CIFS", "Share" : "Share", "Show hidden files" : "Versteckte Dateien anzeigen", + "Verify ACL access when listing files" : "Überprüft den ACL-Zugriff beim Auflisten von Dateien", + "Check the ACL's of each file or folder inside a directory to filter out items where the user has no read permissions, comes with a performance penalty" : "Überprüft die ACLs jeder Datei oder jedes Ordners in einem Verzeichnis, um Elemente herauszufiltern, für die der Benutzer keine Leseberechtigung hat. Dies führt zu Leistungseinbußen", "Timeout" : "Zeitüberschreitung", "SMB / CIFS using OC login" : "SMB / CIFS mit OC-Anmeldung", "Username as share" : "Benutzername als Freigabe", diff --git a/apps/files_external/l10n/de.json b/apps/files_external/l10n/de.json index ccdb6c54d58..b69fe24d030 100644 --- a/apps/files_external/l10n/de.json +++ b/apps/files_external/l10n/de.json @@ -103,6 +103,8 @@ "SMB / CIFS" : "SMB / CIFS", "Share" : "Share", "Show hidden files" : "Versteckte Dateien anzeigen", + "Verify ACL access when listing files" : "Überprüft den ACL-Zugriff beim Auflisten von Dateien", + "Check the ACL's of each file or folder inside a directory to filter out items where the user has no read permissions, comes with a performance penalty" : "Überprüft die ACLs jeder Datei oder jedes Ordners in einem Verzeichnis, um Elemente herauszufiltern, für die der Benutzer keine Leseberechtigung hat. Dies führt zu Leistungseinbußen", "Timeout" : "Zeitüberschreitung", "SMB / CIFS using OC login" : "SMB / CIFS mit OC-Anmeldung", "Username as share" : "Benutzername als Freigabe", diff --git a/apps/files_external/l10n/de_DE.js b/apps/files_external/l10n/de_DE.js index 06ae33f39c8..2316fea4b7c 100644 --- a/apps/files_external/l10n/de_DE.js +++ b/apps/files_external/l10n/de_DE.js @@ -105,6 +105,8 @@ OC.L10N.register( "SMB / CIFS" : "SMB / CIFS", "Share" : "Share", "Show hidden files" : "Versteckte Dateien anzeigen", + "Verify ACL access when listing files" : "Überprüft den ACL-Zugriff beim Auflisten von Dateien", + "Check the ACL's of each file or folder inside a directory to filter out items where the user has no read permissions, comes with a performance penalty" : "Überprüft die ACLs jeder Datei oder jedes Ordners in einem Verzeichnis, um Elemente herauszufiltern, für die der Benutzer keine Leseberechtigung hat. Dies führt zu Leistungseinbußen", "Timeout" : "Zeitüberschreitung", "SMB / CIFS using OC login" : "SMB / CIFS mit OC-Anmeldung", "Username as share" : "Benutzername als Freigabe", diff --git a/apps/files_external/l10n/de_DE.json b/apps/files_external/l10n/de_DE.json index 37c0c9a0ccd..cb92c0d4c8f 100644 --- a/apps/files_external/l10n/de_DE.json +++ b/apps/files_external/l10n/de_DE.json @@ -103,6 +103,8 @@ "SMB / CIFS" : "SMB / CIFS", "Share" : "Share", "Show hidden files" : "Versteckte Dateien anzeigen", + "Verify ACL access when listing files" : "Überprüft den ACL-Zugriff beim Auflisten von Dateien", + "Check the ACL's of each file or folder inside a directory to filter out items where the user has no read permissions, comes with a performance penalty" : "Überprüft die ACLs jeder Datei oder jedes Ordners in einem Verzeichnis, um Elemente herauszufiltern, für die der Benutzer keine Leseberechtigung hat. Dies führt zu Leistungseinbußen", "Timeout" : "Zeitüberschreitung", "SMB / CIFS using OC login" : "SMB / CIFS mit OC-Anmeldung", "Username as share" : "Benutzername als Freigabe", diff --git a/apps/files_external/l10n/es.js b/apps/files_external/l10n/es.js index cb7d58e8752..a563113c4a2 100644 --- a/apps/files_external/l10n/es.js +++ b/apps/files_external/l10n/es.js @@ -105,6 +105,8 @@ OC.L10N.register( "SMB / CIFS" : "SMB / CIFS", "Share" : "Compartir", "Show hidden files" : "Mostrar archivos ocultos", + "Verify ACL access when listing files" : "Verificar acceso ACL al listar archivos", + "Check the ACL's of each file or folder inside a directory to filter out items where the user has no read permissions, comes with a performance penalty" : "Comprobar los ACL de cada archivo o carpeta en un directorio para filtrar los ítems en los que el usuario no tiene permisos de lectura. Provoca un descenso en el rendimiento.", "Timeout" : "Se agotó el tiempo", "SMB / CIFS using OC login" : "SMB / CIFS que usan acceso OC", "Username as share" : "Nombre de usuario como compartir", diff --git a/apps/files_external/l10n/es.json b/apps/files_external/l10n/es.json index a3ec908bdc7..4be0e78d3c9 100644 --- a/apps/files_external/l10n/es.json +++ b/apps/files_external/l10n/es.json @@ -103,6 +103,8 @@ "SMB / CIFS" : "SMB / CIFS", "Share" : "Compartir", "Show hidden files" : "Mostrar archivos ocultos", + "Verify ACL access when listing files" : "Verificar acceso ACL al listar archivos", + "Check the ACL's of each file or folder inside a directory to filter out items where the user has no read permissions, comes with a performance penalty" : "Comprobar los ACL de cada archivo o carpeta en un directorio para filtrar los ítems en los que el usuario no tiene permisos de lectura. Provoca un descenso en el rendimiento.", "Timeout" : "Se agotó el tiempo", "SMB / CIFS using OC login" : "SMB / CIFS que usan acceso OC", "Username as share" : "Nombre de usuario como compartir", diff --git a/apps/files_external/l10n/it.js b/apps/files_external/l10n/it.js index 4cca25b78b0..450651d55d4 100644 --- a/apps/files_external/l10n/it.js +++ b/apps/files_external/l10n/it.js @@ -106,6 +106,7 @@ OC.L10N.register( "Share" : "Condividi", "Show hidden files" : "Mostra i file nascosti", "Verify ACL access when listing files" : "Verifica le ACL di accesso quando elenchi i file", + "Check the ACL's of each file or folder inside a directory to filter out items where the user has no read permissions, comes with a performance penalty" : "Controlla le ACL di ogni file o cartella all'interno di una cartella per filtrare elementi dove l'utente non ha permessi di lettura, implica una riduzione di prestazioni", "Timeout" : "Tempo scaduto", "SMB / CIFS using OC login" : "SMB / CIFS utilizzando le credenziali di OC", "Username as share" : "Nome utente come condivisione", diff --git a/apps/files_external/l10n/it.json b/apps/files_external/l10n/it.json index 252df5c44aa..03a4dba1b82 100644 --- a/apps/files_external/l10n/it.json +++ b/apps/files_external/l10n/it.json @@ -104,6 +104,7 @@ "Share" : "Condividi", "Show hidden files" : "Mostra i file nascosti", "Verify ACL access when listing files" : "Verifica le ACL di accesso quando elenchi i file", + "Check the ACL's of each file or folder inside a directory to filter out items where the user has no read permissions, comes with a performance penalty" : "Controlla le ACL di ogni file o cartella all'interno di una cartella per filtrare elementi dove l'utente non ha permessi di lettura, implica una riduzione di prestazioni", "Timeout" : "Tempo scaduto", "SMB / CIFS using OC login" : "SMB / CIFS utilizzando le credenziali di OC", "Username as share" : "Nome utente come condivisione", diff --git a/apps/files_external/l10n/nl.js b/apps/files_external/l10n/nl.js index cd4f352a875..887e3716dfd 100644 --- a/apps/files_external/l10n/nl.js +++ b/apps/files_external/l10n/nl.js @@ -105,6 +105,8 @@ OC.L10N.register( "SMB / CIFS" : "SMB / CIFS", "Share" : "Share", "Show hidden files" : "Laat verborgen bestanden zien", + "Verify ACL access when listing files" : "Controleer ACL bij bestandsoverzichten", + "Check the ACL's of each file or folder inside a directory to filter out items where the user has no read permissions, comes with a performance penalty" : "Controleer de ACL's van elk bestand of elke map in een directory om objecten uit te filteren waar de gebruiker geen leesrechten heeft, betekent wel prestatieverlies", "Timeout" : "Time-out", "SMB / CIFS using OC login" : "SMB / CIFS via OC inlog", "Username as share" : "Gebruikersnaam als share", diff --git a/apps/files_external/l10n/nl.json b/apps/files_external/l10n/nl.json index e86cad17685..a3805d66c02 100644 --- a/apps/files_external/l10n/nl.json +++ b/apps/files_external/l10n/nl.json @@ -103,6 +103,8 @@ "SMB / CIFS" : "SMB / CIFS", "Share" : "Share", "Show hidden files" : "Laat verborgen bestanden zien", + "Verify ACL access when listing files" : "Controleer ACL bij bestandsoverzichten", + "Check the ACL's of each file or folder inside a directory to filter out items where the user has no read permissions, comes with a performance penalty" : "Controleer de ACL's van elk bestand of elke map in een directory om objecten uit te filteren waar de gebruiker geen leesrechten heeft, betekent wel prestatieverlies", "Timeout" : "Time-out", "SMB / CIFS using OC login" : "SMB / CIFS via OC inlog", "Username as share" : "Gebruikersnaam als share", diff --git a/apps/files_sharing/l10n/he.js b/apps/files_sharing/l10n/he.js index f9ca05ec38a..4ea312594ca 100644 --- a/apps/files_sharing/l10n/he.js +++ b/apps/files_sharing/l10n/he.js @@ -26,7 +26,9 @@ OC.L10N.register( "Something happened. Unable to accept the share." : "משהו התרחש. לא ניתן לשחזר את השיתוף.", "Reject share" : "דחיית השיתוף", "Something happened. Unable to reject the share." : "משהו התרחש. לא ניתן לדחות את השיתוף.", + "Waiting…" : "בהמתנה…", "error" : "שגיאה", + "finished" : "הסתיים", "This will stop your current uploads." : "פעולה זו תעצור את השליחות הנוכחיות שלך.", "Move or copy" : "העברה או העתקה", "Download" : "הורדה", @@ -73,10 +75,12 @@ OC.L10N.register( "Shared with {user}" : "שותף עם {user}", "Removed share for {user}" : "הוסר השיתוף עבור {user}", "You removed yourself" : "הסרת את עצמך", + "{actor} removed themselves" : "{actor} הסירו את עצמם", "{actor} shared with {user}" : "שותף עם {user} על ידי {actor}", "{actor} removed share for {user}" : "השיתוף עבור {user} הוסר על ידי {actor}", "Shared by {actor}" : "שותף על ידי {actor}", "{actor} removed share" : "שיתוף הוסר על ידי {actor}", + "Share for {user} expired" : "תוקף השיתוף של {user} פג", "Share expired" : "תוקף השיתוף פג", "You shared {file} with {user}" : "שיתפת את {file} עם {user}", "You removed {user} from {file}" : "הסרת את {user} מהקובץ {file}", @@ -88,6 +92,7 @@ OC.L10N.register( "A file or folder shared by mail or by public link was <strong>downloaded</strong>" : "קובץ או תיקייה ששותפו בדוא״ל או דרך קישור ציבורי <strong>הורדו</strong>", "A file or folder was shared from <strong>another server</strong>" : "קובץ או תיקייה שותפו מ- <strong>שרת אחר</strong>", "A file or folder has been <strong>shared</strong>" : "קובץ או תיקייה <strong>שותפו<strong/>", + "Shared link" : "שותף קישור", "Wrong share ID, share doesn't exist" : "מספר זיהוי שיתוף שגוי, שיתוף אינו קיים", "Could not delete share" : "לא ניתן היה למחוק את השיתוף", "Please specify a file or folder path" : "יש לספק נתיב לקובץ או תיקייה", @@ -114,13 +119,19 @@ OC.L10N.register( "Add to your Nextcloud" : "הוספה ל־Nextcloud שלך", "Share API is disabled" : "שיתוף API מנוטרל", "File sharing" : "שיתוף קבצים", + "Share will expire tomorrow" : "תוקף השיתוף יפוג מחר", + "One or more of your shares will expire tomorrow" : "תפוגתם של שיתוף אחד או יותר יפוג מחר", "Accept" : "קבלה", "Reject" : "דחייה", "Sharing" : "שיתוף", + "Accept user and group shares by default" : "לקבל את שיתופי המשתמשים והקבוצות כבררת מחדל", "Allow editing" : "לאפשר עריכה", + "Allow creating" : "לאפשר יצירה", + "Allow deleting" : "לאפשר מחיקה", "Allow resharing" : "לאפשר שיתוף מחדש", "Expiration date enforced" : "נאכף מועד תפוגה", "Set expiration date" : "הגדרת תאריך תפוגה", + "Enter a date" : "נא למלא תאריך", "Note to recipient" : "מסר לנמען", "Unshare" : "ביטול שיתוף", "group" : "קבוצה", @@ -128,22 +139,55 @@ OC.L10N.register( "remote" : "מרוחק", "remote group" : "קבוצה מרוחקת", "guest" : "אורח", + "Shared with the group {user} by {owner}" : "שותף עם הקבוצה {user} על ידי {owner}", + "Shared with the conversation {user} by {owner}" : "שותף עם הדיון {user} על ידי {owner}", + "Shared with {user} by {owner}" : "שותף עם {user} על ידי {owner}", + "Added by {initiator}" : "נוסף על ידי {initiator}", + "Via folder" : "דרך התיקייה", + "Internal link" : "קישור פנימי", "Link copied" : "הקישור הועתק", + "Cannot copy, please copy the link manually" : "לא ניתן להעתיק, נא להעתיק את הקישור ידנית", "Copy to clipboard" : "העתקה ללוח הגזירים", - "Only works for users with access to this folder" : "עובד רק עבוד משתמשים עם גישה לתיקייה הזו", + "Only works for users with access to this folder" : "עובד רק עבור משתמשים עם גישה לתיקייה הזו", + "Only works for users with access to this file" : "עובד רק עבור משתמשים עם גישה לקובץ הזה", + "Please enter the following required information before creating the share" : "נא למלא את הפרטים הנחוצים הבאים בטרם יצירת השיתוף", + "Password protection (enforced)" : "הגנה בססמה (נאכף)", "Password protection" : "Password protection", "Enter a password" : "נא להקליד ססמה", + "Expiration date (enforced)" : "תאריך תפוגה (נאכף)", "Cancel" : "ביטול", "Read only" : "קריאה בלבד", "Allow upload and editing" : "לאפשר העלאה ועריכה", "File drop (upload only)" : "השלכת קבצים (העלאה בלבד)", "Hide download" : "הסתרת הורדה", "Password protect" : "הגנה בססמה", + "Video verification" : "אימות וידאו", + "Enter a note for the share recipient" : "נא להוסיף הערה למקבלי השיתוף", "Add another link" : "הוספת קישור נוסף", + "Create a new share link" : "יצירת קישור שיתוף חדש", + "Shared via link by {initiator}" : "שותף דרך קישור על ידי {initiator}", "Share link" : "שיתוף קישור", + "Error, please enter proper password and/or expiration date" : "שגיאה, נא למלא ססמה ו/או תאריך תפוגה כראוי", + "No recommendations. Start typing." : "אין המלצות. נא להתחיל להקליד.", "Resharing is not allowed" : "שיתוף מחדש אסור", + "Name or email address …" : "שם או כתובת דוא״ל…", + "Name or federated cloud ID …" : "שם או מזהה ענן מאוגד…", + "Name, federated cloud ID or email address …" : "שם, מזהה ענן מאוגד או כתובת דוא״ל…", + "Name …" : "שם…", + "Searching …" : "מתבצע חיפוש…", + "No elements found." : "לא נמצאו רכיבים.", "Search globally" : "חיפוש גלובלי", + "on {server}" : "על גבי {server}", + "Others with access" : "נוספים עם גישה", + "No other users with access found" : "לא נמצאו משתמשים נוספים עם גישה", + "Toggle list of others with access to this directory" : "החלפת מצב הנוספים עם גישה לתיקייה הזאת", + "Toggle list of others with access to this file" : "החלפת מצב הנוספים עם גישה לקובץ הזה", + "Unable to fetch inherited shares" : "לא ניתן לקבל את השיתופים שנורשו", + "Unable to load the shares list" : "לא ניתן לטעון את רשימת השיתופים", + "Expires {relativetime}" : "תפוגה: {relativetime}", + "this share just expired." : "תוקף השיתוף פג זה עתה.", "Link to a file" : "קישור לקובץ", + "Error creating the share" : "יצירת השיתוף נכשלה", "Shared" : "משותף", "Share" : "שיתוף", "Shared with" : "משותף עם", @@ -168,6 +212,7 @@ OC.L10N.register( "Upload files to %s" : "העלאת קבצים על %s", "Note" : "פתק", "Select or drop files" : "בחירה או השלכה של קבצים", + "Uploading files" : "מועלים קבצים", "Uploaded files:" : "קבצים שהועלו:", "By uploading files, you agree to the %1$sterms of service%2$s." : "עצם העלאתם של קבצים מביעה את הסכמתך ל%1$sתנאי השירות%2$s.", "could not delete share" : "לא ניתן למחוק שיתוף", diff --git a/apps/files_sharing/l10n/he.json b/apps/files_sharing/l10n/he.json index cdcb68a40af..67b5e5a9ec1 100644 --- a/apps/files_sharing/l10n/he.json +++ b/apps/files_sharing/l10n/he.json @@ -24,7 +24,9 @@ "Something happened. Unable to accept the share." : "משהו התרחש. לא ניתן לשחזר את השיתוף.", "Reject share" : "דחיית השיתוף", "Something happened. Unable to reject the share." : "משהו התרחש. לא ניתן לדחות את השיתוף.", + "Waiting…" : "בהמתנה…", "error" : "שגיאה", + "finished" : "הסתיים", "This will stop your current uploads." : "פעולה זו תעצור את השליחות הנוכחיות שלך.", "Move or copy" : "העברה או העתקה", "Download" : "הורדה", @@ -71,10 +73,12 @@ "Shared with {user}" : "שותף עם {user}", "Removed share for {user}" : "הוסר השיתוף עבור {user}", "You removed yourself" : "הסרת את עצמך", + "{actor} removed themselves" : "{actor} הסירו את עצמם", "{actor} shared with {user}" : "שותף עם {user} על ידי {actor}", "{actor} removed share for {user}" : "השיתוף עבור {user} הוסר על ידי {actor}", "Shared by {actor}" : "שותף על ידי {actor}", "{actor} removed share" : "שיתוף הוסר על ידי {actor}", + "Share for {user} expired" : "תוקף השיתוף של {user} פג", "Share expired" : "תוקף השיתוף פג", "You shared {file} with {user}" : "שיתפת את {file} עם {user}", "You removed {user} from {file}" : "הסרת את {user} מהקובץ {file}", @@ -86,6 +90,7 @@ "A file or folder shared by mail or by public link was <strong>downloaded</strong>" : "קובץ או תיקייה ששותפו בדוא״ל או דרך קישור ציבורי <strong>הורדו</strong>", "A file or folder was shared from <strong>another server</strong>" : "קובץ או תיקייה שותפו מ- <strong>שרת אחר</strong>", "A file or folder has been <strong>shared</strong>" : "קובץ או תיקייה <strong>שותפו<strong/>", + "Shared link" : "שותף קישור", "Wrong share ID, share doesn't exist" : "מספר זיהוי שיתוף שגוי, שיתוף אינו קיים", "Could not delete share" : "לא ניתן היה למחוק את השיתוף", "Please specify a file or folder path" : "יש לספק נתיב לקובץ או תיקייה", @@ -112,13 +117,19 @@ "Add to your Nextcloud" : "הוספה ל־Nextcloud שלך", "Share API is disabled" : "שיתוף API מנוטרל", "File sharing" : "שיתוף קבצים", + "Share will expire tomorrow" : "תוקף השיתוף יפוג מחר", + "One or more of your shares will expire tomorrow" : "תפוגתם של שיתוף אחד או יותר יפוג מחר", "Accept" : "קבלה", "Reject" : "דחייה", "Sharing" : "שיתוף", + "Accept user and group shares by default" : "לקבל את שיתופי המשתמשים והקבוצות כבררת מחדל", "Allow editing" : "לאפשר עריכה", + "Allow creating" : "לאפשר יצירה", + "Allow deleting" : "לאפשר מחיקה", "Allow resharing" : "לאפשר שיתוף מחדש", "Expiration date enforced" : "נאכף מועד תפוגה", "Set expiration date" : "הגדרת תאריך תפוגה", + "Enter a date" : "נא למלא תאריך", "Note to recipient" : "מסר לנמען", "Unshare" : "ביטול שיתוף", "group" : "קבוצה", @@ -126,22 +137,55 @@ "remote" : "מרוחק", "remote group" : "קבוצה מרוחקת", "guest" : "אורח", + "Shared with the group {user} by {owner}" : "שותף עם הקבוצה {user} על ידי {owner}", + "Shared with the conversation {user} by {owner}" : "שותף עם הדיון {user} על ידי {owner}", + "Shared with {user} by {owner}" : "שותף עם {user} על ידי {owner}", + "Added by {initiator}" : "נוסף על ידי {initiator}", + "Via folder" : "דרך התיקייה", + "Internal link" : "קישור פנימי", "Link copied" : "הקישור הועתק", + "Cannot copy, please copy the link manually" : "לא ניתן להעתיק, נא להעתיק את הקישור ידנית", "Copy to clipboard" : "העתקה ללוח הגזירים", - "Only works for users with access to this folder" : "עובד רק עבוד משתמשים עם גישה לתיקייה הזו", + "Only works for users with access to this folder" : "עובד רק עבור משתמשים עם גישה לתיקייה הזו", + "Only works for users with access to this file" : "עובד רק עבור משתמשים עם גישה לקובץ הזה", + "Please enter the following required information before creating the share" : "נא למלא את הפרטים הנחוצים הבאים בטרם יצירת השיתוף", + "Password protection (enforced)" : "הגנה בססמה (נאכף)", "Password protection" : "Password protection", "Enter a password" : "נא להקליד ססמה", + "Expiration date (enforced)" : "תאריך תפוגה (נאכף)", "Cancel" : "ביטול", "Read only" : "קריאה בלבד", "Allow upload and editing" : "לאפשר העלאה ועריכה", "File drop (upload only)" : "השלכת קבצים (העלאה בלבד)", "Hide download" : "הסתרת הורדה", "Password protect" : "הגנה בססמה", + "Video verification" : "אימות וידאו", + "Enter a note for the share recipient" : "נא להוסיף הערה למקבלי השיתוף", "Add another link" : "הוספת קישור נוסף", + "Create a new share link" : "יצירת קישור שיתוף חדש", + "Shared via link by {initiator}" : "שותף דרך קישור על ידי {initiator}", "Share link" : "שיתוף קישור", + "Error, please enter proper password and/or expiration date" : "שגיאה, נא למלא ססמה ו/או תאריך תפוגה כראוי", + "No recommendations. Start typing." : "אין המלצות. נא להתחיל להקליד.", "Resharing is not allowed" : "שיתוף מחדש אסור", + "Name or email address …" : "שם או כתובת דוא״ל…", + "Name or federated cloud ID …" : "שם או מזהה ענן מאוגד…", + "Name, federated cloud ID or email address …" : "שם, מזהה ענן מאוגד או כתובת דוא״ל…", + "Name …" : "שם…", + "Searching …" : "מתבצע חיפוש…", + "No elements found." : "לא נמצאו רכיבים.", "Search globally" : "חיפוש גלובלי", + "on {server}" : "על גבי {server}", + "Others with access" : "נוספים עם גישה", + "No other users with access found" : "לא נמצאו משתמשים נוספים עם גישה", + "Toggle list of others with access to this directory" : "החלפת מצב הנוספים עם גישה לתיקייה הזאת", + "Toggle list of others with access to this file" : "החלפת מצב הנוספים עם גישה לקובץ הזה", + "Unable to fetch inherited shares" : "לא ניתן לקבל את השיתופים שנורשו", + "Unable to load the shares list" : "לא ניתן לטעון את רשימת השיתופים", + "Expires {relativetime}" : "תפוגה: {relativetime}", + "this share just expired." : "תוקף השיתוף פג זה עתה.", "Link to a file" : "קישור לקובץ", + "Error creating the share" : "יצירת השיתוף נכשלה", "Shared" : "משותף", "Share" : "שיתוף", "Shared with" : "משותף עם", @@ -166,6 +210,7 @@ "Upload files to %s" : "העלאת קבצים על %s", "Note" : "פתק", "Select or drop files" : "בחירה או השלכה של קבצים", + "Uploading files" : "מועלים קבצים", "Uploaded files:" : "קבצים שהועלו:", "By uploading files, you agree to the %1$sterms of service%2$s." : "עצם העלאתם של קבצים מביעה את הסכמתך ל%1$sתנאי השירות%2$s.", "could not delete share" : "לא ניתן למחוק שיתוף", diff --git a/apps/files_sharing/lib/Controller/ShareController.php b/apps/files_sharing/lib/Controller/ShareController.php index 2ea57068f25..3e694dc3d7d 100644 --- a/apps/files_sharing/lib/Controller/ShareController.php +++ b/apps/files_sharing/lib/Controller/ShareController.php @@ -539,6 +539,7 @@ class ShareController extends AuthPublicShareController { /** * @PublicPage * @NoCSRFRequired + * @NoSameSiteCookieRequired * * @param string $token * @param string $files diff --git a/apps/settings/l10n/fr.js b/apps/settings/l10n/fr.js index 2d53ee5b52b..c9fb3378423 100644 --- a/apps/settings/l10n/fr.js +++ b/apps/settings/l10n/fr.js @@ -132,7 +132,7 @@ OC.L10N.register( "Save changes" : "Enregistrer les modifications", "This app is supported via your current Nextcloud subscription." : "Vous supportez cette application grâce à votre abonnement Nextcloud.", "Supported" : "Pris en charge", - "Featured apps are developed by and within the community. They offer central functionality and are ready for production use." : "Les applications mise sen avant sont développées par la communauté. Elles proposent des fonctionnalités centrales et sont prêtes pour la production.", + "Featured apps are developed by and within the community. They offer central functionality and are ready for production use." : "Les applications mises en avant sont développées par la communauté. Elles proposent des fonctionnalités centrales et sont prêtes pour la production.", "Featured" : "Mises en avant", "by" : "par", "Update to {version}" : "Mise à jour vers {version}", diff --git a/apps/settings/l10n/fr.json b/apps/settings/l10n/fr.json index 04b8beb800e..8ed0257540c 100644 --- a/apps/settings/l10n/fr.json +++ b/apps/settings/l10n/fr.json @@ -130,7 +130,7 @@ "Save changes" : "Enregistrer les modifications", "This app is supported via your current Nextcloud subscription." : "Vous supportez cette application grâce à votre abonnement Nextcloud.", "Supported" : "Pris en charge", - "Featured apps are developed by and within the community. They offer central functionality and are ready for production use." : "Les applications mise sen avant sont développées par la communauté. Elles proposent des fonctionnalités centrales et sont prêtes pour la production.", + "Featured apps are developed by and within the community. They offer central functionality and are ready for production use." : "Les applications mises en avant sont développées par la communauté. Elles proposent des fonctionnalités centrales et sont prêtes pour la production.", "Featured" : "Mises en avant", "by" : "par", "Update to {version}" : "Mise à jour vers {version}", diff --git a/apps/settings/l10n/he.js b/apps/settings/l10n/he.js index fc6cf9deb13..8ca38f5dec0 100644 --- a/apps/settings/l10n/he.js +++ b/apps/settings/l10n/he.js @@ -38,10 +38,16 @@ OC.L10N.register( "{actor} added {user} to group {group}" : "ההוספה של {user} לקבוצה {group} נעשתה על ידי {actor}", "An administrator added you to group {group}" : "נוספת לקבוצה {group} על ידי מנהל", "An administrator added {user} to group {group}" : "מנהל הוסיף את {user} לקבוצה {group}", + "{actor} removed you from group {group}" : "הוסרת מהקבוצה {group} על ידי {actor}", + "You removed {user} from group {group}" : "הסרת את {user} מהקבוצה {group}", + "{actor} removed {user} from group {group}" : "ההסרה של {user} מהקבוצה {group} בוצעה על ידי {actor}", + "An administrator removed you from group {group}" : "מנהל הסיר אותך מהקבוצה {group}", + "An administrator removed {user} from group {group}" : "מנהל הסיר את {user} מהקבוצה {group}", "Your <strong>group memberships</strong> were modified" : "<strong>חברותך בקבוצות</strong> השתנתה", "{actor} changed your password" : "הססמה שלך הוחלפה על ידי {actor}", "You changed your password" : "שינית את הססמה שלך", "Your password was reset by an administrator" : "הססמה שלך אופסה על ידי מנהל", + "Your password was reset" : "הססמה שלך אופסה", "{actor} changed your email address" : "כתובת הדוא״ל שלך נערכה על ידי {actor}", "You changed your email address" : "שינית את כתובת הדוא״ל שלך", "Your email address was changed by an administrator" : "כתובת הדוא״ל שלך נערכה על ידי המנהל", @@ -103,16 +109,28 @@ OC.L10N.register( "Mobile & desktop" : "נייד ושולחן עבודה", "Create" : "יצירה", "Change" : "שינוי", + "Reshare" : "שיתוף מחדש", "Unlimited" : "ללא הגבלה", "Verifying" : "מתבצע אימות", + "Nextcloud settings" : "הגדרות Nextcloud", + "Enforce two-factor authentication" : "אכיפת אימות דו־שלבי", "Limit to groups" : "הגבלה לקבוצות", + "Enforcement of two-factor authentication can be set for certain groups only." : "ניתן להגדיר אכיפה של אימות דו־שלבי על קבוצות מסוימות בלבד.", + "Two-factor authentication is enforced for all members of the following groups." : "אימות דו־שלבי נאכף על כל החברים בקבוצות הבאות.", + "Enforced groups" : "קבוצות נאכפות", + "Two-factor authentication is not enforced for members of the following groups." : "אימות דו־שלבי לא נאכף על החברים בקבוצות הבאות.", + "Excluded groups" : "קבוצות חריגות", "Save changes" : "שמירת שינויים", + "This app is supported via your current Nextcloud subscription." : "יישומון זה נתמך על ידי מינוי ה־Nextcloud הנוכחי שלך.", + "Supported" : "נתמך", + "Featured apps are developed by and within the community. They offer central functionality and are ready for production use." : "היישומונים המומלצים מפותחים על ידי ובתוך הקהילה. הם מציעים תכונות ליבה מסוימות והן מוכנות לשימוש יומיומי.", "Featured" : "מומלץ", "by" : "מאת", "Update to {version}" : "עדכון ל־{version}", "Remove" : "הסרה", "Disable" : "ניטרול", "All" : "הכל", + "Limit app usage to groups" : "הגבלת השימוש ביישומון לקבוצות", "No results" : "אין תוצאות", "This app has no minimum Nextcloud version assigned. This will be an error in the future." : "ליישומון זה לא מוקצית גרסת Nextcloud מזערית. מצב כזה עשוי להוביל לשגיאה בעתיד.", "This app has no maximum Nextcloud version assigned. This will be an error in the future." : "ליישומון זה לא מוקצית גרסת Nextcloud מרבית. מצב כזה עשוי להוביל לשגיאה בעתיד.", @@ -124,30 +142,42 @@ OC.L10N.register( "Admin documentation" : "תיעוד מנהל", "Developer documentation" : "תיעוד מפתח", "Update to {update}" : "עדכון ל־{version}", + "Update all" : "לעדכן הכול", "Results from other categories" : "תוצאות מקטגוריות אחרות", "No apps found for your version" : "לא נמצאו יישומים לגרסה שלך", "Disable all" : "להשבית הכול", "Enable all" : "הפעלת הכול", + "_%n app has an update available_::_%n apps have an update available_" : ["לאחד היישומונים יש עדכון זמין","ל־%n יישומונים יש עדכון זמין","ל־%n יישומונים יש עדכון זמין","ל־%n יישומונים יש עדכון זמין"], "Download and enable" : "להוריד ולהפעיל", "Enable" : "הפעלה", "Enable untested app" : "לאפשר יישומון שלא נבדק", "The app will be downloaded from the app store" : "היישום ירד מחנות היישומים", "This app is not marked as compatible with your Nextcloud version. If you continue you will still be able to install the app. Note that the app might not work as expected." : "היישומון הזה לא מסומן כנתמך בגרסת ה־Nextcloud. עדיין יתאפשר לך להמשיך ולהתקין את היישומון. נא לשים לב שיכול להיות שהיישומון לא יעבוד כצפוי.", + "Marked for remote wipe" : "מסומן להשמדת נתונים מרחוק", + "Device settings" : "הגדרות מכשיר", "Allow filesystem access" : "לאפשר גישה למערכת הקבצים", "Rename" : "שינוי שם", "Revoke" : "שלילה", + "Wipe device" : "השמדת נתוני מכשיר", + "Revoking this token might prevent the wiping of your device if it hasn't started the wipe yet." : "שלילת האסימון הזו עשויה למנוע את השמדת נתוני המכשיר שלך אם ההשמדה טרם החלה.", "Internet Explorer" : "Internet Explorer", "Edge" : "Edge", "Firefox" : "Firefox", "Google Chrome" : "Google Chrome", "Safari" : "Safari", "Google Chrome for Android" : "Google Chrome עבור Android", + "iPhone" : "iPhone", + "iPad" : "iPad", + "Nextcloud iOS app" : "יישומון iOS של Nextcloud", + "Nextcloud Android app" : "יישומון Android של Nextcloud", "Sync client - {os}" : "לקוח סנכרון - {os}", "This session" : "הפעלה זו", "Device" : "התקן", "Last activity" : "פעילות אחרונה", "Devices & sessions" : "התקנים והפעלות", "Web, desktop and mobile clients currently logged in to your account." : "לקוחות שמחוברים כעת לחשבון שלך דרך דפדפן, שולחן עבודה והתקנים ניידים.", + "Do you really want to wipe your data from this device?" : "להשמיד את הנתונים שלך במכשיר הזה?", + "Confirm wipe" : "אישור ההשמדה", "Error while creating device token" : "שגיאה בזמן יצירת מחרוזת התקן", "Error while deleting the token" : "שגיאה בזמן מחיקת המחרוזת", "App name" : "שם יישום", diff --git a/apps/settings/l10n/he.json b/apps/settings/l10n/he.json index adf2f2e2cb7..0a3ec0f357d 100644 --- a/apps/settings/l10n/he.json +++ b/apps/settings/l10n/he.json @@ -36,10 +36,16 @@ "{actor} added {user} to group {group}" : "ההוספה של {user} לקבוצה {group} נעשתה על ידי {actor}", "An administrator added you to group {group}" : "נוספת לקבוצה {group} על ידי מנהל", "An administrator added {user} to group {group}" : "מנהל הוסיף את {user} לקבוצה {group}", + "{actor} removed you from group {group}" : "הוסרת מהקבוצה {group} על ידי {actor}", + "You removed {user} from group {group}" : "הסרת את {user} מהקבוצה {group}", + "{actor} removed {user} from group {group}" : "ההסרה של {user} מהקבוצה {group} בוצעה על ידי {actor}", + "An administrator removed you from group {group}" : "מנהל הסיר אותך מהקבוצה {group}", + "An administrator removed {user} from group {group}" : "מנהל הסיר את {user} מהקבוצה {group}", "Your <strong>group memberships</strong> were modified" : "<strong>חברותך בקבוצות</strong> השתנתה", "{actor} changed your password" : "הססמה שלך הוחלפה על ידי {actor}", "You changed your password" : "שינית את הססמה שלך", "Your password was reset by an administrator" : "הססמה שלך אופסה על ידי מנהל", + "Your password was reset" : "הססמה שלך אופסה", "{actor} changed your email address" : "כתובת הדוא״ל שלך נערכה על ידי {actor}", "You changed your email address" : "שינית את כתובת הדוא״ל שלך", "Your email address was changed by an administrator" : "כתובת הדוא״ל שלך נערכה על ידי המנהל", @@ -101,16 +107,28 @@ "Mobile & desktop" : "נייד ושולחן עבודה", "Create" : "יצירה", "Change" : "שינוי", + "Reshare" : "שיתוף מחדש", "Unlimited" : "ללא הגבלה", "Verifying" : "מתבצע אימות", + "Nextcloud settings" : "הגדרות Nextcloud", + "Enforce two-factor authentication" : "אכיפת אימות דו־שלבי", "Limit to groups" : "הגבלה לקבוצות", + "Enforcement of two-factor authentication can be set for certain groups only." : "ניתן להגדיר אכיפה של אימות דו־שלבי על קבוצות מסוימות בלבד.", + "Two-factor authentication is enforced for all members of the following groups." : "אימות דו־שלבי נאכף על כל החברים בקבוצות הבאות.", + "Enforced groups" : "קבוצות נאכפות", + "Two-factor authentication is not enforced for members of the following groups." : "אימות דו־שלבי לא נאכף על החברים בקבוצות הבאות.", + "Excluded groups" : "קבוצות חריגות", "Save changes" : "שמירת שינויים", + "This app is supported via your current Nextcloud subscription." : "יישומון זה נתמך על ידי מינוי ה־Nextcloud הנוכחי שלך.", + "Supported" : "נתמך", + "Featured apps are developed by and within the community. They offer central functionality and are ready for production use." : "היישומונים המומלצים מפותחים על ידי ובתוך הקהילה. הם מציעים תכונות ליבה מסוימות והן מוכנות לשימוש יומיומי.", "Featured" : "מומלץ", "by" : "מאת", "Update to {version}" : "עדכון ל־{version}", "Remove" : "הסרה", "Disable" : "ניטרול", "All" : "הכל", + "Limit app usage to groups" : "הגבלת השימוש ביישומון לקבוצות", "No results" : "אין תוצאות", "This app has no minimum Nextcloud version assigned. This will be an error in the future." : "ליישומון זה לא מוקצית גרסת Nextcloud מזערית. מצב כזה עשוי להוביל לשגיאה בעתיד.", "This app has no maximum Nextcloud version assigned. This will be an error in the future." : "ליישומון זה לא מוקצית גרסת Nextcloud מרבית. מצב כזה עשוי להוביל לשגיאה בעתיד.", @@ -122,30 +140,42 @@ "Admin documentation" : "תיעוד מנהל", "Developer documentation" : "תיעוד מפתח", "Update to {update}" : "עדכון ל־{version}", + "Update all" : "לעדכן הכול", "Results from other categories" : "תוצאות מקטגוריות אחרות", "No apps found for your version" : "לא נמצאו יישומים לגרסה שלך", "Disable all" : "להשבית הכול", "Enable all" : "הפעלת הכול", + "_%n app has an update available_::_%n apps have an update available_" : ["לאחד היישומונים יש עדכון זמין","ל־%n יישומונים יש עדכון זמין","ל־%n יישומונים יש עדכון זמין","ל־%n יישומונים יש עדכון זמין"], "Download and enable" : "להוריד ולהפעיל", "Enable" : "הפעלה", "Enable untested app" : "לאפשר יישומון שלא נבדק", "The app will be downloaded from the app store" : "היישום ירד מחנות היישומים", "This app is not marked as compatible with your Nextcloud version. If you continue you will still be able to install the app. Note that the app might not work as expected." : "היישומון הזה לא מסומן כנתמך בגרסת ה־Nextcloud. עדיין יתאפשר לך להמשיך ולהתקין את היישומון. נא לשים לב שיכול להיות שהיישומון לא יעבוד כצפוי.", + "Marked for remote wipe" : "מסומן להשמדת נתונים מרחוק", + "Device settings" : "הגדרות מכשיר", "Allow filesystem access" : "לאפשר גישה למערכת הקבצים", "Rename" : "שינוי שם", "Revoke" : "שלילה", + "Wipe device" : "השמדת נתוני מכשיר", + "Revoking this token might prevent the wiping of your device if it hasn't started the wipe yet." : "שלילת האסימון הזו עשויה למנוע את השמדת נתוני המכשיר שלך אם ההשמדה טרם החלה.", "Internet Explorer" : "Internet Explorer", "Edge" : "Edge", "Firefox" : "Firefox", "Google Chrome" : "Google Chrome", "Safari" : "Safari", "Google Chrome for Android" : "Google Chrome עבור Android", + "iPhone" : "iPhone", + "iPad" : "iPad", + "Nextcloud iOS app" : "יישומון iOS של Nextcloud", + "Nextcloud Android app" : "יישומון Android של Nextcloud", "Sync client - {os}" : "לקוח סנכרון - {os}", "This session" : "הפעלה זו", "Device" : "התקן", "Last activity" : "פעילות אחרונה", "Devices & sessions" : "התקנים והפעלות", "Web, desktop and mobile clients currently logged in to your account." : "לקוחות שמחוברים כעת לחשבון שלך דרך דפדפן, שולחן עבודה והתקנים ניידים.", + "Do you really want to wipe your data from this device?" : "להשמיד את הנתונים שלך במכשיר הזה?", + "Confirm wipe" : "אישור ההשמדה", "Error while creating device token" : "שגיאה בזמן יצירת מחרוזת התקן", "Error while deleting the token" : "שגיאה בזמן מחיקת המחרוזת", "App name" : "שם יישום", diff --git a/apps/settings/l10n/sk.js b/apps/settings/l10n/sk.js index 3bcafefb3cc..d79c09849d7 100644 --- a/apps/settings/l10n/sk.js +++ b/apps/settings/l10n/sk.js @@ -211,7 +211,9 @@ OC.L10N.register( "Common languages" : "Spoločné jazyky", "All languages" : "Všetky jazyky", "Password change is disabled because the master key is disabled" : "Zmena hesla je zablokovaná pretože hlavný kľúč je vypnutý", + "Name your device" : "Pomenujte svoje zariadenie", "Add" : "Pridať", + "Adding your device …" : "Pridáva sa zariadenie...", "Unnamed device" : "Nepomenované zariadenie", "Your apps" : "Vaše aplikácie", "Active apps" : "Aktívne aplikácie", diff --git a/apps/settings/l10n/sk.json b/apps/settings/l10n/sk.json index 85f25e78ba1..3386b5a5ec5 100644 --- a/apps/settings/l10n/sk.json +++ b/apps/settings/l10n/sk.json @@ -209,7 +209,9 @@ "Common languages" : "Spoločné jazyky", "All languages" : "Všetky jazyky", "Password change is disabled because the master key is disabled" : "Zmena hesla je zablokovaná pretože hlavný kľúč je vypnutý", + "Name your device" : "Pomenujte svoje zariadenie", "Add" : "Pridať", + "Adding your device …" : "Pridáva sa zariadenie...", "Unnamed device" : "Nepomenované zariadenie", "Your apps" : "Vaše aplikácie", "Active apps" : "Aktívne aplikácie", diff --git a/apps/settings/l10n/uk.js b/apps/settings/l10n/uk.js index 4ab518196a0..af2ea88ca75 100644 --- a/apps/settings/l10n/uk.js +++ b/apps/settings/l10n/uk.js @@ -299,7 +299,7 @@ OC.L10N.register( "Default share permissions" : "Дозволи на публікування по замовчуванню", "Personal" : "Особисте", "Administration" : "Адміністрування", - "Developed by the {communityopen}Nextcloud community{linkclose}, the {githubopen}source code{linkclose} is licensed under the {licenseopen}AGPL{linkclose}." : "Розроблено {communityopen}спільнотою Nextcloud{linkclose}, {githubopen}сирцевий код{linkclose} ліцензований під ліцензією {licenseopen}AGPL{linkclose}.", + "Developed by the {communityopen}Nextcloud community{linkclose}, the {githubopen}source code{linkclose} is licensed under the {licenseopen}AGPL{linkclose}." : "Розроблено {communityopen}спільнотою Nextcloud{linkclose}, {githubopen}початковий код{linkclose} ліцензований під ліцензією {licenseopen}AGPL{linkclose}.", "Profile picture" : "Зображення облікового запису", "Upload new" : "Завантажити нове", "Select from Files" : "Вибрати з файлів", diff --git a/apps/settings/l10n/uk.json b/apps/settings/l10n/uk.json index c3a2fead986..445ca037dca 100644 --- a/apps/settings/l10n/uk.json +++ b/apps/settings/l10n/uk.json @@ -297,7 +297,7 @@ "Default share permissions" : "Дозволи на публікування по замовчуванню", "Personal" : "Особисте", "Administration" : "Адміністрування", - "Developed by the {communityopen}Nextcloud community{linkclose}, the {githubopen}source code{linkclose} is licensed under the {licenseopen}AGPL{linkclose}." : "Розроблено {communityopen}спільнотою Nextcloud{linkclose}, {githubopen}сирцевий код{linkclose} ліцензований під ліцензією {licenseopen}AGPL{linkclose}.", + "Developed by the {communityopen}Nextcloud community{linkclose}, the {githubopen}source code{linkclose} is licensed under the {licenseopen}AGPL{linkclose}." : "Розроблено {communityopen}спільнотою Nextcloud{linkclose}, {githubopen}початковий код{linkclose} ліцензований під ліцензією {licenseopen}AGPL{linkclose}.", "Profile picture" : "Зображення облікового запису", "Upload new" : "Завантажити нове", "Select from Files" : "Вибрати з файлів", diff --git a/apps/sharebymail/l10n/sk.js b/apps/sharebymail/l10n/sk.js index f8790dd222f..89f0792dc1b 100644 --- a/apps/sharebymail/l10n/sk.js +++ b/apps/sharebymail/l10n/sk.js @@ -5,6 +5,10 @@ OC.L10N.register( "Shared with {email}" : "Sprístupnené pre {email}", "Shared with %1$s by %2$s" : "Sprístupnené používateľovi %1$s používateľom %2$s", "Shared with {email} by {actor}" : "Sprístupnené {email} používateľom {actor}", + "Unshared from %1$s" : "Zdieľanie zrušené od %1$s", + "Unshared from {email}" : "Zdieľanie zrušené od {email}", + "Unshared from %1$s by %2$s" : "%2$s zrušil zdieľanie od %1$s", + "Unshared from {email} by {actor}" : "{actor} zrušil zdieľanie od {email}", "Password for mail share sent to %1$s" : "Heslo na sprístupnenie bolo zaslané %1$s", "Password for mail share sent to {email}" : "Heslo na sprístupnenie bolo zaslané {email}", "Password for mail share sent to you" : "Heslo na sprístupnenie Vám bolo zaslané", @@ -12,6 +16,10 @@ OC.L10N.register( "You shared {file} with {email} by mail" : "Sprístupnili ste {file} používateľovi {email} pomocou emailu", "%3$s shared %1$s with %2$s by mail" : "%3$s sprístupnil %1$s používateľovi %2$s pomocou emailu", "{actor} shared {file} with {email} by mail" : "{actor} sprístupnil {file} používateľovi {email} pomocou emailu", + "You unshared %1$s from %2$s by mail" : "E-mailom ste zrušili zdieľanie %1$s od %2$s", + "You unshared {file} from {email} by mail" : "E-mailom ste zrušili zdieľanie {file} od {email}", + "%3$s unshared %1$s from %2$s by mail" : "%3$s e-mailom zrušil zdieľanie %1$s od %2$s", + "{actor} unshared {file} from {email} by mail" : "{actor} e-mailom zrušil zdieľanie {file} od {email}", "Password to access %1$s was sent to %2s" : "Heslo na sprístupnenie %1$s bolo zaslané %2s", "Password to access {file} was sent to {email}" : "Heslo na sprístupnenie {file} bolo zaslané na {email}", "Password to access %1$s was sent to you" : "Heslo na sprístupnenie %1$s Vám bolo zaslané", @@ -32,10 +40,13 @@ OC.L10N.register( "%1$s shared »%2$s« with you and wants to add:" : "%1$s vám sprístupnil »%2$s« s poznámkou:", "%1$s shared »%2$s« with you and wants to add" : "%1$s vám sprístupnil »%2$s« s poznámkou", "»%s« added a note to a file shared with you" : "»%s« pridal poznámku k súboru ktorý s Vami zdieľa", + "You just shared »%1$s« with %2$s. The share was already sent to the recipient. Due to the security policies defined by the administrator of %3$s each share needs to be protected by password and it is not allowed to send the password directly to the recipient. Therefore you need to forward the password manually to the recipient." : "Práve ste zdieľali „%1$s“ s %2$s. Zdieľanie už bolo odoslané príjemcovi. Z dôvodu bezpečnostných pravidiel definovaných správcom %3$s musí byť každá zdieľaná položka chránená heslom, ktoré nemôže byť priamo poslané príjemcovi. Preto musíte heslo poslať príjemcovi ručne.", + "Password to access »%1$s« shared by you with %2$s" : "Heslo pre prístup k „%1$s“, ktoré zdieľate s %2$s", "This is the password:" : "Toto je heslo:", "You can choose a different password at any time in the share dialog." : "Kedykoľvek môžete vybrať iné heslo v okne zdieľania.", "Could not find share" : "Nebolo možné nájsť sprístupnenie", "Share by mail" : "Sprístupniť e-mailom", + "Share provider which allows you to share files by mail" : "Poskytovateľ zdieľania umožňuje zdieľať súbory pomocou e-mailu", "Allows users to share a personalized link to a file or folder by putting in an email address." : "Povoľuje používateľom zdieľať personalizovaný odkaz na súbor alebo priečinok zadaním e-mailovej adresy.", "Send password by mail" : "Odoslať heslo e-mailom", "Enforce password protection" : "Vynútiť ochranu heslom" diff --git a/apps/sharebymail/l10n/sk.json b/apps/sharebymail/l10n/sk.json index 90eff20c74d..6cb5576d1a0 100644 --- a/apps/sharebymail/l10n/sk.json +++ b/apps/sharebymail/l10n/sk.json @@ -3,6 +3,10 @@ "Shared with {email}" : "Sprístupnené pre {email}", "Shared with %1$s by %2$s" : "Sprístupnené používateľovi %1$s používateľom %2$s", "Shared with {email} by {actor}" : "Sprístupnené {email} používateľom {actor}", + "Unshared from %1$s" : "Zdieľanie zrušené od %1$s", + "Unshared from {email}" : "Zdieľanie zrušené od {email}", + "Unshared from %1$s by %2$s" : "%2$s zrušil zdieľanie od %1$s", + "Unshared from {email} by {actor}" : "{actor} zrušil zdieľanie od {email}", "Password for mail share sent to %1$s" : "Heslo na sprístupnenie bolo zaslané %1$s", "Password for mail share sent to {email}" : "Heslo na sprístupnenie bolo zaslané {email}", "Password for mail share sent to you" : "Heslo na sprístupnenie Vám bolo zaslané", @@ -10,6 +14,10 @@ "You shared {file} with {email} by mail" : "Sprístupnili ste {file} používateľovi {email} pomocou emailu", "%3$s shared %1$s with %2$s by mail" : "%3$s sprístupnil %1$s používateľovi %2$s pomocou emailu", "{actor} shared {file} with {email} by mail" : "{actor} sprístupnil {file} používateľovi {email} pomocou emailu", + "You unshared %1$s from %2$s by mail" : "E-mailom ste zrušili zdieľanie %1$s od %2$s", + "You unshared {file} from {email} by mail" : "E-mailom ste zrušili zdieľanie {file} od {email}", + "%3$s unshared %1$s from %2$s by mail" : "%3$s e-mailom zrušil zdieľanie %1$s od %2$s", + "{actor} unshared {file} from {email} by mail" : "{actor} e-mailom zrušil zdieľanie {file} od {email}", "Password to access %1$s was sent to %2s" : "Heslo na sprístupnenie %1$s bolo zaslané %2s", "Password to access {file} was sent to {email}" : "Heslo na sprístupnenie {file} bolo zaslané na {email}", "Password to access %1$s was sent to you" : "Heslo na sprístupnenie %1$s Vám bolo zaslané", @@ -30,10 +38,13 @@ "%1$s shared »%2$s« with you and wants to add:" : "%1$s vám sprístupnil »%2$s« s poznámkou:", "%1$s shared »%2$s« with you and wants to add" : "%1$s vám sprístupnil »%2$s« s poznámkou", "»%s« added a note to a file shared with you" : "»%s« pridal poznámku k súboru ktorý s Vami zdieľa", + "You just shared »%1$s« with %2$s. The share was already sent to the recipient. Due to the security policies defined by the administrator of %3$s each share needs to be protected by password and it is not allowed to send the password directly to the recipient. Therefore you need to forward the password manually to the recipient." : "Práve ste zdieľali „%1$s“ s %2$s. Zdieľanie už bolo odoslané príjemcovi. Z dôvodu bezpečnostných pravidiel definovaných správcom %3$s musí byť každá zdieľaná položka chránená heslom, ktoré nemôže byť priamo poslané príjemcovi. Preto musíte heslo poslať príjemcovi ručne.", + "Password to access »%1$s« shared by you with %2$s" : "Heslo pre prístup k „%1$s“, ktoré zdieľate s %2$s", "This is the password:" : "Toto je heslo:", "You can choose a different password at any time in the share dialog." : "Kedykoľvek môžete vybrať iné heslo v okne zdieľania.", "Could not find share" : "Nebolo možné nájsť sprístupnenie", "Share by mail" : "Sprístupniť e-mailom", + "Share provider which allows you to share files by mail" : "Poskytovateľ zdieľania umožňuje zdieľať súbory pomocou e-mailu", "Allows users to share a personalized link to a file or folder by putting in an email address." : "Povoľuje používateľom zdieľať personalizovaný odkaz na súbor alebo priečinok zadaním e-mailovej adresy.", "Send password by mail" : "Odoslať heslo e-mailom", "Enforce password protection" : "Vynútiť ochranu heslom" diff --git a/apps/twofactor_backupcodes/l10n/sk.js b/apps/twofactor_backupcodes/l10n/sk.js index 2ccb5a0b897..516b90e3d2e 100644 --- a/apps/twofactor_backupcodes/l10n/sk.js +++ b/apps/twofactor_backupcodes/l10n/sk.js @@ -4,17 +4,24 @@ OC.L10N.register( "You created two-factor backup codes for your account" : "Vytvorili ste záložné kódy dvojfaktorového overovania pre váš účet", "Second-factor backup codes" : "Dvojfaktorové záložné kódy", "Generate backup codes" : "Vytvoriť záložné kódy", + "You enabled two-factor authentication but did not generate backup codes yet. They are needed to restore access to your account in case you lose your second factor." : "Povolili ste dvojfaktorové overenie totožnosti, zatiaľ ste však nevygenerovali záložné kódy. V prípade straty druhého faktora sú potrebné na obnovenie prístupu k vášmu účtu.", "Backup code" : "Záložný kód", "Use backup code" : "Použiť záložný kód", "Two factor backup codes" : "Záložné kódy pre dvojfaktorové overovanie", "A two-factor auth backup codes provider" : "Poskytovateľ záložných kódov pre dvojfaktorové overovanie", + "Backup codes have been generated. {used} of {total} codes have been used." : "Boli vygenerované záložné kódy. Boli použité {použité} z {celkom} kódov.", "These are your backup codes. Please save and/or print them as you will not be able to read the codes again later" : "Tu sú vaše záložné kódy. Uložte si ich prosím alebo vytlačte, pretože ich nebude možné neskôr znovu zobraziť.", "Save backup codes" : "Uložiť záložné kódy", "Print backup codes" : "Vytlačiť záložné kódy", "Regenerate backup codes" : "Vytvoriť nové záložné kódy", "If you regenerate backup codes, you automatically invalidate old codes." : "Ak vytvoríte nové záložné kódy, staré sa automaticky zneplatnia.", "An error occurred while generating your backup codes" : "Pri vytváraní záložných kódov nastala chyba.", + "{name} backup codes" : "{name} záložné kódy", + "Use one of the backup codes you saved when setting up two-factor authentication." : "Pri nastavovaní dvojfaktorového overovania totožnosti použite jeden zo záložných kódov, ktoré ste uložili.", "Submit" : "Odoslať", - "_" : "_" + "_" : "_", + "_icon-loading-small_::_generate-backup-codes_" : ["načítanie-malej-ikony","vytvoriť-záložné-kódy","vytvoriť-záložné-kódy","vytvoriť-záložné-kódy"], + "You have enabled two-factor authentication but have not yet generated backup codes. Be sure to do this in case you lose access to your second factor." : "Povolili ste dvojfaktorové overenie, ale ešte ste nevytvorili záložné kódy. Určite to urobte pre prípad, že stratíte prístup k druhému faktoru.", + "function" : "funkcia" }, "nplurals=4; plural=(n % 1 == 0 && n == 1 ? 0 : n % 1 == 0 && n >= 2 && n <= 4 ? 1 : n % 1 != 0 ? 2: 3);"); diff --git a/apps/twofactor_backupcodes/l10n/sk.json b/apps/twofactor_backupcodes/l10n/sk.json index b731ffc742c..614fc020263 100644 --- a/apps/twofactor_backupcodes/l10n/sk.json +++ b/apps/twofactor_backupcodes/l10n/sk.json @@ -2,17 +2,24 @@ "You created two-factor backup codes for your account" : "Vytvorili ste záložné kódy dvojfaktorového overovania pre váš účet", "Second-factor backup codes" : "Dvojfaktorové záložné kódy", "Generate backup codes" : "Vytvoriť záložné kódy", + "You enabled two-factor authentication but did not generate backup codes yet. They are needed to restore access to your account in case you lose your second factor." : "Povolili ste dvojfaktorové overenie totožnosti, zatiaľ ste však nevygenerovali záložné kódy. V prípade straty druhého faktora sú potrebné na obnovenie prístupu k vášmu účtu.", "Backup code" : "Záložný kód", "Use backup code" : "Použiť záložný kód", "Two factor backup codes" : "Záložné kódy pre dvojfaktorové overovanie", "A two-factor auth backup codes provider" : "Poskytovateľ záložných kódov pre dvojfaktorové overovanie", + "Backup codes have been generated. {used} of {total} codes have been used." : "Boli vygenerované záložné kódy. Boli použité {použité} z {celkom} kódov.", "These are your backup codes. Please save and/or print them as you will not be able to read the codes again later" : "Tu sú vaše záložné kódy. Uložte si ich prosím alebo vytlačte, pretože ich nebude možné neskôr znovu zobraziť.", "Save backup codes" : "Uložiť záložné kódy", "Print backup codes" : "Vytlačiť záložné kódy", "Regenerate backup codes" : "Vytvoriť nové záložné kódy", "If you regenerate backup codes, you automatically invalidate old codes." : "Ak vytvoríte nové záložné kódy, staré sa automaticky zneplatnia.", "An error occurred while generating your backup codes" : "Pri vytváraní záložných kódov nastala chyba.", + "{name} backup codes" : "{name} záložné kódy", + "Use one of the backup codes you saved when setting up two-factor authentication." : "Pri nastavovaní dvojfaktorového overovania totožnosti použite jeden zo záložných kódov, ktoré ste uložili.", "Submit" : "Odoslať", - "_" : "_" + "_" : "_", + "_icon-loading-small_::_generate-backup-codes_" : ["načítanie-malej-ikony","vytvoriť-záložné-kódy","vytvoriť-záložné-kódy","vytvoriť-záložné-kódy"], + "You have enabled two-factor authentication but have not yet generated backup codes. Be sure to do this in case you lose access to your second factor." : "Povolili ste dvojfaktorové overenie, ale ešte ste nevytvorili záložné kódy. Určite to urobte pre prípad, že stratíte prístup k druhému faktoru.", + "function" : "funkcia" },"pluralForm" :"nplurals=4; plural=(n % 1 == 0 && n == 1 ? 0 : n % 1 == 0 && n >= 2 && n <= 4 ? 1 : n % 1 != 0 ? 2: 3);" }
\ No newline at end of file diff --git a/apps/updatenotification/l10n/he.js b/apps/updatenotification/l10n/he.js index 733a114e1ef..6778f56e19f 100644 --- a/apps/updatenotification/l10n/he.js +++ b/apps/updatenotification/l10n/he.js @@ -25,6 +25,7 @@ OC.L10N.register( "Checking apps for compatible updates" : "היישומונים נבדקים לאיתור עדכונים תואמים", "Please make sure your config.php does not set <samp>appstoreenabled</samp> to false." : "נא לוודא שב־config.php שלך ה־<samp>appstoreenabled</samp> לא מוגדר ל־false.", "Could not connect to the appstore or the appstore returned no updates at all. Search manually for updates or make sure your server has access to the internet and can connect to the appstore." : "לא ניתן להתחבר לחנות היישומונים או שחנות היישומונים לא החזירה שיש עדכונים. ניתן לחפש ידנית עדכונים או לוודא שלשרת שלך יש גישה לאינטרנט ושיש לו איך להתחבר לחנות היישומונים.", - "View changelog" : "הצגת יומן שינויים" + "View changelog" : "הצגת יומן שינויים", + "Beta" : "בטא" }, "nplurals=4; plural=(n == 1 && n % 1 == 0) ? 0 : (n == 2 && n % 1 == 0) ? 1: (n % 10 == 0 && n % 1 == 0 && n > 10) ? 2 : 3;"); diff --git a/apps/updatenotification/l10n/he.json b/apps/updatenotification/l10n/he.json index 5e26a55e040..b544df8f630 100644 --- a/apps/updatenotification/l10n/he.json +++ b/apps/updatenotification/l10n/he.json @@ -23,6 +23,7 @@ "Checking apps for compatible updates" : "היישומונים נבדקים לאיתור עדכונים תואמים", "Please make sure your config.php does not set <samp>appstoreenabled</samp> to false." : "נא לוודא שב־config.php שלך ה־<samp>appstoreenabled</samp> לא מוגדר ל־false.", "Could not connect to the appstore or the appstore returned no updates at all. Search manually for updates or make sure your server has access to the internet and can connect to the appstore." : "לא ניתן להתחבר לחנות היישומונים או שחנות היישומונים לא החזירה שיש עדכונים. ניתן לחפש ידנית עדכונים או לוודא שלשרת שלך יש גישה לאינטרנט ושיש לו איך להתחבר לחנות היישומונים.", - "View changelog" : "הצגת יומן שינויים" + "View changelog" : "הצגת יומן שינויים", + "Beta" : "בטא" },"pluralForm" :"nplurals=4; plural=(n == 1 && n % 1 == 0) ? 0 : (n == 2 && n % 1 == 0) ? 1: (n % 10 == 0 && n % 1 == 0 && n > 10) ? 2 : 3;" }
\ No newline at end of file diff --git a/apps/updatenotification/l10n/sk.js b/apps/updatenotification/l10n/sk.js index 9110869ee48..a70561d4b37 100644 --- a/apps/updatenotification/l10n/sk.js +++ b/apps/updatenotification/l10n/sk.js @@ -10,7 +10,9 @@ OC.L10N.register( "Update for %1$s to version %2$s is available." : "Pre %1$s je dostupná aktualizácia na verziu %2$s.", "Update for {app} to version %s is available." : "Pre {app} je dostupná aktualizácia na verziu %s.", "Update notification" : "Aktualizovať hlásenie", + "Displays update notifications for Nextcloud and provides the SSO for the updater." : "Zobrazí upozornenie na aktualizáciu pre Nextcloud a poskytuje zjednotené prihlasovanie pre aktualizátor.", "The version you are running is not maintained anymore. Please make sure to update to a supported version as soon as possible." : "Verzia, ktorú používate už nie je podporovaná. Čím skôr aktualizujte na podporovanú verziu prosím.", + "Apps missing updates" : "Aplikácie s chýbajúcimi aktualizáciami", "View in store" : "Zobraziť v obchode", "Apps with available updates" : "Aplikácie pre ktoré sú dostupné aktualizácie", "Open updater" : "Otvoriť aktualizátor", @@ -20,6 +22,7 @@ OC.L10N.register( "Your version is up to date." : "Vaša verzia je atuálna.", "A non-default update server is in use to be checked for updates:" : "Pre kontrolu aktualizácií sa používa iný než predvolený server:", "Update channel:" : "Aktualizačný kanál:", + "You can always update to a newer version. But you can never downgrade to a more stable version." : "Vždy môžete aktualizovať na novšiu verziu, ale nikdy nemôžete prejsť na nižšiu, stabilnejšiu verziu.", "Note that after a new release it can take some time before it shows up here. We roll out new versions spread out over time to our users and sometimes skip a version when issues are found." : "Upozorňujeme, že po oficiálnom vydaní novej verzie môže chvíľu trvať, než sa tu objaví. Nové verzie medzi našich používateľov distribuujeme priebežne rozložené v čase a ak sa nájdu chyby, niekedy danú verziu preskočíme.", "Notify members of the following groups about available updates:" : "Upozorňovať členov nasledujúcich skupín o dostupných aktualizáciach:", "Only notification for app updates are available." : "Sú dostupné upozornenia iba pre aktualizácie aplikácií.", @@ -32,7 +35,14 @@ OC.L10N.register( "Could not connect to the appstore or the appstore returned no updates at all. Search manually for updates or make sure your server has access to the internet and can connect to the appstore." : "Nepodarilo sa pripojiť k obchodu s aplikáciami alebo obchod nemá žiadne aktualizácie. Aktualizácie hľadajte manuálne alebo sa uistite, že Váš server má prístup na internet a že sa môže pripojiť k obchodu.", "<strong>All</strong> apps have an update for this version available" : "<strong>Všetky</strong> aplikácie majú dostupnú aktualizáciu", "View changelog" : "Zobraziť súhrn zmien", + "Enterprise" : "Podnikové", + "For enterprise use. Provides always the latest patch level, but will not update to the next major release immediately. That update happens once Nextcloud GmbH has done additional hardening and testing for large-scale and mission-critical deployments. This channel is only available to customers and provides the Nextcloud Enterprise package." : "Pre podnikové použitie. Poskytuje vždy najnovšiu úroveň opráv, ale neaktualizuje sa okamžite na ďalšie hlavné vydanie. K tejto aktualizácii dôjde, keď Nextcloud GmbH urobí ďalšie zodolnenie a testovanie pre veľké a kritické nasadenia. Tento kanál je k dispozícii iba pre zákazníkov a poskytuje balík Nextcloud Enterprise.", + "Stable" : "Stabilná", + "The most recent stable version. It is suited for regular use and will always update to the latest major version." : "Najnovšia stabilná verzia. Je vhodná na bežné používanie a vždy sa aktualizuje na najnovšiu hlavnú verziu.", "Beta" : "Beta", - "_<strong>%n</strong> app has no update for this version available_::_<strong>%n</strong> apps have no update for this version available_" : ["<strong>%n</strong> aplikácia nemá dostupnú aktualizáciu na túto verziu","<strong>%n</strong> aplikácie nemá dostupnú aktualizáciu na túto verziu ","<strong>%n</strong> aplikácií nemá dostupnú aktualizáciu na túto verziu","<strong>%n</strong>aplikácií nemá dostupnú aktualizáciu na túto verziu"] + "A pre-release version only for testing new features, not for production environments." : "Verzia pred vydaním iba na testovanie nových funkcií, nie pre praktické nasadenie.", + "_<strong>%n</strong> app has no update for this version available_::_<strong>%n</strong> apps have no update for this version available_" : ["<strong>%n</strong> aplikácia nemá dostupnú aktualizáciu na túto verziu","<strong>%n</strong> aplikácie nemá dostupnú aktualizáciu na túto verziu ","<strong>%n</strong> aplikácií nemá dostupnú aktualizáciu na túto verziu","<strong>%n</strong>aplikácií nemá dostupnú aktualizáciu na túto verziu"], + "Production" : "Produkčná", + "Will always provide the latest patch level, but not update to the next major release immediately. That update usually happens with the second minor release (x.0.2) and only if the instance is already on the latest minor version." : "Vždy poskytne najnovšiu úroveň opráv, ale neaktualizuje sa okamžite na ďalšie hlavné vydanie. Táto aktualizácia sa zvyčajne deje s druhou menšou verziou (x.0.2) a iba v prípade, že inštancia je už v najnovšej menšej verzii." }, "nplurals=4; plural=(n % 1 == 0 && n == 1 ? 0 : n % 1 == 0 && n >= 2 && n <= 4 ? 1 : n % 1 != 0 ? 2: 3);"); diff --git a/apps/updatenotification/l10n/sk.json b/apps/updatenotification/l10n/sk.json index d1d2ac8ba33..de0c94b065d 100644 --- a/apps/updatenotification/l10n/sk.json +++ b/apps/updatenotification/l10n/sk.json @@ -8,7 +8,9 @@ "Update for %1$s to version %2$s is available." : "Pre %1$s je dostupná aktualizácia na verziu %2$s.", "Update for {app} to version %s is available." : "Pre {app} je dostupná aktualizácia na verziu %s.", "Update notification" : "Aktualizovať hlásenie", + "Displays update notifications for Nextcloud and provides the SSO for the updater." : "Zobrazí upozornenie na aktualizáciu pre Nextcloud a poskytuje zjednotené prihlasovanie pre aktualizátor.", "The version you are running is not maintained anymore. Please make sure to update to a supported version as soon as possible." : "Verzia, ktorú používate už nie je podporovaná. Čím skôr aktualizujte na podporovanú verziu prosím.", + "Apps missing updates" : "Aplikácie s chýbajúcimi aktualizáciami", "View in store" : "Zobraziť v obchode", "Apps with available updates" : "Aplikácie pre ktoré sú dostupné aktualizácie", "Open updater" : "Otvoriť aktualizátor", @@ -18,6 +20,7 @@ "Your version is up to date." : "Vaša verzia je atuálna.", "A non-default update server is in use to be checked for updates:" : "Pre kontrolu aktualizácií sa používa iný než predvolený server:", "Update channel:" : "Aktualizačný kanál:", + "You can always update to a newer version. But you can never downgrade to a more stable version." : "Vždy môžete aktualizovať na novšiu verziu, ale nikdy nemôžete prejsť na nižšiu, stabilnejšiu verziu.", "Note that after a new release it can take some time before it shows up here. We roll out new versions spread out over time to our users and sometimes skip a version when issues are found." : "Upozorňujeme, že po oficiálnom vydaní novej verzie môže chvíľu trvať, než sa tu objaví. Nové verzie medzi našich používateľov distribuujeme priebežne rozložené v čase a ak sa nájdu chyby, niekedy danú verziu preskočíme.", "Notify members of the following groups about available updates:" : "Upozorňovať členov nasledujúcich skupín o dostupných aktualizáciach:", "Only notification for app updates are available." : "Sú dostupné upozornenia iba pre aktualizácie aplikácií.", @@ -30,7 +33,14 @@ "Could not connect to the appstore or the appstore returned no updates at all. Search manually for updates or make sure your server has access to the internet and can connect to the appstore." : "Nepodarilo sa pripojiť k obchodu s aplikáciami alebo obchod nemá žiadne aktualizácie. Aktualizácie hľadajte manuálne alebo sa uistite, že Váš server má prístup na internet a že sa môže pripojiť k obchodu.", "<strong>All</strong> apps have an update for this version available" : "<strong>Všetky</strong> aplikácie majú dostupnú aktualizáciu", "View changelog" : "Zobraziť súhrn zmien", + "Enterprise" : "Podnikové", + "For enterprise use. Provides always the latest patch level, but will not update to the next major release immediately. That update happens once Nextcloud GmbH has done additional hardening and testing for large-scale and mission-critical deployments. This channel is only available to customers and provides the Nextcloud Enterprise package." : "Pre podnikové použitie. Poskytuje vždy najnovšiu úroveň opráv, ale neaktualizuje sa okamžite na ďalšie hlavné vydanie. K tejto aktualizácii dôjde, keď Nextcloud GmbH urobí ďalšie zodolnenie a testovanie pre veľké a kritické nasadenia. Tento kanál je k dispozícii iba pre zákazníkov a poskytuje balík Nextcloud Enterprise.", + "Stable" : "Stabilná", + "The most recent stable version. It is suited for regular use and will always update to the latest major version." : "Najnovšia stabilná verzia. Je vhodná na bežné používanie a vždy sa aktualizuje na najnovšiu hlavnú verziu.", "Beta" : "Beta", - "_<strong>%n</strong> app has no update for this version available_::_<strong>%n</strong> apps have no update for this version available_" : ["<strong>%n</strong> aplikácia nemá dostupnú aktualizáciu na túto verziu","<strong>%n</strong> aplikácie nemá dostupnú aktualizáciu na túto verziu ","<strong>%n</strong> aplikácií nemá dostupnú aktualizáciu na túto verziu","<strong>%n</strong>aplikácií nemá dostupnú aktualizáciu na túto verziu"] + "A pre-release version only for testing new features, not for production environments." : "Verzia pred vydaním iba na testovanie nových funkcií, nie pre praktické nasadenie.", + "_<strong>%n</strong> app has no update for this version available_::_<strong>%n</strong> apps have no update for this version available_" : ["<strong>%n</strong> aplikácia nemá dostupnú aktualizáciu na túto verziu","<strong>%n</strong> aplikácie nemá dostupnú aktualizáciu na túto verziu ","<strong>%n</strong> aplikácií nemá dostupnú aktualizáciu na túto verziu","<strong>%n</strong>aplikácií nemá dostupnú aktualizáciu na túto verziu"], + "Production" : "Produkčná", + "Will always provide the latest patch level, but not update to the next major release immediately. That update usually happens with the second minor release (x.0.2) and only if the instance is already on the latest minor version." : "Vždy poskytne najnovšiu úroveň opráv, ale neaktualizuje sa okamžite na ďalšie hlavné vydanie. Táto aktualizácia sa zvyčajne deje s druhou menšou verziou (x.0.2) a iba v prípade, že inštancia je už v najnovšej menšej verzii." },"pluralForm" :"nplurals=4; plural=(n % 1 == 0 && n == 1 ? 0 : n % 1 == 0 && n >= 2 && n <= 4 ? 1 : n % 1 != 0 ? 2: 3);" }
\ No newline at end of file diff --git a/apps/user_ldap/l10n/ca.js b/apps/user_ldap/l10n/ca.js index 4033da0b326..595c800b40d 100644 --- a/apps/user_ldap/l10n/ca.js +++ b/apps/user_ldap/l10n/ca.js @@ -176,6 +176,7 @@ OC.L10N.register( "Email Field" : "Camp de correu electrònic", "Set the user's email from their LDAP attribute. Leave it empty for default behaviour." : "Estableix l'adreça de correu a partir del seu atribut LDAP. Deixeu buit pel comportament predeterminat.", "User Home Folder Naming Rule" : "Norma per anomenar la carpeta arrel d'usuari", + "Leave empty for username (default). Otherwise, specify an LDAP/AD attribute." : "Deixar buit pel nom d'usuari (per defecte). Altrament, especificar un atribut LDAP/AD.", "\"$home\" Placeholder Field" : "Camp de text variable per \"$home\" ", "$home in an external storage configuration will be replaced with the value of the specified attribute" : "En la configuració d'un emmagatzematge extern es reemplaçarà $home amb el valor de l'atribut especificat", "Internal Username" : "Nom d'usuari intern", diff --git a/apps/user_ldap/l10n/ca.json b/apps/user_ldap/l10n/ca.json index 05b732fd30a..96a09db1c70 100644 --- a/apps/user_ldap/l10n/ca.json +++ b/apps/user_ldap/l10n/ca.json @@ -174,6 +174,7 @@ "Email Field" : "Camp de correu electrònic", "Set the user's email from their LDAP attribute. Leave it empty for default behaviour." : "Estableix l'adreça de correu a partir del seu atribut LDAP. Deixeu buit pel comportament predeterminat.", "User Home Folder Naming Rule" : "Norma per anomenar la carpeta arrel d'usuari", + "Leave empty for username (default). Otherwise, specify an LDAP/AD attribute." : "Deixar buit pel nom d'usuari (per defecte). Altrament, especificar un atribut LDAP/AD.", "\"$home\" Placeholder Field" : "Camp de text variable per \"$home\" ", "$home in an external storage configuration will be replaced with the value of the specified attribute" : "En la configuració d'un emmagatzematge extern es reemplaçarà $home amb el valor de l'atribut especificat", "Internal Username" : "Nom d'usuari intern", diff --git a/apps/user_ldap/lib/Access.php b/apps/user_ldap/lib/Access.php index 1b3a97cef95..251c0eeeaa7 100644 --- a/apps/user_ldap/lib/Access.php +++ b/apps/user_ldap/lib/Access.php @@ -61,6 +61,7 @@ use OCP\IUserManager; /** * Class Access + * * @package OCA\User_LDAP */ class Access extends LDAPUtility { @@ -75,6 +76,7 @@ class Access extends LDAPUtility { /** * protected $cookies = []; + * * @var AbstractMapping $userMapper */ protected $userMapper; @@ -114,6 +116,7 @@ class Access extends LDAPUtility { /** * sets the User Mapper + * * @param AbstractMapping $mapper */ public function setUserMapper(AbstractMapping $mapper) { @@ -122,8 +125,9 @@ class Access extends LDAPUtility { /** * returns the User Mapper - * @throws \Exception + * * @return AbstractMapping + * @throws \Exception */ public function getUserMapper() { if (is_null($this->userMapper)) { @@ -134,6 +138,7 @@ class Access extends LDAPUtility { /** * sets the Group Mapper + * * @param AbstractMapping $mapper */ public function setGroupMapper(AbstractMapping $mapper) { @@ -142,8 +147,9 @@ class Access extends LDAPUtility { /** * returns the Group Mapper - * @throws \Exception + * * @return AbstractMapping + * @throws \Exception */ public function getGroupMapper() { if (is_null($this->groupMapper)) { @@ -161,6 +167,7 @@ class Access extends LDAPUtility { /** * returns the Connection instance + * * @return \OCA\User_LDAP\Connection */ public function getConnection() { @@ -226,7 +233,7 @@ class Access extends LDAPUtility { $result = $this->extractRangeData($result, $attr); if (!empty($result)) { $normalizedResult = $this->extractAttributeValuesFromResult( - [ $attr => $result['values'] ], + [$attr => $result['values']], $attr ); $values = array_merge($values, $normalizedResult); @@ -236,14 +243,14 @@ class Access extends LDAPUtility { // no more results left return $values; } else { - $low = $result['rangeHigh'] + 1; + $low = $result['rangeHigh'] + 1; $attrToRead = $result['attributeName'] . ';range=' . $low . '-*'; $isRangeRequest = true; } } } while ($isRangeRequest); - \OCP\Util::writeLog('user_ldap', 'Requested attribute '.$attr.' not found for '.$dn, ILogger::DEBUG); + \OCP\Util::writeLog('user_ldap', 'Requested attribute ' . $attr . ' not found for ' . $dn, ILogger::DEBUG); return false; } @@ -300,7 +307,7 @@ class Access extends LDAPUtility { $values = []; if (isset($result[$attribute]) && $result[$attribute]['count'] > 0) { $lowercaseAttribute = strtolower($attribute); - for ($i=0;$i<$result[$attribute]['count'];$i++) { + for ($i = 0; $i < $result[$attribute]['count']; $i++) { if ($this->resemblesDN($attribute)) { $values[] = $this->helper->sanitizeDN($result[$attribute][$i]); } elseif ($lowercaseAttribute === 'objectguid' || $lowercaseAttribute === 'guid') { @@ -365,14 +372,15 @@ class Access extends LDAPUtility { try { // try PASSWD extended operation first return @$this->invokeLDAPMethod('exopPasswd', $cr, $userDN, '', $password) || - @$this->invokeLDAPMethod('modReplace', $cr, $userDN, $password); + @$this->invokeLDAPMethod('modReplace', $cr, $userDN, $password); } catch (ConstraintViolationException $e) { - throw new HintException('Password change rejected.', \OC::$server->getL10N('user_ldap')->t('Password change rejected. Hint: ').$e->getMessage(), $e->getCode()); + throw new HintException('Password change rejected.', \OC::$server->getL10N('user_ldap')->t('Password change rejected. Hint: ') . $e->getMessage(), $e->getCode()); } } /** * checks whether the given attributes value is probably a DN + * * @param string $attr the attribute in question * @return boolean if so true, otherwise false */ @@ -389,6 +397,7 @@ class Access extends LDAPUtility { /** * checks whether the given string is probably a DN + * * @param string $string * @return boolean */ @@ -403,6 +412,7 @@ class Access extends LDAPUtility { * returns a DN-string that is cleaned from not domain parts, e.g. * cn=foo,cn=bar,dc=foobar,dc=server,dc=org * becomes dc=foobar,dc=server,dc=org + * * @param string $dn * @return string */ @@ -427,6 +437,7 @@ class Access extends LDAPUtility { /** * returns the LDAP DN for the given internal Nextcloud name of the group + * * @param string $name the Nextcloud name in question * @return string|false LDAP DN on success, otherwise false */ @@ -436,6 +447,7 @@ class Access extends LDAPUtility { /** * returns the LDAP DN for the given internal Nextcloud name of the user + * * @param string $name the Nextcloud name in question * @return string|false with the LDAP DN on success, otherwise false */ @@ -471,45 +483,6 @@ class Access extends LDAPUtility { } /** - * accepts an array of group DNs and tests whether they match the user - * filter by doing read operations against the group entries. Returns an - * array of DNs that match the filter. - * - * @param string[] $groupDNs - * @return string[] - * @throws ServerNotAvailableException - */ - public function groupsMatchFilter($groupDNs) { - $validGroupDNs = []; - foreach ($groupDNs as $dn) { - $cacheKey = 'groupsMatchFilter-'.$dn; - $groupMatchFilter = $this->connection->getFromCache($cacheKey); - if (!is_null($groupMatchFilter)) { - if ($groupMatchFilter) { - $validGroupDNs[] = $dn; - } - continue; - } - - // Check the base DN first. If this is not met already, we don't - // need to ask the server at all. - if (!$this->isDNPartOfBase($dn, $this->connection->ldapBaseGroups)) { - $this->connection->writeToCache($cacheKey, false); - continue; - } - - $result = $this->readAttribute($dn, '', $this->connection->ldapGroupFilter); - if (is_array($result)) { - $this->connection->writeToCache($cacheKey, true); - $validGroupDNs[] = $dn; - } else { - $this->connection->writeToCache($cacheKey, false); - } - } - return $validGroupDNs; - } - - /** * returns the internal Nextcloud name for the given LDAP DN of the user, false on DN outside of search DN or failure * * @param string $dn the dn of the user object @@ -567,14 +540,14 @@ class Access extends LDAPUtility { } } else { //If the UUID can't be detected something is foul. - \OCP\Util::writeLog('user_ldap', 'Cannot determine UUID for '.$fdn.'. Skipping.', ILogger::INFO); + \OCP\Util::writeLog('user_ldap', 'Cannot determine UUID for ' . $fdn . '. Skipping.', ILogger::INFO); return false; } if (is_null($ldapName)) { $ldapName = $this->readAttribute($fdn, $nameAttribute, $filter); if (!isset($ldapName[0]) && empty($ldapName[0])) { - \OCP\Util::writeLog('user_ldap', 'No or empty name for '.$fdn.' with filter '.$filter.'.', ILogger::INFO); + \OCP\Util::writeLog('user_ldap', 'No or empty name for ' . $fdn . ' with filter ' . $filter . '.', ILogger::INFO); return false; } $ldapName = $ldapName[0]; @@ -633,7 +606,7 @@ class Access extends LDAPUtility { } //if everything else did not help.. - \OCP\Util::writeLog('user_ldap', 'Could not create unique name for '.$fdn.'.', ILogger::INFO); + \OCP\Util::writeLog('user_ldap', 'Could not create unique name for ' . $fdn . '.', ILogger::INFO); return false; } @@ -643,7 +616,7 @@ class Access extends LDAPUtility { string $name, string $uuid, bool $isUser - ) :bool { + ): bool { if ($mapper->map($fdn, $name, $uuid)) { if ($this->ncUserManager instanceof PublicEmitter && $isUser) { $this->cacheUserExists($name); @@ -691,7 +664,7 @@ class Access extends LDAPUtility { private function ldap2NextcloudNames($ldapObjects, $isUsers) { if ($isUsers) { $nameAttribute = $this->connection->ldapUserDisplayName; - $sndAttribute = $this->connection->ldapUserDisplayName2; + $sndAttribute = $this->connection->ldapUserDisplayName2; } else { $nameAttribute = $this->connection->ldapGroupDisplayName; } @@ -743,27 +716,29 @@ class Access extends LDAPUtility { /** * caches the user display name + * * @param string $ocName the internal Nextcloud username * @param string|false $home the home directory path */ public function cacheUserHome($ocName, $home) { - $cacheKey = 'getHome'.$ocName; + $cacheKey = 'getHome' . $ocName; $this->connection->writeToCache($cacheKey, $home); } /** * caches a user as existing + * * @param string $ocName the internal Nextcloud username */ public function cacheUserExists($ocName) { - $this->connection->writeToCache('userExists'.$ocName, true); + $this->connection->writeToCache('userExists' . $ocName, true); } /** * caches a group as existing */ public function cacheGroupExists(string $gid): void { - $this->connection->writeToCache('groupExists'.$gid, true); + $this->connection->writeToCache('groupExists' . $gid, true); } /** @@ -781,7 +756,7 @@ class Access extends LDAPUtility { } $displayName = $user->composeAndStoreDisplayName($displayName, $displayName2); $cacheKeyTrunk = 'getDisplayName'; - $this->connection->writeToCache($cacheKeyTrunk.$ocName, $displayName); + $this->connection->writeToCache($cacheKeyTrunk . $ocName, $displayName); } public function cacheGroupDisplayName(string $ncName, string $displayName): void { @@ -791,6 +766,7 @@ class Access extends LDAPUtility { /** * creates a unique name for internal Nextcloud use for users. Don't call it directly. + * * @param string $name the display name of the object * @return string|false with with the name to use in Nextcloud or false if unsuccessful * @@ -802,7 +778,7 @@ class Access extends LDAPUtility { //while loop is just a precaution. If a name is not generated within //20 attempts, something else is very wrong. Avoids infinite loop. while ($attempts < 20) { - $altName = $name . '_' . rand(1000,9999); + $altName = $name . '_' . rand(1000, 9999); if (!$this->ncUserManager->userExists($altName)) { return $altName; } @@ -813,6 +789,7 @@ class Access extends LDAPUtility { /** * creates a unique name for internal Nextcloud use for groups. Don't call it directly. + * * @param string $name the display name of the object * @return string|false with with the name to use in Nextcloud or false if unsuccessful. * @@ -832,7 +809,7 @@ class Access extends LDAPUtility { $lastName = array_pop($usedNames); $lastNo = (int)substr($lastName, strrpos($lastName, '_') + 1); } - $altName = $name.'_'. (string)($lastNo+1); + $altName = $name . '_' . (string)($lastNo + 1); unset($usedNames); $attempts = 1; @@ -851,6 +828,7 @@ class Access extends LDAPUtility { /** * creates a unique name for internal Nextcloud use. + * * @param string $name the display name of the object * @param boolean $isUser whether name should be created for a user (true) or a group (false) * @return string|false with with the name to use in Nextcloud or false if unsuccessful @@ -910,9 +888,17 @@ class Access extends LDAPUtility { if (!$forceApplyAttributes) { $isBackgroundJobModeAjax = $this->config ->getAppValue('core', 'backgroundjobs_mode', 'ajax') === 'ajax'; - $recordsToUpdate = array_filter($ldapRecords, function ($record) use ($isBackgroundJobModeAjax) { + $listOfDNs = array_reduce($ldapRecords, function ($listOfDNs, $entry) { + $listOfDNs[] = $entry['dn'][0]; + return $listOfDNs; + }, []); + $idsByDn = $this->userMapper->getListOfIdsByDn($listOfDNs); + $recordsToUpdate = array_filter($ldapRecords, function ($record) use ($isBackgroundJobModeAjax, $idsByDn) { $newlyMapped = false; - $uid = $this->dn2ocname($record['dn'][0], null, true, $newlyMapped, $record); + $uid = $idsByDn[$record['dn'][0]] ?? null; + if ($uid === null) { + $uid = $this->dn2ocname($record['dn'][0], null, true, $newlyMapped, $record); + } if (is_string($uid)) { $this->cacheUserExists($uid); } @@ -938,7 +924,7 @@ class Access extends LDAPUtility { // displayName is obligatory continue; } - $ocName = $this->dn2ocname($userRecord['dn'][0], null, true); + $ocName = $this->dn2ocname($userRecord['dn'][0], null, true); if ($ocName === false) { continue; } @@ -949,7 +935,7 @@ class Access extends LDAPUtility { } else { \OC::$server->getLogger()->debug( "The ldap user manager returned null for $ocName", - ['app'=>'user_ldap'] + ['app' => 'user_ldap'] ); } } @@ -964,9 +950,19 @@ class Access extends LDAPUtility { */ public function fetchListOfGroups($filter, $attr, $limit = null, $offset = null) { $groupRecords = $this->searchGroups($filter, $attr, $limit, $offset); - array_walk($groupRecords, function ($record) { + + $listOfDNs = array_reduce($groupRecords, function ($listOfDNs, $entry) { + $listOfDNs[] = $entry['dn'][0]; + return $listOfDNs; + }, []); + $idsByDn = $this->groupMapper->getListOfIdsByDn($listOfDNs); + + array_walk($groupRecords, function ($record) use ($idsByDn) { $newlyMapped = false; - $gid = $this->dn2ocname($record['dn'][0], null, false, $newlyMapped, $record); + $gid = $uidsByDn[$record['dn'][0]] ?? null; + if ($gid === null) { + $gid = $this->dn2ocname($record['dn'][0], null, false, $newlyMapped, $record); + } if (!$newlyMapped && is_string($gid)) { $this->cacheGroupExists($gid); } @@ -1092,6 +1088,7 @@ class Access extends LDAPUtility { /** * Returns the LDAP handler + * * @throws \OC\ServerNotAvailableException */ @@ -1175,7 +1172,7 @@ class Access extends LDAPUtility { // cannot use $cr anymore, might have changed in the previous call! $error = $this->ldap->errno($this->connection->getConnectionResource()); if (!$this->ldap->isResource($sr) || $error !== 0) { - \OCP\Util::writeLog('user_ldap', 'Attempt for Paging? '.print_r($pagedSearchOK, true), ILogger::ERROR); + \OCP\Util::writeLog('user_ldap', 'Attempt for Paging? ' . print_r($pagedSearchOK, true), ILogger::ERROR); return false; } @@ -1222,7 +1219,7 @@ class Access extends LDAPUtility { if (!is_null($limit) && (int)$this->connection->ldapPagingSize !== 0) { \OC::$server->getLogger()->debug( 'Paged search was not available', - [ 'app' => 'user_ldap' ] + ['app' => 'user_ldap'] ); } } @@ -1409,7 +1406,7 @@ class Access extends LDAPUtility { //a) paged search unsuccessful, though attempted //b) no paged search, but limit set if ((!$this->getPagedSearchResultState() - && $pagedSearchOK) + && $pagedSearchOK) || ( !$pagedSearchOK && !is_null($limit) @@ -1454,7 +1451,8 @@ class Access extends LDAPUtility { /** * escapes (user provided) parts for LDAP filter - * @param string $input, the provided value + * + * @param string $input , the provided value * @param bool $allowAsterisk whether in * at the beginning should be preserved * @return string the escaped string */ @@ -1464,13 +1462,14 @@ class Access extends LDAPUtility { $asterisk = '*'; $input = mb_substr($input, 1, null, 'UTF-8'); } - $search = ['*', '\\', '(', ')']; + $search = ['*', '\\', '(', ')']; $replace = ['\\*', '\\\\', '\\(', '\\)']; return $asterisk . str_replace($search, $replace, $input); } /** * combines the input filters with AND + * * @param string[] $filters the filters to connect * @return string the combined filter */ @@ -1480,6 +1479,7 @@ class Access extends LDAPUtility { /** * combines the input filters with OR + * * @param string[] $filters the filters to connect * @return string the combined filter * Combines Filter arguments with OR @@ -1490,24 +1490,26 @@ class Access extends LDAPUtility { /** * combines the input filters with given operator + * * @param string[] $filters the filters to connect * @param string $operator either & or | * @return string the combined filter */ private function combineFilter($filters, $operator) { - $combinedFilter = '('.$operator; + $combinedFilter = '(' . $operator; foreach ($filters as $filter) { if ($filter !== '' && $filter[0] !== '(') { - $filter = '('.$filter.')'; + $filter = '(' . $filter . ')'; } - $combinedFilter.=$filter; + $combinedFilter .= $filter; } - $combinedFilter.=')'; + $combinedFilter .= ')'; return $combinedFilter; } /** * creates a filter part for to perform search for users + * * @param string $search the search term * @return string the final filter part to use in LDAP searches */ @@ -1519,6 +1521,7 @@ class Access extends LDAPUtility { /** * creates a filter part for to perform search for groups + * * @param string $search the search term * @return string the final filter part to use in LDAP searches */ @@ -1531,6 +1534,7 @@ class Access extends LDAPUtility { /** * creates a filter part for searches by splitting up the given search * string into single words + * * @param string $search the search term * @param string[] $searchAttributes needs to have at least two attributes, * otherwise it does not make sense :) @@ -1557,6 +1561,7 @@ class Access extends LDAPUtility { /** * creates a filter part for searches + * * @param string $search the search term * @param string[]|null $searchAttributes * @param string $fallbackAttribute a fallback attribute in case the user @@ -1590,7 +1595,7 @@ class Access extends LDAPUtility { } } if (count($filter) === 1) { - return '('.$filter[0].')'; + return '(' . $filter[0] . ')'; } return $this->combineFilterWithOr($filter); } @@ -1599,6 +1604,7 @@ class Access extends LDAPUtility { * returns the search term depending on whether we are allowed * list users found by ldap with the current input appended by * a * + * * @return string */ private function prepareSearchTerm($term) { @@ -1617,6 +1623,7 @@ class Access extends LDAPUtility { /** * returns the filter used for counting users + * * @return string */ public function getFilterForUserCount() { @@ -1655,8 +1662,8 @@ class Access extends LDAPUtility { */ public function getUserDnByUuid($uuid) { $uuidOverride = $this->connection->ldapExpertUUIDUserAttr; - $filter = $this->connection->ldapUserFilter; - $bases = $this->connection->ldapBaseUsers; + $filter = $this->connection->ldapUserFilter; + $bases = $this->connection->ldapBaseUsers; if ($this->connection->ldapUuidUserAttribute === 'auto' && $uuidOverride === '') { // Sacrebleu! The UUID attribute is unknown :( We need first an @@ -1711,10 +1718,10 @@ class Access extends LDAPUtility { */ private function detectUuidAttribute($dn, $isUser = true, $force = false, array $ldapRecord = null) { if ($isUser) { - $uuidAttr = 'ldapUuidUserAttribute'; + $uuidAttr = 'ldapUuidUserAttribute'; $uuidOverride = $this->connection->ldapExpertUUIDUserAttr; } else { - $uuidAttr = 'ldapUuidGroupAttribute'; + $uuidAttr = 'ldapUuidGroupAttribute'; $uuidOverride = $this->connection->ldapExpertUUIDGroupAttr; } @@ -1771,10 +1778,10 @@ class Access extends LDAPUtility { */ public function getUUID($dn, $isUser = true, $ldapRecord = null) { if ($isUser) { - $uuidAttr = 'ldapUuidUserAttribute'; + $uuidAttr = 'ldapUuidUserAttribute'; $uuidOverride = $this->connection->ldapExpertUUIDUserAttr; } else { - $uuidAttr = 'ldapUuidGroupAttribute'; + $uuidAttr = 'ldapUuidGroupAttribute'; $uuidOverride = $this->connection->ldapExpertUUIDGroupAttr; } @@ -1799,6 +1806,7 @@ class Access extends LDAPUtility { /** * converts a binary ObjectGUID into a string representation + * * @param string $oguid the ObjectGUID in it's binary form as retrieved from AD * @return string * @link http://www.php.net/manual/en/function.ldap-get-values-len.php#73198 @@ -1854,16 +1862,16 @@ class Access extends LDAPUtility { \OC::$server->getLogger()->info( 'Passed string does not resemble a valid GUID. Known UUID ' . '({uuid}) probably does not match UUID configuration.', - [ 'app' => 'user_ldap', 'uuid' => $guid ] + ['app' => 'user_ldap', 'uuid' => $guid] ); return $guid; } - for ($i=0; $i < 3; $i++) { + for ($i = 0; $i < 3; $i++) { $pairs = str_split($blocks[$i], 2); $pairs = array_reverse($pairs); $blocks[$i] = implode('', $pairs); } - for ($i=0; $i < 5; $i++) { + for ($i = 0; $i < 5; $i++) { $pairs = str_split($blocks[$i], 2); $blocks[$i] = '\\' . implode('\\', $pairs); } @@ -1879,7 +1887,7 @@ class Access extends LDAPUtility { */ public function getSID($dn) { $domainDN = $this->getDomainDNFromDN($dn); - $cacheKey = 'getSID-'.$domainDN; + $cacheKey = 'getSID-' . $domainDN; $sid = $this->connection->getFromCache($cacheKey); if (!is_null($sid)) { return $sid; @@ -1898,6 +1906,7 @@ class Access extends LDAPUtility { /** * converts a binary SID into a string representation + * * @param string $sid * @return string */ @@ -1936,6 +1945,7 @@ class Access extends LDAPUtility { /** * checks if the given DN is part of the given base DN(s) + * * @param string $dn the DN * @param string[] $bases array containing the allowed base DN or DNs * @return bool @@ -1946,7 +1956,7 @@ class Access extends LDAPUtility { foreach ($bases as $base) { $belongsToBase = true; - if (mb_strripos($dn, $base, 0, 'UTF-8') !== (mb_strlen($dn, 'UTF-8')-mb_strlen($base, 'UTF-8'))) { + if (mb_strripos($dn, $base, 0, 'UTF-8') !== (mb_strlen($dn, 'UTF-8') - mb_strlen($base, 'UTF-8'))) { $belongsToBase = false; } if ($belongsToBase) { @@ -1979,6 +1989,7 @@ class Access extends LDAPUtility { * be reset by other operations. Best, call it immediately after a search(), * searchUsers() or searchGroups() call. count-methods are probably safe as * well. Don't rely on it with any fetchList-method. + * * @return bool */ public function hasMoreResults() { @@ -1993,6 +2004,7 @@ class Access extends LDAPUtility { /** * Check whether the most recent paged search was successful. It flushed the state var. Use it always after a possible paged search. + * * @return boolean|null true on success, null or false otherwise */ public function getPagedSearchResultState() { @@ -2045,10 +2057,10 @@ class Access extends LDAPUtility { $this->abandonPagedSearch(); } $pagedSearchOK = true === $this->invokeLDAPMethod( - 'controlPagedResult', $this->connection->getConnectionResource(), $limit, false - ); + 'controlPagedResult', $this->connection->getConnectionResource(), $limit, false + ); if ($pagedSearchOK) { - \OC::$server->getLogger()->debug('Ready for a paged search',['app' => 'user_ldap']); + \OC::$server->getLogger()->debug('Ready for a paged search', ['app' => 'user_ldap']); } /* ++ Fixing RHDS searches with pages with zero results ++ * We coudn't get paged searches working with our RHDS for login ($limit = 0), diff --git a/apps/user_ldap/lib/Group_LDAP.php b/apps/user_ldap/lib/Group_LDAP.php index 85d9e38e03e..f05c8fb8ca4 100644 --- a/apps/user_ldap/lib/Group_LDAP.php +++ b/apps/user_ldap/lib/Group_LDAP.php @@ -85,6 +85,7 @@ class Group_LDAP extends BackendUtility implements \OCP\GroupInterface, IGroupLD /** * is user in group? + * * @param string $uid uid of the user * @param string $gid gid of the group * @return bool @@ -95,7 +96,7 @@ class Group_LDAP extends BackendUtility implements \OCP\GroupInterface, IGroupLD if (!$this->enabled) { return false; } - $cacheKey = 'inGroup'.$uid.':'.$gid; + $cacheKey = 'inGroup' . $uid . ':' . $gid; $inGroup = $this->access->connection->getFromCache($cacheKey); if (!is_null($inGroup)) { return (bool)$inGroup; @@ -108,7 +109,7 @@ class Group_LDAP extends BackendUtility implements \OCP\GroupInterface, IGroupLD return $isInGroup; } - $cacheKeyMembers = 'inGroup-members:'.$gid; + $cacheKeyMembers = 'inGroup-members:' . $gid; $members = $this->access->connection->getFromCache($cacheKeyMembers); if (!is_null($members)) { $this->cachedGroupMembers[$gid] = $members; @@ -199,13 +200,13 @@ class Group_LDAP extends BackendUtility implements \OCP\GroupInterface, IGroupLD $pos = strpos($memberURLs[0], '('); if ($pos !== false) { $memberUrlFilter = substr($memberURLs[0], $pos); - $foundMembers = $this->access->searchUsers($memberUrlFilter,'dn'); + $foundMembers = $this->access->searchUsers($memberUrlFilter, 'dn'); $dynamicMembers = []; foreach ($foundMembers as $value) { $dynamicMembers[$value['dn'][0]] = 1; } } else { - \OCP\Util::writeLog('user_ldap', 'No search filter found on member url '. + \OCP\Util::writeLog('user_ldap', 'No search filter found on member url ' . 'of group ' . $dnGroup, ILogger::DEBUG); } } @@ -228,7 +229,7 @@ class Group_LDAP extends BackendUtility implements \OCP\GroupInterface, IGroupLD return []; } // used extensively in cron job, caching makes sense for nested groups - $cacheKey = '_groupMembers'.$dnGroup; + $cacheKey = '_groupMembers' . $dnGroup; $groupMembers = $this->access->connection->getFromCache($cacheKey); if ($groupMembers !== null) { return $groupMembers; @@ -274,7 +275,7 @@ class Group_LDAP extends BackendUtility implements \OCP\GroupInterface, IGroupLD }; $groups = $this->walkNestedGroups($DN, $fetcher, $groups); - return $this->access->groupsMatchFilter($groups); + return $this->filterValidGroups($groups); } /** @@ -284,7 +285,7 @@ class Group_LDAP extends BackendUtility implements \OCP\GroupInterface, IGroupLD * @return array */ private function walkNestedGroups(string $dn, \Closure $fetcher, array $list): array { - $nesting = (int) $this->access->connection->ldapNestedGroups; + $nesting = (int)$this->access->connection->ldapNestedGroups; // depending on the input, we either have a list of DNs or a list of LDAP records // also, the output expects either DNs or records. Testing the first element should suffice. $recordMode = is_array($list) && isset($list[0]) && is_array($list[0]) && isset($list[0]['dn'][0]); @@ -319,6 +320,7 @@ class Group_LDAP extends BackendUtility implements \OCP\GroupInterface, IGroupLD /** * translates a gidNumber into an ownCloud internal name + * * @param string $gid as given by gidNumber on POSIX LDAP * @param string $dn a DN that belongs to the same domain as the group * @return string|bool @@ -354,6 +356,7 @@ class Group_LDAP extends BackendUtility implements \OCP\GroupInterface, IGroupLD /** * returns the entry's gidNumber + * * @param string $dn * @param string $attribute * @return string|bool @@ -368,6 +371,7 @@ class Group_LDAP extends BackendUtility implements \OCP\GroupInterface, IGroupLD /** * returns the group's primary ID + * * @param string $dn * @return string|bool */ @@ -377,6 +381,7 @@ class Group_LDAP extends BackendUtility implements \OCP\GroupInterface, IGroupLD /** * returns the user's gidNumber + * * @param string $dn * @return string|bool */ @@ -410,7 +415,7 @@ class Group_LDAP extends BackendUtility implements \OCP\GroupInterface, IGroupLD if ($search !== '') { $filterParts[] = $this->access->getFilterPartForUserSearch($search); } - $filterParts[] = $this->access->connection->ldapGidNumber .'=' . $groupID; + $filterParts[] = $this->access->connection->ldapGidNumber . '=' . $groupID; return $this->access->combineFilterWithAnd($filterParts); } @@ -460,6 +465,7 @@ class Group_LDAP extends BackendUtility implements \OCP\GroupInterface, IGroupLD /** * gets the gidNumber of a user + * * @param string $dn * @return string */ @@ -477,6 +483,7 @@ class Group_LDAP extends BackendUtility implements \OCP\GroupInterface, IGroupLD /** * translates a primary group ID into an Nextcloud internal name + * * @param string $gid as given by primaryGroupID on AD * @param string $dn a DN that belongs to the same domain as the group * @return string|bool @@ -516,6 +523,7 @@ class Group_LDAP extends BackendUtility implements \OCP\GroupInterface, IGroupLD /** * returns the entry's primary group ID + * * @param string $dn * @param string $attribute * @return string|bool @@ -530,6 +538,7 @@ class Group_LDAP extends BackendUtility implements \OCP\GroupInterface, IGroupLD /** * returns the group's primary ID + * * @param string $dn * @return string|bool */ @@ -539,6 +548,7 @@ class Group_LDAP extends BackendUtility implements \OCP\GroupInterface, IGroupLD /** * returns the user's primary group ID + * * @param string $dn * @return string|bool */ @@ -622,6 +632,7 @@ class Group_LDAP extends BackendUtility implements \OCP\GroupInterface, IGroupLD /** * gets the primary group of a user + * * @param string $dn * @return string */ @@ -639,6 +650,7 @@ class Group_LDAP extends BackendUtility implements \OCP\GroupInterface, IGroupLD /** * Get all groups a user belongs to + * * @param string $uid Name of the user * @return array with group names * @@ -651,7 +663,7 @@ class Group_LDAP extends BackendUtility implements \OCP\GroupInterface, IGroupLD if (!$this->enabled) { return []; } - $cacheKey = 'getUserGroups'.$uid; + $cacheKey = 'getUserGroups' . $uid; $userGroups = $this->access->connection->getFromCache($cacheKey); if (!is_null($userGroups)) { return $userGroups; @@ -671,14 +683,14 @@ class Group_LDAP extends BackendUtility implements \OCP\GroupInterface, IGroupLD if (!empty($dynamicGroupMemberURL)) { // look through dynamic groups to add them to the result array if needed $groupsToMatch = $this->access->fetchListOfGroups( - $this->access->connection->ldapGroupFilter,['dn',$dynamicGroupMemberURL]); + $this->access->connection->ldapGroupFilter, ['dn', $dynamicGroupMemberURL]); foreach ($groupsToMatch as $dynamicGroup) { if (!array_key_exists($dynamicGroupMemberURL, $dynamicGroup)) { continue; } $pos = strpos($dynamicGroup[$dynamicGroupMemberURL][0], '('); if ($pos !== false) { - $memberUrlFilter = substr($dynamicGroup[$dynamicGroupMemberURL][0],$pos); + $memberUrlFilter = substr($dynamicGroup[$dynamicGroupMemberURL][0], $pos); // apply filter via ldap search to see if this user is in this // dynamic group $userMatch = $this->access->readAttribute( @@ -696,7 +708,7 @@ class Group_LDAP extends BackendUtility implements \OCP\GroupInterface, IGroupLD } } } else { - \OCP\Util::writeLog('user_ldap', 'No search filter found on member url '. + \OCP\Util::writeLog('user_ldap', 'No search filter found on member url ' . 'of group ' . print_r($dynamicGroup, true), ILogger::DEBUG); } } @@ -708,7 +720,7 @@ class Group_LDAP extends BackendUtility implements \OCP\GroupInterface, IGroupLD if ((int)$this->access->connection->hasMemberOfFilterSupport === 1 && (int)$this->access->connection->useMemberOfToDetectMembership === 1 && strtolower($this->access->connection->ldapGroupMemberAssocAttr) !== 'memberuid' - ) { + ) { $groupDNs = $this->_getGroupDNsFromMemberOf($userDN); if (is_array($groupDNs)) { foreach ($groupDNs as $dn) { @@ -739,7 +751,7 @@ class Group_LDAP extends BackendUtility implements \OCP\GroupInterface, IGroupLD } elseif (strtolower($this->access->connection->ldapGroupMemberAssocAttr) === 'memberuid') { $result = $this->access->readAttribute($userDN, 'uid'); if ($result === false) { - \OCP\Util::writeLog('user_ldap', 'No uid attribute found for DN ' . $userDN . ' on '. + \OCP\Util::writeLog('user_ldap', 'No uid attribute found for DN ' . $userDN . ' on ' . $this->access->connection->ldapHost, ILogger::DEBUG); $uid = false; } else { @@ -789,9 +801,9 @@ class Group_LDAP extends BackendUtility implements \OCP\GroupInterface, IGroupLD } $allGroups = []; $seen[$dn] = true; - $filter = $this->access->connection->ldapGroupMemberAssocAttr.'='.$dn; + $filter = $this->access->connection->ldapGroupMemberAssocAttr . '=' . $dn; $groups = $this->access->fetchListOfGroups($filter, - [$this->access->connection->ldapGroupDisplayName, 'dn']); + [strtolower($this->access->connection->ldapGroupMemberAssocAttr), $this->access->connection->ldapGroupDisplayName, 'dn']); if (is_array($groups)) { $fetcher = function ($dn, &$seen) { if (is_array($dn) && isset($dn['dn'][0])) { @@ -801,8 +813,8 @@ class Group_LDAP extends BackendUtility implements \OCP\GroupInterface, IGroupLD }; $allGroups = $this->walkNestedGroups($dn, $fetcher, $groups); } - $visibleGroups = $this->access->groupsMatchFilter(array_keys($allGroups)); - return array_intersect_key($allGroups, array_flip($visibleGroups)); + $visibleGroups = $this->filterValidGroups($allGroups); + return array_intersect_key($allGroups, $visibleGroups); } /** @@ -823,7 +835,7 @@ class Group_LDAP extends BackendUtility implements \OCP\GroupInterface, IGroupLD return []; } $search = $this->access->escapeFilterPart($search, true); - $cacheKey = 'usersInGroup-'.$gid.'-'.$search.'-'.$limit.'-'.$offset; + $cacheKey = 'usersInGroup-' . $gid . '-' . $search . '-' . $limit . '-' . $offset; // check for cache of the exact query $groupUsers = $this->access->connection->getFromCache($cacheKey); if (!is_null($groupUsers)) { @@ -831,7 +843,7 @@ class Group_LDAP extends BackendUtility implements \OCP\GroupInterface, IGroupLD } // check for cache of the query without limit and offset - $groupUsers = $this->access->connection->getFromCache('usersInGroup-'.$gid.'-'.$search); + $groupUsers = $this->access->connection->getFromCache('usersInGroup-' . $gid . '-' . $search); if (!is_null($groupUsers)) { $groupUsers = array_slice($groupUsers, $offset, $limit); $this->access->connection->writeToCache($cacheKey, $groupUsers); @@ -907,7 +919,7 @@ class Group_LDAP extends BackendUtility implements \OCP\GroupInterface, IGroupLD $groupUsers = array_unique(array_merge($groupUsers, $primaryUsers, $posixGroupUsers)); natsort($groupUsers); - $this->access->connection->writeToCache('usersInGroup-'.$gid.'-'.$search, $groupUsers); + $this->access->connection->writeToCache('usersInGroup-' . $gid . '-' . $search, $groupUsers); $groupUsers = array_slice($groupUsers, $offset, $limit); $this->access->connection->writeToCache($cacheKey, $groupUsers); @@ -917,6 +929,7 @@ class Group_LDAP extends BackendUtility implements \OCP\GroupInterface, IGroupLD /** * returns the number of users in a group, who match the search term + * * @param string $gid the internal group name * @param string $search optional, a search string * @return int|bool @@ -926,7 +939,7 @@ class Group_LDAP extends BackendUtility implements \OCP\GroupInterface, IGroupLD return $this->groupPluginManager->countUsersInGroup($gid, $search); } - $cacheKey = 'countUsersInGroup-'.$gid.'-'.$search; + $cacheKey = 'countUsersInGroup-' . $gid . '-' . $search; if (!$this->enabled || !$this->groupExists($gid)) { return false; } @@ -958,7 +971,7 @@ class Group_LDAP extends BackendUtility implements \OCP\GroupInterface, IGroupLD $search = $this->access->escapeFilterPart($search, true); $isMemberUid = (strtolower($this->access->connection->ldapGroupMemberAssocAttr) - === 'memberuid'); + === 'memberuid'); //we need to apply the search filter //alternatives that need to be checked: @@ -1015,10 +1028,10 @@ class Group_LDAP extends BackendUtility implements \OCP\GroupInterface, IGroupLD if (!$this->enabled) { return []; } - $cacheKey = 'getGroups-'.$search.'-'.$limit.'-'.$offset; + $cacheKey = 'getGroups-' . $search . '-' . $limit . '-' . $offset; //Check cache before driving unnecessary searches - \OCP\Util::writeLog('user_ldap', 'getGroups '.$cacheKey, ILogger::DEBUG); + \OCP\Util::writeLog('user_ldap', 'getGroups ' . $cacheKey, ILogger::DEBUG); $ldap_groups = $this->access->connection->getFromCache($cacheKey); if (!is_null($ldap_groups)) { return $ldap_groups; @@ -1033,11 +1046,11 @@ class Group_LDAP extends BackendUtility implements \OCP\GroupInterface, IGroupLD $this->access->connection->ldapGroupFilter, $this->access->getFilterPartForGroupSearch($search) ]); - \OCP\Util::writeLog('user_ldap', 'getGroups Filter '.$filter, ILogger::DEBUG); + \OCP\Util::writeLog('user_ldap', 'getGroups Filter ' . $filter, ILogger::DEBUG); $ldap_groups = $this->access->fetchListOfGroups($filter, - [$this->access->connection->ldapGroupDisplayName, 'dn'], - $limit, - $offset); + [$this->access->connection->ldapGroupDisplayName, 'dn'], + $limit, + $offset); $ldap_groups = $this->access->nextcloudGroupNames($ldap_groups); $this->access->connection->writeToCache($cacheKey, $ldap_groups); @@ -1078,7 +1091,7 @@ class Group_LDAP extends BackendUtility implements \OCP\GroupInterface, IGroupLD $chunkLimit = min($pagingSize, $overallLimit - $chunkOffset); $ldapGroups = $this->getGroupsChunk($search, $chunkLimit, $chunkOffset); $nread = count($ldapGroups); - \OCP\Util::writeLog('user_ldap', 'getGroups('.$search.'): read '.$nread.' at offset '.$chunkOffset.' (limit: '.$chunkLimit.')', ILogger::DEBUG); + \OCP\Util::writeLog('user_ldap', 'getGroups(' . $search . '): read ' . $nread . ' at offset ' . $chunkOffset . ' (limit: ' . $chunkLimit . ')', ILogger::DEBUG); if ($nread) { $allGroups = array_merge($allGroups, $ldapGroups); $chunkOffset += $nread; @@ -1100,11 +1113,12 @@ class Group_LDAP extends BackendUtility implements \OCP\GroupInterface, IGroupLD /** * check if a group exists + * * @param string $gid * @return bool */ public function groupExists($gid) { - $groupExists = $this->access->connection->getFromCache('groupExists'.$gid); + $groupExists = $this->access->connection->getFromCache('groupExists' . $gid); if (!is_null($groupExists)) { return (bool)$groupExists; } @@ -1113,22 +1127,43 @@ class Group_LDAP extends BackendUtility implements \OCP\GroupInterface, IGroupLD //only, requires more checking. $dn = $this->access->groupname2dn($gid); if (!$dn) { - $this->access->connection->writeToCache('groupExists'.$gid, false); + $this->access->connection->writeToCache('groupExists' . $gid, false); + return false; + } + + if (!$this->access->isDNPartOfBase($dn, $this->access->connection->ldapBaseGroups)) { + $this->access->connection->writeToCache('groupExists' . $gid, false); return false; } //if group really still exists, we will be able to read its objectclass - if (!is_array($this->access->readAttribute($dn, ''))) { - $this->access->connection->writeToCache('groupExists'.$gid, false); + if (!is_array($this->access->readAttribute($dn, '', $this->access->connection->ldapGroupFilter))) { + $this->access->connection->writeToCache('groupExists' . $gid, false); return false; } - $this->access->connection->writeToCache('groupExists'.$gid, true); + $this->access->connection->writeToCache('groupExists' . $gid, true); return true; } + protected function filterValidGroups(array $listOfGroups): array { + $validGroupDNs = []; + foreach ($listOfGroups as $key => $item) { + $dn = is_string($item) ? $item : $item['dn'][0]; + $gid = $this->access->dn2groupname($dn); + if (!$gid) { + continue; + } + if ($this->groupExists($gid)) { + $validGroupDNs[$key] = $item; + } + } + return $validGroupDNs; + } + /** * Check if backend implements actions + * * @param int $actions bitwise-or'ed actions * @return boolean * @@ -1142,6 +1177,7 @@ class Group_LDAP extends BackendUtility implements \OCP\GroupInterface, IGroupLD /** * Return access for LDAP interaction. + * * @return Access instance of Access for LDAP interaction */ public function getLDAPAccess($gid) { @@ -1150,6 +1186,7 @@ class Group_LDAP extends BackendUtility implements \OCP\GroupInterface, IGroupLD /** * create a group + * * @param string $gid * @return bool * @throws \Exception @@ -1177,6 +1214,7 @@ class Group_LDAP extends BackendUtility implements \OCP\GroupInterface, IGroupLD /** * delete a group + * * @param string $gid gid of the group to delete * @return bool * @throws \Exception @@ -1186,7 +1224,7 @@ class Group_LDAP extends BackendUtility implements \OCP\GroupInterface, IGroupLD if ($ret = $this->groupPluginManager->deleteGroup($gid)) { #delete group in nextcloud internal db $this->access->getGroupMapper()->unmap($gid); - $this->access->connection->writeToCache("groupExists".$gid, false); + $this->access->connection->writeToCache("groupExists" . $gid, false); } return $ret; } @@ -1195,6 +1233,7 @@ class Group_LDAP extends BackendUtility implements \OCP\GroupInterface, IGroupLD /** * Add a user to a group + * * @param string $uid Name of the user to add to group * @param string $gid Name of the group in which add the user * @return bool @@ -1213,6 +1252,7 @@ class Group_LDAP extends BackendUtility implements \OCP\GroupInterface, IGroupLD /** * Removes a user from a group + * * @param string $uid Name of the user to remove from group * @param string $gid Name of the group from which remove the user * @return bool @@ -1231,6 +1271,7 @@ class Group_LDAP extends BackendUtility implements \OCP\GroupInterface, IGroupLD /** * Gets group details + * * @param string $gid Name of the group * @return array | false * @throws \Exception @@ -1246,6 +1287,7 @@ class Group_LDAP extends BackendUtility implements \OCP\GroupInterface, IGroupLD * Return LDAP connection resource from a cloned connection. * The cloned connection needs to be closed manually. * of the current access. + * * @param string $gid * @return resource of the LDAP connection */ diff --git a/apps/user_ldap/lib/Group_Proxy.php b/apps/user_ldap/lib/Group_Proxy.php index 3bd0cc4c400..c6a2c7fb6fb 100644 --- a/apps/user_ldap/lib/Group_Proxy.php +++ b/apps/user_ldap/lib/Group_Proxy.php @@ -36,6 +36,7 @@ class Group_Proxy extends Proxy implements \OCP\GroupInterface, IGroupLDAP, IGet /** * Constructor + * * @param string[] $serverConfigPrefixes array containing the config Prefixes */ public function __construct($serverConfigPrefixes, ILDAPWrapper $ldap, GroupPluginManager $groupPluginManager) { @@ -51,6 +52,7 @@ class Group_Proxy extends Proxy implements \OCP\GroupInterface, IGroupLDAP, IGet /** * Tries the backends one after the other until a positive result is returned from the specified method + * * @param string $gid the gid connected to the request * @param string $method the method of the group backend that shall be called * @param array $parameters an array of parameters to be passed @@ -60,7 +62,9 @@ class Group_Proxy extends Proxy implements \OCP\GroupInterface, IGroupLDAP, IGet $cacheKey = $this->getGroupCacheKey($gid); foreach ($this->backends as $configPrefix => $backend) { if ($result = call_user_func_array([$backend, $method], $parameters)) { - $this->writeToCache($cacheKey, $configPrefix); + if (!$this->isSingleBackend()) { + $this->writeToCache($cacheKey, $configPrefix); + } return $result; } } @@ -69,6 +73,7 @@ class Group_Proxy extends Proxy implements \OCP\GroupInterface, IGroupLDAP, IGet /** * Asks the backend connected to the server that supposely takes care of the gid from the request. + * * @param string $gid the gid connected to the request * @param string $method the method of the group backend that shall be called * @param array $parameters an array of parameters to be passed @@ -99,8 +104,13 @@ class Group_Proxy extends Proxy implements \OCP\GroupInterface, IGroupLDAP, IGet return false; } + protected function activeBackends(): int { + return count($this->backends); + } + /** * is user in group? + * * @param string $uid uid of the user * @param string $gid gid of the group * @return bool @@ -113,6 +123,7 @@ class Group_Proxy extends Proxy implements \OCP\GroupInterface, IGroupLDAP, IGet /** * Get all groups a user belongs to + * * @param string $uid Name of the user * @return string[] with group names * @@ -134,6 +145,7 @@ class Group_Proxy extends Proxy implements \OCP\GroupInterface, IGroupLDAP, IGet /** * get a list of all users in a group + * * @return string[] with user ids */ public function usersInGroup($gid, $search = '', $limit = -1, $offset = 0) { @@ -160,6 +172,7 @@ class Group_Proxy extends Proxy implements \OCP\GroupInterface, IGroupLDAP, IGet /** * delete a group + * * @param string $gid gid of the group to delete * @return bool */ @@ -170,6 +183,7 @@ class Group_Proxy extends Proxy implements \OCP\GroupInterface, IGroupLDAP, IGet /** * Add a user to a group + * * @param string $uid Name of the user to add to group * @param string $gid Name of the group in which add the user * @return bool @@ -183,6 +197,7 @@ class Group_Proxy extends Proxy implements \OCP\GroupInterface, IGroupLDAP, IGet /** * Removes a user from a group + * * @param string $uid Name of the user to remove from group * @param string $gid Name of the group from which remove the user * @return bool @@ -196,6 +211,7 @@ class Group_Proxy extends Proxy implements \OCP\GroupInterface, IGroupLDAP, IGet /** * returns the number of users in a group, who match the search term + * * @param string $gid the internal group name * @param string $search optional, a search string * @return int|bool @@ -207,6 +223,7 @@ class Group_Proxy extends Proxy implements \OCP\GroupInterface, IGroupLDAP, IGet /** * get an array with group details + * * @param string $gid * @return array|false */ @@ -217,6 +234,7 @@ class Group_Proxy extends Proxy implements \OCP\GroupInterface, IGroupLDAP, IGet /** * get a list of all groups + * * @return string[] with group names * * Returns a list with all groups @@ -236,6 +254,7 @@ class Group_Proxy extends Proxy implements \OCP\GroupInterface, IGroupLDAP, IGet /** * check if a group exists + * * @param string $gid * @return bool */ @@ -245,6 +264,7 @@ class Group_Proxy extends Proxy implements \OCP\GroupInterface, IGroupLDAP, IGet /** * Check if backend implements actions + * * @param int $actions bitwise-or'ed actions * @return boolean * @@ -258,6 +278,7 @@ class Group_Proxy extends Proxy implements \OCP\GroupInterface, IGroupLDAP, IGet /** * Return access for LDAP interaction. + * * @param string $gid * @return Access instance of Access for LDAP interaction */ @@ -268,6 +289,7 @@ class Group_Proxy extends Proxy implements \OCP\GroupInterface, IGroupLDAP, IGet /** * Return a new LDAP connection for the specified group. * The connection needs to be closed manually. + * * @param string $gid * @return resource of the LDAP connection */ diff --git a/apps/user_ldap/lib/Helper.php b/apps/user_ldap/lib/Helper.php index 90fa3d05892..05e56c318af 100644 --- a/apps/user_ldap/lib/Helper.php +++ b/apps/user_ldap/lib/Helper.php @@ -34,6 +34,7 @@ namespace OCA\User_LDAP; +use OC\Cache\CappedMemoryCache; use OCP\IConfig; class Helper { @@ -41,6 +42,9 @@ class Helper { /** @var IConfig */ private $config; + /** @var CappedMemoryCache */ + protected $sanitizeDnCache; + /** * Helper constructor. * @@ -48,10 +52,12 @@ class Helper { */ public function __construct(IConfig $config) { $this->config = $config; + $this->sanitizeDnCache = new CappedMemoryCache(10000); } /** * returns prefixes for each saved LDAP/AD server configuration. + * * @param bool $activeConfigurations optional, whether only active configuration shall be * retrieved, defaults to false * @return array with a list of the available prefixes @@ -92,6 +98,7 @@ class Helper { /** * * determines the host for every configured connection + * * @return array an array with configprefix as keys * */ @@ -144,6 +151,7 @@ class Helper { /** * deletes a given saved LDAP/AD server configuration. + * * @param string $prefix the configuration prefix of the config to delete * @return bool true on success, false otherwise */ @@ -161,11 +169,11 @@ class Helper { DELETE FROM `*PREFIX*appconfig` WHERE `configkey` LIKE ? - '.$saveOtherConfigurations.' + ' . $saveOtherConfigurations . ' AND `appid` = \'user_ldap\' AND `configkey` NOT IN (\'enabled\', \'installed_version\', \'types\', \'bgjUpdateGroupsLastRun\') '); - $delRows = $query->execute([$prefix.'%']); + $delRows = $query->execute([$prefix . '%']); if ($delRows === null) { return false; @@ -180,8 +188,9 @@ class Helper { /** * checks whether there is one or more disabled LDAP configurations - * @throws \Exception + * * @return bool + * @throws \Exception */ public function haveDisabledConfigurations() { $all = $this->getServerConfigurationPrefixes(false); @@ -196,6 +205,7 @@ class Helper { /** * extracts the domain from a given URL + * * @param string $url the URL * @return string|false domain as string on success, false otherwise */ @@ -229,6 +239,7 @@ class Helper { /** * sanitizes a DN received from the LDAP server + * * @param array $dn the DN in question * @return array|string the sanitized DN */ @@ -242,12 +253,20 @@ class Helper { return $result; } + if (!is_string($dn)) { + throw new \LogicException('String expected ' . \gettype($dn) . ' given'); + } + + if (($sanitizedDn = $this->sanitizeDnCache->get($dn)) !== null) { + return $sanitizedDn; + } + //OID sometimes gives back DNs with whitespace after the comma // a la "uid=foo, cn=bar, dn=..." We need to tackle this! - $dn = preg_replace('/([^\\\]),(\s+)/u', '\1,', $dn); + $sanitizedDn = preg_replace('/([^\\\]),(\s+)/u', '\1,', $dn); //make comparisons and everything work - $dn = mb_strtolower($dn, 'UTF-8'); + $sanitizedDn = mb_strtolower($sanitizedDn, 'UTF-8'); //escape DN values according to RFC 2253 – this is already done by ldap_explode_dn //to use the DN in search filters, \ needs to be escaped to \5c additionally @@ -261,17 +280,19 @@ class Helper { '\;' => '\5c3B', '\"' => '\5c22', '\#' => '\5c23', - '(' => '\28', - ')' => '\29', - '*' => '\2A', + '(' => '\28', + ')' => '\29', + '*' => '\2A', ]; - $dn = str_replace(array_keys($replacements), array_values($replacements), $dn); + $sanitizedDn = str_replace(array_keys($replacements), array_values($replacements), $sanitizedDn); + $this->sanitizeDnCache->set($dn, $sanitizedDn); - return $dn; + return $sanitizedDn; } /** * converts a stored DN so it can be used as base parameter for LDAP queries, internally we store them for usage in LDAP filters + * * @param string $dn the DN * @return string */ @@ -302,7 +323,7 @@ class Helper { $userSession = \OC::$server->getUserSession(); $userPluginManager = \OC::$server->query('LDAPUserPluginManager'); - $userBackend = new User_Proxy( + $userBackend = new User_Proxy( $configPrefixes, $ldapWrapper, $ocConfig, $notificationManager, $userSession, $userPluginManager ); $uid = $userBackend->loginName2UserName($param['uid']); diff --git a/apps/user_ldap/lib/Mapping/AbstractMapping.php b/apps/user_ldap/lib/Mapping/AbstractMapping.php index e14c9a572de..6fd07f5f483 100644 --- a/apps/user_ldap/lib/Mapping/AbstractMapping.php +++ b/apps/user_ldap/lib/Mapping/AbstractMapping.php @@ -26,8 +26,11 @@ namespace OCA\User_LDAP\Mapping; +use OC\DB\QueryBuilder\QueryBuilder; + /** * Class AbstractMapping + * * @package OCA\User_LDAP\Mapping */ abstract class AbstractMapping { @@ -38,9 +41,10 @@ abstract class AbstractMapping { /** * returns the DB table name which holds the mappings + * * @return string */ - abstract protected function getTableName(); + abstract protected function getTableName(bool $includePrefix = true); /** * @param \OCP\IDBConnection $dbc @@ -49,8 +53,12 @@ abstract class AbstractMapping { $this->dbc = $dbc; } + /** @var array caches Names (value) by DN (key) */ + protected $cache = []; + /** * checks whether a provided string represents an existing table col + * * @param string $col * @return bool */ @@ -67,11 +75,12 @@ abstract class AbstractMapping { /** * Gets the value of one column based on a provided value of another column + * * @param string $fetchCol * @param string $compareCol * @param string $search - * @throws \Exception * @return string|false + * @throws \Exception */ protected function getXbyY($fetchCol, $compareCol, $search) { if (!$this->isColNameValid($fetchCol)) { @@ -81,7 +90,7 @@ abstract class AbstractMapping { } $query = $this->dbc->prepare(' SELECT `' . $fetchCol . '` - FROM `'. $this->getTableName() .'` + FROM `' . $this->getTableName() . '` WHERE `' . $compareCol . '` = ? '); @@ -95,6 +104,7 @@ abstract class AbstractMapping { /** * Performs a DELETE or UPDATE query to the database. + * * @param \Doctrine\DBAL\Driver\Statement $query * @param array $parameters * @return bool true if at least one row was modified, false otherwise @@ -107,27 +117,42 @@ abstract class AbstractMapping { /** * Gets the LDAP DN based on the provided name. * Replaces Access::ocname2dn + * * @param string $name * @return string|false */ public function getDNByName($name) { - return $this->getXbyY('ldap_dn', 'owncloud_name', $name); + $dn = array_search($name, $this->cache); + if ($dn === false) { + $dn = $this->getXbyY('ldap_dn', 'owncloud_name', $name); + $this->cache[$dn] = $name; + } + return $dn; } /** * Updates the DN based on the given UUID + * * @param string $fdn * @param string $uuid * @return bool */ public function setDNbyUUID($fdn, $uuid) { + $oldDn = $this->getDnByUUID($uuid); $query = $this->dbc->prepare(' UPDATE `' . $this->getTableName() . '` SET `ldap_dn` = ? WHERE `directory_uuid` = ? '); - return $this->modify($query, [$fdn, $uuid]); + $r = $this->modify($query, [$fdn, $uuid]); + + if ($r && is_string($oldDn) && isset($this->cache[$oldDn])) { + $this->cache[$fdn] = $this->cache[$oldDn]; + unset($this->cache[$oldDn]); + } + + return $r; } /** @@ -146,20 +171,44 @@ abstract class AbstractMapping { WHERE `ldap_dn` = ? '); + unset($this->cache[$fdn]); + return $this->modify($query, [$uuid, $fdn]); } /** * Gets the name based on the provided LDAP DN. + * * @param string $fdn * @return string|false */ public function getNameByDN($fdn) { - return $this->getXbyY('owncloud_name', 'ldap_dn', $fdn); + if (!isset($this->cache[$fdn])) { + $this->cache[$fdn] = $this->getXbyY('owncloud_name', 'ldap_dn', $fdn); + } + return $this->cache[$fdn]; + } + + public function getListOfIdsByDn(array $fdns): array { + $qb = $this->dbc->getQueryBuilder(); + $qb->select('owncloud_name', 'ldap_dn') + ->from($this->getTableName(false)) + ->where($qb->expr()->in('ldap_dn', $qb->createNamedParameter($fdns, QueryBuilder::PARAM_STR_ARRAY))); + $stmt = $qb->execute(); + + $results = $stmt->fetchAll(\Doctrine\DBAL\FetchMode::ASSOCIATIVE); + foreach ($results as $key => $entry) { + unset($results[$key]); + $results[$entry['ldap_dn']] = $entry['owncloud_name']; + $this->cache[$entry['ldap_dn']] = $entry['owncloud_name']; + } + + return $results; } /** * Searches mapped names by the giving string in the name column + * * @param string $search * @param string $prefixMatch * @param string $postfixMatch @@ -168,11 +217,11 @@ abstract class AbstractMapping { public function getNamesBySearch($search, $prefixMatch = "", $postfixMatch = "") { $query = $this->dbc->prepare(' SELECT `owncloud_name` - FROM `'. $this->getTableName() .'` + FROM `' . $this->getTableName() . '` WHERE `owncloud_name` LIKE ? '); - $res = $query->execute([$prefixMatch.$this->dbc->escapeLikeParameter($search).$postfixMatch]); + $res = $query->execute([$prefixMatch . $this->dbc->escapeLikeParameter($search) . $postfixMatch]); $names = []; if ($res !== false) { while ($row = $query->fetch()) { @@ -184,6 +233,7 @@ abstract class AbstractMapping { /** * Gets the name based on the provided LDAP UUID. + * * @param string $uuid * @return string|false */ @@ -191,8 +241,13 @@ abstract class AbstractMapping { return $this->getXbyY('owncloud_name', 'directory_uuid', $uuid); } + public function getDnByUUID($uuid) { + return $this->getXbyY('ldap_dn', 'directory_uuid', $uuid); + } + /** * Gets the UUID based on the provided LDAP DN + * * @param string $dn * @return false|string * @throws \Exception @@ -203,6 +258,7 @@ abstract class AbstractMapping { /** * gets a piece of the mapping list + * * @param int $offset * @param int $limit * @return array @@ -224,6 +280,7 @@ abstract class AbstractMapping { /** * attempts to map the given entry + * * @param string $fdn fully distinguished name (from LDAP) * @param string $name * @param string $uuid a unique identifier as used in LDAP @@ -242,13 +299,16 @@ abstract class AbstractMapping { } $row = [ - 'ldap_dn' => $fdn, - 'owncloud_name' => $name, + 'ldap_dn' => $fdn, + 'owncloud_name' => $name, 'directory_uuid' => $uuid ]; try { $result = $this->dbc->insertIfNotExist($this->getTableName(), $row); + if ((bool)$result === true) { + $this->cache[$fdn] = $name; + } // insertIfNotExist returns values as int return (bool)$result; } catch (\Exception $e) { @@ -258,12 +318,13 @@ abstract class AbstractMapping { /** * removes a mapping based on the owncloud_name of the entry + * * @param string $name * @return bool */ public function unmap($name) { $query = $this->dbc->prepare(' - DELETE FROM `'. $this->getTableName() .'` + DELETE FROM `' . $this->getTableName() . '` WHERE `owncloud_name` = ?'); return $this->modify($query, [$name]); @@ -271,6 +332,7 @@ abstract class AbstractMapping { /** * Truncate's the mapping table + * * @return bool */ public function clear() { diff --git a/apps/user_ldap/lib/Mapping/GroupMapping.php b/apps/user_ldap/lib/Mapping/GroupMapping.php index b2c1b9c99af..703cc56a02a 100644 --- a/apps/user_ldap/lib/Mapping/GroupMapping.php +++ b/apps/user_ldap/lib/Mapping/GroupMapping.php @@ -33,7 +33,8 @@ class GroupMapping extends AbstractMapping { * returns the DB table name which holds the mappings * @return string */ - protected function getTableName() { - return '*PREFIX*ldap_group_mapping'; + protected function getTableName(bool $includePrefix = true) { + $p = $includePrefix ? '*PREFIX*' : ''; + return $p . 'ldap_group_mapping'; } } diff --git a/apps/user_ldap/lib/Mapping/UserMapping.php b/apps/user_ldap/lib/Mapping/UserMapping.php index 556f7ecf1a4..b70cb866904 100644 --- a/apps/user_ldap/lib/Mapping/UserMapping.php +++ b/apps/user_ldap/lib/Mapping/UserMapping.php @@ -33,7 +33,8 @@ class UserMapping extends AbstractMapping { * returns the DB table name which holds the mappings * @return string */ - protected function getTableName() { - return '*PREFIX*ldap_user_mapping'; + protected function getTableName(bool $includePrefix = true) { + $p = $includePrefix ? '*PREFIX*' : ''; + return $p . 'ldap_user_mapping'; } } diff --git a/apps/user_ldap/lib/Proxy.php b/apps/user_ldap/lib/Proxy.php index 3cf55f8cd58..7bcbd19ff1c 100644 --- a/apps/user_ldap/lib/Proxy.php +++ b/apps/user_ldap/lib/Proxy.php @@ -40,6 +40,8 @@ use OCA\User_LDAP\User\Manager; abstract class Proxy { private static $accesses = []; private $ldap = null; + /** @var bool */ + private $isSingleBackend; /** @var \OCP\ICache|null */ private $cache; @@ -70,11 +72,11 @@ abstract class Proxy { static $coreNotificationManager; if ($fs === null) { $ocConfig = \OC::$server->getConfig(); - $fs = new FilesystemHelper(); - $log = new LogWrapper(); - $avatarM = \OC::$server->getAvatarManager(); - $db = \OC::$server->getDatabaseConnection(); - $userMap = new UserMapping($db); + $fs = new FilesystemHelper(); + $log = new LogWrapper(); + $avatarM = \OC::$server->getAvatarManager(); + $db = \OC::$server->getDatabaseConnection(); + $userMap = new UserMapping($db); $groupMap = new GroupMapping($db); $coreUserManager = \OC::$server->getUserManager(); $coreNotificationManager = \OC::$server->getNotificationManager(); @@ -105,7 +107,7 @@ abstract class Proxy { * @return string */ protected function getUserCacheKey($uid) { - return 'user-'.$uid.'-lastSeenOn'; + return 'user-' . $uid . '-lastSeenOn'; } /** @@ -113,7 +115,7 @@ abstract class Proxy { * @return string */ protected function getGroupCacheKey($gid) { - return 'group-'.$gid.'-lastSeenOn'; + return 'group-' . $gid . '-lastSeenOn'; } /** @@ -139,8 +141,18 @@ abstract class Proxy { */ abstract public function getLDAPAccess($id); + abstract protected function activeBackends(): int; + + protected function isSingleBackend(): bool { + if ($this->isSingleBackend === null) { + $this->isSingleBackend = $this->activeBackends() === 1; + } + return $this->isSingleBackend; + } + /** * Takes care of the request to the User backend + * * @param string $id * @param string $method string, the method of the user backend that shall be called * @param array $parameters an array of parameters to be passed @@ -148,8 +160,10 @@ abstract class Proxy { * @return mixed, the result of the specified method */ protected function handleRequest($id, $method, $parameters, $passOnWhen = false) { - $result = $this->callOnLastSeenOn($id, $method, $parameters, $passOnWhen); - if ($result === $passOnWhen) { + if (!$this->isSingleBackend()) { + $result = $this->callOnLastSeenOn($id, $method, $parameters, $passOnWhen); + } + if (!isset($result) || $result === $passOnWhen) { $result = $this->walkBackends($id, $method, $parameters); } return $result; @@ -164,7 +178,7 @@ abstract class Proxy { if ($key === null) { return $prefix; } - return $prefix.hash('sha256', $key); + return $prefix . hash('sha256', $key); } /** @@ -193,7 +207,7 @@ abstract class Proxy { if ($this->cache === null) { return; } - $key = $this->getCacheKey($key); + $key = $this->getCacheKey($key); $value = base64_encode(json_encode($value)); $this->cache->set($key, $value, 2592000); } diff --git a/apps/user_ldap/lib/User_Proxy.php b/apps/user_ldap/lib/User_Proxy.php index e9ff92d03eb..a81e4668347 100644 --- a/apps/user_ldap/lib/User_Proxy.php +++ b/apps/user_ldap/lib/User_Proxy.php @@ -72,6 +72,7 @@ class User_Proxy extends Proxy implements \OCP\IUserBackend, \OCP\UserInterface, /** * Tries the backends one after the other until a positive result is returned from the specified method + * * @param string $uid the uid connected to the request * @param string $method the method of the user backend that shall be called * @param array $parameters an array of parameters to be passed @@ -86,7 +87,9 @@ class User_Proxy extends Proxy implements \OCP\IUserBackend, \OCP\UserInterface, $instance = $this->getAccess($configPrefix); } if ($result = call_user_func_array([$instance, $method], $parameters)) { - $this->writeToCache($cacheKey, $configPrefix); + if (!$this->isSingleBackend()) { + $this->writeToCache($cacheKey, $configPrefix); + } return $result; } } @@ -95,6 +98,7 @@ class User_Proxy extends Proxy implements \OCP\IUserBackend, \OCP\UserInterface, /** * Asks the backend connected to the server that supposely takes care of the uid from the request. + * * @param string $uid the uid connected to the request * @param string $method the method of the user backend that shall be called * @param array $parameters an array of parameters to be passed @@ -130,8 +134,13 @@ class User_Proxy extends Proxy implements \OCP\IUserBackend, \OCP\UserInterface, return false; } + protected function activeBackends(): int { + return count($this->backends); + } + /** * Check if backend implements actions + * * @param int $actions bitwise-or'ed actions * @return boolean * @@ -145,6 +154,7 @@ class User_Proxy extends Proxy implements \OCP\IUserBackend, \OCP\UserInterface, /** * Backend name to be shown in user management + * * @return string the name of the backend to be shown */ public function getBackendName() { @@ -173,6 +183,7 @@ class User_Proxy extends Proxy implements \OCP\IUserBackend, \OCP\UserInterface, /** * check if a user exists + * * @param string $uid the username * @return boolean */ @@ -197,6 +208,7 @@ class User_Proxy extends Proxy implements \OCP\IUserBackend, \OCP\UserInterface, /** * check if a user exists on LDAP + * * @param string|\OCA\User_LDAP\User\User $user either the Nextcloud user * name or an instance of that user * @return boolean @@ -208,6 +220,7 @@ class User_Proxy extends Proxy implements \OCP\IUserBackend, \OCP\UserInterface, /** * Check if the password is correct + * * @param string $uid The username * @param string $password The password * @return bool @@ -228,7 +241,7 @@ class User_Proxy extends Proxy implements \OCP\IUserBackend, \OCP\UserInterface, $id = 'LOGINNAME,' . $loginName; return $this->handleRequest($id, 'loginName2UserName', [$loginName]); } - + /** * returns the username for the given LDAP DN, if available * @@ -242,6 +255,7 @@ class User_Proxy extends Proxy implements \OCP\IUserBackend, \OCP\UserInterface, /** * get the user's home directory + * * @param string $uid the username * @return boolean */ @@ -251,6 +265,7 @@ class User_Proxy extends Proxy implements \OCP\IUserBackend, \OCP\UserInterface, /** * get display name of the user + * * @param string $uid user ID of the user * @return string display name */ @@ -271,6 +286,7 @@ class User_Proxy extends Proxy implements \OCP\IUserBackend, \OCP\UserInterface, /** * checks whether the user is allowed to change his avatar in Nextcloud + * * @param string $uid the Nextcloud user name * @return boolean either the user can or cannot */ @@ -280,6 +296,7 @@ class User_Proxy extends Proxy implements \OCP\IUserBackend, \OCP\UserInterface, /** * Get a list of all display names and user ids. + * * @param string $search * @param string|null $limit * @param string|null $offset @@ -299,6 +316,7 @@ class User_Proxy extends Proxy implements \OCP\IUserBackend, \OCP\UserInterface, /** * delete a user + * * @param string $uid The username of the user to delete * @return bool * @@ -307,9 +325,10 @@ class User_Proxy extends Proxy implements \OCP\IUserBackend, \OCP\UserInterface, public function deleteUser($uid) { return $this->handleRequest($uid, 'deleteUser', [$uid]); } - + /** * Set password + * * @param string $uid The username * @param string $password The new password * @return bool @@ -328,6 +347,7 @@ class User_Proxy extends Proxy implements \OCP\IUserBackend, \OCP\UserInterface, /** * Count the number of users + * * @return int|bool */ public function countUsers() { @@ -343,16 +363,18 @@ class User_Proxy extends Proxy implements \OCP\IUserBackend, \OCP\UserInterface, /** * Return access for LDAP interaction. + * * @param string $uid * @return Access instance of Access for LDAP interaction */ public function getLDAPAccess($uid) { return $this->handleRequest($uid, 'getLDAPAccess', [$uid]); } - + /** * Return a new LDAP connection for the specified user. * The connection needs to be closed manually. + * * @param string $uid * @return resource of the LDAP connection */ @@ -362,11 +384,12 @@ class User_Proxy extends Proxy implements \OCP\IUserBackend, \OCP\UserInterface, /** * Creates a new user in LDAP + * * @param $username * @param $password * @return bool */ public function createUser($username, $password) { - return $this->handleRequest($username, 'createUser', [$username,$password]); + return $this->handleRequest($username, 'createUser', [$username, $password]); } } diff --git a/apps/user_ldap/tests/Group_LDAPTest.php b/apps/user_ldap/tests/Group_LDAPTest.php index ae637e0e584..64160e203a0 100644 --- a/apps/user_ldap/tests/Group_LDAPTest.php +++ b/apps/user_ldap/tests/Group_LDAPTest.php @@ -61,7 +61,7 @@ class Group_LDAPTest extends TestCase { $conMethods = get_class_methods('\OCA\User_LDAP\Connection'); $accMethods = get_class_methods('\OCA\User_LDAP\Access'); } - $lw = $this->createMock(ILDAPWrapper::class); + $lw = $this->createMock(ILDAPWrapper::class); $connector = $this->getMockBuilder('\OCA\User_LDAP\Connection') ->setMethods($conMethods) ->setConstructorArgs([$lw, null, null]) @@ -79,7 +79,7 @@ class Group_LDAPTest extends TestCase { private function getPluginManagerMock() { return $this->getMockBuilder('\OCA\User_LDAP\GroupPluginManager')->getMock(); } - + /** * @param Access|\PHPUnit_Framework_MockObject_MockObject $access */ @@ -120,6 +120,9 @@ class Group_LDAPTest extends TestCase { } return []; }); + $access->expects($this->any()) + ->method('isDNPartOfBase') + ->willReturn(true); // for primary groups $access->expects($this->once()) @@ -141,11 +144,9 @@ class Group_LDAPTest extends TestCase { $access->expects($this->any()) ->method('groupname2dn') ->willReturn('cn=group,dc=foo,dc=bar'); - $access->expects($this->any()) ->method('fetchListOfUsers') ->willReturn([]); - $access->expects($this->any()) ->method('readAttribute') ->willReturnCallback(function ($name) { @@ -159,14 +160,16 @@ class Group_LDAPTest extends TestCase { } return ['u11', 'u22', 'u33', 'u34']; }); - $access->expects($this->any()) ->method('dn2username') ->willReturnCallback(function () { return 'foobar' . \OC::$server->getSecureRandom()->generate(7); }); + $access->expects($this->any()) + ->method('isDNPartOfBase') + ->willReturn(true); - $groupBackend = new GroupLDAP($access,$pluginManager); + $groupBackend = new GroupLDAP($access, $pluginManager); $users = $groupBackend->countUsersInGroup('group', '3'); $this->assertSame(2, $users); @@ -175,7 +178,7 @@ class Group_LDAPTest extends TestCase { public function testCountUsersWithPlugin() { /** @var GroupPluginManager|\PHPUnit_Framework_MockObject_MockObject $pluginManager */ $pluginManager = $this->getMockBuilder('\OCA\User_LDAP\GroupPluginManager') - ->setMethods(['implementsActions','countUsersInGroup']) + ->setMethods(['implementsActions', 'countUsersInGroup']) ->getMock(); $pluginManager->expects($this->once()) @@ -193,7 +196,7 @@ class Group_LDAPTest extends TestCase { $ldap = new GroupLDAP($access, $pluginManager); - $this->assertEquals($ldap->countUsersInGroup('gid', 'search'),42); + $this->assertEquals($ldap->countUsersInGroup('gid', 'search'), 42); } public function testGidNumber2NameSuccess() { @@ -475,7 +478,7 @@ class Group_LDAPTest extends TestCase { $uid = 'someUser'; $gid = 'someGroup'; - $cacheKey = 'inGroup'.$uid.':'.$gid; + $cacheKey = 'inGroup' . $uid . ':' . $gid; $access->connection->expects($this->once()) ->method('getFromCache') @@ -534,6 +537,9 @@ class Group_LDAPTest extends TestCase { $access->expects($this->exactly(2)) ->method('nextcloudUserNames') ->willReturnOnConsecutiveCalls(['lisa', 'bart', 'kira', 'brad'], ['walle', 'dino', 'xenia']); + $access->expects($this->any()) + ->method('isDNPartOfBase') + ->willReturn(true); $access->userManager = $this->createMock(Manager::class); $groupBackend = new GroupLDAP($access, $pluginManager); @@ -569,6 +575,9 @@ class Group_LDAPTest extends TestCase { $access->expects($this->once()) ->method('nextcloudUserNames') ->willReturn(['lisa', 'bart', 'kira', 'brad']); + $access->expects($this->any()) + ->method('isDNPartOfBase') + ->willReturn(true); $access->userManager = $this->createMock(Manager::class); $groupBackend = new GroupLDAP($access, $pluginManager); @@ -599,14 +608,15 @@ class Group_LDAPTest extends TestCase { } return []; }); - $access->expects($this->any()) ->method('groupname2dn') ->willReturn('cn=foobar,dc=foo,dc=bar'); - $access->expects($this->once()) ->method('countUsers') ->willReturn(4); + $access->expects($this->any()) + ->method('isDNPartOfBase') + ->willReturn(true); $groupBackend = new GroupLDAP($access, $pluginManager); $users = $groupBackend->countUsersInGroup('foobar'); @@ -629,17 +639,19 @@ class Group_LDAPTest extends TestCase { ->method('username2dn') ->willReturn($dn); - $access->expects($this->exactly(3)) + $access->expects($this->exactly(5)) ->method('readAttribute') - ->will($this->onConsecutiveCalls(['cn=groupA,dc=foobar', 'cn=groupB,dc=foobar'], [], [])); + ->will($this->onConsecutiveCalls(['cn=groupA,dc=foobar', 'cn=groupB,dc=foobar'], [], [], [], [])); - $access->expects($this->exactly(2)) + $access->expects($this->any()) ->method('dn2groupname') ->willReturnArgument(0); - - $access->expects($this->exactly(1)) - ->method('groupsMatchFilter') + $access->expects($this->any()) + ->method('groupname2dn') ->willReturnArgument(0); + $access->expects($this->any()) + ->method('isDNPartOfBase') + ->willReturn(true); $groupBackend = new GroupLDAP($access, $pluginManager); $groups = $groupBackend->getUserGroups('userX'); @@ -677,9 +689,6 @@ class Group_LDAPTest extends TestCase { $access->expects($this->once()) ->method('nextcloudGroupNames') ->willReturn([]); - $access->expects($this->any()) - ->method('groupsMatchFilter') - ->willReturnArgument(0); $groupBackend = new GroupLDAP($access, $pluginManager); $groupBackend->getUserGroups('userX'); @@ -715,9 +724,9 @@ class Group_LDAPTest extends TestCase { ->method('username2dn') ->willReturn($dn); - $access->expects($this->never()) + $access->expects($this->any()) ->method('readAttribute') - ->with($dn, 'memberOf'); + ->willReturn([]); $group1 = [ 'cn' => 'group1', @@ -736,8 +745,23 @@ class Group_LDAPTest extends TestCase { ->method('fetchListOfGroups') ->willReturn([$group1, $group2]); $access->expects($this->any()) - ->method('groupsMatchFilter') - ->willReturnArgument(0); + ->method('dn2groupname') + ->willReturnCallback(function (string $dn) { + return ldap_explode_dn($dn, 1)[0]; + }); + $access->expects($this->any()) + ->method('groupname2dn') + ->willReturnCallback(function (string $gid) use ($group1, $group2) { + if ($gid === $group1['cn']) { + return $group1['dn'][0]; + } + if ($gid === $group2['cn']) { + return $group2['dn'][0]; + } + }); + $access->expects($this->any()) + ->method('isDNPartOfBase') + ->willReturn(true); $groupBackend = new GroupLDAP($access, $pluginManager); $groups = $groupBackend->getUserGroups('userX'); @@ -750,7 +774,7 @@ class Group_LDAPTest extends TestCase { public function testCreateGroupWithPlugin() { /** @var GroupPluginManager|\PHPUnit_Framework_MockObject_MockObject $pluginManager */ $pluginManager = $this->getMockBuilder('\OCA\User_LDAP\GroupPluginManager') - ->setMethods(['implementsActions','createGroup']) + ->setMethods(['implementsActions', 'createGroup']) ->getMock(); $pluginManager->expects($this->once()) @@ -768,10 +792,10 @@ class Group_LDAPTest extends TestCase { $ldap = new GroupLDAP($access, $pluginManager); - $this->assertEquals($ldap->createGroup('gid'),true); + $this->assertEquals($ldap->createGroup('gid'), true); } - + public function testCreateGroupFailing() { $this->expectException(\Exception::class); @@ -796,7 +820,7 @@ class Group_LDAPTest extends TestCase { public function testDeleteGroupWithPlugin() { /** @var GroupPluginManager|\PHPUnit_Framework_MockObject_MockObject $pluginManager */ $pluginManager = $this->getMockBuilder('\OCA\User_LDAP\GroupPluginManager') - ->setMethods(['implementsActions','deleteGroup']) + ->setMethods(['implementsActions', 'deleteGroup']) ->getMock(); $pluginManager->expects($this->once()) @@ -823,10 +847,10 @@ class Group_LDAPTest extends TestCase { $ldap = new GroupLDAP($access, $pluginManager); - $this->assertEquals($ldap->deleteGroup('gid'),'result'); + $this->assertEquals($ldap->deleteGroup('gid'), 'result'); } - + public function testDeleteGroupFailing() { $this->expectException(\Exception::class); @@ -851,7 +875,7 @@ class Group_LDAPTest extends TestCase { public function testAddToGroupWithPlugin() { /** @var GroupPluginManager|\PHPUnit_Framework_MockObject_MockObject $pluginManager */ $pluginManager = $this->getMockBuilder('\OCA\User_LDAP\GroupPluginManager') - ->setMethods(['implementsActions','addToGroup']) + ->setMethods(['implementsActions', 'addToGroup']) ->getMock(); $pluginManager->expects($this->once()) @@ -869,10 +893,10 @@ class Group_LDAPTest extends TestCase { $ldap = new GroupLDAP($access, $pluginManager); - $this->assertEquals($ldap->addToGroup('uid', 'gid'),'result'); + $this->assertEquals($ldap->addToGroup('uid', 'gid'), 'result'); } - + public function testAddToGroupFailing() { $this->expectException(\Exception::class); @@ -897,7 +921,7 @@ class Group_LDAPTest extends TestCase { public function testRemoveFromGroupWithPlugin() { /** @var GroupPluginManager|\PHPUnit_Framework_MockObject_MockObject $pluginManager */ $pluginManager = $this->getMockBuilder('\OCA\User_LDAP\GroupPluginManager') - ->setMethods(['implementsActions','removeFromGroup']) + ->setMethods(['implementsActions', 'removeFromGroup']) ->getMock(); $pluginManager->expects($this->once()) @@ -915,10 +939,10 @@ class Group_LDAPTest extends TestCase { $ldap = new GroupLDAP($access, $pluginManager); - $this->assertEquals($ldap->removeFromGroup('uid', 'gid'),'result'); + $this->assertEquals($ldap->removeFromGroup('uid', 'gid'), 'result'); } - + public function testRemoveFromGroupFailing() { $this->expectException(\Exception::class); @@ -943,7 +967,7 @@ class Group_LDAPTest extends TestCase { public function testGetGroupDetailsWithPlugin() { /** @var GroupPluginManager|\PHPUnit_Framework_MockObject_MockObject $pluginManager */ $pluginManager = $this->getMockBuilder('\OCA\User_LDAP\GroupPluginManager') - ->setMethods(['implementsActions','getGroupDetails']) + ->setMethods(['implementsActions', 'getGroupDetails']) ->getMock(); $pluginManager->expects($this->once()) @@ -961,10 +985,10 @@ class Group_LDAPTest extends TestCase { $ldap = new GroupLDAP($access, $pluginManager); - $this->assertEquals($ldap->getGroupDetails('gid'),'result'); + $this->assertEquals($ldap->getGroupDetails('gid'), 'result'); } - + public function testGetGroupDetailsFailing() { $this->expectException(\Exception::class); diff --git a/apps/workflowengine/l10n/sk.js b/apps/workflowengine/l10n/sk.js index 39f85edbf40..c11ae3cd5e3 100644 --- a/apps/workflowengine/l10n/sk.js +++ b/apps/workflowengine/l10n/sk.js @@ -20,6 +20,14 @@ OC.L10N.register( "File accessed" : "Prístup k súboru", "File copied" : "Kopírovaný súbor", "Tag assigned" : "Priradený štítok", + "Someone" : "Niekto", + "%s created %s" : "%s vytvorené %s", + "%s modified %s" : "%s zmenené %s", + "%s deleted %s" : "%s vymazané %s", + "%s accessed %s" : "%s pristupené k %s", + "%s renamed %s" : "%s premenované %s", + "%s copied %s" : "%s skopírovné %s", + "%s assigned %s to %s" : "%s priradené %s k %s", "Operation #%s does not exist" : "Operácia #%s neexistuje", "Entity %s does not exist" : "Entita%s neexistuje", "Entity %s is invalid" : "Entita %s je neplatná", @@ -27,12 +35,15 @@ OC.L10N.register( "Entity %s has no event %s" : "Entita %s nemá udalosť %s", "Operation %s does not exist" : "Operácia %s neexistuje", "Operation %s is invalid" : "Operácia #%s nie je platná", + "At least one check needs to be provided" : "Musí sa vykonať aspoň jedna kontrola", + "Invalid check provided" : "Poskytnutá neplatná kontrola", "Check %s does not exist" : "Kontrola %s neexistuje", "Check %s is invalid" : "Kontrola %s je neplatná", "Check %s is not allowed with this entity" : "Kontrola %s nie je pre túto entitu povolená", "Check #%s does not exist" : "Kontrola #%s neexistuje", "Check %s is invalid or does not exist" : "Kontrola %s je neplatná alebo neexistuje", "Flow" : "Flow (tok)", + "Nextcloud workflow engine" : "Služba Nextcloud pre pracovné postupy", "Select a filter" : "Vybrať filter", "Select a comparator" : "Vybrať porovnávač", "Select a file type" : "Vyberte typ súboru", @@ -58,15 +69,23 @@ OC.L10N.register( "Desktop client" : "Desktopový klient", "Thunderbird & Outlook addons" : "Doplnky pre Thunderbird a Outlook", "Custom user agent" : "Vlastný agent užívateľa", + "At least one event must be selected" : "Musí byť vybraná aspoň jedna udalosť", + "Add new flow" : "Pridať nový tok", "When" : "Keď", "and" : "a", "Cancel" : "Zrušiť", "Delete" : "Zmazať", "The configuration is invalid" : "Konfigurácia je neplatná", + "Active" : "Aktívne", "Save" : "Uložiť", + "Available flows" : "Dostupné toky", + "For details on how to write your own flow, check out the development documentation." : "Podrobnosti o tom, ako vytvárať vlastné toky, nájdete v dokumentácii pre vývojárov.", + "More flows" : "Ďalšie toky", "Browse the app store" : "Prehliadajte obchod aplikácií", "Show less" : "Zobraziť menej", "Show more" : "Zobraziť viac", + "Configured flows" : "Nastavené toky", + "Your flows" : "Vaše toky", "matches" : "súhlasí", "does not match" : "nesúhlasí", "is" : "je", diff --git a/apps/workflowengine/l10n/sk.json b/apps/workflowengine/l10n/sk.json index f4a97eeafbb..ccfe5c39479 100644 --- a/apps/workflowengine/l10n/sk.json +++ b/apps/workflowengine/l10n/sk.json @@ -18,6 +18,14 @@ "File accessed" : "Prístup k súboru", "File copied" : "Kopírovaný súbor", "Tag assigned" : "Priradený štítok", + "Someone" : "Niekto", + "%s created %s" : "%s vytvorené %s", + "%s modified %s" : "%s zmenené %s", + "%s deleted %s" : "%s vymazané %s", + "%s accessed %s" : "%s pristupené k %s", + "%s renamed %s" : "%s premenované %s", + "%s copied %s" : "%s skopírovné %s", + "%s assigned %s to %s" : "%s priradené %s k %s", "Operation #%s does not exist" : "Operácia #%s neexistuje", "Entity %s does not exist" : "Entita%s neexistuje", "Entity %s is invalid" : "Entita %s je neplatná", @@ -25,12 +33,15 @@ "Entity %s has no event %s" : "Entita %s nemá udalosť %s", "Operation %s does not exist" : "Operácia %s neexistuje", "Operation %s is invalid" : "Operácia #%s nie je platná", + "At least one check needs to be provided" : "Musí sa vykonať aspoň jedna kontrola", + "Invalid check provided" : "Poskytnutá neplatná kontrola", "Check %s does not exist" : "Kontrola %s neexistuje", "Check %s is invalid" : "Kontrola %s je neplatná", "Check %s is not allowed with this entity" : "Kontrola %s nie je pre túto entitu povolená", "Check #%s does not exist" : "Kontrola #%s neexistuje", "Check %s is invalid or does not exist" : "Kontrola %s je neplatná alebo neexistuje", "Flow" : "Flow (tok)", + "Nextcloud workflow engine" : "Služba Nextcloud pre pracovné postupy", "Select a filter" : "Vybrať filter", "Select a comparator" : "Vybrať porovnávač", "Select a file type" : "Vyberte typ súboru", @@ -56,15 +67,23 @@ "Desktop client" : "Desktopový klient", "Thunderbird & Outlook addons" : "Doplnky pre Thunderbird a Outlook", "Custom user agent" : "Vlastný agent užívateľa", + "At least one event must be selected" : "Musí byť vybraná aspoň jedna udalosť", + "Add new flow" : "Pridať nový tok", "When" : "Keď", "and" : "a", "Cancel" : "Zrušiť", "Delete" : "Zmazať", "The configuration is invalid" : "Konfigurácia je neplatná", + "Active" : "Aktívne", "Save" : "Uložiť", + "Available flows" : "Dostupné toky", + "For details on how to write your own flow, check out the development documentation." : "Podrobnosti o tom, ako vytvárať vlastné toky, nájdete v dokumentácii pre vývojárov.", + "More flows" : "Ďalšie toky", "Browse the app store" : "Prehliadajte obchod aplikácií", "Show less" : "Zobraziť menej", "Show more" : "Zobraziť viac", + "Configured flows" : "Nastavené toky", + "Your flows" : "Vaše toky", "matches" : "súhlasí", "does not match" : "nesúhlasí", "is" : "je", |