Merge branch 'addressbook-uid-check-migration' of https://github.com/nextcloud/server into addressbook-uid-check-migration

Signed-off-by: John Molakvoæ (skjnldsv) <skjnldsv@protonmail.com>
This commit is contained in:
John Molakvoæ (skjnldsv) 2018-11-01 17:33:50 +01:00
commit 98f30c2dab
No known key found for this signature in database
GPG Key ID: 60C25B8C072916CF
265 changed files with 7507 additions and 1051 deletions

View File

@ -170,7 +170,7 @@ class IMipPlugin extends SabreIMipPlugin {
$vevent = $iTipMessage->message->VEVENT;
$attendee = $this->getCurrentAttendee($iTipMessage);
$defaultLang = $this->config->getUserValue($this->userId, 'core', 'lang', $this->l10nFactory->findLanguage());
$defaultLang = $this->l10nFactory->findLanguage();
$lang = $this->getAttendeeLangOrDefault($defaultLang, $attendee);
$l10n = $this->l10nFactory->get('dav', $lang);

View File

@ -88,16 +88,26 @@ class AddressBookImpl implements IAddressBook {
/**
* @param string $pattern which should match within the $searchProperties
* @param array $searchProperties defines the properties within the query pattern should match
* @param array $options - for future use. One should always have options!
* @param array $options Options to define the output format
* - types boolean (since 15.0.0) If set to true, fields that come with a TYPE property will be an array
* example: ['id' => 5, 'FN' => 'Thomas Tanghus', 'EMAIL' => ['type => 'HOME', 'value' => 'g@h.i']]
* @return array an array of contacts which are arrays of key-value-pairs
* example result:
* [
* ['id' => 0, 'FN' => 'Thomas Müller', 'EMAIL' => 'a@b.c', 'GEO' => '37.386013;-122.082932'],
* ['id' => 5, 'FN' => 'Thomas Tanghus', 'EMAIL' => ['d@e.f', 'g@h.i']]
* ]
* @return array an array of contacts which are arrays of key-value-pairs
* @since 5.0.0
*/
public function search($pattern, $searchProperties, $options) {
$results = $this->backend->search($this->getKey(), $pattern, $searchProperties);
$withTypes = \array_key_exists('types', $options) && $options['types'] === true;
$vCards = [];
foreach ($results as $result) {
$vCards[] = $this->vCard2Array($result['uri'], $this->readCard($result['carddata']));
$vCards[] = $this->vCard2Array($result['uri'], $this->readCard($result['carddata']), $withTypes);
}
return $vCards;
@ -220,7 +230,7 @@ class AddressBookImpl implements IAddressBook {
* @param VCard $vCard
* @return array
*/
protected function vCard2Array($uri, VCard $vCard) {
protected function vCard2Array($uri, VCard $vCard, $withTypes = false) {
$result = [
'URI' => $uri,
];
@ -255,15 +265,28 @@ class AddressBookImpl implements IAddressBook {
$result[$property->name] = [];
}
$result[$property->name][] = $property->getValue();
$type = $this->getTypeFromProperty($property);
if ($withTypes) {
$result[$property->name][] = [
'type' => $type,
'value' => $property->getValue()
];
} else {
$result[$property->name][] = $property->getValue();
}
} else {
$result[$property->name] = $property->getValue();
}
}
if ($this->addressBookInfo['principaluri'] === 'principals/system/system' &&
$this->addressBookInfo['uri'] === 'system') {
if (
$this->addressBookInfo['principaluri'] === 'principals/system/system' && (
$this->addressBookInfo['uri'] === 'system' ||
$this->addressBookInfo['{DAV:}displayname'] === $this->urlGenerator->getBaseUrl()
)
) {
$result['isLocalSystemBook'] = true;
}
return $result;

View File

@ -1134,8 +1134,8 @@ class CardDavBackend implements BackendInterface, SyncSupport {
* Extract UID from vcard
*
* @param string $cardData the vcard raw data
* @return string the uid or empty if none
* @throws BadRequest
* @return string the uid
* @throws BadRequest if no UID is available
*/
private function getUID($cardData) {
$vCard = Reader::read($cardData);

View File

@ -164,14 +164,19 @@ class File extends Node implements IFile {
$this->changeLock(ILockingProvider::LOCK_EXCLUSIVE);
}
$target = $partStorage->fopen($internalPartPath, 'wb');
if ($target === false) {
\OC::$server->getLogger()->error('\OC\Files\Filesystem::fopen() failed', ['app' => 'webdav']);
// because we have no clue about the cause we can only throw back a 500/Internal Server Error
throw new Exception('Could not write file contents');
if ($partStorage->instanceOfStorage(Storage\IWriteStreamStorage::class)) {
$count = $partStorage->writeStream($internalPartPath, $data);
$result = $count > 0;
} else {
$target = $partStorage->fopen($internalPartPath, 'wb');
if ($target === false) {
\OC::$server->getLogger()->error('\OC\Files\Filesystem::fopen() failed', ['app' => 'webdav']);
// because we have no clue about the cause we can only throw back a 500/Internal Server Error
throw new Exception('Could not write file contents');
}
list($count, $result) = \OC_Helper::streamCopy($data, $target);
fclose($target);
}
list($count, $result) = \OC_Helper::streamCopy($data, $target);
fclose($target);
if ($result === false) {
$expected = -1;
@ -185,7 +190,7 @@ class File extends Node implements IFile {
// double check if the file was fully received
// compare expected and actual size
if (isset($_SERVER['CONTENT_LENGTH']) && $_SERVER['REQUEST_METHOD'] === 'PUT') {
$expected = (int) $_SERVER['CONTENT_LENGTH'];
$expected = (int)$_SERVER['CONTENT_LENGTH'];
if ($count !== $expected) {
throw new BadRequest('expected filesize ' . $expected . ' got ' . $count);
}
@ -219,7 +224,7 @@ class File extends Node implements IFile {
$renameOkay = $storage->moveFromStorage($partStorage, $internalPartPath, $internalPath);
$fileExists = $storage->file_exists($internalPath);
if ($renameOkay === false || $fileExists === false) {
\OC::$server->getLogger()->error('renaming part file to final file failed ($run: ' . ( $run ? 'true' : 'false' ) . ', $renameOkay: ' . ( $renameOkay ? 'true' : 'false' ) . ', $fileExists: ' . ( $fileExists ? 'true' : 'false' ) . ')', ['app' => 'webdav']);
\OC::$server->getLogger()->error('renaming part file to final file failed $renameOkay: ' . ($renameOkay ? 'true' : 'false') . ', $fileExists: ' . ($fileExists ? 'true' : 'false') . ')', ['app' => 'webdav']);
throw new Exception('Could not rename part file to final file');
}
} catch (ForbiddenException $ex) {
@ -246,7 +251,7 @@ class File extends Node implements IFile {
$this->header('X-OC-MTime: accepted');
}
}
if ($view) {
$this->emitPostHooks($exists);
}
@ -443,7 +448,7 @@ class File extends Node implements IFile {
//detect aborted upload
if (isset ($_SERVER['REQUEST_METHOD']) && $_SERVER['REQUEST_METHOD'] === 'PUT') {
if (isset($_SERVER['CONTENT_LENGTH'])) {
$expected = (int) $_SERVER['CONTENT_LENGTH'];
$expected = (int)$_SERVER['CONTENT_LENGTH'];
if ($bytesWritten !== $expected) {
$chunk_handler->remove($info['index']);
throw new BadRequest(

View File

@ -164,7 +164,7 @@ class FileTest extends \Test\TestCase {
public function testSimplePutFails($thrownException, $expectedException, $checkPreviousClass = true) {
// setup
$storage = $this->getMockBuilder(Local::class)
->setMethods(['fopen'])
->setMethods(['writeStream'])
->setConstructorArgs([['datadir' => \OC::$server->getTempManager()->getTemporaryFolder()]])
->getMock();
\OC\Files\Filesystem::mount($storage, [], $this->user . '/');
@ -182,11 +182,11 @@ class FileTest extends \Test\TestCase {
if ($thrownException !== null) {
$storage->expects($this->once())
->method('fopen')
->method('writeStream')
->will($this->throwException($thrownException));
} else {
$storage->expects($this->once())
->method('fopen')
->method('writeStream')
->will($this->returnValue(false));
}

View File

@ -15,7 +15,13 @@
#app-sidebar .mainFileInfoView .permalink {
padding: 6px 10px;
vertical-align: text-top;
vertical-align: top;
opacity: .6;
&:hover,
&:focus {
opacity: 1;
}
}
#app-sidebar .mainFileInfoView .permalink-field>input {
clear: both;
@ -87,7 +93,7 @@
}
#app-sidebar .fileName h3 {
width: calc(100% - 36px); /* 36px is the with of the copy link icon */
width: calc(100% - 42px); /* 36px is the with of the copy link icon, but this breaks so we add some more to be sure */
display: inline-block;
padding: 5px 0;
margin: -5px 0;

View File

@ -8,7 +8,12 @@
}
/* FILE MENU */
.actions { padding:5px; height:32px; display: inline-block; float: left; }
.actions {
padding: 5px;
height: 100%;
display: inline-block;
float: left;
}
.actions input, .actions button, .actions .button { margin:0; float:left; }
.actions .button a { color: #555; }
.actions .button a:hover,
@ -316,6 +321,7 @@ table td.filename .thumbnail {
background-size: 32px;
margin-left: 9px;
margin-top: 9px;
border-radius: var(--border-radius);
cursor: pointer;
position: absolute;
z-index: 4;
@ -658,8 +664,14 @@ table.dragshadow td.size {
top: 100%;
margin-top: 4px;
min-width: 100px;
margin-left: 7px;
margin-left: 22px; /* Align left edge below center of + button … */
transform: translateX(-50%); /* … then center it below button */
z-index: 1001;
/* Center triangle */
&::after {
left: calc(50% - 8px) !important;
}
}
#filestable .filename .action .icon,

View File

@ -174,6 +174,9 @@
// hide other tabs
$tabsContainer.find('.tab').addClass('hidden');
$tabsContainer.attr('class', 'tabsContainer');
$tabsContainer.addClass(tabView.getTabsContainerExtraClasses());
// tab already rendered ?
if (!$tabEl.length) {
// render tab

View File

@ -40,6 +40,21 @@
}
},
/**
* Returns the extra CSS classes used by the tabs container when this
* tab is the selected one.
*
* In general you should not extend this method, as tabs should not
* modify the classes of its container; this is reserved as a last
* resort for very specific cases in which there is no other way to get
* the proper style or behaviour.
*
* @return {String} space-separated CSS classes
*/
getTabsContainerExtraClasses: function() {
return '';
},
/**
* Returns the tab label
*

View File

@ -142,6 +142,7 @@ OC.L10N.register(
"WebDAV" : "WebDAV",
"Use this address to <a href=\"%s\" target=\"_blank\" rel=\"noreferrer noopener\">access your Files via WebDAV</a>" : "Použijte tuto adresu pro <a href=\"%s\" target=\"_blank\" rel=\"noreferrer noopener\">přístup k vašim souborům přes WebDAV</a>",
"Cancel upload" : "Zrušit nahrávání",
"Toggle grid view" : "Přepnout zobrazení mřížky",
"No files in here" : "Žádné soubory",
"Upload some content or sync with your devices!" : "Nahrajte nějaký obsah nebo synchronizujte se svými přístroji!",
"No entries found in this folder" : "V této složce nebylo nic nalezeno",

View File

@ -140,6 +140,7 @@
"WebDAV" : "WebDAV",
"Use this address to <a href=\"%s\" target=\"_blank\" rel=\"noreferrer noopener\">access your Files via WebDAV</a>" : "Použijte tuto adresu pro <a href=\"%s\" target=\"_blank\" rel=\"noreferrer noopener\">přístup k vašim souborům přes WebDAV</a>",
"Cancel upload" : "Zrušit nahrávání",
"Toggle grid view" : "Přepnout zobrazení mřížky",
"No files in here" : "Žádné soubory",
"Upload some content or sync with your devices!" : "Nahrajte nějaký obsah nebo synchronizujte se svými přístroji!",
"No entries found in this folder" : "V této složce nebylo nic nalezeno",

View File

@ -8,16 +8,16 @@
*
*/
if (!OCA.External) {
if (!OCA.Files_External) {
/**
* @namespace
*/
OCA.External = {};
OCA.Files_External = {};
}
/**
* @namespace
*/
OCA.External.App = {
OCA.Files_External.App = {
fileList: null,
@ -26,7 +26,7 @@ OCA.External.App = {
return this.fileList;
}
this.fileList = new OCA.External.FileList(
this.fileList = new OCA.Files_External.FileList(
$el,
{
fileActions: this._createFileActions()
@ -67,10 +67,10 @@ OCA.External.App = {
$(document).ready(function() {
$('#app-content-extstoragemounts').on('show', function(e) {
OCA.External.App.initList($(e.target));
OCA.Files_External.App.initList($(e.target));
});
$('#app-content-extstoragemounts').on('hide', function() {
OCA.External.App.removeList();
OCA.Files_External.App.removeList();
});
/* Status Manager */
@ -82,27 +82,27 @@ $(document).ready(function() {
if (e.dir === '/') {
var mount_point = e.previousDir.split('/', 2)[1];
// Every time that we return to / root folder from a mountpoint, mount_point status is rechecked
OCA.External.StatusManager.getMountPointList(function() {
OCA.External.StatusManager.recheckConnectivityForMount([mount_point], true);
OCA.Files_External.StatusManager.getMountPointList(function() {
OCA.Files_External.StatusManager.recheckConnectivityForMount([mount_point], true);
});
}
})
.on('fileActionsReady', function(e){
if ($.isArray(e.$files)) {
if (OCA.External.StatusManager.mountStatus === null ||
OCA.External.StatusManager.mountPointList === null ||
_.size(OCA.External.StatusManager.mountStatus) !== _.size(OCA.External.StatusManager.mountPointList)) {
if (OCA.Files_External.StatusManager.mountStatus === null ||
OCA.Files_External.StatusManager.mountPointList === null ||
_.size(OCA.Files_External.StatusManager.mountStatus) !== _.size(OCA.Files_External.StatusManager.mountPointList)) {
// Will be the very first check when the files view will be loaded
OCA.External.StatusManager.launchFullConnectivityCheckOneByOne();
OCA.Files_External.StatusManager.launchFullConnectivityCheckOneByOne();
} else {
// When we change between general files view and external files view
OCA.External.StatusManager.getMountPointList(function(){
OCA.Files_External.StatusManager.getMountPointList(function(){
var fileNames = [];
$.each(e.$files, function(key, value){
fileNames.push(value.attr('data-file'));
});
// Recheck if launched but work from cache
OCA.External.StatusManager.recheckConnectivityForMount(fileNames, false);
OCA.Files_External.StatusManager.recheckConnectivityForMount(fileNames, false);
});
}
}

View File

@ -10,7 +10,7 @@
(function() {
/**
* @class OCA.External.FileList
* @class OCA.Files_External.FileList
* @augments OCA.Files.FileList
*
* @classdesc External storage file list.
@ -27,7 +27,7 @@
};
FileList.prototype = _.extend({}, OCA.Files.FileList.prototype,
/** @lends OCA.External.FileList.prototype */ {
/** @lends OCA.Files_External.FileList.prototype */ {
appName: 'External storages',
_allowSelection: false,
@ -43,7 +43,7 @@
},
/**
* @param {OCA.External.MountPointInfo} fileData
* @param {OCA.Files_External.MountPointInfo} fileData
*/
_createRow: function(fileData) {
// TODO: hook earlier and render the whole row here
@ -138,12 +138,12 @@
/**
* Mount point info attributes.
*
* @typedef {Object} OCA.External.MountPointInfo
* @typedef {Object} OCA.Files_External.MountPointInfo
*
* @property {String} name mount point name
* @property {String} scope mount point scope "personal" or "system"
* @property {String} backend external storage backend name
*/
OCA.External.FileList = FileList;
OCA.Files_External.FileList = FileList;
})();

View File

@ -4,7 +4,7 @@ $(document).ready(function() {
$tr.find('.configuration input.auth-param').attr('disabled', 'disabled').addClass('disabled-success');
}
OCA.External.Settings.mountConfig.whenSelectAuthMechanism(function($tr, authMechanism, scheme, onCompletion) {
OCA.Files_External.Settings.mountConfig.whenSelectAuthMechanism(function($tr, authMechanism, scheme, onCompletion) {
if (authMechanism === 'oauth1::oauth1') {
var config = $tr.find('.configuration');
config.append($(document.createElement('input'))
@ -34,7 +34,7 @@ $(document).ready(function() {
$(token).val(result.access_token);
$(token_secret).val(result.access_token_secret);
$(configured).val('true');
OCA.External.Settings.mountConfig.saveStorageConfig($tr, function(status) {
OCA.Files_External.Settings.mountConfig.saveStorageConfig($tr, function(status) {
if (status) {
displayGranted($tr);
}
@ -64,7 +64,7 @@ $(document).ready(function() {
$(configured).val('false');
$(token).val(result.data.request_token);
$(token_secret).val(result.data.request_token_secret);
OCA.External.Settings.mountConfig.saveStorageConfig(tr, function() {
OCA.Files_External.Settings.mountConfig.saveStorageConfig(tr, function() {
window.location = result.data.url;
});
} else {

View File

@ -4,7 +4,7 @@ $(document).ready(function() {
$tr.find('.configuration input.auth-param').attr('disabled', 'disabled').addClass('disabled-success');
}
OCA.External.Settings.mountConfig.whenSelectAuthMechanism(function($tr, authMechanism, scheme, onCompletion) {
OCA.Files_External.Settings.mountConfig.whenSelectAuthMechanism(function($tr, authMechanism, scheme, onCompletion) {
if (authMechanism === 'oauth2::oauth2') {
var config = $tr.find('.configuration');
config.append($(document.createElement('input'))
@ -43,7 +43,7 @@ $(document).ready(function() {
if (result && result.status == 'success') {
$(token).val(result.data.token);
$(configured).val('true');
OCA.External.Settings.mountConfig.saveStorageConfig($tr, function(status) {
OCA.Files_External.Settings.mountConfig.saveStorageConfig($tr, function(status) {
if (status) {
displayGranted($tr);
}
@ -80,7 +80,7 @@ $(document).ready(function() {
if (result && result.status == 'success') {
$(configured).val('false');
$(token).val('false');
OCA.External.Settings.mountConfig.saveStorageConfig(tr, function(status) {
OCA.Files_External.Settings.mountConfig.saveStorageConfig(tr, function(status) {
window.location = result.data.url;
});
} else {

View File

@ -1,6 +1,6 @@
$(document).ready(function() {
OCA.External.Settings.mountConfig.whenSelectAuthMechanism(function($tr, authMechanism, scheme, onCompletion) {
OCA.Files_External.Settings.mountConfig.whenSelectAuthMechanism(function($tr, authMechanism, scheme, onCompletion) {
if (scheme === 'publickey' && authMechanism === 'publickey::rsa') {
var config = $tr.find('.configuration');
if ($(config).find('[name="public_key_generate"]').length === 0) {
@ -53,7 +53,7 @@ $(document).ready(function() {
if (result && result.status === 'success') {
$(config).find('[data-parameter="public_key"]').val(result.data.public_key).keyup();
$(config).find('[data-parameter="private_key"]').val(result.data.private_key);
OCA.External.Settings.mountConfig.saveStorageConfig(tr, function() {
OCA.Files_External.Settings.mountConfig.saveStorageConfig(tr, function() {
// Nothing to do
});
} else {

View File

@ -124,14 +124,14 @@ var RollingQueue = function (functionList, queueWindow, callback) {
};
};
if (!OCA.External) {
OCA.External = {};
if (!OCA.Files_External) {
OCA.Files_External = {};
}
if (!OCA.External.StatusManager) {
OCA.External.StatusManager = {};
if (!OCA.Files_External.StatusManager) {
OCA.Files_External.StatusManager = {};
}
OCA.External.StatusManager.RollingQueue = RollingQueue;
OCA.Files_External.StatusManager.RollingQueue = RollingQueue;
})();

View File

@ -163,7 +163,7 @@ function addSelect2 ($elements, userListLimit) {
}
/**
* @class OCA.External.Settings.StorageConfig
* @class OCA.Files_External.Settings.StorageConfig
*
* @classdesc External storage config
*/
@ -185,7 +185,7 @@ StorageConfig.Visibility = {
DEFAULT: 3
};
/**
* @memberof OCA.External.Settings
* @memberof OCA.Files_External.Settings
*/
StorageConfig.prototype = {
_url: null,
@ -348,8 +348,8 @@ StorageConfig.prototype = {
};
/**
* @class OCA.External.Settings.GlobalStorageConfig
* @augments OCA.External.Settings.StorageConfig
* @class OCA.Files_External.Settings.GlobalStorageConfig
* @augments OCA.Files_External.Settings.StorageConfig
*
* @classdesc Global external storage config
*/
@ -359,10 +359,10 @@ var GlobalStorageConfig = function(id) {
this.applicableGroups = [];
};
/**
* @memberOf OCA.External.Settings
* @memberOf OCA.Files_External.Settings
*/
GlobalStorageConfig.prototype = _.extend({}, StorageConfig.prototype,
/** @lends OCA.External.Settings.GlobalStorageConfig.prototype */ {
/** @lends OCA.Files_External.Settings.GlobalStorageConfig.prototype */ {
_url: 'apps/files_external/globalstorages',
/**
@ -402,8 +402,8 @@ GlobalStorageConfig.prototype = _.extend({}, StorageConfig.prototype,
});
/**
* @class OCA.External.Settings.UserStorageConfig
* @augments OCA.External.Settings.StorageConfig
* @class OCA.Files_External.Settings.UserStorageConfig
* @augments OCA.Files_External.Settings.StorageConfig
*
* @classdesc User external storage config
*/
@ -411,13 +411,13 @@ var UserStorageConfig = function(id) {
this.id = id;
};
UserStorageConfig.prototype = _.extend({}, StorageConfig.prototype,
/** @lends OCA.External.Settings.UserStorageConfig.prototype */ {
/** @lends OCA.Files_External.Settings.UserStorageConfig.prototype */ {
_url: 'apps/files_external/userstorages'
});
/**
* @class OCA.External.Settings.UserGlobalStorageConfig
* @augments OCA.External.Settings.StorageConfig
* @class OCA.Files_External.Settings.UserGlobalStorageConfig
* @augments OCA.Files_External.Settings.StorageConfig
*
* @classdesc User external storage config
*/
@ -425,13 +425,13 @@ var UserGlobalStorageConfig = function (id) {
this.id = id;
};
UserGlobalStorageConfig.prototype = _.extend({}, StorageConfig.prototype,
/** @lends OCA.External.Settings.UserStorageConfig.prototype */ {
/** @lends OCA.Files_External.Settings.UserStorageConfig.prototype */ {
_url: 'apps/files_external/userglobalstorages'
});
/**
* @class OCA.External.Settings.MountOptionsDropdown
* @class OCA.Files_External.Settings.MountOptionsDropdown
*
* @classdesc Dropdown for mount options
*
@ -440,7 +440,7 @@ UserGlobalStorageConfig.prototype = _.extend({}, StorageConfig.prototype,
var MountOptionsDropdown = function() {
};
/**
* @memberof OCA.External.Settings
* @memberof OCA.Files_External.Settings
*/
MountOptionsDropdown.prototype = {
/**
@ -462,7 +462,7 @@ MountOptionsDropdown.prototype = {
MountOptionsDropdown._last.hide();
}
var $el = $(OCA.External.Templates.mountOptionsDropDown({
var $el = $(OCA.Files_External.Templates.mountOptionsDropDown({
mountOptionsEncodingLabel: t('files_external', 'Compatibility with Mac NFD encoding (slow)'),
mountOptionsEncryptLabel: t('files_external', 'Enable encryption'),
mountOptionsPreviewsLabel: t('files_external', 'Enable previews'),
@ -549,7 +549,7 @@ MountOptionsDropdown.prototype = {
};
/**
* @class OCA.External.Settings.MountConfigListView
* @class OCA.Files_External.Settings.MountConfigListView
*
* @classdesc Mount configuration list view
*
@ -574,7 +574,7 @@ MountConfigListView.ParameterTypes = {
};
/**
* @memberOf OCA.External.Settings
* @memberOf OCA.Files_External.Settings
*/
MountConfigListView.prototype = _.extend({
@ -633,9 +633,9 @@ MountConfigListView.prototype = _.extend({
this.$el = $el;
this._isPersonal = ($el.data('admin') !== true);
if (this._isPersonal) {
this._storageConfigClass = OCA.External.Settings.UserStorageConfig;
this._storageConfigClass = OCA.Files_External.Settings.UserStorageConfig;
} else {
this._storageConfigClass = OCA.External.Settings.GlobalStorageConfig;
this._storageConfigClass = OCA.Files_External.Settings.GlobalStorageConfig;
}
if (options && !_.isUndefined(options.userListLimit)) {
@ -1008,7 +1008,7 @@ MountConfigListView.prototype = _.extend({
* Gets the storage model from the given row
*
* @param $tr row element
* @return {OCA.External.StorageConfig} storage model instance
* @return {OCA.Files_External.StorageConfig} storage model instance
*/
getStorageConfig: function($tr) {
var storageId = $tr.data('id');
@ -1367,13 +1367,13 @@ $(document).ready(function() {
});
// global instance
OCA.External.Settings.mountConfig = mountConfigListView;
OCA.Files_External.Settings.mountConfig = mountConfigListView;
/**
* Legacy
*
* @namespace
* @deprecated use OCA.External.Settings.mountConfig instead
* @deprecated use OCA.Files_External.Settings.mountConfig instead
*/
OC.MountConfig = {
saveStorage: _.bind(mountConfigListView.saveStorageConfig, mountConfigListView)
@ -1382,14 +1382,14 @@ $(document).ready(function() {
// export
OCA.External = OCA.External || {};
OCA.Files_External = OCA.Files_External || {};
/**
* @namespace
*/
OCA.External.Settings = OCA.External.Settings || {};
OCA.Files_External.Settings = OCA.Files_External.Settings || {};
OCA.External.Settings.GlobalStorageConfig = GlobalStorageConfig;
OCA.External.Settings.UserStorageConfig = UserStorageConfig;
OCA.External.Settings.MountConfigListView = MountConfigListView;
OCA.Files_External.Settings.GlobalStorageConfig = GlobalStorageConfig;
OCA.Files_External.Settings.UserStorageConfig = UserStorageConfig;
OCA.Files_External.Settings.MountConfigListView = MountConfigListView;
})();

View File

@ -14,15 +14,15 @@
/** @global Handlebars */
if (!OCA.External) {
OCA.External = {};
if (!OCA.Files_External) {
OCA.Files_External = {};
}
if (!OCA.External.StatusManager) {
OCA.External.StatusManager = {};
if (!OCA.Files_External.StatusManager) {
OCA.Files_External.StatusManager = {};
}
OCA.External.StatusManager = {
OCA.Files_External.StatusManager = {
mountStatus: null,
mountPointList: null,
@ -209,18 +209,18 @@ OCA.External.StatusManager = {
var mountPoint = mountData.mount_point;
if (mountStatus.status > 0) {
var trElement = FileList.findFileEl(OCA.External.StatusManager.Utils.jqSelEscape(mountPoint));
var trElement = FileList.findFileEl(OCA.Files_External.StatusManager.Utils.jqSelEscape(mountPoint));
var route = OCA.External.StatusManager.Utils.getIconRoute(trElement) + '-error';
var route = OCA.Files_External.StatusManager.Utils.getIconRoute(trElement) + '-error';
if (OCA.External.StatusManager.Utils.isCorrectViewAndRootFolder()) {
OCA.External.StatusManager.Utils.showIconError(mountPoint, $.proxy(OCA.External.StatusManager.manageMountPointError, OCA.External.StatusManager), route);
if (OCA.Files_External.StatusManager.Utils.isCorrectViewAndRootFolder()) {
OCA.Files_External.StatusManager.Utils.showIconError(mountPoint, $.proxy(OCA.Files_External.StatusManager.manageMountPointError, OCA.Files_External.StatusManager), route);
}
return false;
} else {
if (OCA.External.StatusManager.Utils.isCorrectViewAndRootFolder()) {
OCA.External.StatusManager.Utils.restoreFolder(mountPoint);
OCA.External.StatusManager.Utils.toggleLink(mountPoint, true, true);
if (OCA.Files_External.StatusManager.Utils.isCorrectViewAndRootFolder()) {
OCA.Files_External.StatusManager.Utils.restoreFolder(mountPoint);
OCA.Files_External.StatusManager.Utils.toggleLink(mountPoint, true, true);
}
return true;
}
@ -235,7 +235,7 @@ OCA.External.StatusManager = {
processMountList: function (mountList) {
var elementList = null;
$.each(mountList, function (name, value) {
var trElement = $('#fileList tr[data-file=\"' + OCA.External.StatusManager.Utils.jqSelEscape(value.mount_point) + '\"]'); //FileList.findFileEl(OCA.External.StatusManager.Utils.jqSelEscape(value.mount_point));
var trElement = $('#fileList tr[data-file=\"' + OCA.Files_External.StatusManager.Utils.jqSelEscape(value.mount_point) + '\"]'); //FileList.findFileEl(OCA.Files_External.StatusManager.Utils.jqSelEscape(value.mount_point));
trElement.attr('data-external-backend', value.backend);
if (elementList) {
elementList = elementList.add(trElement);
@ -245,14 +245,14 @@ OCA.External.StatusManager = {
});
if (elementList instanceof $) {
if (OCA.External.StatusManager.Utils.isCorrectViewAndRootFolder()) {
if (OCA.Files_External.StatusManager.Utils.isCorrectViewAndRootFolder()) {
// Put their custom icon
OCA.External.StatusManager.Utils.changeFolderIcon(elementList);
OCA.Files_External.StatusManager.Utils.changeFolderIcon(elementList);
// Save default view
OCA.External.StatusManager.Utils.storeDefaultFolderIconAndBgcolor(elementList);
OCA.Files_External.StatusManager.Utils.storeDefaultFolderIconAndBgcolor(elementList);
// Disable row until check status
elementList.addClass('externalDisabledRow');
OCA.External.StatusManager.Utils.toggleLink(elementList.find('a.name'), false, false);
OCA.Files_External.StatusManager.Utils.toggleLink(elementList.find('a.name'), false, false);
}
}
},
@ -289,7 +289,7 @@ OCA.External.StatusManager = {
ajaxQueue.push(queueElement);
});
var rolQueue = new OCA.External.StatusManager.RollingQueue(ajaxQueue, 4, function () {
var rolQueue = new OCA.Files_External.StatusManager.RollingQueue(ajaxQueue, 4, function () {
if (!self.notificationHasShown) {
var showNotification = false;
$.each(self.mountStatus, function (key, value) {
@ -335,7 +335,7 @@ OCA.External.StatusManager = {
};
ajaxQueue.push(queueElement);
});
new OCA.External.StatusManager.RollingQueue(ajaxQueue, 4).runQueue();
new OCA.Files_External.StatusManager.RollingQueue(ajaxQueue, 4).runQueue();
},
@ -392,7 +392,7 @@ OCA.External.StatusManager = {
* @param mountData
*/
showCredentialsDialog: function (mountPoint, mountData) {
var dialog = $(OCA.External.Templates.credentialsDialog({
var dialog = $(OCA.Files_External.Templates.credentialsDialog({
credentials_text: t('files_external', 'Please enter the credentials for the {mount} mount', {
'mount': mountPoint
}),
@ -422,7 +422,7 @@ OCA.External.StatusManager = {
OC.Notification.show(t('files_external', 'Credentials saved'), {type: 'error'});
dialog.ocdialog('close');
/* Trigger status check again */
OCA.External.StatusManager.recheckConnectivityForMount([OC.basename(data.mountPoint)], true);
OCA.Files_External.StatusManager.recheckConnectivityForMount([OC.basename(data.mountPoint)], true);
},
error: function () {
$('.oc-dialog-close').show();
@ -461,11 +461,11 @@ OCA.External.StatusManager = {
}
};
OCA.External.StatusManager.Utils = {
OCA.Files_External.StatusManager.Utils = {
showIconError: function (folder, clickAction, errorImageUrl) {
var imageUrl = "url(" + errorImageUrl + ")";
var trFolder = $('#fileList tr[data-file=\"' + OCA.External.StatusManager.Utils.jqSelEscape(folder) + '\"]'); //FileList.findFileEl(OCA.External.StatusManager.Utils.jqSelEscape(folder));
var trFolder = $('#fileList tr[data-file=\"' + OCA.Files_External.StatusManager.Utils.jqSelEscape(folder) + '\"]'); //FileList.findFileEl(OCA.Files_External.StatusManager.Utils.jqSelEscape(folder));
this.changeFolderIcon(folder, imageUrl);
this.toggleLink(folder, false, clickAction);
trFolder.addClass('externalErroredRow');
@ -479,7 +479,7 @@ OCA.External.StatusManager.Utils = {
if (folder instanceof $) {
trFolder = folder;
} else {
trFolder = $('#fileList tr[data-file=\"' + OCA.External.StatusManager.Utils.jqSelEscape(folder) + '\"]'); //FileList.findFileEl(OCA.External.StatusManager.Utils.jqSelEscape(folder)); //$('#fileList tr[data-file=\"' + OCA.External.StatusManager.Utils.jqSelEscape(folder) + '\"]');
trFolder = $('#fileList tr[data-file=\"' + OCA.Files_External.StatusManager.Utils.jqSelEscape(folder) + '\"]'); //FileList.findFileEl(OCA.Files_External.StatusManager.Utils.jqSelEscape(folder)); //$('#fileList tr[data-file=\"' + OCA.Files_External.StatusManager.Utils.jqSelEscape(folder) + '\"]');
}
trFolder.each(function () {
var thisElement = $(this);
@ -505,8 +505,8 @@ OCA.External.StatusManager.Utils = {
if (folder instanceof $) {
trFolder = folder;
} else {
// can't use here FileList.findFileEl(OCA.External.StatusManager.Utils.jqSelEscape(folder)); return incorrect instance of filelist
trFolder = $('#fileList tr[data-file=\"' + OCA.External.StatusManager.Utils.jqSelEscape(folder) + '\"]');
// can't use here FileList.findFileEl(OCA.Files_External.StatusManager.Utils.jqSelEscape(folder)); return incorrect instance of filelist
trFolder = $('#fileList tr[data-file=\"' + OCA.Files_External.StatusManager.Utils.jqSelEscape(folder) + '\"]');
}
trFolder.removeClass('externalErroredRow').removeClass('externalDisabledRow');
var tdChilds = trFolder.find("td.filename div.thumbnail");
@ -526,14 +526,14 @@ OCA.External.StatusManager.Utils = {
if (filename instanceof $) {
//trElementList
$.each(filename, function (index) {
route = OCA.External.StatusManager.Utils.getIconRoute($(this));
route = OCA.Files_External.StatusManager.Utils.getIconRoute($(this));
$(this).attr("data-icon", route);
$(this).find('td.filename div.thumbnail').css('background-image', "url(" + route + ")").css('display', 'none').css('display', 'inline');
});
} else {
file = $("#fileList tr[data-file=\"" + this.jqSelEscape(filename) + "\"] > td.filename div.thumbnail");
var parentTr = file.parents('tr:first');
route = OCA.External.StatusManager.Utils.getIconRoute(parentTr);
route = OCA.Files_External.StatusManager.Utils.getIconRoute(parentTr);
parentTr.attr("data-icon", route);
file.css('background-image', "url(" + route + ")").css('display', 'none').css('display', 'inline');
}

View File

@ -1,5 +1,5 @@
(function() {
var template = Handlebars.template, templates = OCA.External.Templates = OCA.External.Templates || {};
var template = Handlebars.template, templates = OCA.Files_External.Templates = OCA.Files_External.Templates || {};
templates['credentialsDialog'] = template({"compiler":[7,">= 4.0.0"],"main":function(container,depth0,helpers,partials,data) {
var helper, alias1=depth0 != null ? depth0 : (container.nullContext || {}), alias2=helpers.helperMissing, alias3="function", alias4=container.escapeExpression;

View File

@ -19,8 +19,8 @@
*
*/
describe('OCA.External.App tests', function() {
var App = OCA.External.App;
describe('OCA.Files_External.App tests', function() {
var App = OCA.Files_External.App;
var fileList;
beforeEach(function() {

View File

@ -8,7 +8,7 @@
*
*/
describe('OCA.External.FileList tests', function() {
describe('OCA.Files_External.FileList tests', function() {
var testFiles, alertStub, notificationStub, fileList;
beforeEach(function() {
@ -62,7 +62,7 @@ describe('OCA.External.FileList tests', function() {
var ocsResponse;
beforeEach(function() {
fileList = new OCA.External.FileList(
fileList = new OCA.Files_External.FileList(
$('#app-content-container')
);

View File

@ -8,7 +8,7 @@
*
*/
describe('OCA.External.Settings tests', function() {
describe('OCA.Files_External.Settings tests', function() {
var clock;
var select2Stub;
var select2ApplicableUsers;
@ -156,7 +156,7 @@ describe('OCA.External.Settings tests', function() {
beforeEach(function() {
var $el = $('#externalStorage');
view = new OCA.External.Settings.MountConfigListView($el, {encryptionEnabled: false});
view = new OCA.Files_External.Settings.MountConfigListView($el, {encryptionEnabled: false});
});
afterEach(function() {
view = null;

View File

@ -4,6 +4,10 @@
.share-autocomplete-item {
display: flex;
&.merged {
margin-left: 32px;
}
.autocomplete-item-text {
margin-left: 10px;
margin-right: 10px;
@ -12,6 +16,27 @@
overflow: hidden;
line-height: 32px;
vertical-align: middle;
flex-grow: 1;
.ui-state-highlight {
border: none;
margin: 0;
}
}
&.with-description {
.autocomplete-item-text {
line-height: 100%;
}
}
.autocomplete-item-details {
display: block;
line-height: 130%;
font-size: 90%;
opacity: 0.7;
}
.icon {
opacity: .7;
margin-right: 7px;
}
}
@ -204,8 +229,8 @@
}
.ui-autocomplete {
/* limit dropdown height to 4 1/2 entries */
max-height: calc(36px * 4.5);;
/* limit dropdown height to 6 1/2 entries */
max-height: calc(36px * 6.5);
overflow-y: auto;
overflow-x: hidden;
z-index: 1550 !important;

View File

@ -252,6 +252,7 @@ class ShareAPIController extends OCSController {
$result['mail_send'] = $share->getMailSend() ? 1 : 0;
$result['hide_download'] = $share->getHideDownload() ? 1 : 0;
return $result;
}
@ -745,6 +746,7 @@ class ShareAPIController extends OCSController {
* @param string $publicUpload
* @param string $expireDate
* @param string $note
* @param string $hideDownload
* @return DataResponse
* @throws LockedException
* @throws NotFoundException
@ -759,7 +761,8 @@ class ShareAPIController extends OCSController {
string $sendPasswordByTalk = null,
string $publicUpload = null,
string $expireDate = null,
string $note = null
string $note = null,
string $hideDownload = null
): DataResponse {
try {
$share = $this->getShareById($id);
@ -773,7 +776,7 @@ class ShareAPIController extends OCSController {
throw new OCSNotFoundException($this->l->t('Wrong share ID, share doesn\'t exist'));
}
if ($permissions === null && $password === null && $sendPasswordByTalk === null && $publicUpload === null && $expireDate === null && $note === null) {
if ($permissions === null && $password === null && $sendPasswordByTalk === null && $publicUpload === null && $expireDate === null && $note === null && $hideDownload === null) {
throw new OCSBadRequestException($this->l->t('Wrong or no update parameter given'));
}
@ -786,6 +789,13 @@ class ShareAPIController extends OCSController {
*/
if ($share->getShareType() === Share::SHARE_TYPE_LINK) {
// Update hide download state
if ($hideDownload === 'true') {
$share->setHideDownload(true);
} else if ($hideDownload === 'false') {
$share->setHideDownload(false);
}
$newPermissions = null;
if ($publicUpload === 'true') {
$newPermissions = Constants::PERMISSION_READ | Constants::PERMISSION_CREATE | Constants::PERMISSION_UPDATE | Constants::PERMISSION_DELETE;

View File

@ -321,6 +321,7 @@ class ShareController extends AuthPublicShareController {
$shareTmpl['dir'] = '';
$shareTmpl['nonHumanFileSize'] = $share->getNode()->getSize();
$shareTmpl['fileSize'] = \OCP\Util::humanFileSize($share->getNode()->getSize());
$shareTmpl['hideDownload'] = $share->getHideDownload();
// Show file list
$hideFileList = false;
@ -444,12 +445,14 @@ class ShareController extends AuthPublicShareController {
$response = new PublicTemplateResponse($this->appName, 'public', $shareTmpl);
$response->setHeaderTitle($shareTmpl['filename']);
$response->setHeaderDetails($this->l10n->t('shared by %s', [$shareTmpl['displayName']]));
$response->setHeaderActions([
new SimpleMenuAction('download', $this->l10n->t('Download'), 'icon-download-white', $shareTmpl['downloadURL'], 0),
new SimpleMenuAction('download', $this->l10n->t('Download'), 'icon-download', $shareTmpl['downloadURL'], 10, $shareTmpl['fileSize']),
new LinkMenuAction($this->l10n->t('Direct link'), 'icon-public', $shareTmpl['previewURL']),
new ExternalShareMenuAction($this->l10n->t('Add to your Nextcloud'), 'icon-external', $shareTmpl['owner'], $shareTmpl['displayName'], $shareTmpl['filename']),
]);
if (!$share->getHideDownload()) {
$response->setHeaderActions([
new SimpleMenuAction('download', $this->l10n->t('Download'), 'icon-download-white', $shareTmpl['downloadURL'], 0),
new SimpleMenuAction('download', $this->l10n->t('Download'), 'icon-download', $shareTmpl['downloadURL'], 10, $shareTmpl['fileSize']),
new LinkMenuAction($this->l10n->t('Direct link'), 'icon-public', $shareTmpl['previewURL']),
new ExternalShareMenuAction($this->l10n->t('Add to your Nextcloud'), 'icon-external', $shareTmpl['owner'], $shareTmpl['displayName'], $shareTmpl['filename']),
]);
}
$response->setContentSecurityPolicy($csp);

View File

@ -11,13 +11,16 @@
<input type="hidden" id="filesApp" name="filesApp" value="1">
<input type="hidden" id="isPublic" name="isPublic" value="1">
<input type="hidden" name="dir" value="<?php p($_['dir']) ?>" id="dir">
<input type="hidden" name="downloadURL" value="<?php p($_['downloadURL']) ?>" id="downloadURL">
<?php if (!$_['hideDownload']): ?>
<input type="hidden" name="downloadURL" value="<?php p($_['downloadURL']) ?>" id="downloadURL">
<?php endif; ?>
<input type="hidden" name="previewURL" value="<?php p($_['previewURL']) ?>" id="previewURL">
<input type="hidden" name="sharingToken" value="<?php p($_['sharingToken']) ?>" id="sharingToken">
<input type="hidden" name="filename" value="<?php p($_['filename']) ?>" id="filename">
<input type="hidden" name="mimetype" value="<?php p($_['mimetype']) ?>" id="mimetype">
<input type="hidden" name="previewSupported" value="<?php p($_['previewSupported'] ? 'true' : 'false'); ?>" id="previewSupported">
<input type="hidden" name="mimetypeIcon" value="<?php p(\OC::$server->getMimeTypeDetector()->mimeTypeIcon($_['mimetype'])); ?>" id="mimetypeIcon">
<input type="hidden" name="hideDownload" value="<?php p($_['hideDownload'] ? 'true' : 'false'); ?>" id="hideDownload">
<?php
$upload_max_filesize = OC::$server->getIniWrapper()->getBytes('upload_max_filesize');
$post_max_size = OC::$server->getIniWrapper()->getBytes('post_max_size');
@ -58,7 +61,7 @@ $maxUploadFilesize = min($upload_max_filesize, $post_max_size);
<!-- Preview frame is filled via JS to support SVG images for modern browsers -->
<div id="imgframe"></div>
<?php endif; ?>
<?php if ($_['previewURL'] === $_['downloadURL']): ?>
<?php if ($_['previewURL'] === $_['downloadURL'] && !$_['hideDownload']): ?>
<div class="directDownload">
<a href="<?php p($_['downloadURL']); ?>" id="downloadFile" class="button">
<span class="icon icon-download"></span>
@ -97,4 +100,4 @@ $maxUploadFilesize = min($upload_max_filesize, $post_max_size);
data-url="<?php p(\OC::$server->getURLGenerator()->linkTo('files', 'ajax/upload.php')); ?>" />
</div>
<?php endif; ?>
</div>
</div>

View File

@ -353,6 +353,7 @@ class ShareAPIControllerTest extends TestCase {
'note' => 'personal note',
'displayname_file_owner' => 'ownerDisplay',
'mimetype' => 'myMimeType',
'hide_download' => 0,
];
$data[] = [$share, $expected];
@ -397,6 +398,7 @@ class ShareAPIControllerTest extends TestCase {
'note' => 'personal note',
'displayname_file_owner' => 'ownerDisplay',
'mimetype' => 'myFolderMimeType',
'hide_download' => 0,
];
$data[] = [$share, $expected];
@ -445,6 +447,7 @@ class ShareAPIControllerTest extends TestCase {
'note' => 'personal note',
'displayname_file_owner' => 'ownerDisplay',
'mimetype' => 'myFolderMimeType',
'hide_download' => 0,
];
$data[] = [$share, $expected];
@ -2175,6 +2178,7 @@ class ShareAPIControllerTest extends TestCase {
'note' => 'personal note',
'mail_send' => 0,
'mimetype' => 'myMimeType',
'hide_download' => 0,
], $share, [], false
];
// User backend up
@ -2204,6 +2208,7 @@ class ShareAPIControllerTest extends TestCase {
'share_with_displayname' => 'recipientDN',
'mail_send' => 0,
'mimetype' => 'myMimeType',
'hide_download' => 0,
], $share, [
['owner', $owner],
['initiator', $initiator],
@ -2249,6 +2254,7 @@ class ShareAPIControllerTest extends TestCase {
'share_with_displayname' => 'recipient',
'mail_send' => 0,
'mimetype' => 'myMimeType',
'hide_download' => 0,
], $share, [], false
];
@ -2292,6 +2298,7 @@ class ShareAPIControllerTest extends TestCase {
'share_with_displayname' => 'recipientGroupDisplayName',
'mail_send' => 0,
'mimetype' => 'myMimeType',
'hide_download' => 0,
], $share, [], false
];
@ -2333,6 +2340,7 @@ class ShareAPIControllerTest extends TestCase {
'share_with_displayname' => 'recipientGroup2',
'mail_send' => 0,
'mimetype' => 'myMimeType',
'hide_download' => 0,
], $share, [], false
];
@ -2377,6 +2385,7 @@ class ShareAPIControllerTest extends TestCase {
'mail_send' => 0,
'url' => 'myLink',
'mimetype' => 'myMimeType',
'hide_download' => 0,
], $share, [], false
];
@ -2418,6 +2427,7 @@ class ShareAPIControllerTest extends TestCase {
'share_with_displayname' => 'foobar',
'mail_send' => 0,
'mimetype' => 'myFolderMimeType',
'hide_download' => 0,
], $share, [], false
];
@ -2462,6 +2472,7 @@ class ShareAPIControllerTest extends TestCase {
'share_with_avatar' => 'path/to/the/avatar',
'mail_send' => 0,
'mimetype' => 'myFolderMimeType',
'hide_download' => 0,
], $share, [], false
];
@ -2504,6 +2515,7 @@ class ShareAPIControllerTest extends TestCase {
'share_with_avatar' => '',
'mail_send' => 0,
'mimetype' => 'myFolderMimeType',
'hide_download' => 0,
], $share, [], false
];
@ -2546,6 +2558,7 @@ class ShareAPIControllerTest extends TestCase {
'share_with_avatar' => '',
'mail_send' => 0,
'mimetype' => 'myFolderMimeType',
'hide_download' => 0,
], $share, [], false
];
@ -2603,7 +2616,8 @@ class ShareAPIControllerTest extends TestCase {
'mail_send' => 0,
'mimetype' => 'myFolderMimeType',
'password' => 'password',
'send_password_by_talk' => false
'send_password_by_talk' => false,
'hide_download' => 0,
], $share, [], false
];
@ -2647,7 +2661,8 @@ class ShareAPIControllerTest extends TestCase {
'mail_send' => 0,
'mimetype' => 'myFolderMimeType',
'password' => 'password',
'send_password_by_talk' => true
'send_password_by_talk' => true,
'hide_download' => 0,
], $share, [], false
];
@ -2787,6 +2802,7 @@ class ShareAPIControllerTest extends TestCase {
'share_with_displayname' => '',
'mail_send' => 0,
'mimetype' => 'myMimeType',
'hide_download' => 0,
], $share, false, []
];
@ -2828,6 +2844,7 @@ class ShareAPIControllerTest extends TestCase {
'share_with_displayname' => 'recipientRoomName',
'mail_send' => 0,
'mimetype' => 'myMimeType',
'hide_download' => 0,
], $share, true, [
'share_with_displayname' => 'recipientRoomName'
]

View File

@ -287,7 +287,8 @@ class ShareControllerTest extends \Test\TestCase {
'shareUrl' => null,
'previewImage' => null,
'previewURL' => 'downloadURL',
'note' => $note
'note' => $note,
'hideDownload' => false
);
$csp = new \OCP\AppFramework\Http\ContentSecurityPolicy();
@ -306,6 +307,120 @@ class ShareControllerTest extends \Test\TestCase {
$this->assertEquals($expectedResponse, $response);
}
public function testShowShareHideDownload() {
$note = 'personal note';
$this->shareController->setToken('token');
$owner = $this->getMockBuilder(IUser::class)->getMock();
$owner->method('getDisplayName')->willReturn('ownerDisplay');
$owner->method('getUID')->willReturn('ownerUID');
$file = $this->getMockBuilder('OCP\Files\File')->getMock();
$file->method('getName')->willReturn('file1.txt');
$file->method('getMimetype')->willReturn('text/plain');
$file->method('getSize')->willReturn(33);
$file->method('isReadable')->willReturn(true);
$file->method('isShareable')->willReturn(true);
$share = \OC::$server->getShareManager()->newShare();
$share->setId(42);
$share->setPassword('password')
->setShareOwner('ownerUID')
->setNode($file)
->setNote($note)
->setTarget('/file1.txt')
->setHideDownload(true);
$this->session->method('exists')->with('public_link_authenticated')->willReturn(true);
$this->session->method('get')->with('public_link_authenticated')->willReturn('42');
// Even if downloads are disabled the "downloadURL" parameter is
// provided to the template, as it is needed to preview audio and GIF
// files.
$this->urlGenerator->expects($this->at(0))
->method('linkToRouteAbsolute')
->with('files_sharing.sharecontroller.downloadShare', ['token' => 'token'])
->willReturn('downloadURL');
$this->previewManager->method('isMimeSupported')->with('text/plain')->willReturn(true);
$this->config->method('getSystemValue')
->willReturnMap(
[
['max_filesize_animated_gifs_public_sharing', 10, 10],
['enable_previews', true, true],
['preview_max_x', 1024, 1024],
['preview_max_y', 1024, 1024],
]
);
$shareTmpl['maxSizeAnimateGif'] = $this->config->getSystemValue('max_filesize_animated_gifs_public_sharing', 10);
$shareTmpl['previewEnabled'] = $this->config->getSystemValue('enable_previews', true);
$this->shareManager
->expects($this->once())
->method('getShareByToken')
->with('token')
->willReturn($share);
$this->config
->expects($this->once())
->method('getAppValue')
->with('core', 'shareapi_public_link_disclaimertext', null)
->willReturn('My disclaimer text');
$this->userManager->method('get')->with('ownerUID')->willReturn($owner);
$this->eventDispatcher->expects($this->once())
->method('dispatch')
->with('OCA\Files_Sharing::loadAdditionalScripts');
$this->l10n->expects($this->any())
->method('t')
->will($this->returnCallback(function($text, $parameters) {
return vsprintf($text, $parameters);
}));
$response = $this->shareController->showShare();
$sharedTmplParams = array(
'displayName' => 'ownerDisplay',
'owner' => 'ownerUID',
'filename' => 'file1.txt',
'directory_path' => '/file1.txt',
'mimetype' => 'text/plain',
'dirToken' => 'token',
'sharingToken' => 'token',
'server2serversharing' => true,
'protected' => 'true',
'dir' => '',
'downloadURL' => 'downloadURL',
'fileSize' => '33 B',
'nonHumanFileSize' => 33,
'maxSizeAnimateGif' => 10,
'previewSupported' => true,
'previewEnabled' => true,
'previewMaxX' => 1024,
'previewMaxY' => 1024,
'hideFileList' => false,
'shareOwner' => 'ownerDisplay',
'disclaimer' => 'My disclaimer text',
'shareUrl' => null,
'previewImage' => null,
'previewURL' => 'downloadURL',
'note' => $note,
'hideDownload' => true
);
$csp = new \OCP\AppFramework\Http\ContentSecurityPolicy();
$csp->addAllowedFrameDomain('\'self\'');
$expectedResponse = new PublicTemplateResponse($this->appName, 'public', $sharedTmplParams);
$expectedResponse->setContentSecurityPolicy($csp);
$expectedResponse->setHeaderTitle($sharedTmplParams['filename']);
$expectedResponse->setHeaderDetails('shared by ' . $sharedTmplParams['displayName']);
$expectedResponse->setHeaderActions([]);
$this->assertEquals($expectedResponse, $response);
}
/**
* @expectedException \OCP\Files\NotFoundException
*/

View File

@ -41,4 +41,8 @@
<collection>OCA\Files_Versions\Sabre\RootCollection</collection>
</collections>
</sabre>
<versions>
<backend for="OCP\Files\Storage\IStorage">OCA\Files_Versions\Versions\LegacyVersionsBackend</backend>
</versions>
</info>

View File

@ -23,4 +23,11 @@ return array(
'OCA\\Files_Versions\\Sabre\\VersionHome' => $baseDir . '/../lib/Sabre/VersionHome.php',
'OCA\\Files_Versions\\Sabre\\VersionRoot' => $baseDir . '/../lib/Sabre/VersionRoot.php',
'OCA\\Files_Versions\\Storage' => $baseDir . '/../lib/Storage.php',
'OCA\\Files_Versions\\Versions\\BackendNotFoundException' => $baseDir . '/../lib/Versions/BackendNotFoundException.php',
'OCA\\Files_Versions\\Versions\\IVersion' => $baseDir . '/../lib/Versions/IVersion.php',
'OCA\\Files_Versions\\Versions\\IVersionBackend' => $baseDir . '/../lib/Versions/IVersionBackend.php',
'OCA\\Files_Versions\\Versions\\IVersionManager' => $baseDir . '/../lib/Versions/IVersionManager.php',
'OCA\\Files_Versions\\Versions\\LegacyVersionsBackend' => $baseDir . '/../lib/Versions/LegacyVersionsBackend.php',
'OCA\\Files_Versions\\Versions\\Version' => $baseDir . '/../lib/Versions/Version.php',
'OCA\\Files_Versions\\Versions\\VersionManager' => $baseDir . '/../lib/Versions/VersionManager.php',
);

View File

@ -38,6 +38,13 @@ class ComposerStaticInitFiles_Versions
'OCA\\Files_Versions\\Sabre\\VersionHome' => __DIR__ . '/..' . '/../lib/Sabre/VersionHome.php',
'OCA\\Files_Versions\\Sabre\\VersionRoot' => __DIR__ . '/..' . '/../lib/Sabre/VersionRoot.php',
'OCA\\Files_Versions\\Storage' => __DIR__ . '/..' . '/../lib/Storage.php',
'OCA\\Files_Versions\\Versions\\BackendNotFoundException' => __DIR__ . '/..' . '/../lib/Versions/BackendNotFoundException.php',
'OCA\\Files_Versions\\Versions\\IVersion' => __DIR__ . '/..' . '/../lib/Versions/IVersion.php',
'OCA\\Files_Versions\\Versions\\IVersionBackend' => __DIR__ . '/..' . '/../lib/Versions/IVersionBackend.php',
'OCA\\Files_Versions\\Versions\\IVersionManager' => __DIR__ . '/..' . '/../lib/Versions/IVersionManager.php',
'OCA\\Files_Versions\\Versions\\LegacyVersionsBackend' => __DIR__ . '/..' . '/../lib/Versions/LegacyVersionsBackend.php',
'OCA\\Files_Versions\\Versions\\Version' => __DIR__ . '/..' . '/../lib/Versions/Version.php',
'OCA\\Files_Versions\\Versions\\VersionManager' => __DIR__ . '/..' . '/../lib/Versions/VersionManager.php',
);
public static function getInitializer(ClassLoader $loader)

View File

@ -38,6 +38,10 @@
return t('files_versions', 'Versions');
},
getIcon: function() {
return 'icon-history';
},
nextPage: function() {
if (this._loading) {
return;

View File

@ -24,9 +24,10 @@
namespace OCA\Files_Versions\AppInfo;
use OCA\DAV\Connector\Sabre\Principal;
use OCA\Files_Versions\Versions\IVersionManager;
use OCA\Files_Versions\Versions\VersionManager;
use OCP\AppFramework\App;
use OCA\Files_Versions\Expiration;
use OCP\AppFramework\Utility\ITimeFactory;
use OCP\AppFramework\IAppContainer;
use OCA\Files_Versions\Capabilities;
class Application extends App {
@ -43,14 +44,45 @@ class Application extends App {
/*
* Register $principalBackend for the DAV collection
*/
$container->registerService('principalBackend', function () {
$container->registerService('principalBackend', function (IAppContainer $c) {
$server = $c->getServer();
return new Principal(
\OC::$server->getUserManager(),
\OC::$server->getGroupManager(),
\OC::$server->getShareManager(),
\OC::$server->getUserSession(),
\OC::$server->getConfig()
$server->getUserManager(),
$server->getGroupManager(),
$server->getShareManager(),
$server->getUserSession(),
$server->getConfig()
);
});
$container->registerService(IVersionManager::class, function(IAppContainer $c) {
return new VersionManager();
});
$this->registerVersionBackends();
}
public function registerVersionBackends() {
$server = $this->getContainer()->getServer();
$logger = $server->getLogger();
$appManager = $server->getAppManager();
/** @var IVersionManager $versionManager */
$versionManager = $this->getContainer()->getServer()->query(IVersionManager::class);
foreach($appManager->getInstalledApps() as $app) {
$appInfo = $appManager->getAppInfo($app);
if (isset($appInfo['versions'])) {
$backends = $appInfo['versions'];
foreach($backends as $backend) {
$class = $backend['@value'];
$for = $backend['@attributes']['for'];
try {
$backendObject = $server->query($class);
$versionManager->registerBackend($for, $backendObject);
} catch (\Exception $e) {
$logger->logException($e);
}
}
}
}
}
}

View File

@ -21,45 +21,53 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\Files_Versions\Controller;
use OCA\Files_Versions\Versions\IVersionManager;
use OCP\AppFramework\Controller;
use OCP\AppFramework\Http;
use OCP\AppFramework\Http\DataResponse;
use OCP\AppFramework\Http\FileDisplayResponse;
use OCP\Files\File;
use OCP\Files\Folder;
use OCP\Files\IMimeTypeDetector;
use OCP\Files\IRootFolder;
use OCP\Files\NotFoundException;
use OCP\IPreview;
use OCP\IRequest;
use OCP\IUserSession;
class PreviewController extends Controller {
/** @var IRootFolder */
private $rootFolder;
/** @var string */
private $userId;
/** @var IUserSession */
private $userSession;
/** @var IMimeTypeDetector */
private $mimeTypeDetector;
/** @var IVersionManager */
private $versionManager;
/** @var IPreview */
private $previewManager;
public function __construct($appName,
IRequest $request,
IRootFolder $rootFolder,
$userId,
IMimeTypeDetector $mimeTypeDetector,
IPreview $previewManager) {
public function __construct(
$appName,
IRequest $request,
IRootFolder $rootFolder,
IUserSession $userSession,
IMimeTypeDetector $mimeTypeDetector,
IVersionManager $versionManager,
IPreview $previewManager
) {
parent::__construct($appName, $request);
$this->rootFolder = $rootFolder;
$this->userId = $userId;
$this->userSession = $userSession;
$this->mimeTypeDetector = $mimeTypeDetector;
$this->versionManager = $versionManager;
$this->previewManager = $previewManager;
}
@ -79,20 +87,17 @@ class PreviewController extends Controller {
$y = 44,
$version = ''
) {
if($file === '' || $version === '' || $x === 0 || $y === 0) {
if ($file === '' || $version === '' || $x === 0 || $y === 0) {
return new DataResponse([], Http::STATUS_BAD_REQUEST);
}
try {
$userFolder = $this->rootFolder->getUserFolder($this->userId);
/** @var Folder $versionFolder */
$versionFolder = $userFolder->getParent()->get('files_versions');
$mimeType = $this->mimeTypeDetector->detectPath($file);
$file = $versionFolder->get($file.'.v'.$version);
/** @var File $file */
$f = $this->previewManager->getPreview($file, $x, $y, true, IPreview::MODE_FILL, $mimeType);
return new FileDisplayResponse($f, Http::STATUS_OK, ['Content-Type' => $f->getMimeType()]);
$user = $this->userSession->getUser();
$userFolder = $this->rootFolder->getUserFolder($user->getUID());
$file = $userFolder->get($file);
$versionFile = $this->versionManager->getVersionFile($user, $file, (int)$version);
$preview = $this->previewManager->getPreview($versionFile, $x, $y, true, IPreview::MODE_FILL, $versionFile->getMimetype());
return new FileDisplayResponse($preview, Http::STATUS_OK, ['Content-Type' => $preview->getMimeType()]);
} catch (NotFoundException $e) {
return new DataResponse([], Http::STATUS_NOT_FOUND);
} catch (\InvalidArgumentException $e) {

View File

@ -24,6 +24,7 @@ declare(strict_types=1);
namespace OCA\Files_Versions\Sabre;
use OCP\IUser;
use Sabre\DAV\Exception\Forbidden;
use Sabre\DAV\ICollection;
use Sabre\DAV\IMoveTarget;
@ -31,14 +32,6 @@ use Sabre\DAV\INode;
class RestoreFolder implements ICollection, IMoveTarget {
/** @var string */
protected $userId;
public function __construct(string $userId) {
$this->userId = $userId;
}
public function createFile($name, $data = null) {
throw new Forbidden();
}
@ -80,7 +73,8 @@ class RestoreFolder implements ICollection, IMoveTarget {
return false;
}
return $sourceNode->rollBack();
$sourceNode->rollBack();
return true;
}
}

View File

@ -20,10 +20,13 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\Files_Versions\Sabre;
use OCA\Files_Versions\Versions\IVersionManager;
use OCP\Files\IRootFolder;
use OCP\IConfig;
use OCP\IUserManager;
use Sabre\DAV\INode;
use Sabre\DAVACL\AbstractPrincipalCollection;
use Sabre\DAVACL\PrincipalBackend;
@ -33,12 +36,24 @@ class RootCollection extends AbstractPrincipalCollection {
/** @var IRootFolder */
private $rootFolder;
public function __construct(PrincipalBackend\BackendInterface $principalBackend,
IRootFolder $rootFolder,
IConfig $config) {
/** @var IUserManager */
private $userManager;
/** @var IVersionManager */
private $versionManager;
public function __construct(
PrincipalBackend\BackendInterface $principalBackend,
IRootFolder $rootFolder,
IConfig $config,
IUserManager $userManager,
IVersionManager $versionManager
) {
parent::__construct($principalBackend, 'principals/users');
$this->rootFolder = $rootFolder;
$this->userManager = $userManager;
$this->versionManager = $versionManager;
$this->disableListing = !$config->getSystemValue('debug', false);
}
@ -54,12 +69,12 @@ class RootCollection extends AbstractPrincipalCollection {
* @return INode
*/
public function getChildForPrincipal(array $principalInfo) {
list(,$name) = \Sabre\Uri\split($principalInfo['uri']);
list(, $name) = \Sabre\Uri\split($principalInfo['uri']);
$user = \OC::$server->getUserSession()->getUser();
if (is_null($user) || $name !== $user->getUID()) {
throw new \Sabre\DAV\Exception\Forbidden();
}
return new VersionHome($principalInfo, $this->rootFolder);
return new VersionHome($principalInfo, $this->rootFolder, $this->userManager, $this->versionManager);
}
public function getName() {

View File

@ -21,11 +21,15 @@ declare(strict_types=1);
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\Files_Versions\Sabre;
use OCA\Files_Versions\Storage;
use OCA\Files_Versions\Versions\IVersion;
use OCA\Files_Versions\Versions\IVersionManager;
use OCP\Files\File;
use OCP\Files\Folder;
use OCP\IUser;
use Sabre\DAV\Exception\Forbidden;
use Sabre\DAV\Exception\NotFound;
use Sabre\DAV\ICollection;
@ -37,13 +41,17 @@ class VersionCollection implements ICollection {
/** @var File */
private $file;
/** @var string */
private $userId;
/** @var IUser */
private $user;
public function __construct(Folder $userFolder, File $file, string $userId) {
/** @var IVersionManager */
private $versionManager;
public function __construct(Folder $userFolder, File $file, IUser $user, IVersionManager $versionManager) {
$this->userFolder = $userFolder;
$this->file = $file;
$this->userId = $userId;
$this->user = $user;
$this->versionManager = $versionManager;
}
public function createFile($name, $data = null) {
@ -68,10 +76,10 @@ class VersionCollection implements ICollection {
}
public function getChildren(): array {
$versions = Storage::getVersions($this->userId, $this->userFolder->getRelativePath($this->file->getPath()));
$versions = $this->versionManager->getVersionsForFile($this->user, $this->file);
return array_map(function (array $data) {
return new VersionFile($data, $this->userFolder->getParent());
return array_map(function (IVersion $version) {
return new VersionFile($version, $this->versionManager);
}, $versions);
}

View File

@ -21,26 +21,26 @@ declare(strict_types=1);
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\Files_Versions\Sabre;
use OCA\Files_Versions\Storage;
use OCP\Files\File;
use OCP\Files\Folder;
use OCA\Files_Versions\Versions\IVersion;
use OCA\Files_Versions\Versions\IVersionManager;
use OCP\Files\NotFoundException;
use Sabre\DAV\Exception\Forbidden;
use Sabre\DAV\Exception\NotFound;
use Sabre\DAV\IFile;
class VersionFile implements IFile {
/** @var array */
private $data;
/** @var IVersion */
private $version;
/** @var Folder */
private $userRoot;
/** @var IVersionManager */
private $versionManager;
public function __construct(array $data, Folder $userRoot) {
$this->data = $data;
$this->userRoot = $userRoot;
public function __construct(IVersion $version, IVersionManager $versionManager) {
$this->version = $version;
$this->versionManager = $versionManager;
}
public function put($data) {
@ -49,27 +49,22 @@ class VersionFile implements IFile {
public function get() {
try {
/** @var Folder $versions */
$versions = $this->userRoot->get('files_versions');
/** @var File $version */
$version = $versions->get($this->data['path'].'.v'.$this->data['version']);
return $this->versionManager->read($this->version);
} catch (NotFoundException $e) {
throw new NotFound();
}
return $version->fopen('rb');
}
public function getContentType(): string {
return $this->data['mimetype'];
return $this->version->getMimeType();
}
public function getETag(): string {
return $this->data['version'];
return (string)$this->version->getRevisionId();
}
public function getSize(): int {
return $this->data['size'];
return $this->version->getSize();
}
public function delete() {
@ -77,7 +72,7 @@ class VersionFile implements IFile {
}
public function getName(): string {
return $this->data['version'];
return (string)$this->version->getRevisionId();
}
public function setName($name) {
@ -85,10 +80,10 @@ class VersionFile implements IFile {
}
public function getLastModified(): int {
return (int)$this->data['version'];
return $this->version->getTimestamp();
}
public function rollBack(): bool {
return Storage::rollback($this->data['path'], $this->data['version']);
public function rollBack() {
$this->versionManager->rollback($this->version);
}
}

View File

@ -20,9 +20,13 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\Files_Versions\Sabre;
use OC\User\NoUserException;
use OCA\Files_Versions\Versions\IVersionManager;
use OCP\Files\IRootFolder;
use OCP\IUserManager;
use Sabre\DAV\Exception\Forbidden;
use Sabre\DAV\ICollection;
@ -34,9 +38,25 @@ class VersionHome implements ICollection {
/** @var IRootFolder */
private $rootFolder;
public function __construct(array $principalInfo, IRootFolder $rootFolder) {
/** @var IUserManager */
private $userManager;
/** @var IVersionManager */
private $versionManager;
public function __construct(array $principalInfo, IRootFolder $rootFolder, IUserManager $userManager, IVersionManager $versionManager) {
$this->principalInfo = $principalInfo;
$this->rootFolder = $rootFolder;
$this->userManager = $userManager;
$this->versionManager = $versionManager;
}
private function getUser() {
list(, $name) = \Sabre\Uri\split($this->principalInfo['uri']);
$user = $this->userManager->get($name);
if (!$user) {
throw new NoUserException();
}
}
public function delete() {
@ -44,8 +64,7 @@ class VersionHome implements ICollection {
}
public function getName(): string {
list(,$name) = \Sabre\Uri\split($this->principalInfo['uri']);
return $name;
return $this->getUser()->getUID();
}
public function setName($name) {
@ -61,22 +80,22 @@ class VersionHome implements ICollection {
}
public function getChild($name) {
list(,$userId) = \Sabre\Uri\split($this->principalInfo['uri']);
$user = $this->getUser();
if ($name === 'versions') {
return new VersionRoot($userId, $this->rootFolder);
return new VersionRoot($user, $this->rootFolder, $this->versionManager);
}
if ($name === 'restore') {
return new RestoreFolder($userId);
return new RestoreFolder();
}
}
public function getChildren() {
list(,$userId) = \Sabre\Uri\split($this->principalInfo['uri']);
$user = $this->getUser();
return [
new VersionRoot($userId, $this->rootFolder),
new RestoreFolder($userId),
new VersionRoot($user, $this->rootFolder, $this->versionManager),
new RestoreFolder(),
];
}

View File

@ -23,23 +23,29 @@ declare(strict_types=1);
*/
namespace OCA\Files_Versions\Sabre;
use OCA\Files_Versions\Versions\IVersionManager;
use OCP\Files\File;
use OCP\Files\IRootFolder;
use OCP\IUser;
use Sabre\DAV\Exception\Forbidden;
use Sabre\DAV\Exception\NotFound;
use Sabre\DAV\ICollection;
class VersionRoot implements ICollection {
/** @var string */
private $userId;
/** @var IUser */
private $user;
/** @var IRootFolder */
private $rootFolder;
public function __construct(string $userId, IRootFolder $rootFolder) {
$this->userId = $userId;
/** @var IVersionManager */
private $versionManager;
public function __construct(IUser $user, IRootFolder $rootFolder, IVersionManager $versionManager) {
$this->user = $user;
$this->rootFolder = $rootFolder;
$this->versionManager = $versionManager;
}
public function delete() {
@ -63,7 +69,7 @@ class VersionRoot implements ICollection {
}
public function getChild($name) {
$userFolder = $this->rootFolder->getUserFolder($this->userId);
$userFolder = $this->rootFolder->getUserFolder($this->user->getUID());
$fileId = (int)$name;
$nodes = $userFolder->getById($fileId);
@ -78,7 +84,7 @@ class VersionRoot implements ICollection {
throw new NotFound();
}
return new VersionCollection($userFolder, $node, $this->userId);
return new VersionCollection($userFolder, $node, $this->user, $this->versionManager);
}
public function getChildren(): array {

View File

@ -48,6 +48,7 @@ use OC\Files\View;
use OCA\Files_Versions\AppInfo\Application;
use OCA\Files_Versions\Command\Expire;
use OCA\Files_Versions\Events\CreateVersionEvent;
use OCA\Files_Versions\Versions\IVersionManager;
use OCP\Files\NotFoundException;
use OCP\Lock\ILockingProvider;
use OCP\User;
@ -178,10 +179,10 @@ class Storage {
list($uid, $filename) = self::getUidAndFilename($filename);
$files_view = new View('/'.$uid .'/files');
$users_view = new View('/'.$uid);
$eventDispatcher = \OC::$server->getEventDispatcher();
$id = $files_view->getFileInfo($filename)->getId();
$fileInfo = $files_view->getFileInfo($filename);
$id = $fileInfo->getId();
$nodes = \OC::$server->getRootFolder()->getById($id);
foreach ($nodes as $node) {
$event = new CreateVersionEvent($node);
@ -192,20 +193,16 @@ class Storage {
}
// no use making versions for empty files
if ($files_view->filesize($filename) === 0) {
if ($fileInfo->getSize() === 0) {
return false;
}
// create all parent folders
self::createMissingDirectories($filename, $users_view);
/** @var IVersionManager $versionManager */
$versionManager = \OC::$server->query(IVersionManager::class);
$userManager = \OC::$server->getUserManager();
$user = $userManager->get($uid);
self::scheduleExpire($uid, $filename);
// store a new version of a file
$mtime = $users_view->filemtime('files/' . $filename);
$users_view->copy('files/' . $filename, 'files_versions/' . $filename . '.v' . $mtime);
// call getFileInfo to enforce a file cache entry for the new version
$users_view->getFileInfo('files_versions/' . $filename . '.v' . $mtime);
$versionManager->createVersion($user, $fileInfo);
}
@ -695,7 +692,7 @@ class Storage {
* @param string $uid owner of the file
* @param string $fileName file/folder for which to schedule expiration
*/
private static function scheduleExpire($uid, $fileName) {
public static function scheduleExpire($uid, $fileName) {
// let the admin disable auto expire
$expiration = self::getExpiration();
if ($expiration->isEnabled()) {
@ -833,7 +830,7 @@ class Storage {
* "files" folder
* @param View $view view on data/user/
*/
private static function createMissingDirectories($filename, $view) {
public static function createMissingDirectories($filename, $view) {
$dirname = Filesystem::normalizePath(dirname($filename));
$dirParts = explode('/', $dirname);
$dir = "/files_versions";

View File

@ -0,0 +1,26 @@
<?php
/**
* @copyright Copyright (c) 2018 Robin Appelman <robin@icewind.nl>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\Files_Versions\Versions;
class BackendNotFoundException extends \Exception {
}

View File

@ -0,0 +1,99 @@
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2018 Robin Appelman <robin@icewind.nl>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\Files_Versions\Versions;
use OCP\Files\FileInfo;
use OCP\IUser;
/**
* @since 15.0.0
*/
interface IVersion {
/**
* @return IVersionBackend
* @since 15.0.0
*/
public function getBackend(): IVersionBackend;
/**
* Get the file info of the source file
*
* @return FileInfo
* @since 15.0.0
*/
public function getSourceFile(): FileInfo;
/**
* Get the id of the revision for the file
*
* @return int
* @since 15.0.0
*/
public function getRevisionId(): int;
/**
* Get the timestamp this version was created
*
* @return int
* @since 15.0.0
*/
public function getTimestamp(): int;
/**
* Get the size of this version
*
* @return int
* @since 15.0.0
*/
public function getSize(): int;
/**
* Get the name of the source file at the time of making this version
*
* @return string
* @since 15.0.0
*/
public function getSourceFileName(): string;
/**
* Get the mimetype of this version
*
* @return string
* @since 15.0.0
*/
public function getMimeType(): string;
/**
* Get the path of this version
*
* @return string
* @since 15.0.0
*/
public function getVersionPath(): string;
/**
* @return IUser
* @since 15.0.0
*/
public function getUser(): IUser;
}

View File

@ -0,0 +1,81 @@
<?php declare(strict_types=1);
/**
* @copyright Copyright (c) 2018 Robin Appelman <robin@icewind.nl>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\Files_Versions\Versions;
use OCP\Files\File;
use OCP\Files\FileInfo;
use OCP\Files\NotFoundException;
use OCP\Files\SimpleFS\ISimpleFile;
use OCP\IUser;
/**
* @since 15.0.0
*/
interface IVersionBackend {
/**
* Get all versions for a file
*
* @param IUser $user
* @param FileInfo $file
* @return IVersion[]
* @since 15.0.0
*/
public function getVersionsForFile(IUser $user, FileInfo $file): array;
/**
* Create a new version for a file
*
* @param IUser $user
* @param FileInfo $file
* @since 15.0.0
*/
public function createVersion(IUser $user, FileInfo $file);
/**
* Restore this version
*
* @param IVersion $version
* @since 15.0.0
*/
public function rollback(IVersion $version);
/**
* Open the file for reading
*
* @param IVersion $version
* @return resource
* @throws NotFoundException
* @since 15.0.0
*/
public function read(IVersion $version);
/**
* Get the preview for a specific version of a file
*
* @param IUser $user
* @param FileInfo $sourceFile
* @param int $revision
* @return ISimpleFile
* @since 15.0.0
*/
public function getVersionFile(IUser $user, FileInfo $sourceFile, int $revision): File;
}

View File

@ -0,0 +1,36 @@
<?php declare(strict_types=1);
/**
* @copyright Copyright (c) 2018 Robin Appelman <robin@icewind.nl>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\Files_Versions\Versions;
/**
* @since 15.0.0
*/
interface IVersionManager extends IVersionBackend {
/**
* Register a new backend
*
* @param string $storageType
* @param IVersionBackend $backend
* @since 15.0.0
*/
public function registerBackend(string $storageType, IVersionBackend $backend);
}

View File

@ -0,0 +1,105 @@
<?php declare(strict_types=1);
/**
* @copyright Copyright (c) 2018 Robin Appelman <robin@icewind.nl>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\Files_Versions\Versions;
use OC\Files\View;
use OCA\Files_Versions\Storage;
use OCP\Files\File;
use OCP\Files\FileInfo;
use OCP\Files\Folder;
use OCP\Files\IRootFolder;
use OCP\Files\NotFoundException;
use OCP\IUser;
class LegacyVersionsBackend implements IVersionBackend {
/** @var IRootFolder */
private $rootFolder;
public function __construct(IRootFolder $rootFolder) {
$this->rootFolder = $rootFolder;
}
public function getVersionsForFile(IUser $user, FileInfo $file): array {
$userFolder = $this->rootFolder->getUserFolder($user->getUID());
$versions = Storage::getVersions($user->getUID(), $userFolder->getRelativePath($file->getPath()));
return array_map(function (array $data) use ($file, $user) {
return new Version(
(int)$data['version'],
(int)$data['version'],
$data['name'],
(int)$data['size'],
$data['mimetype'],
$data['path'],
$file,
$this,
$user
);
}, $versions);
}
public function createVersion(IUser $user, FileInfo $file) {
$userFolder = $this->rootFolder->getUserFolder($user->getUID());
$relativePath = $userFolder->getRelativePath($file->getPath());
$userView = new View('/' . $user->getUID());
// create all parent folders
Storage::createMissingDirectories($relativePath, $userView);
Storage::scheduleExpire($user->getUID(), $relativePath);
// store a new version of a file
$userView->copy('files/' . $relativePath, 'files_versions/' . $relativePath . '.v' . $file->getMtime());
// ensure the file is scanned
$userView->getFileInfo('files_versions/' . $relativePath . '.v' . $file->getMtime());
}
public function rollback(IVersion $version) {
return Storage::rollback($version->getVersionPath(), $version->getRevisionId());
}
private function getVersionFolder(IUser $user): Folder {
$userRoot = $this->rootFolder->getUserFolder($user->getUID())
->getParent();
try {
/** @var Folder $folder */
$folder = $userRoot->get('files_versions');
return $folder;
} catch (NotFoundException $e) {
return $userRoot->newFolder('files_versions');
}
}
public function read(IVersion $version) {
$versions = $this->getVersionFolder($version->getUser());
/** @var File $file */
$file = $versions->get($version->getVersionPath() . '.v' . $version->getRevisionId());
return $file->fopen('r');
}
public function getVersionFile(IUser $user, FileInfo $sourceFile, int $revision): File {
$userFolder = $this->rootFolder->getUserFolder($user->getUID());
$versionFolder = $this->getVersionFolder($user);
/** @var File $file */
$file = $versionFolder->get($userFolder->getRelativePath($sourceFile->getPath()) . '.v' . $revision);
return $file;
}
}

View File

@ -0,0 +1,113 @@
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2018 Robin Appelman <robin@icewind.nl>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\Files_Versions\Versions;
use OCP\Files\FileInfo;
use OCP\IUser;
class Version implements IVersion {
/** @var int */
private $timestamp;
/** @var int */
private $revisionId;
/** @var string */
private $name;
/** @var int */
private $size;
/** @var string */
private $mimetype;
/** @var string */
private $path;
/** @var FileInfo */
private $sourceFileInfo;
/** @var IVersionBackend */
private $backend;
/** @var IUser */
private $user;
public function __construct(
int $timestamp,
int $revisionId,
string $name,
int $size,
string $mimetype,
string $path,
FileInfo $sourceFileInfo,
IVersionBackend $backend,
IUser $user
) {
$this->timestamp = $timestamp;
$this->revisionId = $revisionId;
$this->name = $name;
$this->size = $size;
$this->mimetype = $mimetype;
$this->path = $path;
$this->sourceFileInfo = $sourceFileInfo;
$this->backend = $backend;
$this->user = $user;
}
public function getBackend(): IVersionBackend {
return $this->backend;
}
public function getSourceFile(): FileInfo {
return $this->sourceFileInfo;
}
public function getRevisionId(): int {
return $this->revisionId;
}
public function getTimestamp(): int {
return $this->timestamp;
}
public function getSize(): int {
return $this->size;
}
public function getSourceFileName(): string {
return $this->name;
}
public function getMimeType(): string {
return $this->mimetype;
}
public function getVersionPath(): string {
return $this->path;
}
public function getUser(): IUser {
return $this->user;
}
}

View File

@ -0,0 +1,93 @@
<?php declare(strict_types=1);
/**
* @copyright Copyright (c) 2018 Robin Appelman <robin@icewind.nl>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\Files_Versions\Versions;
use OCP\Files\File;
use OCP\Files\FileInfo;
use OCP\Files\Storage\IStorage;
use OCP\IUser;
class VersionManager implements IVersionManager {
/** @var IVersionBackend[] */
private $backends = [];
public function registerBackend(string $storageType, IVersionBackend $backend) {
$this->backends[$storageType] = $backend;
}
/**
* @return IVersionBackend[]
*/
private function getBackends(): array {
return $this->backends;
}
/**
* @param IStorage $storage
* @return IVersionBackend
* @throws BackendNotFoundException
*/
public function getBackendForStorage(IStorage $storage): IVersionBackend {
$fullType = get_class($storage);
$backends = $this->getBackends();
$foundType = array_reduce(array_keys($backends), function ($type, $registeredType) use ($storage) {
if (
$storage->instanceOfStorage($registeredType) &&
($type === '' || is_subclass_of($registeredType, $type))
) {
return $registeredType;
} else {
return $type;
}
}, '');
if ($foundType === '') {
throw new BackendNotFoundException("Version backend for $fullType not found");
} else {
return $backends[$foundType];
}
}
public function getVersionsForFile(IUser $user, FileInfo $file): array {
$backend = $this->getBackendForStorage($file->getStorage());
return $backend->getVersionsForFile($user, $file);
}
public function createVersion(IUser $user, FileInfo $file) {
$backend = $this->getBackendForStorage($file->getStorage());
$backend->createVersion($user, $file);
}
public function rollback(IVersion $version) {
$backend = $version->getBackend();
return $backend->rollback($version);
}
public function read(IVersion $version) {
$backend = $version->getBackend();
return $backend->read($version);
}
public function getVersionFile(IUser $user, FileInfo $sourceFile, int $revision): File {
$backend = $this->getBackendForStorage($sourceFile->getStorage());
return $backend->getVersionFile($user, $sourceFile, $revision);
}
}

View File

@ -20,9 +20,12 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\Files_Versions\Tests\Controller;
use OC\User\User;
use OCA\Files_Versions\Controller\PreviewController;
use OCA\Files_Versions\Versions\IVersionManager;
use OCP\AppFramework\Http;
use OCP\AppFramework\Http\DataResponse;
use OCP\AppFramework\Http\FileDisplayResponse;
@ -34,6 +37,8 @@ use OCP\Files\NotFoundException;
use OCP\Files\SimpleFS\ISimpleFile;
use OCP\IPreview;
use OCP\IRequest;
use OCP\IUser;
use OCP\IUserSession;
use Test\TestCase;
class PreviewControllerTest extends TestCase {
@ -50,23 +55,39 @@ class PreviewControllerTest extends TestCase {
/** @var IPreview|\PHPUnit_Framework_MockObject_MockObject */
private $previewManager;
/** @var PreviewController */
/** @var PreviewController|\PHPUnit_Framework_MockObject_MockObject */
private $controller;
/** @var IUserSession|\PHPUnit_Framework_MockObject_MockObject */
private $userSession;
/** @var IVersionManager|\PHPUnit_Framework_MockObject_MockObject */
private $versionManager;
public function setUp() {
parent::setUp();
$this->rootFolder = $this->createMock(IRootFolder::class);
$this->userId = 'user';
$user = $this->createMock(IUser::class);
$user->expects($this->any())
->method('getUID')
->willReturn($this->userId);
$this->mimeTypeDetector = $this->createMock(IMimeTypeDetector::class);
$this->previewManager = $this->createMock(IPreview::class);
$this->userSession = $this->createMock(IUserSession::class);
$this->userSession->expects($this->any())
->method('getUser')
->willReturn($user);
$this->versionManager = $this->createMock(IVersionManager::class);
$this->controller = new PreviewController(
'files_versions',
$this->createMock(IRequest::class),
$this->rootFolder,
$this->userId,
$this->userSession,
$this->mimeTypeDetector,
$this->versionManager,
$this->previewManager
);
}
@ -102,24 +123,23 @@ class PreviewControllerTest extends TestCase {
public function testValidPreview() {
$userFolder = $this->createMock(Folder::class);
$userRoot = $this->createMock(Folder::class);
$versions = $this->createMock(Folder::class);
$this->rootFolder->method('getUserFolder')
->with($this->userId)
->willReturn($userFolder);
$userFolder->method('getParent')
->willReturn($userRoot);
$userRoot->method('get')
->with('files_versions')
->willReturn($versions);
$this->mimeTypeDetector->method('detectPath')
->with($this->equalTo('file'))
->willReturn('myMime');
$sourceFile = $this->createMock(File::class);
$userFolder->method('get')
->with('file')
->willReturn($sourceFile);
$file = $this->createMock(File::class);
$versions->method('get')
->with($this->equalTo('file.v42'))
$file->method('getMimetype')
->willReturn('myMime');
$this->versionManager->method('getVersionFile')
->willReturn($file);
$preview = $this->createMock(ISimpleFile::class);
@ -138,24 +158,23 @@ class PreviewControllerTest extends TestCase {
public function testVersionNotFound() {
$userFolder = $this->createMock(Folder::class);
$userRoot = $this->createMock(Folder::class);
$versions = $this->createMock(Folder::class);
$this->rootFolder->method('getUserFolder')
->with($this->userId)
->willReturn($userFolder);
$userFolder->method('getParent')
->willReturn($userRoot);
$userRoot->method('get')
->with('files_versions')
->willReturn($versions);
$sourceFile = $this->createMock(File::class);
$userFolder->method('get')
->with('file')
->willReturn($sourceFile);
$this->mimeTypeDetector->method('detectPath')
->with($this->equalTo('file'))
->willReturn('myMime');
$file = $this->createMock(File::class);
$versions->method('get')
->with($this->equalTo('file.v42'))
$this->versionManager->method('getVersionFile')
->willThrowException(new NotFoundException());
$res = $this->controller->getPreview('file', 10, 10, '42');

View File

@ -22,8 +22,9 @@
namespace OCA\OAuth2\Controller;
use OC\Authentication\Exceptions\InvalidTokenException;
use OC\Authentication\Token\ExpiredTokenException;
use OC\Authentication\Exceptions\ExpiredTokenException;
use OC\Authentication\Token\IProvider as TokenProvider;
use OC\Security\Bruteforce\Throttler;
use OCA\OAuth2\Db\AccessTokenMapper;
use OCA\OAuth2\Db\ClientMapper;
use OCA\OAuth2\Exceptions\AccessTokenNotFoundException;
@ -49,6 +50,8 @@ class OauthApiController extends Controller {
private $secureRandom;
/** @var ITimeFactory */
private $time;
/** @var Throttler */
private $throttler;
/**
* @param string $appName
@ -59,6 +62,7 @@ class OauthApiController extends Controller {
* @param TokenProvider $tokenProvider
* @param ISecureRandom $secureRandom
* @param ITimeFactory $time
* @param Throttler $throttler
*/
public function __construct($appName,
IRequest $request,
@ -67,7 +71,8 @@ class OauthApiController extends Controller {
ClientMapper $clientMapper,
TokenProvider $tokenProvider,
ISecureRandom $secureRandom,
ITimeFactory $time) {
ITimeFactory $time,
Throttler $throttler) {
parent::__construct($appName, $request);
$this->crypto = $crypto;
$this->accessTokenMapper = $accessTokenMapper;
@ -75,6 +80,7 @@ class OauthApiController extends Controller {
$this->tokenProvider = $tokenProvider;
$this->secureRandom = $secureRandom;
$this->time = $time;
$this->throttler = $throttler;
}
/**
@ -164,6 +170,8 @@ class OauthApiController extends Controller {
$accessToken->setEncryptedToken($this->crypto->encrypt($newToken, $newCode));
$this->accessTokenMapper->update($accessToken);
$this->throttler->resetDelay($this->request->getRemoteAddress(), 'login', ['user' => $appToken->getUID()]);
return new JSONResponse(
[
'access_token' => $newToken,

View File

@ -22,11 +22,10 @@
namespace OCA\OAuth2\Tests\Controller;
use OC\Authentication\Exceptions\InvalidTokenException;
use OC\Authentication\Exceptions\ExpiredTokenException;
use OC\Authentication\Token\DefaultToken;
use OC\Authentication\Token\DefaultTokenMapper;
use OC\Authentication\Token\ExpiredTokenException;
use OC\Authentication\Token\IProvider as TokenProvider;
use OC\Authentication\Token\IToken;
use OC\Security\Bruteforce\Throttler;
use OCA\OAuth2\Controller\OauthApiController;
use OCA\OAuth2\Db\AccessToken;
use OCA\OAuth2\Db\AccessTokenMapper;
@ -57,6 +56,8 @@ class OauthApiControllerTest extends TestCase {
private $secureRandom;
/** @var ITimeFactory|\PHPUnit_Framework_MockObject_MockObject */
private $time;
/** @var Throttler|\PHPUnit_Framework_MockObject_MockObject */
private $throttler;
/** @var OauthApiController */
private $oauthApiController;
@ -70,6 +71,7 @@ class OauthApiControllerTest extends TestCase {
$this->tokenProvider = $this->createMock(TokenProvider::class);
$this->secureRandom = $this->createMock(ISecureRandom::class);
$this->time = $this->createMock(ITimeFactory::class);
$this->throttler = $this->createMock(Throttler::class);
$this->oauthApiController = new OauthApiController(
'oauth2',
@ -79,7 +81,8 @@ class OauthApiControllerTest extends TestCase {
$this->clientMapper,
$this->tokenProvider,
$this->secureRandom,
$this->time
$this->time,
$this->throttler
);
}
@ -286,6 +289,17 @@ class OauthApiControllerTest extends TestCase {
'user_id' => 'userId',
]);
$this->request->method('getRemoteAddress')
->willReturn('1.2.3.4');
$this->throttler->expects($this->once())
->method('resetDelay')
->with(
'1.2.3.4',
'login',
['user' => 'userId']
);
$this->assertEquals($expected, $this->oauthApiController->getToken('refresh_token', null, 'validrefresh', 'clientId', 'clientSecret'));
}
@ -370,6 +384,17 @@ class OauthApiControllerTest extends TestCase {
$this->request->server['PHP_AUTH_USER'] = 'clientId';
$this->request->server['PHP_AUTH_PW'] = 'clientSecret';
$this->request->method('getRemoteAddress')
->willReturn('1.2.3.4');
$this->throttler->expects($this->once())
->method('resetDelay')
->with(
'1.2.3.4',
'login',
['user' => 'userId']
);
$this->assertEquals($expected, $this->oauthApiController->getToken('refresh_token', null, 'validrefresh', null, null));
}
@ -451,6 +476,17 @@ class OauthApiControllerTest extends TestCase {
'user_id' => 'userId',
]);
$this->request->method('getRemoteAddress')
->willReturn('1.2.3.4');
$this->throttler->expects($this->once())
->method('resetDelay')
->with(
'1.2.3.4',
'login',
['user' => 'userId']
);
$this->assertEquals($expected, $this->oauthApiController->getToken('refresh_token', null, 'validrefresh', 'clientId', 'clientSecret'));
}
}

View File

@ -0,0 +1,51 @@
<?php
/**
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\ShareByMail\Tests;
use OCA\ShareByMail\Capabilities;
use Test\TestCase;
class CapabilitiesTest extends TestCase {
/** @var Capabilities */
private $capabilities;
public function setUp() {
parent::setUp();
$this->capabilities = new Capabilities();
}
public function testGetCapabilities() {
$capabilities = [
'files_sharing' =>
[
'sharebymail' =>
[
'enabled' => true,
'upload_files_drop' => ['enabled' => true],
'password' => ['enabled' => true],
'expire_date' => ['enabled' => true]
]
]
];
$this->assertSame($capabilities, $this->capabilities->getCapabilities());
}
}

View File

@ -0,0 +1,72 @@
<?php
/**
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\SystemTags\Tests\Activity;
use OCA\SystemTags\Activity\Setting;
use OCP\IL10N;
use Test\TestCase;
class SettingTest extends TestCase {
/** @var IL10N|\PHPUnit_Framework_MockObject_MockObject */
private $l;
/** @var Setting */
private $setting;
public function setUp() {
parent::setUp();
$this->l = $this->createMock(IL10N::class);
$this->setting = new Setting($this->l);
}
public function testGetIdentifier() {
$this->assertSame('systemtags', $this->setting->getIdentifier());
}
public function testGetName() {
$this->l
->expects($this->once())
->method('t')
->with('<strong>System tags</strong> for a file have been modified')
->willReturn('<strong>System tags</strong> for a file have been modified');
$this->assertSame('<strong>System tags</strong> for a file have been modified', $this->setting->getName());
}
public function testGetPriority() {
$this->assertSame(50, $this->setting->getPriority());
}
public function testCanChangeStream() {
$this->assertSame(true, $this->setting->canChangeStream());
}
public function testIsDefaultEnabledStream() {
$this->assertSame(true, $this->setting->isDefaultEnabledStream());
}
public function testCanChangeMail() {
$this->assertSame(true, $this->setting->canChangeMail());
}
public function testIsDefaultEnabledMail() {
$this->assertSame(false, $this->setting->isDefaultEnabledMail());
}
}

View File

@ -56,7 +56,6 @@ return array(
'OCA\\User_LDAP\\Settings\\Section' => $baseDir . '/../lib/Settings/Section.php',
'OCA\\User_LDAP\\UserPluginManager' => $baseDir . '/../lib/UserPluginManager.php',
'OCA\\User_LDAP\\User\\DeletedUsersIndex' => $baseDir . '/../lib/User/DeletedUsersIndex.php',
'OCA\\User_LDAP\\User\\IUserTools' => $baseDir . '/../lib/User/IUserTools.php',
'OCA\\User_LDAP\\User\\Manager' => $baseDir . '/../lib/User/Manager.php',
'OCA\\User_LDAP\\User\\OfflineUser' => $baseDir . '/../lib/User/OfflineUser.php',
'OCA\\User_LDAP\\User\\User' => $baseDir . '/../lib/User/User.php',

View File

@ -71,7 +71,6 @@ class ComposerStaticInitUser_LDAP
'OCA\\User_LDAP\\Settings\\Section' => __DIR__ . '/..' . '/../lib/Settings/Section.php',
'OCA\\User_LDAP\\UserPluginManager' => __DIR__ . '/..' . '/../lib/UserPluginManager.php',
'OCA\\User_LDAP\\User\\DeletedUsersIndex' => __DIR__ . '/..' . '/../lib/User/DeletedUsersIndex.php',
'OCA\\User_LDAP\\User\\IUserTools' => __DIR__ . '/..' . '/../lib/User/IUserTools.php',
'OCA\\User_LDAP\\User\\Manager' => __DIR__ . '/..' . '/../lib/User/Manager.php',
'OCA\\User_LDAP\\User\\OfflineUser' => __DIR__ . '/..' . '/../lib/User/OfflineUser.php',
'OCA\\User_LDAP\\User\\User' => __DIR__ . '/..' . '/../lib/User/User.php',

View File

@ -46,7 +46,6 @@ namespace OCA\User_LDAP;
use OC\HintException;
use OC\Hooks\PublicEmitter;
use OCA\User_LDAP\Exceptions\ConstraintViolationException;
use OCA\User_LDAP\User\IUserTools;
use OCA\User_LDAP\User\Manager;
use OCA\User_LDAP\User\OfflineUser;
use OCA\User_LDAP\Mapping\AbstractMapping;
@ -59,7 +58,7 @@ use OCP\IUserManager;
* Class Access
* @package OCA\User_LDAP
*/
class Access extends LDAPUtility implements IUserTools {
class Access extends LDAPUtility {
const UUID_ATTRIBUTES = ['entryuuid', 'nsuniqueid', 'objectguid', 'guid', 'ipauniqueid'];
/** @var \OCA\User_LDAP\Connection */

View File

@ -1,42 +0,0 @@
<?php
/**
* @copyright Copyright (c) 2016, ownCloud, Inc.
*
* @author Arthur Schiwon <blizzz@arthur-schiwon.de>
* @author Joas Schilling <coding@schilljs.com>
* @author Morris Jobke <hey@morrisjobke.de>
*
* @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 <http://www.gnu.org/licenses/>
*
*/
namespace OCA\User_LDAP\User;
/**
* IUserTools
*
* defines methods that are required by User class for LDAP interaction
*/
interface IUserTools {
public function getConnection();
public function readAttribute($dn, $attr, $filter = 'objectClass=*');
public function stringResemblesDN($string);
public function dn2username($dn, $ldapname = null);
public function username2dn($name);
}

View File

@ -45,7 +45,7 @@ use OCP\Notification\IManager as INotificationManager;
* cache
*/
class Manager {
/** @var IUserTools */
/** @var Access */
protected $access;
/** @var IConfig */
@ -110,11 +110,11 @@ class Manager {
}
/**
* @brief binds manager to an instance of IUserTools (implemented by
* Access). It needs to be assigned first before the manager can be used.
* @param IUserTools
* Binds manager to an instance of Access.
* It needs to be assigned first before the manager can be used.
* @param Access
*/
public function setLdapAccess(IUserTools $access) {
public function setLdapAccess(Access $access) {
$this->access = $access;
}

View File

@ -30,6 +30,7 @@
namespace OCA\User_LDAP\User;
use OCA\User_LDAP\Access;
use OCA\User_LDAP\Connection;
use OCA\User_LDAP\FilesystemHelper;
use OCA\User_LDAP\LogWrapper;
@ -48,7 +49,7 @@ use OCP\Notification\IManager as INotificationManager;
*/
class User {
/**
* @var IUserTools
* @var Access
*/
protected $access;
/**
@ -110,8 +111,7 @@ class User {
* @brief constructor, make sure the subclasses call this one!
* @param string $username the internal username
* @param string $dn the LDAP DN
* @param IUserTools $access an instance that implements IUserTools for
* LDAP interaction
* @param Access $access
* @param IConfig $config
* @param FilesystemHelper $fs
* @param Image $image any empty instance
@ -120,7 +120,7 @@ class User {
* @param IUserManager $userManager
* @param INotificationManager $notificationManager
*/
public function __construct($username, $dn, IUserTools $access,
public function __construct($username, $dn, Access $access,
IConfig $config, FilesystemHelper $fs, Image $image,
LogWrapper $log, IAvatarManager $avatarManager, IUserManager $userManager,
INotificationManager $notificationManager) {
@ -414,14 +414,23 @@ class User {
*
* @param string $displayName
* @param string $displayName2
* @returns string the effective display name
* @return string the effective display name
*/
public function composeAndStoreDisplayName($displayName, $displayName2 = '') {
$displayName2 = (string)$displayName2;
if($displayName2 !== '') {
$displayName .= ' (' . $displayName2 . ')';
}
$this->store('displayName', $displayName);
$oldName = $this->config->getUserValue($this->uid, 'user_ldap', 'displayName', null);
if ($oldName !== $displayName) {
$this->store('displayName', $displayName);
$user = $this->userManager->get($this->getUsername());
if (!empty($oldName) && $user instanceof \OC\User\User) {
// if it was empty, it would be a new record, not a change emitting the trigger could
// potentially cause a UniqueConstraintViolationException, depending on some factors.
$user->triggerChange('displayName', $displayName);
}
}
return $displayName;
}

View File

@ -28,11 +28,13 @@
namespace OCA\User_LDAP\Tests\User;
use OCA\User_LDAP\Access;
use OCA\User_LDAP\Connection;
use OCA\User_LDAP\FilesystemHelper;
use OCA\User_LDAP\ILDAPWrapper;
use OCA\User_LDAP\LogWrapper;
use OCA\User_LDAP\User\IUserTools;
use OCA\User_LDAP\User\Manager;
use OCA\User_LDAP\User\User;
use OCP\IAvatarManager;
use OCP\IConfig;
use OCP\IDBConnection;
@ -48,200 +50,181 @@ use OCP\Notification\IManager as INotificationManager;
* @package OCA\User_LDAP\Tests\User
*/
class ManagerTest extends \Test\TestCase {
/** @var Access|\PHPUnit_Framework_MockObject_MockObject */
protected $access;
private function getTestInstances() {
$access = $this->createMock(IUserTools::class);
$config = $this->createMock(IConfig::class);
$filesys = $this->createMock(FilesystemHelper::class);
$log = $this->createMock(LogWrapper::class);
$avaMgr = $this->createMock(IAvatarManager::class);
$image = $this->createMock(Image::class);
$dbc = $this->createMock(IDBConnection::class);
$userMgr = $this->createMock(IUserManager::class);
$notiMgr = $this->createMock(INotificationManager::class);
/** @var IConfig|\PHPUnit_Framework_MockObject_MockObject */
protected $config;
$connection = new \OCA\User_LDAP\Connection(
$lw = $this->createMock(ILDAPWrapper::class),
'',
null
/** @var FilesystemHelper|\PHPUnit_Framework_MockObject_MockObject */
protected $fileSystemHelper;
/** @var LogWrapper|\PHPUnit_Framework_MockObject_MockObject */
protected $log;
/** @var IAvatarManager|\PHPUnit_Framework_MockObject_MockObject */
protected $avatarManager;
/** @var Image|\PHPUnit_Framework_MockObject_MockObject */
protected $image;
/** @var IDBConnection|\PHPUnit_Framework_MockObject_MockObject */
protected $dbc;
/** @var IUserManager|\PHPUnit_Framework_MockObject_MockObject */
protected $ncUserManager;
/** @var INotificationManager|\PHPUnit_Framework_MockObject_MockObject */
protected $notificationManager;
/** @var ILDAPWrapper|\PHPUnit_Framework_MockObject_MockObject */
protected $ldapWrapper;
/** @var Connection */
protected $connection;
/** @var Manager */
protected $manager;
public function setUp() {
parent::setUp();
$this->access = $this->createMock(Access::class);
$this->config = $this->createMock(IConfig::class);
$this->fileSystemHelper = $this->createMock(FilesystemHelper::class);
$this->log = $this->createMock(LogWrapper::class);
$this->avatarManager = $this->createMock(IAvatarManager::class);
$this->image = $this->createMock(Image::class);
$this->dbc = $this->createMock(IDBConnection::class);
$this->ncUserManager = $this->createMock(IUserManager::class);
$this->notificationManager = $this->createMock(INotificationManager::class);
$this->ldapWrapper = $this->createMock(ILDAPWrapper::class);
$this->connection = new Connection($this->ldapWrapper, '', null);
$this->access->expects($this->any())
->method('getConnection')
->will($this->returnValue($this->connection));
/** @noinspection PhpUnhandledExceptionInspection */
$this->manager = new Manager(
$this->config,
$this->fileSystemHelper,
$this->log,
$this->avatarManager,
$this->image,
$this->dbc,
$this->ncUserManager,
$this->notificationManager
);
$access->expects($this->any())
->method('getConnection')
->will($this->returnValue($connection));
return array($access, $config, $filesys, $image, $log, $avaMgr, $dbc, $userMgr, $notiMgr);
$this->manager->setLdapAccess($this->access);
}
public function testGetByDNExisting() {
list($access, $config, $filesys, $image, $log, $avaMgr, $dbc, $userMgr, $notiMgr) =
$this->getTestInstances();
public function dnProvider() {
return [
['cn=foo,dc=foobar,dc=bar'],
['uid=foo,o=foobar,c=bar'],
['ab=cde,f=ghei,mno=pq'],
];
}
$inputDN = 'cn=foo,dc=foobar,dc=bar';
/**
* @dataProvider dnProvider
*/
public function testGetByDNExisting(string $inputDN) {
$uid = '563418fc-423b-1033-8d1c-ad5f418ee02e';
$access->expects($this->once())
$this->access->expects($this->once())
->method('stringResemblesDN')
->with($this->equalTo($inputDN))
->will($this->returnValue(true));
$access->expects($this->once())
$this->access->expects($this->once())
->method('dn2username')
->with($this->equalTo($inputDN))
->will($this->returnValue($uid));
$access->expects($this->never())
$this->access->expects($this->never())
->method('username2dn');
$manager = new Manager($config, $filesys, $log, $avaMgr, $image, $dbc, $userMgr, $notiMgr);
$manager->setLdapAccess($access);
$user = $manager->get($inputDN);
/** @noinspection PhpUnhandledExceptionInspection */
$this->manager->get($inputDN);
// Now we fetch the user again. If this leads to a failing test,
// runtime caching the manager is broken.
$user = $manager->get($inputDN);
/** @noinspection PhpUnhandledExceptionInspection */
$user = $this->manager->get($inputDN);
$this->assertInstanceOf('\OCA\User_LDAP\User\User', $user);
}
public function testGetByEDirectoryDN() {
list($access, $config, $filesys, $image, $log, $avaMgr, $dbc, $userMgr, $notiMgr) =
$this->getTestInstances();
$inputDN = 'uid=foo,o=foobar,c=bar';
$uid = '563418fc-423b-1033-8d1c-ad5f418ee02e';
$access->expects($this->once())
->method('stringResemblesDN')
->with($this->equalTo($inputDN))
->will($this->returnValue(true));
$access->expects($this->once())
->method('dn2username')
->with($this->equalTo($inputDN))
->will($this->returnValue($uid));
$access->expects($this->never())
->method('username2dn');
$manager = new Manager($config, $filesys, $log, $avaMgr, $image, $dbc, $userMgr, $notiMgr);
$manager->setLdapAccess($access);
$user = $manager->get($inputDN);
$this->assertInstanceOf('\OCA\User_LDAP\User\User', $user);
}
public function testGetByExoticDN() {
list($access, $config, $filesys, $image, $log, $avaMgr, $dbc, $userMgr, $notiMgr) =
$this->getTestInstances();
$inputDN = 'ab=cde,f=ghei,mno=pq';
$uid = '563418fc-423b-1033-8d1c-ad5f418ee02e';
$access->expects($this->once())
->method('stringResemblesDN')
->with($this->equalTo($inputDN))
->will($this->returnValue(true));
$access->expects($this->once())
->method('dn2username')
->with($this->equalTo($inputDN))
->will($this->returnValue($uid));
$access->expects($this->never())
->method('username2dn');
$manager = new Manager($config, $filesys, $log, $avaMgr, $image, $dbc, $userMgr, $notiMgr);
$manager->setLdapAccess($access);
$user = $manager->get($inputDN);
$this->assertInstanceOf('\OCA\User_LDAP\User\User', $user);
$this->assertInstanceOf(User::class, $user);
}
public function testGetByDNNotExisting() {
list($access, $config, $filesys, $image, $log, $avaMgr, $dbc, $userMgr, $notiMgr) =
$this->getTestInstances();
$inputDN = 'cn=gone,dc=foobar,dc=bar';
$access->expects($this->once())
$this->access->expects($this->once())
->method('stringResemblesDN')
->with($this->equalTo($inputDN))
->will($this->returnValue(true));
$access->expects($this->once())
$this->access->expects($this->once())
->method('dn2username')
->with($this->equalTo($inputDN))
->will($this->returnValue(false));
$access->expects($this->once())
$this->access->expects($this->once())
->method('username2dn')
->with($this->equalTo($inputDN))
->will($this->returnValue(false));
$manager = new Manager($config, $filesys, $log, $avaMgr, $image, $dbc, $userMgr, $notiMgr);
$manager->setLdapAccess($access);
$user = $manager->get($inputDN);
/** @noinspection PhpUnhandledExceptionInspection */
$user = $this->manager->get($inputDN);
$this->assertNull($user);
}
public function testGetByUidExisting() {
list($access, $config, $filesys, $image, $log, $avaMgr, $dbc, $userMgr, $notiMgr) =
$this->getTestInstances();
$dn = 'cn=foo,dc=foobar,dc=bar';
$uid = '563418fc-423b-1033-8d1c-ad5f418ee02e';
$access->expects($this->never())
$this->access->expects($this->never())
->method('dn2username');
$access->expects($this->once())
$this->access->expects($this->once())
->method('username2dn')
->with($this->equalTo($uid))
->will($this->returnValue($dn));
$access->expects($this->once())
$this->access->expects($this->once())
->method('stringResemblesDN')
->with($this->equalTo($uid))
->will($this->returnValue(false));
$manager = new Manager($config, $filesys, $log, $avaMgr, $image, $dbc, $userMgr, $notiMgr);
$manager->setLdapAccess($access);
$user = $manager->get($uid);
/** @noinspection PhpUnhandledExceptionInspection */
$this->manager->get($uid);
// Now we fetch the user again. If this leads to a failing test,
// runtime caching the manager is broken.
$user = $manager->get($uid);
/** @noinspection PhpUnhandledExceptionInspection */
$user = $this->manager->get($uid);
$this->assertInstanceOf('\OCA\User_LDAP\User\User', $user);
$this->assertInstanceOf(User::class, $user);
}
public function testGetByUidNotExisting() {
list($access, $config, $filesys, $image, $log, $avaMgr, $dbc, $userMgr, $notiMgr) =
$this->getTestInstances();
$uid = 'gone';
$access->expects($this->never())
$this->access->expects($this->never())
->method('dn2username');
$access->expects($this->exactly(1))
$this->access->expects($this->exactly(1))
->method('username2dn')
->with($this->equalTo($uid))
->will($this->returnValue(false));
$manager = new Manager($config, $filesys, $log, $avaMgr, $image, $dbc, $userMgr, $notiMgr);
$manager->setLdapAccess($access);
$user = $manager->get($uid);
/** @noinspection PhpUnhandledExceptionInspection */
$user = $this->manager->get($uid);
$this->assertNull($user);
}
public function attributeRequestProvider() {
return [
[ false ],
[ true ],
[false],
[true],
];
}
@ -249,23 +232,16 @@ class ManagerTest extends \Test\TestCase {
* @dataProvider attributeRequestProvider
*/
public function testGetAttributes($minimal) {
list($access, $config, $filesys, $image, $log, $avaMgr, $dbc, $userMgr, $notiMgr) =
$this->getTestInstances();
$manager = new Manager($config, $filesys, $log, $avaMgr, $image, $dbc, $userMgr, $notiMgr);
$manager->setLdapAccess($access);
$connection = $access->getConnection();
$connection->setConfiguration([
$this->connection->setConfiguration([
'ldapEmailAttribute' => 'mail',
'ldapUserAvatarRule' => 'default',
'ldapQuotaAttribute' => '',
]);
$attributes = $manager->getAttributes($minimal);
$attributes = $this->manager->getAttributes($minimal);
$this->assertTrue(in_array('dn', $attributes));
$this->assertTrue(in_array($access->getConnection()->ldapEmailAttribute, $attributes));
$this->assertTrue(in_array($this->access->getConnection()->ldapEmailAttribute, $attributes));
$this->assertFalse(in_array('', $attributes));
$this->assertSame(!$minimal, in_array('jpegphoto', $attributes));
$this->assertSame(!$minimal, in_array('thumbnailphoto', $attributes));

View File

@ -998,23 +998,58 @@ class UserTest extends \Test\TestCase {
public function displayNameProvider() {
return [
['Roland Deschain', '', 'Roland Deschain'],
['Roland Deschain', null, 'Roland Deschain'],
['Roland Deschain', 'gunslinger@darktower.com', 'Roland Deschain (gunslinger@darktower.com)'],
['Roland Deschain', '', 'Roland Deschain', false],
['Roland Deschain', '', 'Roland Deschain', true],
['Roland Deschain', null, 'Roland Deschain', false],
['Roland Deschain', 'gunslinger@darktower.com', 'Roland Deschain (gunslinger@darktower.com)', false],
['Roland Deschain', 'gunslinger@darktower.com', 'Roland Deschain (gunslinger@darktower.com)', true],
];
}
/**
* @dataProvider displayNameProvider
*/
public function testComposeAndStoreDisplayName($part1, $part2, $expected) {
public function testComposeAndStoreDisplayName($part1, $part2, $expected, $expectTriggerChange) {
$this->config->expects($this->once())
->method('setUserValue');
$oldName = $expectTriggerChange ? 'xxGunslingerxx' : null;
$this->config->expects($this->once())
->method('getUserValue')
->with($this->user->getUsername(), 'user_ldap', 'displayName', null)
->willReturn($oldName);
$ncUserObj = $this->createMock(\OC\User\User::class);
if ($expectTriggerChange) {
$ncUserObj->expects($this->once())
->method('triggerChange')
->with('displayName', $expected);
} else {
$ncUserObj->expects($this->never())
->method('triggerChange');
}
$this->userManager->expects($this->once())
->method('get')
->willReturn($ncUserObj);
$displayName = $this->user->composeAndStoreDisplayName($part1, $part2);
$this->assertSame($expected, $displayName);
}
public function testComposeAndStoreDisplayNameNoOverwrite() {
$displayName = 'Randall Flagg';
$this->config->expects($this->never())
->method('setUserValue');
$this->config->expects($this->once())
->method('getUserValue')
->willReturn($displayName);
$this->userManager->expects($this->never())
->method('get'); // Implicit: no triggerChange can be called
$composedDisplayName = $this->user->composeAndStoreDisplayName($displayName);
$this->assertSame($composedDisplayName, $displayName);
}
public function testHandlePasswordExpiryWarningDefaultPolicy() {
$this->connection->expects($this->any())
->method('__get')

View File

@ -32,7 +32,7 @@ handlebars -n OCA.WorkflowEngine.Templates apps/workflowengine/js/templates -f a
handlebars -n OCA.Sharing.Templates apps/files_sharing/js/templates -f apps/files_sharing/js/templates.js
# Files external
handlebars -n OCA.External.Templates apps/files_external/js/templates -f apps/files_external/js/templates.js
handlebars -n OCA.Files_External.Templates apps/files_external/js/templates -f apps/files_external/js/templates.js
if [[ $(git diff --name-only) ]]; then
echo "Please submit your compiled handlebars templates"

View File

@ -1499,11 +1499,26 @@ $CONFIG = array(
/**
* List of trusted proxy servers
*
* If you configure these also consider setting `forwarded_for_headers` which
* otherwise defaults to `HTTP_X_FORWARDED_FOR` (the `X-Forwarded-For` header).
* You may set this to an array containing a combination of
* - IPv4 addresses, e.g. `192.168.2.123`
* - IPv4 ranges in CIDR notation, e.g. `192.168.2.0/24`
* - IPv6 addresses, e.g. `fd9e:21a7:a92c:2323::1`
*
* _(CIDR notation for IPv6 is currently work in progress and thus not
* available as of yet)_
*
* When an incoming request's `REMOTE_ADDR` matches any of the IP addresses
* specified here, it is assumed to be a proxy instead of a client. Thus, the
* client IP will be read from the HTTP header specified in
* `forwarded_for_headers` instead of from `REMOTE_ADDR`.
*
* So if you configure `trusted_proxies`, also consider setting
* `forwarded_for_headers` which otherwise defaults to `HTTP_X_FORWARDED_FOR`
* (the `X-Forwarded-For` header).
*
* Defaults to an empty array.
*/
'trusted_proxies' => array('203.0.113.45', '198.51.100.128'),
'trusted_proxies' => array('203.0.113.45', '198.51.100.128', '192.168.2.0/24'),
/**
* Headers that should be trusted as client IP address in combination with
@ -1648,4 +1663,14 @@ $CONFIG = array(
* If this is set to "false" it will not show the link.
*/
'simpleSignUpLink.shown' => true,
/**
* By default autocompletion is enabled for the login form on Nextcloud's login page.
* While this is enabled, browsers are allowed to "remember" login names and such.
* Some companies require it to be disabled to comply with their security policy.
*
* Simply set this property to "false", if you want to turn this feature off.
*/
'login_form_autocomplete' => true,
);

View File

@ -171,6 +171,14 @@ class LoginController extends Controller {
$parameters['loginName'] = '';
$parameters['user_autofocus'] = true;
}
$autocomplete = $this->config->getSystemValue('login_form_autocomplete', true);
if ($autocomplete){
$parameters['login_form_autocomplete'] = 'on';
} else {
$parameters['login_form_autocomplete'] = 'off';
}
if (!empty($redirect_url)) {
$parameters['redirect_url'] = $redirect_url;
}

View File

@ -0,0 +1,54 @@
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2018 Roeland Jago Douma <roeland@famdouma.nl>
*
* @author Roeland Jago Douma <roeland@famdouma.nl>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OC\Core\Migrations;
use Closure;
use Doctrine\DBAL\Types\Type;
use OCP\DB\ISchemaWrapper;
use OCP\Migration\SimpleMigrationStep;
use OCP\Migration\IOutput;
class Version15000Date20181015062942 extends SimpleMigrationStep {
/**
* @param IOutput $output
* @param Closure $schemaClosure The `\Closure` returns a `ISchemaWrapper`
* @param array $options
* @return null|ISchemaWrapper
*/
public function changeSchema(IOutput $output, Closure $schemaClosure, array $options) {
/** @var ISchemaWrapper $schema */
$schema = $schemaClosure();
$table = $schema->getTable('share');
$table->addColumn('hide_download', 'smallint', [
'notnull' => true,
'length' => 1,
'default' => 0,
]);
return $schema;
}
}

View File

@ -781,27 +781,47 @@ kbd {
/* TABS ------------------------------------------------------------ */
.tabHeaders {
display: inline-block;
margin: 15px;
display: flex;
margin-bottom: 16px;
.tabHeader {
float: left;
padding: 12px;
display: flex;
flex-direction: column;
flex-grow: 1;
text-align: center;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
cursor: pointer;
color: var(--color-text-lighter);
margin-bottom: 1px;
padding: 5px;
/* Use same amount as sidebar padding */
&:first-child {
padding-left: 15px;
}
&:last-child {
padding-right: 15px;
}
.icon {
display: inline-block;
width: 16px;
width: 100%;
height: 16px;
background-size: 16px;
vertical-align: middle;
margin-top: -2px;
margin-right: 3px;
opacity: .7;
cursor: pointer;
}
a {
color: var(--color-text-lighter);
margin-bottom: 1px;
overflow: hidden;
text-overflow: ellipsis;
}
&.selected {
font-weight: bold;

View File

@ -39,4 +39,6 @@
--border-radius-pill: $border-radius-pill;
--font-face: $font-face;
--animation-quick: $animation-quick;
}

View File

@ -25,12 +25,12 @@
}
.hidden {
display: none;
display: none !important; /* Hiding should take precedence */
}
.hidden-visually {
position: absolute;
left:-10000px;
left: -10000px;
top: auto;
width: 1px;
height: 1px;
@ -47,4 +47,4 @@
.inlineblock {
display: inline-block;
}
}

View File

@ -513,25 +513,90 @@ nav[role='navigation'] {
height: 20px;
}
/* app title popup */
/* App title */
li span {
display: none;
opacity: 0;
position: absolute;
overflow: visible;
background-color: var(--color-main-background);
white-space: nowrap;
border: none;
border-radius: var(--border-radius);
border-top-left-radius: 0;
border-top-right-radius: 0;
color: var(--color-text-lighter);
width: auto;
left: 50%;
top: 100%;
transform: translateX(-50%);
padding: 4px 10px;
filter: drop-shadow(0 1px 10px var(--color-box-shadow));
z-index: 100;
color: var(--color-primary-text);
bottom: -5px;
width: 100%;
text-align: center;
overflow: hidden;
text-overflow: ellipsis;
}
/* Set up transitions for showing app titles on hover */
li {
/* Prevent flicker effect because of low-hanging span element */
overflow-y: hidden;
/* App icon */
svg,
.icon-more-white {
transition: transform var(--animation-quick) ease;
}
/* App title */
span {
transition: all var(--animation-quick) ease;
}
/* Triangle */
a::before {
transition: border var(--animation-quick) ease;
}
}
/* Show all app titles on hovering app menu area */
&:hover {
li {
/* Move up app icon */
svg,
.icon-more-white {
transform: translateY(-7px);
}
/* Show app title */
span {
opacity: .6;
bottom: 2px;
z-index: -1; /* fix clickability issue - otherwise we need to move the span into the link */
}
/* Prominent app title for current and hovered/focused app */
&:hover span,
&:focus span,
.active + span {
opacity: 1;
}
/* Smaller triangle because of limited space */
a::before {
border-width: 5px;
}
}
}
/* Also show app title on focusing single entry (showing all on focus is only possible with CSS4 and parent selectors) */
li a:focus {
/* Move up app icon */
svg,
.icon-more-white, {
transform: translateY(-7px);
}
/* Show app title */
& + span,
span {
opacity: 1;
bottom: 2px;
}
/* Smaller triangle because of limited space */
&::before {
border-width: 5px;
}
}
/* show triangle below active app */
@ -549,6 +614,7 @@ nav[role='navigation'] {
bottom: 0;
display: none;
}
/* triangle focus feedback */
li a.active::before,
li:hover a::before,
@ -560,7 +626,6 @@ nav[role='navigation'] {
z-index: 99;
}
li:hover a::before,
li:hover a::before,
li a.active:hover::before,
li a:focus::before {
z-index: 101;

View File

@ -466,3 +466,7 @@ img, object, video, button, textarea, input, select, div[contenteditable='true']
@include icon-color('search', 'actions', $color-black, 1, true);
}
.icon-talk {
@include icon-color('app-dark', 'spreed', $color-black, 1);
}

View File

@ -163,6 +163,7 @@ input[type='reset'] {
padding: 6px 12px;
width: auto;
min-height: 34px;
display: inline-block;
cursor: pointer;
box-sizing: border-box;
background-color: var(--color-background-dark);

View File

@ -70,7 +70,8 @@
.ui-widget-header .ui-state-highlight {
border: 1px solid var(--color-main-background);
background: var(--color-main-background) none;
color: var(--color-text-lighter);
color: var(--color-text-light);
font-weight: 600;
}
.ui-state-highlight a,
.ui-widget-content .ui-state-highlight a,
@ -171,9 +172,12 @@
&.ui-menu {
padding: 0;
.ui-menu-item a {
color: var(--color-text-lighter);
padding: 4px 4px 4px 14px;
&.ui-state-focus, &.ui-state-active {
font-weight: inherit;
box-shadow: inset 4px 0 var(--color-primary);
color: var(--color-text);
}
}
}

View File

@ -798,7 +798,7 @@ code {
&.view-grid {
$grid-size: 120px;
$grid-pad: 10px;
$name-height: 20px;
$name-height: 30px;
display: flex;
flex-direction: column;
@ -818,20 +818,22 @@ code {
flex-direction: column;
width: $grid-size - 2 * $grid-pad;
td {
border: none;
padding: 0;
text-align: center;
border-radius: var(--border-radius);
&.filename {
padding: #{$grid-size - 2 * $grid-pad} 0 0 0;
background-position: center top;
background-size: contain;
line-height: $name-height;
height: $name-height;
}
&.filesize {
line-height: $name-height;
text-align: left;
line-height: $name-height / 3;
width: 100%;
}
&.date {
display: none;

View File

@ -80,6 +80,7 @@ $border-radius-pill: 100px !default;
$font-face: 'Nunito', 'Open Sans', Frutiger, Calibri, 'Myriad Pro', Myriad, sans-serif !default;
$animation-quick: 100ms;
// various structure data
$header-height: 50px;

View File

@ -11,6 +11,16 @@
<input id="linkText-{{cid}}" class="linkText" type="text" readonly="readonly" value="{{shareLinkURL}}" />
</span>
</li>
{{#if showHideDownloadCheckbox}}
<li>
<span class="shareOption menuitem">
<span class="icon-loading-small hidden"></span>
<input type="checkbox" name="hideDownload" id="sharingDialogHideDownload-{{cid}}" class="checkbox hideDownloadCheckbox"
{{#if hideDownload}}checked="checked"{{/if}} />
<label for="sharingDialogHideDownload-{{cid}}">{{hideDownloadLabel}}</label>
</span>
</li>
{{/if}}
{{#if publicUpload}}
<li>
<span class="shareOption menuitem">

View File

@ -47,6 +47,8 @@
'change .linkCheckbox': 'onLinkCheckBoxChange',
// open menu
'click .share-menu .icon-more': 'onToggleMenu',
// hide download
'change .hideDownloadCheckbox': 'onHideDownloadChange',
// password
'focusout input.linkPassText': 'onPasswordEntered',
'keyup input.linkPassText': 'onPasswordKeyUp',
@ -179,6 +181,20 @@
$el.select();
},
onHideDownloadChange: function() {
var $checkbox = this.$('.hideDownloadCheckbox');
$checkbox.siblings('.icon-loading-small').removeClass('hidden').addClass('inlineblock');
var hideDownload = false;
if($checkbox.is(':checked')) {
hideDownload = true;
}
this.model.saveLinkShare({
hideDownload: hideDownload
});
},
onShowPasswordClick: function() {
this.$el.find('.linkPass').slideToggle(OC.menuSpeed);
this.$el.find('.linkPassMenu').toggleClass('hidden');
@ -401,6 +417,9 @@
var passwordPlaceholderInitial = this.configModel.get('enforcePasswordForPublicLink')
? PASSWORD_PLACEHOLDER_MESSAGE : PASSWORD_PLACEHOLDER_MESSAGE_OPTIONAL;
var showHideDownloadCheckbox = !this.model.isFolder();
var hideDownload = this.model.get('linkShare').hideDownload;
var publicEditable =
!this.model.isFolder()
&& isLinkShare
@ -464,6 +483,9 @@
shareLinkURL: this.model.get('linkShare').link,
urlLabel: t('core', 'Link'),
showHideDownloadCheckbox: showHideDownloadCheckbox,
hideDownload: hideDownload,
hideDownloadLabel: t('core', 'Hide download'),
enablePasswordLabel: t('core', 'Password protect'),
passwordLabel: t('core', 'Password'),
passwordPlaceholder: isPasswordSet ? PASSWORD_PLACEHOLDER : PASSWORD_PLACEHOLDER_MESSAGE,

View File

@ -314,7 +314,9 @@
var $edit = _this.$('#canEdit-' + _this.cid + '-' + sharee.shareId);
if($edit.length === 1) {
$edit.prop('checked', sharee.editPermissionState === 'checked');
$edit.prop('indeterminate', sharee.editPermissionState === 'indeterminate');
if (sharee.isFolder) {
$edit.prop('indeterminate', sharee.editPermissionState === 'indeterminate');
}
}
});
this.$('.popovermenu').on('afterHide', function() {

View File

@ -312,6 +312,41 @@
var suggestions = exactMatches.concat(users).concat(groups).concat(remotes).concat(remoteGroups).concat(emails).concat(circles).concat(rooms).concat(lookup);
function dynamicSort(property) {
return function (a,b) {
var aProperty = '';
var bProperty = '';
if (typeof a[property] !== 'undefined') {
aProperty = a[property];
}
if (typeof b[property] !== 'undefined') {
bProperty = b[property];
}
return (aProperty < bProperty) ? -1 : (aProperty > bProperty) ? 1 : 0;
}
}
/**
* Sort share entries by uuid to properly group them
*/
var grouped = suggestions.sort(dynamicSort('uuid'));
var previousUuid = null;
var groupedLength = grouped.length;
var result = [];
/**
* build the result array that only contains all contact entries from
* merged contacts, if the search term matches its contact name
*/
for (i = 0; i < groupedLength; i++) {
if (typeof grouped[i].uuid !== 'undefined' && grouped[i].uuid === previousUuid) {
grouped[i].merged = true;
}
if (searchTerm === grouped[i].name || typeof grouped[i].merged === 'undefined') {
result.push(grouped[i]);
}
previousUuid = grouped[i].uuid;
}
var moreResultsAvailable =
(
oc_config['sharing.maxAutocompleteResults'] > 0
@ -328,7 +363,7 @@
)
);
deferred.resolve(suggestions, exactMatches, moreResultsAvailable);
deferred.resolve(result, exactMatches, moreResultsAvailable);
} else {
deferred.reject(result.ocs.meta.message);
}
@ -441,33 +476,72 @@
},
autocompleteRenderItem: function(ul, item) {
var icon = 'icon-user';
var text = item.label;
if (typeof item.name !== 'undefined') {
text = item.name;
}
if (item.value.shareType === OC.Share.SHARE_TYPE_GROUP) {
text = t('core', '{sharee} (group)', { sharee: text }, undefined, { escape: false });
icon = 'icon-contacts-dark';
} else if (item.value.shareType === OC.Share.SHARE_TYPE_REMOTE) {
text = t('core', '{sharee} (remote)', {sharee: text}, undefined, {escape: false});
icon = 'icon-shared';
} else if (item.value.shareType === OC.Share.SHARE_TYPE_REMOTE_GROUP) {
text = t('core', '{sharee} (remote group)', { sharee: text }, undefined, { escape: false });
icon = 'icon-shared';
} else if (item.value.shareType === OC.Share.SHARE_TYPE_EMAIL) {
text = t('core', '{sharee} (email)', { sharee: text }, undefined, { escape: false });
icon = 'icon-mail';
} else if (item.value.shareType === OC.Share.SHARE_TYPE_CIRCLE) {
text = t('core', '{sharee} ({type}, {owner})', {sharee: text, type: item.value.circleInfo, owner: item.value.circleOwner}, undefined, {escape: false});
icon = 'icon-circle';
} else if (item.value.shareType === OC.Share.SHARE_TYPE_ROOM) {
text = t('core', '{sharee} (conversation)', { sharee: text }, undefined, { escape: false });
icon = 'icon-talk';
}
var description = '';
var getTranslatedType = function(type) {
switch (type) {
case 'HOME':
return t('core', 'Home');
case 'WORK':
return t('core', 'Home');
case 'OTHER':
return t('core', 'Other');
default:
return type;
}
};
if (typeof item.type !== 'undefined' && item.type !== null) {
description = getTranslatedType(item.type);
}
var insert = $("<div class='share-autocomplete-item'/>");
var avatar = $("<div class='avatardiv'></div>").appendTo(insert);
if (item.value.shareType === OC.Share.SHARE_TYPE_USER || item.value.shareType === OC.Share.SHARE_TYPE_CIRCLE) {
avatar.avatar(item.value.shareWith, 32, undefined, undefined, undefined, item.label);
if (item.merged) {
insert.addClass('merged');
text = item.value.shareWith;
} else {
avatar.imageplaceholder(text, undefined, 32);
var avatar = $("<div class='avatardiv'></div>").appendTo(insert);
if (item.value.shareType === OC.Share.SHARE_TYPE_USER || item.value.shareType === OC.Share.SHARE_TYPE_CIRCLE) {
avatar.avatar(item.value.shareWith, 32, undefined, undefined, undefined, item.label);
} else {
if (typeof item.uuid === 'undefined') {
item.uuid = text;
}
avatar.imageplaceholder(item.uuid, text, 32);
}
description = item.value.shareWith;
}
if (description !== '') {
insert.addClass('with-description');
}
$("<div class='autocomplete-item-text'></div>")
.text(text)
.html(
text.replace(
new RegExp(this.term, "gi"),
"<span class='ui-state-highlight'>$&</span>")
+ '<span class="autocomplete-item-details">' + description + '</span>'
)
.appendTo(insert);
insert.attr('title', item.value.shareWith);
insert.append('<span class="icon '+icon+'" title="' + text + '"></span>');
insert = $("<a>")
.append(insert);
return $("<li>")
@ -479,6 +553,20 @@
_onSelectRecipient: function(e, s) {
var self = this;
if (e.keyCode == 9) {
e.preventDefault();
if (typeof s.item.name !== 'undefined') {
e.target.value = s.item.name;
} else {
e.target.value = s.item.label;
}
setTimeout(function() {
$(e.target).attr('disabled', false)
.autocomplete('search', $(e.target).val());
}, 0);
return false;
}
e.preventDefault();
// Ensure that the keydown handler for the input field is not
// called; otherwise it would try to add the recipient again, which

View File

@ -18,6 +18,7 @@
* @typedef {object} OC.Share.Types.LinkShareInfo
* @property {bool} isLinkShare
* @property {string} token
* @property {bool} hideDownload
* @property {string|null} password
* @property {string} link
* @property {number} permissions
@ -136,6 +137,7 @@
call = this.updateShare(shareId, attributes, options);
} else {
attributes = _.defaults(attributes, {
hideDownload: false,
password: '',
passwordChanged: false,
permissions: OC.PERMISSION_READ,
@ -614,6 +616,12 @@
var hcp = this.hasCreatePermission(shareIndex);
var hup = this.hasUpdatePermission(shareIndex);
var hdp = this.hasDeletePermission(shareIndex);
if (this.isFile()) {
if (hcp || hup || hdp) {
return 'checked';
}
return '';
}
if (!hcp && !hup && !hdp) {
return '';
}
@ -866,6 +874,9 @@
isLinkShare: true,
id: share.id,
token: share.token,
// hide_download is returned as an int, so force it
// to a boolean
hideDownload: !!share.hide_download,
password: share.share_with,
link: link,
permissions: share.permissions,

View File

@ -61,6 +61,20 @@ templates['sharedialoglinkshareview'] = template({"1":function(container,depth0,
templates['sharedialoglinkshareview_popover_menu'] = template({"1":function(container,depth0,helpers,partials,data) {
var stack1, helper, alias1=depth0 != null ? depth0 : (container.nullContext || {}), alias2=helpers.helperMissing, alias3="function", alias4=container.escapeExpression;
return " <li>\n <span class=\"shareOption menuitem\">\n <span class=\"icon-loading-small hidden\"></span>\n <input type=\"checkbox\" name=\"hideDownload\" id=\"sharingDialogHideDownload-"
+ alias4(((helper = (helper = helpers.cid || (depth0 != null ? depth0.cid : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"cid","hash":{},"data":data}) : helper)))
+ "\" class=\"checkbox hideDownloadCheckbox\"\n "
+ ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.hideDownload : depth0),{"name":"if","hash":{},"fn":container.program(2, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+ " />\n <label for=\"sharingDialogHideDownload-"
+ alias4(((helper = (helper = helpers.cid || (depth0 != null ? depth0.cid : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"cid","hash":{},"data":data}) : helper)))
+ "\">"
+ alias4(((helper = (helper = helpers.hideDownloadLabel || (depth0 != null ? depth0.hideDownloadLabel : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"hideDownloadLabel","hash":{},"data":data}) : helper)))
+ "</label>\n </span>\n </li>\n";
},"2":function(container,depth0,helpers,partials,data) {
return "checked=\"checked\"";
},"4":function(container,depth0,helpers,partials,data) {
var stack1, helper, alias1=depth0 != null ? depth0 : (container.nullContext || {}), alias2=helpers.helperMissing, alias3="function", alias4=container.escapeExpression;
return " <li>\n <span class=\"shareOption menuitem\">\n <span class=\"icon-loading-small hidden\"></span>\n <input type=\"radio\" name=\"publicUpload\" value=\""
+ alias4(((helper = (helper = helpers.publicUploadRValue || (depth0 != null ? depth0.publicUploadRValue : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"publicUploadRValue","hash":{},"data":data}) : helper)))
+ "\" id=\"sharingDialogAllowPublicUpload-r-"
@ -92,7 +106,7 @@ templates['sharedialoglinkshareview_popover_menu'] = template({"1":function(cont
+ "\">"
+ alias4(((helper = (helper = helpers.publicUploadWLabel || (depth0 != null ? depth0.publicUploadWLabel : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"publicUploadWLabel","hash":{},"data":data}) : helper)))
+ "</label>\n </span>\n </li>\n";
},"3":function(container,depth0,helpers,partials,data) {
},"6":function(container,depth0,helpers,partials,data) {
var stack1, helper, alias1=depth0 != null ? depth0 : (container.nullContext || {}), alias2=helpers.helperMissing, alias3="function", alias4=container.escapeExpression;
return " <li id=\"allowPublicEditingWrapper\">\n <span class=\"shareOption menuitem\">\n <span class=\"icon-loading-small hidden\"></span>\n <input type=\"checkbox\" name=\"allowPublicEditing\" id=\"sharingDialogAllowPublicEditing-"
@ -104,41 +118,39 @@ templates['sharedialoglinkshareview_popover_menu'] = template({"1":function(cont
+ "\">"
+ alias4(((helper = (helper = helpers.publicEditingLabel || (depth0 != null ? depth0.publicEditingLabel : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"publicEditingLabel","hash":{},"data":data}) : helper)))
+ "</label>\n </span>\n </li>\n";
},"5":function(container,depth0,helpers,partials,data) {
},"8":function(container,depth0,helpers,partials,data) {
var stack1, helper, alias1=depth0 != null ? depth0 : (container.nullContext || {}), alias2=helpers.helperMissing, alias3="function", alias4=container.escapeExpression;
return " <li>\n <span class=\"shareOption menuitem\">\n <input type=\"checkbox\" name=\"showPassword\" id=\"showPassword-"
+ alias4(((helper = (helper = helpers.cid || (depth0 != null ? depth0.cid : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"cid","hash":{},"data":data}) : helper)))
+ "\" class=\"checkbox showPasswordCheckbox\"\n "
+ ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.isPasswordSet : depth0),{"name":"if","hash":{},"fn":container.program(6, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+ ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.isPasswordSet : depth0),{"name":"if","hash":{},"fn":container.program(2, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+ " "
+ ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.isPasswordEnforced : depth0),{"name":"if","hash":{},"fn":container.program(8, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+ ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.isPasswordEnforced : depth0),{"name":"if","hash":{},"fn":container.program(9, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+ " value=\"1\" />\n <label for=\"showPassword-"
+ alias4(((helper = (helper = helpers.cid || (depth0 != null ? depth0.cid : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"cid","hash":{},"data":data}) : helper)))
+ "\">"
+ alias4(((helper = (helper = helpers.enablePasswordLabel || (depth0 != null ? depth0.enablePasswordLabel : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"enablePasswordLabel","hash":{},"data":data}) : helper)))
+ "</label>\n </span>\n </li>\n <li class=\""
+ ((stack1 = helpers.unless.call(alias1,(depth0 != null ? depth0.isPasswordSet : depth0),{"name":"unless","hash":{},"fn":container.program(10, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+ ((stack1 = helpers.unless.call(alias1,(depth0 != null ? depth0.isPasswordSet : depth0),{"name":"unless","hash":{},"fn":container.program(11, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+ " linkPassMenu\">\n <span class=\"shareOption menuitem icon-share-pass\">\n <input id=\"linkPassText-"
+ alias4(((helper = (helper = helpers.cid || (depth0 != null ? depth0.cid : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"cid","hash":{},"data":data}) : helper)))
+ "\" class=\"linkPassText\" type=\"password\" placeholder=\""
+ alias4(((helper = (helper = helpers.passwordPlaceholder || (depth0 != null ? depth0.passwordPlaceholder : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"passwordPlaceholder","hash":{},"data":data}) : helper)))
+ "\" autocomplete=\"new-password\" />\n <span class=\"icon icon-loading-small hidden\"></span>\n </span>\n </li>\n";
},"6":function(container,depth0,helpers,partials,data) {
return "checked=\"checked\"";
},"8":function(container,depth0,helpers,partials,data) {
},"9":function(container,depth0,helpers,partials,data) {
return "disabled=\"disabled\"";
},"10":function(container,depth0,helpers,partials,data) {
},"11":function(container,depth0,helpers,partials,data) {
return "hidden";
},"12":function(container,depth0,helpers,partials,data) {
},"13":function(container,depth0,helpers,partials,data) {
var helper;
return container.escapeExpression(((helper = (helper = helpers.expireDate || (depth0 != null ? depth0.expireDate : depth0)) != null ? helper : helpers.helperMissing),(typeof helper === "function" ? helper.call(depth0 != null ? depth0 : (container.nullContext || {}),{"name":"expireDate","hash":{},"data":data}) : helper)));
},"14":function(container,depth0,helpers,partials,data) {
},"15":function(container,depth0,helpers,partials,data) {
var helper;
return container.escapeExpression(((helper = (helper = helpers.defaultExpireDate || (depth0 != null ? depth0.defaultExpireDate : depth0)) != null ? helper : helpers.helperMissing),(typeof helper === "function" ? helper.call(depth0 != null ? depth0 : (container.nullContext || {}),{"name":"defaultExpireDate","hash":{},"data":data}) : helper)));
},"16":function(container,depth0,helpers,partials,data) {
},"17":function(container,depth0,helpers,partials,data) {
var helper, alias1=depth0 != null ? depth0 : (container.nullContext || {}), alias2=helpers.helperMissing, alias3="function", alias4=container.escapeExpression;
return " <li>\n <a href=\"#\" class=\"shareOption menuitem pop-up\" data-url=\""
@ -162,21 +174,22 @@ templates['sharedialoglinkshareview_popover_menu'] = template({"1":function(cont
+ "\" class=\"linkText\" type=\"text\" readonly=\"readonly\" value=\""
+ alias4(((helper = (helper = helpers.shareLinkURL || (depth0 != null ? depth0.shareLinkURL : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"shareLinkURL","hash":{},"data":data}) : helper)))
+ "\" />\n </span>\n </li>\n"
+ ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.publicUpload : depth0),{"name":"if","hash":{},"fn":container.program(1, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+ ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.publicEditing : depth0),{"name":"if","hash":{},"fn":container.program(3, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+ ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.showPasswordCheckBox : depth0),{"name":"if","hash":{},"fn":container.program(5, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+ ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.showHideDownloadCheckbox : depth0),{"name":"if","hash":{},"fn":container.program(1, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+ ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.publicUpload : depth0),{"name":"if","hash":{},"fn":container.program(4, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+ ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.publicEditing : depth0),{"name":"if","hash":{},"fn":container.program(6, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+ ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.showPasswordCheckBox : depth0),{"name":"if","hash":{},"fn":container.program(8, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+ " <li>\n <span class=\"shareOption menuitem\">\n <input id=\"expireDate-"
+ alias4(((helper = (helper = helpers.cid || (depth0 != null ? depth0.cid : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"cid","hash":{},"data":data}) : helper)))
+ "\" type=\"checkbox\" name=\"expirationDate\" class=\"expireDate checkbox\"\n "
+ ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.hasExpireDate : depth0),{"name":"if","hash":{},"fn":container.program(6, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+ ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.hasExpireDate : depth0),{"name":"if","hash":{},"fn":container.program(2, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+ " "
+ ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.isExpirationEnforced : depth0),{"name":"if","hash":{},"fn":container.program(8, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+ ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.isExpirationEnforced : depth0),{"name":"if","hash":{},"fn":container.program(9, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+ "\" />\n <label for=\"expireDate-"
+ alias4(((helper = (helper = helpers.cid || (depth0 != null ? depth0.cid : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"cid","hash":{},"data":data}) : helper)))
+ "\">"
+ alias4(((helper = (helper = helpers.expireDateLabel || (depth0 != null ? depth0.expireDateLabel : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"expireDateLabel","hash":{},"data":data}) : helper)))
+ "</label>\n </span>\n </li>\n <li class=\""
+ ((stack1 = helpers.unless.call(alias1,(depth0 != null ? depth0.hasExpireDate : depth0),{"name":"unless","hash":{},"fn":container.program(10, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+ ((stack1 = helpers.unless.call(alias1,(depth0 != null ? depth0.hasExpireDate : depth0),{"name":"unless","hash":{},"fn":container.program(11, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+ "\">\n <span class=\"menuitem icon-expiredate expirationDateContainer-"
+ alias4(((helper = (helper = helpers.cid || (depth0 != null ? depth0.cid : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"cid","hash":{},"data":data}) : helper)))
+ "\">\n <label for=\"expirationDatePicker-"
@ -190,7 +203,7 @@ templates['sharedialoglinkshareview_popover_menu'] = template({"1":function(cont
+ "\" class=\"datepicker\" type=\"text\" placeholder=\""
+ alias4(((helper = (helper = helpers.expirationDatePlaceholder || (depth0 != null ? depth0.expirationDatePlaceholder : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"expirationDatePlaceholder","hash":{},"data":data}) : helper)))
+ "\" value=\""
+ ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.hasExpireDate : depth0),{"name":"if","hash":{},"fn":container.program(12, data, 0),"inverse":container.program(14, data, 0),"data":data})) != null ? stack1 : "")
+ ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.hasExpireDate : depth0),{"name":"if","hash":{},"fn":container.program(13, data, 0),"inverse":container.program(15, data, 0),"data":data})) != null ? stack1 : "")
+ "\" />\n </span>\n </li>\n <li>\n <a href=\"#\" class=\"share-add\">\n <span class=\"icon-loading-small hidden\"></span>\n <span class=\"icon icon-edit\"></span>\n <span>"
+ alias4(((helper = (helper = helpers.addNoteLabel || (depth0 != null ? depth0.addNoteLabel : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"addNoteLabel","hash":{},"data":data}) : helper)))
+ "</span>\n <input type=\"button\" class=\"share-note-delete icon-delete\">\n </a>\n </li>\n <li class=\"share-note-form share-note-link hidden\">\n <span class=\"menuitem icon-note\">\n <textarea class=\"share-note\">"
@ -198,7 +211,7 @@ templates['sharedialoglinkshareview_popover_menu'] = template({"1":function(cont
+ "</textarea>\n <input type=\"submit\" class=\"icon-confirm share-note-submit\" value=\"\" id=\"add-note-"
+ alias4(((helper = (helper = helpers.shareId || (depth0 != null ? depth0.shareId : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"shareId","hash":{},"data":data}) : helper)))
+ "\" />\n </span>\n </li>\n"
+ ((stack1 = helpers.each.call(alias1,(depth0 != null ? depth0.social : depth0),{"name":"each","hash":{},"fn":container.program(16, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+ ((stack1 = helpers.each.call(alias1,(depth0 != null ? depth0.social : depth0),{"name":"each","hash":{},"fn":container.program(17, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+ " </ul>\n</div>\n";
},"useData":true});
templates['sharedialoglinkshareview_popover_menu_pending'] = template({"1":function(container,depth0,helpers,partials,data) {

View File

@ -72,6 +72,100 @@ describe('OC.Share.ShareDialogLinkShareView', function () {
configModel.isShareWithLinkAllowed.restore();
});
describe('hide download', function () {
var $hideDownloadCheckbox;
var $workingIcon;
beforeEach(function () {
// Needed to render the view
configModel.isShareWithLinkAllowed.returns(true);
// Setting the share also triggers the rendering
shareModel.set({
linkShare: {
isLinkShare: true,
}
});
$hideDownloadCheckbox = view.$el.find('.hideDownloadCheckbox');
$workingIcon = $hideDownloadCheckbox.prev('.icon-loading-small');
sinon.stub(shareModel, 'saveLinkShare');
expect($workingIcon.hasClass('hidden')).toBeTruthy();
});
afterEach(function () {
shareModel.saveLinkShare.restore();
});
it('is shown if the share is a file', function() {
expect($hideDownloadCheckbox.length).toBeTruthy();
});
it('is not shown if the share is a folder', function() {
shareModel.fileInfoModel.set('mimetype', 'httpd/unix-directory');
// Setting the item type also triggers the rendering
shareModel.set({
itemType: 'folder'
});
$hideDownloadCheckbox = view.$el.find('.hideDownloadCheckbox');
expect($hideDownloadCheckbox.length).toBeFalsy();
});
it('checkbox is checked when the setting is enabled', function () {
shareModel.set({
linkShare: {
isLinkShare: true,
hideDownload: true
}
});
$hideDownloadCheckbox = view.$el.find('.hideDownloadCheckbox');
expect($hideDownloadCheckbox.is(':checked')).toEqual(true);
});
it('checkbox is not checked when the setting is disabled', function () {
expect($hideDownloadCheckbox.is(':checked')).toEqual(false);
});
it('enables the setting if clicked when unchecked', function () {
// Simulate the click by checking the checkbox and then triggering
// the "change" event.
$hideDownloadCheckbox.prop('checked', true);
$hideDownloadCheckbox.change();
expect($workingIcon.hasClass('hidden')).toBeFalsy();
expect(shareModel.saveLinkShare.withArgs({ hideDownload: true }).calledOnce).toBeTruthy();
});
it('disables the setting if clicked when checked', function () {
shareModel.set({
linkShare: {
isLinkShare: true,
hideDownload: true
}
});
$hideDownloadCheckbox = view.$el.find('.hideDownloadCheckbox');
$workingIcon = $hideDownloadCheckbox.prev('.icon-loading-small');
// Simulate the click by unchecking the checkbox and then triggering
// the "change" event.
$hideDownloadCheckbox.prop('checked', false);
$hideDownloadCheckbox.change();
expect($workingIcon.hasClass('hidden')).toBeFalsy();
expect(shareModel.saveLinkShare.withArgs({ hideDownload: false }).calledOnce).toBeTruthy();
});
});
describe('onPasswordEntered', function () {
var $passwordText;

View File

@ -90,6 +90,37 @@ describe('OC.Share.ShareDialogShareeListView', function () {
});
describe('Sets correct initial checkbox state', function () {
it('marks edit box as unchecked for file shares without edit permissions', function () {
shareModel.set('shares', [{
id: 100,
item_source: 123,
permissions: 1,
share_type: OC.Share.SHARE_TYPE_USER,
share_with: 'user1',
share_with_displayname: 'User One',
uid_owner: oc_current_user,
itemType: 'file'
}]);
listView.render();
expect(listView.$el.find("input[name='edit']").is(':not(:checked)')).toEqual(true);
});
it('marks edit box as checked for file shares', function () {
shareModel.set('shares', [{
id: 100,
item_source: 123,
permissions: 1 | OC.PERMISSION_UPDATE,
share_type: OC.Share.SHARE_TYPE_USER,
share_with: 'user1',
share_with_displayname: 'User One',
uid_owner: oc_current_user,
itemType: 'file'
}]);
listView.render();
expect(listView.$el.find("input[name='edit']").is(':checked')).toEqual(true);
});
it('marks edit box as indeterminate when only some permissions are given', function () {
shareModel.set('shares', [{
id: 100,

View File

@ -168,7 +168,8 @@ describe('OC.Share.ShareItemModel', function() {
stime: 1403884258,
storage: 1,
token: 'tehtoken',
uid_owner: 'root'
uid_owner: 'root',
hide_download: 1
}
]));
@ -186,6 +187,7 @@ describe('OC.Share.ShareItemModel', function() {
var linkShare = model.get('linkShare');
expect(linkShare.isLinkShare).toEqual(true);
expect(linkShare.hideDownload).toEqual(true);
// TODO: check more attributes
});
@ -289,7 +291,8 @@ describe('OC.Share.ShareItemModel', function() {
stime: 1403884258,
storage: 1,
token: 'tehtoken',
uid_owner: 'root'
uid_owner: 'root',
hide_download: 0
}, {
displayname_owner: 'root',
expiration: '2015-10-15 00:00:00',
@ -307,7 +310,8 @@ describe('OC.Share.ShareItemModel', function() {
stime: 1403884509,
storage: 1,
token: 'anothertoken',
uid_owner: 'root'
uid_owner: 'root',
hide_download: 1
}]
));
OC.currentUser = 'root';
@ -320,6 +324,7 @@ describe('OC.Share.ShareItemModel', function() {
var linkShare = model.get('linkShare');
expect(linkShare.isLinkShare).toEqual(true);
expect(linkShare.token).toEqual('tehtoken');
expect(linkShare.hideDownload).toEqual(false);
// TODO: check child too
});
@ -579,6 +584,7 @@ describe('OC.Share.ShareItemModel', function() {
expect(addShareStub.calledOnce).toEqual(true);
expect(addShareStub.firstCall.args[0]).toEqual({
hideDownload: false,
password: '',
passwordChanged: false,
permissions: OC.PERMISSION_READ,
@ -603,6 +609,7 @@ describe('OC.Share.ShareItemModel', function() {
expect(addShareStub.calledOnce).toEqual(true);
expect(addShareStub.firstCall.args[0]).toEqual({
hideDownload: false,
password: '',
passwordChanged: false,
permissions: OC.PERMISSION_READ,

View File

@ -141,9 +141,6 @@ OC.L10N.register(
"No users found for {search}" : "Geen gebruiker gevind vir {search}",
"An error occurred (\"{message}\"). Please try again" : "'n Fout het voorgekom (\"{message}\"). Probeer asseblief weer",
"An error occurred. Please try again" : "'n Fout het voorgekom. Probeer asseblief weer",
"{sharee} (group)" : "{sharee} (groep)",
"{sharee} (remote)" : "{sharee} (afgeleë)",
"{sharee} (email)" : "{sharee} (e-pos)",
"{sharee} ({type}, {owner})" : "{sharee} ({type}, {owner})",
"Share" : "Deel",
"Name or email address..." : "Naam of e-posadres...",
@ -243,6 +240,9 @@ OC.L10N.register(
"Error setting expiration date" : "Fout terwyl vervaldatum stel",
"The public link will expire no later than {days} days after it is created" : "Die publieke skakel sal presies {days} na dit geskep is verval",
"{{shareInitiatorDisplayName}} shared via link" : "{{shareInitiatorDisplayName}} gedeel via skakel",
"{sharee} (group)" : "{sharee} (groep)",
"{sharee} (remote)" : "{sharee} (afgeleë)",
"{sharee} (email)" : "{sharee} (e-pos)",
"Share with other people by entering a user or group or an email address." : "Deel met ander deur 'n gebruiker, groep of e-posadres in te vul. ",
"The specified document has not been found on the server." : "Die gekose dokument was nie op die bediener gevind nie.",
"You can click here to return to %s." : "U kan hier klik om terug te keer na %s",

View File

@ -139,9 +139,6 @@
"No users found for {search}" : "Geen gebruiker gevind vir {search}",
"An error occurred (\"{message}\"). Please try again" : "'n Fout het voorgekom (\"{message}\"). Probeer asseblief weer",
"An error occurred. Please try again" : "'n Fout het voorgekom. Probeer asseblief weer",
"{sharee} (group)" : "{sharee} (groep)",
"{sharee} (remote)" : "{sharee} (afgeleë)",
"{sharee} (email)" : "{sharee} (e-pos)",
"{sharee} ({type}, {owner})" : "{sharee} ({type}, {owner})",
"Share" : "Deel",
"Name or email address..." : "Naam of e-posadres...",
@ -241,6 +238,9 @@
"Error setting expiration date" : "Fout terwyl vervaldatum stel",
"The public link will expire no later than {days} days after it is created" : "Die publieke skakel sal presies {days} na dit geskep is verval",
"{{shareInitiatorDisplayName}} shared via link" : "{{shareInitiatorDisplayName}} gedeel via skakel",
"{sharee} (group)" : "{sharee} (groep)",
"{sharee} (remote)" : "{sharee} (afgeleë)",
"{sharee} (email)" : "{sharee} (e-pos)",
"Share with other people by entering a user or group or an email address." : "Deel met ander deur 'n gebruiker, groep of e-posadres in te vul. ",
"The specified document has not been found on the server." : "Die gekose dokument was nie op die bediener gevind nie.",
"You can click here to return to %s." : "U kan hier klik om terug te keer na %s",

View File

@ -130,8 +130,6 @@ OC.L10N.register(
"No users or groups found for {search}" : "Nun s'alcontraron usuarios o grupos pa {search}",
"No users found for {search}" : "Nun s'alcontraron usuarios pa {search}",
"An error occurred. Please try again" : "Asocedió un fallu. Volvi tentalo, por favor",
"{sharee} (group)" : "{sharee} (grupu)",
"{sharee} (email)" : "{sharee} (corréu)",
"{sharee} ({type}, {owner})" : "{sharee} ({type}, {owner})",
"Share" : "Compartir",
"Name or email address..." : "Nome o direición de corréu...",
@ -230,6 +228,8 @@ OC.L10N.register(
"Error setting expiration date" : "Fallu afitando la fecha de caducidá",
"The public link will expire no later than {days} days after it is created" : "L'enllaz públicu va caducar enantes de {days} díes dende la so creación",
"{{shareInitiatorDisplayName}} shared via link" : "{{shareInitiatorDisplayName}} compartió per enllaz",
"{sharee} (group)" : "{sharee} (grupu)",
"{sharee} (email)" : "{sharee} (corréu)",
"Share with other people by entering a user or group, a federated cloud ID or an email address." : "Comparti con otra xente introduciendo un usuariu, grupu, ID de ñube federada o direición de corréu.",
"Share with other people by entering a user or group or a federated cloud ID." : "Comparti con otra xente introduciendo un usuariu, grupu o ID de ñube federada.",
"Share with other people by entering a user or group or an email address." : "Comparti con otra xente introduciendo un usuariu, grupu o direición de corréu.",

View File

@ -128,8 +128,6 @@
"No users or groups found for {search}" : "Nun s'alcontraron usuarios o grupos pa {search}",
"No users found for {search}" : "Nun s'alcontraron usuarios pa {search}",
"An error occurred. Please try again" : "Asocedió un fallu. Volvi tentalo, por favor",
"{sharee} (group)" : "{sharee} (grupu)",
"{sharee} (email)" : "{sharee} (corréu)",
"{sharee} ({type}, {owner})" : "{sharee} ({type}, {owner})",
"Share" : "Compartir",
"Name or email address..." : "Nome o direición de corréu...",
@ -228,6 +226,8 @@
"Error setting expiration date" : "Fallu afitando la fecha de caducidá",
"The public link will expire no later than {days} days after it is created" : "L'enllaz públicu va caducar enantes de {days} díes dende la so creación",
"{{shareInitiatorDisplayName}} shared via link" : "{{shareInitiatorDisplayName}} compartió per enllaz",
"{sharee} (group)" : "{sharee} (grupu)",
"{sharee} (email)" : "{sharee} (corréu)",
"Share with other people by entering a user or group, a federated cloud ID or an email address." : "Comparti con otra xente introduciendo un usuariu, grupu, ID de ñube federada o direición de corréu.",
"Share with other people by entering a user or group or a federated cloud ID." : "Comparti con otra xente introduciendo un usuariu, grupu o ID de ñube federada.",
"Share with other people by entering a user or group or an email address." : "Comparti con otra xente introduciendo un usuariu, grupu o direición de corréu.",

View File

@ -151,12 +151,8 @@ OC.L10N.register(
"No users or groups found for {search}" : "Няма потребители или групи за {search}",
"No users found for {search}" : "Няма потребители за {search}",
"An error occurred. Please try again" : "Възникна грешка. Моля, опитайте отново",
"{sharee} (group)" : "{sharee} (група)",
"{sharee} (remote)" : "{sharee} (отдалечен)",
"{sharee} (remote group)" : "{sharee} (отдалечена група)",
"{sharee} (email)" : "{sharee} (имейл)",
"{sharee} ({type}, {owner})" : "{sharee} ({type}, {owner})",
"{sharee} (conversation)" : "{sharee} (разговор)",
"Share" : "Споделяне",
"Name or email address..." : "Име или имейл адрес...",
"Name..." : "Име...",
@ -278,6 +274,9 @@ OC.L10N.register(
"Error setting expiration date" : "Грешка при задаване на срок на валидност",
"The public link will expire no later than {days} days after it is created" : "Общодостъпната връзка ще изтече не по-късно от {days} дни след създаването ѝ.",
"{{shareInitiatorDisplayName}} shared via link" : "{{shareInitiatorDisplayName}} споделен с връзка",
"{sharee} (group)" : "{sharee} (група)",
"{sharee} (remote)" : "{sharee} (отдалечен)",
"{sharee} (email)" : "{sharee} (имейл)",
"The specified document has not been found on the server." : "Избраният документ не е намерен на сървъра.",
"You can click here to return to %s." : "Можете да натиснете тук, за да се върнете на %s.",
"The server encountered an internal error and was unable to complete your request." : "Поради вътрешно сървърна грешка, сървърът не можа да изпълни заявката ви.",
@ -297,6 +296,7 @@ OC.L10N.register(
"This page will refresh itself when the %s instance is available again." : "Страницата ще се зареди автоматично, когато %s е отново на линия.",
"Thank you for your patience." : "Благодарим ви за търпението.",
"You are about to grant %s access to your %s account." : "Ще разрешите на %s да ползва профила %s.",
"{sharee} (conversation)" : "{sharee} (разговор)",
"Please log in before granting %s access to your %s account." : "Необходимо е да се впишете, преди да дадете достъп на %s до вашия %s профил."
},
"nplurals=2; plural=(n != 1);");

View File

@ -149,12 +149,8 @@
"No users or groups found for {search}" : "Няма потребители или групи за {search}",
"No users found for {search}" : "Няма потребители за {search}",
"An error occurred. Please try again" : "Възникна грешка. Моля, опитайте отново",
"{sharee} (group)" : "{sharee} (група)",
"{sharee} (remote)" : "{sharee} (отдалечен)",
"{sharee} (remote group)" : "{sharee} (отдалечена група)",
"{sharee} (email)" : "{sharee} (имейл)",
"{sharee} ({type}, {owner})" : "{sharee} ({type}, {owner})",
"{sharee} (conversation)" : "{sharee} (разговор)",
"Share" : "Споделяне",
"Name or email address..." : "Име или имейл адрес...",
"Name..." : "Име...",
@ -276,6 +272,9 @@
"Error setting expiration date" : "Грешка при задаване на срок на валидност",
"The public link will expire no later than {days} days after it is created" : "Общодостъпната връзка ще изтече не по-късно от {days} дни след създаването ѝ.",
"{{shareInitiatorDisplayName}} shared via link" : "{{shareInitiatorDisplayName}} споделен с връзка",
"{sharee} (group)" : "{sharee} (група)",
"{sharee} (remote)" : "{sharee} (отдалечен)",
"{sharee} (email)" : "{sharee} (имейл)",
"The specified document has not been found on the server." : "Избраният документ не е намерен на сървъра.",
"You can click here to return to %s." : "Можете да натиснете тук, за да се върнете на %s.",
"The server encountered an internal error and was unable to complete your request." : "Поради вътрешно сървърна грешка, сървърът не можа да изпълни заявката ви.",
@ -295,6 +294,7 @@
"This page will refresh itself when the %s instance is available again." : "Страницата ще се зареди автоматично, когато %s е отново на линия.",
"Thank you for your patience." : "Благодарим ви за търпението.",
"You are about to grant %s access to your %s account." : "Ще разрешите на %s да ползва профила %s.",
"{sharee} (conversation)" : "{sharee} (разговор)",
"Please log in before granting %s access to your %s account." : "Необходимо е да се впишете, преди да дадете достъп на %s до вашия %s профил."
},"pluralForm" :"nplurals=2; plural=(n != 1);"
}

View File

@ -210,12 +210,8 @@ OC.L10N.register(
"No users found for {search}" : "No s'han trobat usuaris per {search}",
"An error occurred (\"{message}\"). Please try again" : "S'ha produït un error (\"{message}\"). Si us plau, torni a intentar-ho",
"An error occurred. Please try again" : "S'ha produït un error. Si us plau, torni a intentar-ho",
"{sharee} (group)" : "{sharee} (grup)",
"{sharee} (remote)" : "{sharee} (remot)",
"{sharee} (remote group)" : "{sharee} (grup remot)",
"{sharee} (email)" : "{sharee} (email)",
"{sharee} ({type}, {owner})" : "{sharee} ({type}, {owner})",
"{sharee} (conversation)" : "{sharee} (conversation)",
"Share" : "Comparteix",
"Name or email address..." : "Nom o adreça electrònica...",
"Name or federated cloud ID..." : "Nom o ID de Núvol Federat…",
@ -382,6 +378,9 @@ OC.L10N.register(
"Error setting expiration date" : "Error en establir la data de venciment",
"The public link will expire no later than {days} days after it is created" : "L'enllaç públic tindrà venciment abans de {days} dies després de crear-lo",
"{{shareInitiatorDisplayName}} shared via link" : "{{shareInitiatorDisplayName}} ha compartit per enllaç",
"{sharee} (group)" : "{sharee} (grup)",
"{sharee} (remote)" : "{sharee} (remot)",
"{sharee} (email)" : "{sharee} (email)",
"Share with other people by entering a user or group, a federated cloud ID or an email address." : "Compartir amb altres persones introduint un usuari o grup, un ID de núvol federat o una adreça demail.",
"Share with other people by entering a user or group or a federated cloud ID." : "Compartir amb altres persones introduint un usuari o grup o ID de núvol federat.",
"Share with other people by entering a user or group or an email address." : "Compartir amb altres persones introduint un usuari o grup o una adreça demail.",
@ -413,6 +412,7 @@ OC.L10N.register(
"You are about to grant %s access to your %s account." : "Estàs a punt d'autoritzar a %s a accedir al teu compte %s.",
"Depending on your configuration, this button could also work to trust the domain:" : "Depenent de la teva configuració, aquest botó també podria funcionar per confiar en el domini:",
"Copy URL" : "Copiar URL",
"{sharee} (conversation)" : "{sharee} (conversation)",
"Please log in before granting %s access to your %s account." : "Si us plau entrar abans de donar %s accés al teu compte %s.",
"Further information how to configure this can be found in the %sdocumentation%s." : "Més informació de com configurar això es pot trobar a la %sdocumentation%s."
},

View File

@ -208,12 +208,8 @@
"No users found for {search}" : "No s'han trobat usuaris per {search}",
"An error occurred (\"{message}\"). Please try again" : "S'ha produït un error (\"{message}\"). Si us plau, torni a intentar-ho",
"An error occurred. Please try again" : "S'ha produït un error. Si us plau, torni a intentar-ho",
"{sharee} (group)" : "{sharee} (grup)",
"{sharee} (remote)" : "{sharee} (remot)",
"{sharee} (remote group)" : "{sharee} (grup remot)",
"{sharee} (email)" : "{sharee} (email)",
"{sharee} ({type}, {owner})" : "{sharee} ({type}, {owner})",
"{sharee} (conversation)" : "{sharee} (conversation)",
"Share" : "Comparteix",
"Name or email address..." : "Nom o adreça electrònica...",
"Name or federated cloud ID..." : "Nom o ID de Núvol Federat…",
@ -380,6 +376,9 @@
"Error setting expiration date" : "Error en establir la data de venciment",
"The public link will expire no later than {days} days after it is created" : "L'enllaç públic tindrà venciment abans de {days} dies després de crear-lo",
"{{shareInitiatorDisplayName}} shared via link" : "{{shareInitiatorDisplayName}} ha compartit per enllaç",
"{sharee} (group)" : "{sharee} (grup)",
"{sharee} (remote)" : "{sharee} (remot)",
"{sharee} (email)" : "{sharee} (email)",
"Share with other people by entering a user or group, a federated cloud ID or an email address." : "Compartir amb altres persones introduint un usuari o grup, un ID de núvol federat o una adreça demail.",
"Share with other people by entering a user or group or a federated cloud ID." : "Compartir amb altres persones introduint un usuari o grup o ID de núvol federat.",
"Share with other people by entering a user or group or an email address." : "Compartir amb altres persones introduint un usuari o grup o una adreça demail.",
@ -411,6 +410,7 @@
"You are about to grant %s access to your %s account." : "Estàs a punt d'autoritzar a %s a accedir al teu compte %s.",
"Depending on your configuration, this button could also work to trust the domain:" : "Depenent de la teva configuració, aquest botó també podria funcionar per confiar en el domini:",
"Copy URL" : "Copiar URL",
"{sharee} (conversation)" : "{sharee} (conversation)",
"Please log in before granting %s access to your %s account." : "Si us plau entrar abans de donar %s accés al teu compte %s.",
"Further information how to configure this can be found in the %sdocumentation%s." : "Més informació de com configurar això es pot trobar a la %sdocumentation%s."
},"pluralForm" :"nplurals=2; plural=(n != 1);"

View File

@ -210,12 +210,8 @@ OC.L10N.register(
"No users found for {search}" : "Nebyli nalezeni žádní uživatelé pro {search}",
"An error occurred (\"{message}\"). Please try again" : "Došlo k chybě („{message}“). Zkuste to znovu",
"An error occurred. Please try again" : "Došlo k chybě. Zkuste to znovu",
"{sharee} (group)" : "{sharee} (skupina)",
"{sharee} (remote)" : "{sharee} (na protějšku)",
"{sharee} (remote group)" : "{sharee} (skupina na protějšku)",
"{sharee} (email)" : "{sharee} (e-mail)",
"{sharee} ({type}, {owner})" : "{sharee} ({type}, {owner})",
"{sharee} (conversation)" : "{sharee} (konverzace)",
"Share" : "Sdílet",
"Name or email address..." : "Jméno nebo e-mailová adresa…",
"Name or federated cloud ID..." : "Jméno nebo identifikátor v rámci sdruženého cloudu…",
@ -356,6 +352,7 @@ OC.L10N.register(
"For help, see the <a target=\"_blank\" rel=\"noreferrer noopener\" href=\"%s\">documentation</a>." : "Pro pomoc, nahlédněte do <a target=\"_blank\" rel=\"noreferrer noopener\" href=\"%s\">dokumentace</a>.",
"I know that if I continue doing the update via web UI has the risk, that the request runs into a timeout and could cause data loss, but I have a backup and know how to restore my instance in case of a failure." : "Beru na vědomí, že při aktualizaci skrze webové rozhraní hrozí nebezpečí vypršení požadavku, který může vyústit ve ztrátu dat. Mám pro takový případ zálohu a vím, jak ji v případě selhání obnovit.",
"Upgrade via web on my own risk" : "Na vlastní nebezpečí aktualizovat skrze web",
"Maintenance mode" : "Režim údržby",
"This %s instance is currently in maintenance mode, which may take a while." : "Tato instalace %s je právě ve stavu údržby a ta může chvíli trvat.",
"Contact your system administrator if this message persists or appeared unexpectedly." : "Pokud se tato zpráva objevuje opakovaně nebo nečekaně, obraťte se správce systému.",
"Updated \"%s\" to %s" : "Aktualizováno z „%s“ na %s",
@ -380,6 +377,9 @@ OC.L10N.register(
"Error setting expiration date" : "Chyba při nastavení data skončení platnosti",
"The public link will expire no later than {days} days after it is created" : "Veřejný odkaz vyprší nejpozději {days} dní od svého vytvoření",
"{{shareInitiatorDisplayName}} shared via link" : "{{shareInitiatorDisplayName}} sdílí pomocí odkazu",
"{sharee} (group)" : "{sharee} (skupina)",
"{sharee} (remote)" : "{sharee} (na protějšku)",
"{sharee} (email)" : "{sharee} (e-mail)",
"Share with other people by entering a user or group, a federated cloud ID or an email address." : "Sdílejte s dalšími lidmi zadáním uživatelského jména, skupiny, federovaného cloud ID, nebo e-mailové adresy.",
"Share with other people by entering a user or group or a federated cloud ID." : "Sdílejte s dalšími lidmi zadáním uživatelského jména, skupiny, nebo sdruženého cloud ID.",
"Share with other people by entering a user or group or an email address." : "Sdílejte s dalšími lidmi zadáním uživatelského jména, jména skupiny, nebo e-mailové adresy.",
@ -411,6 +411,7 @@ OC.L10N.register(
"You are about to grant %s access to your %s account." : "Chystáte se povolit %s přístup k vašemu %s účtu.",
"Depending on your configuration, this button could also work to trust the domain:" : "V závislosti na vaší konfiguraci by pro označení domény za důvěryhodnou mohlo fungovat i toto tlačítko:",
"Copy URL" : "Kopírovat URL",
"{sharee} (conversation)" : "{sharee} (konverzace)",
"Please log in before granting %s access to your %s account." : "Přihlaste se před udělením %s přístupu k vašemu %s účtu.",
"Further information how to configure this can be found in the %sdocumentation%s." : "Více informací o tom, jak toto nastavit, jsou k dispozici v%sdokumentaci%s."
},

View File

@ -208,12 +208,8 @@
"No users found for {search}" : "Nebyli nalezeni žádní uživatelé pro {search}",
"An error occurred (\"{message}\"). Please try again" : "Došlo k chybě („{message}“). Zkuste to znovu",
"An error occurred. Please try again" : "Došlo k chybě. Zkuste to znovu",
"{sharee} (group)" : "{sharee} (skupina)",
"{sharee} (remote)" : "{sharee} (na protějšku)",
"{sharee} (remote group)" : "{sharee} (skupina na protějšku)",
"{sharee} (email)" : "{sharee} (e-mail)",
"{sharee} ({type}, {owner})" : "{sharee} ({type}, {owner})",
"{sharee} (conversation)" : "{sharee} (konverzace)",
"Share" : "Sdílet",
"Name or email address..." : "Jméno nebo e-mailová adresa…",
"Name or federated cloud ID..." : "Jméno nebo identifikátor v rámci sdruženého cloudu…",
@ -354,6 +350,7 @@
"For help, see the <a target=\"_blank\" rel=\"noreferrer noopener\" href=\"%s\">documentation</a>." : "Pro pomoc, nahlédněte do <a target=\"_blank\" rel=\"noreferrer noopener\" href=\"%s\">dokumentace</a>.",
"I know that if I continue doing the update via web UI has the risk, that the request runs into a timeout and could cause data loss, but I have a backup and know how to restore my instance in case of a failure." : "Beru na vědomí, že při aktualizaci skrze webové rozhraní hrozí nebezpečí vypršení požadavku, který může vyústit ve ztrátu dat. Mám pro takový případ zálohu a vím, jak ji v případě selhání obnovit.",
"Upgrade via web on my own risk" : "Na vlastní nebezpečí aktualizovat skrze web",
"Maintenance mode" : "Režim údržby",
"This %s instance is currently in maintenance mode, which may take a while." : "Tato instalace %s je právě ve stavu údržby a ta může chvíli trvat.",
"Contact your system administrator if this message persists or appeared unexpectedly." : "Pokud se tato zpráva objevuje opakovaně nebo nečekaně, obraťte se správce systému.",
"Updated \"%s\" to %s" : "Aktualizováno z „%s“ na %s",
@ -378,6 +375,9 @@
"Error setting expiration date" : "Chyba při nastavení data skončení platnosti",
"The public link will expire no later than {days} days after it is created" : "Veřejný odkaz vyprší nejpozději {days} dní od svého vytvoření",
"{{shareInitiatorDisplayName}} shared via link" : "{{shareInitiatorDisplayName}} sdílí pomocí odkazu",
"{sharee} (group)" : "{sharee} (skupina)",
"{sharee} (remote)" : "{sharee} (na protějšku)",
"{sharee} (email)" : "{sharee} (e-mail)",
"Share with other people by entering a user or group, a federated cloud ID or an email address." : "Sdílejte s dalšími lidmi zadáním uživatelského jména, skupiny, federovaného cloud ID, nebo e-mailové adresy.",
"Share with other people by entering a user or group or a federated cloud ID." : "Sdílejte s dalšími lidmi zadáním uživatelského jména, skupiny, nebo sdruženého cloud ID.",
"Share with other people by entering a user or group or an email address." : "Sdílejte s dalšími lidmi zadáním uživatelského jména, jména skupiny, nebo e-mailové adresy.",
@ -409,6 +409,7 @@
"You are about to grant %s access to your %s account." : "Chystáte se povolit %s přístup k vašemu %s účtu.",
"Depending on your configuration, this button could also work to trust the domain:" : "V závislosti na vaší konfiguraci by pro označení domény za důvěryhodnou mohlo fungovat i toto tlačítko:",
"Copy URL" : "Kopírovat URL",
"{sharee} (conversation)" : "{sharee} (konverzace)",
"Please log in before granting %s access to your %s account." : "Přihlaste se před udělením %s přístupu k vašemu %s účtu.",
"Further information how to configure this can be found in the %sdocumentation%s." : "Více informací o tom, jak toto nastavit, jsou k dispozici v%sdokumentaci%s."
},"pluralForm" :"nplurals=4; plural=(n == 1 && n % 1 == 0) ? 0 : (n >= 2 && n <= 4 && n % 1 == 0) ? 1: (n % 1 != 0 ) ? 2 : 3;"

View File

@ -170,9 +170,6 @@ OC.L10N.register(
"No users found for {search}" : "Ingen brugere fundet for {search}",
"An error occurred (\"{message}\"). Please try again" : "Der opstor den fejl (\"{message}\"). Prøv igen",
"An error occurred. Please try again" : "Der opstor den fejl. Prøv igen",
"{sharee} (group)" : "{sharee} (gruppe)",
"{sharee} (remote)" : "{sharee} (ekstern)",
"{sharee} (email)" : "{sharee} (e-mail)",
"{sharee} ({type}, {owner})" : "{share} ({type}, {owner})",
"Share" : "Del",
"Name or email address..." : "Navn eller e-mail adresse...",
@ -319,6 +316,9 @@ OC.L10N.register(
"Error setting expiration date" : "Fejl under sætning af udløbsdato",
"The public link will expire no later than {days} days after it is created" : "Det offentlige link udløber senest {days} dage efter det blev oprettet",
"{{shareInitiatorDisplayName}} shared via link" : "{{shareInitiatorDisplayName}} delt via link",
"{sharee} (group)" : "{sharee} (gruppe)",
"{sharee} (remote)" : "{sharee} (ekstern)",
"{sharee} (email)" : "{sharee} (e-mail)",
"Share with other people by entering a user or group, a federated cloud ID or an email address." : "Del med andre ved at indtaste et brugernavn, en gruppe, et federated cloud id eller en e-mail adresse.",
"Share with other people by entering a user or group or a federated cloud ID." : "Del med andre ved at indtaste et brugernavn, en gruppe eller et federated cloud id.",
"Share with other people by entering a user or group or an email address." : "Del med andre ved at indtaste et brugernavn, en gruppe eller e-mail adresse.",

View File

@ -168,9 +168,6 @@
"No users found for {search}" : "Ingen brugere fundet for {search}",
"An error occurred (\"{message}\"). Please try again" : "Der opstor den fejl (\"{message}\"). Prøv igen",
"An error occurred. Please try again" : "Der opstor den fejl. Prøv igen",
"{sharee} (group)" : "{sharee} (gruppe)",
"{sharee} (remote)" : "{sharee} (ekstern)",
"{sharee} (email)" : "{sharee} (e-mail)",
"{sharee} ({type}, {owner})" : "{share} ({type}, {owner})",
"Share" : "Del",
"Name or email address..." : "Navn eller e-mail adresse...",
@ -317,6 +314,9 @@
"Error setting expiration date" : "Fejl under sætning af udløbsdato",
"The public link will expire no later than {days} days after it is created" : "Det offentlige link udløber senest {days} dage efter det blev oprettet",
"{{shareInitiatorDisplayName}} shared via link" : "{{shareInitiatorDisplayName}} delt via link",
"{sharee} (group)" : "{sharee} (gruppe)",
"{sharee} (remote)" : "{sharee} (ekstern)",
"{sharee} (email)" : "{sharee} (e-mail)",
"Share with other people by entering a user or group, a federated cloud ID or an email address." : "Del med andre ved at indtaste et brugernavn, en gruppe, et federated cloud id eller en e-mail adresse.",
"Share with other people by entering a user or group or a federated cloud ID." : "Del med andre ved at indtaste et brugernavn, en gruppe eller et federated cloud id.",
"Share with other people by entering a user or group or an email address." : "Del med andre ved at indtaste et brugernavn, en gruppe eller e-mail adresse.",

View File

@ -167,6 +167,7 @@ OC.L10N.register(
"Share to {name}" : "Mit {name} teilen",
"Copy link" : "Link kopieren",
"Link" : "Link",
"Hide download" : "Download verbergen",
"Password protect" : "Passwortschutz",
"Allow editing" : "Bearbeitung erlauben",
"Email link to person" : "Link per E-Mail verschicken",
@ -210,12 +211,10 @@ OC.L10N.register(
"No users found for {search}" : "Keine Benutzer für {search} gefunden",
"An error occurred (\"{message}\"). Please try again" : "Benötigt keine Übersetzung. Für iOS wird nur die formelle Übersetzung verwendet (de_DE). ",
"An error occurred. Please try again" : "Es ist ein Fehler aufgetreten. Bitte versuche es noch einmal",
"{sharee} (group)" : "{sharee} (Gruppe)",
"{sharee} (remote)" : "{sharee} (remote)",
"{sharee} (remote group)" : "{sharee} (externe Gruppe)",
"{sharee} (email)" : "{sharee} (E-Mail)",
"{sharee} ({type}, {owner})" : "{sharee} ({type}, {owner})",
"{sharee} (conversation)" : "{sharee} (conversation)",
"Home" : "Start",
"Other" : "Andere",
"Share" : "Teilen",
"Name or email address..." : "Name oder E-Mail-Adresse…",
"Name or federated cloud ID..." : "Name oder Federated-Cloud-ID…",
@ -382,6 +381,9 @@ OC.L10N.register(
"Error setting expiration date" : "Fehler beim Setzen des Ablaufdatums",
"The public link will expire no later than {days} days after it is created" : "Der öffentliche Link wird spätestens {days} Tage nach seiner Erstellung ablaufen",
"{{shareInitiatorDisplayName}} shared via link" : "{{shareInitiatorDisplayName}} mittels Link geteilt",
"{sharee} (group)" : "{sharee} (Gruppe)",
"{sharee} (remote)" : "{sharee} (remote)",
"{sharee} (email)" : "{sharee} (E-Mail)",
"Share with other people by entering a user or group, a federated cloud ID or an email address." : "Teile mit Anderen, indem Du einen Benutzer, eine Gruppe, eine Federated-Cloud-ID oder eine E-Mail-Adressen eingibst.",
"Share with other people by entering a user or group or a federated cloud ID." : "Teile mit Anderen, indem Du einen Benutzer, eine Gruppe, oder eine Federated-Cloud-ID eingibst.",
"Share with other people by entering a user or group or an email address." : "Teile mit Anderen, indem Du einen Benutzer, eine Gruppe, oder eine E-Mail-Adresse eingibst.",
@ -413,6 +415,7 @@ OC.L10N.register(
"You are about to grant %s access to your %s account." : "Du bist dabei, %s Zugriff auf Dein %s-Konto zu gewähren.",
"Depending on your configuration, this button could also work to trust the domain:" : "Abhängig von Deiner Konfiguration kann diese Schaltfläche verwandt werden, um die Domain als vertrauenswürdig einzustufen:",
"Copy URL" : "URL kopieren",
"{sharee} (conversation)" : "{sharee} (conversation)",
"Please log in before granting %s access to your %s account." : "Bitte anmelden, bevor Du %s Zugriff auf Dein %s-Konto gewährst.",
"Further information how to configure this can be found in the %sdocumentation%s." : "Weitere Informationen zur Konfiguration findest du in der %sDokumentation%s."
},

View File

@ -165,6 +165,7 @@
"Share to {name}" : "Mit {name} teilen",
"Copy link" : "Link kopieren",
"Link" : "Link",
"Hide download" : "Download verbergen",
"Password protect" : "Passwortschutz",
"Allow editing" : "Bearbeitung erlauben",
"Email link to person" : "Link per E-Mail verschicken",
@ -208,12 +209,10 @@
"No users found for {search}" : "Keine Benutzer für {search} gefunden",
"An error occurred (\"{message}\"). Please try again" : "Benötigt keine Übersetzung. Für iOS wird nur die formelle Übersetzung verwendet (de_DE). ",
"An error occurred. Please try again" : "Es ist ein Fehler aufgetreten. Bitte versuche es noch einmal",
"{sharee} (group)" : "{sharee} (Gruppe)",
"{sharee} (remote)" : "{sharee} (remote)",
"{sharee} (remote group)" : "{sharee} (externe Gruppe)",
"{sharee} (email)" : "{sharee} (E-Mail)",
"{sharee} ({type}, {owner})" : "{sharee} ({type}, {owner})",
"{sharee} (conversation)" : "{sharee} (conversation)",
"Home" : "Start",
"Other" : "Andere",
"Share" : "Teilen",
"Name or email address..." : "Name oder E-Mail-Adresse…",
"Name or federated cloud ID..." : "Name oder Federated-Cloud-ID…",
@ -380,6 +379,9 @@
"Error setting expiration date" : "Fehler beim Setzen des Ablaufdatums",
"The public link will expire no later than {days} days after it is created" : "Der öffentliche Link wird spätestens {days} Tage nach seiner Erstellung ablaufen",
"{{shareInitiatorDisplayName}} shared via link" : "{{shareInitiatorDisplayName}} mittels Link geteilt",
"{sharee} (group)" : "{sharee} (Gruppe)",
"{sharee} (remote)" : "{sharee} (remote)",
"{sharee} (email)" : "{sharee} (E-Mail)",
"Share with other people by entering a user or group, a federated cloud ID or an email address." : "Teile mit Anderen, indem Du einen Benutzer, eine Gruppe, eine Federated-Cloud-ID oder eine E-Mail-Adressen eingibst.",
"Share with other people by entering a user or group or a federated cloud ID." : "Teile mit Anderen, indem Du einen Benutzer, eine Gruppe, oder eine Federated-Cloud-ID eingibst.",
"Share with other people by entering a user or group or an email address." : "Teile mit Anderen, indem Du einen Benutzer, eine Gruppe, oder eine E-Mail-Adresse eingibst.",
@ -411,6 +413,7 @@
"You are about to grant %s access to your %s account." : "Du bist dabei, %s Zugriff auf Dein %s-Konto zu gewähren.",
"Depending on your configuration, this button could also work to trust the domain:" : "Abhängig von Deiner Konfiguration kann diese Schaltfläche verwandt werden, um die Domain als vertrauenswürdig einzustufen:",
"Copy URL" : "URL kopieren",
"{sharee} (conversation)" : "{sharee} (conversation)",
"Please log in before granting %s access to your %s account." : "Bitte anmelden, bevor Du %s Zugriff auf Dein %s-Konto gewährst.",
"Further information how to configure this can be found in the %sdocumentation%s." : "Weitere Informationen zur Konfiguration findest du in der %sDokumentation%s."
},"pluralForm" :"nplurals=2; plural=(n != 1);"

View File

@ -167,6 +167,7 @@ OC.L10N.register(
"Share to {name}" : "Mit {name} teilen",
"Copy link" : "Link kopieren",
"Link" : "Link",
"Hide download" : "Download verbergen",
"Password protect" : "Passwortschutz",
"Allow editing" : "Bearbeitung erlauben",
"Email link to person" : "Link per E-Mail verschicken",
@ -210,12 +211,10 @@ OC.L10N.register(
"No users found for {search}" : "Keine Benutzer für {search} gefunden",
"An error occurred (\"{message}\"). Please try again" : "Es ist ein Fehler aufgetreten (\"{message}\"). Bitte erneut versuchen.",
"An error occurred. Please try again" : "Es ist ein Fehler aufgetreten. Bitte versuchen Sie es noch einmal",
"{sharee} (group)" : "{sharee} (Gruppe)",
"{sharee} (remote)" : "{sharee} (remote)",
"{sharee} (remote group)" : "{sharee} (Externe Gruppe)",
"{sharee} (email)" : "{sharee} (E-Mail)",
"{sharee} ({type}, {owner})" : "{sharee} ({type}, {owner})",
"{sharee} (conversation)" : "{sharee} (conversation)",
"Home" : "Start",
"Other" : "Andere",
"Share" : "Teilen",
"Name or email address..." : "Name oder E-Mail-Adresse…",
"Name or federated cloud ID..." : "Name oder Federated-Cloud-ID…",
@ -382,6 +381,9 @@ OC.L10N.register(
"Error setting expiration date" : "Fehler beim Setzen des Ablaufdatums",
"The public link will expire no later than {days} days after it is created" : "Der öffentliche Link wird spätestens {days} Tage nach seiner Erstellung ablaufen",
"{{shareInitiatorDisplayName}} shared via link" : "{{shareInitiatorDisplayName}} mittels Link geteilt",
"{sharee} (group)" : "{sharee} (Gruppe)",
"{sharee} (remote)" : "{sharee} (remote)",
"{sharee} (email)" : "{sharee} (E-Mail)",
"Share with other people by entering a user or group, a federated cloud ID or an email address." : "Teilen mit Anderen, indem Sie einen Benutzer, eine Gruppe, eine Federated-Cloud-ID oder eine E-Mail-Adresse eingeben.",
"Share with other people by entering a user or group or a federated cloud ID." : "Teilen mit Anderen, indem Sie einen Benutzer, eine Gruppe, oder eine Federated-Cloud-ID eingeben.",
"Share with other people by entering a user or group or an email address." : "Teilen mit Anderen, indem Sie einen Benutzer, eine Gruppe, oder eine E-Mail-Adresse eingeben.",
@ -413,6 +415,7 @@ OC.L10N.register(
"You are about to grant %s access to your %s account." : "Sie sind dabei, %s Zugriff auf Ihr %s-Konto zu gewähren.",
"Depending on your configuration, this button could also work to trust the domain:" : "Abhängig von Ihrer Konfiguration kann diese Schaltfläche verwandt werden, um die Domain als vertrauenswürdig einzustufen:",
"Copy URL" : "URL kopieren",
"{sharee} (conversation)" : "{sharee} (conversation)",
"Please log in before granting %s access to your %s account." : "Bitte anmelden, bevor Du %s Zugriff auf Dein %s-Konto gewährst.",
"Further information how to configure this can be found in the %sdocumentation%s." : "Weitere Informationen zur Konfiguration finden Sie in der %sDokumentation%s."
},

View File

@ -165,6 +165,7 @@
"Share to {name}" : "Mit {name} teilen",
"Copy link" : "Link kopieren",
"Link" : "Link",
"Hide download" : "Download verbergen",
"Password protect" : "Passwortschutz",
"Allow editing" : "Bearbeitung erlauben",
"Email link to person" : "Link per E-Mail verschicken",
@ -208,12 +209,10 @@
"No users found for {search}" : "Keine Benutzer für {search} gefunden",
"An error occurred (\"{message}\"). Please try again" : "Es ist ein Fehler aufgetreten (\"{message}\"). Bitte erneut versuchen.",
"An error occurred. Please try again" : "Es ist ein Fehler aufgetreten. Bitte versuchen Sie es noch einmal",
"{sharee} (group)" : "{sharee} (Gruppe)",
"{sharee} (remote)" : "{sharee} (remote)",
"{sharee} (remote group)" : "{sharee} (Externe Gruppe)",
"{sharee} (email)" : "{sharee} (E-Mail)",
"{sharee} ({type}, {owner})" : "{sharee} ({type}, {owner})",
"{sharee} (conversation)" : "{sharee} (conversation)",
"Home" : "Start",
"Other" : "Andere",
"Share" : "Teilen",
"Name or email address..." : "Name oder E-Mail-Adresse…",
"Name or federated cloud ID..." : "Name oder Federated-Cloud-ID…",
@ -380,6 +379,9 @@
"Error setting expiration date" : "Fehler beim Setzen des Ablaufdatums",
"The public link will expire no later than {days} days after it is created" : "Der öffentliche Link wird spätestens {days} Tage nach seiner Erstellung ablaufen",
"{{shareInitiatorDisplayName}} shared via link" : "{{shareInitiatorDisplayName}} mittels Link geteilt",
"{sharee} (group)" : "{sharee} (Gruppe)",
"{sharee} (remote)" : "{sharee} (remote)",
"{sharee} (email)" : "{sharee} (E-Mail)",
"Share with other people by entering a user or group, a federated cloud ID or an email address." : "Teilen mit Anderen, indem Sie einen Benutzer, eine Gruppe, eine Federated-Cloud-ID oder eine E-Mail-Adresse eingeben.",
"Share with other people by entering a user or group or a federated cloud ID." : "Teilen mit Anderen, indem Sie einen Benutzer, eine Gruppe, oder eine Federated-Cloud-ID eingeben.",
"Share with other people by entering a user or group or an email address." : "Teilen mit Anderen, indem Sie einen Benutzer, eine Gruppe, oder eine E-Mail-Adresse eingeben.",
@ -411,6 +413,7 @@
"You are about to grant %s access to your %s account." : "Sie sind dabei, %s Zugriff auf Ihr %s-Konto zu gewähren.",
"Depending on your configuration, this button could also work to trust the domain:" : "Abhängig von Ihrer Konfiguration kann diese Schaltfläche verwandt werden, um die Domain als vertrauenswürdig einzustufen:",
"Copy URL" : "URL kopieren",
"{sharee} (conversation)" : "{sharee} (conversation)",
"Please log in before granting %s access to your %s account." : "Bitte anmelden, bevor Du %s Zugriff auf Dein %s-Konto gewährst.",
"Further information how to configure this can be found in the %sdocumentation%s." : "Weitere Informationen zur Konfiguration finden Sie in der %sDokumentation%s."
},"pluralForm" :"nplurals=2; plural=(n != 1);"

Some files were not shown because too many files have changed in this diff Show More