diff options
60 files changed, 674 insertions, 78 deletions
diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 00000000000..d9060072e46 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,9 @@ +In the Nextcloud community, participants from all over the world come together to create Free Software for a free internet. This is made possible by the support, hard work and enthusiasm of thousands of people, including those who create and use Nextcloud software. + +Our code of conduct offers some guidance to ensure Nextcloud participants can cooperate effectively in a positive and inspiring atmosphere, and to explain how together we can strengthen and support each other. + +The Code of Conduct is shared by all contributors and users who engage with the Nextcloud team and its community services. It presents a summary of the shared values and “common sense” thinking in our community. + +You can find our full code of conduct on our website: https://nextcloud.com/code-of-conduct/ + +Please, keep our CoC in mind when you contribute! That way, everyone can be a part of our community in a productive, positive, creative and fun way. diff --git a/apps/files/js/mainfileinfodetailview.js b/apps/files/js/mainfileinfodetailview.js index ea3063f6176..626ab86ded3 100644 --- a/apps/files/js/mainfileinfodetailview.js +++ b/apps/files/js/mainfileinfodetailview.js @@ -20,9 +20,11 @@ '</a>' + '</div>' + ' <div class="file-details ellipsis">' + + ' {{#if hasFavoriteAction}}' + ' <a href="#" class="action action-favorite favorite permanent">' + ' <span class="icon {{starClass}}" title="{{starAltText}}"></span>' + ' </a>' + + ' {{/if}}' + ' {{#if hasSize}}<span class="size" title="{{altSize}}">{{size}}</span>, {{/if}}<span class="date live-relative-timestamp" data-timestamp="{{timestamp}}" title="{{altDate}}">{{date}}</span>' + ' </div>' + '</div>' + @@ -175,6 +177,12 @@ if (this.model) { var isFavorite = (this.model.get('tags') || []).indexOf(OC.TAG_FAVORITE) >= 0; + var availableActions = this._fileActions.get( + this.model.get('mimetype'), + this.model.get('type'), + this.model.get('permissions') + ); + var hasFavoriteAction = 'Favorite' in availableActions; this.$el.html(this.template({ type: this.model.isImage()? 'image': '', nameLabel: t('files', 'Name'), @@ -189,6 +197,7 @@ altDate: OC.Util.formatDate(this.model.get('mtime')), timestamp: this.model.get('mtime'), date: OC.Util.relativeModifiedDate(this.model.get('mtime')), + hasFavoriteAction: hasFavoriteAction, starAltText: isFavorite ? t('files', 'Favorited') : t('files', 'Favorite'), starClass: isFavorite ? 'icon-starred' : 'icon-star', permalink: this._makePermalink(this.model.get('id')), diff --git a/apps/files/l10n/es.js b/apps/files/l10n/es.js index a4666bd7bdf..0852da86efc 100644 --- a/apps/files/l10n/es.js +++ b/apps/files/l10n/es.js @@ -62,8 +62,11 @@ OC.L10N.register( "You don’t have permission to upload or create files here" : "No tiene permisos para subir o crear archivos aquí", "_Uploading %n file_::_Uploading %n files_" : ["Subiendo %n archivo","Subiendo %n archivos"], "New" : "Nuevo", + "{used} of {quota} used" : "{used} usados de {quota}", + "{used} used" : "{used} usados", "\"{name}\" is an invalid file name." : "\"{name}\" es un nombre de archivo inválido.", "File name cannot be empty." : "El nombre de archivo no puede estar vacío.", + "\"/\" is not allowed inside a file name." : "\"/\" no se permite dentro de un nombre de archivo.", "\"{name}\" is not an allowed filetype" : "\"{name}\" no es un tipo de archivo permitido", "Storage of {owner} is full, files can not be updated or synced anymore!" : "El almacén de {owner} está repleto, ¡los archivos no se actualizarán ni sincronizarán más!", "Your storage is full, files can not be updated or synced anymore!" : "Su almacenamiento está lleno, ¡los archivos no se actualizarán ni sincronizarán más!", diff --git a/apps/files/l10n/es.json b/apps/files/l10n/es.json index 44db92002f2..db8baad60b3 100644 --- a/apps/files/l10n/es.json +++ b/apps/files/l10n/es.json @@ -60,8 +60,11 @@ "You don’t have permission to upload or create files here" : "No tiene permisos para subir o crear archivos aquí", "_Uploading %n file_::_Uploading %n files_" : ["Subiendo %n archivo","Subiendo %n archivos"], "New" : "Nuevo", + "{used} of {quota} used" : "{used} usados de {quota}", + "{used} used" : "{used} usados", "\"{name}\" is an invalid file name." : "\"{name}\" es un nombre de archivo inválido.", "File name cannot be empty." : "El nombre de archivo no puede estar vacío.", + "\"/\" is not allowed inside a file name." : "\"/\" no se permite dentro de un nombre de archivo.", "\"{name}\" is not an allowed filetype" : "\"{name}\" no es un tipo de archivo permitido", "Storage of {owner} is full, files can not be updated or synced anymore!" : "El almacén de {owner} está repleto, ¡los archivos no se actualizarán ni sincronizarán más!", "Your storage is full, files can not be updated or synced anymore!" : "Su almacenamiento está lleno, ¡los archivos no se actualizarán ni sincronizarán más!", diff --git a/apps/files/l10n/nb.js b/apps/files/l10n/nb.js index b9e3ee0a1b8..45b9027f782 100644 --- a/apps/files/l10n/nb.js +++ b/apps/files/l10n/nb.js @@ -62,6 +62,8 @@ OC.L10N.register( "You don’t have permission to upload or create files here" : "Du har ikke tillatelse til å laste opp eller opprette filer her", "_Uploading %n file_::_Uploading %n files_" : ["Laster opp %n fil","Laster opp %n filer"], "New" : "Ny", + "{used} of {quota} used" : "{used} av {quota} brukt", + "{used} used" : "{used} brukt", "\"{name}\" is an invalid file name." : "\"{name}\" er et uglydig filnavn.", "File name cannot be empty." : "Filnavn kan ikke være tomt.", "\"{name}\" is not an allowed filetype" : "\"{name}\" er ikke en tillatt filtype", diff --git a/apps/files/l10n/nb.json b/apps/files/l10n/nb.json index 4c4e0622362..0328ceb283a 100644 --- a/apps/files/l10n/nb.json +++ b/apps/files/l10n/nb.json @@ -60,6 +60,8 @@ "You don’t have permission to upload or create files here" : "Du har ikke tillatelse til å laste opp eller opprette filer her", "_Uploading %n file_::_Uploading %n files_" : ["Laster opp %n fil","Laster opp %n filer"], "New" : "Ny", + "{used} of {quota} used" : "{used} av {quota} brukt", + "{used} used" : "{used} brukt", "\"{name}\" is an invalid file name." : "\"{name}\" er et uglydig filnavn.", "File name cannot be empty." : "Filnavn kan ikke være tomt.", "\"{name}\" is not an allowed filetype" : "\"{name}\" er ikke en tillatt filtype", diff --git a/apps/files/l10n/ru.js b/apps/files/l10n/ru.js index b6222725a72..9878e2f78dd 100644 --- a/apps/files/l10n/ru.js +++ b/apps/files/l10n/ru.js @@ -62,8 +62,11 @@ OC.L10N.register( "You don’t have permission to upload or create files here" : "У вас нет разрешений на создание или загрузку файлов в эту папку.", "_Uploading %n file_::_Uploading %n files_" : ["Выгружа%nется файл","Выгружаются %n файла","Выгружаются %n файлов","Загружаются %n файлов"], "New" : "Новый", + "{used} of {quota} used" : "использовано {used} из {quota}", + "{used} used" : "использовано {used}", "\"{name}\" is an invalid file name." : "«{name}» — недопустимое имя файла.", "File name cannot be empty." : "Имя файла не может быть пустым.", + "\"/\" is not allowed inside a file name." : "Символ «/» недопустим в имени файла.", "\"{name}\" is not an allowed filetype" : "«{name}» - недопустимый тип файла.", "Storage of {owner} is full, files can not be updated or synced anymore!" : "Хранилище {owner} переполнено, файлы больше не могут быть обновлены или синхронизированы!", "Your storage is full, files can not be updated or synced anymore!" : "Ваше хранилище переполнено, файлы больше не могут быть обновлены или синхронизированы!", diff --git a/apps/files/l10n/ru.json b/apps/files/l10n/ru.json index e989cdd3183..be07497fda4 100644 --- a/apps/files/l10n/ru.json +++ b/apps/files/l10n/ru.json @@ -60,8 +60,11 @@ "You don’t have permission to upload or create files here" : "У вас нет разрешений на создание или загрузку файлов в эту папку.", "_Uploading %n file_::_Uploading %n files_" : ["Выгружа%nется файл","Выгружаются %n файла","Выгружаются %n файлов","Загружаются %n файлов"], "New" : "Новый", + "{used} of {quota} used" : "использовано {used} из {quota}", + "{used} used" : "использовано {used}", "\"{name}\" is an invalid file name." : "«{name}» — недопустимое имя файла.", "File name cannot be empty." : "Имя файла не может быть пустым.", + "\"/\" is not allowed inside a file name." : "Символ «/» недопустим в имени файла.", "\"{name}\" is not an allowed filetype" : "«{name}» - недопустимый тип файла.", "Storage of {owner} is full, files can not be updated or synced anymore!" : "Хранилище {owner} переполнено, файлы больше не могут быть обновлены или синхронизированы!", "Your storage is full, files can not be updated or synced anymore!" : "Ваше хранилище переполнено, файлы больше не могут быть обновлены или синхронизированы!", diff --git a/apps/files/l10n/tr.js b/apps/files/l10n/tr.js index 6d68c8889bc..e1252d20b18 100644 --- a/apps/files/l10n/tr.js +++ b/apps/files/l10n/tr.js @@ -62,8 +62,11 @@ OC.L10N.register( "You don’t have permission to upload or create files here" : "Buraya dosya yükleme veya ekleme izniniz yok", "_Uploading %n file_::_Uploading %n files_" : ["%n dosya yükleniyor","%n dosya yükleniyor"], "New" : "Yeni", + "{used} of {quota} used" : "{used} / {quota} kullanılmış", + "{used} used" : "{used} kullanılmış", "\"{name}\" is an invalid file name." : "\"{name}\" geçersiz bir dosya adı.", "File name cannot be empty." : "Dosya adı boş olamaz.", + "\"/\" is not allowed inside a file name." : "Dosya adında \"/\" kullanılamaz.", "\"{name}\" is not an allowed filetype" : "\"{name}\" dosya türüne izin verilmiyor", "Storage of {owner} is full, files can not be updated or synced anymore!" : "{owner} için boş depolama alanı kalmadı. Artık dosyalar güncellenmeyecek ya da eşitlenmeyecek!", "Your storage is full, files can not be updated or synced anymore!" : "Boş depolama alanınız kalmadı. Artık dosyalar güncellenmeyecek ya da eşitlenmeyecek!", diff --git a/apps/files/l10n/tr.json b/apps/files/l10n/tr.json index cde68e5b35b..ca8bfbf8028 100644 --- a/apps/files/l10n/tr.json +++ b/apps/files/l10n/tr.json @@ -60,8 +60,11 @@ "You don’t have permission to upload or create files here" : "Buraya dosya yükleme veya ekleme izniniz yok", "_Uploading %n file_::_Uploading %n files_" : ["%n dosya yükleniyor","%n dosya yükleniyor"], "New" : "Yeni", + "{used} of {quota} used" : "{used} / {quota} kullanılmış", + "{used} used" : "{used} kullanılmış", "\"{name}\" is an invalid file name." : "\"{name}\" geçersiz bir dosya adı.", "File name cannot be empty." : "Dosya adı boş olamaz.", + "\"/\" is not allowed inside a file name." : "Dosya adında \"/\" kullanılamaz.", "\"{name}\" is not an allowed filetype" : "\"{name}\" dosya türüne izin verilmiyor", "Storage of {owner} is full, files can not be updated or synced anymore!" : "{owner} için boş depolama alanı kalmadı. Artık dosyalar güncellenmeyecek ya da eşitlenmeyecek!", "Your storage is full, files can not be updated or synced anymore!" : "Boş depolama alanınız kalmadı. Artık dosyalar güncellenmeyecek ya da eşitlenmeyecek!", diff --git a/apps/files/lib/AppInfo/Application.php b/apps/files/lib/AppInfo/Application.php index 7042af10ca2..e55d1c549a5 100644 --- a/apps/files/lib/AppInfo/Application.php +++ b/apps/files/lib/AppInfo/Application.php @@ -81,13 +81,14 @@ class Application extends App { $container->registerService('Tagger', function(IContainer $c) { return $c->query('ServerContainer')->getTagManager()->load('files'); }); - $container->registerService('TagService', function(IContainer $c) { + $container->registerService('TagService', function(IContainer $c) use ($server) { $homeFolder = $c->query('ServerContainer')->getUserFolder(); return new TagService( $c->query('ServerContainer')->getUserSession(), $c->query('ServerContainer')->getActivityManager(), $c->query('Tagger'), - $homeFolder + $homeFolder, + $server->getEventDispatcher() ); }); diff --git a/apps/files/lib/Service/TagService.php b/apps/files/lib/Service/TagService.php index d812b16c30e..7437f0c31ad 100644 --- a/apps/files/lib/Service/TagService.php +++ b/apps/files/lib/Service/TagService.php @@ -31,6 +31,8 @@ use OCP\Files\Folder; use OCP\ITags; use OCP\IUser; use OCP\IUserSession; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; +use Symfony\Component\EventDispatcher\GenericEvent; /** * Service class to manage tags on files. @@ -45,23 +47,28 @@ class TagService { private $tagger; /** @var Folder */ private $homeFolder; + /** @var EventDispatcherInterface */ + private $dispatcher; /** * @param IUserSession $userSession * @param IManager $activityManager * @param ITags $tagger * @param Folder $homeFolder + * @param EventDispatcherInterface $dispatcher */ public function __construct( IUserSession $userSession, IManager $activityManager, ITags $tagger, - Folder $homeFolder + Folder $homeFolder, + EventDispatcherInterface $dispatcher ) { $this->userSession = $userSession; $this->activityManager = $activityManager; $this->tagger = $tagger; $this->homeFolder = $homeFolder; + $this->dispatcher = $dispatcher; } /** @@ -114,6 +121,13 @@ class TagService { return; } + $eventName = $addToFavorite ? 'addFavorite' : 'removeFavorite'; + $this->dispatcher->dispatch(self::class . '::' . $eventName, new GenericEvent(null, [ + 'userId' => $user->getUID(), + 'fileId' => $fileId, + 'path' => $path, + ])); + $event = $this->activityManager->generateEvent(); try { $event->setApp('files') diff --git a/apps/files/tests/Service/TagServiceTest.php b/apps/files/tests/Service/TagServiceTest.php index 1c4ac2328ec..4e2aeb84246 100644 --- a/apps/files/tests/Service/TagServiceTest.php +++ b/apps/files/tests/Service/TagServiceTest.php @@ -28,6 +28,7 @@ use OC\Tags; use OCA\Files\Service\TagService; use OCP\Activity\IManager; use OCP\IUserSession; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; /** * Class TagServiceTest @@ -54,6 +55,9 @@ class TagServiceTest extends \Test\TestCase { */ private $root; + /** @var EventDispatcherInterface|\PHPUnit_Framework_MockObject_MockObject */ + private $dispatcher; + /** * @var \OCA\Files\Service\TagService|\PHPUnit_Framework_MockObject_MockObject */ @@ -66,7 +70,7 @@ class TagServiceTest extends \Test\TestCase { protected function setUp() { parent::setUp(); - $this->user = $this->getUniqueID('user'); + $this->user = static::getUniqueID('user'); $this->activityManager = $this->createMock(IManager::class); \OC::$server->getUserManager()->createUser($this->user, 'test'); \OC_User::setUserId($this->user); @@ -83,6 +87,7 @@ class TagServiceTest extends \Test\TestCase { ->will($this->returnValue($user)); $this->root = \OC::$server->getUserFolder(); + $this->dispatcher = $this->createMock(EventDispatcherInterface::class); $this->tagger = \OC::$server->getTagManager()->load('files'); $this->tagService = $this->getTagService(['addActivity']); @@ -99,6 +104,7 @@ class TagServiceTest extends \Test\TestCase { $this->activityManager, $this->tagger, $this->root, + $this->dispatcher, ]) ->setMethods($methods) ->getMock(); diff --git a/apps/files/tests/js/mainfileinfodetailviewSpec.js b/apps/files/tests/js/mainfileinfodetailviewSpec.js index faf0faa8d8f..0201429a472 100644 --- a/apps/files/tests/js/mainfileinfodetailviewSpec.js +++ b/apps/files/tests/js/mainfileinfodetailviewSpec.js @@ -68,6 +68,12 @@ describe('OCA.Files.MainFileInfoDetailView tests', function() { .toEqual(OC.getProtocol() + '://' + OC.getHost() + OC.generateUrl('/f/5')); }); it('displays favorite icon', function() { + fileActions.registerAction({ + name: 'Favorite', + mime: 'all', + permissions: OC.PERMISSION_NONE + }); + testFileInfo.set('tags', [OC.TAG_FAVORITE]); view.setFileInfo(testFileInfo); expect(view.$el.find('.action-favorite > span').hasClass('icon-starred')).toEqual(true); @@ -78,6 +84,15 @@ describe('OCA.Files.MainFileInfoDetailView tests', function() { expect(view.$el.find('.action-favorite > span').hasClass('icon-starred')).toEqual(false); expect(view.$el.find('.action-favorite > span').hasClass('icon-star')).toEqual(true); }); + it('does not display favorite icon if favorite action is not available', function() { + testFileInfo.set('tags', [OC.TAG_FAVORITE]); + view.setFileInfo(testFileInfo); + expect(view.$el.find('.action-favorite').length).toEqual(0); + + testFileInfo.set('tags', []); + view.setFileInfo(testFileInfo); + expect(view.$el.find('.action-favorite').length).toEqual(0); + }); it('displays mime icon', function() { // File var lazyLoadPreviewStub = sinon.stub(fileList, 'lazyLoadPreview'); @@ -183,6 +198,13 @@ describe('OCA.Files.MainFileInfoDetailView tests', function() { expect(view.$el.find('.fileName h3').attr('title')).toEqual('hello.txt'); }); it('rerenders when changes are made on the model', function() { + // Show the "Favorite" icon + fileActions.registerAction({ + name: 'Favorite', + mime: 'all', + permissions: OC.PERMISSION_NONE + }); + view.setFileInfo(testFileInfo); testFileInfo.set('tags', [OC.TAG_FAVORITE]); @@ -196,6 +218,13 @@ describe('OCA.Files.MainFileInfoDetailView tests', function() { expect(view.$el.find('.action-favorite > span').hasClass('icon-star')).toEqual(true); }); it('unbinds change listener from model', function() { + // Show the "Favorite" icon + fileActions.registerAction({ + name: 'Favorite', + mime: 'all', + permissions: OC.PERMISSION_NONE + }); + view.setFileInfo(testFileInfo); view.setFileInfo(new OCA.Files.FileInfoModel({ id: 999, diff --git a/apps/files_external/l10n/de.js b/apps/files_external/l10n/de.js index 40126676f0a..eb8fbdf0d8c 100644 --- a/apps/files_external/l10n/de.js +++ b/apps/files_external/l10n/de.js @@ -75,6 +75,7 @@ OC.L10N.register( "Region" : "Region", "Enable SSL" : "SSL aktivieren", "Enable Path Style" : "Pfad-Stil aktivieren", + "Legacy (v2) authentication" : "Legacy-Authentifizierung (V2)", "WebDAV" : "WebDAV", "URL" : "URL", "Remote subfolder" : "Entfernter Unterordner", diff --git a/apps/files_external/l10n/de.json b/apps/files_external/l10n/de.json index c93ba785568..53fd2e422fa 100644 --- a/apps/files_external/l10n/de.json +++ b/apps/files_external/l10n/de.json @@ -73,6 +73,7 @@ "Region" : "Region", "Enable SSL" : "SSL aktivieren", "Enable Path Style" : "Pfad-Stil aktivieren", + "Legacy (v2) authentication" : "Legacy-Authentifizierung (V2)", "WebDAV" : "WebDAV", "URL" : "URL", "Remote subfolder" : "Entfernter Unterordner", diff --git a/apps/files_external/l10n/de_DE.js b/apps/files_external/l10n/de_DE.js index 6851466d426..5f01b5179de 100644 --- a/apps/files_external/l10n/de_DE.js +++ b/apps/files_external/l10n/de_DE.js @@ -75,6 +75,7 @@ OC.L10N.register( "Region" : "Region", "Enable SSL" : "SSL aktivieren", "Enable Path Style" : "Pfadstil aktivieren", + "Legacy (v2) authentication" : "Legacy-Authentifizierung (V2)", "WebDAV" : "WebDAV", "URL" : "Adresse", "Remote subfolder" : "Entfernter Unterordner", diff --git a/apps/files_external/l10n/de_DE.json b/apps/files_external/l10n/de_DE.json index 22e359c0378..e5f25c97a1f 100644 --- a/apps/files_external/l10n/de_DE.json +++ b/apps/files_external/l10n/de_DE.json @@ -73,6 +73,7 @@ "Region" : "Region", "Enable SSL" : "SSL aktivieren", "Enable Path Style" : "Pfadstil aktivieren", + "Legacy (v2) authentication" : "Legacy-Authentifizierung (V2)", "WebDAV" : "WebDAV", "URL" : "Adresse", "Remote subfolder" : "Entfernter Unterordner", diff --git a/apps/files_external/l10n/es.js b/apps/files_external/l10n/es.js index b980588862e..7bfed57158d 100644 --- a/apps/files_external/l10n/es.js +++ b/apps/files_external/l10n/es.js @@ -75,6 +75,7 @@ OC.L10N.register( "Region" : "Región", "Enable SSL" : "Habilitar SSL", "Enable Path Style" : "Habilitar Estilo de Ruta", + "Legacy (v2) authentication" : "Autenticación heredada (v2)", "WebDAV" : "WebDAV", "URL" : "URL", "Remote subfolder" : "Subcarpeta remota", diff --git a/apps/files_external/l10n/es.json b/apps/files_external/l10n/es.json index 0c6ab09d88d..79c81accd58 100644 --- a/apps/files_external/l10n/es.json +++ b/apps/files_external/l10n/es.json @@ -73,6 +73,7 @@ "Region" : "Región", "Enable SSL" : "Habilitar SSL", "Enable Path Style" : "Habilitar Estilo de Ruta", + "Legacy (v2) authentication" : "Autenticación heredada (v2)", "WebDAV" : "WebDAV", "URL" : "URL", "Remote subfolder" : "Subcarpeta remota", diff --git a/apps/files_external/l10n/it.js b/apps/files_external/l10n/it.js index e19f6a657db..4bd2908b431 100644 --- a/apps/files_external/l10n/it.js +++ b/apps/files_external/l10n/it.js @@ -75,6 +75,7 @@ OC.L10N.register( "Region" : "Regione", "Enable SSL" : "Abilita SSL", "Enable Path Style" : "Abilita stile percorsi", + "Legacy (v2) authentication" : "Autenticazione tradizionale (v2)", "WebDAV" : "WebDAV", "URL" : "URL", "Remote subfolder" : "Sottocartella remota", diff --git a/apps/files_external/l10n/it.json b/apps/files_external/l10n/it.json index ecb8d2082e6..36d4b296199 100644 --- a/apps/files_external/l10n/it.json +++ b/apps/files_external/l10n/it.json @@ -73,6 +73,7 @@ "Region" : "Regione", "Enable SSL" : "Abilita SSL", "Enable Path Style" : "Abilita stile percorsi", + "Legacy (v2) authentication" : "Autenticazione tradizionale (v2)", "WebDAV" : "WebDAV", "URL" : "URL", "Remote subfolder" : "Sottocartella remota", diff --git a/apps/files_external/l10n/tr.js b/apps/files_external/l10n/tr.js index 152963db282..1def1246fde 100644 --- a/apps/files_external/l10n/tr.js +++ b/apps/files_external/l10n/tr.js @@ -75,6 +75,7 @@ OC.L10N.register( "Region" : "Bölge", "Enable SSL" : "SSL Kullanılsın", "Enable Path Style" : "Yol Stili Kullanılsın", + "Legacy (v2) authentication" : "Eski (v2) kimlik doğrulama", "WebDAV" : "WebDAV", "URL" : "Adres", "Remote subfolder" : "Uzak alt klasör", diff --git a/apps/files_external/l10n/tr.json b/apps/files_external/l10n/tr.json index efa75513f54..ea9fbc84c5f 100644 --- a/apps/files_external/l10n/tr.json +++ b/apps/files_external/l10n/tr.json @@ -73,6 +73,7 @@ "Region" : "Bölge", "Enable SSL" : "SSL Kullanılsın", "Enable Path Style" : "Yol Stili Kullanılsın", + "Legacy (v2) authentication" : "Eski (v2) kimlik doğrulama", "WebDAV" : "WebDAV", "URL" : "Adres", "Remote subfolder" : "Uzak alt klasör", diff --git a/apps/theming/css/theming.scss b/apps/theming/css/theming.scss index 4474c232d94..63d466542e1 100644 --- a/apps/theming/css/theming.scss +++ b/apps/theming/css/theming.scss @@ -1,3 +1,12 @@ +/** Calculate luma as it is also used in OCA\Theming\Util::calculateLuma */ +@function luma($c) { + $-local-red: red(rgba($c, 1.0)); + $-local-green: green(rgba($c, 1.0)); + $-local-blue: blue(rgba($c, 1.0)); + + @return (0.2126 * $-local-red + 0.7152 * $-local-green + 0.0722 * $-local-blue) / 255; +} + .nc-theming-main-background { background-color: $color-primary; } @@ -10,7 +19,13 @@ color: $color-primary-text; } -@if (lightness($color-primary) > 55) { +@if (luma($color-primary) > 0.6) { + #appmenu:not(.inverted) svg { + filter: invert(1); + } + #appmenu.inverted svg { + filter: none; + } .searchbox input[type="search"] { background: transparent url('../../../core/img/actions/search.svg') no-repeat 6px center; } @@ -53,6 +68,13 @@ background-color: nc-darken($color-primary-element, 30%) !important; } } +} @else { + #appmenu:not(.inverted) svg { + filter: none; + } + #appmenu.inverted svg { + filter: invert(1); + } } /* Colorized svg images */ @@ -90,8 +112,8 @@ input.primary, color: $color-primary-text; } -@if (lightness($color-primary) > 50) { - #body-login #submit-icon.icon-confirm-white { +@if (luma($color-primary) > 0.6) { + #body-login #submit-wrapper .icon-confirm-white { background-image: url('../../../core/img/actions/confirm.svg'); } } diff --git a/apps/theming/lib/Controller/ThemingController.php b/apps/theming/lib/Controller/ThemingController.php index 5fd6edd8ab7..6592eb7f7ab 100644 --- a/apps/theming/lib/Controller/ThemingController.php +++ b/apps/theming/lib/Controller/ThemingController.php @@ -324,7 +324,7 @@ class ThemingController extends Controller { public function undo($setting) { $value = $this->themingDefaults->undo($setting); // reprocess server scss for preview - $cssCached = $this->scssCacher->process(\OC::$SERVERROOT, '/core/css/server.scss', 'core'); + $cssCached = $this->scssCacher->process(\OC::$SERVERROOT, 'core/css/server.scss', 'core'); if($setting === 'logoMime') { try { diff --git a/apps/theming/lib/Util.php b/apps/theming/lib/Util.php index 194b5eeb0d0..6a1aa672108 100644 --- a/apps/theming/lib/Util.php +++ b/apps/theming/lib/Util.php @@ -63,8 +63,8 @@ class Util { * @return bool */ public function invertTextColor($color) { - $l = $this->calculateLuminance($color); - if($l>0.55) { + $l = $this->calculateLuma($color); + if($l>0.6) { return true; } else { return false; @@ -90,6 +90,26 @@ class Util { * @return float */ public function calculateLuminance($color) { + list($red, $green, $blue) = $this->hexToRGB($color); + $compiler = new Compiler(); + $hsl = $compiler->toHSL($red, $green, $blue); + return $hsl[3]/100; + } + + /** + * @param string $color rgb color value + * @return float + */ + public function calculateLuma($color) { + list($red, $green, $blue) = $this->hexToRGB($color); + return (0.2126 * $red + 0.7152 * $green + 0.0722 * $blue) / 255; + } + + /** + * @param string $color rgb color value + * @return int[] + */ + public function hexToRGB($color) { $hex = preg_replace("/[^0-9A-Fa-f]/", '', $color); if (strlen($hex) === 3) { $hex = $hex{0} . $hex{0} . $hex{1} . $hex{1} . $hex{2} . $hex{2}; @@ -97,12 +117,11 @@ class Util { if (strlen($hex) !== 6) { return 0; } - $red = hexdec(substr($hex, 0, 2)); - $green = hexdec(substr($hex, 2, 2)); - $blue = hexdec(substr($hex, 4, 2)); - $compiler = new Compiler(); - $hsl = $compiler->toHSL($red, $green, $blue); - return $hsl[3]/100; + return [ + hexdec(substr($hex, 0, 2)), + hexdec(substr($hex, 2, 2)), + hexdec(substr($hex, 4, 2)) + ]; } /** diff --git a/apps/theming/tests/UtilTest.php b/apps/theming/tests/UtilTest.php index 94e4c9cbcc8..1ad77be72d5 100644 --- a/apps/theming/tests/UtilTest.php +++ b/apps/theming/tests/UtilTest.php @@ -53,14 +53,20 @@ class UtilTest extends TestCase { $this->util = new Util($this->config, $this->appManager, $this->appData); } - public function testInvertTextColorLight() { - $invert = $this->util->invertTextColor('#ffffff'); - $this->assertEquals(true, $invert); + public function dataInvertTextColor() { + return [ + ['#ffffff', true], + ['#000000', false], + ['#0082C9', false], + ['#ffff00', true], + ]; } - - public function testInvertTextColorDark() { - $invert = $this->util->invertTextColor('#000000'); - $this->assertEquals(false, $invert); + /** + * @dataProvider dataInvertTextColor + */ + public function testInvertTextColor($color, $expected) { + $invert = $this->util->invertTextColor($color); + $this->assertEquals($expected, $invert); } public function testCalculateLuminanceLight() { diff --git a/apps/user_ldap/composer/composer/autoload_classmap.php b/apps/user_ldap/composer/composer/autoload_classmap.php index 7bade37d9f7..98a1bbfa1b7 100644 --- a/apps/user_ldap/composer/composer/autoload_classmap.php +++ b/apps/user_ldap/composer/composer/autoload_classmap.php @@ -7,6 +7,7 @@ $baseDir = $vendorDir; return array( 'OCA\\User_LDAP\\Access' => $baseDir . '/../lib/Access.php', + 'OCA\\User_LDAP\\AccessFactory' => $baseDir . '/../lib/AccessFactory.php', 'OCA\\User_LDAP\\AppInfo\\Application' => $baseDir . '/../lib/AppInfo/Application.php', 'OCA\\User_LDAP\\BackendUtility' => $baseDir . '/../lib/BackendUtility.php', 'OCA\\User_LDAP\\Command\\CheckUser' => $baseDir . '/../lib/Command/CheckUser.php', @@ -19,6 +20,7 @@ return array( 'OCA\\User_LDAP\\Command\\TestConfig' => $baseDir . '/../lib/Command/TestConfig.php', 'OCA\\User_LDAP\\Configuration' => $baseDir . '/../lib/Configuration.php', 'OCA\\User_LDAP\\Connection' => $baseDir . '/../lib/Connection.php', + 'OCA\\User_LDAP\\ConnectionFactory' => $baseDir . '/../lib/ConnectionFactory.php', 'OCA\\User_LDAP\\Controller\\ConfigAPIController' => $baseDir . '/../lib/Controller/ConfigAPIController.php', 'OCA\\User_LDAP\\Controller\\RenewPasswordController' => $baseDir . '/../lib/Controller/RenewPasswordController.php', 'OCA\\User_LDAP\\Exceptions\\ConstraintViolationException' => $baseDir . '/../lib/Exceptions/ConstraintViolationException.php', diff --git a/apps/user_ldap/composer/composer/autoload_static.php b/apps/user_ldap/composer/composer/autoload_static.php index fbe63285273..6c97237d62d 100644 --- a/apps/user_ldap/composer/composer/autoload_static.php +++ b/apps/user_ldap/composer/composer/autoload_static.php @@ -19,6 +19,7 @@ class ComposerStaticInitUser_LDAP public static $classMap = array ( 'OCA\\User_LDAP\\Access' => __DIR__ . '/..' . '/../lib/Access.php', + 'OCA\\User_LDAP\\AccessFactory' => __DIR__ . '/..' . '/../lib/AccessFactory.php', 'OCA\\User_LDAP\\AppInfo\\Application' => __DIR__ . '/..' . '/../lib/AppInfo/Application.php', 'OCA\\User_LDAP\\BackendUtility' => __DIR__ . '/..' . '/../lib/BackendUtility.php', 'OCA\\User_LDAP\\Command\\CheckUser' => __DIR__ . '/..' . '/../lib/Command/CheckUser.php', @@ -31,6 +32,7 @@ class ComposerStaticInitUser_LDAP 'OCA\\User_LDAP\\Command\\TestConfig' => __DIR__ . '/..' . '/../lib/Command/TestConfig.php', 'OCA\\User_LDAP\\Configuration' => __DIR__ . '/..' . '/../lib/Configuration.php', 'OCA\\User_LDAP\\Connection' => __DIR__ . '/..' . '/../lib/Connection.php', + 'OCA\\User_LDAP\\ConnectionFactory' => __DIR__ . '/..' . '/../lib/ConnectionFactory.php', 'OCA\\User_LDAP\\Controller\\ConfigAPIController' => __DIR__ . '/..' . '/../lib/Controller/ConfigAPIController.php', 'OCA\\User_LDAP\\Controller\\RenewPasswordController' => __DIR__ . '/..' . '/../lib/Controller/RenewPasswordController.php', 'OCA\\User_LDAP\\Exceptions\\ConstraintViolationException' => __DIR__ . '/..' . '/../lib/Exceptions/ConstraintViolationException.php', diff --git a/apps/user_ldap/lib/AccessFactory.php b/apps/user_ldap/lib/AccessFactory.php new file mode 100644 index 00000000000..45ff779bb01 --- /dev/null +++ b/apps/user_ldap/lib/AccessFactory.php @@ -0,0 +1,61 @@ +<?php +/** + * @copyright Copyright (c) 2018 Arthur Schiwon <blizzz@arthur-schiwon.de> + * + * @author Arthur Schiwon <blizzz@arthur-schiwon.de> + * + * @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\User_LDAP; + + +use OCA\User_LDAP\User\Manager; +use OCP\IConfig; + +class AccessFactory { + /** @var ILDAPWrapper */ + protected $ldap; + /** @var Manager */ + protected $userManager; + /** @var Helper */ + protected $helper; + /** @var IConfig */ + protected $config; + + public function __construct( + ILDAPWrapper $ldap, + Manager $userManager, + Helper $helper, + IConfig $config) + { + $this->ldap = $ldap; + $this->userManager = $userManager; + $this->helper = $helper; + $this->config = $config; + } + + public function get(Connection $connection) { + return new Access( + $connection, + $this->ldap, + $this->userManager, + $this->helper, + $this->config + ); + } +} diff --git a/apps/user_ldap/lib/ConnectionFactory.php b/apps/user_ldap/lib/ConnectionFactory.php new file mode 100644 index 00000000000..0857afdcc8d --- /dev/null +++ b/apps/user_ldap/lib/ConnectionFactory.php @@ -0,0 +1,38 @@ +<?php +/** + * @copyright Copyright (c) 2018 Arthur Schiwon <blizzz@arthur-schiwon.de> + * + * @author Arthur Schiwon <blizzz@arthur-schiwon.de> + * + * @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\User_LDAP; + + +class ConnectionFactory { + /** @var ILDAPWrapper */ + private $ldap; + + public function __construct(ILDAPWrapper $ldap) { + $this->ldap = $ldap; + } + + public function get($prefix) { + return new Connection($this->ldap, $prefix, 'user_ldap'); + } +} diff --git a/apps/user_ldap/lib/Jobs/Sync.php b/apps/user_ldap/lib/Jobs/Sync.php index d8e0e854718..b78a1947e27 100644 --- a/apps/user_ldap/lib/Jobs/Sync.php +++ b/apps/user_ldap/lib/Jobs/Sync.php @@ -26,8 +26,10 @@ namespace OCA\User_LDAP\Jobs; use OC\BackgroundJob\TimedJob; use OC\ServerNotAvailableException; use OCA\User_LDAP\Access; +use OCA\User_LDAP\AccessFactory; use OCA\User_LDAP\Configuration; use OCA\User_LDAP\Connection; +use OCA\User_LDAP\ConnectionFactory; use OCA\User_LDAP\FilesystemHelper; use OCA\User_LDAP\Helper; use OCA\User_LDAP\LDAP; @@ -62,6 +64,10 @@ class Sync extends TimedJob { protected $ncUserManager; /** @var IManager */ protected $notificationManager; + /** @var ConnectionFactory */ + protected $connectionFactory; + /** @var AccessFactory */ + protected $accessFactory; public function __construct() { $this->setInterval( @@ -112,7 +118,7 @@ class Sync extends TimedJob { /** * @param array $argument */ - protected function run($argument) { + public function run($argument) { $this->setArgument($argument); $isBackgroundJobModeAjax = $this->config @@ -140,11 +146,11 @@ class Sync extends TimedJob { if ($expectMoreResults) { $this->increaseOffset($cycleData); } else { - $this->determineNextCycle(); + $this->determineNextCycle($cycleData); } $this->updateInterval(); } catch (ServerNotAvailableException $e) { - $this->determineNextCycle(); + $this->determineNextCycle($cycleData); } } @@ -153,8 +159,8 @@ class Sync extends TimedJob { * @return bool whether more results are expected from the same configuration */ public function runCycle($cycleData) { - $connection = new Connection($this->ldap, $cycleData['prefix']); - $access = new Access($connection, $this->ldap, $this->userManager, $this->ldapHelper, $this->config); + $connection = $this->connectionFactory->get($cycleData['prefix']); + $access = $this->accessFactory->get($connection); $access->setUserMapper($this->mapper); $filter = $access->combineFilterWithAnd(array( @@ -173,7 +179,7 @@ class Sync extends TimedJob { if($connection->ldapPagingSize === 0) { return true; } - return count($results) !== $connection->ldapPagingSize; + return count($results) >= $connection->ldapPagingSize; } /** @@ -246,7 +252,7 @@ class Sync extends TimedJob { * @param $cycleData * @return bool */ - protected function qualifiesToRun($cycleData) { + public function qualifiesToRun($cycleData) { $lastChange = $this->config->getAppValue('user_ldap', $cycleData['prefix'] . '_lastChange', 0); if((time() - $lastChange) > 60 * 30) { return true; @@ -358,5 +364,22 @@ class Sync extends TimedJob { } else { $this->mapper = new UserMapping($this->dbc); } + + if(isset($argument['connectionFactory'])) { + $this->connectionFactory = $argument['connectionFactory']; + } else { + $this->connectionFactory = new ConnectionFactory($this->ldap); + } + + if(isset($argument['accessFactory'])) { + $this->accessFactory = $argument['accessFactory']; + } else { + $this->accessFactory = new AccessFactory( + $this->ldap, + $this->userManager, + $this->ldapHelper, + $this->config + ); + } } } diff --git a/apps/user_ldap/tests/Jobs/SyncTest.php b/apps/user_ldap/tests/Jobs/SyncTest.php index f8a44de87e8..f8852a46664 100644 --- a/apps/user_ldap/tests/Jobs/SyncTest.php +++ b/apps/user_ldap/tests/Jobs/SyncTest.php @@ -23,6 +23,10 @@ namespace OCA\User_LDAP\Tests\Jobs; +use OCA\User_LDAP\Access; +use OCA\User_LDAP\AccessFactory; +use OCA\User_LDAP\Connection; +use OCA\User_LDAP\ConnectionFactory; use OCA\User_LDAP\Helper; use OCA\User_LDAP\Jobs\Sync; use OCA\User_LDAP\LDAP; @@ -33,6 +37,7 @@ use OCP\IConfig; use OCP\IDBConnection; use OCP\IUserManager; use OCP\Notification\IManager; +use function Sodium\memcmp; use Test\TestCase; class SyncTest extends TestCase { @@ -59,6 +64,10 @@ class SyncTest extends TestCase { protected $ncUserManager; /** @var IManager|\PHPUnit_Framework_MockObject_MockObject */ protected $notificationManager; + /** @var ConnectionFactory|\PHPUnit_Framework_MockObject_MockObject */ + protected $connectionFactory; + /** @var AccessFactory|\PHPUnit_Framework_MockObject_MockObject */ + protected $accessFactory; public function setUp() { parent::setUp(); @@ -72,6 +81,8 @@ class SyncTest extends TestCase { $this->dbc = $this->createMock(IDBConnection::class); $this->ncUserManager = $this->createMock(IUserManager::class); $this->notificationManager = $this->createMock(IManager::class); + $this->connectionFactory = $this->createMock(ConnectionFactory::class); + $this->accessFactory = $this->createMock(AccessFactory::class); $this->arguments = [ 'helper' => $this->helper, @@ -83,6 +94,8 @@ class SyncTest extends TestCase { 'dbc' => $this->dbc, 'ncUserManager' => $this->ncUserManager, 'notificationManager' => $this->notificationManager, + 'connectionFactory' => $this->connectionFactory, + 'accessFactory' => $this->accessFactory, ]; $this->sync = new Sync(); @@ -141,4 +154,216 @@ class SyncTest extends TestCase { $this->sync->updateInterval(); } + public function moreResultsProvider() { + return [ + [ 3, 3, true ], + [ 3, 5, true ], + [ 3, 2, false] + ]; + } + + /** + * @dataProvider moreResultsProvider + */ + public function testMoreResults($pagingSize, $results, $expected) { + $connection = $this->createMock(Connection::class); + $this->connectionFactory->expects($this->any()) + ->method('get') + ->willReturn($connection); + $connection->expects($this->any()) + ->method('__get') + ->willReturnCallback(function ($key) use ($pagingSize) { + if($key === 'ldapPagingSize') { + return $pagingSize; + } + return null; + }); + + /** @var Access|\PHPUnit_Framework_MockObject_MockObject $access */ + $access = $this->createMock(Access::class); + $this->accessFactory->expects($this->any()) + ->method('get') + ->with($connection) + ->willReturn($access); + + $access->expects($this->once()) + ->method('fetchListOfUsers') + ->willReturn(array_pad([], $results, 'someUser')); + $access->connection = $connection; + $access->userManager = $this->userManager; + + $this->sync->setArgument($this->arguments); + $hasMoreResults = $this->sync->runCycle(['prefix' => 's01', 'offset' => 100]); + $this->assertSame($expected, $hasMoreResults); + } + + public function cycleDataProvider() { + $lastCycle = ['prefix' => 's01', 'offset' => 1000]; + $lastCycle2 = ['prefix' => '', 'offset' => 1000]; + return [ + [ null, ['s01'], ['prefix' => 's01', 'offset' => 0] ], + [ null, [''], ['prefix' => '', 'offset' => 0] ], + [ $lastCycle, ['s01', 's02'], ['prefix' => 's02', 'offset' => 0] ], + [ $lastCycle, [''], ['prefix' => '', 'offset' => 0] ], + [ $lastCycle2, ['', 's01'], ['prefix' => 's01', 'offset' => 0] ], + [ $lastCycle, [], null ], + ]; + } + + /** + * @dataProvider cycleDataProvider + */ + public function testDetermineNextCycle($cycleData, $prefixes, $expectedCycle) { + $this->helper->expects($this->any()) + ->method('getServerConfigurationPrefixes') + ->with(true) + ->willReturn($prefixes); + + if(is_array($expectedCycle)) { + $this->config->expects($this->exactly(2)) + ->method('setAppValue') + ->withConsecutive( + ['user_ldap', 'background_sync_prefix', $expectedCycle['prefix']], + ['user_ldap', 'background_sync_offset', $expectedCycle['offset']] + ); + } else { + $this->config->expects($this->never()) + ->method('setAppValue'); + } + + $this->sync->setArgument($this->arguments); + $nextCycle = $this->sync->determineNextCycle($cycleData); + + if($expectedCycle === null) { + $this->assertNull($nextCycle); + } else { + $this->assertSame($expectedCycle['prefix'], $nextCycle['prefix']); + $this->assertSame($expectedCycle['offset'], $nextCycle['offset']); + } + } + + public function testQualifiesToRun() { + $cycleData = ['prefix' => 's01']; + + $this->config->expects($this->exactly(2)) + ->method('getAppValue') + ->willReturnOnConsecutiveCalls(time() - 60*40, time() - 60*20); + + $this->sync->setArgument($this->arguments); + $this->assertTrue($this->sync->qualifiesToRun($cycleData)); + $this->assertFalse($this->sync->qualifiesToRun($cycleData)); + } + + public function runDataProvider() { + return [ + #0 - one LDAP server, reset + [[ + 'prefixes' => [''], + 'scheduledCycle' => ['prefix' => '', 'offset' => '4500'], + 'pagingSize' => 500, + 'usersThisCycle' => 0, + 'expectedNextCycle' => ['prefix' => '', 'offset' => '0'], + 'mappedUsers' => 123, + ]], + #1 - 2 LDAP servers, next prefix + [[ + 'prefixes' => ['', 's01'], + 'scheduledCycle' => ['prefix' => '', 'offset' => '4500'], + 'pagingSize' => 500, + 'usersThisCycle' => 0, + 'expectedNextCycle' => ['prefix' => 's01', 'offset' => '0'], + 'mappedUsers' => 123, + ]], + #2 - 2 LDAP servers, rotate prefix + [[ + 'prefixes' => ['', 's01'], + 'scheduledCycle' => ['prefix' => 's01', 'offset' => '4500'], + 'pagingSize' => 500, + 'usersThisCycle' => 0, + 'expectedNextCycle' => ['prefix' => '', 'offset' => '0'], + 'mappedUsers' => 123, + ]], + ]; + } + + /** + * @dataProvider runDataProvider + */ + public function testRun($runData) { + $this->config->expects($this->any()) + ->method('getAppValue') + ->willReturnCallback(function($app, $key, $default) use ($runData) { + if($app === 'core' && $key === 'backgroundjobs_mode') { + return 'cron'; + } + if($app = 'user_ldap') { + // for getCycle() + if($key === 'background_sync_prefix') { + return $runData['scheduledCycle']['prefix']; + } + if($key === 'background_sync_offset') { + return $runData['scheduledCycle']['offset']; + } + // for qualifiesToRun() + if($key === $runData['scheduledCycle']['prefix'] . '_lastChange') { + return time() - 60*40; + } + // for getMinPagingSize + if($key === $runData['scheduledCycle']['prefix'] . 'ldap_paging_size') { + return $runData['pagingSize']; + } + } + + return $default; + }); + $this->config->expects($this->exactly(3)) + ->method('setAppValue') + ->withConsecutive( + ['user_ldap', 'background_sync_prefix', $runData['expectedNextCycle']['prefix']], + ['user_ldap', 'background_sync_offset', $runData['expectedNextCycle']['offset']], + ['user_ldap', 'background_sync_interval', $this->anything()] + ); + $this->config->expects($this->any()) + ->method('getAppKeys') + ->with('user_ldap') + ->willReturn([$runData['scheduledCycle']['prefix'] . 'ldap_paging_size']); + + $this->helper->expects($this->any()) + ->method('getServerConfigurationPrefixes') + ->with(true) + ->willReturn($runData['prefixes']); + + $connection = $this->createMock(Connection::class); + $this->connectionFactory->expects($this->any()) + ->method('get') + ->willReturn($connection); + $connection->expects($this->any()) + ->method('__get') + ->willReturnCallback(function ($key) use ($runData) { + if($key === 'ldapPagingSize') { + return $runData['pagingSize']; + } + return null; + }); + + /** @var Access|\PHPUnit_Framework_MockObject_MockObject $access */ + $access = $this->createMock(Access::class); + $this->accessFactory->expects($this->any()) + ->method('get') + ->with($connection) + ->willReturn($access); + + $access->expects($this->once()) + ->method('fetchListOfUsers') + ->willReturn(array_pad([], $runData['usersThisCycle'], 'someUser')); + $access->connection = $connection; + $access->userManager = $this->userManager; + + $this->mapper->expects($this->any()) + ->method('count') + ->willReturn($runData['mappedUsers']); + + $this->sync->run($this->arguments); + } + } diff --git a/build/files-checker.php b/build/files-checker.php index 66c44bd971b..20d8b4b5f33 100644 --- a/build/files-checker.php +++ b/build/files-checker.php @@ -49,6 +49,7 @@ $expectedFiles = [ 'build', 'buildjsdocs.sh', 'CHANGELOG.md', + 'CODE_OF_CONDUCT.md', 'composer.json', 'config', 'console.php', diff --git a/config/config.sample.php b/config/config.sample.php index 05efcaa2738..5f29933ec65 100644 --- a/config/config.sample.php +++ b/config/config.sample.php @@ -849,25 +849,16 @@ $CONFIG = array( * The maximum width, in pixels, of a preview. A value of ``null`` means there * is no limit. * - * Defaults to ``2048`` + * Defaults to ``4096`` */ -'preview_max_x' => 2048, +'preview_max_x' => 4096, /** * The maximum height, in pixels, of a preview. A value of ``null`` means there * is no limit. * - * Defaults to ``2048`` + * Defaults to ``4096`` */ -'preview_max_y' => 2048, -/** - * If a lot of small pictures are stored on the Nextcloud instance and the - * preview system generates blurry previews, you might want to consider setting - * a maximum scale factor. By default, pictures are upscaled to 10 times the - * original size. A value of ``1`` or ``null`` disables scaling. - * - * Defaults to ``2`` - */ -'preview_max_scale_factor' => 10, +'preview_max_y' => 4096, /** * max file size for generating image previews with imagegd (default behavior) diff --git a/core/css/apps.scss b/core/css/apps.scss index e6ead27e12b..41eea3bb524 100644 --- a/core/css/apps.scss +++ b/core/css/apps.scss @@ -235,6 +235,7 @@ kbd { &:first-child img { margin-right: 11px; width: 16px; + height: 16px; margin-left: -30px; } diff --git a/core/css/fixes.scss b/core/css/fixes.scss index 0303b4d751a..09ab9c1d244 100644 --- a/core/css/fixes.scss +++ b/core/css/fixes.scss @@ -20,6 +20,7 @@ select { .ie .header-left #navigation, .ie .ui-datepicker, .ie .ui-timepicker.ui-widget, -.ie #appmenu li span { +.ie #appmenu li span, +.ie .tooltip-inner { box-shadow: 0 1px 10px $color-box-shadow; } diff --git a/core/css/styles.scss b/core/css/styles.scss index b782c4e558a..4b02041976b 100644 --- a/core/css/styles.scss +++ b/core/css/styles.scss @@ -234,7 +234,7 @@ body { padding: 0; margin: 0; background-color: rgba($color-main-background, 0.95); - z-index: 55; + z-index: 60; -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; diff --git a/core/css/tooltip.scss b/core/css/tooltip.scss index e9982b580ca..a974e05e1a6 100644 --- a/core/css/tooltip.scss +++ b/core/css/tooltip.scss @@ -31,6 +31,7 @@ font-size: 12px; opacity: 0; z-index: 100000; + filter: drop-shadow(0 1px 10px $color-box-shadow); &.in { opacity: 1; } @@ -115,7 +116,6 @@ padding: 5px 8px; background-color: $color-main-background; color: $color-main-text; - box-shadow: 0 1px 10px $color-box-shadow; text-align: center; border-radius: $border-radius; } diff --git a/core/js/jquery.avatar.js b/core/js/jquery.avatar.js index 958f0f9edd7..6da86341c1e 100644 --- a/core/js/jquery.avatar.js +++ b/core/js/jquery.avatar.js @@ -110,9 +110,8 @@ // If the new image loads successfully set it. img.onload = function() { - $div.text(''); - $div.append(img); $div.clearimageplaceholder(); + $div.append(img); if(typeof callback === 'function') { callback(); @@ -127,7 +126,6 @@ $div.imageplaceholder(user, displayname); } else { setAvatarForUnknownUser($div); - $div.removeClass('icon-loading'); } if(typeof callback === 'function') { @@ -136,7 +134,6 @@ }; $div.addClass('icon-loading'); - $div.show(); img.width = size; img.height = size; img.src = url; diff --git a/core/js/placeholder.js b/core/js/placeholder.js index 5cf7b9095ad..29f91b6698d 100644 --- a/core/js/placeholder.js +++ b/core/js/placeholder.js @@ -156,6 +156,7 @@ this.css('text-align', ''); this.css('line-height', ''); this.css('font-size', ''); + this.html(''); this.removeClass('icon-loading'); }; }(jQuery)); diff --git a/core/js/public/comments.js b/core/js/public/comments.js index 6de7ff7d38a..955e88c8609 100644 --- a/core/js/public/comments.js +++ b/core/js/public/comments.js @@ -43,7 +43,7 @@ } var linkText = url.replace(self.protocolRegex, ''); - return '<a class="external" href="' + url + '">' + linkText + '</a>'; + return '<a class="external" target="_blank" href="' + url + '">' + linkText + '</a>'; }); }, diff --git a/core/js/tests/specs/jquery.avatarSpec.js b/core/js/tests/specs/jquery.avatarSpec.js index bdd1fdcc163..4e13b7f26ff 100644 --- a/core/js/tests/specs/jquery.avatarSpec.js +++ b/core/js/tests/specs/jquery.avatarSpec.js @@ -202,8 +202,6 @@ describe('jquery.avatar tests', function() { expect(window.Image().height).toEqual(32); expect(window.Image().width).toEqual(32); expect(window.Image().src).toEqual('http://localhost/index.php/avatar/foo/32'); - - expect($div.css('display')).toEqual('block'); }); it('callback called', function() { diff --git a/core/templates/layout.user.php b/core/templates/layout.user.php index 127e794e120..32762e2c240 100644 --- a/core/templates/layout.user.php +++ b/core/templates/layout.user.php @@ -41,19 +41,17 @@ </div> </a> - <ul id="appmenu"> + <ul id="appmenu" <?php if ($_['themingInvertMenu']) { ?>class="inverted"<?php } ?>> <?php foreach ($_['navigation'] as $entry): ?> <li data-id="<?php p($entry['id']); ?>" class="hidden"> <a href="<?php print_unescaped($entry['href']); ?>" <?php if ($entry['active']): ?> class="active"<?php endif; ?>> - <?php if ($_['themingInvertMenu']) { ?> <svg width="20" height="20" viewBox="0 0 20 20"> - <defs><filter id="invertMenuMain-<?php p($entry['id']); ?>"><feColorMatrix in="SourceGraphic" type="matrix" values="-1 0 0 0 1 0 -1 0 0 1 0 0 -1 0 1 0 0 0 1 0" /></filter></defs> - <image x="0" y="0" width="20" height="20" preserveAspectRatio="xMinYMin meet" filter="url(#invertMenuMain-<?php p($entry['id']); ?>)" xlink:href="<?php print_unescaped($entry['icon'] . '?v=' . $_['versionHash']); ?>" class="app-icon" /></svg> - <?php } else { ?> - <img src="<?php print_unescaped($entry['icon'] . '?v=' . $_['versionHash']); ?>" - class="app-icon" alt="<?php p($entry['name']); ?>" /> - <?php } ?> + <?php if ($_['themingInvertMenu']) { ?> + <defs><filter id="invertMenuMain-<?php p($entry['id']); ?>"><feColorMatrix in="SourceGraphic" type="matrix" values="-1 0 0 0 1 0 -1 0 0 1 0 0 -1 0 1 0 0 0 1 0" /></filter></defs> + <?php } ?> + <image x="0" y="0" width="20" height="20" preserveAspectRatio="xMinYMin meet"<?php if ($_['themingInvertMenu']) { ?> filter="url(#invertMenuMain-<?php p($entry['id']); ?>)"<?php } ?> xlink:href="<?php print_unescaped($entry['icon'] . '?v=' . $_['versionHash']); ?>" class="app-icon" /> + </svg> <div class="icon-loading-small-dark" style="display:none;"></div> </a> diff --git a/lib/private/Preview/Generator.php b/lib/private/Preview/Generator.php index 402732ecda9..448a7a57580 100644 --- a/lib/private/Preview/Generator.php +++ b/lib/private/Preview/Generator.php @@ -128,9 +128,13 @@ class Generator { // Try to get a cached preview. Else generate (and store) one try { - $file = $this->getCachedPreview($previewFolder, $width, $height, $crop); - } catch (NotFoundException $e) { - $file = $this->generatePreview($previewFolder, $maxPreview, $width, $height, $crop, $maxWidth, $maxHeight); + try { + $file = $this->getCachedPreview($previewFolder, $width, $height, $crop, $maxPreview->getMimeType()); + } catch (NotFoundException $e) { + $file = $this->generatePreview($previewFolder, $maxPreview, $width, $height, $crop, $maxWidth, $maxHeight); + } + } catch (\InvalidArgumentException $e) { + throw new NotFoundException(); } return $file; @@ -164,8 +168,8 @@ class Generator { continue; } - $maxWidth = (int)$this->config->getSystemValue('preview_max_x', 2048); - $maxHeight = (int)$this->config->getSystemValue('preview_max_y', 2048); + $maxWidth = (int)$this->config->getSystemValue('preview_max_x', 4096); + $maxHeight = (int)$this->config->getSystemValue('preview_max_y', 4096); $preview = $this->helper->getThumbnail($provider, $file, $maxWidth, $maxHeight); @@ -173,7 +177,15 @@ class Generator { continue; } - $path = (string)$preview->width() . '-' . (string)$preview->height() . '-max.png'; + // Try to get the extention. + try { + $ext = $this->getExtention($preview->dataMimeType()); + } catch (\InvalidArgumentException $e) { + // Just continue to the next iteration if this preview doesn't have a valid mimetype + continue; + } + + $path = (string)$preview->width() . '-' . (string)$preview->height() . '-max.' . $ext; try { $file = $previewFolder->newFile($path); $file->putContent($preview->data()); @@ -201,14 +213,17 @@ class Generator { * @param int $width * @param int $height * @param bool $crop + * @param string $mimeType * @return string */ - private function generatePath($width, $height, $crop) { + private function generatePath($width, $height, $crop, $mimeType) { $path = (string)$width . '-' . (string)$height; if ($crop) { $path .= '-crop'; } - $path .= '.png'; + + $ext = $this->getExtention($mimeType); + $path .= '.' . $ext; return $path; } @@ -340,7 +355,7 @@ class Generator { } - $path = $this->generatePath($width, $height, $crop); + $path = $this->generatePath($width, $height, $crop, $preview->dataMimeType()); try { $file = $previewFolder->newFile($path); $file->putContent($preview->data()); @@ -356,12 +371,13 @@ class Generator { * @param int $width * @param int $height * @param bool $crop + * @param string $mimeType * @return ISimpleFile * * @throws NotFoundException */ - private function getCachedPreview(ISimpleFolder $previewFolder, $width, $height, $crop) { - $path = $this->generatePath($width, $height, $crop); + private function getCachedPreview(ISimpleFolder $previewFolder, $width, $height, $crop, $mimeType) { + $path = $this->generatePath($width, $height, $crop, $mimeType); return $previewFolder->getFile($path); } @@ -381,4 +397,22 @@ class Generator { return $folder; } + + /** + * @param string $mimeType + * @return null|string + * @throws \InvalidArgumentException + */ + private function getExtention($mimeType) { + switch ($mimeType) { + case 'image/png': + return 'png'; + case 'image/jpeg': + return 'jpg'; + case 'image/gif': + return 'gif'; + default: + throw new \InvalidArgumentException('Not a valid mimetype'); + } + } } diff --git a/lib/private/Settings/Manager.php b/lib/private/Settings/Manager.php index cd0af5e7bb2..efeedbe6fcc 100644 --- a/lib/private/Settings/Manager.php +++ b/lib/private/Settings/Manager.php @@ -47,6 +47,7 @@ use OCP\Lock\ILockingProvider; use OCP\Settings\ISettings; use OCP\Settings\IManager; use OCP\Settings\ISection; +use OCP\Util; class Manager implements IManager { /** @var ILogger */ @@ -344,7 +345,7 @@ class Manager implements IManager { try { return \OC::$server->query($className); } catch (QueryException $e) { - $this->log->logException($e); + $this->log->logException($e, ['level' => Util::INFO]); throw $e; } } diff --git a/lib/private/Share20/DefaultShareProvider.php b/lib/private/Share20/DefaultShareProvider.php index 844b36b2994..b9ab7a46873 100644 --- a/lib/private/Share20/DefaultShareProvider.php +++ b/lib/private/Share20/DefaultShareProvider.php @@ -452,9 +452,9 @@ class DefaultShareProvider implements IShareProvider { 'uid_owner' => $qb->createNamedParameter($share->getShareOwner()), 'uid_initiator' => $qb->createNamedParameter($share->getSharedBy()), 'parent' => $qb->createNamedParameter($share->getId()), - 'item_type' => $qb->createNamedParameter($share->getNode() instanceof File ? 'file' : 'folder'), - 'item_source' => $qb->createNamedParameter($share->getNode()->getId()), - 'file_source' => $qb->createNamedParameter($share->getNode()->getId()), + 'item_type' => $qb->createNamedParameter($share->getNodeType()), + 'item_source' => $qb->createNamedParameter($share->getNodeId()), + 'file_source' => $qb->createNamedParameter($share->getNodeId()), 'file_target' => $qb->createNamedParameter($share->getTarget()), 'permissions' => $qb->createNamedParameter($share->getPermissions()), 'stime' => $qb->createNamedParameter($share->getShareTime()->getTimestamp()), diff --git a/lib/private/legacy/image.php b/lib/private/legacy/image.php index a7d702ac032..a0159b927f9 100644 --- a/lib/private/legacy/image.php +++ b/lib/private/legacy/image.php @@ -305,6 +305,25 @@ class OC_Image implements \OCP\IImage { } /** + * @return string Returns the mimetype of the data. Returns the empty string + * if the data is not valid. + */ + public function dataMimeType() { + if (!$this->valid()) { + return ''; + } + + switch ($this->mimeType) { + case 'image/png': + case 'image/jpeg': + case 'image/gif': + return $this->mimeType; + default: + return 'image/png'; + } + } + + /** * @return null|string Returns the raw image data. */ public function data() { diff --git a/lib/public/IImage.php b/lib/public/IImage.php index f63a1b8ca60..70e8b3cff75 100644 --- a/lib/public/IImage.php +++ b/lib/public/IImage.php @@ -103,6 +103,12 @@ interface IImage { public function resource(); /** + * @return string Returns the raw data mimetype + * @since 13.0.0 + */ + public function dataMimeType(); + + /** * @return string Returns the raw image data. * @since 8.1.0 */ diff --git a/ocs/v1.php b/ocs/v1.php index 43a1c2d9e0e..b6ed5697a69 100644 --- a/ocs/v1.php +++ b/ocs/v1.php @@ -81,7 +81,7 @@ try { $format = \OC::$server->getRequest()->getParam('format', 'xml'); $txt='Invalid query, please check the syntax. API specifications are here:' - .' http://www.freedesktop.org/wiki/Specifications/open-collaboration-services. DEBUG OUTPUT:'."\n"; + .' http://www.freedesktop.org/wiki/Specifications/open-collaboration-services.'."\n"; OC_API::respond(new \OC\OCS\Result(null, \OCP\API::RESPOND_NOT_FOUND, $txt), $format); } catch (MethodNotAllowedException $e) { OC_API::setContentType(); @@ -96,7 +96,7 @@ try { $format = \OC::$server->getRequest()->getParam('format', 'xml'); $txt='Invalid query, please check the syntax. API specifications are here:' - .' http://www.freedesktop.org/wiki/Specifications/open-collaboration-services. DEBUG OUTPUT:'."\n"; + .' http://www.freedesktop.org/wiki/Specifications/open-collaboration-services.'."\n"; OC_API::respond(new \OC\OCS\Result(null, \OCP\API::RESPOND_NOT_FOUND, $txt), $format); } diff --git a/settings/l10n/es.js b/settings/l10n/es.js index bef3b13fcf5..041b63f6fc7 100644 --- a/settings/l10n/es.js +++ b/settings/l10n/es.js @@ -104,9 +104,15 @@ OC.L10N.register( "Error: This app can not be enabled because it makes the server unstable" : "Error: Esta app no se puede activar porque desestabiliza el servidor", "Error: Could not disable broken app" : "Error: No se ha podido desactivar una app estropeada", "Error while disabling broken app" : "Error mientras deshabilitaba la App dañada", + "App up to date" : "App actualizada", + "Upgrading …" : "Actualizando...", + "Could not upgrade app" : "No se ha podido actualizar la app", "Updated" : "Actualizado", "Removing …" : "Eliminando...", + "Could not remove app" : "No se ha podido eliminar la app", "Remove" : "Eliminar", + "The app has been enabled but needs to be upgraded. You will be redirected to the upgrade page in 5 seconds." : "La app ha sido activada pero tiene que actualizarse. Serás redirigido a la página de actualización en 5 segundos.", + "App upgrade" : "Actualización de la app", "Approved" : "Aprobado", "Experimental" : "Experimental", "No apps found for {query}" : "No se han encontrado apps para {query}", diff --git a/settings/l10n/es.json b/settings/l10n/es.json index be7a4ecad19..9c8007c4848 100644 --- a/settings/l10n/es.json +++ b/settings/l10n/es.json @@ -102,9 +102,15 @@ "Error: This app can not be enabled because it makes the server unstable" : "Error: Esta app no se puede activar porque desestabiliza el servidor", "Error: Could not disable broken app" : "Error: No se ha podido desactivar una app estropeada", "Error while disabling broken app" : "Error mientras deshabilitaba la App dañada", + "App up to date" : "App actualizada", + "Upgrading …" : "Actualizando...", + "Could not upgrade app" : "No se ha podido actualizar la app", "Updated" : "Actualizado", "Removing …" : "Eliminando...", + "Could not remove app" : "No se ha podido eliminar la app", "Remove" : "Eliminar", + "The app has been enabled but needs to be upgraded. You will be redirected to the upgrade page in 5 seconds." : "La app ha sido activada pero tiene que actualizarse. Serás redirigido a la página de actualización en 5 segundos.", + "App upgrade" : "Actualización de la app", "Approved" : "Aprobado", "Experimental" : "Experimental", "No apps found for {query}" : "No se han encontrado apps para {query}", diff --git a/settings/l10n/nb.js b/settings/l10n/nb.js index 793ee8ff43b..e889a966092 100644 --- a/settings/l10n/nb.js +++ b/settings/l10n/nb.js @@ -104,6 +104,9 @@ OC.L10N.register( "Error: This app can not be enabled because it makes the server unstable" : "Feil: Dette programmet kan ikke aktiveres fordi det gjør tjeneren ustabil", "Error: Could not disable broken app" : "Feil: Kunne ikke deaktivere ustabilt program", "Error while disabling broken app" : "Feil ved deaktivering av ustabilt program", + "App up to date" : "Appen er oppdatert", + "Upgrading …" : "Oppgraderer…", + "Could not upgrade app" : "Kunne ikke oppgradere appen", "Updated" : "Oppdatert", "Removing …" : "Fjerner…", "Remove" : "Fjern", diff --git a/settings/l10n/nb.json b/settings/l10n/nb.json index c7f926f4acb..b95a39680b8 100644 --- a/settings/l10n/nb.json +++ b/settings/l10n/nb.json @@ -102,6 +102,9 @@ "Error: This app can not be enabled because it makes the server unstable" : "Feil: Dette programmet kan ikke aktiveres fordi det gjør tjeneren ustabil", "Error: Could not disable broken app" : "Feil: Kunne ikke deaktivere ustabilt program", "Error while disabling broken app" : "Feil ved deaktivering av ustabilt program", + "App up to date" : "Appen er oppdatert", + "Upgrading …" : "Oppgraderer…", + "Could not upgrade app" : "Kunne ikke oppgradere appen", "Updated" : "Oppdatert", "Removing …" : "Fjerner…", "Remove" : "Fjern", diff --git a/settings/l10n/ru.js b/settings/l10n/ru.js index 8c11d9ea667..b5d98373e91 100644 --- a/settings/l10n/ru.js +++ b/settings/l10n/ru.js @@ -104,9 +104,15 @@ OC.L10N.register( "Error: This app can not be enabled because it makes the server unstable" : "Ошибка: это приложение не может быть включено, так как оно сделает сервер нестабильным", "Error: Could not disable broken app" : "Ошибка: невозможно отключить «сломанное» приложение", "Error while disabling broken app" : "Ошибка при отключении сломанного приложения", + "App up to date" : "Приложение не нуждается в обновлении", + "Upgrading …" : "Обновление...", + "Could not upgrade app" : "Не удалось обновить приложение", "Updated" : "Обновлено", "Removing …" : "Удаление…", + "Could not remove app" : "Не удалось удалить приложение.", "Remove" : "Удалить", + "The app has been enabled but needs to be upgraded. You will be redirected to the upgrade page in 5 seconds." : "Приложение было включено, но нуждается в обновлении. В течении 5 секунд будет выполнено перенаправление на страницу обновления.", + "App upgrade" : "Обновление приложения", "Approved" : "Подтвержденное", "Experimental" : "Экспериментальное", "No apps found for {query}" : "Приложения не найдены по {query}", diff --git a/settings/l10n/ru.json b/settings/l10n/ru.json index bcacfeef65d..387973ffc82 100644 --- a/settings/l10n/ru.json +++ b/settings/l10n/ru.json @@ -102,9 +102,15 @@ "Error: This app can not be enabled because it makes the server unstable" : "Ошибка: это приложение не может быть включено, так как оно сделает сервер нестабильным", "Error: Could not disable broken app" : "Ошибка: невозможно отключить «сломанное» приложение", "Error while disabling broken app" : "Ошибка при отключении сломанного приложения", + "App up to date" : "Приложение не нуждается в обновлении", + "Upgrading …" : "Обновление...", + "Could not upgrade app" : "Не удалось обновить приложение", "Updated" : "Обновлено", "Removing …" : "Удаление…", + "Could not remove app" : "Не удалось удалить приложение.", "Remove" : "Удалить", + "The app has been enabled but needs to be upgraded. You will be redirected to the upgrade page in 5 seconds." : "Приложение было включено, но нуждается в обновлении. В течении 5 секунд будет выполнено перенаправление на страницу обновления.", + "App upgrade" : "Обновление приложения", "Approved" : "Подтвержденное", "Experimental" : "Экспериментальное", "No apps found for {query}" : "Приложения не найдены по {query}", diff --git a/settings/l10n/tr.js b/settings/l10n/tr.js index 58e6952bbee..08b81502141 100644 --- a/settings/l10n/tr.js +++ b/settings/l10n/tr.js @@ -104,9 +104,15 @@ OC.L10N.register( "Error: This app can not be enabled because it makes the server unstable" : "Hata: Bu uygulama sunucuda kararsızlığa yol açtığından etkinleştirilemez", "Error: Could not disable broken app" : "Hata: Bozuk uygulama devre dışı bırakılamadı", "Error while disabling broken app" : "Bozuk uygulama devre dışı bırakılırken sorun çıktı", + "App up to date" : "Uygulama güncel", + "Upgrading …" : "Güncelleniyor …", + "Could not upgrade app" : "Uygulama güncellenemedi", "Updated" : "Güncellendi", "Removing …" : "Kaldırılıyor...", + "Could not remove app" : "Uygulama kaldırılamadı", "Remove" : "Kaldır", + "The app has been enabled but needs to be upgraded. You will be redirected to the upgrade page in 5 seconds." : "Uygulama etkinleştirilmiş fakat güncellenmesi gerekiyor. 5 saniye içinde güncelleme sayfasına yönlendirileceksiniz.", + "App upgrade" : "Uygulama güncellemesi", "Approved" : "Onaylanmış", "Experimental" : "Deneysel", "No apps found for {query}" : "{query} aramasına uyan bir uygulama bulunamadı", diff --git a/settings/l10n/tr.json b/settings/l10n/tr.json index 0485390111f..a04b466a87b 100644 --- a/settings/l10n/tr.json +++ b/settings/l10n/tr.json @@ -102,9 +102,15 @@ "Error: This app can not be enabled because it makes the server unstable" : "Hata: Bu uygulama sunucuda kararsızlığa yol açtığından etkinleştirilemez", "Error: Could not disable broken app" : "Hata: Bozuk uygulama devre dışı bırakılamadı", "Error while disabling broken app" : "Bozuk uygulama devre dışı bırakılırken sorun çıktı", + "App up to date" : "Uygulama güncel", + "Upgrading …" : "Güncelleniyor …", + "Could not upgrade app" : "Uygulama güncellenemedi", "Updated" : "Güncellendi", "Removing …" : "Kaldırılıyor...", + "Could not remove app" : "Uygulama kaldırılamadı", "Remove" : "Kaldır", + "The app has been enabled but needs to be upgraded. You will be redirected to the upgrade page in 5 seconds." : "Uygulama etkinleştirilmiş fakat güncellenmesi gerekiyor. 5 saniye içinde güncelleme sayfasına yönlendirileceksiniz.", + "App upgrade" : "Uygulama güncellemesi", "Approved" : "Onaylanmış", "Experimental" : "Deneysel", "No apps found for {query}" : "{query} aramasına uyan bir uygulama bulunamadı", diff --git a/tests/lib/Preview/GeneratorTest.php b/tests/lib/Preview/GeneratorTest.php index f1383b0691b..130cccdf09e 100644 --- a/tests/lib/Preview/GeneratorTest.php +++ b/tests/lib/Preview/GeneratorTest.php @@ -93,6 +93,8 @@ class GeneratorTest extends \Test\TestCase { $maxPreview = $this->createMock(ISimpleFile::class); $maxPreview->method('getName') ->willReturn('1000-1000-max.png'); + $maxPreview->method('getMimeType') + ->willReturn('image/png'); $previewFolder->method('getDirectoryListing') ->willReturn([$maxPreview]); @@ -170,6 +172,7 @@ class GeneratorTest extends \Test\TestCase { $image->method('width')->willReturn(2048); $image->method('height')->willReturn(2048); $image->method('valid')->willReturn(true); + $image->method('dataMimeType')->willReturn('image/png'); $this->helper->method('getThumbnail') ->will($this->returnCallback(function ($provider, $file, $x, $y) use ($invalidProvider, $validProvider, $image) { @@ -185,6 +188,7 @@ class GeneratorTest extends \Test\TestCase { $maxPreview = $this->createMock(ISimpleFile::class); $maxPreview->method('getName')->willReturn('2048-2048-max.png'); + $maxPreview->method('getMimeType')->willReturn('image/png'); $previewFile = $this->createMock(ISimpleFile::class); @@ -219,6 +223,7 @@ class GeneratorTest extends \Test\TestCase { $image->method('data') ->willReturn('my resized data'); $image->method('valid')->willReturn(true); + $image->method('dataMimeType')->willReturn('image/png'); $previewFile->expects($this->once()) ->method('putContent') @@ -362,6 +367,8 @@ class GeneratorTest extends \Test\TestCase { $maxPreview = $this->createMock(ISimpleFile::class); $maxPreview->method('getName') ->willReturn($maxX . '-' . $maxY . '-max.png'); + $maxPreview->method('getMimeType') + ->willReturn('image/png'); $previewFolder->method('getDirectoryListing') ->willReturn([$maxPreview]); @@ -382,6 +389,7 @@ class GeneratorTest extends \Test\TestCase { $image->method('height')->willReturn($maxY); $image->method('width')->willReturn($maxX); $image->method('valid')->willReturn(true); + $image->method('dataMimeType')->willReturn('image/png'); $preview = $this->createMock(ISimpleFile::class); $previewFolder->method('newFile') |