From 8190441335aaa01baed01a3984c0a153126a5d0f Mon Sep 17 00:00:00 2001 From: Tiramisu Mokka Date: Mon, 7 Mar 2022 10:19:58 +0100 Subject: files: close open directory file descriptor on error path --- lib/private/Files/Storage/Common.php | 1 + 1 file changed, 1 insertion(+) (limited to 'lib/private/Files') diff --git a/lib/private/Files/Storage/Common.php b/lib/private/Files/Storage/Common.php index 7239c58a8a1..cfb2d621e1e 100644 --- a/lib/private/Files/Storage/Common.php +++ b/lib/private/Files/Storage/Common.php @@ -225,6 +225,7 @@ abstract class Common implements Storage, ILockingStorage, IWriteStreamStorage { while ($file = readdir($dir)) { if (!Filesystem::isIgnoredDir($file)) { if (!$this->copy($path1 . '/' . $file, $path2 . '/' . $file)) { + closedir($dir); return false; } } -- cgit v1.2.3 From f755ee08689a9400e1e9b2bc15ae116ae7483d5c Mon Sep 17 00:00:00 2001 From: Marcel Klehr Date: Wed, 28 Apr 2021 19:07:15 +0200 Subject: Files: Extend search to also cover tags fixes #326 Signed-off-by: Marcel Klehr --- .../composer/composer/autoload_classmap.php | 1 + .../composer/composer/autoload_static.php | 1 + apps/systemtags/lib/AppInfo/Application.php | 2 + apps/systemtags/lib/Search/TagSearchProvider.php | 217 +++++++++++++++++++++ apps/systemtags/src/app.js | 4 + apps/systemtags/src/systemtagsfilelist.js | 3 +- dist/systemtags-systemtags.js | 4 +- dist/systemtags-systemtags.js.map | 2 +- lib/private/Files/Cache/CacheQueryBuilder.php | 2 +- lib/private/Files/Cache/QuerySearchHelper.php | 19 +- lib/private/Files/Cache/SearchBuilder.php | 8 +- tests/lib/Files/Cache/SearchBuilderTest.php | 2 +- 12 files changed, 253 insertions(+), 12 deletions(-) create mode 100644 apps/systemtags/lib/Search/TagSearchProvider.php (limited to 'lib/private/Files') diff --git a/apps/systemtags/composer/composer/autoload_classmap.php b/apps/systemtags/composer/composer/autoload_classmap.php index c2fb4daa824..604b7df1672 100644 --- a/apps/systemtags/composer/composer/autoload_classmap.php +++ b/apps/systemtags/composer/composer/autoload_classmap.php @@ -12,5 +12,6 @@ return array( 'OCA\\SystemTags\\Activity\\Setting' => $baseDir . '/../lib/Activity/Setting.php', 'OCA\\SystemTags\\AppInfo\\Application' => $baseDir . '/../lib/AppInfo/Application.php', 'OCA\\SystemTags\\Controller\\LastUsedController' => $baseDir . '/../lib/Controller/LastUsedController.php', + 'OCA\\SystemTags\\Search\\TagSearchProvider' => $baseDir . '/../lib/Search/TagSearchProvider.php', 'OCA\\SystemTags\\Settings\\Admin' => $baseDir . '/../lib/Settings/Admin.php', ); diff --git a/apps/systemtags/composer/composer/autoload_static.php b/apps/systemtags/composer/composer/autoload_static.php index b679d3bf430..9c77f6d7a43 100644 --- a/apps/systemtags/composer/composer/autoload_static.php +++ b/apps/systemtags/composer/composer/autoload_static.php @@ -27,6 +27,7 @@ class ComposerStaticInitSystemTags 'OCA\\SystemTags\\Activity\\Setting' => __DIR__ . '/..' . '/../lib/Activity/Setting.php', 'OCA\\SystemTags\\AppInfo\\Application' => __DIR__ . '/..' . '/../lib/AppInfo/Application.php', 'OCA\\SystemTags\\Controller\\LastUsedController' => __DIR__ . '/..' . '/../lib/Controller/LastUsedController.php', + 'OCA\\SystemTags\\Search\\TagSearchProvider' => __DIR__ . '/..' . '/../lib/Search/TagSearchProvider.php', 'OCA\\SystemTags\\Settings\\Admin' => __DIR__ . '/..' . '/../lib/Settings/Admin.php', ); diff --git a/apps/systemtags/lib/AppInfo/Application.php b/apps/systemtags/lib/AppInfo/Application.php index cdc059d4a42..fc318aa2f1e 100644 --- a/apps/systemtags/lib/AppInfo/Application.php +++ b/apps/systemtags/lib/AppInfo/Application.php @@ -25,6 +25,7 @@ declare(strict_types=1); */ namespace OCA\SystemTags\AppInfo; +use OCA\SystemTags\Search\TagSearchProvider; use OCA\SystemTags\Activity\Listener; use OCP\AppFramework\App; use OCP\AppFramework\Bootstrap\IBootContext; @@ -42,6 +43,7 @@ class Application extends App implements IBootstrap { } public function register(IRegistrationContext $context): void { + $context->registerSearchProvider(TagSearchProvider::class); } public function boot(IBootContext $context): void { diff --git a/apps/systemtags/lib/Search/TagSearchProvider.php b/apps/systemtags/lib/Search/TagSearchProvider.php new file mode 100644 index 00000000000..7a7cb0b061c --- /dev/null +++ b/apps/systemtags/lib/Search/TagSearchProvider.php @@ -0,0 +1,217 @@ + + * + * @author Christoph Wurst + * @author Joas Schilling + * @author John Molakvoæ + * @author Robin Appelman + * @author Roeland Jago Douma + * @author Marcel Klehr + * + * @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 . + * + */ +namespace OCA\SystemTags\Search; + +use OC\Files\Search\SearchBinaryOperator; +use OC\Files\Search\SearchComparison; +use OC\Files\Search\SearchOrder; +use OC\Files\Search\SearchQuery; +use OCP\SystemTag\ISystemTag; +use OCP\SystemTag\ISystemTagManager; +use OCP\SystemTag\ISystemTagObjectMapper; +use OCP\Files\FileInfo; +use OCP\Files\IMimeTypeDetector; +use OCP\Files\IRootFolder; +use OCP\Files\Search\ISearchComparison; +use OCP\Files\Node; +use OCP\Files\Search\ISearchOrder; +use OCP\IL10N; +use OCP\IURLGenerator; +use OCP\IUser; +use OCP\Search\IProvider; +use OCP\Search\ISearchQuery; +use OCP\Search\SearchResult; +use OCP\Search\SearchResultEntry; +use RecursiveArrayIterator; +use RecursiveIteratorIterator; + +class TagSearchProvider implements IProvider { + + /** @var IL10N */ + private $l10n; + + /** @var IURLGenerator */ + private $urlGenerator; + + /** @var IMimeTypeDetector */ + private $mimeTypeDetector; + + /** @var IRootFolder */ + private $rootFolder; + private ISystemTagObjectMapper $objectMapper; + private ISystemTagManager $tagManager; + + public function __construct( + IL10N $l10n, + IURLGenerator $urlGenerator, + IMimeTypeDetector $mimeTypeDetector, + IRootFolder $rootFolder, + ISystemTagObjectMapper $objectMapper, + ISystemTagManager $tagManager + ) { + $this->l10n = $l10n; + $this->urlGenerator = $urlGenerator; + $this->mimeTypeDetector = $mimeTypeDetector; + $this->rootFolder = $rootFolder; + $this->objectMapper = $objectMapper; + $this->tagManager = $tagManager; + } + + /** + * @inheritDoc + */ + public function getId(): string { + return 'systemtags'; + } + + /** + * @inheritDoc + */ + public function getName(): string { + return $this->l10n->t('Tags'); + } + + /** + * @inheritDoc + */ + public function getOrder(string $route, array $routeParameters): int { + if ($route === 'files.View.index') { + return -4; + } + return 6; + } + + /** + * @inheritDoc + */ + public function search(IUser $user, ISearchQuery $query): SearchResult { + $userFolder = $this->rootFolder->getUserFolder($user->getUID()); + $fileQuery = new SearchQuery( + new SearchBinaryOperator(SearchBinaryOperator::OPERATOR_OR, [ + new SearchComparison(ISearchComparison::COMPARE_LIKE, 'tagname', '%' . $query->getTerm() . '%'), + new SearchComparison(ISearchComparison::COMPARE_LIKE, 'systemtag', '%' . $query->getTerm() . '%'), + ]), + $query->getLimit(), + (int)$query->getCursor(), + $query->getSortOrder() === ISearchQuery::SORT_DATE_DESC ? [ + new SearchOrder(ISearchOrder::DIRECTION_DESCENDING, 'mtime'), + ] : [], + $user + ); + + // do search + $searchResults = $userFolder->search($fileQuery); + $resultIds = array_map(function(Node $node) { + return $node->getId(); + }, $searchResults); + $matchedTags = $this->objectMapper->getTagIdsForObjects($resultIds, 'files'); + $relevantTags = $this->tagManager->getTagsByIds(array_unique($this->flattenArray($matchedTags))); + + // prepare direct tag results + $tagResults = array_map(function(ISystemTag $tag) { + $thumbnailUrl = ''; + $link = $this->urlGenerator->linkToRoute( + 'files.view.index' + ) . '?view=systemtagsfilter&tags='.$tag->getId(); + $searchResultEntry = new SearchResultEntry( + $thumbnailUrl, + $this->l10n->t('All tagged %s …', [$tag->getName()]), + '', + $this->urlGenerator->getAbsoluteURL($link), + 'icon-tag' + ); + return $searchResultEntry; + }, array_filter($relevantTags, function($tag) use ($query) { + return $tag->isUserVisible() && strpos($tag->getName(), $query->getTerm()) !== false; + })); + + // prepare files results + return SearchResult::paginated( + $this->l10n->t('Tags'), + array_map(function (Node $result) use ($userFolder, $matchedTags, $query) { + // Generate thumbnail url + $thumbnailUrl = $this->urlGenerator->linkToRouteAbsolute('core.Preview.getPreviewByFileId', ['x' => 32, 'y' => 32, 'fileId' => $result->getId()]); + $path = $userFolder->getRelativePath($result->getPath()); + + // Use shortened link to centralize the various + // files/folder url redirection in files.View.showFile + $link = $this->urlGenerator->linkToRoute( + 'files.View.showFile', + ['fileid' => $result->getId()] + ); + + $searchResultEntry = new SearchResultEntry( + $thumbnailUrl, + $result->getName(), + $this->formatSubline($query, $matchedTags[$result->getId()]), + $this->urlGenerator->getAbsoluteURL($link), + $result->getMimetype() === FileInfo::MIMETYPE_FOLDER ? 'icon-folder' : $this->mimeTypeDetector->mimeTypeIcon($result->getMimetype()) + ); + $searchResultEntry->addAttribute('fileId', (string)$result->getId()); + $searchResultEntry->addAttribute('path', $path); + return $searchResultEntry; + }, $searchResults) + + $tagResults, + $query->getCursor() + $query->getLimit() + ); + } + + /** + * Format subline for tagged files: Show the first 3 tags + * + * @param $query + * @param array $tagInfo + * @return string + */ + private function formatSubline(ISearchQuery $query, array $tagInfo): string { + /** + * @var ISystemTag[] + */ + $tags = $this->tagManager->getTagsByIds($tagInfo); + $tagNames = array_map(function($tag) { + return $tag->getName(); + }, array_filter($tags, function($tag) { + return $tag->isUserVisible(); + })); + + // show the tag that you have searched for first + usort($tagNames, function($tagName) use($query) { + return strpos($tagName, $query->getTerm()) !== false? -1 : 1; + }); + + return $this->l10n->t('tagged %s', [implode(', ', array_slice($tagNames, 0, 3))]); + } + + private function flattenArray($array) { + $it = new RecursiveIteratorIterator(new RecursiveArrayIterator($array)); + return iterator_to_array($it, true); + } +} diff --git a/apps/systemtags/src/app.js b/apps/systemtags/src/app.js index b5f75c0e7db..e7e5fea5372 100644 --- a/apps/systemtags/src/app.js +++ b/apps/systemtags/src/app.js @@ -38,6 +38,9 @@ return this._fileList } + const tagsParam = (new URL(window.location.href)).searchParams.get('tags') + const initialTags = tagsParam ? tagsParam.split(',').map(parseInt) : [] + this._fileList = new OCA.SystemTags.FileList( $el, { @@ -49,6 +52,7 @@ // done if handling the event with the file list already // created. shown: true, + systemTagIds: initialTags } ) diff --git a/apps/systemtags/src/systemtagsfilelist.js b/apps/systemtags/src/systemtagsfilelist.js index 468bee25b40..a87b5a96c3e 100644 --- a/apps/systemtags/src/systemtagsfilelist.js +++ b/apps/systemtags/src/systemtagsfilelist.js @@ -101,6 +101,7 @@ _initFilterField($container) { const self = this this.$filterField = $('') + this.$filterField.val(this._systemTagIds.join(',')) $container.append(this.$filterField) this.$filterField.select2({ placeholder: t('systemtags', 'Select tags to filter by'), @@ -132,8 +133,8 @@ tags.push(tag.toJSON()) } }) - callback(tags) + self._onTagsChanged({ target: element }) }, }) } else { diff --git a/dist/systemtags-systemtags.js b/dist/systemtags-systemtags.js index 145a8cb4208..a4ae287a69a 100644 --- a/dist/systemtags-systemtags.js +++ b/dist/systemtags-systemtags.js @@ -1,3 +1,3 @@ /*! For license information please see systemtags-systemtags.js.LICENSE.txt */ -!function(){var e,i={30213:function(){OCA.SystemTags||(OCA.SystemTags={}),OCA.SystemTags.App={initFileList:function(e){return this._fileList||(this._fileList=new OCA.SystemTags.FileList(e,{id:"systemtags",fileActions:this._createFileActions(),config:OCA.Files.App.getFilesConfig(),shown:!0}),this._fileList.appName=t("systemtags","Tags")),this._fileList},removeFileList:function(){this._fileList&&this._fileList.$fileList.empty()},_createFileActions:function(){var t=new OCA.Files.FileActions;return t.registerDefaultActions(),t.merge(OCA.Files.fileActions),this._globalActionsInitialized||(this._onActionsUpdated=_.bind(this._onActionsUpdated,this),OCA.Files.fileActions.on("setDefault.app-systemtags",this._onActionsUpdated),OCA.Files.fileActions.on("registerAction.app-systemtags",this._onActionsUpdated),this._globalActionsInitialized=!0),t.register("dir","Open",OC.PERMISSION_READ,"",(function(t,e){OCA.Files.App.setActiveView("files",{silent:!0}),OCA.Files.App.fileList.changeDirectory(OC.joinPaths(e.$file.attr("data-path"),t),!0,!0)})),t.setDefault("dir","Open"),t},_onActionsUpdated:function(t){this._fileList&&(t.action?this._fileList.fileActions.registerAction(t.action):t.defaultAction&&this._fileList.fileActions.setDefault(t.defaultAction.mime,t.defaultAction.name))},destroy:function(){OCA.Files.fileActions.off("setDefault.app-systemtags",this._onActionsUpdated),OCA.Files.fileActions.off("registerAction.app-systemtags",this._onActionsUpdated),this.removeFileList(),this._fileList=null,delete this._globalActionsInitialized}},window.addEventListener("DOMContentLoaded",(function(){$("#app-content-systemtagsfilter").on("show",(function(t){OCA.SystemTags.App.initFileList($(t.target))})),$("#app-content-systemtagsfilter").on("hide",(function(){OCA.SystemTags.App.removeFileList()}))}))},22609:function(){OCA.SystemTags=_.extend({},OCA.SystemTags),OCA.SystemTags||(OCA.SystemTags={}),OCA.SystemTags.FilesPlugin={ignoreLists:["trashbin","files.public"],attach:function(t){if(!(this.ignoreLists.indexOf(t.id)>=0||OCA.SystemTags.View)){var e=new OCA.SystemTags.SystemTagsInfoView;t.registerDetailView(e),OCA.SystemTags.View=e}}},OC.Plugins.register("OCA.Files.FileList",OCA.SystemTags.FilesPlugin)},19294:function(t,e,i){"use strict";i(30213),i(99641),i(22609),i(36670);var s=i(93379),n=i.n(s),l=i(7795),o=i.n(l),a=i(90569),r=i.n(a),c=i(3565),d=i.n(c),f=i(19216),u=i.n(f),h=i(44589),g=i.n(h),p=i(79891),m={};m.styleTagTransform=g(),m.setAttributes=d(),m.insert=r().bind(null,"head"),m.domAPI=o(),m.insertStyleElement=u(),n()(p.Z,m),p.Z&&p.Z.locals&&p.Z.locals,window.OCA.SystemTags=OCA.SystemTags},99641:function(){var e;(e=function(t,e){this.initialize(t,e)}).prototype=_.extend({},OCA.Files.FileList.prototype,{id:"systemtagsfilter",appName:t("systemtags","Tagged files"),_systemTagIds:[],_lastUsedTags:[],_clientSideSort:!0,_allowSelection:!1,_filterField:null,initialize:function(t,e){if(OCA.Files.FileList.prototype.initialize.apply(this,arguments),!this.initialized){e&&e.systemTagIds&&(this._systemTagIds=e.systemTagIds),OC.Plugins.attach("OCA.SystemTags.FileList",this);var i=this.$el.find("#controls").empty();_.defer(_.bind(this._getLastUsedTags,this)),this._initFilterField(i)}},destroy:function(){this.$filterField.remove(),OCA.Files.FileList.prototype.destroy.apply(this,arguments)},_getLastUsedTags:function(){var t=this;$.ajax({type:"GET",url:OC.generateUrl("/apps/systemtags/lastused"),success:function(e){t._lastUsedTags=e}})},_initFilterField:function(e){var i=this;return this.$filterField=$(''),e.append(this.$filterField),this.$filterField.select2({placeholder:t("systemtags","Select tags to filter by"),allowClear:!1,multiple:!0,toggleSelect:!0,separator:",",query:_.bind(this._queryTagsAutocomplete,this),id:function(t){return t.id},initSelection:function(t,e){var i=$(t).val().trim();if(i){var s=i.split(","),n=[];OC.SystemTags.collection.fetch({success:function(){_.each(s,(function(t){var e=OC.SystemTags.collection.get(t);_.isUndefined(e)||n.push(e.toJSON())})),e(n)}})}else e([])},formatResult:function(t){return OC.SystemTags.getDescriptiveTag(t)},formatSelection:function(t){return OC.SystemTags.getDescriptiveTag(t)[0].outerHTML},sortResults:function(t){return t.sort((function(t,e){var s=i._lastUsedTags.indexOf(t.id),n=i._lastUsedTags.indexOf(e.id);return s!==n?-1===n?-1:-1===s?1:s

'+t("systemtags","No files found for the selected tags")+"

"):this.$el.find("#emptycontent").html('

'+t("systemtags","Please select tags to filter by")+"

"),this.$el.find("#emptycontent").toggleClass("hidden",!this.isEmpty),this.$el.find("#filestable thead th").toggleClass("hidden",this.isEmpty)):OCA.Files.FileList.prototype.updateEmptyContent.apply(this,arguments)},getDirectoryPermissions:function(){return OC.PERMISSION_READ|OC.PERMISSION_DELETE},updateStorageStatistics:function(){},reload:function(){if(this._setCurrentDir("/",!1),!this._systemTagIds.length)return this.updateEmptyContent(),this.setFiles([]),$.Deferred().resolve();this._selectedFiles={},this._selectionSummary.clear(),this._currentFileModel&&this._currentFileModel.off(),this._currentFileModel=null,this.$el.find(".select-all").prop("checked",!1),this.showMask(),this._reloadCall=this.filesClient.getFilteredFiles({systemTagIds:this._systemTagIds},{properties:this._getWebdavProperties()}),this._detailsView&&this._updateDetailsView(null);var t=this.reloadCallback.bind(this);return this._reloadCall.then(t,t)},reloadCallback:function(t,e){return e&&e.unshift({}),OCA.Files.FileList.prototype.reloadCallback.call(this,t,e)}}),OCA.SystemTags.FileList=e},36670:function(){!function(t){function e(t){var e=t.toJSON();return OC.isUserAdmin()||e.canAssign||(e.locked=!0),e}var i=t.Files.DetailFileInfoView.extend({_rendered:!1,className:"systemTagsInfoView",name:"systemTags",id:"systemTagsInfoView",_inputView:null,initialize:function(t){var i=this;t=t||{},this._inputView=new OC.SystemTags.SystemTagsInputField({multiple:!0,allowActions:!0,allowCreate:!0,isAdmin:OC.isUserAdmin(),initSelection:function(t,s){s(i.selectedTagsCollection.map(e))}}),this.selectedTagsCollection=new OC.SystemTags.SystemTagsMappingCollection([],{objectType:"files"}),this._inputView.collection.on("change:name",this._onTagRenamedGlobally,this),this._inputView.collection.on("remove",this._onTagDeletedGlobally,this),this._inputView.on("select",this._onSelectTag,this),this._inputView.on("deselect",this._onDeselectTag,this)},_onSelectTag:function(t){this.selectedTagsCollection.create(t.toJSON())},_onDeselectTag:function(t){this.selectedTagsCollection.get(t).destroy()},_onTagRenamedGlobally:function(t){var e=this.selectedTagsCollection.get(t.id);e&&e.set(t.toJSON())},_onTagDeletedGlobally:function(t){this.selectedTagsCollection.remove(t)},setFileInfo:function(t){var i=this;this._rendered||this.render(),t&&(this.selectedTagsCollection.setObjectId(t.id),this.selectedTagsCollection.fetch({success:function(t){t.fetched=!0;var s=t.map(e);i._inputView.setData(s),s.length>0&&i.show()}})),this.hide()},render:function(){this.$el.append(this._inputView.$el),this._inputView.render()},isVisible:function(){return!this.$el.hasClass("hidden")},show:function(){this.$el.removeClass("hidden")},hide:function(){this.$el.addClass("hidden")},toggle:function(){this.$el.toggleClass("hidden")},openDropdown:function(){this.$el.find(".systemTagsInputField").select2("open")},remove:function(){this._inputView.remove()}});t.SystemTags.SystemTagsInfoView=i}(OCA)},79891:function(t,e,i){"use strict";var s=i(87537),n=i.n(s),l=i(23645),o=i.n(l)()(n());o.push([t.id,"#app-content-systemtagsfilter .select2-container{width:30%;margin-left:10px}#app-sidebar .app-sidebar-header__action .tag-label{cursor:pointer;padding:13px 0;display:flex;color:var(--color-text-light);position:relative;margin-top:-20px}","",{version:3,sources:["webpack://./apps/systemtags/src/css/systemtagsfilelist.scss"],names:[],mappings:"AASA,iDACC,SAAA,CACA,gBAAA,CAGD,oDACC,cAAA,CACA,cAAA,CACA,YAAA,CACA,6BAAA,CACA,iBAAA,CACA,gBAAA",sourcesContent:["/*\n * Copyright (c) 2016\n *\n * This file is licensed under the Affero General Public License version 3\n * or later.\n *\n * See the COPYING-README file.\n *\n */\n#app-content-systemtagsfilter .select2-container {\n\twidth: 30%;\n\tmargin-left: 10px;\n}\n\n#app-sidebar .app-sidebar-header__action .tag-label {\n\tcursor: pointer;\n\tpadding: 13px 0;\n\tdisplay: flex;\n\tcolor: var(--color-text-light);\n\tposition: relative;\n\tmargin-top: -20px;\n}\n"],sourceRoot:""}]),e.Z=o}},s={};function n(t){var e=s[t];if(void 0!==e)return e.exports;var l=s[t]={id:t,loaded:!1,exports:{}};return i[t].call(l.exports,l,l.exports,n),l.loaded=!0,l.exports}n.m=i,n.amdD=function(){throw new Error("define cannot be used indirect")},n.amdO={},e=[],n.O=function(t,i,s,l){if(!i){var o=1/0;for(d=0;d=l)&&Object.keys(n.O).every((function(t){return n.O[t](i[r])}))?i.splice(r--,1):(a=!1,l0&&e[d-1][2]>l;d--)e[d]=e[d-1];e[d]=[i,s,l]},n.n=function(t){var e=t&&t.__esModule?function(){return t.default}:function(){return t};return n.d(e,{a:e}),e},n.d=function(t,e){for(var i in e)n.o(e,i)&&!n.o(t,i)&&Object.defineProperty(t,i,{enumerable:!0,get:e[i]})},n.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(t){if("object"==typeof window)return window}}(),n.o=function(t,e){return Object.prototype.hasOwnProperty.call(t,e)},n.r=function(t){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(t,"__esModule",{value:!0})},n.nmd=function(t){return t.paths=[],t.children||(t.children=[]),t},n.j=698,function(){n.b=document.baseURI||self.location.href;var t={698:0};n.O.j=function(e){return 0===t[e]};var e=function(e,i){var s,l,o=i[0],a=i[1],r=i[2],c=0;if(o.some((function(e){return 0!==t[e]}))){for(s in a)n.o(a,s)&&(n.m[s]=a[s]);if(r)var d=r(n)}for(e&&e(i);c=0||OCA.SystemTags.View)){var e=new OCA.SystemTags.SystemTagsInfoView;t.registerDetailView(e),OCA.SystemTags.View=e}}},OC.Plugins.register("OCA.Files.FileList",OCA.SystemTags.FilesPlugin)},19294:function(t,e,i){"use strict";i(30213),i(99641),i(22609),i(36670);var s=i(93379),n=i.n(s),l=i(7795),o=i.n(l),a=i(90569),r=i.n(a),c=i(3565),d=i.n(c),f=i(19216),u=i.n(f),h=i(44589),g=i.n(h),p=i(79891),m={};m.styleTagTransform=g(),m.setAttributes=d(),m.insert=r().bind(null,"head"),m.domAPI=o(),m.insertStyleElement=u(),n()(p.Z,m),p.Z&&p.Z.locals&&p.Z.locals,window.OCA.SystemTags=OCA.SystemTags},99641:function(){var e;(e=function(t,e){this.initialize(t,e)}).prototype=_.extend({},OCA.Files.FileList.prototype,{id:"systemtagsfilter",appName:t("systemtags","Tagged files"),_systemTagIds:[],_lastUsedTags:[],_clientSideSort:!0,_allowSelection:!1,_filterField:null,initialize:function(t,e){if(OCA.Files.FileList.prototype.initialize.apply(this,arguments),!this.initialized){e&&e.systemTagIds&&(this._systemTagIds=e.systemTagIds),OC.Plugins.attach("OCA.SystemTags.FileList",this);var i=this.$el.find("#controls").empty();_.defer(_.bind(this._getLastUsedTags,this)),this._initFilterField(i)}},destroy:function(){this.$filterField.remove(),OCA.Files.FileList.prototype.destroy.apply(this,arguments)},_getLastUsedTags:function(){var t=this;$.ajax({type:"GET",url:OC.generateUrl("/apps/systemtags/lastused"),success:function(e){t._lastUsedTags=e}})},_initFilterField:function(e){var i=this;return this.$filterField=$(''),this.$filterField.val(this._systemTagIds.join(",")),e.append(this.$filterField),this.$filterField.select2({placeholder:t("systemtags","Select tags to filter by"),allowClear:!1,multiple:!0,toggleSelect:!0,separator:",",query:_.bind(this._queryTagsAutocomplete,this),id:function(t){return t.id},initSelection:function(t,e){var s=$(t).val().trim();if(s){var n=s.split(","),l=[];OC.SystemTags.collection.fetch({success:function(){_.each(n,(function(t){var e=OC.SystemTags.collection.get(t);_.isUndefined(e)||l.push(e.toJSON())})),e(l),i._onTagsChanged({target:t})}})}else e([])},formatResult:function(t){return OC.SystemTags.getDescriptiveTag(t)},formatSelection:function(t){return OC.SystemTags.getDescriptiveTag(t)[0].outerHTML},sortResults:function(t){return t.sort((function(t,e){var s=i._lastUsedTags.indexOf(t.id),n=i._lastUsedTags.indexOf(e.id);return s!==n?-1===n?-1:-1===s?1:s

'+t("systemtags","No files found for the selected tags")+"

"):this.$el.find("#emptycontent").html('

'+t("systemtags","Please select tags to filter by")+"

"),this.$el.find("#emptycontent").toggleClass("hidden",!this.isEmpty),this.$el.find("#filestable thead th").toggleClass("hidden",this.isEmpty)):OCA.Files.FileList.prototype.updateEmptyContent.apply(this,arguments)},getDirectoryPermissions:function(){return OC.PERMISSION_READ|OC.PERMISSION_DELETE},updateStorageStatistics:function(){},reload:function(){if(this._setCurrentDir("/",!1),!this._systemTagIds.length)return this.updateEmptyContent(),this.setFiles([]),$.Deferred().resolve();this._selectedFiles={},this._selectionSummary.clear(),this._currentFileModel&&this._currentFileModel.off(),this._currentFileModel=null,this.$el.find(".select-all").prop("checked",!1),this.showMask(),this._reloadCall=this.filesClient.getFilteredFiles({systemTagIds:this._systemTagIds},{properties:this._getWebdavProperties()}),this._detailsView&&this._updateDetailsView(null);var t=this.reloadCallback.bind(this);return this._reloadCall.then(t,t)},reloadCallback:function(t,e){return e&&e.unshift({}),OCA.Files.FileList.prototype.reloadCallback.call(this,t,e)}}),OCA.SystemTags.FileList=e},36670:function(){!function(t){function e(t){var e=t.toJSON();return OC.isUserAdmin()||e.canAssign||(e.locked=!0),e}var i=t.Files.DetailFileInfoView.extend({_rendered:!1,className:"systemTagsInfoView",name:"systemTags",id:"systemTagsInfoView",_inputView:null,initialize:function(t){var i=this;t=t||{},this._inputView=new OC.SystemTags.SystemTagsInputField({multiple:!0,allowActions:!0,allowCreate:!0,isAdmin:OC.isUserAdmin(),initSelection:function(t,s){s(i.selectedTagsCollection.map(e))}}),this.selectedTagsCollection=new OC.SystemTags.SystemTagsMappingCollection([],{objectType:"files"}),this._inputView.collection.on("change:name",this._onTagRenamedGlobally,this),this._inputView.collection.on("remove",this._onTagDeletedGlobally,this),this._inputView.on("select",this._onSelectTag,this),this._inputView.on("deselect",this._onDeselectTag,this)},_onSelectTag:function(t){this.selectedTagsCollection.create(t.toJSON())},_onDeselectTag:function(t){this.selectedTagsCollection.get(t).destroy()},_onTagRenamedGlobally:function(t){var e=this.selectedTagsCollection.get(t.id);e&&e.set(t.toJSON())},_onTagDeletedGlobally:function(t){this.selectedTagsCollection.remove(t)},setFileInfo:function(t){var i=this;this._rendered||this.render(),t&&(this.selectedTagsCollection.setObjectId(t.id),this.selectedTagsCollection.fetch({success:function(t){t.fetched=!0;var s=t.map(e);i._inputView.setData(s),s.length>0&&i.show()}})),this.hide()},render:function(){this.$el.append(this._inputView.$el),this._inputView.render()},isVisible:function(){return!this.$el.hasClass("hidden")},show:function(){this.$el.removeClass("hidden")},hide:function(){this.$el.addClass("hidden")},toggle:function(){this.$el.toggleClass("hidden")},openDropdown:function(){this.$el.find(".systemTagsInputField").select2("open")},remove:function(){this._inputView.remove()}});t.SystemTags.SystemTagsInfoView=i}(OCA)},79891:function(t,e,i){"use strict";var s=i(87537),n=i.n(s),l=i(23645),o=i.n(l)()(n());o.push([t.id,"#app-content-systemtagsfilter .select2-container{width:30%;margin-left:10px}#app-sidebar .app-sidebar-header__action .tag-label{cursor:pointer;padding:13px 0;display:flex;color:var(--color-text-light);position:relative;margin-top:-20px}","",{version:3,sources:["webpack://./apps/systemtags/src/css/systemtagsfilelist.scss"],names:[],mappings:"AASA,iDACC,SAAA,CACA,gBAAA,CAGD,oDACC,cAAA,CACA,cAAA,CACA,YAAA,CACA,6BAAA,CACA,iBAAA,CACA,gBAAA",sourcesContent:["/*\n * Copyright (c) 2016\n *\n * This file is licensed under the Affero General Public License version 3\n * or later.\n *\n * See the COPYING-README file.\n *\n */\n#app-content-systemtagsfilter .select2-container {\n\twidth: 30%;\n\tmargin-left: 10px;\n}\n\n#app-sidebar .app-sidebar-header__action .tag-label {\n\tcursor: pointer;\n\tpadding: 13px 0;\n\tdisplay: flex;\n\tcolor: var(--color-text-light);\n\tposition: relative;\n\tmargin-top: -20px;\n}\n"],sourceRoot:""}]),e.Z=o}},s={};function n(t){var e=s[t];if(void 0!==e)return e.exports;var l=s[t]={id:t,loaded:!1,exports:{}};return i[t].call(l.exports,l,l.exports,n),l.loaded=!0,l.exports}n.m=i,n.amdD=function(){throw new Error("define cannot be used indirect")},n.amdO={},e=[],n.O=function(t,i,s,l){if(!i){var o=1/0;for(d=0;d=l)&&Object.keys(n.O).every((function(t){return n.O[t](i[r])}))?i.splice(r--,1):(a=!1,l0&&e[d-1][2]>l;d--)e[d]=e[d-1];e[d]=[i,s,l]},n.n=function(t){var e=t&&t.__esModule?function(){return t.default}:function(){return t};return n.d(e,{a:e}),e},n.d=function(t,e){for(var i in e)n.o(e,i)&&!n.o(t,i)&&Object.defineProperty(t,i,{enumerable:!0,get:e[i]})},n.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(t){if("object"==typeof window)return window}}(),n.o=function(t,e){return Object.prototype.hasOwnProperty.call(t,e)},n.r=function(t){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(t,"__esModule",{value:!0})},n.nmd=function(t){return t.paths=[],t.children||(t.children=[]),t},n.j=698,function(){n.b=document.baseURI||self.location.href;var t={698:0};n.O.j=function(e){return 0===t[e]};var e=function(e,i){var s,l,o=i[0],a=i[1],r=i[2],c=0;if(o.some((function(e){return 0!==t[e]}))){for(s in a)n.o(a,s)&&(n.m[s]=a[s]);if(r)var d=r(n)}for(e&&e(i);c 0 && deferred[i - 1][2] > priority; i--) deferred[i] = deferred[i - 1];\n\t\tdeferred[i] = [chunkIds, fn, priority];\n\t\treturn;\n\t}\n\tvar notFulfilled = Infinity;\n\tfor (var i = 0; i < deferred.length; i++) {\n\t\tvar chunkIds = deferred[i][0];\n\t\tvar fn = deferred[i][1];\n\t\tvar priority = deferred[i][2];\n\t\tvar fulfilled = true;\n\t\tfor (var j = 0; j < chunkIds.length; j++) {\n\t\t\tif ((priority & 1 === 0 || notFulfilled >= priority) && Object.keys(__webpack_require__.O).every(function(key) { return __webpack_require__.O[key](chunkIds[j]); })) {\n\t\t\t\tchunkIds.splice(j--, 1);\n\t\t\t} else {\n\t\t\t\tfulfilled = false;\n\t\t\t\tif(priority < notFulfilled) notFulfilled = priority;\n\t\t\t}\n\t\t}\n\t\tif(fulfilled) {\n\t\t\tdeferred.splice(i--, 1)\n\t\t\tvar r = fn();\n\t\t\tif (r !== undefined) result = r;\n\t\t}\n\t}\n\treturn result;\n};","/**\n * Copyright (c) 2015 Vincent Petry \n *\n * @author Christoph Wurst \n * @author Daniel Calviño Sánchez \n * @author John Molakvoæ \n * @author Vincent Petry \n *\n * @license AGPL-3.0-or-later\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program. If not, see .\n *\n */\n\n(function() {\n\tif (!OCA.SystemTags) {\n\t\t/**\n\t\t * @namespace\n\t\t */\n\t\tOCA.SystemTags = {}\n\t}\n\n\tOCA.SystemTags.App = {\n\n\t\tinitFileList($el) {\n\t\t\tif (this._fileList) {\n\t\t\t\treturn this._fileList\n\t\t\t}\n\n\t\t\tthis._fileList = new OCA.SystemTags.FileList(\n\t\t\t\t$el,\n\t\t\t\t{\n\t\t\t\t\tid: 'systemtags',\n\t\t\t\t\tfileActions: this._createFileActions(),\n\t\t\t\t\tconfig: OCA.Files.App.getFilesConfig(),\n\t\t\t\t\t// The file list is created when a \"show\" event is handled,\n\t\t\t\t\t// so it should be marked as \"shown\" like it would have been\n\t\t\t\t\t// done if handling the event with the file list already\n\t\t\t\t\t// created.\n\t\t\t\t\tshown: true,\n\t\t\t\t}\n\t\t\t)\n\n\t\t\tthis._fileList.appName = t('systemtags', 'Tags')\n\t\t\treturn this._fileList\n\t\t},\n\n\t\tremoveFileList() {\n\t\t\tif (this._fileList) {\n\t\t\t\tthis._fileList.$fileList.empty()\n\t\t\t}\n\t\t},\n\n\t\t_createFileActions() {\n\t\t\t// inherit file actions from the files app\n\t\t\tconst fileActions = new OCA.Files.FileActions()\n\t\t\t// note: not merging the legacy actions because legacy apps are not\n\t\t\t// compatible with the sharing overview and need to be adapted first\n\t\t\tfileActions.registerDefaultActions()\n\t\t\tfileActions.merge(OCA.Files.fileActions)\n\n\t\t\tif (!this._globalActionsInitialized) {\n\t\t\t\t// in case actions are registered later\n\t\t\t\tthis._onActionsUpdated = _.bind(this._onActionsUpdated, this)\n\t\t\t\tOCA.Files.fileActions.on('setDefault.app-systemtags', this._onActionsUpdated)\n\t\t\t\tOCA.Files.fileActions.on('registerAction.app-systemtags', this._onActionsUpdated)\n\t\t\t\tthis._globalActionsInitialized = true\n\t\t\t}\n\n\t\t\t// when the user clicks on a folder, redirect to the corresponding\n\t\t\t// folder in the files app instead of opening it directly\n\t\t\tfileActions.register('dir', 'Open', OC.PERMISSION_READ, '', function(filename, context) {\n\t\t\t\tOCA.Files.App.setActiveView('files', { silent: true })\n\t\t\t\tOCA.Files.App.fileList.changeDirectory(OC.joinPaths(context.$file.attr('data-path'), filename), true, true)\n\t\t\t})\n\t\t\tfileActions.setDefault('dir', 'Open')\n\t\t\treturn fileActions\n\t\t},\n\n\t\t_onActionsUpdated(ev) {\n\t\t\tif (!this._fileList) {\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif (ev.action) {\n\t\t\t\tthis._fileList.fileActions.registerAction(ev.action)\n\t\t\t} else if (ev.defaultAction) {\n\t\t\t\tthis._fileList.fileActions.setDefault(\n\t\t\t\t\tev.defaultAction.mime,\n\t\t\t\t\tev.defaultAction.name\n\t\t\t\t)\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Destroy the app\n\t\t */\n\t\tdestroy() {\n\t\t\tOCA.Files.fileActions.off('setDefault.app-systemtags', this._onActionsUpdated)\n\t\t\tOCA.Files.fileActions.off('registerAction.app-systemtags', this._onActionsUpdated)\n\t\t\tthis.removeFileList()\n\t\t\tthis._fileList = null\n\t\t\tdelete this._globalActionsInitialized\n\t\t},\n\t}\n\n})()\n\nwindow.addEventListener('DOMContentLoaded', function() {\n\t$('#app-content-systemtagsfilter').on('show', function(e) {\n\t\tOCA.SystemTags.App.initFileList($(e.target))\n\t})\n\t$('#app-content-systemtagsfilter').on('hide', function() {\n\t\tOCA.SystemTags.App.removeFileList()\n\t})\n})\n","/**\n * Copyright (c) 2015 Vincent Petry \n *\n * @author Joas Schilling \n * @author John Molakvoæ \n * @author Vincent Petry \n *\n * @license AGPL-3.0-or-later\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program. If not, see .\n *\n */\n\n(function() {\n\tOCA.SystemTags = _.extend({}, OCA.SystemTags)\n\tif (!OCA.SystemTags) {\n\t\t/**\n\t\t * @namespace\n\t\t */\n\t\tOCA.SystemTags = {}\n\t}\n\n\t/**\n\t * @namespace\n\t */\n\tOCA.SystemTags.FilesPlugin = {\n\t\tignoreLists: [\n\t\t\t'trashbin',\n\t\t\t'files.public',\n\t\t],\n\n\t\tattach(fileList) {\n\t\t\tif (this.ignoreLists.indexOf(fileList.id) >= 0) {\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\t// only create and attach once\n\t\t\t// FIXME: this should likely be done on a different code path now\n\t\t\t// for the sidebar to only have it registered once\n\t\t\tif (!OCA.SystemTags.View) {\n\t\t\t\tconst systemTagsInfoView = new OCA.SystemTags.SystemTagsInfoView()\n\t\t\t\tfileList.registerDetailView(systemTagsInfoView)\n\t\t\t\tOCA.SystemTags.View = systemTagsInfoView\n\t\t\t}\n\t\t},\n\t}\n\n})()\n\nOC.Plugins.register('OCA.Files.FileList', OCA.SystemTags.FilesPlugin)\n","\n import API from \"!../../../../node_modules/style-loader/dist/runtime/injectStylesIntoStyleTag.js\";\n import domAPI from \"!../../../../node_modules/style-loader/dist/runtime/styleDomAPI.js\";\n import insertFn from \"!../../../../node_modules/style-loader/dist/runtime/insertBySelector.js\";\n import setAttributes from \"!../../../../node_modules/style-loader/dist/runtime/setAttributesWithoutAttributes.js\";\n import insertStyleElement from \"!../../../../node_modules/style-loader/dist/runtime/insertStyleElement.js\";\n import styleTagTransformFn from \"!../../../../node_modules/style-loader/dist/runtime/styleTagTransform.js\";\n import content, * as namedExport from \"!!../../../../node_modules/css-loader/dist/cjs.js!../../../../node_modules/sass-loader/dist/cjs.js!./systemtagsfilelist.scss\";\n \n \n\nvar options = {};\n\noptions.styleTagTransform = styleTagTransformFn;\noptions.setAttributes = setAttributes;\n\n options.insert = insertFn.bind(null, \"head\");\n \noptions.domAPI = domAPI;\noptions.insertStyleElement = insertStyleElement;\n\nvar update = API(content, options);\n\n\n\nexport * from \"!!../../../../node_modules/css-loader/dist/cjs.js!../../../../node_modules/sass-loader/dist/cjs.js!./systemtagsfilelist.scss\";\n export default content && content.locals ? content.locals : undefined;\n","/**\n * @copyright Copyright (c) 2016 Roeland Jago Douma \n *\n * @author John Molakvoæ \n * @author Roeland Jago Douma \n *\n * @license AGPL-3.0-or-later\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program. If not, see .\n *\n */\n\nimport './app'\nimport './systemtagsfilelist'\nimport './filesplugin'\nimport './systemtagsinfoview'\nimport './css/systemtagsfilelist.scss'\n\nwindow.OCA.SystemTags = OCA.SystemTags\n","/**\n * Copyright (c) 2016 Vincent Petry \n *\n * @author Joas Schilling \n * @author John Molakvoæ \n * @author Vincent Petry \n *\n * @license AGPL-3.0-or-later\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program. If not, see .\n *\n */\n\n(function() {\n\t/**\n\t * @class OCA.SystemTags.FileList\n\t * @augments OCA.Files.FileList\n\t *\n\t * @classdesc SystemTags file list.\n\t * Contains a list of files filtered by system tags.\n\t *\n\t * @param {object} $el container element with existing markup for the #controls and a table\n\t * @param {Array} [options] map of options, see other parameters\n\t * @param {Array.} [options.systemTagIds] array of system tag ids to\n\t * filter by\n\t */\n\tconst FileList = function($el, options) {\n\t\tthis.initialize($el, options)\n\t}\n\tFileList.prototype = _.extend(\n\t\t{},\n\t\tOCA.Files.FileList.prototype,\n\t\t/** @lends OCA.SystemTags.FileList.prototype */ {\n\t\t\tid: 'systemtagsfilter',\n\t\t\tappName: t('systemtags', 'Tagged files'),\n\n\t\t\t/**\n\t\t\t * Array of system tag ids to filter by\n\t\t\t *\n\t\t\t * @type {Array.}\n\t\t\t */\n\t\t\t_systemTagIds: [],\n\t\t\t_lastUsedTags: [],\n\n\t\t\t_clientSideSort: true,\n\t\t\t_allowSelection: false,\n\n\t\t\t_filterField: null,\n\n\t\t\t/**\n\t\t\t * @private\n\t\t\t * @param {object} $el container element\n\t\t\t * @param {object} [options] map of options, see other parameters\n\t\t\t */\n\t\t\tinitialize($el, options) {\n\t\t\t\tOCA.Files.FileList.prototype.initialize.apply(this, arguments)\n\t\t\t\tif (this.initialized) {\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\tif (options && options.systemTagIds) {\n\t\t\t\t\tthis._systemTagIds = options.systemTagIds\n\t\t\t\t}\n\n\t\t\t\tOC.Plugins.attach('OCA.SystemTags.FileList', this)\n\n\t\t\t\tconst $controls = this.$el.find('#controls').empty()\n\n\t\t\t\t_.defer(_.bind(this._getLastUsedTags, this))\n\t\t\t\tthis._initFilterField($controls)\n\t\t\t},\n\n\t\t\tdestroy() {\n\t\t\t\tthis.$filterField.remove()\n\n\t\t\t\tOCA.Files.FileList.prototype.destroy.apply(this, arguments)\n\t\t\t},\n\n\t\t\t_getLastUsedTags() {\n\t\t\t\tconst self = this\n\t\t\t\t$.ajax({\n\t\t\t\t\ttype: 'GET',\n\t\t\t\t\turl: OC.generateUrl('/apps/systemtags/lastused'),\n\t\t\t\t\tsuccess(response) {\n\t\t\t\t\t\tself._lastUsedTags = response\n\t\t\t\t\t},\n\t\t\t\t})\n\t\t\t},\n\n\t\t\t_initFilterField($container) {\n\t\t\t\tconst self = this\n\t\t\t\tthis.$filterField = $('')\n\t\t\t\t$container.append(this.$filterField)\n\t\t\t\tthis.$filterField.select2({\n\t\t\t\t\tplaceholder: t('systemtags', 'Select tags to filter by'),\n\t\t\t\t\tallowClear: false,\n\t\t\t\t\tmultiple: true,\n\t\t\t\t\ttoggleSelect: true,\n\t\t\t\t\tseparator: ',',\n\t\t\t\t\tquery: _.bind(this._queryTagsAutocomplete, this),\n\n\t\t\t\t\tid(tag) {\n\t\t\t\t\t\treturn tag.id\n\t\t\t\t\t},\n\n\t\t\t\t\tinitSelection(element, callback) {\n\t\t\t\t\t\tconst val = $(element)\n\t\t\t\t\t\t\t.val()\n\t\t\t\t\t\t\t.trim()\n\t\t\t\t\t\tif (val) {\n\t\t\t\t\t\t\tconst tagIds = val.split(',')\n\t\t\t\t\t\t\tconst tags = []\n\n\t\t\t\t\t\t\tOC.SystemTags.collection.fetch({\n\t\t\t\t\t\t\t\tsuccess() {\n\t\t\t\t\t\t\t\t\t_.each(tagIds, function(tagId) {\n\t\t\t\t\t\t\t\t\t\tconst tag = OC.SystemTags.collection.get(\n\t\t\t\t\t\t\t\t\t\t\ttagId\n\t\t\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t\t\t\tif (!_.isUndefined(tag)) {\n\t\t\t\t\t\t\t\t\t\t\ttags.push(tag.toJSON())\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t})\n\n\t\t\t\t\t\t\t\t\tcallback(tags)\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t})\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t// eslint-disable-next-line n/no-callback-literal\n\t\t\t\t\t\t\tcallback([])\n\t\t\t\t\t\t}\n\t\t\t\t\t},\n\n\t\t\t\t\tformatResult(tag) {\n\t\t\t\t\t\treturn OC.SystemTags.getDescriptiveTag(tag)\n\t\t\t\t\t},\n\n\t\t\t\t\tformatSelection(tag) {\n\t\t\t\t\t\treturn OC.SystemTags.getDescriptiveTag(tag)[0]\n\t\t\t\t\t\t\t.outerHTML\n\t\t\t\t\t},\n\n\t\t\t\t\tsortResults(results) {\n\t\t\t\t\t\tresults.sort(function(a, b) {\n\t\t\t\t\t\t\tconst aLastUsed = self._lastUsedTags.indexOf(a.id)\n\t\t\t\t\t\t\tconst bLastUsed = self._lastUsedTags.indexOf(b.id)\n\n\t\t\t\t\t\t\tif (aLastUsed !== bLastUsed) {\n\t\t\t\t\t\t\t\tif (bLastUsed === -1) {\n\t\t\t\t\t\t\t\t\treturn -1\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tif (aLastUsed === -1) {\n\t\t\t\t\t\t\t\t\treturn 1\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\treturn aLastUsed < bLastUsed ? -1 : 1\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t// Both not found\n\t\t\t\t\t\t\treturn OC.Util.naturalSortCompare(a.name, b.name)\n\t\t\t\t\t\t})\n\t\t\t\t\t\treturn results\n\t\t\t\t\t},\n\n\t\t\t\t\tescapeMarkup(m) {\n\t\t\t\t\t\t// prevent double markup escape\n\t\t\t\t\t\treturn m\n\t\t\t\t\t},\n\t\t\t\t\tformatNoMatches() {\n\t\t\t\t\t\treturn t('systemtags', 'No tags found')\n\t\t\t\t\t},\n\t\t\t\t})\n\t\t\t\tthis.$filterField.on(\n\t\t\t\t\t'change',\n\t\t\t\t\t_.bind(this._onTagsChanged, this)\n\t\t\t\t)\n\t\t\t\treturn this.$filterField\n\t\t\t},\n\n\t\t\t/**\n\t\t\t * Autocomplete function for dropdown results\n\t\t\t *\n\t\t\t * @param {object} query select2 query object\n\t\t\t */\n\t\t\t_queryTagsAutocomplete(query) {\n\t\t\t\tOC.SystemTags.collection.fetch({\n\t\t\t\t\tsuccess() {\n\t\t\t\t\t\tconst results = OC.SystemTags.collection.filterByName(\n\t\t\t\t\t\t\tquery.term\n\t\t\t\t\t\t)\n\n\t\t\t\t\t\tquery.callback({\n\t\t\t\t\t\t\tresults: _.invoke(results, 'toJSON'),\n\t\t\t\t\t\t})\n\t\t\t\t\t},\n\t\t\t\t})\n\t\t\t},\n\n\t\t\t/**\n\t\t\t * Event handler for when the URL changed\n\t\t\t *\n\t\t\t * @param {Event} e the urlchanged event\n\t\t\t */\n\t\t\t_onUrlChanged(e) {\n\t\t\t\tif (e.dir) {\n\t\t\t\t\tconst tags = _.filter(e.dir.split('/'), function(val) {\n\t\t\t\t\t\treturn val.trim() !== ''\n\t\t\t\t\t})\n\t\t\t\t\tthis.$filterField.select2('val', tags || [])\n\t\t\t\t\tthis._systemTagIds = tags\n\t\t\t\t\tthis.reload()\n\t\t\t\t}\n\t\t\t},\n\n\t\t\t_onTagsChanged(ev) {\n\t\t\t\tconst val = $(ev.target)\n\t\t\t\t\t.val()\n\t\t\t\t\t.trim()\n\t\t\t\tif (val !== '') {\n\t\t\t\t\tthis._systemTagIds = val.split(',')\n\t\t\t\t} else {\n\t\t\t\t\tthis._systemTagIds = []\n\t\t\t\t}\n\n\t\t\t\tthis.$el.trigger(\n\t\t\t\t\t$.Event('changeDirectory', {\n\t\t\t\t\t\tdir: this._systemTagIds.join('/'),\n\t\t\t\t\t})\n\t\t\t\t)\n\t\t\t\tthis.reload()\n\t\t\t},\n\n\t\t\tupdateEmptyContent() {\n\t\t\t\tconst dir = this.getCurrentDirectory()\n\t\t\t\tif (dir === '/') {\n\t\t\t\t\t// root has special permissions\n\t\t\t\t\tif (!this._systemTagIds.length) {\n\t\t\t\t\t\t// no tags selected\n\t\t\t\t\t\tthis.$el\n\t\t\t\t\t\t\t.find('#emptycontent')\n\t\t\t\t\t\t\t.html(\n\t\t\t\t\t\t\t\t'
'\n\t\t\t\t\t\t\t\t\t+ '

'\n\t\t\t\t\t\t\t\t\t+ t(\n\t\t\t\t\t\t\t\t\t\t'systemtags',\n\t\t\t\t\t\t\t\t\t\t'Please select tags to filter by'\n\t\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t\t\t+ '

'\n\t\t\t\t\t\t\t)\n\t\t\t\t\t} else {\n\t\t\t\t\t\t// tags selected but no results\n\t\t\t\t\t\tthis.$el\n\t\t\t\t\t\t\t.find('#emptycontent')\n\t\t\t\t\t\t\t.html(\n\t\t\t\t\t\t\t\t'
'\n\t\t\t\t\t\t\t\t\t+ '

'\n\t\t\t\t\t\t\t\t\t+ t(\n\t\t\t\t\t\t\t\t\t\t'systemtags',\n\t\t\t\t\t\t\t\t\t\t'No files found for the selected tags'\n\t\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t\t\t+ '

'\n\t\t\t\t\t\t\t)\n\t\t\t\t\t}\n\t\t\t\t\tthis.$el\n\t\t\t\t\t\t.find('#emptycontent')\n\t\t\t\t\t\t.toggleClass('hidden', !this.isEmpty)\n\t\t\t\t\tthis.$el\n\t\t\t\t\t\t.find('#filestable thead th')\n\t\t\t\t\t\t.toggleClass('hidden', this.isEmpty)\n\t\t\t\t} else {\n\t\t\t\t\tOCA.Files.FileList.prototype.updateEmptyContent.apply(\n\t\t\t\t\t\tthis,\n\t\t\t\t\t\targuments\n\t\t\t\t\t)\n\t\t\t\t}\n\t\t\t},\n\n\t\t\tgetDirectoryPermissions() {\n\t\t\t\treturn OC.PERMISSION_READ | OC.PERMISSION_DELETE\n\t\t\t},\n\n\t\t\tupdateStorageStatistics() {\n\t\t\t\t// no op because it doesn't have\n\t\t\t\t// storage info like free space / used space\n\t\t\t},\n\n\t\t\treload() {\n\t\t\t\t// there is only root\n\t\t\t\tthis._setCurrentDir('/', false)\n\n\t\t\t\tif (!this._systemTagIds.length) {\n\t\t\t\t\t// don't reload\n\t\t\t\t\tthis.updateEmptyContent()\n\t\t\t\t\tthis.setFiles([])\n\t\t\t\t\treturn $.Deferred().resolve()\n\t\t\t\t}\n\n\t\t\t\tthis._selectedFiles = {}\n\t\t\t\tthis._selectionSummary.clear()\n\t\t\t\tif (this._currentFileModel) {\n\t\t\t\t\tthis._currentFileModel.off()\n\t\t\t\t}\n\t\t\t\tthis._currentFileModel = null\n\t\t\t\tthis.$el.find('.select-all').prop('checked', false)\n\t\t\t\tthis.showMask()\n\t\t\t\tthis._reloadCall = this.filesClient.getFilteredFiles(\n\t\t\t\t\t{\n\t\t\t\t\t\tsystemTagIds: this._systemTagIds,\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tproperties: this._getWebdavProperties(),\n\t\t\t\t\t}\n\t\t\t\t)\n\t\t\t\tif (this._detailsView) {\n\t\t\t\t\t// close sidebar\n\t\t\t\t\tthis._updateDetailsView(null)\n\t\t\t\t}\n\t\t\t\tconst callBack = this.reloadCallback.bind(this)\n\t\t\t\treturn this._reloadCall.then(callBack, callBack)\n\t\t\t},\n\n\t\t\treloadCallback(status, result) {\n\t\t\t\tif (result) {\n\t\t\t\t\t// prepend empty dir info because original handler\n\t\t\t\t\tresult.unshift({})\n\t\t\t\t}\n\n\t\t\t\treturn OCA.Files.FileList.prototype.reloadCallback.call(\n\t\t\t\t\tthis,\n\t\t\t\t\tstatus,\n\t\t\t\t\tresult\n\t\t\t\t)\n\t\t\t},\n\t\t}\n\t)\n\n\tOCA.SystemTags.FileList = FileList\n})()\n","/**\n * Copyright (c) 2015\n *\n * @author Daniel Calviño Sánchez \n * @author Joas Schilling \n * @author John Molakvoæ \n * @author Julius Härtl \n * @author Vincent Petry \n *\n * @license AGPL-3.0-or-later\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program. If not, see .\n *\n */\n\n(function(OCA) {\n\n\t/**\n\t * @param {any} model -\n\t */\n\tfunction modelToSelection(model) {\n\t\tconst data = model.toJSON()\n\t\tif (!OC.isUserAdmin() && !data.canAssign) {\n\t\t\tdata.locked = true\n\t\t}\n\t\treturn data\n\t}\n\n\t/**\n\t * @class OCA.SystemTags.SystemTagsInfoView\n\t * @classdesc\n\t *\n\t * Displays a file's system tags\n\t *\n\t */\n\tconst SystemTagsInfoView = OCA.Files.DetailFileInfoView.extend(\n\t\t/** @lends OCA.SystemTags.SystemTagsInfoView.prototype */ {\n\n\t\t\t_rendered: false,\n\n\t\t\tclassName: 'systemTagsInfoView',\n\t\t\tname: 'systemTags',\n\n\t\t\t/* required by the new files sidebar to check if the view is unique */\n\t\t\tid: 'systemTagsInfoView',\n\n\t\t\t/**\n\t\t\t * @type {OC.SystemTags.SystemTagsInputField}\n\t\t\t */\n\t\t\t_inputView: null,\n\n\t\t\tinitialize(options) {\n\t\t\t\tconst self = this\n\t\t\t\toptions = options || {}\n\n\t\t\t\tthis._inputView = new OC.SystemTags.SystemTagsInputField({\n\t\t\t\t\tmultiple: true,\n\t\t\t\t\tallowActions: true,\n\t\t\t\t\tallowCreate: true,\n\t\t\t\t\tisAdmin: OC.isUserAdmin(),\n\t\t\t\t\tinitSelection(element, callback) {\n\t\t\t\t\t\tcallback(self.selectedTagsCollection.map(modelToSelection))\n\t\t\t\t\t},\n\t\t\t\t})\n\n\t\t\t\tthis.selectedTagsCollection = new OC.SystemTags.SystemTagsMappingCollection([], { objectType: 'files' })\n\n\t\t\t\tthis._inputView.collection.on('change:name', this._onTagRenamedGlobally, this)\n\t\t\t\tthis._inputView.collection.on('remove', this._onTagDeletedGlobally, this)\n\n\t\t\t\tthis._inputView.on('select', this._onSelectTag, this)\n\t\t\t\tthis._inputView.on('deselect', this._onDeselectTag, this)\n\t\t\t},\n\n\t\t\t/**\n\t\t\t * Event handler whenever a tag was selected\n\t\t\t *\n\t\t\t * @param {object} tag the tag to create\n\t\t\t */\n\t\t\t_onSelectTag(tag) {\n\t\t\t// create a mapping entry for this tag\n\t\t\t\tthis.selectedTagsCollection.create(tag.toJSON())\n\t\t\t},\n\n\t\t\t/**\n\t\t\t * Event handler whenever a tag gets deselected.\n\t\t\t * Removes the selected tag from the mapping collection.\n\t\t\t *\n\t\t\t * @param {string} tagId tag id\n\t\t\t */\n\t\t\t_onDeselectTag(tagId) {\n\t\t\t\tthis.selectedTagsCollection.get(tagId).destroy()\n\t\t\t},\n\n\t\t\t/**\n\t\t\t * Event handler whenever a tag was renamed globally.\n\t\t\t *\n\t\t\t * This will automatically adjust the tag mapping collection to\n\t\t\t * container the new name.\n\t\t\t *\n\t\t\t * @param {OC.Backbone.Model} changedTag tag model that has changed\n\t\t\t */\n\t\t\t_onTagRenamedGlobally(changedTag) {\n\t\t\t// also rename it in the selection, if applicable\n\t\t\t\tconst selectedTagMapping = this.selectedTagsCollection.get(changedTag.id)\n\t\t\t\tif (selectedTagMapping) {\n\t\t\t\t\tselectedTagMapping.set(changedTag.toJSON())\n\t\t\t\t}\n\t\t\t},\n\n\t\t\t/**\n\t\t\t * Event handler whenever a tag was deleted globally.\n\t\t\t *\n\t\t\t * This will automatically adjust the tag mapping collection to\n\t\t\t * container the new name.\n\t\t\t *\n\t\t\t * @param {OC.Backbone.Model} tagId tag model that has changed\n\t\t\t */\n\t\t\t_onTagDeletedGlobally(tagId) {\n\t\t\t// also rename it in the selection, if applicable\n\t\t\t\tthis.selectedTagsCollection.remove(tagId)\n\t\t\t},\n\n\t\t\tsetFileInfo(fileInfo) {\n\t\t\t\tconst self = this\n\t\t\t\tif (!this._rendered) {\n\t\t\t\t\tthis.render()\n\t\t\t\t}\n\n\t\t\t\tif (fileInfo) {\n\t\t\t\t\tthis.selectedTagsCollection.setObjectId(fileInfo.id)\n\t\t\t\t\tthis.selectedTagsCollection.fetch({\n\t\t\t\t\t\tsuccess(collection) {\n\t\t\t\t\t\t\tcollection.fetched = true\n\n\t\t\t\t\t\t\tconst appliedTags = collection.map(modelToSelection)\n\t\t\t\t\t\t\tself._inputView.setData(appliedTags)\n\t\t\t\t\t\t\tif (appliedTags.length > 0) {\n\t\t\t\t\t\t\t\tself.show()\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t})\n\t\t\t\t}\n\n\t\t\t\tthis.hide()\n\t\t\t},\n\n\t\t\t/**\n\t\t\t * Renders this details view\n\t\t\t */\n\t\t\trender() {\n\t\t\t\tthis.$el.append(this._inputView.$el)\n\t\t\t\tthis._inputView.render()\n\t\t\t},\n\n\t\t\tisVisible() {\n\t\t\t\treturn !this.$el.hasClass('hidden')\n\t\t\t},\n\n\t\t\tshow() {\n\t\t\t\tthis.$el.removeClass('hidden')\n\t\t\t},\n\n\t\t\thide() {\n\t\t\t\tthis.$el.addClass('hidden')\n\t\t\t},\n\n\t\t\ttoggle() {\n\t\t\t\tthis.$el.toggleClass('hidden')\n\t\t\t},\n\n\t\t\topenDropdown() {\n\t\t\t\tthis.$el.find('.systemTagsInputField').select2('open')\n\t\t\t},\n\n\t\t\tremove() {\n\t\t\t\tthis._inputView.remove()\n\t\t\t},\n\t\t})\n\n\tOCA.SystemTags.SystemTagsInfoView = SystemTagsInfoView\n\n})(OCA)\n","// Imports\nimport ___CSS_LOADER_API_SOURCEMAP_IMPORT___ from \"../../../../node_modules/css-loader/dist/runtime/sourceMaps.js\";\nimport ___CSS_LOADER_API_IMPORT___ from \"../../../../node_modules/css-loader/dist/runtime/api.js\";\nvar ___CSS_LOADER_EXPORT___ = ___CSS_LOADER_API_IMPORT___(___CSS_LOADER_API_SOURCEMAP_IMPORT___);\n// Module\n___CSS_LOADER_EXPORT___.push([module.id, \"#app-content-systemtagsfilter .select2-container{width:30%;margin-left:10px}#app-sidebar .app-sidebar-header__action .tag-label{cursor:pointer;padding:13px 0;display:flex;color:var(--color-text-light);position:relative;margin-top:-20px}\", \"\",{\"version\":3,\"sources\":[\"webpack://./apps/systemtags/src/css/systemtagsfilelist.scss\"],\"names\":[],\"mappings\":\"AASA,iDACC,SAAA,CACA,gBAAA,CAGD,oDACC,cAAA,CACA,cAAA,CACA,YAAA,CACA,6BAAA,CACA,iBAAA,CACA,gBAAA\",\"sourcesContent\":[\"/*\\n * Copyright (c) 2016\\n *\\n * This file is licensed under the Affero General Public License version 3\\n * or later.\\n *\\n * See the COPYING-README file.\\n *\\n */\\n#app-content-systemtagsfilter .select2-container {\\n\\twidth: 30%;\\n\\tmargin-left: 10px;\\n}\\n\\n#app-sidebar .app-sidebar-header__action .tag-label {\\n\\tcursor: pointer;\\n\\tpadding: 13px 0;\\n\\tdisplay: flex;\\n\\tcolor: var(--color-text-light);\\n\\tposition: relative;\\n\\tmargin-top: -20px;\\n}\\n\"],\"sourceRoot\":\"\"}]);\n// Exports\nexport default ___CSS_LOADER_EXPORT___;\n","// The module cache\nvar __webpack_module_cache__ = {};\n\n// The require function\nfunction __webpack_require__(moduleId) {\n\t// Check if module is in cache\n\tvar cachedModule = __webpack_module_cache__[moduleId];\n\tif (cachedModule !== undefined) {\n\t\treturn cachedModule.exports;\n\t}\n\t// Create a new module (and put it into the cache)\n\tvar module = __webpack_module_cache__[moduleId] = {\n\t\tid: moduleId,\n\t\tloaded: false,\n\t\texports: {}\n\t};\n\n\t// Execute the module function\n\t__webpack_modules__[moduleId].call(module.exports, module, module.exports, __webpack_require__);\n\n\t// Flag the module as loaded\n\tmodule.loaded = true;\n\n\t// Return the exports of the module\n\treturn module.exports;\n}\n\n// expose the modules object (__webpack_modules__)\n__webpack_require__.m = __webpack_modules__;\n\n","__webpack_require__.amdD = function () {\n\tthrow new Error('define cannot be used indirect');\n};","__webpack_require__.amdO = {};","// getDefaultExport function for compatibility with non-harmony modules\n__webpack_require__.n = function(module) {\n\tvar getter = module && module.__esModule ?\n\t\tfunction() { return module['default']; } :\n\t\tfunction() { return module; };\n\t__webpack_require__.d(getter, { a: getter });\n\treturn getter;\n};","// define getter functions for harmony exports\n__webpack_require__.d = function(exports, definition) {\n\tfor(var key in definition) {\n\t\tif(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {\n\t\t\tObject.defineProperty(exports, key, { enumerable: true, get: definition[key] });\n\t\t}\n\t}\n};","__webpack_require__.g = (function() {\n\tif (typeof globalThis === 'object') return globalThis;\n\ttry {\n\t\treturn this || new Function('return this')();\n\t} catch (e) {\n\t\tif (typeof window === 'object') return window;\n\t}\n})();","__webpack_require__.o = function(obj, prop) { return Object.prototype.hasOwnProperty.call(obj, prop); }","// define __esModule on exports\n__webpack_require__.r = function(exports) {\n\tif(typeof Symbol !== 'undefined' && Symbol.toStringTag) {\n\t\tObject.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });\n\t}\n\tObject.defineProperty(exports, '__esModule', { value: true });\n};","__webpack_require__.nmd = function(module) {\n\tmodule.paths = [];\n\tif (!module.children) module.children = [];\n\treturn module;\n};","__webpack_require__.j = 698;","__webpack_require__.b = document.baseURI || self.location.href;\n\n// object to store loaded and loading chunks\n// undefined = chunk not loaded, null = chunk preloaded/prefetched\n// [resolve, reject, Promise] = chunk loading, 0 = chunk loaded\nvar installedChunks = {\n\t698: 0\n};\n\n// no chunk on demand loading\n\n// no prefetching\n\n// no preloaded\n\n// no HMR\n\n// no HMR manifest\n\n__webpack_require__.O.j = function(chunkId) { return installedChunks[chunkId] === 0; };\n\n// install a JSONP callback for chunk loading\nvar webpackJsonpCallback = function(parentChunkLoadingFunction, data) {\n\tvar chunkIds = data[0];\n\tvar moreModules = data[1];\n\tvar runtime = data[2];\n\t// add \"moreModules\" to the modules object,\n\t// then flag all \"chunkIds\" as loaded and fire callback\n\tvar moduleId, chunkId, i = 0;\n\tif(chunkIds.some(function(id) { return installedChunks[id] !== 0; })) {\n\t\tfor(moduleId in moreModules) {\n\t\t\tif(__webpack_require__.o(moreModules, moduleId)) {\n\t\t\t\t__webpack_require__.m[moduleId] = moreModules[moduleId];\n\t\t\t}\n\t\t}\n\t\tif(runtime) var result = runtime(__webpack_require__);\n\t}\n\tif(parentChunkLoadingFunction) parentChunkLoadingFunction(data);\n\tfor(;i < chunkIds.length; i++) {\n\t\tchunkId = chunkIds[i];\n\t\tif(__webpack_require__.o(installedChunks, chunkId) && installedChunks[chunkId]) {\n\t\t\tinstalledChunks[chunkId][0]();\n\t\t}\n\t\tinstalledChunks[chunkId] = 0;\n\t}\n\treturn __webpack_require__.O(result);\n}\n\nvar chunkLoadingGlobal = self[\"webpackChunknextcloud\"] = self[\"webpackChunknextcloud\"] || [];\nchunkLoadingGlobal.forEach(webpackJsonpCallback.bind(null, 0));\nchunkLoadingGlobal.push = webpackJsonpCallback.bind(null, chunkLoadingGlobal.push.bind(chunkLoadingGlobal));","// startup\n// Load entry module and return exports\n// This entry module depends on other loaded chunks and execution need to be delayed\nvar __webpack_exports__ = __webpack_require__.O(undefined, [874], function() { return __webpack_require__(19294); })\n__webpack_exports__ = __webpack_require__.O(__webpack_exports__);\n"],"names":["deferred","OCA","SystemTags","App","initFileList","$el","this","_fileList","FileList","id","fileActions","_createFileActions","config","Files","getFilesConfig","shown","appName","t","removeFileList","$fileList","empty","FileActions","registerDefaultActions","merge","_globalActionsInitialized","_onActionsUpdated","_","bind","on","register","OC","PERMISSION_READ","filename","context","setActiveView","silent","fileList","changeDirectory","joinPaths","$file","attr","setDefault","ev","action","registerAction","defaultAction","mime","name","destroy","off","window","addEventListener","$","e","target","extend","FilesPlugin","ignoreLists","attach","indexOf","View","systemTagsInfoView","SystemTagsInfoView","registerDetailView","Plugins","options","styleTagTransform","setAttributes","insert","domAPI","insertStyleElement","initialize","prototype","_systemTagIds","_lastUsedTags","_clientSideSort","_allowSelection","_filterField","apply","arguments","initialized","systemTagIds","$controls","find","defer","_getLastUsedTags","_initFilterField","$filterField","remove","self","ajax","type","url","generateUrl","success","response","$container","append","select2","placeholder","allowClear","multiple","toggleSelect","separator","query","_queryTagsAutocomplete","tag","initSelection","element","callback","val","trim","tagIds","split","tags","collection","fetch","each","tagId","get","isUndefined","push","toJSON","formatResult","getDescriptiveTag","formatSelection","outerHTML","sortResults","results","sort","a","b","aLastUsed","bLastUsed","Util","naturalSortCompare","escapeMarkup","m","formatNoMatches","_onTagsChanged","filterByName","term","invoke","_onUrlChanged","dir","filter","reload","trigger","Event","join","updateEmptyContent","getCurrentDirectory","length","html","toggleClass","isEmpty","getDirectoryPermissions","PERMISSION_DELETE","updateStorageStatistics","_setCurrentDir","setFiles","Deferred","resolve","_selectedFiles","_selectionSummary","clear","_currentFileModel","prop","showMask","_reloadCall","filesClient","getFilteredFiles","properties","_getWebdavProperties","_detailsView","_updateDetailsView","callBack","reloadCallback","then","status","result","unshift","call","modelToSelection","model","data","isUserAdmin","canAssign","locked","DetailFileInfoView","_rendered","className","_inputView","SystemTagsInputField","allowActions","allowCreate","isAdmin","selectedTagsCollection","map","SystemTagsMappingCollection","objectType","_onTagRenamedGlobally","_onTagDeletedGlobally","_onSelectTag","_onDeselectTag","create","changedTag","selectedTagMapping","set","setFileInfo","fileInfo","render","setObjectId","fetched","appliedTags","setData","show","hide","isVisible","hasClass","removeClass","addClass","toggle","openDropdown","___CSS_LOADER_EXPORT___","module","__webpack_module_cache__","__webpack_require__","moduleId","cachedModule","undefined","exports","loaded","__webpack_modules__","amdD","Error","amdO","O","chunkIds","fn","priority","notFulfilled","Infinity","i","fulfilled","j","Object","keys","every","key","splice","r","n","getter","__esModule","d","definition","o","defineProperty","enumerable","g","globalThis","Function","obj","hasOwnProperty","Symbol","toStringTag","value","nmd","paths","children","document","baseURI","location","href","installedChunks","chunkId","webpackJsonpCallback","parentChunkLoadingFunction","moreModules","runtime","some","chunkLoadingGlobal","forEach","__webpack_exports__"],"sourceRoot":""} \ No newline at end of file +{"version":3,"file":"systemtags-systemtags.js?v=d45463feae08fe2d6936","mappings":"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","sources":["webpack:///nextcloud/webpack/runtime/chunk loaded","webpack:///nextcloud/apps/systemtags/src/app.js","webpack:///nextcloud/apps/systemtags/src/filesplugin.js","webpack://nextcloud/./apps/systemtags/src/css/systemtagsfilelist.scss?3cf4","webpack:///nextcloud/apps/systemtags/src/systemtags.js","webpack:///nextcloud/apps/systemtags/src/systemtagsfilelist.js","webpack:///nextcloud/apps/systemtags/src/systemtagsinfoview.js","webpack:///nextcloud/apps/systemtags/src/css/systemtagsfilelist.scss","webpack:///nextcloud/webpack/bootstrap","webpack:///nextcloud/webpack/runtime/amd define","webpack:///nextcloud/webpack/runtime/amd options","webpack:///nextcloud/webpack/runtime/compat get default export","webpack:///nextcloud/webpack/runtime/define property getters","webpack:///nextcloud/webpack/runtime/global","webpack:///nextcloud/webpack/runtime/hasOwnProperty shorthand","webpack:///nextcloud/webpack/runtime/make namespace object","webpack:///nextcloud/webpack/runtime/node module decorator","webpack:///nextcloud/webpack/runtime/runtimeId","webpack:///nextcloud/webpack/runtime/jsonp chunk loading","webpack:///nextcloud/webpack/startup"],"sourcesContent":["var deferred = [];\n__webpack_require__.O = function(result, chunkIds, fn, priority) {\n\tif(chunkIds) {\n\t\tpriority = priority || 0;\n\t\tfor(var i = deferred.length; i > 0 && deferred[i - 1][2] > priority; i--) deferred[i] = deferred[i - 1];\n\t\tdeferred[i] = [chunkIds, fn, priority];\n\t\treturn;\n\t}\n\tvar notFulfilled = Infinity;\n\tfor (var i = 0; i < deferred.length; i++) {\n\t\tvar chunkIds = deferred[i][0];\n\t\tvar fn = deferred[i][1];\n\t\tvar priority = deferred[i][2];\n\t\tvar fulfilled = true;\n\t\tfor (var j = 0; j < chunkIds.length; j++) {\n\t\t\tif ((priority & 1 === 0 || notFulfilled >= priority) && Object.keys(__webpack_require__.O).every(function(key) { return __webpack_require__.O[key](chunkIds[j]); })) {\n\t\t\t\tchunkIds.splice(j--, 1);\n\t\t\t} else {\n\t\t\t\tfulfilled = false;\n\t\t\t\tif(priority < notFulfilled) notFulfilled = priority;\n\t\t\t}\n\t\t}\n\t\tif(fulfilled) {\n\t\t\tdeferred.splice(i--, 1)\n\t\t\tvar r = fn();\n\t\t\tif (r !== undefined) result = r;\n\t\t}\n\t}\n\treturn result;\n};","/**\n * Copyright (c) 2015 Vincent Petry \n *\n * @author Christoph Wurst \n * @author Daniel Calviño Sánchez \n * @author John Molakvoæ \n * @author Vincent Petry \n *\n * @license AGPL-3.0-or-later\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program. If not, see .\n *\n */\n\n(function() {\n\tif (!OCA.SystemTags) {\n\t\t/**\n\t\t * @namespace\n\t\t */\n\t\tOCA.SystemTags = {}\n\t}\n\n\tOCA.SystemTags.App = {\n\n\t\tinitFileList($el) {\n\t\t\tif (this._fileList) {\n\t\t\t\treturn this._fileList\n\t\t\t}\n\n\t\t\tconst tagsParam = (new URL(window.location.href)).searchParams.get('tags')\n\t\t\tconst initialTags = tagsParam ? tagsParam.split(',').map(parseInt) : []\n\n\t\t\tthis._fileList = new OCA.SystemTags.FileList(\n\t\t\t\t$el,\n\t\t\t\t{\n\t\t\t\t\tid: 'systemtags',\n\t\t\t\t\tfileActions: this._createFileActions(),\n\t\t\t\t\tconfig: OCA.Files.App.getFilesConfig(),\n\t\t\t\t\t// The file list is created when a \"show\" event is handled,\n\t\t\t\t\t// so it should be marked as \"shown\" like it would have been\n\t\t\t\t\t// done if handling the event with the file list already\n\t\t\t\t\t// created.\n\t\t\t\t\tshown: true,\n\t\t\t\t\tsystemTagIds: initialTags\n\t\t\t\t}\n\t\t\t)\n\n\t\t\tthis._fileList.appName = t('systemtags', 'Tags')\n\t\t\treturn this._fileList\n\t\t},\n\n\t\tremoveFileList() {\n\t\t\tif (this._fileList) {\n\t\t\t\tthis._fileList.$fileList.empty()\n\t\t\t}\n\t\t},\n\n\t\t_createFileActions() {\n\t\t\t// inherit file actions from the files app\n\t\t\tconst fileActions = new OCA.Files.FileActions()\n\t\t\t// note: not merging the legacy actions because legacy apps are not\n\t\t\t// compatible with the sharing overview and need to be adapted first\n\t\t\tfileActions.registerDefaultActions()\n\t\t\tfileActions.merge(OCA.Files.fileActions)\n\n\t\t\tif (!this._globalActionsInitialized) {\n\t\t\t\t// in case actions are registered later\n\t\t\t\tthis._onActionsUpdated = _.bind(this._onActionsUpdated, this)\n\t\t\t\tOCA.Files.fileActions.on('setDefault.app-systemtags', this._onActionsUpdated)\n\t\t\t\tOCA.Files.fileActions.on('registerAction.app-systemtags', this._onActionsUpdated)\n\t\t\t\tthis._globalActionsInitialized = true\n\t\t\t}\n\n\t\t\t// when the user clicks on a folder, redirect to the corresponding\n\t\t\t// folder in the files app instead of opening it directly\n\t\t\tfileActions.register('dir', 'Open', OC.PERMISSION_READ, '', function(filename, context) {\n\t\t\t\tOCA.Files.App.setActiveView('files', { silent: true })\n\t\t\t\tOCA.Files.App.fileList.changeDirectory(OC.joinPaths(context.$file.attr('data-path'), filename), true, true)\n\t\t\t})\n\t\t\tfileActions.setDefault('dir', 'Open')\n\t\t\treturn fileActions\n\t\t},\n\n\t\t_onActionsUpdated(ev) {\n\t\t\tif (!this._fileList) {\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif (ev.action) {\n\t\t\t\tthis._fileList.fileActions.registerAction(ev.action)\n\t\t\t} else if (ev.defaultAction) {\n\t\t\t\tthis._fileList.fileActions.setDefault(\n\t\t\t\t\tev.defaultAction.mime,\n\t\t\t\t\tev.defaultAction.name\n\t\t\t\t)\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Destroy the app\n\t\t */\n\t\tdestroy() {\n\t\t\tOCA.Files.fileActions.off('setDefault.app-systemtags', this._onActionsUpdated)\n\t\t\tOCA.Files.fileActions.off('registerAction.app-systemtags', this._onActionsUpdated)\n\t\t\tthis.removeFileList()\n\t\t\tthis._fileList = null\n\t\t\tdelete this._globalActionsInitialized\n\t\t},\n\t}\n\n})()\n\nwindow.addEventListener('DOMContentLoaded', function() {\n\t$('#app-content-systemtagsfilter').on('show', function(e) {\n\t\tOCA.SystemTags.App.initFileList($(e.target))\n\t})\n\t$('#app-content-systemtagsfilter').on('hide', function() {\n\t\tOCA.SystemTags.App.removeFileList()\n\t})\n})\n","/**\n * Copyright (c) 2015 Vincent Petry \n *\n * @author Joas Schilling \n * @author John Molakvoæ \n * @author Vincent Petry \n *\n * @license AGPL-3.0-or-later\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program. If not, see .\n *\n */\n\n(function() {\n\tOCA.SystemTags = _.extend({}, OCA.SystemTags)\n\tif (!OCA.SystemTags) {\n\t\t/**\n\t\t * @namespace\n\t\t */\n\t\tOCA.SystemTags = {}\n\t}\n\n\t/**\n\t * @namespace\n\t */\n\tOCA.SystemTags.FilesPlugin = {\n\t\tignoreLists: [\n\t\t\t'trashbin',\n\t\t\t'files.public',\n\t\t],\n\n\t\tattach(fileList) {\n\t\t\tif (this.ignoreLists.indexOf(fileList.id) >= 0) {\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\t// only create and attach once\n\t\t\t// FIXME: this should likely be done on a different code path now\n\t\t\t// for the sidebar to only have it registered once\n\t\t\tif (!OCA.SystemTags.View) {\n\t\t\t\tconst systemTagsInfoView = new OCA.SystemTags.SystemTagsInfoView()\n\t\t\t\tfileList.registerDetailView(systemTagsInfoView)\n\t\t\t\tOCA.SystemTags.View = systemTagsInfoView\n\t\t\t}\n\t\t},\n\t}\n\n})()\n\nOC.Plugins.register('OCA.Files.FileList', OCA.SystemTags.FilesPlugin)\n","\n import API from \"!../../../../node_modules/style-loader/dist/runtime/injectStylesIntoStyleTag.js\";\n import domAPI from \"!../../../../node_modules/style-loader/dist/runtime/styleDomAPI.js\";\n import insertFn from \"!../../../../node_modules/style-loader/dist/runtime/insertBySelector.js\";\n import setAttributes from \"!../../../../node_modules/style-loader/dist/runtime/setAttributesWithoutAttributes.js\";\n import insertStyleElement from \"!../../../../node_modules/style-loader/dist/runtime/insertStyleElement.js\";\n import styleTagTransformFn from \"!../../../../node_modules/style-loader/dist/runtime/styleTagTransform.js\";\n import content, * as namedExport from \"!!../../../../node_modules/css-loader/dist/cjs.js!../../../../node_modules/sass-loader/dist/cjs.js!./systemtagsfilelist.scss\";\n \n \n\nvar options = {};\n\noptions.styleTagTransform = styleTagTransformFn;\noptions.setAttributes = setAttributes;\n\n options.insert = insertFn.bind(null, \"head\");\n \noptions.domAPI = domAPI;\noptions.insertStyleElement = insertStyleElement;\n\nvar update = API(content, options);\n\n\n\nexport * from \"!!../../../../node_modules/css-loader/dist/cjs.js!../../../../node_modules/sass-loader/dist/cjs.js!./systemtagsfilelist.scss\";\n export default content && content.locals ? content.locals : undefined;\n","/**\n * @copyright Copyright (c) 2016 Roeland Jago Douma \n *\n * @author John Molakvoæ \n * @author Roeland Jago Douma \n *\n * @license AGPL-3.0-or-later\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program. If not, see .\n *\n */\n\nimport './app'\nimport './systemtagsfilelist'\nimport './filesplugin'\nimport './systemtagsinfoview'\nimport './css/systemtagsfilelist.scss'\n\nwindow.OCA.SystemTags = OCA.SystemTags\n","/**\n * Copyright (c) 2016 Vincent Petry \n *\n * @author Joas Schilling \n * @author John Molakvoæ \n * @author Vincent Petry \n *\n * @license AGPL-3.0-or-later\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program. If not, see .\n *\n */\n\n(function() {\n\t/**\n\t * @class OCA.SystemTags.FileList\n\t * @augments OCA.Files.FileList\n\t *\n\t * @classdesc SystemTags file list.\n\t * Contains a list of files filtered by system tags.\n\t *\n\t * @param {object} $el container element with existing markup for the #controls and a table\n\t * @param {Array} [options] map of options, see other parameters\n\t * @param {Array.} [options.systemTagIds] array of system tag ids to\n\t * filter by\n\t */\n\tconst FileList = function($el, options) {\n\t\tthis.initialize($el, options)\n\t}\n\tFileList.prototype = _.extend(\n\t\t{},\n\t\tOCA.Files.FileList.prototype,\n\t\t/** @lends OCA.SystemTags.FileList.prototype */ {\n\t\t\tid: 'systemtagsfilter',\n\t\t\tappName: t('systemtags', 'Tagged files'),\n\n\t\t\t/**\n\t\t\t * Array of system tag ids to filter by\n\t\t\t *\n\t\t\t * @type {Array.}\n\t\t\t */\n\t\t\t_systemTagIds: [],\n\t\t\t_lastUsedTags: [],\n\n\t\t\t_clientSideSort: true,\n\t\t\t_allowSelection: false,\n\n\t\t\t_filterField: null,\n\n\t\t\t/**\n\t\t\t * @private\n\t\t\t * @param {object} $el container element\n\t\t\t * @param {object} [options] map of options, see other parameters\n\t\t\t */\n\t\t\tinitialize($el, options) {\n\t\t\t\tOCA.Files.FileList.prototype.initialize.apply(this, arguments)\n\t\t\t\tif (this.initialized) {\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\tif (options && options.systemTagIds) {\n\t\t\t\t\tthis._systemTagIds = options.systemTagIds\n\t\t\t\t}\n\n\t\t\t\tOC.Plugins.attach('OCA.SystemTags.FileList', this)\n\n\t\t\t\tconst $controls = this.$el.find('#controls').empty()\n\n\t\t\t\t_.defer(_.bind(this._getLastUsedTags, this))\n\t\t\t\tthis._initFilterField($controls)\n\t\t\t},\n\n\t\t\tdestroy() {\n\t\t\t\tthis.$filterField.remove()\n\n\t\t\t\tOCA.Files.FileList.prototype.destroy.apply(this, arguments)\n\t\t\t},\n\n\t\t\t_getLastUsedTags() {\n\t\t\t\tconst self = this\n\t\t\t\t$.ajax({\n\t\t\t\t\ttype: 'GET',\n\t\t\t\t\turl: OC.generateUrl('/apps/systemtags/lastused'),\n\t\t\t\t\tsuccess(response) {\n\t\t\t\t\t\tself._lastUsedTags = response\n\t\t\t\t\t},\n\t\t\t\t})\n\t\t\t},\n\n\t\t\t_initFilterField($container) {\n\t\t\t\tconst self = this\n\t\t\t\tthis.$filterField = $('')\n\t\t\t\tthis.$filterField.val(this._systemTagIds.join(','))\n\t\t\t\t$container.append(this.$filterField)\n\t\t\t\tthis.$filterField.select2({\n\t\t\t\t\tplaceholder: t('systemtags', 'Select tags to filter by'),\n\t\t\t\t\tallowClear: false,\n\t\t\t\t\tmultiple: true,\n\t\t\t\t\ttoggleSelect: true,\n\t\t\t\t\tseparator: ',',\n\t\t\t\t\tquery: _.bind(this._queryTagsAutocomplete, this),\n\n\t\t\t\t\tid(tag) {\n\t\t\t\t\t\treturn tag.id\n\t\t\t\t\t},\n\n\t\t\t\t\tinitSelection(element, callback) {\n\t\t\t\t\t\tconst val = $(element)\n\t\t\t\t\t\t\t.val()\n\t\t\t\t\t\t\t.trim()\n\t\t\t\t\t\tif (val) {\n\t\t\t\t\t\t\tconst tagIds = val.split(',')\n\t\t\t\t\t\t\tconst tags = []\n\n\t\t\t\t\t\t\tOC.SystemTags.collection.fetch({\n\t\t\t\t\t\t\t\tsuccess() {\n\t\t\t\t\t\t\t\t\t_.each(tagIds, function(tagId) {\n\t\t\t\t\t\t\t\t\t\tconst tag = OC.SystemTags.collection.get(\n\t\t\t\t\t\t\t\t\t\t\ttagId\n\t\t\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t\t\t\tif (!_.isUndefined(tag)) {\n\t\t\t\t\t\t\t\t\t\t\ttags.push(tag.toJSON())\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\t\t\tcallback(tags)\n\t\t\t\t\t\t\t\t\tself._onTagsChanged({ target: element })\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t})\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t// eslint-disable-next-line n/no-callback-literal\n\t\t\t\t\t\t\tcallback([])\n\t\t\t\t\t\t}\n\t\t\t\t\t},\n\n\t\t\t\t\tformatResult(tag) {\n\t\t\t\t\t\treturn OC.SystemTags.getDescriptiveTag(tag)\n\t\t\t\t\t},\n\n\t\t\t\t\tformatSelection(tag) {\n\t\t\t\t\t\treturn OC.SystemTags.getDescriptiveTag(tag)[0]\n\t\t\t\t\t\t\t.outerHTML\n\t\t\t\t\t},\n\n\t\t\t\t\tsortResults(results) {\n\t\t\t\t\t\tresults.sort(function(a, b) {\n\t\t\t\t\t\t\tconst aLastUsed = self._lastUsedTags.indexOf(a.id)\n\t\t\t\t\t\t\tconst bLastUsed = self._lastUsedTags.indexOf(b.id)\n\n\t\t\t\t\t\t\tif (aLastUsed !== bLastUsed) {\n\t\t\t\t\t\t\t\tif (bLastUsed === -1) {\n\t\t\t\t\t\t\t\t\treturn -1\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tif (aLastUsed === -1) {\n\t\t\t\t\t\t\t\t\treturn 1\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\treturn aLastUsed < bLastUsed ? -1 : 1\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t// Both not found\n\t\t\t\t\t\t\treturn OC.Util.naturalSortCompare(a.name, b.name)\n\t\t\t\t\t\t})\n\t\t\t\t\t\treturn results\n\t\t\t\t\t},\n\n\t\t\t\t\tescapeMarkup(m) {\n\t\t\t\t\t\t// prevent double markup escape\n\t\t\t\t\t\treturn m\n\t\t\t\t\t},\n\t\t\t\t\tformatNoMatches() {\n\t\t\t\t\t\treturn t('systemtags', 'No tags found')\n\t\t\t\t\t},\n\t\t\t\t})\n\t\t\t\tthis.$filterField.on(\n\t\t\t\t\t'change',\n\t\t\t\t\t_.bind(this._onTagsChanged, this)\n\t\t\t\t)\n\t\t\t\treturn this.$filterField\n\t\t\t},\n\n\t\t\t/**\n\t\t\t * Autocomplete function for dropdown results\n\t\t\t *\n\t\t\t * @param {object} query select2 query object\n\t\t\t */\n\t\t\t_queryTagsAutocomplete(query) {\n\t\t\t\tOC.SystemTags.collection.fetch({\n\t\t\t\t\tsuccess() {\n\t\t\t\t\t\tconst results = OC.SystemTags.collection.filterByName(\n\t\t\t\t\t\t\tquery.term\n\t\t\t\t\t\t)\n\n\t\t\t\t\t\tquery.callback({\n\t\t\t\t\t\t\tresults: _.invoke(results, 'toJSON'),\n\t\t\t\t\t\t})\n\t\t\t\t\t},\n\t\t\t\t})\n\t\t\t},\n\n\t\t\t/**\n\t\t\t * Event handler for when the URL changed\n\t\t\t *\n\t\t\t * @param {Event} e the urlchanged event\n\t\t\t */\n\t\t\t_onUrlChanged(e) {\n\t\t\t\tif (e.dir) {\n\t\t\t\t\tconst tags = _.filter(e.dir.split('/'), function(val) {\n\t\t\t\t\t\treturn val.trim() !== ''\n\t\t\t\t\t})\n\t\t\t\t\tthis.$filterField.select2('val', tags || [])\n\t\t\t\t\tthis._systemTagIds = tags\n\t\t\t\t\tthis.reload()\n\t\t\t\t}\n\t\t\t},\n\n\t\t\t_onTagsChanged(ev) {\n\t\t\t\tconst val = $(ev.target)\n\t\t\t\t\t.val()\n\t\t\t\t\t.trim()\n\t\t\t\tif (val !== '') {\n\t\t\t\t\tthis._systemTagIds = val.split(',')\n\t\t\t\t} else {\n\t\t\t\t\tthis._systemTagIds = []\n\t\t\t\t}\n\n\t\t\t\tthis.$el.trigger(\n\t\t\t\t\t$.Event('changeDirectory', {\n\t\t\t\t\t\tdir: this._systemTagIds.join('/'),\n\t\t\t\t\t})\n\t\t\t\t)\n\t\t\t\tthis.reload()\n\t\t\t},\n\n\t\t\tupdateEmptyContent() {\n\t\t\t\tconst dir = this.getCurrentDirectory()\n\t\t\t\tif (dir === '/') {\n\t\t\t\t\t// root has special permissions\n\t\t\t\t\tif (!this._systemTagIds.length) {\n\t\t\t\t\t\t// no tags selected\n\t\t\t\t\t\tthis.$el\n\t\t\t\t\t\t\t.find('#emptycontent')\n\t\t\t\t\t\t\t.html(\n\t\t\t\t\t\t\t\t'
'\n\t\t\t\t\t\t\t\t\t+ '

'\n\t\t\t\t\t\t\t\t\t+ t(\n\t\t\t\t\t\t\t\t\t\t'systemtags',\n\t\t\t\t\t\t\t\t\t\t'Please select tags to filter by'\n\t\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t\t\t+ '

'\n\t\t\t\t\t\t\t)\n\t\t\t\t\t} else {\n\t\t\t\t\t\t// tags selected but no results\n\t\t\t\t\t\tthis.$el\n\t\t\t\t\t\t\t.find('#emptycontent')\n\t\t\t\t\t\t\t.html(\n\t\t\t\t\t\t\t\t'
'\n\t\t\t\t\t\t\t\t\t+ '

'\n\t\t\t\t\t\t\t\t\t+ t(\n\t\t\t\t\t\t\t\t\t\t'systemtags',\n\t\t\t\t\t\t\t\t\t\t'No files found for the selected tags'\n\t\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t\t\t+ '

'\n\t\t\t\t\t\t\t)\n\t\t\t\t\t}\n\t\t\t\t\tthis.$el\n\t\t\t\t\t\t.find('#emptycontent')\n\t\t\t\t\t\t.toggleClass('hidden', !this.isEmpty)\n\t\t\t\t\tthis.$el\n\t\t\t\t\t\t.find('#filestable thead th')\n\t\t\t\t\t\t.toggleClass('hidden', this.isEmpty)\n\t\t\t\t} else {\n\t\t\t\t\tOCA.Files.FileList.prototype.updateEmptyContent.apply(\n\t\t\t\t\t\tthis,\n\t\t\t\t\t\targuments\n\t\t\t\t\t)\n\t\t\t\t}\n\t\t\t},\n\n\t\t\tgetDirectoryPermissions() {\n\t\t\t\treturn OC.PERMISSION_READ | OC.PERMISSION_DELETE\n\t\t\t},\n\n\t\t\tupdateStorageStatistics() {\n\t\t\t\t// no op because it doesn't have\n\t\t\t\t// storage info like free space / used space\n\t\t\t},\n\n\t\t\treload() {\n\t\t\t\t// there is only root\n\t\t\t\tthis._setCurrentDir('/', false)\n\n\t\t\t\tif (!this._systemTagIds.length) {\n\t\t\t\t\t// don't reload\n\t\t\t\t\tthis.updateEmptyContent()\n\t\t\t\t\tthis.setFiles([])\n\t\t\t\t\treturn $.Deferred().resolve()\n\t\t\t\t}\n\n\t\t\t\tthis._selectedFiles = {}\n\t\t\t\tthis._selectionSummary.clear()\n\t\t\t\tif (this._currentFileModel) {\n\t\t\t\t\tthis._currentFileModel.off()\n\t\t\t\t}\n\t\t\t\tthis._currentFileModel = null\n\t\t\t\tthis.$el.find('.select-all').prop('checked', false)\n\t\t\t\tthis.showMask()\n\t\t\t\tthis._reloadCall = this.filesClient.getFilteredFiles(\n\t\t\t\t\t{\n\t\t\t\t\t\tsystemTagIds: this._systemTagIds,\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tproperties: this._getWebdavProperties(),\n\t\t\t\t\t}\n\t\t\t\t)\n\t\t\t\tif (this._detailsView) {\n\t\t\t\t\t// close sidebar\n\t\t\t\t\tthis._updateDetailsView(null)\n\t\t\t\t}\n\t\t\t\tconst callBack = this.reloadCallback.bind(this)\n\t\t\t\treturn this._reloadCall.then(callBack, callBack)\n\t\t\t},\n\n\t\t\treloadCallback(status, result) {\n\t\t\t\tif (result) {\n\t\t\t\t\t// prepend empty dir info because original handler\n\t\t\t\t\tresult.unshift({})\n\t\t\t\t}\n\n\t\t\t\treturn OCA.Files.FileList.prototype.reloadCallback.call(\n\t\t\t\t\tthis,\n\t\t\t\t\tstatus,\n\t\t\t\t\tresult\n\t\t\t\t)\n\t\t\t},\n\t\t}\n\t)\n\n\tOCA.SystemTags.FileList = FileList\n})()\n","/**\n * Copyright (c) 2015\n *\n * @author Daniel Calviño Sánchez \n * @author Joas Schilling \n * @author John Molakvoæ \n * @author Julius Härtl \n * @author Vincent Petry \n *\n * @license AGPL-3.0-or-later\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program. If not, see .\n *\n */\n\n(function(OCA) {\n\n\t/**\n\t * @param {any} model -\n\t */\n\tfunction modelToSelection(model) {\n\t\tconst data = model.toJSON()\n\t\tif (!OC.isUserAdmin() && !data.canAssign) {\n\t\t\tdata.locked = true\n\t\t}\n\t\treturn data\n\t}\n\n\t/**\n\t * @class OCA.SystemTags.SystemTagsInfoView\n\t * @classdesc\n\t *\n\t * Displays a file's system tags\n\t *\n\t */\n\tconst SystemTagsInfoView = OCA.Files.DetailFileInfoView.extend(\n\t\t/** @lends OCA.SystemTags.SystemTagsInfoView.prototype */ {\n\n\t\t\t_rendered: false,\n\n\t\t\tclassName: 'systemTagsInfoView',\n\t\t\tname: 'systemTags',\n\n\t\t\t/* required by the new files sidebar to check if the view is unique */\n\t\t\tid: 'systemTagsInfoView',\n\n\t\t\t/**\n\t\t\t * @type {OC.SystemTags.SystemTagsInputField}\n\t\t\t */\n\t\t\t_inputView: null,\n\n\t\t\tinitialize(options) {\n\t\t\t\tconst self = this\n\t\t\t\toptions = options || {}\n\n\t\t\t\tthis._inputView = new OC.SystemTags.SystemTagsInputField({\n\t\t\t\t\tmultiple: true,\n\t\t\t\t\tallowActions: true,\n\t\t\t\t\tallowCreate: true,\n\t\t\t\t\tisAdmin: OC.isUserAdmin(),\n\t\t\t\t\tinitSelection(element, callback) {\n\t\t\t\t\t\tcallback(self.selectedTagsCollection.map(modelToSelection))\n\t\t\t\t\t},\n\t\t\t\t})\n\n\t\t\t\tthis.selectedTagsCollection = new OC.SystemTags.SystemTagsMappingCollection([], { objectType: 'files' })\n\n\t\t\t\tthis._inputView.collection.on('change:name', this._onTagRenamedGlobally, this)\n\t\t\t\tthis._inputView.collection.on('remove', this._onTagDeletedGlobally, this)\n\n\t\t\t\tthis._inputView.on('select', this._onSelectTag, this)\n\t\t\t\tthis._inputView.on('deselect', this._onDeselectTag, this)\n\t\t\t},\n\n\t\t\t/**\n\t\t\t * Event handler whenever a tag was selected\n\t\t\t *\n\t\t\t * @param {object} tag the tag to create\n\t\t\t */\n\t\t\t_onSelectTag(tag) {\n\t\t\t// create a mapping entry for this tag\n\t\t\t\tthis.selectedTagsCollection.create(tag.toJSON())\n\t\t\t},\n\n\t\t\t/**\n\t\t\t * Event handler whenever a tag gets deselected.\n\t\t\t * Removes the selected tag from the mapping collection.\n\t\t\t *\n\t\t\t * @param {string} tagId tag id\n\t\t\t */\n\t\t\t_onDeselectTag(tagId) {\n\t\t\t\tthis.selectedTagsCollection.get(tagId).destroy()\n\t\t\t},\n\n\t\t\t/**\n\t\t\t * Event handler whenever a tag was renamed globally.\n\t\t\t *\n\t\t\t * This will automatically adjust the tag mapping collection to\n\t\t\t * container the new name.\n\t\t\t *\n\t\t\t * @param {OC.Backbone.Model} changedTag tag model that has changed\n\t\t\t */\n\t\t\t_onTagRenamedGlobally(changedTag) {\n\t\t\t// also rename it in the selection, if applicable\n\t\t\t\tconst selectedTagMapping = this.selectedTagsCollection.get(changedTag.id)\n\t\t\t\tif (selectedTagMapping) {\n\t\t\t\t\tselectedTagMapping.set(changedTag.toJSON())\n\t\t\t\t}\n\t\t\t},\n\n\t\t\t/**\n\t\t\t * Event handler whenever a tag was deleted globally.\n\t\t\t *\n\t\t\t * This will automatically adjust the tag mapping collection to\n\t\t\t * container the new name.\n\t\t\t *\n\t\t\t * @param {OC.Backbone.Model} tagId tag model that has changed\n\t\t\t */\n\t\t\t_onTagDeletedGlobally(tagId) {\n\t\t\t// also rename it in the selection, if applicable\n\t\t\t\tthis.selectedTagsCollection.remove(tagId)\n\t\t\t},\n\n\t\t\tsetFileInfo(fileInfo) {\n\t\t\t\tconst self = this\n\t\t\t\tif (!this._rendered) {\n\t\t\t\t\tthis.render()\n\t\t\t\t}\n\n\t\t\t\tif (fileInfo) {\n\t\t\t\t\tthis.selectedTagsCollection.setObjectId(fileInfo.id)\n\t\t\t\t\tthis.selectedTagsCollection.fetch({\n\t\t\t\t\t\tsuccess(collection) {\n\t\t\t\t\t\t\tcollection.fetched = true\n\n\t\t\t\t\t\t\tconst appliedTags = collection.map(modelToSelection)\n\t\t\t\t\t\t\tself._inputView.setData(appliedTags)\n\t\t\t\t\t\t\tif (appliedTags.length > 0) {\n\t\t\t\t\t\t\t\tself.show()\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t})\n\t\t\t\t}\n\n\t\t\t\tthis.hide()\n\t\t\t},\n\n\t\t\t/**\n\t\t\t * Renders this details view\n\t\t\t */\n\t\t\trender() {\n\t\t\t\tthis.$el.append(this._inputView.$el)\n\t\t\t\tthis._inputView.render()\n\t\t\t},\n\n\t\t\tisVisible() {\n\t\t\t\treturn !this.$el.hasClass('hidden')\n\t\t\t},\n\n\t\t\tshow() {\n\t\t\t\tthis.$el.removeClass('hidden')\n\t\t\t},\n\n\t\t\thide() {\n\t\t\t\tthis.$el.addClass('hidden')\n\t\t\t},\n\n\t\t\ttoggle() {\n\t\t\t\tthis.$el.toggleClass('hidden')\n\t\t\t},\n\n\t\t\topenDropdown() {\n\t\t\t\tthis.$el.find('.systemTagsInputField').select2('open')\n\t\t\t},\n\n\t\t\tremove() {\n\t\t\t\tthis._inputView.remove()\n\t\t\t},\n\t\t})\n\n\tOCA.SystemTags.SystemTagsInfoView = SystemTagsInfoView\n\n})(OCA)\n","// Imports\nimport ___CSS_LOADER_API_SOURCEMAP_IMPORT___ from \"../../../../node_modules/css-loader/dist/runtime/sourceMaps.js\";\nimport ___CSS_LOADER_API_IMPORT___ from \"../../../../node_modules/css-loader/dist/runtime/api.js\";\nvar ___CSS_LOADER_EXPORT___ = ___CSS_LOADER_API_IMPORT___(___CSS_LOADER_API_SOURCEMAP_IMPORT___);\n// Module\n___CSS_LOADER_EXPORT___.push([module.id, \"#app-content-systemtagsfilter .select2-container{width:30%;margin-left:10px}#app-sidebar .app-sidebar-header__action .tag-label{cursor:pointer;padding:13px 0;display:flex;color:var(--color-text-light);position:relative;margin-top:-20px}\", \"\",{\"version\":3,\"sources\":[\"webpack://./apps/systemtags/src/css/systemtagsfilelist.scss\"],\"names\":[],\"mappings\":\"AASA,iDACC,SAAA,CACA,gBAAA,CAGD,oDACC,cAAA,CACA,cAAA,CACA,YAAA,CACA,6BAAA,CACA,iBAAA,CACA,gBAAA\",\"sourcesContent\":[\"/*\\n * Copyright (c) 2016\\n *\\n * This file is licensed under the Affero General Public License version 3\\n * or later.\\n *\\n * See the COPYING-README file.\\n *\\n */\\n#app-content-systemtagsfilter .select2-container {\\n\\twidth: 30%;\\n\\tmargin-left: 10px;\\n}\\n\\n#app-sidebar .app-sidebar-header__action .tag-label {\\n\\tcursor: pointer;\\n\\tpadding: 13px 0;\\n\\tdisplay: flex;\\n\\tcolor: var(--color-text-light);\\n\\tposition: relative;\\n\\tmargin-top: -20px;\\n}\\n\"],\"sourceRoot\":\"\"}]);\n// Exports\nexport default ___CSS_LOADER_EXPORT___;\n","// The module cache\nvar __webpack_module_cache__ = {};\n\n// The require function\nfunction __webpack_require__(moduleId) {\n\t// Check if module is in cache\n\tvar cachedModule = __webpack_module_cache__[moduleId];\n\tif (cachedModule !== undefined) {\n\t\treturn cachedModule.exports;\n\t}\n\t// Create a new module (and put it into the cache)\n\tvar module = __webpack_module_cache__[moduleId] = {\n\t\tid: moduleId,\n\t\tloaded: false,\n\t\texports: {}\n\t};\n\n\t// Execute the module function\n\t__webpack_modules__[moduleId].call(module.exports, module, module.exports, __webpack_require__);\n\n\t// Flag the module as loaded\n\tmodule.loaded = true;\n\n\t// Return the exports of the module\n\treturn module.exports;\n}\n\n// expose the modules object (__webpack_modules__)\n__webpack_require__.m = __webpack_modules__;\n\n","__webpack_require__.amdD = function () {\n\tthrow new Error('define cannot be used indirect');\n};","__webpack_require__.amdO = {};","// getDefaultExport function for compatibility with non-harmony modules\n__webpack_require__.n = function(module) {\n\tvar getter = module && module.__esModule ?\n\t\tfunction() { return module['default']; } :\n\t\tfunction() { return module; };\n\t__webpack_require__.d(getter, { a: getter });\n\treturn getter;\n};","// define getter functions for harmony exports\n__webpack_require__.d = function(exports, definition) {\n\tfor(var key in definition) {\n\t\tif(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {\n\t\t\tObject.defineProperty(exports, key, { enumerable: true, get: definition[key] });\n\t\t}\n\t}\n};","__webpack_require__.g = (function() {\n\tif (typeof globalThis === 'object') return globalThis;\n\ttry {\n\t\treturn this || new Function('return this')();\n\t} catch (e) {\n\t\tif (typeof window === 'object') return window;\n\t}\n})();","__webpack_require__.o = function(obj, prop) { return Object.prototype.hasOwnProperty.call(obj, prop); }","// define __esModule on exports\n__webpack_require__.r = function(exports) {\n\tif(typeof Symbol !== 'undefined' && Symbol.toStringTag) {\n\t\tObject.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });\n\t}\n\tObject.defineProperty(exports, '__esModule', { value: true });\n};","__webpack_require__.nmd = function(module) {\n\tmodule.paths = [];\n\tif (!module.children) module.children = [];\n\treturn module;\n};","__webpack_require__.j = 698;","__webpack_require__.b = document.baseURI || self.location.href;\n\n// object to store loaded and loading chunks\n// undefined = chunk not loaded, null = chunk preloaded/prefetched\n// [resolve, reject, Promise] = chunk loading, 0 = chunk loaded\nvar installedChunks = {\n\t698: 0\n};\n\n// no chunk on demand loading\n\n// no prefetching\n\n// no preloaded\n\n// no HMR\n\n// no HMR manifest\n\n__webpack_require__.O.j = function(chunkId) { return installedChunks[chunkId] === 0; };\n\n// install a JSONP callback for chunk loading\nvar webpackJsonpCallback = function(parentChunkLoadingFunction, data) {\n\tvar chunkIds = data[0];\n\tvar moreModules = data[1];\n\tvar runtime = data[2];\n\t// add \"moreModules\" to the modules object,\n\t// then flag all \"chunkIds\" as loaded and fire callback\n\tvar moduleId, chunkId, i = 0;\n\tif(chunkIds.some(function(id) { return installedChunks[id] !== 0; })) {\n\t\tfor(moduleId in moreModules) {\n\t\t\tif(__webpack_require__.o(moreModules, moduleId)) {\n\t\t\t\t__webpack_require__.m[moduleId] = moreModules[moduleId];\n\t\t\t}\n\t\t}\n\t\tif(runtime) var result = runtime(__webpack_require__);\n\t}\n\tif(parentChunkLoadingFunction) parentChunkLoadingFunction(data);\n\tfor(;i < chunkIds.length; i++) {\n\t\tchunkId = chunkIds[i];\n\t\tif(__webpack_require__.o(installedChunks, chunkId) && installedChunks[chunkId]) {\n\t\t\tinstalledChunks[chunkId][0]();\n\t\t}\n\t\tinstalledChunks[chunkId] = 0;\n\t}\n\treturn __webpack_require__.O(result);\n}\n\nvar chunkLoadingGlobal = self[\"webpackChunknextcloud\"] = self[\"webpackChunknextcloud\"] || [];\nchunkLoadingGlobal.forEach(webpackJsonpCallback.bind(null, 0));\nchunkLoadingGlobal.push = webpackJsonpCallback.bind(null, chunkLoadingGlobal.push.bind(chunkLoadingGlobal));","// startup\n// Load entry module and return exports\n// This entry module depends on other loaded chunks and execution need to be delayed\nvar __webpack_exports__ = __webpack_require__.O(undefined, [874], function() { return __webpack_require__(19294); })\n__webpack_exports__ = __webpack_require__.O(__webpack_exports__);\n"],"names":["deferred","OCA","SystemTags","App","initFileList","$el","this","_fileList","tagsParam","URL","window","location","href","searchParams","get","initialTags","split","map","parseInt","FileList","id","fileActions","_createFileActions","config","Files","getFilesConfig","shown","systemTagIds","appName","t","removeFileList","$fileList","empty","FileActions","registerDefaultActions","merge","_globalActionsInitialized","_onActionsUpdated","_","bind","on","register","OC","PERMISSION_READ","filename","context","setActiveView","silent","fileList","changeDirectory","joinPaths","$file","attr","setDefault","ev","action","registerAction","defaultAction","mime","name","destroy","off","addEventListener","$","e","target","extend","FilesPlugin","ignoreLists","attach","indexOf","View","systemTagsInfoView","SystemTagsInfoView","registerDetailView","Plugins","options","styleTagTransform","setAttributes","insert","domAPI","insertStyleElement","initialize","prototype","_systemTagIds","_lastUsedTags","_clientSideSort","_allowSelection","_filterField","apply","arguments","initialized","$controls","find","defer","_getLastUsedTags","_initFilterField","$filterField","remove","self","ajax","type","url","generateUrl","success","response","$container","val","join","append","select2","placeholder","allowClear","multiple","toggleSelect","separator","query","_queryTagsAutocomplete","tag","initSelection","element","callback","trim","tagIds","tags","collection","fetch","each","tagId","isUndefined","push","toJSON","_onTagsChanged","formatResult","getDescriptiveTag","formatSelection","outerHTML","sortResults","results","sort","a","b","aLastUsed","bLastUsed","Util","naturalSortCompare","escapeMarkup","m","formatNoMatches","filterByName","term","invoke","_onUrlChanged","dir","filter","reload","trigger","Event","updateEmptyContent","getCurrentDirectory","length","html","toggleClass","isEmpty","getDirectoryPermissions","PERMISSION_DELETE","updateStorageStatistics","_setCurrentDir","setFiles","Deferred","resolve","_selectedFiles","_selectionSummary","clear","_currentFileModel","prop","showMask","_reloadCall","filesClient","getFilteredFiles","properties","_getWebdavProperties","_detailsView","_updateDetailsView","callBack","reloadCallback","then","status","result","unshift","call","modelToSelection","model","data","isUserAdmin","canAssign","locked","DetailFileInfoView","_rendered","className","_inputView","SystemTagsInputField","allowActions","allowCreate","isAdmin","selectedTagsCollection","SystemTagsMappingCollection","objectType","_onTagRenamedGlobally","_onTagDeletedGlobally","_onSelectTag","_onDeselectTag","create","changedTag","selectedTagMapping","set","setFileInfo","fileInfo","render","setObjectId","fetched","appliedTags","setData","show","hide","isVisible","hasClass","removeClass","addClass","toggle","openDropdown","___CSS_LOADER_EXPORT___","module","__webpack_module_cache__","__webpack_require__","moduleId","cachedModule","undefined","exports","loaded","__webpack_modules__","amdD","Error","amdO","O","chunkIds","fn","priority","notFulfilled","Infinity","i","fulfilled","j","Object","keys","every","key","splice","r","n","getter","__esModule","d","definition","o","defineProperty","enumerable","g","globalThis","Function","obj","hasOwnProperty","Symbol","toStringTag","value","nmd","paths","children","document","baseURI","installedChunks","chunkId","webpackJsonpCallback","parentChunkLoadingFunction","moreModules","runtime","some","chunkLoadingGlobal","forEach","__webpack_exports__"],"sourceRoot":""} \ No newline at end of file diff --git a/lib/private/Files/Cache/CacheQueryBuilder.php b/lib/private/Files/Cache/CacheQueryBuilder.php index b5a9101877c..b448424c1a8 100644 --- a/lib/private/Files/Cache/CacheQueryBuilder.php +++ b/lib/private/Files/Cache/CacheQueryBuilder.php @@ -43,7 +43,7 @@ class CacheQueryBuilder extends QueryBuilder { public function selectFileCache(string $alias = null) { $name = $alias ? $alias : 'filecache'; - $this->select("$name.fileid", 'storage', 'path', 'path_hash', "$name.parent", 'name', 'mimetype', 'mimepart', 'size', 'mtime', + $this->select("$name.fileid", 'storage', 'path', 'path_hash', "$name.parent", "$name.name", 'mimetype', 'mimepart', 'size', 'mtime', 'storage_mtime', 'encrypted', 'etag', 'permissions', 'checksum', 'metadata_etag', 'creation_time', 'upload_time') ->from('filecache', $name) ->leftJoin($name, 'filecache_extended', 'fe', $this->expr()->eq("$name.fileid", 'fe.fileid')); diff --git a/lib/private/Files/Cache/QuerySearchHelper.php b/lib/private/Files/Cache/QuerySearchHelper.php index 3bf9abf3524..e7bccbf521c 100644 --- a/lib/private/Files/Cache/QuerySearchHelper.php +++ b/lib/private/Files/Cache/QuerySearchHelper.php @@ -28,6 +28,7 @@ namespace OC\Files\Cache; use OC\Files\Search\QueryOptimizer\QueryOptimizer; use OC\Files\Search\SearchBinaryOperator; use OC\SystemConfig; +use OCP\DB\QueryBuilder\IQueryBuilder; use OCP\Files\Cache\ICache; use OCP\Files\Cache\ICacheEntry; use OCP\Files\IMimeTypeLoader; @@ -110,13 +111,21 @@ class QuerySearchHelper { throw new \InvalidArgumentException("Searching by tag requires the user to be set in the query"); } $query - ->innerJoin('file', 'vcategory_to_object', 'tagmap', $builder->expr()->eq('file.fileid', 'tagmap.objid')) - ->innerJoin('tagmap', 'vcategory', 'tag', $builder->expr()->andX( + ->leftJoin('file', 'vcategory_to_object', 'tagmap', $builder->expr()->eq('file.fileid', 'tagmap.objid')) + ->leftJoin('tagmap', 'vcategory', 'tag', $builder->expr()->andX( $builder->expr()->eq('tagmap.type', 'tag.type'), - $builder->expr()->eq('tagmap.categoryid', 'tag.id') + $builder->expr()->eq('tagmap.categoryid', 'tag.id'), + $builder->expr()->eq('tag.type', $builder->createNamedParameter('files')), + $builder->expr()->eq('tag.uid', $builder->createNamedParameter($user->getUID())) )) - ->andWhere($builder->expr()->eq('tag.type', $builder->createNamedParameter('files'))) - ->andWhere($builder->expr()->eq('tag.uid', $builder->createNamedParameter($user->getUID()))); + ->leftJoin('file', 'systemtag_object_mapping', 'systemtagmap', $builder->expr()->andX( + $builder->expr()->eq('file.fileid', $builder->expr()->castColumn('systemtagmap.objectid', IQueryBuilder::PARAM_INT)), + $builder->expr()->eq('systemtagmap.objecttype', $builder->createNamedParameter('files')) + )) + ->leftJoin('systemtagmap', 'systemtag', 'systemtag', $builder->expr()->andX( + $builder->expr()->eq('systemtag.id', 'systemtagmap.systemtagid'), + $builder->expr()->eq('systemtag.visibility', $builder->createNamedParameter(true)) + )); } $storageFilters = array_values(array_map(function (ICache $cache) { diff --git a/lib/private/Files/Cache/SearchBuilder.php b/lib/private/Files/Cache/SearchBuilder.php index c8c442bcb8c..b5f548dd563 100644 --- a/lib/private/Files/Cache/SearchBuilder.php +++ b/lib/private/Files/Cache/SearchBuilder.php @@ -80,7 +80,7 @@ class SearchBuilder { return $shouldJoin || $this->shouldJoinTags($operator); }, false); } elseif ($operator instanceof ISearchComparison) { - return $operator->getField() === 'tagname' || $operator->getField() === 'favorite'; + return $operator->getField() === 'tagname' || $operator->getField() === 'favorite' || $operator->getField() === 'systemtag'; } return false; } @@ -163,8 +163,12 @@ class SearchBuilder { } elseif ($field === 'favorite') { $field = 'tag.category'; $value = self::TAG_FAVORITE; + } elseif ($field === 'name') { + $field = 'file.name'; } elseif ($field === 'tagname') { $field = 'tag.category'; + } elseif ($field === 'systemtag') { + $field = 'systemtag.name'; } elseif ($field === 'fileid') { $field = 'file.fileid'; } elseif ($field === 'path' && $type === ISearchComparison::COMPARE_EQUAL && $operator->getQueryHint(ISearchComparison::HINT_PATH_EQ_HASH, true)) { @@ -182,6 +186,7 @@ class SearchBuilder { 'path' => 'string', 'size' => 'integer', 'tagname' => 'string', + 'systemtag' => 'string', 'favorite' => 'boolean', 'fileid' => 'integer', 'storage' => 'integer', @@ -193,6 +198,7 @@ class SearchBuilder { 'path' => ['eq', 'like', 'clike'], 'size' => ['eq', 'gt', 'lt', 'gte', 'lte'], 'tagname' => ['eq', 'like'], + 'systemtag' => ['eq', 'like'], 'favorite' => ['eq'], 'fileid' => ['eq'], 'storage' => ['eq'], diff --git a/tests/lib/Files/Cache/SearchBuilderTest.php b/tests/lib/Files/Cache/SearchBuilderTest.php index 82c4dbaa27f..5eb1a0252f0 100644 --- a/tests/lib/Files/Cache/SearchBuilderTest.php +++ b/tests/lib/Files/Cache/SearchBuilderTest.php @@ -79,7 +79,7 @@ class SearchBuilderTest extends TestCase { $this->numericStorageId = 10000; $this->builder->select(['fileid']) - ->from('filecache') + ->from('filecache', 'file') // alias needed for QuerySearchHelper#getOperatorFieldAndValue ->where($this->builder->expr()->eq('storage', new Literal($this->numericStorageId))); } -- cgit v1.2.3 From f399fd41ece6aa91e8a0331e2460ca54097908e2 Mon Sep 17 00:00:00 2001 From: Daniel Kesselberg Date: Fri, 27 May 2022 20:05:46 +0200 Subject: Fix docblock for return type Signed-off-by: Daniel Kesselberg --- lib/private/Files/Mount/MoveableMount.php | 1 - 1 file changed, 1 deletion(-) (limited to 'lib/private/Files') diff --git a/lib/private/Files/Mount/MoveableMount.php b/lib/private/Files/Mount/MoveableMount.php index 7ebb7fa2c87..a7372153d75 100644 --- a/lib/private/Files/Mount/MoveableMount.php +++ b/lib/private/Files/Mount/MoveableMount.php @@ -37,7 +37,6 @@ interface MoveableMount { /** * Remove the mount points * - * @return mixed * @return bool */ public function removeMount(); -- cgit v1.2.3 From 0f0e74a78c2e0aab4723528977cadbe9149ee373 Mon Sep 17 00:00:00 2001 From: Julius Härtl Date: Mon, 30 May 2022 17:53:07 +0200 Subject: Cleanup temporary files after finishing the write to object storage MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Julius Härtl --- lib/private/Files/ObjectStore/ObjectStoreStorage.php | 2 ++ 1 file changed, 2 insertions(+) (limited to 'lib/private/Files') diff --git a/lib/private/Files/ObjectStore/ObjectStoreStorage.php b/lib/private/Files/ObjectStore/ObjectStoreStorage.php index adb3928b28a..898f64d97c2 100644 --- a/lib/private/Files/ObjectStore/ObjectStoreStorage.php +++ b/lib/private/Files/ObjectStore/ObjectStoreStorage.php @@ -335,6 +335,7 @@ class ObjectStoreStorage extends \OC\Files\Storage\Common { $handle = fopen($tmpFile, $mode); return CallbackWrapper::wrap($handle, null, null, function () use ($path, $tmpFile) { $this->writeBack($tmpFile, $path); + unlink($tmpFile); }); case 'a': case 'ab': @@ -352,6 +353,7 @@ class ObjectStoreStorage extends \OC\Files\Storage\Common { $handle = fopen($tmpFile, $mode); return CallbackWrapper::wrap($handle, null, null, function () use ($path, $tmpFile) { $this->writeBack($tmpFile, $path); + unlink($tmpFile); }); } return false; -- cgit v1.2.3 From 8238582e59b7b6ec03318bcf81bf47cce54af320 Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Wed, 13 Apr 2022 16:05:45 +0200 Subject: store unencrypted size in the unencrypted_size column Signed-off-by: Robin Appelman --- lib/private/Files/Cache/Cache.php | 35 ++++++-- lib/private/Files/Cache/CacheEntry.php | 8 ++ lib/private/Files/Cache/CacheQueryBuilder.php | 2 +- lib/private/Files/Cache/Propagator.php | 15 ++++ lib/private/Files/FileInfo.php | 27 +++++- lib/private/Files/Storage/Wrapper/Encryption.php | 98 +++++++++++++--------- lib/public/Files/Cache/ICacheEntry.php | 10 +++ tests/lib/Files/Storage/Wrapper/EncryptionTest.php | 7 +- tests/lib/HelperStorageTest.php | 3 + 9 files changed, 153 insertions(+), 52 deletions(-) (limited to 'lib/private/Files') diff --git a/lib/private/Files/Cache/Cache.php b/lib/private/Files/Cache/Cache.php index 949079dfa22..c6cadf14f86 100644 --- a/lib/private/Files/Cache/Cache.php +++ b/lib/private/Files/Cache/Cache.php @@ -37,6 +37,7 @@ * along with this program. If not, see * */ + namespace OC\Files\Cache; use Doctrine\DBAL\Exception\UniqueConstraintViolationException; @@ -188,6 +189,7 @@ class Cache implements ICache { $data['fileid'] = (int)$data['fileid']; $data['parent'] = (int)$data['parent']; $data['size'] = 0 + $data['size']; + $data['unencrypted_size'] = 0 + ($data['unencrypted_size'] ?? 0); $data['mtime'] = (int)$data['mtime']; $data['storage_mtime'] = (int)$data['storage_mtime']; $data['encryptedVersion'] = (int)$data['encrypted']; @@ -428,7 +430,7 @@ class Cache implements ICache { protected function normalizeData(array $data): array { $fields = [ 'path', 'parent', 'name', 'mimetype', 'size', 'mtime', 'storage_mtime', 'encrypted', - 'etag', 'permissions', 'checksum', 'storage']; + 'etag', 'permissions', 'checksum', 'storage', 'unencrypted_size']; $extensionFields = ['metadata_etag', 'creation_time', 'upload_time']; $doNotCopyStorageMTime = false; @@ -873,8 +875,16 @@ class Cache implements ICache { $id = $entry['fileid']; $query = $this->getQueryBuilder(); - $query->selectAlias($query->func()->sum('size'), 'f1') - ->selectAlias($query->func()->min('size'), 'f2') + $query->selectAlias($query->func()->sum('size'), 'size_sum') + ->selectAlias($query->func()->min('size'), 'size_min') + // in case of encryption being enabled after some files are already uploaded, some entries will have an unencrypted_size of 0 and a non-zero size + ->selectAlias($query->func()->sum( + $query->func()->case([ + ['when' => $query->expr()->eq('unencrypted_size', $query->expr()->literal(0, IQueryBuilder::PARAM_INT)), 'then' => 'size'], + ], 'unencrypted_size') + ), 'unencrypted_sum') + ->selectAlias($query->func()->min('unencrypted_size'), 'unencrypted_min') + ->selectAlias($query->func()->max('unencrypted_size'), 'unencrypted_max') ->from('filecache') ->whereStorageId($this->getNumericStorageId()) ->whereParent($id); @@ -884,7 +894,7 @@ class Cache implements ICache { $result->closeCursor(); if ($row) { - [$sum, $min] = array_values($row); + ['size_sum' => $sum, 'size_min' => $min, 'unencrypted_sum' => $unencryptedSum, 'unencrypted_min' => $unencryptedMin, 'unencrypted_max' => $unencryptedMax] = $row; $sum = 0 + $sum; $min = 0 + $min; if ($min === -1) { @@ -892,8 +902,23 @@ class Cache implements ICache { } else { $totalSize = $sum; } + if ($unencryptedMin === -1 || $min === -1) { + $unencryptedTotal = $unencryptedMin; + } else { + $unencryptedTotal = $unencryptedSum; + } if ($entry['size'] !== $totalSize) { - $this->update($id, ['size' => $totalSize]); + // only set unencrypted size for a folder if any child entries have it set + if ($unencryptedMax > 0) { + $this->update($id, [ + 'size' => $totalSize, + 'unencrypted_size' => $unencryptedTotal, + ]); + } else { + $this->update($id, [ + 'size' => $totalSize, + ]); + } } } } diff --git a/lib/private/Files/Cache/CacheEntry.php b/lib/private/Files/Cache/CacheEntry.php index 12f0273fb6e..8ac76acf6d1 100644 --- a/lib/private/Files/Cache/CacheEntry.php +++ b/lib/private/Files/Cache/CacheEntry.php @@ -132,4 +132,12 @@ class CacheEntry implements ICacheEntry { public function __clone() { $this->data = array_merge([], $this->data); } + + public function getUnencryptedSize(): int { + if (isset($this->data['unencrypted_size']) && $this->data['unencrypted_size'] > 0) { + return $this->data['unencrypted_size']; + } else { + return $this->data['size']; + } + } } diff --git a/lib/private/Files/Cache/CacheQueryBuilder.php b/lib/private/Files/Cache/CacheQueryBuilder.php index b448424c1a8..b92e79aa229 100644 --- a/lib/private/Files/Cache/CacheQueryBuilder.php +++ b/lib/private/Files/Cache/CacheQueryBuilder.php @@ -44,7 +44,7 @@ class CacheQueryBuilder extends QueryBuilder { public function selectFileCache(string $alias = null) { $name = $alias ? $alias : 'filecache'; $this->select("$name.fileid", 'storage', 'path', 'path_hash', "$name.parent", "$name.name", 'mimetype', 'mimepart', 'size', 'mtime', - 'storage_mtime', 'encrypted', 'etag', 'permissions', 'checksum', 'metadata_etag', 'creation_time', 'upload_time') + 'storage_mtime', 'encrypted', 'etag', 'permissions', 'checksum', 'metadata_etag', 'creation_time', 'upload_time', 'unencrypted_size') ->from('filecache', $name) ->leftJoin($name, 'filecache_extended', 'fe', $this->expr()->eq("$name.fileid", 'fe.fileid')); diff --git a/lib/private/Files/Cache/Propagator.php b/lib/private/Files/Cache/Propagator.php index 270b2b013f5..afff2fb51ff 100644 --- a/lib/private/Files/Cache/Propagator.php +++ b/lib/private/Files/Cache/Propagator.php @@ -24,6 +24,7 @@ namespace OC\Files\Cache; +use OC\Files\Storage\Wrapper\Encryption; use OCP\DB\QueryBuilder\IQueryBuilder; use OCP\Files\Cache\IPropagator; use OCP\Files\Storage\IReliableEtagStorage; @@ -113,6 +114,20 @@ class Propagator implements IPropagator { ->andWhere($builder->expr()->in('path_hash', $hashParams)) ->andWhere($builder->expr()->gt('size', $builder->expr()->literal(-1, IQueryBuilder::PARAM_INT))); + if ($this->storage->instanceOfStorage(Encryption::class)) { + // in case of encryption being enabled after some files are already uploaded, some entries will have an unencrypted_size of 0 and a non-zero size + $builder->set('unencrypted_size', $builder->func()->greatest( + $builder->func()->add( + $builder->func()->case([ + ['when' => $builder->expr()->eq('unencrypted_size', $builder->expr()->literal(0, IQueryBuilder::PARAM_INT)), 'then' => 'size'] + ], 'unencrypted_size'), + $builder->createNamedParameter($sizeDifference) + ), + $builder->createNamedParameter(-1, IQueryBuilder::PARAM_INT) + )); + } + + $a = $builder->getSQL(); $builder->execute(); } } diff --git a/lib/private/Files/FileInfo.php b/lib/private/Files/FileInfo.php index 6389544184f..5912eefcf9c 100644 --- a/lib/private/Files/FileInfo.php +++ b/lib/private/Files/FileInfo.php @@ -101,7 +101,11 @@ class FileInfo implements \OCP\Files\FileInfo, \ArrayAccess { $this->data = $data; $this->mount = $mount; $this->owner = $owner; - $this->rawSize = $this->data['size'] ?? 0; + if (isset($this->data['unencrypted_size'])) { + $this->rawSize = $this->data['unencrypted_size']; + } else { + $this->rawSize = $this->data['size'] ?? 0; + } } public function offsetSet($offset, $value): void { @@ -208,7 +212,12 @@ class FileInfo implements \OCP\Files\FileInfo, \ArrayAccess { public function getSize($includeMounts = true) { if ($includeMounts) { $this->updateEntryfromSubMounts(); - return isset($this->data['size']) ? 0 + $this->data['size'] : 0; + + if (isset($this->data['unencrypted_size']) && $this->data['unencrypted_size'] > 0) { + return $this->data['unencrypted_size']; + } else { + return isset($this->data['size']) ? 0 + $this->data['size'] : 0; + } } else { return $this->rawSize; } @@ -386,7 +395,19 @@ class FileInfo implements \OCP\Files\FileInfo, \ArrayAccess { * @param string $entryPath full path of the child entry */ public function addSubEntry($data, $entryPath) { - $this->data['size'] += isset($data['size']) ? $data['size'] : 0; + if (!$data) { + return; + } + $hasUnencryptedSize = isset($data['unencrypted_size']) && $data['unencrypted_size'] > 0; + if ($hasUnencryptedSize) { + $subSize = $data['unencrypted_size']; + } else { + $subSize = $data['size'] ?: 0; + } + $this->data['size'] += $subSize; + if ($hasUnencryptedSize) { + $this->data['unencrypted_size'] += $subSize; + } if (isset($data['mtime'])) { $this->data['mtime'] = max($this->data['mtime'], $data['mtime']); } diff --git a/lib/private/Files/Storage/Wrapper/Encryption.php b/lib/private/Files/Storage/Wrapper/Encryption.php index 4cfe932cc9f..d5bf929101f 100644 --- a/lib/private/Files/Storage/Wrapper/Encryption.php +++ b/lib/private/Files/Storage/Wrapper/Encryption.php @@ -33,6 +33,7 @@ * along with this program. If not, see * */ + namespace OC\Files\Storage\Wrapper; use OC\Encryption\Exceptions\ModuleDoesNotExistsException; @@ -41,6 +42,7 @@ use OC\Encryption\Util; use OC\Files\Cache\CacheEntry; use OC\Files\Filesystem; use OC\Files\Mount\Manager; +use OC\Files\ObjectStore\ObjectStoreStorage; use OC\Files\Storage\LocalTempFileTrait; use OC\Memcache\ArrayCache; use OCP\Encryption\Exceptions\GenericEncryptionException; @@ -139,28 +141,36 @@ class Encryption extends Wrapper { $size = $this->unencryptedSize[$fullPath]; // update file cache if ($info instanceof ICacheEntry) { - $info = $info->getData(); $info['encrypted'] = $info['encryptedVersion']; } else { if (!is_array($info)) { $info = []; } $info['encrypted'] = true; + $info = new CacheEntry($info); } - $info['size'] = $size; - $this->getCache()->put($path, $info); + if ($size !== $info->getUnencryptedSize()) { + $this->getCache()->update($info->getId(), [ + 'unencrypted_size' => $size + ]); + } return $size; } if (isset($info['fileid']) && $info['encrypted']) { - return $this->verifyUnencryptedSize($path, $info['size']); + return $this->verifyUnencryptedSize($path, $info->getUnencryptedSize()); } return $this->storage->filesize($path); } + /** + * @param string $path + * @param array $data + * @return array + */ private function modifyMetaData(string $path, array $data): array { $fullPath = $this->getFullPath($path); $info = $this->getCache()->get($path); @@ -170,7 +180,7 @@ class Encryption extends Wrapper { $data['size'] = $this->unencryptedSize[$fullPath]; } else { if (isset($info['fileid']) && $info['encrypted']) { - $data['size'] = $this->verifyUnencryptedSize($path, $info['size']); + $data['size'] = $this->verifyUnencryptedSize($path, $info->getUnencryptedSize()); $data['encrypted'] = true; } } @@ -478,7 +488,7 @@ class Encryption extends Wrapper { * * @return int unencrypted size */ - protected function verifyUnencryptedSize($path, $unencryptedSize) { + protected function verifyUnencryptedSize(string $path, int $unencryptedSize): int { $size = $this->storage->filesize($path); $result = $unencryptedSize; @@ -510,7 +520,7 @@ class Encryption extends Wrapper { * * @return int calculated unencrypted size */ - protected function fixUnencryptedSize($path, $size, $unencryptedSize) { + protected function fixUnencryptedSize(string $path, int $size, int $unencryptedSize): int { $headerSize = $this->getHeaderSize($path); $header = $this->getHeader($path); $encryptionModule = $this->getEncryptionModule($path); @@ -581,7 +591,9 @@ class Encryption extends Wrapper { $cache = $this->storage->getCache(); if ($cache) { $entry = $cache->get($path); - $cache->update($entry['fileid'], ['size' => $newUnencryptedSize]); + $cache->update($entry['fileid'], [ + 'unencrypted_size' => $newUnencryptedSize + ]); } return $newUnencryptedSize; @@ -621,7 +633,12 @@ class Encryption extends Wrapper { * @param bool $preserveMtime * @return bool */ - public function moveFromStorage(Storage\IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath, $preserveMtime = true) { + public function moveFromStorage( + Storage\IStorage $sourceStorage, + $sourceInternalPath, + $targetInternalPath, + $preserveMtime = true + ) { if ($sourceStorage === $this) { return $this->rename($sourceInternalPath, $targetInternalPath); } @@ -656,7 +673,13 @@ class Encryption extends Wrapper { * @param bool $isRename * @return bool */ - public function copyFromStorage(Storage\IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath, $preserveMtime = false, $isRename = false) { + public function copyFromStorage( + Storage\IStorage $sourceStorage, + $sourceInternalPath, + $targetInternalPath, + $preserveMtime = false, + $isRename = false + ) { // TODO clean this up once the underlying moveFromStorage in OC\Files\Storage\Wrapper\Common is fixed: // - call $this->storage->copyFromStorage() instead of $this->copyBetweenStorage @@ -676,7 +699,13 @@ class Encryption extends Wrapper { * @param bool $isRename * @param bool $keepEncryptionVersion */ - private function updateEncryptedVersion(Storage\IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath, $isRename, $keepEncryptionVersion) { + private function updateEncryptedVersion( + Storage\IStorage $sourceStorage, + $sourceInternalPath, + $targetInternalPath, + $isRename, + $keepEncryptionVersion + ) { $isEncrypted = $this->encryptionManager->isEnabled() && $this->shouldEncrypt($targetInternalPath); $cacheInformation = [ 'encrypted' => $isEncrypted, @@ -725,7 +754,13 @@ class Encryption extends Wrapper { * @return bool * @throws \Exception */ - private function copyBetweenStorage(Storage\IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath, $preserveMtime, $isRename) { + private function copyBetweenStorage( + Storage\IStorage $sourceStorage, + $sourceInternalPath, + $targetInternalPath, + $preserveMtime, + $isRename + ) { // for versions we have nothing to do, because versions should always use the // key from the original file. Just create a 1:1 copy and done @@ -743,7 +778,7 @@ class Encryption extends Wrapper { if (isset($info['encrypted']) && $info['encrypted'] === true) { $this->updateUnencryptedSize( $this->getFullPath($targetInternalPath), - $info['size'] + $info->getUnencryptedSize() ); } $this->updateEncryptedVersion($sourceStorage, $sourceInternalPath, $targetInternalPath, $isRename, true); @@ -808,13 +843,6 @@ class Encryption extends Wrapper { return (bool)$result; } - /** - * get the path to a local version of the file. - * The local version of the file can be temporary and doesn't have to be persistent across requests - * - * @param string $path - * @return string - */ public function getLocalFile($path) { if ($this->encryptionManager->isEnabled()) { $cachedFile = $this->getCachedFile($path); @@ -825,11 +853,6 @@ class Encryption extends Wrapper { return $this->storage->getLocalFile($path); } - /** - * Returns the wrapped storage's value for isLocal() - * - * @return bool wrapped storage's isLocal() value - */ public function isLocal() { if ($this->encryptionManager->isEnabled()) { return false; @@ -837,15 +860,11 @@ class Encryption extends Wrapper { return $this->storage->isLocal(); } - /** - * see https://www.php.net/manual/en/function.stat.php - * only the following keys are required in the result: size and mtime - * - * @param string $path - * @return array - */ public function stat($path) { $stat = $this->storage->stat($path); + if (!$stat) { + return false; + } $fileSize = $this->filesize($path); $stat['size'] = $fileSize; $stat[7] = $fileSize; @@ -853,14 +872,6 @@ class Encryption extends Wrapper { return $stat; } - /** - * see https://www.php.net/manual/en/function.hash.php - * - * @param string $type - * @param string $path - * @param bool $raw - * @return string - */ public function hash($type, $path, $raw = false) { $fh = $this->fopen($path, 'rb'); $ctx = hash_init($type); @@ -1068,6 +1079,13 @@ class Encryption extends Wrapper { [$count, $result] = \OC_Helper::streamCopy($stream, $target); fclose($stream); fclose($target); + + // object store, stores the size after write and doesn't update this during scan + // manually store the unencrypted size + if ($result && $this->getWrapperStorage()->instanceOfStorage(ObjectStoreStorage::class)) { + $this->getCache()->put($path, ['unencrypted_size' => $count]); + } + return $count; } } diff --git a/lib/public/Files/Cache/ICacheEntry.php b/lib/public/Files/Cache/ICacheEntry.php index 17eecf89ddb..e1e8129394c 100644 --- a/lib/public/Files/Cache/ICacheEntry.php +++ b/lib/public/Files/Cache/ICacheEntry.php @@ -162,4 +162,14 @@ interface ICacheEntry extends ArrayAccess { * @since 18.0.0 */ public function getUploadTime(): ?int; + + /** + * Get the unencrypted size + * + * This might be different from the result of getSize + * + * @return int + * @since 25.0.0 + */ + public function getUnencryptedSize(): int; } diff --git a/tests/lib/Files/Storage/Wrapper/EncryptionTest.php b/tests/lib/Files/Storage/Wrapper/EncryptionTest.php index d26e5c499e7..ebb97a25c77 100644 --- a/tests/lib/Files/Storage/Wrapper/EncryptionTest.php +++ b/tests/lib/Files/Storage/Wrapper/EncryptionTest.php @@ -5,6 +5,7 @@ namespace Test\Files\Storage\Wrapper; use OC\Encryption\Exceptions\ModuleDoesNotExistsException; use OC\Encryption\Update; use OC\Encryption\Util; +use OC\Files\Cache\CacheEntry; use OC\Files\Storage\Temporary; use OC\Files\Storage\Wrapper\Encryption; use OC\Files\View; @@ -259,7 +260,7 @@ class EncryptionTest extends Storage { ->method('get') ->willReturnCallback( function ($path) use ($encrypted) { - return ['encrypted' => $encrypted, 'path' => $path, 'size' => 0, 'fileid' => 1]; + return new CacheEntry(['encrypted' => $encrypted, 'path' => $path, 'size' => 0, 'fileid' => 1]); } ); @@ -332,7 +333,7 @@ class EncryptionTest extends Storage { ->disableOriginalConstructor()->getMock(); $cache->expects($this->any()) ->method('get') - ->willReturn(['encrypted' => true, 'path' => '/test.txt', 'size' => 0, 'fileid' => 1]); + ->willReturn(new CacheEntry(['encrypted' => true, 'path' => '/test.txt', 'size' => 0, 'fileid' => 1])); $this->instance = $this->getMockBuilder('\OC\Files\Storage\Wrapper\Encryption') ->setConstructorArgs( @@ -910,7 +911,7 @@ class EncryptionTest extends Storage { if ($copyResult) { $cache->expects($this->once())->method('get') ->with($sourceInternalPath) - ->willReturn(['encrypted' => $encrypted, 'size' => 42]); + ->willReturn(new CacheEntry(['encrypted' => $encrypted, 'size' => 42])); if ($encrypted) { $instance->expects($this->once())->method('updateUnencryptedSize') ->with($mountPoint . $targetInternalPath, 42); diff --git a/tests/lib/HelperStorageTest.php b/tests/lib/HelperStorageTest.php index 6d7ea513d3f..d3f480502b2 100644 --- a/tests/lib/HelperStorageTest.php +++ b/tests/lib/HelperStorageTest.php @@ -104,6 +104,9 @@ class HelperStorageTest extends \Test\TestCase { $extStorage->file_put_contents('extfile.txt', 'abcdefghijklmnopq'); $extStorage->getScanner()->scan(''); // update root size + $config = \OC::$server->getConfig(); + $config->setSystemValue('quota_include_external_storage', false); + \OC\Files\Filesystem::mount($extStorage, [], '/' . $this->user . '/files/ext'); $storageInfo = \OC_Helper::getStorageInfo(''); -- cgit v1.2.3 From 74095699940184048e4cdd84360f41cc08a12528 Mon Sep 17 00:00:00 2001 From: Martin Brugnara Date: Sat, 4 Jun 2022 00:24:35 +0200 Subject: Expose umask override value as config parameter: localstorage.umask Commit 451c06d introduced override for umask value. This is needed to avoid broken env configuration or dirty workers to mess with the permissions when creating new files. Most Nextcloud, that does not integrate with external software would work fine with an hard-coded value (451c06d set it at 022). Advanced install may require more flexibility, as such this commit exposes the "umask override value" as configuration parameter: `localstorage.umask` It defaults to 0022 both in code and in config/config.sample.php . Signed-off-by: Martin Brugnara --- config/config.sample.php | 12 ++++++++++++ lib/private/Files/Storage/Local.php | 14 +++++++++----- 2 files changed, 21 insertions(+), 5 deletions(-) (limited to 'lib/private/Files') diff --git a/config/config.sample.php b/config/config.sample.php index c3a0048625c..4cb19c8d9e9 100644 --- a/config/config.sample.php +++ b/config/config.sample.php @@ -1839,6 +1839,18 @@ $CONFIG = [ */ 'localstorage.allowsymlinks' => false, +/** + * Nextcloud overrides umask to ensure suitable access permissions + * regardless of webserver/php-fpm configuration and worker state. + * WARNING: Modifying this value has security implications and + * may soft-break the installation. + * + * Most installs shall not modify this value. + * + * Defaults to ``0022`` + */ +'localstorage.umask' => 0022, + /** * EXPERIMENTAL: option whether to include external storage in quota * calculation, defaults to false. diff --git a/lib/private/Files/Storage/Local.php b/lib/private/Files/Storage/Local.php index ee8a8c7d161..4996572a40e 100644 --- a/lib/private/Files/Storage/Local.php +++ b/lib/private/Files/Storage/Local.php @@ -15,6 +15,7 @@ * @author Jörn Friedrich Dreyer * @author Klaas Freitag * @author Lukas Reschke + * @author Martin Brugnara * @author Michael Gapczynski * @author Morris Jobke * @author Robin Appelman @@ -66,6 +67,8 @@ class Local extends \OC\Files\Storage\Common { private IMimeTypeDetector $mimeTypeDetector; + private $defUMask; + public function __construct($arguments) { if (!isset($arguments['datadir']) || !is_string($arguments['datadir'])) { throw new \InvalidArgumentException('No data directory set for local storage'); @@ -84,6 +87,7 @@ class Local extends \OC\Files\Storage\Common { $this->dataDirLength = strlen($this->realDataDir); $this->config = \OC::$server->get(IConfig::class); $this->mimeTypeDetector = \OC::$server->get(IMimeTypeDetector::class); + $this->defUMask = $this->config->getSystemValue('localstorage.umask', 0022); } public function __destruct() { @@ -95,7 +99,7 @@ class Local extends \OC\Files\Storage\Common { public function mkdir($path) { $sourcePath = $this->getSourcePath($path); - $oldMask = umask(022); + $oldMask = umask($this->defUMask); $result = @mkdir($sourcePath, 0777, true); umask($oldMask); return $result; @@ -273,7 +277,7 @@ class Local extends \OC\Files\Storage\Common { if ($this->file_exists($path) and !$this->isUpdatable($path)) { return false; } - $oldMask = umask(022); + $oldMask = umask($this->defUMask); if (!is_null($mtime)) { $result = @touch($this->getSourcePath($path), $mtime); } else { @@ -292,7 +296,7 @@ class Local extends \OC\Files\Storage\Common { } public function file_put_contents($path, $data) { - $oldMask = umask(022); + $oldMask = umask($this->defUMask); $result = file_put_contents($this->getSourcePath($path), $data); umask($oldMask); return $result; @@ -365,7 +369,7 @@ class Local extends \OC\Files\Storage\Common { if ($this->is_dir($path1)) { return parent::copy($path1, $path2); } else { - $oldMask = umask(022); + $oldMask = umask($this->defUMask); $result = copy($this->getSourcePath($path1), $this->getSourcePath($path2)); umask($oldMask); return $result; @@ -373,7 +377,7 @@ class Local extends \OC\Files\Storage\Common { } public function fopen($path, $mode) { - $oldMask = umask(022); + $oldMask = umask($this->defUMask); $result = fopen($this->getSourcePath($path), $mode); umask($oldMask); return $result; -- cgit v1.2.3 From 499995b37a6a761cead629a64dd216eeac794619 Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Wed, 8 Jun 2022 17:52:27 +0200 Subject: handle stream wrappers in SeekableHttpStream Signed-off-by: Robin Appelman --- lib/private/Files/Stream/SeekableHttpStream.php | 13 +++++++++++++ 1 file changed, 13 insertions(+) (limited to 'lib/private/Files') diff --git a/lib/private/Files/Stream/SeekableHttpStream.php b/lib/private/Files/Stream/SeekableHttpStream.php index af797c7720d..820a681bd07 100644 --- a/lib/private/Files/Stream/SeekableHttpStream.php +++ b/lib/private/Files/Stream/SeekableHttpStream.php @@ -24,6 +24,7 @@ namespace OC\Files\Stream; use Icewind\Streams\File; +use Icewind\Streams\Wrapper; /** * A stream wrapper that uses http range requests to provide a seekable stream for http reading @@ -92,6 +93,18 @@ class SeekableHttpStream implements File { } $responseHead = stream_get_meta_data($this->current)['wrapper_data']; + + while ($responseHead instanceof Wrapper) { + $wrapperOptions = stream_context_get_options($responseHead->context); + foreach ($wrapperOptions as $options) { + if (isset($options['source']) && is_resource($options['source'])) { + $responseHead = stream_get_meta_data($options['source'])['wrapper_data']; + continue 2; + } + } + throw new \Exception("Failed to get source stream from stream wrapper of " . get_class($responseHead)); + } + $rangeHeaders = array_values(array_filter($responseHead, function ($v) { return preg_match('#^content-range:#i', $v) === 1; })); -- cgit v1.2.3 From a937ab03dd29d59081f9b10bbca13414affa9637 Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Wed, 8 Jun 2022 18:49:54 +0200 Subject: perform onetime setup earlier to ensure wrappers are registered on time this fixes an issue with wrappers like encryption not always being applied to mountpoint that create the storage object directly (such as external storage) Signed-off-by: Robin Appelman --- lib/private/Files/SetupManager.php | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'lib/private/Files') diff --git a/lib/private/Files/SetupManager.php b/lib/private/Files/SetupManager.php index 040ba6b898f..2bcaa234c29 100644 --- a/lib/private/Files/SetupManager.php +++ b/lib/private/Files/SetupManager.php @@ -403,6 +403,10 @@ class SetupManager { return; } + if (!$this->isSetupStarted($user)) { + $this->oneTimeUserSetup($user); + } + $mounts = []; if (!in_array($cachedMount->getMountProvider(), $setupProviders)) { $setupProviders[] = $cachedMount->getMountProvider(); -- cgit v1.2.3 From 5c768f980e3936343dc9de13b4d1b303d2ed5443 Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Thu, 9 Jun 2022 19:21:56 +0200 Subject: fix mounts mounted at the users home this fixes external storages with '/' as mountpoint Signed-off-by: Robin Appelman --- lib/private/Files/SetupManager.php | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) (limited to 'lib/private/Files') diff --git a/lib/private/Files/SetupManager.php b/lib/private/Files/SetupManager.php index 040ba6b898f..629ed6f1bf0 100644 --- a/lib/private/Files/SetupManager.php +++ b/lib/private/Files/SetupManager.php @@ -380,13 +380,9 @@ class SetupManager { return; } - // for the user's home folder, it's always the home mount - if (rtrim($path) === "/" . $user->getUID() . "/files") { - if ($includeChildren) { - $this->setupForUser($user); - } else { - $this->oneTimeUserSetup($user); - } + // for the user's home folder, and includes children we need everything always + if (rtrim($path) === "/" . $user->getUID() . "/files" && $includeChildren) { + $this->setupForUser($user); return; } -- cgit v1.2.3 From 1bd5222224bac9b8f1037c992367c9af85a9ec5a Mon Sep 17 00:00:00 2001 From: Côme Chilliet Date: Tue, 21 Jun 2022 16:03:45 +0200 Subject: Fix PHP 8.2 warnings about undeclared properties MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Côme Chilliet --- lib/private/App/Platform.php | 35 ++++++---------------- lib/private/App/PlatformRepository.php | 23 +++++--------- .../Contacts/ContactsMenu/ContactsStore.php | 2 +- lib/private/DB/MigrationService.php | 34 ++++++++------------- lib/private/Files/Node/File.php | 1 - lib/private/Files/Node/Folder.php | 1 - tests/lib/AppFramework/Http/DispatcherTest.php | 2 ++ 7 files changed, 33 insertions(+), 65 deletions(-) (limited to 'lib/private/Files') diff --git a/lib/private/App/Platform.php b/lib/private/App/Platform.php index 12097abbc78..8da16c038f1 100644 --- a/lib/private/App/Platform.php +++ b/lib/private/App/Platform.php @@ -36,39 +36,26 @@ use OCP\IConfig; */ class Platform { - /** - * @param IConfig $config - */ + private IConfig $config; + public function __construct(IConfig $config) { $this->config = $config; } - /** - * @return string - */ - public function getPhpVersion() { + public function getPhpVersion(): string { return phpversion(); } - /** - * @return int - */ - public function getIntSize() { + public function getIntSize(): int { return PHP_INT_SIZE; } - /** - * @return string - */ - public function getOcVersion() { + public function getOcVersion(): string { $v = \OCP\Util::getVersion(); return implode('.', $v); } - /** - * @return string - */ - public function getDatabase() { + public function getDatabase(): string { $dbType = $this->config->getSystemValue('dbtype', 'sqlite'); if ($dbType === 'sqlite3') { $dbType = 'sqlite'; @@ -77,23 +64,19 @@ class Platform { return $dbType; } - /** - * @return string - */ - public function getOS() { + public function getOS(): string { return php_uname('s'); } /** * @param $command - * @return bool */ - public function isCommandKnown($command) { + public function isCommandKnown($command): bool { $path = \OC_Helper::findBinaryPath($command); return ($path !== null); } - public function getLibraryVersion($name) { + public function getLibraryVersion(string $name): ?string { $repo = new PlatformRepository(); return $repo->findLibrary($name); } diff --git a/lib/private/App/PlatformRepository.php b/lib/private/App/PlatformRepository.php index 94fac5260e1..4166c2ead03 100644 --- a/lib/private/App/PlatformRepository.php +++ b/lib/private/App/PlatformRepository.php @@ -31,11 +31,13 @@ namespace OC\App; * @package OC\App */ class PlatformRepository { + private array $packages; + public function __construct() { $this->packages = $this->initialize(); } - protected function initialize() { + protected function initialize(): array { $loadedExtensions = get_loaded_extensions(); $packages = []; @@ -121,15 +123,11 @@ class PlatformRepository { return $packages; } - private function buildPackageName($name) { + private function buildPackageName(string $name): string { return str_replace(' ', '-', $name); } - /** - * @param $name - * @return string - */ - public function findLibrary($name) { + public function findLibrary(string $name): ?string { $extName = $this->buildPackageName($name); if (isset($this->packages[$extName])) { return $this->packages[$extName]; @@ -137,19 +135,17 @@ class PlatformRepository { return null; } - private static $modifierRegex = '[._-]?(?:(stable|beta|b|RC|alpha|a|patch|pl|p)(?:[.-]?(\d+))?)?([.-]?dev)?'; + private static string $modifierRegex = '[._-]?(?:(stable|beta|b|RC|alpha|a|patch|pl|p)(?:[.-]?(\d+))?)?([.-]?dev)?'; /** * Normalizes a version string to be able to perform comparisons on it * * https://github.com/composer/composer/blob/master/src/Composer/Package/Version/VersionParser.php#L94 * - * @param string $version * @param string $fullVersion optional complete version string to give more context * @throws \UnexpectedValueException - * @return string */ - public function normalizeVersion($version, $fullVersion = null) { + public function normalizeVersion(string $version, ?string $fullVersion = null): string { $version = trim($version); if (null === $fullVersion) { $fullVersion = $version; @@ -204,10 +200,7 @@ class PlatformRepository { throw new \UnexpectedValueException('Invalid version string "' . $version . '"' . $extraMessage); } - /** - * @param string $stability - */ - private function expandStability($stability) { + private function expandStability(string $stability): string { $stability = strtolower($stability); switch ($stability) { case 'a': diff --git a/lib/private/Contacts/ContactsMenu/ContactsStore.php b/lib/private/Contacts/ContactsMenu/ContactsStore.php index 020e8604910..4d7fda39c6a 100644 --- a/lib/private/Contacts/ContactsMenu/ContactsStore.php +++ b/lib/private/Contacts/ContactsMenu/ContactsStore.php @@ -150,7 +150,7 @@ class ContactsStore implements IContactsStore { $selfGroups = $this->groupManager->getUserGroupIds($self); if ($excludedGroups) { - $excludedGroups = $this->config->getAppValue('core', 'shareapi_exclude_groups_list'); + $excludedGroups = $this->config->getAppValue('core', 'shareapi_exclude_groups_list', ''); $decodedExcludeGroups = json_decode($excludedGroups, true); $excludeGroupsList = $decodedExcludeGroups ?? []; diff --git a/lib/private/DB/MigrationService.php b/lib/private/DB/MigrationService.php index 13bbe8dc5d0..22fffa596ec 100644 --- a/lib/private/DB/MigrationService.php +++ b/lib/private/DB/MigrationService.php @@ -46,34 +46,25 @@ use Psr\Log\LoggerInterface; class MigrationService { - /** @var boolean */ - private $migrationTableCreated; - /** @var array */ - private $migrations; - /** @var IOutput */ - private $output; - /** @var Connection */ - private $connection; - /** @var string */ - private $appName; - /** @var bool */ - private $checkOracle; + private bool $migrationTableCreated; + private array $migrations; + private string $migrationsPath; + private string $migrationsNamespace; + private IOutput $output; + private Connection $connection; + private string $appName; + private bool $checkOracle; /** - * MigrationService constructor. - * - * @param $appName - * @param Connection $connection - * @param AppLocator $appLocator - * @param IOutput|null $output * @throws \Exception */ - public function __construct($appName, Connection $connection, IOutput $output = null, AppLocator $appLocator = null) { + public function __construct($appName, Connection $connection, ?IOutput $output = null, ?AppLocator $appLocator = null) { $this->appName = $appName; $this->connection = $connection; - $this->output = $output; - if (null === $this->output) { + if ($output === null) { $this->output = new SimpleOutput(\OC::$server->get(LoggerInterface::class), $appName); + } else { + $this->output = $output; } if ($appName === 'core') { @@ -104,6 +95,7 @@ class MigrationService { } } } + $this->migrationTableCreated = false; } /** diff --git a/lib/private/Files/Node/File.php b/lib/private/Files/Node/File.php index e125715e6a8..d8a6741dc6e 100644 --- a/lib/private/Files/Node/File.php +++ b/lib/private/Files/Node/File.php @@ -131,7 +131,6 @@ class File extends Node implements \OCP\Files\File { $this->view->unlink($this->path); $nonExisting = new NonExistingFile($this->root, $this->view, $this->path, $fileInfo); $this->sendHooks(['postDelete'], [$nonExisting]); - $this->exists = false; $this->fileInfo = null; } else { throw new NotPermittedException(); diff --git a/lib/private/Files/Node/Folder.php b/lib/private/Files/Node/Folder.php index 9c15f0edf41..b56b7e0f851 100644 --- a/lib/private/Files/Node/Folder.php +++ b/lib/private/Files/Node/Folder.php @@ -388,7 +388,6 @@ class Folder extends Node implements \OCP\Files\Folder { $this->view->rmdir($this->path); $nonExisting = new NonExistingFolder($this->root, $this->view, $this->path, $fileInfo); $this->sendHooks(['postDelete'], [$nonExisting]); - $this->exists = false; } else { throw new NotPermittedException('No delete permission for path'); } diff --git a/tests/lib/AppFramework/Http/DispatcherTest.php b/tests/lib/AppFramework/Http/DispatcherTest.php index e1d78082a2d..8f591f26e58 100644 --- a/tests/lib/AppFramework/Http/DispatcherTest.php +++ b/tests/lib/AppFramework/Http/DispatcherTest.php @@ -89,6 +89,8 @@ class DispatcherTest extends \Test\TestCase { /** @var Dispatcher */ private $dispatcher; private $controllerMethod; + /** @var Controller|MockObject */ + private $controller; private $response; /** @var IRequest|MockObject */ private $request; -- cgit v1.2.3 From 940b5e8f0a0eaf2092a870bfa2ed48ed90a26a7e Mon Sep 17 00:00:00 2001 From: Carl Schwan Date: Wed, 22 Jun 2022 17:27:09 +0200 Subject: Fix metadata extraction The metadata extraction only happens when the size is not equal to 0, but due to a regression in FileInfo the size is always zero. This fix the regression. Signed-off-by: Carl Schwan --- lib/private/Files/FileInfo.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib/private/Files') diff --git a/lib/private/Files/FileInfo.php b/lib/private/Files/FileInfo.php index 5912eefcf9c..7a984429d1f 100644 --- a/lib/private/Files/FileInfo.php +++ b/lib/private/Files/FileInfo.php @@ -101,7 +101,7 @@ class FileInfo implements \OCP\Files\FileInfo, \ArrayAccess { $this->data = $data; $this->mount = $mount; $this->owner = $owner; - if (isset($this->data['unencrypted_size'])) { + if (isset($this->data['unencrypted_size']) && $this->data['unencrypted_size'] !== 0) { $this->rawSize = $this->data['unencrypted_size']; } else { $this->rawSize = $this->data['size'] ?? 0; -- cgit v1.2.3 From f326b54e5378b0beffe54f1de83359a71d9d167e Mon Sep 17 00:00:00 2001 From: Carl Schwan Date: Thu, 23 Jun 2022 16:18:07 +0200 Subject: Search without join on filecache_extended Signed-off-by: Carl Schwan --- lib/private/Files/Cache/CacheQueryBuilder.php | 12 ++++++++---- lib/private/Files/Cache/QuerySearchHelper.php | 2 +- 2 files changed, 9 insertions(+), 5 deletions(-) (limited to 'lib/private/Files') diff --git a/lib/private/Files/Cache/CacheQueryBuilder.php b/lib/private/Files/Cache/CacheQueryBuilder.php index b92e79aa229..496a8361d77 100644 --- a/lib/private/Files/Cache/CacheQueryBuilder.php +++ b/lib/private/Files/Cache/CacheQueryBuilder.php @@ -41,12 +41,16 @@ class CacheQueryBuilder extends QueryBuilder { parent::__construct($connection, $systemConfig, $logger); } - public function selectFileCache(string $alias = null) { + public function selectFileCache(string $alias = null, bool $joinExtendedCache = true) { $name = $alias ? $alias : 'filecache'; $this->select("$name.fileid", 'storage', 'path', 'path_hash', "$name.parent", "$name.name", 'mimetype', 'mimepart', 'size', 'mtime', - 'storage_mtime', 'encrypted', 'etag', 'permissions', 'checksum', 'metadata_etag', 'creation_time', 'upload_time', 'unencrypted_size') - ->from('filecache', $name) - ->leftJoin($name, 'filecache_extended', 'fe', $this->expr()->eq("$name.fileid", 'fe.fileid')); + 'storage_mtime', 'encrypted', 'etag', 'permissions', 'checksum', 'unencrypted_size') + ->from('filecache', $name); + + if ($joinExtendedCache) { + $this->addSelect('metadata_etag', 'creation_time', 'upload_time'); + $this->leftJoin($name, 'filecache_extended', 'fe', $this->expr()->eq("$name.fileid", 'fe.fileid')); + } $this->alias = $name; diff --git a/lib/private/Files/Cache/QuerySearchHelper.php b/lib/private/Files/Cache/QuerySearchHelper.php index e7bccbf521c..69a2944b2dd 100644 --- a/lib/private/Files/Cache/QuerySearchHelper.php +++ b/lib/private/Files/Cache/QuerySearchHelper.php @@ -103,7 +103,7 @@ class QuerySearchHelper { $builder = $this->getQueryBuilder(); - $query = $builder->selectFileCache('file'); + $query = $builder->selectFileCache('file', false); if ($this->searchBuilder->shouldJoinTags($searchQuery->getSearchOperation())) { $user = $searchQuery->getUser(); -- cgit v1.2.3 From 7d07e06bfe3fa7e33a543e652e94b6d3305efae6 Mon Sep 17 00:00:00 2001 From: Jonas Date: Tue, 28 Jun 2022 15:27:09 +0100 Subject: Check whether entry is of type ICacheEntry in Cache->remove() In some scenarios (file not in cache, but partial data of it in the object), Cache->get() might return an array, which leads to errors like "Call to a member function getId() on array". So check whether the returned entry is of type ICacheEntry before doing operations on it in Cache->remove(). Fixes: #33023 Signed-off-by: Jonas --- lib/private/Files/Cache/Cache.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib/private/Files') diff --git a/lib/private/Files/Cache/Cache.php b/lib/private/Files/Cache/Cache.php index c6cadf14f86..33a07e9b9e5 100644 --- a/lib/private/Files/Cache/Cache.php +++ b/lib/private/Files/Cache/Cache.php @@ -540,7 +540,7 @@ class Cache implements ICache { public function remove($file) { $entry = $this->get($file); - if ($entry) { + if ($entry instanceof ICacheEntry) { $query = $this->getQueryBuilder(); $query->delete('filecache') ->whereFileId($entry->getId()); -- cgit v1.2.3 From bffa67c48beced2147af196a5b63414c113aaad4 Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Tue, 21 Jun 2022 16:50:06 +0200 Subject: also use nextcloud certificate bundle when downloading from s3 Signed-off-by: Robin Appelman --- .../Files/ObjectStore/S3ConnectionTrait.php | 22 ++++++++++++---------- lib/private/Files/ObjectStore/S3ObjectTrait.php | 5 +++++ 2 files changed, 17 insertions(+), 10 deletions(-) (limited to 'lib/private/Files') diff --git a/lib/private/Files/ObjectStore/S3ConnectionTrait.php b/lib/private/Files/ObjectStore/S3ConnectionTrait.php index c3836749c6d..a1dd8ba3909 100644 --- a/lib/private/Files/ObjectStore/S3ConnectionTrait.php +++ b/lib/private/Files/ObjectStore/S3ConnectionTrait.php @@ -121,15 +121,6 @@ trait S3ConnectionTrait { ) ); - // since we store the certificate bundles on the primary storage, we can't get the bundle while setting up the primary storage - if (!isset($this->params['primary_storage'])) { - /** @var ICertificateManager $certManager */ - $certManager = \OC::$server->get(ICertificateManager::class); - $certPath = $certManager->getAbsoluteBundlePath(); - } else { - $certPath = \OC::$SERVERROOT . '/resources/config/ca-bundle.crt'; - } - $options = [ 'version' => isset($this->params['version']) ? $this->params['version'] : 'latest', 'credentials' => $provider, @@ -139,7 +130,7 @@ trait S3ConnectionTrait { 'signature_provider' => \Aws\or_chain([self::class, 'legacySignatureProvider'], ClientResolver::_default_signature_provider()), 'csm' => false, 'use_arn_region' => false, - 'http' => ['verify' => $certPath], + 'http' => ['verify' => $this->getCertificateBundlePath()], ]; if ($this->getProxy()) { $options['http']['proxy'] = $this->getProxy(); @@ -218,4 +209,15 @@ trait S3ConnectionTrait { return new RejectedPromise(new CredentialsException($msg)); }; } + + protected function getCertificateBundlePath(): string { + // since we store the certificate bundles on the primary storage, we can't get the bundle while setting up the primary storage + if (!isset($this->params['primary_storage'])) { + /** @var ICertificateManager $certManager */ + $certManager = \OC::$server->get(ICertificateManager::class); + return $certManager->getAbsoluteBundlePath(); + } else { + return \OC::$SERVERROOT . '/resources/config/ca-bundle.crt'; + } + } } diff --git a/lib/private/Files/ObjectStore/S3ObjectTrait.php b/lib/private/Files/ObjectStore/S3ObjectTrait.php index 4e54a26e98a..a4efc687236 100644 --- a/lib/private/Files/ObjectStore/S3ObjectTrait.php +++ b/lib/private/Files/ObjectStore/S3ObjectTrait.php @@ -43,6 +43,8 @@ trait S3ObjectTrait { */ abstract protected function getConnection(); + abstract protected function getCertificateBundlePath(): string; + /** * @param string $urn the unified resource name used to identify the object * @return resource stream with the read data @@ -68,6 +70,9 @@ trait S3ObjectTrait { 'protocol_version' => $request->getProtocolVersion(), 'header' => $headers, ], + 'ssl' => [ + 'cafile' => $this->getCertificateBundlePath() + ] ]; if ($this->getProxy()) { -- cgit v1.2.3 From f1486890d7391cfe15f210f4d7d38d18a7573ec4 Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Tue, 5 Jul 2022 16:06:55 +0200 Subject: only use nextcloud bundle when explicitly enabled Signed-off-by: Robin Appelman --- .../Files/ObjectStore/S3ConnectionTrait.php | 23 +++++++++++++--------- lib/private/Files/ObjectStore/S3ObjectTrait.php | 2 +- 2 files changed, 15 insertions(+), 10 deletions(-) (limited to 'lib/private/Files') diff --git a/lib/private/Files/ObjectStore/S3ConnectionTrait.php b/lib/private/Files/ObjectStore/S3ConnectionTrait.php index a1dd8ba3909..8286321450d 100644 --- a/lib/private/Files/ObjectStore/S3ConnectionTrait.php +++ b/lib/private/Files/ObjectStore/S3ConnectionTrait.php @@ -28,6 +28,7 @@ * along with this program. If not, see . * */ + namespace OC\Files\ObjectStore; use Aws\ClientResolver; @@ -143,7 +144,7 @@ trait S3ConnectionTrait { if (!$this->connection::isBucketDnsCompatible($this->bucket)) { $logger = \OC::$server->get(LoggerInterface::class); $logger->debug('Bucket "' . $this->bucket . '" This bucket name is not dns compatible, it may contain invalid characters.', - ['app' => 'objectstore']); + ['app' => 'objectstore']); } if ($this->params['verify_bucket_exists'] && !$this->connection->doesBucketExist($this->bucket)) { @@ -194,7 +195,7 @@ trait S3ConnectionTrait { /** * This function creates a credential provider based on user parameter file */ - protected function paramCredentialProvider() : callable { + protected function paramCredentialProvider(): callable { return function () { $key = empty($this->params['key']) ? null : $this->params['key']; $secret = empty($this->params['secret']) ? null : $this->params['secret']; @@ -210,14 +211,18 @@ trait S3ConnectionTrait { }; } - protected function getCertificateBundlePath(): string { - // since we store the certificate bundles on the primary storage, we can't get the bundle while setting up the primary storage - if (!isset($this->params['primary_storage'])) { - /** @var ICertificateManager $certManager */ - $certManager = \OC::$server->get(ICertificateManager::class); - return $certManager->getAbsoluteBundlePath(); + protected function getCertificateBundlePath(): ?string { + if ((int)($this->params['use_nextcloud_bundle'] ?? "0")) { + // since we store the certificate bundles on the primary storage, we can't get the bundle while setting up the primary storage + if (!isset($this->params['primary_storage'])) { + /** @var ICertificateManager $certManager */ + $certManager = \OC::$server->get(ICertificateManager::class); + return $certManager->getAbsoluteBundlePath(); + } else { + return \OC::$SERVERROOT . '/resources/config/ca-bundle.crt'; + } } else { - return \OC::$SERVERROOT . '/resources/config/ca-bundle.crt'; + return null; } } } diff --git a/lib/private/Files/ObjectStore/S3ObjectTrait.php b/lib/private/Files/ObjectStore/S3ObjectTrait.php index a4efc687236..0b2fd95c652 100644 --- a/lib/private/Files/ObjectStore/S3ObjectTrait.php +++ b/lib/private/Files/ObjectStore/S3ObjectTrait.php @@ -43,7 +43,7 @@ trait S3ObjectTrait { */ abstract protected function getConnection(); - abstract protected function getCertificateBundlePath(): string; + abstract protected function getCertificateBundlePath(): ?string; /** * @param string $urn the unified resource name used to identify the object -- cgit v1.2.3 From 74e9ef0fb16e9561b5e77878975fca1a51941931 Mon Sep 17 00:00:00 2001 From: Jonas Date: Tue, 12 Jul 2022 13:13:36 +0100 Subject: Fix listening for circle events in SetupManager So far, SetupManager listened for deprecated events that are no longer triggered. Instead, use the circle events that actually get triggered when adding or removing a circle or circle member. Also, these events get triggered on each instance of a globalscale setup. Fixes: #33210 Signed-off-by: Jonas --- lib/private/Files/SetupManager.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'lib/private/Files') diff --git a/lib/private/Files/SetupManager.php b/lib/private/Files/SetupManager.php index 876514b473c..f19c9bfa062 100644 --- a/lib/private/Files/SetupManager.php +++ b/lib/private/Files/SetupManager.php @@ -554,10 +554,10 @@ class SetupManager { }); $genericEvents = [ - '\OCA\Circles::onCircleCreation', - '\OCA\Circles::onCircleDestruction', - '\OCA\Circles::onMemberNew', - '\OCA\Circles::onMemberLeaving', + 'OCA\Circles\Events\CreatingCircleEvent', + 'OCA\Circles\Events\DestroyingCircleEvent', + 'OCA\Circles\Events\AddingCircleMemberEvent', + 'OCA\Circles\Events\RemovingCircleMemberEvent', ]; foreach ($genericEvents as $genericEvent) { -- cgit v1.2.3 From d5c23dbb9fed1a1b958e07ebdb7202c37bddc074 Mon Sep 17 00:00:00 2001 From: Carl Schwan Date: Wed, 29 Jun 2022 15:34:06 +0200 Subject: Move CappedMemoryCache to OCP This is an helpful helper that should be used in more place than just server and this is already the case with groupfodlers, deck, user_oidc and more using it, so let's make it public Signed-off-by: Carl Schwan --- apps/files_external/lib/Lib/Storage/AmazonS3.php | 2 +- apps/files_external/lib/Lib/Storage/SMB.php | 2 +- apps/files_external/lib/Lib/Storage/Swift.php | 2 +- apps/files_sharing/lib/MountProvider.php | 2 +- apps/files_sharing/lib/SharedMount.php | 2 +- apps/user_ldap/lib/Group_LDAP.php | 2 +- apps/user_ldap/lib/Helper.php | 2 +- apps/user_ldap/lib/User/Manager.php | 2 +- apps/workflowengine/lib/Manager.php | 2 +- lib/composer/composer/autoload_classmap.php | 1 + lib/composer/composer/autoload_static.php | 1 + lib/private/Accounts/AccountManager.php | 2 +- lib/private/AllConfig.php | 2 +- .../Token/PublicKeyTokenProvider.php | 2 +- lib/private/Cache/CappedMemoryCache.php | 1 + lib/private/Diagnostics/QueryLogger.php | 2 +- lib/private/Encryption/File.php | 2 +- lib/private/Files/AppData/AppData.php | 2 +- lib/private/Files/Config/UserMountCache.php | 2 +- lib/private/Files/Filesystem.php | 2 +- lib/private/Files/Mount/Manager.php | 2 +- lib/private/Files/Node/Root.php | 2 +- lib/private/Files/Storage/Wrapper/Encoding.php | 2 +- lib/private/Share20/Manager.php | 2 +- lib/private/User/Database.php | 2 +- lib/public/Cache/CappedMemoryCache.php | 160 +++++++++++++++++++++ tests/lib/App/InfoParserTest.php | 6 +- tests/lib/Cache/CappedMemoryCacheTest.php | 4 +- tests/lib/Files/Config/UserMountCacheTest.php | 2 +- tests/lib/Files/Node/RootTest.php | 2 +- tests/lib/Files/ViewTest.php | 2 +- 31 files changed, 193 insertions(+), 30 deletions(-) create mode 100644 lib/public/Cache/CappedMemoryCache.php (limited to 'lib/private/Files') diff --git a/apps/files_external/lib/Lib/Storage/AmazonS3.php b/apps/files_external/lib/Lib/Storage/AmazonS3.php index e7a8c5f9329..6ebbed33a87 100644 --- a/apps/files_external/lib/Lib/Storage/AmazonS3.php +++ b/apps/files_external/lib/Lib/Storage/AmazonS3.php @@ -44,7 +44,7 @@ use Aws\S3\Exception\S3Exception; use Aws\S3\S3Client; use Icewind\Streams\CallbackWrapper; use Icewind\Streams\IteratorDirectory; -use OC\Cache\CappedMemoryCache; +use OCP\Cache\CappedMemoryCache; use OC\Files\Cache\CacheEntry; use OC\Files\ObjectStore\S3ConnectionTrait; use OC\Files\ObjectStore\S3ObjectTrait; diff --git a/apps/files_external/lib/Lib/Storage/SMB.php b/apps/files_external/lib/Lib/Storage/SMB.php index bdb9b4f9c8f..6c59263ddd5 100644 --- a/apps/files_external/lib/Lib/Storage/SMB.php +++ b/apps/files_external/lib/Lib/Storage/SMB.php @@ -54,7 +54,7 @@ use Icewind\SMB\ServerFactory; use Icewind\SMB\System; use Icewind\Streams\CallbackWrapper; use Icewind\Streams\IteratorDirectory; -use OC\Cache\CappedMemoryCache; +use OCP\Cache\CappedMemoryCache; use OC\Files\Filesystem; use OC\Files\Storage\Common; use OCA\Files_External\Lib\Notify\SMBNotifyHandler; diff --git a/apps/files_external/lib/Lib/Storage/Swift.php b/apps/files_external/lib/Lib/Storage/Swift.php index fb93c6cdce2..cc0ee6c7c21 100644 --- a/apps/files_external/lib/Lib/Storage/Swift.php +++ b/apps/files_external/lib/Lib/Storage/Swift.php @@ -200,7 +200,7 @@ class Swift extends \OC\Files\Storage\Common { $this->params = $params; // FIXME: private class... - $this->objectCache = new \OC\Cache\CappedMemoryCache(); + $this->objectCache = new \OCP\Cache\CappedMemoryCache(); $this->connectionFactory = new SwiftFactory( \OC::$server->getMemCacheFactory()->createDistributed('swift/'), $this->params, diff --git a/apps/files_sharing/lib/MountProvider.php b/apps/files_sharing/lib/MountProvider.php index d27f9e5e0da..5817ece6809 100644 --- a/apps/files_sharing/lib/MountProvider.php +++ b/apps/files_sharing/lib/MountProvider.php @@ -28,7 +28,7 @@ */ namespace OCA\Files_Sharing; -use OC\Cache\CappedMemoryCache; +use OCP\Cache\CappedMemoryCache; use OC\Files\View; use OCA\Files_Sharing\Event\ShareMountedEvent; use OCP\EventDispatcher\IEventDispatcher; diff --git a/apps/files_sharing/lib/SharedMount.php b/apps/files_sharing/lib/SharedMount.php index 95ff66c4b71..676e253344f 100644 --- a/apps/files_sharing/lib/SharedMount.php +++ b/apps/files_sharing/lib/SharedMount.php @@ -29,7 +29,7 @@ namespace OCA\Files_Sharing; -use OC\Cache\CappedMemoryCache; +use OCP\Cache\CappedMemoryCache; use OC\Files\Filesystem; use OC\Files\Mount\MountPoint; use OC\Files\Mount\MoveableMount; diff --git a/apps/user_ldap/lib/Group_LDAP.php b/apps/user_ldap/lib/Group_LDAP.php index 8fcb10cb850..5e3d3afc140 100644 --- a/apps/user_ldap/lib/Group_LDAP.php +++ b/apps/user_ldap/lib/Group_LDAP.php @@ -45,7 +45,7 @@ namespace OCA\User_LDAP; use Closure; use Exception; use OC; -use OC\Cache\CappedMemoryCache; +use OCP\Cache\CappedMemoryCache; use OC\ServerNotAvailableException; use OCP\Group\Backend\IGetDisplayNameBackend; use OCP\Group\Backend\IDeleteGroupBackend; diff --git a/apps/user_ldap/lib/Helper.php b/apps/user_ldap/lib/Helper.php index 3ca5de67874..6668338d195 100644 --- a/apps/user_ldap/lib/Helper.php +++ b/apps/user_ldap/lib/Helper.php @@ -29,7 +29,7 @@ */ namespace OCA\User_LDAP; -use OC\Cache\CappedMemoryCache; +use OCP\Cache\CappedMemoryCache; use OCP\DB\QueryBuilder\IQueryBuilder; use OCP\IConfig; use OCP\IDBConnection; diff --git a/apps/user_ldap/lib/User/Manager.php b/apps/user_ldap/lib/User/Manager.php index e52b093f5af..655463a0ecd 100644 --- a/apps/user_ldap/lib/User/Manager.php +++ b/apps/user_ldap/lib/User/Manager.php @@ -28,7 +28,7 @@ */ namespace OCA\User_LDAP\User; -use OC\Cache\CappedMemoryCache; +use OCP\Cache\CappedMemoryCache; use OCA\User_LDAP\Access; use OCA\User_LDAP\FilesystemHelper; use OCP\IAvatarManager; diff --git a/apps/workflowengine/lib/Manager.php b/apps/workflowengine/lib/Manager.php index f6c3e3086c2..659fd2421c1 100644 --- a/apps/workflowengine/lib/Manager.php +++ b/apps/workflowengine/lib/Manager.php @@ -30,7 +30,7 @@ namespace OCA\WorkflowEngine; use Doctrine\DBAL\Exception; -use OC\Cache\CappedMemoryCache; +use OCP\Cache\CappedMemoryCache; use OCA\WorkflowEngine\AppInfo\Application; use OCA\WorkflowEngine\Check\FileMimeType; use OCA\WorkflowEngine\Check\FileName; diff --git a/lib/composer/composer/autoload_classmap.php b/lib/composer/composer/autoload_classmap.php index e3572aa833c..e8510bc865e 100644 --- a/lib/composer/composer/autoload_classmap.php +++ b/lib/composer/composer/autoload_classmap.php @@ -116,6 +116,7 @@ return array( 'OCP\\BackgroundJob\\QueuedJob' => $baseDir . '/lib/public/BackgroundJob/QueuedJob.php', 'OCP\\BackgroundJob\\TimedJob' => $baseDir . '/lib/public/BackgroundJob/TimedJob.php', 'OCP\\Broadcast\\Events\\IBroadcastEvent' => $baseDir . '/lib/public/Broadcast/Events/IBroadcastEvent.php', + 'OCP\\Cache\\CappedMemoryCache' => $baseDir . '/lib/public/Cache/CappedMemoryCache.php', 'OCP\\Calendar\\BackendTemporarilyUnavailableException' => $baseDir . '/lib/public/Calendar/BackendTemporarilyUnavailableException.php', 'OCP\\Calendar\\Exceptions\\CalendarException' => $baseDir . '/lib/public/Calendar/Exceptions/CalendarException.php', 'OCP\\Calendar\\ICalendar' => $baseDir . '/lib/public/Calendar/ICalendar.php', diff --git a/lib/composer/composer/autoload_static.php b/lib/composer/composer/autoload_static.php index 658f2cdfe2d..26b25e89aae 100644 --- a/lib/composer/composer/autoload_static.php +++ b/lib/composer/composer/autoload_static.php @@ -149,6 +149,7 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2 'OCP\\BackgroundJob\\QueuedJob' => __DIR__ . '/../../..' . '/lib/public/BackgroundJob/QueuedJob.php', 'OCP\\BackgroundJob\\TimedJob' => __DIR__ . '/../../..' . '/lib/public/BackgroundJob/TimedJob.php', 'OCP\\Broadcast\\Events\\IBroadcastEvent' => __DIR__ . '/../../..' . '/lib/public/Broadcast/Events/IBroadcastEvent.php', + 'OCP\\Cache\\CappedMemoryCache' => __DIR__ . '/../../..' . '/lib/public/Cache/CappedMemoryCache.php', 'OCP\\Calendar\\BackendTemporarilyUnavailableException' => __DIR__ . '/../../..' . '/lib/public/Calendar/BackendTemporarilyUnavailableException.php', 'OCP\\Calendar\\Exceptions\\CalendarException' => __DIR__ . '/../../..' . '/lib/public/Calendar/Exceptions/CalendarException.php', 'OCP\\Calendar\\ICalendar' => __DIR__ . '/../../..' . '/lib/public/Calendar/ICalendar.php', diff --git a/lib/private/Accounts/AccountManager.php b/lib/private/Accounts/AccountManager.php index bdf33d73c29..20e0add1ccb 100644 --- a/lib/private/Accounts/AccountManager.php +++ b/lib/private/Accounts/AccountManager.php @@ -42,7 +42,7 @@ use libphonenumber\PhoneNumber; use libphonenumber\PhoneNumberFormat; use libphonenumber\PhoneNumberUtil; use OC\Profile\TProfileHelper; -use OC\Cache\CappedMemoryCache; +use OCP\Cache\CappedMemoryCache; use OCA\Settings\BackgroundJobs\VerifyUserData; use OCP\Accounts\IAccount; use OCP\Accounts\IAccountManager; diff --git a/lib/private/AllConfig.php b/lib/private/AllConfig.php index f282baee146..7e01e0ca815 100644 --- a/lib/private/AllConfig.php +++ b/lib/private/AllConfig.php @@ -32,7 +32,7 @@ */ namespace OC; -use OC\Cache\CappedMemoryCache; +use OCP\Cache\CappedMemoryCache; use OCP\DB\QueryBuilder\IQueryBuilder; use OCP\IConfig; use OCP\IDBConnection; diff --git a/lib/private/Authentication/Token/PublicKeyTokenProvider.php b/lib/private/Authentication/Token/PublicKeyTokenProvider.php index 96bf9a86087..d21179a35c1 100644 --- a/lib/private/Authentication/Token/PublicKeyTokenProvider.php +++ b/lib/private/Authentication/Token/PublicKeyTokenProvider.php @@ -34,7 +34,7 @@ use OC\Authentication\Exceptions\InvalidTokenException; use OC\Authentication\Exceptions\TokenPasswordExpiredException; use OC\Authentication\Exceptions\PasswordlessTokenException; use OC\Authentication\Exceptions\WipeTokenException; -use OC\Cache\CappedMemoryCache; +use OCP\Cache\CappedMemoryCache; use OCP\AppFramework\Db\DoesNotExistException; use OCP\AppFramework\Utility\ITimeFactory; use OCP\IConfig; diff --git a/lib/private/Cache/CappedMemoryCache.php b/lib/private/Cache/CappedMemoryCache.php index 6063b5e7110..31e8ef3e720 100644 --- a/lib/private/Cache/CappedMemoryCache.php +++ b/lib/private/Cache/CappedMemoryCache.php @@ -28,6 +28,7 @@ use OCP\ICache; * * Uses a simple FIFO expiry mechanism * @template T + * @deprecated use OCP\Cache\CappedMemoryCache instead */ class CappedMemoryCache implements ICache, \ArrayAccess { private $capacity; diff --git a/lib/private/Diagnostics/QueryLogger.php b/lib/private/Diagnostics/QueryLogger.php index 40d68d94ae3..5f401751077 100644 --- a/lib/private/Diagnostics/QueryLogger.php +++ b/lib/private/Diagnostics/QueryLogger.php @@ -24,7 +24,7 @@ */ namespace OC\Diagnostics; -use OC\Cache\CappedMemoryCache; +use OCP\Cache\CappedMemoryCache; use OCP\Diagnostics\IQueryLogger; class QueryLogger implements IQueryLogger { diff --git a/lib/private/Encryption/File.php b/lib/private/Encryption/File.php index 2d7e23a8883..844059923bd 100644 --- a/lib/private/Encryption/File.php +++ b/lib/private/Encryption/File.php @@ -27,7 +27,7 @@ */ namespace OC\Encryption; -use OC\Cache\CappedMemoryCache; +use OCP\Cache\CappedMemoryCache; use OCA\Files_External\Service\GlobalStoragesService; use OCP\Files\IRootFolder; use OCP\Files\NotFoundException; diff --git a/lib/private/Files/AppData/AppData.php b/lib/private/Files/AppData/AppData.php index 471de799c2f..237fcb42e03 100644 --- a/lib/private/Files/AppData/AppData.php +++ b/lib/private/Files/AppData/AppData.php @@ -26,7 +26,7 @@ declare(strict_types=1); */ namespace OC\Files\AppData; -use OC\Cache\CappedMemoryCache; +use OCP\Cache\CappedMemoryCache; use OC\Files\SimpleFS\SimpleFolder; use OC\SystemConfig; use OCP\Files\Folder; diff --git a/lib/private/Files/Config/UserMountCache.php b/lib/private/Files/Config/UserMountCache.php index c326eeb0b6c..f26c42938d3 100644 --- a/lib/private/Files/Config/UserMountCache.php +++ b/lib/private/Files/Config/UserMountCache.php @@ -28,7 +28,7 @@ */ namespace OC\Files\Config; -use OC\Cache\CappedMemoryCache; +use OCP\Cache\CappedMemoryCache; use OCA\Files_Sharing\SharedMount; use OCP\DB\QueryBuilder\IQueryBuilder; use OCP\Files\Config\ICachedMountFileInfo; diff --git a/lib/private/Files/Filesystem.php b/lib/private/Files/Filesystem.php index 20b44e2736a..9542666b03c 100644 --- a/lib/private/Files/Filesystem.php +++ b/lib/private/Files/Filesystem.php @@ -37,7 +37,7 @@ */ namespace OC\Files; -use OC\Cache\CappedMemoryCache; +use OCP\Cache\CappedMemoryCache; use OC\Files\Mount\MountPoint; use OC\User\NoUserException; use OCP\EventDispatcher\IEventDispatcher; diff --git a/lib/private/Files/Mount/Manager.php b/lib/private/Files/Mount/Manager.php index 69285018d17..9ba0e504058 100644 --- a/lib/private/Files/Mount/Manager.php +++ b/lib/private/Files/Mount/Manager.php @@ -29,7 +29,7 @@ declare(strict_types=1); namespace OC\Files\Mount; -use OC\Cache\CappedMemoryCache; +use OCP\Cache\CappedMemoryCache; use OC\Files\Filesystem; use OC\Files\SetupManager; use OC\Files\SetupManagerFactory; diff --git a/lib/private/Files/Node/Root.php b/lib/private/Files/Node/Root.php index 6dd65a4291d..9e3d4afd8d8 100644 --- a/lib/private/Files/Node/Root.php +++ b/lib/private/Files/Node/Root.php @@ -32,7 +32,7 @@ namespace OC\Files\Node; -use OC\Cache\CappedMemoryCache; +use OCP\Cache\CappedMemoryCache; use OC\Files\FileInfo; use OC\Files\Mount\Manager; use OC\Files\Mount\MountPoint; diff --git a/lib/private/Files/Storage/Wrapper/Encoding.php b/lib/private/Files/Storage/Wrapper/Encoding.php index d6201dc8877..ac9cc248ce6 100644 --- a/lib/private/Files/Storage/Wrapper/Encoding.php +++ b/lib/private/Files/Storage/Wrapper/Encoding.php @@ -28,7 +28,7 @@ */ namespace OC\Files\Storage\Wrapper; -use OC\Cache\CappedMemoryCache; +use OCP\Cache\CappedMemoryCache; use OC\Files\Filesystem; use OCP\Files\Storage\IStorage; use OCP\ICache; diff --git a/lib/private/Share20/Manager.php b/lib/private/Share20/Manager.php index b1a9783d7b8..905a006372f 100644 --- a/lib/private/Share20/Manager.php +++ b/lib/private/Share20/Manager.php @@ -41,7 +41,7 @@ */ namespace OC\Share20; -use OC\Cache\CappedMemoryCache; +use OCP\Cache\CappedMemoryCache; use OC\Files\Mount\MoveableMount; use OC\KnownUser\KnownUserService; use OC\Share20\Exception\ProviderException; diff --git a/lib/private/User/Database.php b/lib/private/User/Database.php index fce7551c242..0b38f04bfe3 100644 --- a/lib/private/User/Database.php +++ b/lib/private/User/Database.php @@ -45,7 +45,7 @@ declare(strict_types=1); */ namespace OC\User; -use OC\Cache\CappedMemoryCache; +use OCP\Cache\CappedMemoryCache; use OCP\EventDispatcher\IEventDispatcher; use OCP\IDBConnection; use OCP\Security\Events\ValidatePasswordPolicyEvent; diff --git a/lib/public/Cache/CappedMemoryCache.php b/lib/public/Cache/CappedMemoryCache.php new file mode 100644 index 00000000000..6699600d42c --- /dev/null +++ b/lib/public/Cache/CappedMemoryCache.php @@ -0,0 +1,160 @@ + + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * 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, version 3, + * along with this program. If not, see + * + */ +namespace OCP\Cache; + +use OCP\ICache; + +/** + * In-memory cache with a capacity limit to keep memory usage in check + * + * Uses a simple FIFO expiry mechanism + * + * @since 25.0.0 + * @template T + */ +class CappedMemoryCache implements ICache, \ArrayAccess { + private int $capacity; + /** @var T[] */ + private array $cache = []; + + /** + * @inheritdoc + * @since 25.0.0 + */ + public function __construct(int $capacity = 512) { + $this->capacity = $capacity; + } + + /** + * @inheritdoc + * @since 25.0.0 + */ + public function hasKey($key): bool { + return isset($this->cache[$key]); + } + + /** + * @return ?T + * @since 25.0.0 + */ + public function get($key) { + return $this->cache[$key] ?? null; + } + + /** + * @inheritdoc + * @param string $key + * @param T $value + * @param int $ttl + * @since 25.0.0 + * @return bool + */ + public function set($key, $value, $ttl = 0): bool { + if (is_null($key)) { + $this->cache[] = $value; + } else { + $this->cache[$key] = $value; + } + $this->garbageCollect(); + return true; + } + + /** + * @since 25.0.0 + */ + public function remove($key): bool { + unset($this->cache[$key]); + return true; + } + + /** + * @inheritdoc + * @since 25.0.0 + */ + public function clear($prefix = ''): bool { + $this->cache = []; + return true; + } + + /** + * @since 25.0.0 + */ + public function offsetExists($offset): bool { + return $this->hasKey($offset); + } + + /** + * @inheritdoc + * @return T + * @since 25.0.0 + */ + #[\ReturnTypeWillChange] + public function &offsetGet($offset) { + return $this->cache[$offset]; + } + + /** + * @inheritdoc + * @param string $offset + * @param T $value + * @since 25.0.0 + */ + public function offsetSet($offset, $value): void { + $this->set($offset, $value); + } + + /** + * @inheritdoc + * @since 25.0.0 + */ + public function offsetUnset($offset): void { + $this->remove($offset); + } + + /** + * @return T[] + * @since 25.0.0 + */ + public function getData(): array { + return $this->cache; + } + + + /** + * @since 25.0.0 + */ + private function garbageCollect(): void { + while (count($this->cache) > $this->capacity) { + reset($this->cache); + $key = key($this->cache); + $this->remove($key); + } + } + + /** + * @inheritdoc + * @since 25.0.0 + */ + public static function isAvailable(): bool { + return true; + } +} diff --git a/tests/lib/App/InfoParserTest.php b/tests/lib/App/InfoParserTest.php index 8de0f4cfd4f..bc561611501 100644 --- a/tests/lib/App/InfoParserTest.php +++ b/tests/lib/App/InfoParserTest.php @@ -11,16 +11,16 @@ namespace Test\App; use OC; use OC\App\InfoParser; use Test\TestCase; +use OCP\Cache\CappedMemoryCache; class InfoParserTest extends TestCase { - /** @var OC\Cache\CappedMemoryCache */ + /** @var OCP\Cache\CappedMemoryCache */ private static $cache; public static function setUpBeforeClass(): void { - self::$cache = new OC\Cache\CappedMemoryCache(); + self::$cache = new CappedMemoryCache(); } - public function parserTest($expectedJson, $xmlFile, $cache = null) { $parser = new InfoParser($cache); diff --git a/tests/lib/Cache/CappedMemoryCacheTest.php b/tests/lib/Cache/CappedMemoryCacheTest.php index db0d2bd1193..b9d10b66100 100644 --- a/tests/lib/Cache/CappedMemoryCacheTest.php +++ b/tests/lib/Cache/CappedMemoryCacheTest.php @@ -30,11 +30,11 @@ namespace Test\Cache; class CappedMemoryCacheTest extends TestCache { protected function setUp(): void { parent::setUp(); - $this->instance = new \OC\Cache\CappedMemoryCache(); + $this->instance = new \OCP\Cache\CappedMemoryCache(); } public function testSetOverCap() { - $instance = new \OC\Cache\CappedMemoryCache(3); + $instance = new \OCP\Cache\CappedMemoryCache(3); $instance->set('1', 'a'); $instance->set('2', 'b'); diff --git a/tests/lib/Files/Config/UserMountCacheTest.php b/tests/lib/Files/Config/UserMountCacheTest.php index 221159bc983..8b26b309daa 100644 --- a/tests/lib/Files/Config/UserMountCacheTest.php +++ b/tests/lib/Files/Config/UserMountCacheTest.php @@ -11,7 +11,7 @@ namespace Test\Files\Config; use OC\DB\QueryBuilder\Literal; use OC\Files\Mount\MountPoint; use OC\Files\Storage\Storage; -use OC\Cache\CappedMemoryCache; +use OCP\Cache\CappedMemoryCache; use OC\User\Manager; use OCP\EventDispatcher\IEventDispatcher; use OCP\Files\Config\ICachedMountInfo; diff --git a/tests/lib/Files/Node/RootTest.php b/tests/lib/Files/Node/RootTest.php index ee86eab5675..5d8e2a4ac62 100644 --- a/tests/lib/Files/Node/RootTest.php +++ b/tests/lib/Files/Node/RootTest.php @@ -8,7 +8,7 @@ namespace Test\Files\Node; -use OC\Cache\CappedMemoryCache; +use OCP\Cache\CappedMemoryCache; use OC\Files\FileInfo; use OC\Files\Mount\Manager; use OC\Files\Node\Folder; diff --git a/tests/lib/Files/ViewTest.php b/tests/lib/Files/ViewTest.php index 37cd8414a05..86101d79a1e 100644 --- a/tests/lib/Files/ViewTest.php +++ b/tests/lib/Files/ViewTest.php @@ -7,7 +7,7 @@ namespace Test\Files; -use OC\Cache\CappedMemoryCache; +use OCP\Cache\CappedMemoryCache; use OC\Files\Cache\Watcher; use OC\Files\Filesystem; use OC\Files\Mount\MountPoint; -- cgit v1.2.3 From 19a36b58a69f9a8cc31da213657fdf828d9fdb3c Mon Sep 17 00:00:00 2001 From: Carl Schwan Date: Wed, 22 Jun 2022 12:05:26 +0200 Subject: Add typing to SimpleFS - Fix putContent sometimes return a bool and sometimes nothing Signed-off-by: Carl Schwan --- lib/private/Files/SimpleFS/NewSimpleFile.php | 36 ++++++++---------------- lib/private/Files/SimpleFS/SimpleFile.php | 42 ++++++++-------------------- lib/private/Files/SimpleFS/SimpleFolder.php | 13 +++++---- lib/public/Files/SimpleFS/ISimpleFile.php | 22 ++++++--------- lib/public/Files/SimpleFS/ISimpleFolder.php | 17 ++++------- lib/public/Files/SimpleFS/ISimpleRoot.php | 4 --- lib/public/Files/SimpleFS/InMemoryFile.php | 24 +++++++--------- tests/lib/Avatar/UserAvatarTest.php | 19 +++++++------ tests/lib/Preview/GeneratorTest.php | 8 ++++++ 9 files changed, 74 insertions(+), 111 deletions(-) (limited to 'lib/private/Files') diff --git a/lib/private/Files/SimpleFS/NewSimpleFile.php b/lib/private/Files/SimpleFS/NewSimpleFile.php index 76fc69ebbe7..5fdd794ba98 100644 --- a/lib/private/Files/SimpleFS/NewSimpleFile.php +++ b/lib/private/Files/SimpleFS/NewSimpleFile.php @@ -34,15 +34,12 @@ use OCP\Files\NotPermittedException; use OCP\Files\SimpleFS\ISimpleFile; class NewSimpleFile implements ISimpleFile { - private $parentFolder; - private $name; - /** @var File|null */ - private $file = null; + private Folder $parentFolder; + private string $name; + private ?File $file = null; /** * File constructor. - * - * @param File $file */ public function __construct(Folder $parentFolder, string $name) { $this->parentFolder = $parentFolder; @@ -51,19 +48,15 @@ class NewSimpleFile implements ISimpleFile { /** * Get the name - * - * @return string */ - public function getName() { + public function getName(): string { return $this->name; } /** * Get the size in bytes - * - * @return int */ - public function getSize() { + public function getSize(): int { if ($this->file) { return $this->file->getSize(); } else { @@ -73,10 +66,8 @@ class NewSimpleFile implements ISimpleFile { /** * Get the ETag - * - * @return string */ - public function getETag() { + public function getETag(): string { if ($this->file) { return $this->file->getEtag(); } else { @@ -86,10 +77,8 @@ class NewSimpleFile implements ISimpleFile { /** * Get the last modification time - * - * @return int */ - public function getMTime() { + public function getMTime(): int { if ($this->file) { return $this->file->getMTime(); } else { @@ -100,11 +89,10 @@ class NewSimpleFile implements ISimpleFile { /** * Get the content * - * @return string * @throws NotFoundException * @throws NotPermittedException */ - public function getContent() { + public function getContent(): string { if ($this->file) { $result = $this->file->getContent(); @@ -125,7 +113,7 @@ class NewSimpleFile implements ISimpleFile { * @throws NotPermittedException * @throws NotFoundException */ - public function putContent($data) { + public function putContent($data): void { try { if ($this->file) { $this->file->putContent($data); @@ -147,7 +135,7 @@ class NewSimpleFile implements ISimpleFile { * * @throws NotFoundException */ - private function checkFile() { + private function checkFile(): void { $cur = $this->file; while ($cur->stat() === false) { @@ -171,7 +159,7 @@ class NewSimpleFile implements ISimpleFile { * * @throws NotPermittedException */ - public function delete() { + public function delete(): void { if ($this->file) { $this->file->delete(); } @@ -182,7 +170,7 @@ class NewSimpleFile implements ISimpleFile { * * @return string */ - public function getMimeType() { + public function getMimeType(): string { if ($this->file) { return $this->file->getMimeType(); } else { diff --git a/lib/private/Files/SimpleFS/SimpleFile.php b/lib/private/Files/SimpleFS/SimpleFile.php index 21a2fd92dcb..a07871337a6 100644 --- a/lib/private/Files/SimpleFS/SimpleFile.php +++ b/lib/private/Files/SimpleFS/SimpleFile.php @@ -30,52 +30,37 @@ use OCP\Files\NotPermittedException; use OCP\Files\SimpleFS\ISimpleFile; class SimpleFile implements ISimpleFile { + private File $file; - /** @var File $file */ - private $file; - - /** - * File constructor. - * - * @param File $file - */ public function __construct(File $file) { $this->file = $file; } /** * Get the name - * - * @return string */ - public function getName() { + public function getName(): string { return $this->file->getName(); } /** * Get the size in bytes - * - * @return int */ - public function getSize() { + public function getSize(): int { return $this->file->getSize(); } /** * Get the ETag - * - * @return string */ - public function getETag() { + public function getETag(): string { return $this->file->getEtag(); } /** * Get the last modification time - * - * @return int */ - public function getMTime() { + public function getMTime(): int { return $this->file->getMTime(); } @@ -84,9 +69,8 @@ class SimpleFile implements ISimpleFile { * * @throws NotPermittedException * @throws NotFoundException - * @return string */ - public function getContent() { + public function getContent(): string { $result = $this->file->getContent(); if ($result === false) { @@ -103,9 +87,9 @@ class SimpleFile implements ISimpleFile { * @throws NotPermittedException * @throws NotFoundException */ - public function putContent($data) { + public function putContent($data): void { try { - return $this->file->putContent($data); + $this->file->putContent($data); } catch (NotFoundException $e) { $this->checkFile(); } @@ -121,7 +105,7 @@ class SimpleFile implements ISimpleFile { * * @throws NotFoundException */ - private function checkFile() { + private function checkFile(): void { $cur = $this->file; while ($cur->stat() === false) { @@ -145,16 +129,14 @@ class SimpleFile implements ISimpleFile { * * @throws NotPermittedException */ - public function delete() { + public function delete(): void { $this->file->delete(); } /** * Get the MimeType - * - * @return string */ - public function getMimeType() { + public function getMimeType(): string { return $this->file->getMimeType(); } @@ -179,7 +161,7 @@ class SimpleFile implements ISimpleFile { /** * Open the file as stream for writing, resulting resource can be operated as stream like the result from php's own fopen * - * @return resource + * @return resource|false * @throws \OCP\Files\NotPermittedException * @since 14.0.0 */ diff --git a/lib/private/Files/SimpleFS/SimpleFolder.php b/lib/private/Files/SimpleFS/SimpleFolder.php index cd2a712019e..263c25a8873 100644 --- a/lib/private/Files/SimpleFS/SimpleFolder.php +++ b/lib/private/Files/SimpleFS/SimpleFolder.php @@ -29,6 +29,7 @@ use OCP\Files\Folder; use OCP\Files\Node; use OCP\Files\NotFoundException; use OCP\Files\SimpleFS\ISimpleFolder; +use OCP\Files\SimpleFS\ISimpleFile; class SimpleFolder implements ISimpleFolder { @@ -44,11 +45,11 @@ class SimpleFolder implements ISimpleFolder { $this->folder = $folder; } - public function getName() { + public function getName(): string { return $this->folder->getName(); } - public function getDirectoryListing() { + public function getDirectoryListing(): array { $listing = $this->folder->getDirectoryListing(); $fileListing = array_map(function (Node $file) { @@ -63,15 +64,15 @@ class SimpleFolder implements ISimpleFolder { return array_values($fileListing); } - public function delete() { + public function delete(): void { $this->folder->delete(); } - public function fileExists($name) { + public function fileExists(string $name): bool { return $this->folder->nodeExists($name); } - public function getFile($name) { + public function getFile(string $name): ISimpleFile { $file = $this->folder->get($name); if (!($file instanceof File)) { @@ -81,7 +82,7 @@ class SimpleFolder implements ISimpleFolder { return new SimpleFile($file); } - public function newFile($name, $content = null) { + public function newFile(string $name, $content = null): ISimpleFile { if ($content === null) { // delay creating the file until it's written to return new NewSimpleFile($this->folder, $name); diff --git a/lib/public/Files/SimpleFS/ISimpleFile.php b/lib/public/Files/SimpleFS/ISimpleFile.php index 8f1921cb7cb..34cd128d449 100644 --- a/lib/public/Files/SimpleFS/ISimpleFile.php +++ b/lib/public/Files/SimpleFS/ISimpleFile.php @@ -41,44 +41,39 @@ interface ISimpleFile { /** * Get the name * - * @return string * @since 11.0.0 */ - public function getName(); + public function getName(): string; /** * Get the size in bytes * - * @return int * @since 11.0.0 */ - public function getSize(); + public function getSize(): int; /** * Get the ETag * - * @return string * @since 11.0.0 */ - public function getETag(); + public function getETag(): string; /** * Get the last modification time * - * @return int * @since 11.0.0 */ - public function getMTime(); + public function getMTime(): int; /** * Get the content * * @throws NotPermittedException * @throws NotFoundException - * @return string * @since 11.0.0 */ - public function getContent(); + public function getContent(): string; /** * Overwrite the file @@ -88,7 +83,7 @@ interface ISimpleFile { * @throws NotFoundException * @since 11.0.0 */ - public function putContent($data); + public function putContent($data): void; /** * Delete the file @@ -96,15 +91,14 @@ interface ISimpleFile { * @throws NotPermittedException * @since 11.0.0 */ - public function delete(); + public function delete(): void; /** * Get the MimeType * - * @return string * @since 11.0.0 */ - public function getMimeType(); + public function getMimeType(): string; /** * @since 24.0.0 diff --git a/lib/public/Files/SimpleFS/ISimpleFolder.php b/lib/public/Files/SimpleFS/ISimpleFolder.php index 0159c41760b..3c8e6e88ab3 100644 --- a/lib/public/Files/SimpleFS/ISimpleFolder.php +++ b/lib/public/Files/SimpleFS/ISimpleFolder.php @@ -38,7 +38,7 @@ interface ISimpleFolder { * @return ISimpleFile[] * @since 11.0.0 */ - public function getDirectoryListing(); + public function getDirectoryListing(): array; /** * Check if a file with $name exists @@ -47,28 +47,24 @@ interface ISimpleFolder { * @return bool * @since 11.0.0 */ - public function fileExists($name); + public function fileExists(string $name): bool; /** * Get the file named $name from the folder * - * @param string $name - * @return ISimpleFile * @throws NotFoundException * @since 11.0.0 */ - public function getFile($name); + public function getFile(string $name): ISimpleFile; /** * Creates a new file with $name in the folder * - * @param string $name * @param string|resource|null $content @since 19.0.0 - * @return ISimpleFile * @throws NotPermittedException * @since 11.0.0 */ - public function newFile($name, $content = null); + public function newFile(string $name, $content = null): ISimpleFile; /** * Remove the folder and all the files in it @@ -76,13 +72,12 @@ interface ISimpleFolder { * @throws NotPermittedException * @since 11.0.0 */ - public function delete(); + public function delete(): void; /** * Get the folder name * - * @return string * @since 11.0.0 */ - public function getName(); + public function getName(): string; } diff --git a/lib/public/Files/SimpleFS/ISimpleRoot.php b/lib/public/Files/SimpleFS/ISimpleRoot.php index fd590b66b31..31c3efe76dd 100644 --- a/lib/public/Files/SimpleFS/ISimpleRoot.php +++ b/lib/public/Files/SimpleFS/ISimpleRoot.php @@ -35,8 +35,6 @@ interface ISimpleRoot { /** * Get the folder with name $name * - * @param string $name - * @return ISimpleFolder * @throws NotFoundException * @throws \RuntimeException * @since 11.0.0 @@ -56,8 +54,6 @@ interface ISimpleRoot { /** * Create a new folder named $name * - * @param string $name - * @return ISimpleFolder * @throws NotPermittedException * @throws \RuntimeException * @since 11.0.0 diff --git a/lib/public/Files/SimpleFS/InMemoryFile.php b/lib/public/Files/SimpleFS/InMemoryFile.php index 590cb43e1d6..393449d4f1f 100644 --- a/lib/public/Files/SimpleFS/InMemoryFile.php +++ b/lib/public/Files/SimpleFS/InMemoryFile.php @@ -36,17 +36,13 @@ use OCP\Files\NotPermittedException; class InMemoryFile implements ISimpleFile { /** * Holds the file name. - * - * @var string */ - private $name; + private string $name; /** * Holds the file contents. - * - * @var string */ - private $contents; + private string $contents; /** * InMemoryFile constructor. @@ -64,7 +60,7 @@ class InMemoryFile implements ISimpleFile { * @inheritdoc * @since 16.0.0 */ - public function getName() { + public function getName(): string { return $this->name; } @@ -72,7 +68,7 @@ class InMemoryFile implements ISimpleFile { * @inheritdoc * @since 16.0.0 */ - public function getSize() { + public function getSize(): int { return strlen($this->contents); } @@ -80,7 +76,7 @@ class InMemoryFile implements ISimpleFile { * @inheritdoc * @since 16.0.0 */ - public function getETag() { + public function getETag(): string { return ''; } @@ -88,7 +84,7 @@ class InMemoryFile implements ISimpleFile { * @inheritdoc * @since 16.0.0 */ - public function getMTime() { + public function getMTime(): int { return time(); } @@ -96,7 +92,7 @@ class InMemoryFile implements ISimpleFile { * @inheritdoc * @since 16.0.0 */ - public function getContent() { + public function getContent(): string { return $this->contents; } @@ -104,7 +100,7 @@ class InMemoryFile implements ISimpleFile { * @inheritdoc * @since 16.0.0 */ - public function putContent($data) { + public function putContent($data): void { $this->contents = $data; } @@ -113,7 +109,7 @@ class InMemoryFile implements ISimpleFile { * * @since 16.0.0 */ - public function delete() { + public function delete(): void { // unimplemented for in memory files } @@ -121,7 +117,7 @@ class InMemoryFile implements ISimpleFile { * @inheritdoc * @since 16.0.0 */ - public function getMimeType() { + public function getMimeType(): string { $fileInfo = new \finfo(FILEINFO_MIME_TYPE); return $fileInfo->buffer($this->contents); } diff --git a/tests/lib/Avatar/UserAvatarTest.php b/tests/lib/Avatar/UserAvatarTest.php index dd5f25163f2..5e903007239 100644 --- a/tests/lib/Avatar/UserAvatarTest.php +++ b/tests/lib/Avatar/UserAvatarTest.php @@ -11,7 +11,6 @@ namespace Test\Avatar; use OC\Files\SimpleFS\SimpleFolder; use OC\User\User; use OCP\Files\File; -use OCP\Files\Folder; use OCP\Files\NotFoundException; use OCP\Files\SimpleFS\ISimpleFile; use OCP\IConfig; @@ -19,7 +18,7 @@ use OCP\IL10N; use Psr\Log\LoggerInterface; class UserAvatarTest extends \Test\TestCase { - /** @var Folder | \PHPUnit\Framework\MockObject\MockObject */ + /** @var SimpleFolder | \PHPUnit\Framework\MockObject\MockObject */ private $folder; /** @var \OC\Avatar\UserAvatar */ @@ -91,12 +90,13 @@ class UserAvatarTest extends \Test\TestCase { ->willReturnMap([ ['avatar.jpg', true], ['avatar.128.jpg', true], + ['generated', false], ]); $expected = new \OC_Image(); $expected->loadFromFile(\OC::$SERVERROOT . '/tests/data/testavatar.png'); - $file = $this->createMock(File::class); + $file = $this->createMock(ISimpleFile::class); $file->method('getContent')->willReturn($expected->data()); $this->folder->method('getFile')->with('avatar.128.jpg')->willReturn($file); @@ -107,12 +107,13 @@ class UserAvatarTest extends \Test\TestCase { $this->folder->method('fileExists') ->willReturnMap([ ['avatar.jpg', true], + ['generated', false], ]); $expected = new \OC_Image(); $expected->loadFromFile(\OC::$SERVERROOT . '/tests/data/testavatar.png'); - $file = $this->createMock(File::class); + $file = $this->createMock(ISimpleFile::class); $file->method('getContent')->willReturn($expected->data()); $this->folder->method('getFile')->with('avatar.jpg')->willReturn($file); @@ -122,8 +123,10 @@ class UserAvatarTest extends \Test\TestCase { public function testGetAvatarNoSizeMatch() { $this->folder->method('fileExists') ->willReturnMap([ + ['avatar.jpg', false], ['avatar.png', true], ['avatar.32.png', false], + ['generated', false], ]); $expected = new \OC_Image(); @@ -132,7 +135,7 @@ class UserAvatarTest extends \Test\TestCase { $expected2->loadFromFile(\OC::$SERVERROOT . '/tests/data/testavatar.png'); $expected2->resize(32); - $file = $this->createMock(File::class); + $file = $this->createMock(ISimpleFile::class); $file->method('getContent')->willReturn($expected->data()); $this->folder->method('getFile') @@ -146,7 +149,7 @@ class UserAvatarTest extends \Test\TestCase { } ); - $newFile = $this->createMock(File::class); + $newFile = $this->createMock(ISimpleFile::class); $newFile->expects($this->once()) ->method('putContent') ->with($expected2->data()); @@ -202,12 +205,12 @@ class UserAvatarTest extends \Test\TestCase { $this->folder->method('getDirectoryListing') ->willReturn([$avatarFileJPG, $avatarFilePNG, $resizedAvatarFile]); - $generated = $this->createMock(File::class); + $generated = $this->createMock(ISimpleFile::class); $this->folder->method('getFile') ->with('generated') ->willReturn($generated); - $newFile = $this->createMock(File::class); + $newFile = $this->createMock(ISimpleFile::class); $this->folder->expects($this->once()) ->method('newFile') ->with('avatar.png') diff --git a/tests/lib/Preview/GeneratorTest.php b/tests/lib/Preview/GeneratorTest.php index 43f5c1e0d36..1e38afd7744 100644 --- a/tests/lib/Preview/GeneratorTest.php +++ b/tests/lib/Preview/GeneratorTest.php @@ -95,6 +95,7 @@ class GeneratorTest extends \Test\TestCase { $maxPreview = $this->createMock(ISimpleFile::class); $maxPreview->method('getName') ->willReturn('1000-1000-max.png'); + $maxPreview->method('getSize')->willReturn(1000); $maxPreview->method('getMimeType') ->willReturn('image/png'); @@ -102,6 +103,7 @@ class GeneratorTest extends \Test\TestCase { ->willReturn([$maxPreview]); $previewFile = $this->createMock(ISimpleFile::class); + $previewFile->method('getSize')->willReturn(1000); $previewFolder->method('getFile') ->with($this->equalTo('256-256.png')) @@ -203,8 +205,10 @@ class GeneratorTest extends \Test\TestCase { $maxPreview = $this->createMock(ISimpleFile::class); $maxPreview->method('getName')->willReturn('2048-2048-max.png'); $maxPreview->method('getMimeType')->willReturn('image/png'); + $maxPreview->method('getSize')->willReturn(1000); $previewFile = $this->createMock(ISimpleFile::class); + $previewFile->method('getSize')->willReturn(1000); $previewFolder->method('getDirectoryListing') ->willReturn([]); @@ -313,6 +317,7 @@ class GeneratorTest extends \Test\TestCase { $maxPreview = $this->createMock(ISimpleFile::class); $maxPreview->method('getName') ->willReturn('2048-2048-max.png'); + $maxPreview->method('getSize')->willReturn(1000); $maxPreview->method('getMimeType') ->willReturn('image/png'); @@ -320,6 +325,7 @@ class GeneratorTest extends \Test\TestCase { ->willReturn([$maxPreview]); $preview = $this->createMock(ISimpleFile::class); + $preview->method('getSize')->willReturn(1000); $previewFolder->method('getFile') ->with($this->equalTo('1024-512-crop.png')) ->willReturn($preview); @@ -471,6 +477,7 @@ class GeneratorTest extends \Test\TestCase { ->willReturn($maxX . '-' . $maxY . '-max.png'); $maxPreview->method('getMimeType') ->willReturn('image/png'); + $maxPreview->method('getSize')->willReturn(1000); $previewFolder->method('getDirectoryListing') ->willReturn([$maxPreview]); @@ -490,6 +497,7 @@ class GeneratorTest extends \Test\TestCase { ->willReturn($image); $preview = $this->createMock(ISimpleFile::class); + $preview->method('getSize')->willReturn(1000); $previewFolder->method('newFile') ->with($this->equalTo($filename)) ->willReturn($preview); -- cgit v1.2.3 From 13eef55a925f397afbb5c7d4e058ae080b1ac824 Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Thu, 21 Jul 2022 16:36:32 +0200 Subject: always triger setup of builtin storage wrappers Signed-off-by: Robin Appelman --- lib/private/Files/SetupManager.php | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) (limited to 'lib/private/Files') diff --git a/lib/private/Files/SetupManager.php b/lib/private/Files/SetupManager.php index f19c9bfa062..5782a5a72a6 100644 --- a/lib/private/Files/SetupManager.php +++ b/lib/private/Files/SetupManager.php @@ -82,6 +82,7 @@ class SetupManager { private IConfig $config; private bool $listeningForProviders; private array $fullSetupRequired = []; + private bool $setupBuiltinWrappersDone = false; public function __construct( IEventLogger $eventLogger, @@ -121,6 +122,15 @@ class SetupManager { } private function setupBuiltinWrappers() { + if ($this->setupBuiltinWrappersDone) { + return; + } + $this->setupBuiltinWrappersDone = true; + + // load all filesystem apps before, so no setup-hook gets lost + OC_App::loadApps(['filesystem']); + $prevLogging = Filesystem::logWarningWhenAddingStorageWrapper(false); + Filesystem::addStorageWrapper('mount_options', function ($mountPoint, IStorage $storage, IMountPoint $mount) { if ($storage->instanceOfStorage(Common::class)) { $storage->setMountOptions($mount->getOptions()); @@ -188,6 +198,8 @@ class SetupManager { } return $storage; }); + + Filesystem::logWarningWhenAddingStorageWrapper($prevLogging); } /** @@ -223,6 +235,9 @@ class SetupManager { return; } $this->setupUsers[] = $user->getUID(); + + $this->setupBuiltinWrappers(); + $prevLogging = Filesystem::logWarningWhenAddingStorageWrapper(false); OC_Hook::emit('OC_Filesystem', 'preSetup', ['user' => $user->getUID()]); @@ -321,14 +336,8 @@ class SetupManager { $this->eventLogger->start('setup_root_fs', 'Setup root filesystem'); - // load all filesystem apps before, so no setup-hook gets lost - OC_App::loadApps(['filesystem']); - $prevLogging = Filesystem::logWarningWhenAddingStorageWrapper(false); - $this->setupBuiltinWrappers(); - Filesystem::logWarningWhenAddingStorageWrapper($prevLogging); - $rootMounts = $this->mountProviderCollection->getRootMounts(); foreach ($rootMounts as $rootMountProvider) { $this->mountManager->addMount($rootMountProvider); -- cgit v1.2.3 From 61fdd91dcf6a17f97f2a416caee2ba39982f3b08 Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Wed, 27 Jul 2022 12:58:07 +0200 Subject: don't set `null` as a bundle path Signed-off-by: Robin Appelman --- lib/private/Files/ObjectStore/S3ObjectTrait.php | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) (limited to 'lib/private/Files') diff --git a/lib/private/Files/ObjectStore/S3ObjectTrait.php b/lib/private/Files/ObjectStore/S3ObjectTrait.php index 0b2fd95c652..9d692e01a23 100644 --- a/lib/private/Files/ObjectStore/S3ObjectTrait.php +++ b/lib/private/Files/ObjectStore/S3ObjectTrait.php @@ -69,11 +69,14 @@ trait S3ObjectTrait { 'http' => [ 'protocol_version' => $request->getProtocolVersion(), 'header' => $headers, - ], - 'ssl' => [ - 'cafile' => $this->getCertificateBundlePath() ] ]; + $bundle = $this->getCertificateBundlePath(); + if ($bundle) { + $opts['ssl'] = [ + 'cafile' => $bundle + ]; + } if ($this->getProxy()) { $opts['http']['proxy'] = $this->getProxy(); -- cgit v1.2.3 From 368f83095d443c28b0d90d2308549349cc28dd05 Mon Sep 17 00:00:00 2001 From: luz paz Date: Wed, 27 Jul 2022 08:51:42 -0400 Subject: Fix typos in lib/private subdirectory Found via `codespell -q 3 -S l10n -L jus ./lib/private` Signed-off-by: luz paz --- lib/private/App/CompareVersion.php | 2 +- lib/private/AppFramework/Http/Dispatcher.php | 4 ++-- lib/private/AppFramework/Http/Request.php | 2 +- lib/private/AppFramework/Middleware/MiddlewareDispatcher.php | 2 +- lib/private/Authentication/Token/IProvider.php | 2 +- lib/private/Avatar/Avatar.php | 2 +- lib/private/Contacts/ContactsMenu/ContactsStore.php | 2 +- lib/private/DB/QueryBuilder/QueryBuilder.php | 2 +- lib/private/Files/Cache/Cache.php | 4 ++-- lib/private/Files/Cache/Propagator.php | 2 +- lib/private/Files/Cache/QuerySearchHelper.php | 2 +- lib/private/Files/Cache/Storage.php | 4 ++-- lib/private/Files/Cache/StorageGlobal.php | 2 +- lib/private/Files/Cache/Updater.php | 4 ++-- lib/private/Files/Config/UserMountCache.php | 2 +- lib/private/Files/FileInfo.php | 2 +- lib/private/Files/Node/Folder.php | 2 +- lib/private/Files/Node/Root.php | 2 +- lib/private/Files/SimpleFS/NewSimpleFile.php | 2 +- lib/private/Files/SimpleFS/SimpleFile.php | 2 +- lib/private/Files/View.php | 4 ++-- lib/private/HintException.php | 2 +- lib/private/Http/Client/Client.php | 2 +- lib/private/MemoryInfo.php | 2 +- lib/private/Metadata/IMetadataManager.php | 2 +- lib/private/OCS/DiscoveryService.php | 2 +- lib/private/Profile/ProfileManager.php | 4 ++-- lib/private/Repair/RemoveLinkShares.php | 2 +- lib/private/Route/Router.php | 2 +- lib/private/Security/TrustedDomainHelper.php | 2 +- lib/private/Session/Internal.php | 2 +- lib/private/Share/Constants.php | 2 +- lib/private/Share/Share.php | 4 ++-- lib/private/Share20/DefaultShareProvider.php | 2 +- lib/private/Share20/Manager.php | 2 +- lib/private/Share20/Share.php | 2 +- lib/private/URLGenerator.php | 2 +- 37 files changed, 44 insertions(+), 44 deletions(-) (limited to 'lib/private/Files') diff --git a/lib/private/App/CompareVersion.php b/lib/private/App/CompareVersion.php index d155945fff1..a349c7aa6f2 100644 --- a/lib/private/App/CompareVersion.php +++ b/lib/private/App/CompareVersion.php @@ -41,7 +41,7 @@ class CompareVersion { * so '13.0.1', '13.0' and '13' are valid. * * @param string $actual version as major.minor.patch notation - * @param string $required version where major is requried and minor and patch are optional + * @param string $required version where major is required and minor and patch are optional * @param string $comparator passed to `version_compare` * @return bool whether the requirement is fulfilled * @throws InvalidArgumentException if versions specified in an invalid format diff --git a/lib/private/AppFramework/Http/Dispatcher.php b/lib/private/AppFramework/Http/Dispatcher.php index 21d61bc95aa..c1a203a7165 100644 --- a/lib/private/AppFramework/Http/Dispatcher.php +++ b/lib/private/AppFramework/Http/Dispatcher.php @@ -118,7 +118,7 @@ class Dispatcher { $out = [null, [], null]; try { - // prefill reflector with everything thats needed for the + // prefill reflector with everything that's needed for the // middlewares $this->reflector->reflect($controller, $methodName); @@ -156,7 +156,7 @@ class Dispatcher { // if an exception appears, the middleware checks if it can handle the // exception and creates a response. If no response is created, it is - // assumed that theres no middleware who can handle it and the error is + // assumed that there's no middleware who can handle it and the error is // thrown again } catch (\Exception $exception) { $response = $this->middlewareDispatcher->afterException( diff --git a/lib/private/AppFramework/Http/Request.php b/lib/private/AppFramework/Http/Request.php index 010d889070e..35cd46bf68a 100644 --- a/lib/private/AppFramework/Http/Request.php +++ b/lib/private/AppFramework/Http/Request.php @@ -342,7 +342,7 @@ class Request implements \ArrayAccess, \Countable, IRequest { /** * Returns all params that were received, be it from the request - * (as GET or POST) or throuh the URL by the route + * (as GET or POST) or through the URL by the route * @return array the array with all parameters */ public function getParams(): array { diff --git a/lib/private/AppFramework/Middleware/MiddlewareDispatcher.php b/lib/private/AppFramework/Middleware/MiddlewareDispatcher.php index 950ef8a13a3..adf17e53caa 100644 --- a/lib/private/AppFramework/Middleware/MiddlewareDispatcher.php +++ b/lib/private/AppFramework/Middleware/MiddlewareDispatcher.php @@ -46,7 +46,7 @@ class MiddlewareDispatcher { private $middlewares; /** - * @var int counter which tells us what middlware was executed once an + * @var int counter which tells us what middleware was executed once an * exception occurs */ private $middlewareCounter; diff --git a/lib/private/Authentication/Token/IProvider.php b/lib/private/Authentication/Token/IProvider.php index 0a145bfd7e6..33e0ad46263 100644 --- a/lib/private/Authentication/Token/IProvider.php +++ b/lib/private/Authentication/Token/IProvider.php @@ -158,7 +158,7 @@ interface IProvider { public function setPassword(IToken $token, string $tokenId, string $password); /** - * Rotate the token. Usefull for for example oauth tokens + * Rotate the token. Useful for for example oauth tokens * * @param IToken $token * @param string $oldTokenId diff --git a/lib/private/Avatar/Avatar.php b/lib/private/Avatar/Avatar.php index 25099a4f139..0eb8f8816d8 100644 --- a/lib/private/Avatar/Avatar.php +++ b/lib/private/Avatar/Avatar.php @@ -236,7 +236,7 @@ abstract class Avatar implements IAvatar { } /** - * @return Color Object containting r g b int in the range [0, 255] + * @return Color Object containing r g b int in the range [0, 255] */ public function avatarBackgroundColor(string $hash): Color { // Normalize hash diff --git a/lib/private/Contacts/ContactsMenu/ContactsStore.php b/lib/private/Contacts/ContactsMenu/ContactsStore.php index 4d7fda39c6a..dd4bd973fa9 100644 --- a/lib/private/Contacts/ContactsMenu/ContactsStore.php +++ b/lib/private/Contacts/ContactsMenu/ContactsStore.php @@ -123,7 +123,7 @@ class ContactsStore implements IContactsStore { * 2. if the `shareapi_exclude_groups` config option is enabled and the * current user is in an excluded group it will filter all local users. * 3. if the `shareapi_only_share_with_group_members` config option is - * enabled it will filter all users which doens't have a common group + * enabled it will filter all users which doesn't have a common group * with the current user. * * @param IUser $self diff --git a/lib/private/DB/QueryBuilder/QueryBuilder.php b/lib/private/DB/QueryBuilder/QueryBuilder.php index 8fc66755a99..d991cbd1dd5 100644 --- a/lib/private/DB/QueryBuilder/QueryBuilder.php +++ b/lib/private/DB/QueryBuilder/QueryBuilder.php @@ -852,7 +852,7 @@ class QueryBuilder implements IQueryBuilder { * ->from('users', 'u') * ->where('u.id = ?'); * - * // You can optionally programatically build and/or expressions + * // You can optionally programmatically build and/or expressions * $qb = $conn->getQueryBuilder(); * * $or = $qb->expr()->orx(); diff --git a/lib/private/Files/Cache/Cache.php b/lib/private/Files/Cache/Cache.php index 33a07e9b9e5..110e55bcc6c 100644 --- a/lib/private/Files/Cache/Cache.php +++ b/lib/private/Files/Cache/Cache.php @@ -64,7 +64,7 @@ use Psr\Log\LoggerInterface; /** * Metadata cache for a storage * - * The cache stores the metadata for all files and folders in a storage and is kept up to date trough the following mechanisms: + * The cache stores the metadata for all files and folders in a storage and is kept up to date through the following mechanisms: * * - Scanner: scans the storage and updates the cache where needed * - Watcher: checks for changes made to the filesystem outside of the Nextcloud instance and rescans files and folder when a change is detected @@ -582,7 +582,7 @@ class Cache implements ICache { $parentIds = [$entry->getId()]; $queue = [$entry->getId()]; - // we walk depth first trough the file tree, removing all filecache_extended attributes while we walk + // we walk depth first through the file tree, removing all filecache_extended attributes while we walk // and collecting all folder ids to later use to delete the filecache entries while ($entryId = array_pop($queue)) { $children = $this->getFolderContentsById($entryId); diff --git a/lib/private/Files/Cache/Propagator.php b/lib/private/Files/Cache/Propagator.php index afff2fb51ff..a0953baa785 100644 --- a/lib/private/Files/Cache/Propagator.php +++ b/lib/private/Files/Cache/Propagator.php @@ -66,7 +66,7 @@ class Propagator implements IPropagator { * @param int $sizeDifference number of bytes the file has grown */ public function propagateChange($internalPath, $time, $sizeDifference = 0) { - // Do not propogate changes in ignored paths + // Do not propagate changes in ignored paths foreach ($this->ignore as $ignore) { if (strpos($internalPath, $ignore) === 0) { return; diff --git a/lib/private/Files/Cache/QuerySearchHelper.php b/lib/private/Files/Cache/QuerySearchHelper.php index 69a2944b2dd..3529ede9746 100644 --- a/lib/private/Files/Cache/QuerySearchHelper.php +++ b/lib/private/Files/Cache/QuerySearchHelper.php @@ -158,7 +158,7 @@ class QuerySearchHelper { $result->closeCursor(); - // loop trough all caches for each result to see if the result matches that storage + // loop through all caches for each result to see if the result matches that storage // results are grouped by the same array keys as the caches argument to allow the caller to distringuish the source of the results $results = array_fill_keys(array_keys($caches), []); foreach ($rawEntries as $rawEntry) { diff --git a/lib/private/Files/Cache/Storage.php b/lib/private/Files/Cache/Storage.php index fb9e5500658..f77c9b71dd7 100644 --- a/lib/private/Files/Cache/Storage.php +++ b/lib/private/Files/Cache/Storage.php @@ -40,7 +40,7 @@ use Psr\Log\LoggerInterface; * a string id which is generated by the storage backend and reflects the configuration of the storage (e.g. 'smb://user@host/share') * and a numeric storage id which is referenced in the file cache * - * A mapping between the two storage ids is stored in the database and accessible trough this class + * A mapping between the two storage ids is stored in the database and accessible through this class * * @package OC\Files\Cache */ @@ -135,7 +135,7 @@ class Storage { * Get the numeric of the storage with the provided string id * * @param $storageId - * @return int|null either the numeric storage id or null if the storage id is not knwon + * @return int|null either the numeric storage id or null if the storage id is not known */ public static function getNumericStorageId($storageId) { $storageId = self::adjustStorageId($storageId); diff --git a/lib/private/Files/Cache/StorageGlobal.php b/lib/private/Files/Cache/StorageGlobal.php index a898c435415..74cbd5abdb2 100644 --- a/lib/private/Files/Cache/StorageGlobal.php +++ b/lib/private/Files/Cache/StorageGlobal.php @@ -33,7 +33,7 @@ use OCP\IDBConnection; * a string id which is generated by the storage backend and reflects the configuration of the storage (e.g. 'smb://user@host/share') * and a numeric storage id which is referenced in the file cache * - * A mapping between the two storage ids is stored in the database and accessible trough this class + * A mapping between the two storage ids is stored in the database and accessible through this class * * @package OC\Files\Cache */ diff --git a/lib/private/Files/Cache/Updater.php b/lib/private/Files/Cache/Updater.php index 98fb51fe264..f8c187996e6 100644 --- a/lib/private/Files/Cache/Updater.php +++ b/lib/private/Files/Cache/Updater.php @@ -73,14 +73,14 @@ class Updater implements IUpdater { } /** - * Disable updating the cache trough this updater + * Disable updating the cache through this updater */ public function disable() { $this->enabled = false; } /** - * Re-enable the updating of the cache trough this updater + * Re-enable the updating of the cache through this updater */ public function enable() { $this->enabled = true; diff --git a/lib/private/Files/Config/UserMountCache.php b/lib/private/Files/Config/UserMountCache.php index f26c42938d3..685057a7860 100644 --- a/lib/private/Files/Config/UserMountCache.php +++ b/lib/private/Files/Config/UserMountCache.php @@ -42,7 +42,7 @@ use OCP\IUserManager; use Psr\Log\LoggerInterface; /** - * Cache mounts points per user in the cache so we can easilly look them up + * Cache mounts points per user in the cache so we can easily look them up */ class UserMountCache implements IUserMountCache { private IDBConnection $connection; diff --git a/lib/private/Files/FileInfo.php b/lib/private/Files/FileInfo.php index 7a984429d1f..47c893ebbf1 100644 --- a/lib/private/Files/FileInfo.php +++ b/lib/private/Files/FileInfo.php @@ -414,7 +414,7 @@ class FileInfo implements \OCP\Files\FileInfo, \ArrayAccess { if (isset($data['etag'])) { // prefix the etag with the relative path of the subentry to propagate etag on mount moves $relativeEntryPath = substr($entryPath, strlen($this->getPath())); - // attach the permissions to propagate etag on permision changes of submounts + // attach the permissions to propagate etag on permission changes of submounts $permissions = isset($data['permissions']) ? $data['permissions'] : 0; $this->childEtags[] = $relativeEntryPath . '/' . $data['etag'] . $permissions; } diff --git a/lib/private/Files/Node/Folder.php b/lib/private/Files/Node/Folder.php index b56b7e0f851..42562c99bcb 100644 --- a/lib/private/Files/Node/Folder.php +++ b/lib/private/Files/Node/Folder.php @@ -262,7 +262,7 @@ class Folder extends Node implements \OCP\Files\Folder { $searchHelper = \OC::$server->get(QuerySearchHelper::class); $resultsPerCache = $searchHelper->searchInCaches($query, $caches); - // loop trough all results per-cache, constructing the FileInfo object from the CacheEntry and merge them all + // loop through all results per-cache, constructing the FileInfo object from the CacheEntry and merge them all $files = array_merge(...array_map(function (array $results, $relativeMountPoint) use ($mountByMountPoint) { $mount = $mountByMountPoint[$relativeMountPoint]; return array_map(function (ICacheEntry $result) use ($relativeMountPoint, $mount) { diff --git a/lib/private/Files/Node/Root.php b/lib/private/Files/Node/Root.php index 9e3d4afd8d8..ca930c1002c 100644 --- a/lib/private/Files/Node/Root.php +++ b/lib/private/Files/Node/Root.php @@ -427,7 +427,7 @@ class Root extends Folder implements IRootFolder { $mountsContainingFile = $mountCache->getMountsForFileId($id, $user); } - // when a user has access trough the same storage trough multiple paths + // when a user has access through the same storage through multiple paths // (such as an external storage that is both mounted for a user and shared to the user) // the mount cache will only hold a single entry for the storage // this can lead to issues as the different ways the user has access to a storage can have different permissions diff --git a/lib/private/Files/SimpleFS/NewSimpleFile.php b/lib/private/Files/SimpleFS/NewSimpleFile.php index 5fdd794ba98..b2a183b7d29 100644 --- a/lib/private/Files/SimpleFS/NewSimpleFile.php +++ b/lib/private/Files/SimpleFS/NewSimpleFile.php @@ -127,7 +127,7 @@ class NewSimpleFile implements ISimpleFile { /** * Sometimes there are some issues with the AppData. Most of them are from - * user error. But we should handle them gracefull anyway. + * user error. But we should handle them gracefully anyway. * * If for some reason the current file can't be found. We remove it. * Then traverse up and check all folders if they exists. This so that the diff --git a/lib/private/Files/SimpleFS/SimpleFile.php b/lib/private/Files/SimpleFS/SimpleFile.php index a07871337a6..a2571ac50e8 100644 --- a/lib/private/Files/SimpleFS/SimpleFile.php +++ b/lib/private/Files/SimpleFS/SimpleFile.php @@ -97,7 +97,7 @@ class SimpleFile implements ISimpleFile { /** * Sometimes there are some issues with the AppData. Most of them are from - * user error. But we should handle them gracefull anyway. + * user error. But we should handle them gracefully anyway. * * If for some reason the current file can't be found. We remove it. * Then traverse up and check all folders if they exists. This so that the diff --git a/lib/private/Files/View.php b/lib/private/Files/View.php index 2b6732e2ba0..d12869fbdaa 100644 --- a/lib/private/Files/View.php +++ b/lib/private/Files/View.php @@ -1164,7 +1164,7 @@ class View { try { $this->changeLock($path, ILockingProvider::LOCK_EXCLUSIVE); } catch (LockedException $e) { - // release the shared lock we acquired before quiting + // release the shared lock we acquired before quitting $this->unlockFile($path, ILockingProvider::LOCK_SHARED); throw $e; } @@ -1725,7 +1725,7 @@ class View { /** * Get the path of a file by id, relative to the view * - * Note that the resulting path is not guarantied to be unique for the id, multiple paths can point to the same file + * Note that the resulting path is not guaranteed to be unique for the id, multiple paths can point to the same file * * @param int $id * @param int|null $storageId diff --git a/lib/private/HintException.php b/lib/private/HintException.php index 735832266cf..20f7142d1c0 100644 --- a/lib/private/HintException.php +++ b/lib/private/HintException.php @@ -31,7 +31,7 @@ namespace OC; * An Exception class with the intention to be presented to the end user * * @package OC - * @depreacted 23.0.0 Use \OCP\HintException + * @deprecated 23.0.0 Use \OCP\HintException */ class HintException extends \OCP\HintException { } diff --git a/lib/private/Http/Client/Client.php b/lib/private/Http/Client/Client.php index 3ba85a2dd9f..4bf7fd02400 100644 --- a/lib/private/Http/Client/Client.php +++ b/lib/private/Http/Client/Client.php @@ -128,7 +128,7 @@ class Client implements IClient { } /** - * Returns a null or an associative array specifiying the proxy URI for + * Returns a null or an associative array specifying the proxy URI for * 'http' and 'https' schemes, in addition to a 'no' key value pair * providing a list of host names that should not be proxied to. * diff --git a/lib/private/MemoryInfo.php b/lib/private/MemoryInfo.php index 074e9f915fe..ed6617d879d 100644 --- a/lib/private/MemoryInfo.php +++ b/lib/private/MemoryInfo.php @@ -68,7 +68,7 @@ class MemoryInfo { $last = strtolower(substr($memoryLimit, -1)); $memoryLimit = (int)substr($memoryLimit, 0, -1); - // intended fall trough + // intended fall through switch ($last) { case 'g': $memoryLimit *= 1024; diff --git a/lib/private/Metadata/IMetadataManager.php b/lib/private/Metadata/IMetadataManager.php index d2d37f15c25..fa0bcc22801 100644 --- a/lib/private/Metadata/IMetadataManager.php +++ b/lib/private/Metadata/IMetadataManager.php @@ -29,7 +29,7 @@ interface IMetadataManager { public function fetchMetadataFor(string $group, array $fileIds): array; /** - * Get the capabilites as an array of mimetype regex to the type provided + * Get the capabilities as an array of mimetype regex to the type provided */ public function getCapabilities(): array; } diff --git a/lib/private/OCS/DiscoveryService.php b/lib/private/OCS/DiscoveryService.php index 1d10bbac870..7ab876811e7 100644 --- a/lib/private/OCS/DiscoveryService.php +++ b/lib/private/OCS/DiscoveryService.php @@ -62,7 +62,7 @@ class DiscoveryService implements IDiscoveryService { * * @param string $remote * @param string $service the service you want to discover - * @param bool $skipCache We won't check if the data is in the cache. This is usefull if a background job is updating the status + * @param bool $skipCache We won't check if the data is in the cache. This is useful if a background job is updating the status * @return array */ public function discover(string $remote, string $service, bool $skipCache = false): array { diff --git a/lib/private/Profile/ProfileManager.php b/lib/private/Profile/ProfileManager.php index edb51458c66..f2eacd1ef25 100644 --- a/lib/private/Profile/ProfileManager.php +++ b/lib/private/Profile/ProfileManager.php @@ -348,13 +348,13 @@ class ProfileManager { * Return the default profile config */ private function getDefaultProfileConfig(IUser $targetUser, ?IUser $visitingUser): array { - // Contruct the default config for actions + // Construct the default config for actions $actionsConfig = []; foreach ($this->getActions($targetUser, $visitingUser) as $action) { $actionsConfig[$action->getId()] = ['visibility' => ProfileConfig::DEFAULT_VISIBILITY]; } - // Contruct the default config for account properties + // Construct the default config for account properties $propertiesConfig = []; foreach (ProfileConfig::DEFAULT_PROPERTY_VISIBILITY as $property => $visibility) { $propertiesConfig[$property] = ['visibility' => $visibility]; diff --git a/lib/private/Repair/RemoveLinkShares.php b/lib/private/Repair/RemoveLinkShares.php index 1b0270e928d..e1ce78cdbf3 100644 --- a/lib/private/Repair/RemoveLinkShares.php +++ b/lib/private/Repair/RemoveLinkShares.php @@ -217,7 +217,7 @@ class RemoveLinkShares implements IRepairStep { $output->finishProgress(); $shareResult->closeCursor(); - // Notifiy all admins + // Notify all admins $adminGroup = $this->groupManager->get('admin'); $adminUsers = $adminGroup->getUsers(); foreach ($adminUsers as $user) { diff --git a/lib/private/Route/Router.php b/lib/private/Route/Router.php index b957173cacc..7e1acd49800 100644 --- a/lib/private/Route/Router.php +++ b/lib/private/Route/Router.php @@ -409,7 +409,7 @@ class Router implements IRouter { * register the routes for the app. The application class will be chosen by * camelcasing the appname, e.g.: my_app will be turned into * \OCA\MyApp\AppInfo\Application. If that class does not exist, a default - * App will be intialized. This makes it optional to ship an + * App will be initialized. This makes it optional to ship an * appinfo/application.php by using the built in query resolver * * @param array $routes the application routes diff --git a/lib/private/Security/TrustedDomainHelper.php b/lib/private/Security/TrustedDomainHelper.php index 0688ebba5b3..1927af9cb1d 100644 --- a/lib/private/Security/TrustedDomainHelper.php +++ b/lib/private/Security/TrustedDomainHelper.php @@ -97,7 +97,7 @@ class TrustedDomainHelper implements ITrustedDomainHelper { if (preg_match(Request::REGEX_LOCALHOST, $domain) === 1) { return true; } - // Reject misformed domains in any case + // Reject malformed domains in any case if (strpos($domain, '-') === 0 || strpos($domain, '..') !== false) { return false; } diff --git a/lib/private/Session/Internal.php b/lib/private/Session/Internal.php index 285b6fd7960..6e0c54c6fab 100644 --- a/lib/private/Session/Internal.php +++ b/lib/private/Session/Internal.php @@ -172,7 +172,7 @@ class Internal extends Session { * @throws \Exception */ public function reopen() { - throw new \Exception('The session cannot be reopened - reopen() is ony to be used in unit testing.'); + throw new \Exception('The session cannot be reopened - reopen() is only to be used in unit testing.'); } /** diff --git a/lib/private/Share/Constants.php b/lib/private/Share/Constants.php index 31c734f94aa..3632a2a26d1 100644 --- a/lib/private/Share/Constants.php +++ b/lib/private/Share/Constants.php @@ -79,7 +79,7 @@ class Constants { public const FORMAT_STATUSES = -2; public const FORMAT_SOURCES = -3; // ToDo Check if it is still in use otherwise remove it - public const RESPONSE_FORMAT = 'json'; // default resonse format for ocs calls + public const RESPONSE_FORMAT = 'json'; // default response format for ocs calls public const TOKEN_LENGTH = 15; // old (oc7) length is 32, keep token length in db at least that for compatibility diff --git a/lib/private/Share/Share.php b/lib/private/Share/Share.php index 9018f35ac2a..f47c042df29 100644 --- a/lib/private/Share/Share.php +++ b/lib/private/Share/Share.php @@ -732,7 +732,7 @@ class Share extends Constants { foreach ($result as $key => $r) { // for file/folder shares we need to compare file_source, otherwise we compare item_source // only group shares if they already point to the same target, otherwise the file where shared - // before grouping of shares was added. In this case we don't group them toi avoid confusions + // before grouping of shares was added. In this case we don't group them to avoid confusions if (($fileSharing && $item['file_source'] === $r['file_source'] && $item['file_target'] === $r['file_target']) || (!$fileSharing && $item['item_source'] === $r['item_source'] && $item['item_target'] === $r['item_target'])) { // add the first item to the list of grouped shares @@ -757,7 +757,7 @@ class Share extends Constants { /** * construct select statement * @param int $format - * @param boolean $fileDependent ist it a file/folder share or a generla share + * @param boolean $fileDependent ist it a file/folder share or a general share * @param string $uidOwner * @return string select statement */ diff --git a/lib/private/Share20/DefaultShareProvider.php b/lib/private/Share20/DefaultShareProvider.php index 7719a1e6be3..e4cf0415202 100644 --- a/lib/private/Share20/DefaultShareProvider.php +++ b/lib/private/Share20/DefaultShareProvider.php @@ -1294,7 +1294,7 @@ class DefaultShareProvider implements IShareProvider { $chunks = array_chunk($ids, 100); foreach ($chunks as $chunk) { /* - * Delete all special shares wit this users for the found group shares + * Delete all special shares with this users for the found group shares */ $qb->delete('share') ->where($qb->expr()->eq('share_type', $qb->createNamedParameter(IShare::TYPE_USERGROUP))) diff --git a/lib/private/Share20/Manager.php b/lib/private/Share20/Manager.php index 46f256df54d..25511491a24 100644 --- a/lib/private/Share20/Manager.php +++ b/lib/private/Share20/Manager.php @@ -2010,7 +2010,7 @@ class Manager implements IManager { /** * Copied from \OC_Util::isSharingDisabledForUser * - * TODO: Deprecate fuction from OC_Util + * TODO: Deprecate function from OC_Util * * @param string $userId * @return bool diff --git a/lib/private/Share20/Share.php b/lib/private/Share20/Share.php index 7ed03832e4c..e21564563a2 100644 --- a/lib/private/Share20/Share.php +++ b/lib/private/Share20/Share.php @@ -319,7 +319,7 @@ class Share implements IShare { * @inheritdoc */ public function setPermissions($permissions) { - //TODO checkes + //TODO checks $this->permissions = $permissions; return $this; diff --git a/lib/private/URLGenerator.php b/lib/private/URLGenerator.php index 753a4a217d1..47979a038ba 100644 --- a/lib/private/URLGenerator.php +++ b/lib/private/URLGenerator.php @@ -320,7 +320,7 @@ class URLGenerator implements IURLGenerator { * @return string base url of the current request */ public function getBaseUrl(): string { - // BaseUrl can be equal to 'http(s)://' during the first steps of the intial setup. + // BaseUrl can be equal to 'http(s)://' during the first steps of the initial setup. if ($this->baseUrl === null || $this->baseUrl === "http://" || $this->baseUrl === "https://") { $this->baseUrl = $this->request->getServerProtocol() . '://' . $this->request->getServerHost() . \OC::$WEBROOT; } -- cgit v1.2.3 From cb271b759e27c4c26c0e60b7503350682aca33eb Mon Sep 17 00:00:00 2001 From: Côme Chilliet Date: Mon, 1 Aug 2022 15:07:53 +0200 Subject: Fix dynamic property creations in test files MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This fixes warnings in PHP 8.2 Signed-off-by: Côme Chilliet --- apps/dav/lib/DAV/Sharing/Backend.php | 2 +- apps/dav/tests/unit/CalDAV/AbstractCalDavBackend.php | 14 +++++++++----- lib/private/Files/Cache/Cache.php | 2 +- tests/lib/DirectEditing/ManagerTest.php | 4 ++++ tests/lib/Metadata/FileMetadataMapperTest.php | 6 +++++- tests/lib/Repair/RepairMimeTypesTest.php | 1 - tests/lib/User/SessionTest.php | 2 ++ 7 files changed, 22 insertions(+), 9 deletions(-) (limited to 'lib/private/Files') diff --git a/apps/dav/lib/DAV/Sharing/Backend.php b/apps/dav/lib/DAV/Sharing/Backend.php index 92971992c20..90d2c7ebf82 100644 --- a/apps/dav/lib/DAV/Sharing/Backend.php +++ b/apps/dav/lib/DAV/Sharing/Backend.php @@ -179,7 +179,7 @@ class Backend { while ($row = $result->fetch()) { $p = $this->principalBackend->getPrincipalByPath($row['principaluri']); $shares[] = [ - 'href' => "principal:${row['principaluri']}", + 'href' => "principal:{$row['principaluri']}", 'commonName' => isset($p['{DAV:}displayname']) ? (string)$p['{DAV:}displayname'] : '', 'status' => 1, 'readOnly' => (int) $row['access'] === self::ACCESS_READ, diff --git a/apps/dav/tests/unit/CalDAV/AbstractCalDavBackend.php b/apps/dav/tests/unit/CalDAV/AbstractCalDavBackend.php index f2d2014d553..73d21746b64 100644 --- a/apps/dav/tests/unit/CalDAV/AbstractCalDavBackend.php +++ b/apps/dav/tests/unit/CalDAV/AbstractCalDavBackend.php @@ -27,7 +27,6 @@ */ namespace OCA\DAV\Tests\unit\CalDAV; -use OC\KnownUser\KnownUserService; use OCA\DAV\CalDAV\CalDavBackend; use OCA\DAV\CalDAV\Proxy\ProxyMapper; use OCA\DAV\Connector\Sabre\Principal; @@ -41,6 +40,8 @@ use OCP\IUserSession; use OCP\L10N\IFactory; use OCP\Security\ISecureRandom; use OCP\Share\IManager as ShareManager; +use OC\KnownUser\KnownUserService; +use PHPUnit\Framework\MockObject\MockObject; use Psr\Log\LoggerInterface; use Sabre\CalDAV\Xml\Property\SupportedCalendarComponentSet; use Sabre\DAV\Xml\Property\Href; @@ -58,15 +59,18 @@ abstract class AbstractCalDavBackend extends TestCase { /** @var CalDavBackend */ protected $backend; - /** @var Principal | \PHPUnit\Framework\MockObject\MockObject */ + /** @var Principal | MockObject */ protected $principal; - /** @var IUserManager|\PHPUnit\Framework\MockObject\MockObject */ + /** @var IUserManager|MockObject */ protected $userManager; - /** @var IGroupManager|\PHPUnit\Framework\MockObject\MockObject */ + /** @var IGroupManager|MockObject */ protected $groupManager; - /** @var IEventDispatcher|\PHPUnit\Framework\MockObject\MockObject */ + /** @var IEventDispatcher|MockObject */ protected $dispatcher; + + /** @var IConfig | MockObject */ + private $config; /** @var ISecureRandom */ private $random; /** @var LoggerInterface*/ diff --git a/lib/private/Files/Cache/Cache.php b/lib/private/Files/Cache/Cache.php index 110e55bcc6c..f23635aa01b 100644 --- a/lib/private/Files/Cache/Cache.php +++ b/lib/private/Files/Cache/Cache.php @@ -952,7 +952,7 @@ class Cache implements ICache { * use the one with the highest id gives the best result with the background scanner, since that is most * likely the folder where we stopped scanning previously * - * @return string|bool the path of the folder or false when no folder matched + * @return string|false the path of the folder or false when no folder matched */ public function getIncomplete() { $query = $this->getQueryBuilder(); diff --git a/tests/lib/DirectEditing/ManagerTest.php b/tests/lib/DirectEditing/ManagerTest.php index b00de02bcf5..e19c44b1a06 100644 --- a/tests/lib/DirectEditing/ManagerTest.php +++ b/tests/lib/DirectEditing/ManagerTest.php @@ -105,6 +105,10 @@ class ManagerTest extends TestCase { * @var MockObject|Folder */ private $userFolder; + /** + * @var MockObject|IL10N + */ + private $l10n; /** * @var MockObject|IManager */ diff --git a/tests/lib/Metadata/FileMetadataMapperTest.php b/tests/lib/Metadata/FileMetadataMapperTest.php index 8e385351be2..1a005f24b8a 100644 --- a/tests/lib/Metadata/FileMetadataMapperTest.php +++ b/tests/lib/Metadata/FileMetadataMapperTest.php @@ -24,6 +24,7 @@ namespace Test\Metadata; use OC\Metadata\FileMetadataMapper; use OC\Metadata\FileMetadata; +use PHPUnit\Framework\MockObject\MockObject; /** * @group DB @@ -33,9 +34,12 @@ class FileMetadataMapperTest extends \Test\TestCase { /** @var IDBConnection */ protected $connection; - /** @var SystemConfig|\PHPUnit\Framework\MockObject\MockObject */ + /** @var SystemConfig|MockObject */ protected $config; + /** @var FileMetadataMapper|MockObject */ + protected $mapper; + protected function setUp(): void { parent::setUp(); diff --git a/tests/lib/Repair/RepairMimeTypesTest.php b/tests/lib/Repair/RepairMimeTypesTest.php index 26a52459c24..53c8e53d486 100644 --- a/tests/lib/Repair/RepairMimeTypesTest.php +++ b/tests/lib/Repair/RepairMimeTypesTest.php @@ -36,7 +36,6 @@ class RepairMimeTypesTest extends \Test\TestCase { protected function setUp(): void { parent::setUp(); - $this->savedMimetypeLoader = \OC::$server->getMimeTypeLoader(); $this->mimetypeLoader = \OC::$server->getMimeTypeLoader(); /** @var IConfig | \PHPUnit\Framework\MockObject\MockObject $config */ diff --git a/tests/lib/User/SessionTest.php b/tests/lib/User/SessionTest.php index 0e199e5d5b5..735a3b3d06a 100644 --- a/tests/lib/User/SessionTest.php +++ b/tests/lib/User/SessionTest.php @@ -43,6 +43,8 @@ use OC\Security\CSRF\CsrfTokenManager; class SessionTest extends \Test\TestCase { /** @var ITimeFactory|MockObject */ private $timeFactory; + /** @var IProvider|MockObject */ + private $tokenProvider; /** @var IConfig|MockObject */ private $config; /** @var Throttler|MockObject */ -- cgit v1.2.3 From 7ae67917374844bc704a15da46159b5c19513f60 Mon Sep 17 00:00:00 2001 From: Côme Chilliet Date: Tue, 2 Aug 2022 14:23:14 +0200 Subject: Document all getIncomplete implementations as returning string|false MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Côme Chilliet --- lib/private/Files/Cache/Wrapper/CacheJail.php | 2 +- lib/private/Files/Cache/Wrapper/CacheWrapper.php | 2 +- lib/public/Files/Cache/ICache.php | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) (limited to 'lib/private/Files') diff --git a/lib/private/Files/Cache/Wrapper/CacheJail.php b/lib/private/Files/Cache/Wrapper/CacheJail.php index 7183a6c0d2a..4053042edd9 100644 --- a/lib/private/Files/Cache/Wrapper/CacheJail.php +++ b/lib/private/Files/Cache/Wrapper/CacheJail.php @@ -267,7 +267,7 @@ class CacheJail extends CacheWrapper { * use the one with the highest id gives the best result with the background scanner, since that is most * likely the folder where we stopped scanning previously * - * @return string|bool the path of the folder or false when no folder matched + * @return string|false the path of the folder or false when no folder matched */ public function getIncomplete() { // not supported diff --git a/lib/private/Files/Cache/Wrapper/CacheWrapper.php b/lib/private/Files/Cache/Wrapper/CacheWrapper.php index e5300dc75f5..66ae83fd144 100644 --- a/lib/private/Files/Cache/Wrapper/CacheWrapper.php +++ b/lib/private/Files/Cache/Wrapper/CacheWrapper.php @@ -267,7 +267,7 @@ class CacheWrapper extends Cache { * use the one with the highest id gives the best result with the background scanner, since that is most * likely the folder where we stopped scanning previously * - * @return string|bool the path of the folder or false when no folder matched + * @return string|false the path of the folder or false when no folder matched */ public function getIncomplete() { return $this->getCache()->getIncomplete(); diff --git a/lib/public/Files/Cache/ICache.php b/lib/public/Files/Cache/ICache.php index e27f4207f1e..37e71f3ac79 100644 --- a/lib/public/Files/Cache/ICache.php +++ b/lib/public/Files/Cache/ICache.php @@ -243,7 +243,7 @@ interface ICache { * use the one with the highest id gives the best result with the background scanner, since that is most * likely the folder where we stopped scanning previously * - * @return string|bool the path of the folder or false when no folder matched + * @return string|false the path of the folder or false when no folder matched * @since 9.0.0 */ public function getIncomplete(); -- cgit v1.2.3