diff --git a/.scrutinizer.yml b/.scrutinizer.yml index fa05b9b8a4..d1dbb20139 100644 --- a/.scrutinizer.yml +++ b/.scrutinizer.yml @@ -8,7 +8,9 @@ filter: - 'lib/l10n/*' - 'core/js/tests/lib/*.js' - 'core/js/tests/specs/*.js' + - 'core/js/jquery-1.10.0.js' - 'core/js/jquery-1.10.0.min.js' + - 'core/js/jquery-migrate-1.2.1.js' - 'core/js/jquery-migrate-1.2.1.min.js' - 'core/js/jquery-showpassword.js' - 'core/js/jquery-tipsy.js' diff --git a/apps/files/ajax/upload.php b/apps/files/ajax/upload.php index b21a9dfba2..a5ce7b257d 100644 --- a/apps/files/ajax/upload.php +++ b/apps/files/ajax/upload.php @@ -111,22 +111,32 @@ if ($maxUploadFileSize >= 0 and $totalSize > $maxUploadFileSize) { } $result = array(); +$directory = ''; if (strpos($dir, '..') === false) { $fileCount = count($files['name']); for ($i = 0; $i < $fileCount; $i++) { + + // Get the files directory + if(isset($_POST['file_directory']) === true) { + $directory = '/'.$_POST['file_directory']; + } + // $path needs to be normalized - this failed within drag'n'drop upload to a sub-folder if (isset($_POST['resolution']) && $_POST['resolution']==='autorename') { // append a number in brackets like 'filename (2).ext' - $target = OCP\Files::buildNotExistingFileName(stripslashes($dir), $files['name'][$i]); + $target = OCP\Files::buildNotExistingFileName(stripslashes($dir.$directory), $files['name'][$i]); } else { - $target = \OC\Files\Filesystem::normalizePath(stripslashes($dir).'/'.$files['name'][$i]); + $target = \OC\Files\Filesystem::normalizePath(stripslashes($dir.$directory).'/'.$files['name'][$i]); } - - $directory = \OC\Files\Filesystem::normalizePath(stripslashes($dir)); - if (isset($public_directory)) { - // If we are uploading from the public app, - // we want to send the relative path in the ajax request. - $directory = $public_directory; + + if(empty($directory) === true) + { + $directory = \OC\Files\Filesystem::normalizePath(stripslashes($dir)); + if (isset($public_directory)) { + // If we are uploading from the public app, + // we want to send the relative path in the ajax request. + $directory = $public_directory; + } } if ( ! \OC\Files\Filesystem::file_exists($target) @@ -186,7 +196,6 @@ if (strpos($dir, '..') === false) { if ($error === false) { OCP\JSON::encodedPrint($result); - exit(); } else { OCP\JSON::error(array(array('data' => array_merge(array('message' => $error, 'code' => $errorCode), $storageStats)))); } diff --git a/apps/files/appinfo/remote.php b/apps/files/appinfo/remote.php index c70db0b5ae..c1baee4f1e 100644 --- a/apps/files/appinfo/remote.php +++ b/apps/files/appinfo/remote.php @@ -28,11 +28,8 @@ $authBackend = new OC_Connector_Sabre_Auth(); $lockBackend = new OC_Connector_Sabre_Locks(); $requestBackend = new OC_Connector_Sabre_Request(); -// Create ownCloud Dir -$rootDir = new OC_Connector_Sabre_Directory(''); -$objectTree = new \OC\Connector\Sabre\ObjectTree($rootDir); - // Fire up server +$objectTree = new \OC\Connector\Sabre\ObjectTree(); $server = new OC_Connector_Sabre_Server($objectTree); $server->httpRequest = $requestBackend; $server->setBaseUri($baseuri); @@ -43,10 +40,21 @@ $server->addPlugin(new Sabre_DAV_Auth_Plugin($authBackend, $defaults->getName()) $server->addPlugin(new Sabre_DAV_Locks_Plugin($lockBackend)); $server->addPlugin(new Sabre_DAV_Browser_Plugin(false)); $server->addPlugin(new OC_Connector_Sabre_FilesPlugin()); -$server->addPlugin(new OC_Connector_Sabre_AbortedUploadDetectionPlugin()); -$server->addPlugin(new OC_Connector_Sabre_QuotaPlugin()); $server->addPlugin(new OC_Connector_Sabre_MaintenancePlugin()); $server->addPlugin(new OC_Connector_Sabre_ExceptionLoggerPlugin('webdav')); +// wait with registering these until auth is handled and the filesystem is setup +$server->subscribeEvent('beforeMethod', function () use ($server, $objectTree) { + $view = \OC\Files\Filesystem::getView(); + $rootInfo = $view->getFileInfo(''); + + // Create ownCloud Dir + $rootDir = new OC_Connector_Sabre_Directory($view, $rootInfo); + $objectTree->init($rootDir, $view); + + $server->addPlugin(new OC_Connector_Sabre_AbortedUploadDetectionPlugin($view)); + $server->addPlugin(new OC_Connector_Sabre_QuotaPlugin($view)); +}, 30); // priority 30: after auth (10) and acl(20), before lock(50) and handling the request + // And off we go! $server->exec(); diff --git a/apps/files/css/files.css b/apps/files/css/files.css index 1bac5d2b7d..474f1af072 100644 --- a/apps/files/css/files.css +++ b/apps/files/css/files.css @@ -51,7 +51,7 @@ margin: 5px; padding-left: 42px; padding-bottom: 2px; - background-position: initial; + background-position: left center; cursor: pointer; } #new > ul > li > p { diff --git a/apps/files/js/file-upload.js b/apps/files/js/file-upload.js index e5d1eacbd1..03ebdccb32 100644 --- a/apps/files/js/file-upload.js +++ b/apps/files/js/file-upload.js @@ -235,18 +235,26 @@ OC.Upload = { var file = data.files[0]; try { // FIXME: not so elegant... need to refactor that method to return a value - Files.isFileNameValid(file.name, FileList.getCurrentDirectory()); + Files.isFileNameValid(file.name); } catch (errorMessage) { data.textStatus = 'invalidcharacters'; data.errorThrown = errorMessage; } - if (file.type === '' && file.size === 4096) { - data.textStatus = 'dirorzero'; - data.errorThrown = t('files', 'Unable to upload {filename} as it is a directory or has 0 bytes', - {filename: file.name} - ); + // in case folder drag and drop is not supported file will point to a directory + // http://stackoverflow.com/a/20448357 + if (!file.type && file.size%4096 === 0 && file.size <= 102400) { + try { + reader = new FileReader(); + reader.readAsBinaryString(f); + } catch (NS_ERROR_FILE_ACCESS_DENIED) { + //file is a directory + data.textStatus = 'dirorzero'; + data.errorThrown = t('files', 'Unable to upload {filename} as it is a directory or has 0 bytes', + {filename: file.name} + ); + } } // add size @@ -326,10 +334,15 @@ OC.Upload = { submit: function(e, data) { OC.Upload.rememberUpload(data); if ( ! data.formData ) { + var fileDirectory = ''; + if(typeof data.files[0].relativePath !== 'undefined') { + fileDirectory = data.files[0].relativePath; + } // noone set update parameters, we set the minimum data.formData = { requesttoken: oc_requesttoken, - dir: $('#dir').val() + dir: $('#dir').val(), + file_directory: fileDirectory }; } }, @@ -542,8 +555,6 @@ OC.Upload = { throw t('files', 'URL cannot be empty'); } else if (type !== 'web' && !Files.isFileNameValid(filename)) { // Files.isFileNameValid(filename) throws an exception itself - } else if (FileList.getCurrentDirectory() === '/' && filename.toLowerCase() === 'shared') { - throw t('files', 'In the home folder \'Shared\' is a reserved filename'); } else if (FileList.inList(filename)) { throw t('files', '{new_name} already exists', {new_name: filename}); } else { @@ -683,3 +694,4 @@ $(document).ready(function() { OC.Upload.init(); }); + diff --git a/apps/files/js/fileactions.js b/apps/files/js/fileactions.js index 631aebea95..ecdfa72a47 100644 --- a/apps/files/js/fileactions.js +++ b/apps/files/js/fileactions.js @@ -118,10 +118,6 @@ var FileActions = { }; var addAction = function (name, action, displayName) { - // NOTE: Temporary fix to prevent rename action in root of Shared directory - if (name === 'Rename' && $('#dir').val() === '/Shared') { - return true; - } if ((name === 'Download' || action !== defaultAction) && name !== 'Delete') { @@ -160,7 +156,7 @@ var FileActions = { addAction(name, ah, displayName); } }); - if(actions.Share && !($('#dir').val() === '/' && file === 'Shared')){ + if(actions.Share){ displayName = t('files', 'Share'); addAction('Share', actions.Share, displayName); } @@ -223,7 +219,7 @@ $(document).ready(function () { $('#fileList tr').each(function () { FileActions.display($(this).children('td.filename')); }); - + $('#fileList').trigger(jQuery.Event("fileActionsReady")); }); diff --git a/apps/files/js/filelist.js b/apps/files/js/filelist.js index 506741eb6e..c33b638b5a 100644 --- a/apps/files/js/filelist.js +++ b/apps/files/js/filelist.js @@ -9,7 +9,7 @@ */ /* global OC, t, n, FileList, FileActions, Files, BreadCrumb */ -/* global procesSelection, dragOptions */ +/* global procesSelection, dragOptions, folderDropOptions */ window.FileList = { appName: t('files', 'Files'), isEmpty: true, @@ -178,6 +178,13 @@ window.FileList = { if (type === 'dir') { mime = mime || 'httpd/unix-directory'; } + + // user should always be able to rename a share mount point + var allowRename = 0; + if (fileData.isShareMountPoint) { + allowRename = OC.PERMISSION_UPDATE; + } + //containing tr var tr = $('').attr({ "data-id" : fileData.id, @@ -187,7 +194,7 @@ window.FileList = { "data-mime": mime, "data-mtime": mtime, "data-etag": fileData.etag, - "data-permissions": fileData.permissions || this.getDirectoryPermissions() + "data-permissions": fileData.permissions | allowRename || this.getDirectoryPermissions() }); if (type === 'dir') { @@ -212,7 +219,7 @@ window.FileList = { linkUrl = Files.getDownloadUrl(name, FileList.getCurrentDirectory()); } td.append(''); - var link_elem = $('').attr({ + var linkElem = $('').attr({ "class": "name", "href": linkUrl }); @@ -228,19 +235,19 @@ window.FileList = { basename = name; extension = false; } - var name_span=$('').addClass('nametext').text(basename); - link_elem.append(name_span); + var nameSpan=$('').addClass('nametext').text(basename); + linkElem.append(nameSpan); if (extension) { - name_span.append($('').addClass('extension').text(extension)); + nameSpan.append($('').addClass('extension').text(extension)); } // dirs can show the number of uploaded files if (type === 'dir') { - link_elem.append($('').attr({ + linkElem.append($('').attr({ 'class': 'uploadtext', 'currentUploads': 0 })); } - td.append(link_elem); + td.append(linkElem); tr.append(td); // size column @@ -250,7 +257,7 @@ window.FileList = { } else { simpleSize = t('files', 'Pending'); } - var lastModifiedTime = Math.round(mtime / 1000); + td = $('').attr({ "class": "filesize", "style": 'color:rgb(' + sizeColor + ',' + sizeColor + ',' + sizeColor + ')' @@ -283,6 +290,10 @@ window.FileList = { mime = fileData.mimetype, permissions = parseInt(fileData.permissions, 10) || 0; + if (fileData.isShareMountPoint) { + permissions = permissions | OC.PERMISSION_UPDATE; + } + if (type === 'dir') { mime = mime || 'httpd/unix-directory'; } @@ -437,8 +448,6 @@ window.FileList = { }); }, reloadCallback: function(result) { - var $controls = $('#controls'); - delete this._reloadCall; this.hideMask(); @@ -574,7 +583,8 @@ window.FileList = { input.focus(); //preselect input var len = input.val().lastIndexOf('.'); - if (len === -1) { + if ( len === -1 || + tr.data('type') === 'dir' ) { len = input.val().length; } input.selectRange(0, len); @@ -582,7 +592,7 @@ window.FileList = { var filename = input.val(); if (filename !== oldname) { // Files.isFileNameValid(filename) throws an exception itself - Files.isFileNameValid(filename, FileList.getCurrentDirectory()); + Files.isFileNameValid(filename); if (FileList.inList(filename)) { throw t('files', '{new_name} already exists', {new_name: filename}); } @@ -810,10 +820,9 @@ window.FileList = { var info = t('files', '{dirs} and {files}', infoVars); // don't show the filesize column, if filesize is NaN (e.g. in trashbin) - if (isNaN(summary.totalSize)) { - var fileSize = ''; - } else { - var fileSize = ''+humanFileSize(summary.totalSize)+''; + var fileSize = ''; + if (!isNaN(summary.totalSize)) { + fileSize = ''+humanFileSize(summary.totalSize)+''; } var $summary = $(''+info+''+fileSize+''); @@ -899,7 +908,6 @@ window.FileList = { } }, updateEmptyContent: function() { - var $fileList = $('#fileList'); var permissions = $('#permissions').val(); var isCreatable = (permissions & OC.PERMISSION_CREATE) !== 0; $('#emptycontent').toggleClass('hidden', !isCreatable || !FileList.isEmpty); @@ -937,13 +945,13 @@ window.FileList = { }, scrollTo:function(file) { //scroll to and highlight preselected file - var $scrolltorow = FileList.findFileEl(file); - if ($scrolltorow.exists()) { - $scrolltorow.addClass('searchresult'); - $(window).scrollTop($scrolltorow.position().top); + var $scrollToRow = FileList.findFileEl(file); + if ($scrollToRow.exists()) { + $scrollToRow.addClass('searchresult'); + $(window).scrollTop($scrollToRow.position().top); //remove highlight when hovered over - $scrolltorow.one('hover', function() { - $scrolltorow.removeClass('searchresult'); + $scrollToRow.one('hover', function() { + $scrollToRow.removeClass('searchresult'); }); } }, @@ -979,9 +987,9 @@ $(document).ready(function() { FileList.initialize(); // handle upload events - var file_upload_start = $('#file_upload_start'); + var fileUploadStart = $('#file_upload_start'); - file_upload_start.on('fileuploaddrop', function(e, data) { + fileUploadStart.on('fileuploaddrop', function(e, data) { OC.Upload.log('filelist handle fileuploaddrop', e, data); var dropTarget = $(e.originalEvent.target).closest('tr, .crumb'); @@ -1008,7 +1016,8 @@ $(document).ready(function() { data.formData = function(form) { return [ {name: 'dir', value: dir}, - {name: 'requesttoken', value: oc_requesttoken} + {name: 'requesttoken', value: oc_requesttoken}, + {name: 'file_directory', value: data.files[0].relativePath} ]; }; } else { @@ -1019,7 +1028,7 @@ $(document).ready(function() { } } }); - file_upload_start.on('fileuploadadd', function(e, data) { + fileUploadStart.on('fileuploadadd', function(e, data) { OC.Upload.log('filelist handle fileuploadadd', e, data); //finish delete if we are uploading a deleted file @@ -1032,19 +1041,19 @@ $(document).ready(function() { // add to existing folder // update upload counter ui - var uploadtext = data.context.find('.uploadtext'); - var currentUploads = parseInt(uploadtext.attr('currentUploads')); + var uploadText = data.context.find('.uploadtext'); + var currentUploads = parseInt(uploadText.attr('currentUploads'), 10); currentUploads += 1; - uploadtext.attr('currentUploads', currentUploads); + uploadText.attr('currentUploads', currentUploads); var translatedText = n('files', 'Uploading %n file', 'Uploading %n files', currentUploads); if (currentUploads === 1) { var img = OC.imagePath('core', 'loading.gif'); data.context.find('td.filename').attr('style','background-image:url('+img+')'); - uploadtext.text(translatedText); - uploadtext.show(); + uploadText.text(translatedText); + uploadText.show(); } else { - uploadtext.text(translatedText); + uploadText.text(translatedText); } } @@ -1053,7 +1062,7 @@ $(document).ready(function() { * when file upload done successfully add row to filelist * update counter when uploading to sub folder */ - file_upload_start.on('fileuploaddone', function(e, data) { + fileUploadStart.on('fileuploaddone', function(e, data) { OC.Upload.log('filelist handle fileuploaddone', e, data); var response; @@ -1067,38 +1076,69 @@ $(document).ready(function() { if (typeof result[0] !== 'undefined' && result[0].status === 'success') { var file = result[0]; + var size = 0; if (data.context && data.context.data('type') === 'dir') { // update upload counter ui - var uploadtext = data.context.find('.uploadtext'); - var currentUploads = parseInt(uploadtext.attr('currentUploads')); + var uploadText = data.context.find('.uploadtext'); + var currentUploads = parseInt(uploadText.attr('currentUploads'), 10); currentUploads -= 1; - uploadtext.attr('currentUploads', currentUploads); + uploadText.attr('currentUploads', currentUploads); var translatedText = n('files', 'Uploading %n file', 'Uploading %n files', currentUploads); if (currentUploads === 0) { var img = OC.imagePath('core', 'filetypes/folder'); data.context.find('td.filename').attr('style','background-image:url('+img+')'); - uploadtext.text(translatedText); - uploadtext.hide(); + uploadText.text(translatedText); + uploadText.hide(); } else { - uploadtext.text(translatedText); + uploadText.text(translatedText); } // update folder size - var size = parseInt(data.context.data('size')); - size += parseInt(file.size); + size = parseInt(data.context.data('size'), 10); + size += parseInt(file.size, 10); data.context.attr('data-size', size); data.context.find('td.filesize').text(humanFileSize(size)); - } else { // only append new file if uploaded into the current folder - if (file.directory !== FileList.getCurrentDirectory()) { + if (file.directory !== '/' && file.directory !== FileList.getCurrentDirectory()) { + + var fileDirectory = file.directory.replace('/','').replace(/\/$/, "").split('/'); + + if (fileDirectory.length === 1) { + fileDirectory = fileDirectory[0]; + + // Get the directory + var fd = FileList.findFileEl(fileDirectory); + if (fd.length === 0) { + var dir = { + name: fileDirectory, + type: 'dir', + mimetype: 'httpd/unix-directory', + permissions: file.permissions, + size: 0, + id: file.parentId + }; + FileList.add(dir, {insert: true}); + } + } else { + fileDirectory = fileDirectory[0]; + } + + fileDirectory = FileList.findFileEl(fileDirectory); + + // update folder size + size = parseInt(fileDirectory.attr('data-size'), 10); + size += parseInt(file.size, 10); + fileDirectory.attr('data-size', size); + fileDirectory.find('td.filesize').text(humanFileSize(size)); + return; } // add as stand-alone row to filelist - var size=t('files', 'Pending'); + size = t('files', 'Pending'); if (data.files[0].size>=0) { size=data.files[0].size; } @@ -1110,37 +1150,40 @@ $(document).ready(function() { } } }); - file_upload_start.on('fileuploadstop', function(e, data) { + fileUploadStart.on('fileuploadstop', function(e, data) { OC.Upload.log('filelist handle fileuploadstop', e, data); //if user pressed cancel hide upload chrome if (data.errorThrown === 'abort') { //cleanup uploading to a dir - var uploadtext = $('tr .uploadtext'); + var uploadText = $('tr .uploadtext'); var img = OC.imagePath('core', 'filetypes/folder'); - uploadtext.parents('td.filename').attr('style','background-image:url('+img+')'); - uploadtext.fadeOut(); - uploadtext.attr('currentUploads', 0); + uploadText.parents('td.filename').attr('style','background-image:url('+img+')'); + uploadText.fadeOut(); + uploadText.attr('currentUploads', 0); } }); - file_upload_start.on('fileuploadfail', function(e, data) { + fileUploadStart.on('fileuploadfail', function(e, data) { OC.Upload.log('filelist handle fileuploadfail', e, data); //if user pressed cancel hide upload chrome if (data.errorThrown === 'abort') { //cleanup uploading to a dir - var uploadtext = $('tr .uploadtext'); + var uploadText = $('tr .uploadtext'); var img = OC.imagePath('core', 'filetypes/folder'); - uploadtext.parents('td.filename').attr('style','background-image:url('+img+')'); - uploadtext.fadeOut(); - uploadtext.attr('currentUploads', 0); + uploadText.parents('td.filename').attr('style','background-image:url('+img+')'); + uploadText.fadeOut(); + uploadText.attr('currentUploads', 0); } }); $('#notification').hide(); $('#notification:first-child').on('click', '.replace', function() { OC.Notification.hide(function() { - FileList.replace($('#notification > span').attr('data-oldName'), $('#notification > span').attr('data-newName'), $('#notification > span').attr('data-isNewFile')); + FileList.replace( + $('#notification > span').attr('data-oldName'), + $('#notification > span').attr('data-newName'), + $('#notification > span').attr('data-isNewFile')); }); }); $('#notification:first-child').on('click', '.suggest', function() { @@ -1170,8 +1213,7 @@ $(document).ready(function() { function parseHashQuery() { var hash = window.location.hash, - pos = hash.indexOf('?'), - query; + pos = hash.indexOf('?'); if (pos >= 0) { return hash.substr(pos + 1); } @@ -1180,8 +1222,7 @@ $(document).ready(function() { function parseCurrentDirFromUrl() { var query = parseHashQuery(), - params, - dir = '/'; + params; // try and parse from URL hash first if (query) { params = OC.parseQueryString(decodeQuery(query)); diff --git a/apps/files/js/files.js b/apps/files/js/files.js index ac10191618..5e669a796a 100644 --- a/apps/files/js/files.js +++ b/apps/files/js/files.js @@ -87,11 +87,9 @@ var Files = { * Throws a string exception with an error message if * the file name is not valid */ - isFileNameValid: function (name, root) { + isFileNameValid: function (name) { var trimmedName = name.trim(); - if (trimmedName === '.' - || trimmedName === '..' - || (root === '/' && trimmedName.toLowerCase() === 'shared')) + if (trimmedName === '.' || trimmedName === '..') { throw t('files', '"{name}" is an invalid file name.', {name: name}); } else if (trimmedName.length === 0) { @@ -135,7 +133,7 @@ var Files = { return; } if (initStatus === '1') { // encryption tried to init but failed - OC.Notification.showHtml(t('files', 'Invalid private key for Encryption App. Please update your private key password in your personal settings to recover access to your encrypted files.')); + OC.Notification.show(t('files', 'Invalid private key for Encryption App. Please update your private key password in your personal settings to recover access to your encrypted files.')); return; } if (encryptedFiles === '1') { diff --git a/apps/files/l10n/ar.php b/apps/files/l10n/ar.php index e7c081b1c4..9a78e3bbff 100644 --- a/apps/files/l10n/ar.php +++ b/apps/files/l10n/ar.php @@ -44,7 +44,6 @@ $TRANSLATIONS = array( "Size" => "حجم", "Modified" => "معدل", "%s could not be renamed" => "%s لا يمكن إعادة تسميته. ", -"Upload" => "رفع", "File handling" => "التعامل مع الملف", "Maximum upload size" => "الحد الأقصى لحجم الملفات التي يمكن رفعها", "max. possible: " => "الحد الأقصى المسموح به", diff --git a/apps/files/l10n/ast.php b/apps/files/l10n/ast.php index 93ae47b25c..5c6af60be3 100644 --- a/apps/files/l10n/ast.php +++ b/apps/files/l10n/ast.php @@ -19,9 +19,9 @@ $TRANSLATIONS = array( "_Uploading %n file_::_Uploading %n files_" => array("",""), "Name" => "Nome", "Size" => "Tamañu", -"Upload" => "Xubir", "Save" => "Guardar", "New folder" => "Nueva carpeta", +"Folder" => "Carpeta", "Cancel upload" => "Encaboxar xuba", "Download" => "Descargar", "Delete" => "Desaniciar" diff --git a/apps/files/l10n/bg_BG.php b/apps/files/l10n/bg_BG.php index 2418010cdd..d9cad21e79 100644 --- a/apps/files/l10n/bg_BG.php +++ b/apps/files/l10n/bg_BG.php @@ -20,7 +20,6 @@ $TRANSLATIONS = array( "Name" => "Име", "Size" => "Размер", "Modified" => "Променено", -"Upload" => "Качване", "Maximum upload size" => "Максимален размер за качване", "0 is unlimited" => "Ползвайте 0 за без ограничения", "Save" => "Запис", diff --git a/apps/files/l10n/bn_BD.php b/apps/files/l10n/bn_BD.php index 667a68bb62..4d71d7dacd 100644 --- a/apps/files/l10n/bn_BD.php +++ b/apps/files/l10n/bn_BD.php @@ -27,7 +27,6 @@ $TRANSLATIONS = array( "Name" => "রাম", "Size" => "আকার", "Modified" => "পরিবর্তিত", -"Upload" => "আপলোড", "File handling" => "ফাইল হ্যার্ডলিং", "Maximum upload size" => "আপলোডের সর্বোচ্চ আকার", "max. possible: " => "অনুমোদিত সর্বোচ্চ আকার", diff --git a/apps/files/l10n/ca.php b/apps/files/l10n/ca.php index 8ef9b76448..6938da220b 100644 --- a/apps/files/l10n/ca.php +++ b/apps/files/l10n/ca.php @@ -35,7 +35,6 @@ $TRANSLATIONS = array( "Could not get result from server." => "No hi ha resposta del servidor.", "File upload is in progress. Leaving the page now will cancel the upload." => "Hi ha una pujada en curs. Si abandoneu la pàgina la pujada es cancel·larà.", "URL cannot be empty" => "L'URL no pot ser buit", -"In the home folder 'Shared' is a reserved filename" => "A la carpeta inici 'Compartit' és un nom de fitxer reservat", "{new_name} already exists" => "{new_name} ja existeix", "Could not create file" => "No s'ha pogut crear el fitxer", "Could not create folder" => "No s'ha pogut crear la carpeta", @@ -62,9 +61,7 @@ $TRANSLATIONS = array( "Name" => "Nom", "Size" => "Mida", "Modified" => "Modificat", -"Invalid folder name. Usage of 'Shared' is reserved." => "Nom de carpeta no vàlid. L'ús de 'Shared' és reservat", "%s could not be renamed" => "%s no es pot canviar el nom", -"Upload" => "Puja", "File handling" => "Gestió de fitxers", "Maximum upload size" => "Mida màxima de pujada", "max. possible: " => "màxim possible:", diff --git a/apps/files/l10n/cs_CZ.php b/apps/files/l10n/cs_CZ.php index 8aea17a705..40bb288ca1 100644 --- a/apps/files/l10n/cs_CZ.php +++ b/apps/files/l10n/cs_CZ.php @@ -35,7 +35,6 @@ $TRANSLATIONS = array( "Could not get result from server." => "Nepodařilo se získat výsledek ze serveru.", "File upload is in progress. Leaving the page now will cancel the upload." => "Probíhá odesílání souboru. Opuštění stránky způsobí zrušení nahrávání.", "URL cannot be empty" => "URL nemůže zůstat prázdná", -"In the home folder 'Shared' is a reserved filename" => "V osobní složce je název 'Shared' rezervovaný", "{new_name} already exists" => "{new_name} již existuje", "Could not create file" => "Nepodařilo se vytvořit soubor", "Could not create folder" => "Nepodařilo se vytvořit složku", @@ -62,9 +61,8 @@ $TRANSLATIONS = array( "Name" => "Název", "Size" => "Velikost", "Modified" => "Upraveno", -"Invalid folder name. Usage of 'Shared' is reserved." => "Neplatný název složky. Použití 'Shared' je rezervováno.", "%s could not be renamed" => "%s nemůže být přejmenován", -"Upload" => "Odeslat", +"Upload (max. %s)" => "Nahrát (max. %s)", "File handling" => "Zacházení se soubory", "Maximum upload size" => "Maximální velikost pro odesílání", "max. possible: " => "největší možná: ", diff --git a/apps/files/l10n/cy_GB.php b/apps/files/l10n/cy_GB.php index b27e4c3bfc..f0c12b2fde 100644 --- a/apps/files/l10n/cy_GB.php +++ b/apps/files/l10n/cy_GB.php @@ -32,7 +32,6 @@ $TRANSLATIONS = array( "Name" => "Enw", "Size" => "Maint", "Modified" => "Addaswyd", -"Upload" => "Llwytho i fyny", "File handling" => "Trafod ffeiliau", "Maximum upload size" => "Maint mwyaf llwytho i fyny", "max. possible: " => "mwyaf. posib:", diff --git a/apps/files/l10n/da.php b/apps/files/l10n/da.php index 6a7ea4745c..9a6ea9c0dc 100644 --- a/apps/files/l10n/da.php +++ b/apps/files/l10n/da.php @@ -35,7 +35,6 @@ $TRANSLATIONS = array( "Could not get result from server." => "Kunne ikke hente resultat fra server.", "File upload is in progress. Leaving the page now will cancel the upload." => "Fil upload kører. Hvis du forlader siden nu, vil uploadet blive annuleret.", "URL cannot be empty" => "URL kan ikke være tom", -"In the home folder 'Shared' is a reserved filename" => "Navnet 'Shared' er reserveret i hjemmemappen.", "{new_name} already exists" => "{new_name} eksisterer allerede", "Could not create file" => "Kunne ikke oprette fil", "Could not create folder" => "Kunne ikke oprette mappe", @@ -62,9 +61,7 @@ $TRANSLATIONS = array( "Name" => "Navn", "Size" => "Størrelse", "Modified" => "Ændret", -"Invalid folder name. Usage of 'Shared' is reserved." => "Ugyldig mappenavn. 'Shared' er reserveret.", "%s could not be renamed" => "%s kunne ikke omdøbes", -"Upload" => "Upload", "File handling" => "Filhåndtering", "Maximum upload size" => "Maksimal upload-størrelse", "max. possible: " => "max. mulige: ", diff --git a/apps/files/l10n/de.php b/apps/files/l10n/de.php index 401ee243f2..1d15469dac 100644 --- a/apps/files/l10n/de.php +++ b/apps/files/l10n/de.php @@ -35,7 +35,6 @@ $TRANSLATIONS = array( "Could not get result from server." => "Ergebnis konnte nicht vom Server abgerufen werden.", "File upload is in progress. Leaving the page now will cancel the upload." => "Dateiupload läuft. Wenn Du die Seite jetzt verlässt, wird der Upload abgebrochen.", "URL cannot be empty" => "Die URL darf nicht leer sein", -"In the home folder 'Shared' is a reserved filename" => "Das Benutzerverzeichnis 'Shared' ist ein reservierter Dateiname", "{new_name} already exists" => "{new_name} existiert bereits", "Could not create file" => "Die Datei konnte nicht erstellt werden", "Could not create folder" => "Der Ordner konnte nicht erstellt werden", @@ -62,9 +61,8 @@ $TRANSLATIONS = array( "Name" => "Name", "Size" => "Größe", "Modified" => "Geändert", -"Invalid folder name. Usage of 'Shared' is reserved." => "Ungültiger Verzeichnisname. Die Nutzung von 'Shared' ist reserviert.", "%s could not be renamed" => "%s konnte nicht umbenannt werden", -"Upload" => "Hochladen", +"Upload (max. %s)" => "Hochladen (max. %s)", "File handling" => "Dateibehandlung", "Maximum upload size" => "Maximale Upload-Größe", "max. possible: " => "maximal möglich:", diff --git a/apps/files/l10n/de_CH.php b/apps/files/l10n/de_CH.php index f797be99e9..907b9e1b67 100644 --- a/apps/files/l10n/de_CH.php +++ b/apps/files/l10n/de_CH.php @@ -36,7 +36,6 @@ $TRANSLATIONS = array( "Size" => "Grösse", "Modified" => "Geändert", "%s could not be renamed" => "%s konnte nicht umbenannt werden", -"Upload" => "Hochladen", "File handling" => "Dateibehandlung", "Maximum upload size" => "Maximale Upload-Grösse", "max. possible: " => "maximal möglich:", diff --git a/apps/files/l10n/de_DE.php b/apps/files/l10n/de_DE.php index 4768faa97d..41ca83e6f8 100644 --- a/apps/files/l10n/de_DE.php +++ b/apps/files/l10n/de_DE.php @@ -35,7 +35,6 @@ $TRANSLATIONS = array( "Could not get result from server." => "Ergebnis konnte nicht vom Server abgerufen werden.", "File upload is in progress. Leaving the page now will cancel the upload." => "Dateiupload läuft. Wenn Sie die Seite jetzt verlassen, wird der Upload abgebrochen.", "URL cannot be empty" => "Die URL darf nicht leer sein", -"In the home folder 'Shared' is a reserved filename" => "Das Benutzerverzeichnis 'Shared' ist ein reservierter Dateiname", "{new_name} already exists" => "{new_name} existiert bereits", "Could not create file" => "Die Datei konnte nicht erstellt werden", "Could not create folder" => "Der Ordner konnte nicht erstellt werden", @@ -62,9 +61,8 @@ $TRANSLATIONS = array( "Name" => "Name", "Size" => "Größe", "Modified" => "Geändert", -"Invalid folder name. Usage of 'Shared' is reserved." => "Ungültiger Verzeichnisname. Die Nutzung von 'Shared' ist reserviert.", "%s could not be renamed" => "%s konnte nicht umbenannt werden", -"Upload" => "Hochladen", +"Upload (max. %s)" => "Hochladen (max. %s)", "File handling" => "Dateibehandlung", "Maximum upload size" => "Maximale Upload-Größe", "max. possible: " => "maximal möglich:", @@ -76,7 +74,7 @@ $TRANSLATIONS = array( "New" => "Neu", "New text file" => "Neue Textdatei", "Text file" => "Textdatei", -"New folder" => "Neues Ordner", +"New folder" => "Neuer Ordner", "Folder" => "Ordner", "From link" => "Von einem Link", "Deleted files" => "Gelöschte Dateien", diff --git a/apps/files/l10n/el.php b/apps/files/l10n/el.php index 713072d3e0..f103d0621e 100644 --- a/apps/files/l10n/el.php +++ b/apps/files/l10n/el.php @@ -35,7 +35,6 @@ $TRANSLATIONS = array( "Could not get result from server." => "Αδυναμία λήψης αποτελέσματος από το διακομιστή.", "File upload is in progress. Leaving the page now will cancel the upload." => "Η αποστολή του αρχείου βρίσκεται σε εξέλιξη. Το κλείσιμο της σελίδας θα ακυρώσει την αποστολή.", "URL cannot be empty" => "Η URL δεν πρέπει να είναι κενή", -"In the home folder 'Shared' is a reserved filename" => "Στον αρχικό φάκελο το όνομα 'Shared' διατηρείται από το σύστημα", "{new_name} already exists" => "{new_name} υπάρχει ήδη", "Could not create file" => "Αδυναμία δημιουργίας αρχείου", "Could not create folder" => "Αδυναμία δημιουργίας φακέλου", @@ -62,14 +61,12 @@ $TRANSLATIONS = array( "Name" => "Όνομα", "Size" => "Μέγεθος", "Modified" => "Τροποποιήθηκε", -"Invalid folder name. Usage of 'Shared' is reserved." => "Άκυρο όνομα φακέλου. Η χρήση του 'Shared' διατηρείται από το σύστημα.", "%s could not be renamed" => "Αδυναμία μετονομασίας του %s", -"Upload" => "Μεταφόρτωση", "File handling" => "Διαχείριση αρχείων", "Maximum upload size" => "Μέγιστο μέγεθος αποστολής", "max. possible: " => "μέγιστο δυνατό:", "Needed for multi-file and folder downloads." => "Απαραίτητο για κατέβασμα πολλαπλών αρχείων και φακέλων", -"Enable ZIP-download" => "Ενεργοποίηση κατεβάσματος ZIP", +"Enable ZIP-download" => "Επιτρέπεται η λήψη ZIP", "0 is unlimited" => "0 για απεριόριστο", "Maximum input size for ZIP files" => "Μέγιστο μέγεθος για αρχεία ZIP", "Save" => "Αποθήκευση", diff --git a/apps/files/l10n/en_GB.php b/apps/files/l10n/en_GB.php index 705f6b99b0..93e1007e37 100644 --- a/apps/files/l10n/en_GB.php +++ b/apps/files/l10n/en_GB.php @@ -35,7 +35,6 @@ $TRANSLATIONS = array( "Could not get result from server." => "Could not get result from server.", "File upload is in progress. Leaving the page now will cancel the upload." => "File upload is in progress. Leaving the page now will cancel the upload.", "URL cannot be empty" => "URL cannot be empty", -"In the home folder 'Shared' is a reserved filename" => "In the home folder 'Shared' is a reserved file name", "{new_name} already exists" => "{new_name} already exists", "Could not create file" => "Could not create file", "Could not create folder" => "Could not create folder", @@ -62,9 +61,8 @@ $TRANSLATIONS = array( "Name" => "Name", "Size" => "Size", "Modified" => "Modified", -"Invalid folder name. Usage of 'Shared' is reserved." => "Invalid folder name. Usage of 'Shared' is reserved.", "%s could not be renamed" => "%s could not be renamed", -"Upload" => "Upload", +"Upload (max. %s)" => "Upload (max. %s)", "File handling" => "File handling", "Maximum upload size" => "Maximum upload size", "max. possible: " => "max. possible: ", @@ -88,6 +86,6 @@ $TRANSLATIONS = array( "Upload too large" => "Upload too large", "The files you are trying to upload exceed the maximum size for file uploads on this server." => "The files you are trying to upload exceed the maximum size for file uploads on this server.", "Files are being scanned, please wait." => "Files are being scanned, please wait.", -"Current scanning" => "Current scanning" +"Current scanning" => "Currently scanning" ); $PLURAL_FORMS = "nplurals=2; plural=(n != 1);"; diff --git a/apps/files/l10n/eo.php b/apps/files/l10n/eo.php index a6e0d55317..dfa6f7ec03 100644 --- a/apps/files/l10n/eo.php +++ b/apps/files/l10n/eo.php @@ -50,7 +50,6 @@ $TRANSLATIONS = array( "Size" => "Grando", "Modified" => "Modifita", "%s could not be renamed" => "%s ne povis alinomiĝi", -"Upload" => "Alŝuti", "File handling" => "Dosieradministro", "Maximum upload size" => "Maksimuma alŝutogrando", "max. possible: " => "maks. ebla: ", diff --git a/apps/files/l10n/es.php b/apps/files/l10n/es.php index 10a378c371..fd1d915ea8 100644 --- a/apps/files/l10n/es.php +++ b/apps/files/l10n/es.php @@ -35,7 +35,6 @@ $TRANSLATIONS = array( "Could not get result from server." => "No se pudo obtener respuesta del servidor.", "File upload is in progress. Leaving the page now will cancel the upload." => "La subida del archivo está en proceso. Si sale de la página ahora, la subida será cancelada.", "URL cannot be empty" => "La dirección URL no puede estar vacía", -"In the home folder 'Shared' is a reserved filename" => "En la carpeta home, no se puede usar 'Shared'", "{new_name} already exists" => "{new_name} ya existe", "Could not create file" => "No se pudo crear el archivo", "Could not create folder" => "No se pudo crear la carpeta", @@ -62,9 +61,8 @@ $TRANSLATIONS = array( "Name" => "Nombre", "Size" => "Tamaño", "Modified" => "Modificado", -"Invalid folder name. Usage of 'Shared' is reserved." => "Nombre de carpeta inválido. El uso de \"Shared\" esta reservado.", "%s could not be renamed" => "%s no pudo ser renombrado", -"Upload" => "Subir", +"Upload (max. %s)" => "Subida (máx. %s)", "File handling" => "Administración de archivos", "Maximum upload size" => "Tamaño máximo de subida", "max. possible: " => "máx. posible:", diff --git a/apps/files/l10n/es_AR.php b/apps/files/l10n/es_AR.php index f78615fc92..4d4a349a10 100644 --- a/apps/files/l10n/es_AR.php +++ b/apps/files/l10n/es_AR.php @@ -31,7 +31,6 @@ $TRANSLATIONS = array( "Could not get result from server." => "No se pudo obtener resultados del servidor.", "File upload is in progress. Leaving the page now will cancel the upload." => "La subida del archivo está en proceso. Si salís de la página ahora, la subida se cancelará.", "URL cannot be empty" => "La URL no puede estar vacía", -"In the home folder 'Shared' is a reserved filename" => "En el directorio inicial 'Shared' es un nombre de archivo reservado", "{new_name} already exists" => "{new_name} ya existe", "Could not create file" => "No se pudo crear el archivo", "Could not create folder" => "No se pudo crear el directorio", @@ -57,9 +56,7 @@ $TRANSLATIONS = array( "Name" => "Nombre", "Size" => "Tamaño", "Modified" => "Modificado", -"Invalid folder name. Usage of 'Shared' is reserved." => "Nombre de directorio inválido. 'Shared' está reservado.", "%s could not be renamed" => "No se pudo renombrar %s", -"Upload" => "Subir", "File handling" => "Tratamiento de archivos", "Maximum upload size" => "Tamaño máximo de subida", "max. possible: " => "máx. posible:", diff --git a/apps/files/l10n/es_CL.php b/apps/files/l10n/es_CL.php index 8e051d1c38..fa856ad529 100644 --- a/apps/files/l10n/es_CL.php +++ b/apps/files/l10n/es_CL.php @@ -2,11 +2,12 @@ $TRANSLATIONS = array( "Files" => "Archivos", "Share" => "Compartir", +"Rename" => "Renombrar", "Error" => "Error", "_%n folder_::_%n folders_" => array("",""), "_%n file_::_%n files_" => array("",""), "_Uploading %n file_::_Uploading %n files_" => array("",""), -"Upload" => "Subir", +"New folder" => "Nuevo directorio", "Download" => "Descargar" ); $PLURAL_FORMS = "nplurals=2; plural=(n != 1);"; diff --git a/apps/files/l10n/es_CR.php b/apps/files/l10n/es_CR.php new file mode 100644 index 0000000000..0157af093e --- /dev/null +++ b/apps/files/l10n/es_CR.php @@ -0,0 +1,7 @@ + array("",""), +"_%n file_::_%n files_" => array("",""), +"_Uploading %n file_::_Uploading %n files_" => array("","") +); +$PLURAL_FORMS = "nplurals=2; plural=(n != 1);"; diff --git a/apps/files/l10n/es_MX.php b/apps/files/l10n/es_MX.php index ea7db0d7b9..f5f773c760 100644 --- a/apps/files/l10n/es_MX.php +++ b/apps/files/l10n/es_MX.php @@ -31,7 +31,6 @@ $TRANSLATIONS = array( "Could not get result from server." => "No se pudo obtener respuesta del servidor.", "File upload is in progress. Leaving the page now will cancel the upload." => "La subida del archivo está en proceso. Si sale de la página ahora, la subida será cancelada.", "URL cannot be empty" => "La dirección URL no puede estar vacía", -"In the home folder 'Shared' is a reserved filename" => "En la carpeta de inicio, 'Shared' es un nombre reservado", "{new_name} already exists" => "{new_name} ya existe", "Could not create file" => "No se pudo crear el archivo", "Could not create folder" => "No se pudo crear la carpeta", @@ -57,9 +56,7 @@ $TRANSLATIONS = array( "Name" => "Nombre", "Size" => "Tamaño", "Modified" => "Modificado", -"Invalid folder name. Usage of 'Shared' is reserved." => "Nombre de carpeta inválido. El uso de \"Shared\" esta reservado.", "%s could not be renamed" => "%s no pudo ser renombrado", -"Upload" => "Subir", "File handling" => "Administración de archivos", "Maximum upload size" => "Tamaño máximo de subida", "max. possible: " => "máx. posible:", diff --git a/apps/files/l10n/et_EE.php b/apps/files/l10n/et_EE.php index 4f0614feb5..4af93fa9ba 100644 --- a/apps/files/l10n/et_EE.php +++ b/apps/files/l10n/et_EE.php @@ -35,7 +35,6 @@ $TRANSLATIONS = array( "Could not get result from server." => "Serverist ei saadud tulemusi", "File upload is in progress. Leaving the page now will cancel the upload." => "Faili üleslaadimine on töös. Lehelt lahkumine katkestab selle üleslaadimise.", "URL cannot be empty" => "URL ei saa olla tühi", -"In the home folder 'Shared' is a reserved filename" => "Kodukataloogis 'Shared' on reserveeritud failinimi", "{new_name} already exists" => "{new_name} on juba olemas", "Could not create file" => "Ei suuda luua faili", "Could not create folder" => "Ei suuda luua kataloogi", @@ -62,9 +61,8 @@ $TRANSLATIONS = array( "Name" => "Nimi", "Size" => "Suurus", "Modified" => "Muudetud", -"Invalid folder name. Usage of 'Shared' is reserved." => "Vigane kausta nimi. Nime 'Shared' kasutamine on reserveeritud.", "%s could not be renamed" => "%s ümbernimetamine ebaõnnestus", -"Upload" => "Lae üles", +"Upload (max. %s)" => "Üleslaadimine (max. %s)", "File handling" => "Failide käsitlemine", "Maximum upload size" => "Maksimaalne üleslaadimise suurus", "max. possible: " => "maks. võimalik: ", diff --git a/apps/files/l10n/eu.php b/apps/files/l10n/eu.php index d59dd39628..e1bb7033eb 100644 --- a/apps/files/l10n/eu.php +++ b/apps/files/l10n/eu.php @@ -31,7 +31,6 @@ $TRANSLATIONS = array( "Could not get result from server." => "Ezin da zerbitzaritik emaitzik lortu", "File upload is in progress. Leaving the page now will cancel the upload." => "Fitxategien igoera martxan da. Orria orain uzteak igoera ezeztatutko du.", "URL cannot be empty" => "URLa ezin da hutsik egon", -"In the home folder 'Shared' is a reserved filename" => "Etxeko (home) karpetan 'Shared' erreserbatutako fitxategi izena da", "{new_name} already exists" => "{new_name} dagoeneko existitzen da", "Could not create file" => "Ezin izan da fitxategia sortu", "Could not create folder" => "Ezin izan da karpeta sortu", @@ -57,9 +56,7 @@ $TRANSLATIONS = array( "Name" => "Izena", "Size" => "Tamaina", "Modified" => "Aldatuta", -"Invalid folder name. Usage of 'Shared' is reserved." => "Baliogabeako karpeta izena. 'Shared' izena erreserbatuta dago.", "%s could not be renamed" => "%s ezin da berrizendatu", -"Upload" => "Igo", "File handling" => "Fitxategien kudeaketa", "Maximum upload size" => "Igo daitekeen gehienezko tamaina", "max. possible: " => "max, posiblea:", diff --git a/apps/files/l10n/fa.php b/apps/files/l10n/fa.php index 2e8f6255e2..4d2a929195 100644 --- a/apps/files/l10n/fa.php +++ b/apps/files/l10n/fa.php @@ -35,7 +35,6 @@ $TRANSLATIONS = array( "Size" => "اندازه", "Modified" => "تاریخ", "%s could not be renamed" => "%s نمیتواند تغییر نام دهد.", -"Upload" => "بارگزاری", "File handling" => "اداره پرونده ها", "Maximum upload size" => "حداکثر اندازه بارگزاری", "max. possible: " => "حداکثرمقدارممکن:", diff --git a/apps/files/l10n/fi_FI.php b/apps/files/l10n/fi_FI.php index b6383c144d..ba3c921dfe 100644 --- a/apps/files/l10n/fi_FI.php +++ b/apps/files/l10n/fi_FI.php @@ -53,14 +53,15 @@ $TRANSLATIONS = array( "\"{name}\" is an invalid file name." => "\"{name}\" on virheellinen tiedostonimi.", "Your storage is full, files can not be updated or synced anymore!" => "Tallennustila on loppu, tiedostoja ei voi enää päivittää tai synkronoida!", "Your storage is almost full ({usedSpacePercent}%)" => "Tallennustila on melkein loppu ({usedSpacePercent}%)", +"Encryption App is enabled but your keys are not initialized, please log-out and log-in again" => "Salaussovellus on käytössä, mutta salausavaimia ei ole alustettu. Ole hyvä ja kirjaudu sisään uudelleen.", +"Invalid private key for Encryption App. Please update your private key password in your personal settings to recover access to your encrypted files." => "Salaussovelluksen salausavain on virheellinen. Ole hyvä ja päivitä salausavain henkilökohtaisissa asetuksissasi jotta voit taas avata salatuskirjoitetut tiedostosi.", "Encryption was disabled but your files are still encrypted. Please go to your personal settings to decrypt your files." => "Salaus poistettiin käytöstä, mutta tiedostosi ovat edelleen salattuina. Siirry henkilökohtaisiin asetuksiin avataksesi tiedostojesi salauksen.", "Your download is being prepared. This might take some time if the files are big." => "Lataustasi valmistellaan. Tämä saattaa kestää hetken, jos tiedostot ovat suuria kooltaan.", "Name" => "Nimi", "Size" => "Koko", "Modified" => "Muokattu", -"Invalid folder name. Usage of 'Shared' is reserved." => "Virheellinen kansion nimi. 'Shared':n käyttö on varattu.", "%s could not be renamed" => "kohteen %s nimeäminen uudelleen epäonnistui", -"Upload" => "Lähetä", +"Upload (max. %s)" => "Lähetys (enintään %s)", "File handling" => "Tiedostonhallinta", "Maximum upload size" => "Lähetettävän tiedoston suurin sallittu koko", "max. possible: " => "suurin mahdollinen:", diff --git a/apps/files/l10n/fr.php b/apps/files/l10n/fr.php index 0eed6a70f9..26ba8445f4 100644 --- a/apps/files/l10n/fr.php +++ b/apps/files/l10n/fr.php @@ -35,7 +35,6 @@ $TRANSLATIONS = array( "Could not get result from server." => "Ne peut recevoir les résultats du serveur.", "File upload is in progress. Leaving the page now will cancel the upload." => "L'envoi du fichier est en cours. Quitter cette page maintenant annulera l'envoi du fichier.", "URL cannot be empty" => "L'URL ne peut pas être vide", -"In the home folder 'Shared' is a reserved filename" => "Dans le dossier home, 'Partagé' est un nom de fichier réservé", "{new_name} already exists" => "{new_name} existe déjà", "Could not create file" => "Impossible de créer le fichier", "Could not create folder" => "Impossible de créer le dossier", @@ -62,9 +61,8 @@ $TRANSLATIONS = array( "Name" => "Nom", "Size" => "Taille", "Modified" => "Modifié", -"Invalid folder name. Usage of 'Shared' is reserved." => "Nom de dossier invalide. L'utilisation du mot 'Shared' est réservée.", "%s could not be renamed" => "%s ne peut être renommé", -"Upload" => "Envoyer", +"Upload (max. %s)" => "Envoi (max. %s)", "File handling" => "Gestion des fichiers", "Maximum upload size" => "Taille max. d'envoi", "max. possible: " => "Max. possible :", diff --git a/apps/files/l10n/gl.php b/apps/files/l10n/gl.php index 9fe6546de5..d9c6d00360 100644 --- a/apps/files/l10n/gl.php +++ b/apps/files/l10n/gl.php @@ -35,7 +35,6 @@ $TRANSLATIONS = array( "Could not get result from server." => "Non foi posíbel obter o resultado do servidor.", "File upload is in progress. Leaving the page now will cancel the upload." => "O envío do ficheiro está en proceso. Saír agora da páxina cancelará o envío.", "URL cannot be empty" => "O URL non pode quedar en branco.", -"In the home folder 'Shared' is a reserved filename" => "«Shared» dentro do cartafol persoal é un nome reservado", "{new_name} already exists" => "Xa existe un {new_name}", "Could not create file" => "Non foi posíbel crear o ficheiro", "Could not create folder" => "Non foi posíbel crear o cartafol", @@ -62,9 +61,8 @@ $TRANSLATIONS = array( "Name" => "Nome", "Size" => "Tamaño", "Modified" => "Modificado", -"Invalid folder name. Usage of 'Shared' is reserved." => "Nome de cartafol non válido. O uso de «Shared» está reservado.", "%s could not be renamed" => "%s non pode cambiar de nome", -"Upload" => "Enviar", +"Upload (max. %s)" => "Envío (máx. %s)", "File handling" => "Manexo de ficheiro", "Maximum upload size" => "Tamaño máximo do envío", "max. possible: " => "máx. posíbel: ", diff --git a/apps/files/l10n/he.php b/apps/files/l10n/he.php index ab8640a91d..6279e675db 100644 --- a/apps/files/l10n/he.php +++ b/apps/files/l10n/he.php @@ -32,7 +32,6 @@ $TRANSLATIONS = array( "Name" => "שם", "Size" => "גודל", "Modified" => "זמן שינוי", -"Upload" => "העלאה", "File handling" => "טיפול בקבצים", "Maximum upload size" => "גודל העלאה מקסימלי", "max. possible: " => "המרבי האפשרי: ", diff --git a/apps/files/l10n/hi.php b/apps/files/l10n/hi.php index b4234b5137..13fded2671 100644 --- a/apps/files/l10n/hi.php +++ b/apps/files/l10n/hi.php @@ -5,7 +5,6 @@ $TRANSLATIONS = array( "_%n folder_::_%n folders_" => array("",""), "_%n file_::_%n files_" => array("",""), "_Uploading %n file_::_Uploading %n files_" => array("",""), -"Upload" => "अपलोड ", "Save" => "सहेजें" ); $PLURAL_FORMS = "nplurals=2; plural=(n != 1);"; diff --git a/apps/files/l10n/hr.php b/apps/files/l10n/hr.php index ef978e6cfb..0876dcdd1e 100644 --- a/apps/files/l10n/hr.php +++ b/apps/files/l10n/hr.php @@ -19,7 +19,6 @@ $TRANSLATIONS = array( "Name" => "Ime", "Size" => "Veličina", "Modified" => "Zadnja promjena", -"Upload" => "Učitaj", "File handling" => "datoteka za rukovanje", "Maximum upload size" => "Maksimalna veličina prijenosa", "max. possible: " => "maksimalna moguća: ", diff --git a/apps/files/l10n/hu_HU.php b/apps/files/l10n/hu_HU.php index e4ab355c9b..9ed0032682 100644 --- a/apps/files/l10n/hu_HU.php +++ b/apps/files/l10n/hu_HU.php @@ -31,7 +31,6 @@ $TRANSLATIONS = array( "Could not get result from server." => "A kiszolgálótól nem kapható meg az eredmény.", "File upload is in progress. Leaving the page now will cancel the upload." => "Fájlfeltöltés van folyamatban. Az oldal elhagyása megszakítja a feltöltést.", "URL cannot be empty" => "Az URL-cím nem maradhat kitöltetlenül", -"In the home folder 'Shared' is a reserved filename" => "A kiindulási mappában a 'Shared' egy belső használatra fenntartott név", "{new_name} already exists" => "{new_name} már létezik", "Could not create file" => "Az állomány nem hozható létre", "Could not create folder" => "A mappa nem hozható létre", @@ -57,9 +56,7 @@ $TRANSLATIONS = array( "Name" => "Név", "Size" => "Méret", "Modified" => "Módosítva", -"Invalid folder name. Usage of 'Shared' is reserved." => "Érvénytelen mappanév. A 'Shared' a rendszer számára fenntartott elnevezés.", "%s could not be renamed" => "%s átnevezése nem sikerült", -"Upload" => "Feltöltés", "File handling" => "Fájlkezelés", "Maximum upload size" => "Maximális feltölthető fájlméret", "max. possible: " => "max. lehetséges: ", diff --git a/apps/files/l10n/ia.php b/apps/files/l10n/ia.php index 420e48395c..ff4cb0225b 100644 --- a/apps/files/l10n/ia.php +++ b/apps/files/l10n/ia.php @@ -12,11 +12,11 @@ $TRANSLATIONS = array( "Name" => "Nomine", "Size" => "Dimension", "Modified" => "Modificate", -"Upload" => "Incargar", "Maximum upload size" => "Dimension maxime de incargamento", "Save" => "Salveguardar", "New" => "Nove", "Text file" => "File de texto", +"New folder" => "Nove dossier", "Folder" => "Dossier", "Nothing in here. Upload something!" => "Nihil hic. Incarga alcun cosa!", "Download" => "Discargar", diff --git a/apps/files/l10n/id.php b/apps/files/l10n/id.php index 8356c5465e..d8c0a47789 100644 --- a/apps/files/l10n/id.php +++ b/apps/files/l10n/id.php @@ -30,7 +30,6 @@ $TRANSLATIONS = array( "Could not get result from server." => "Tidak mendapatkan hasil dari server.", "File upload is in progress. Leaving the page now will cancel the upload." => "Berkas sedang diunggah. Meninggalkan halaman ini akan membatalkan proses.", "URL cannot be empty" => "URL tidak boleh kosong", -"In the home folder 'Shared' is a reserved filename" => "Pada folder home, 'Shared' adalah nama berkas yang sudah digunakan", "{new_name} already exists" => "{new_name} sudah ada", "Could not create file" => "Tidak dapat membuat berkas", "Could not create folder" => "Tidak dapat membuat folder", @@ -55,9 +54,7 @@ $TRANSLATIONS = array( "Name" => "Nama", "Size" => "Ukuran", "Modified" => "Dimodifikasi", -"Invalid folder name. Usage of 'Shared' is reserved." => "Nama folder tidak sah. Menggunakan 'Shared' sudah digunakan.", "%s could not be renamed" => "%s tidak dapat diubah nama", -"Upload" => "Unggah", "File handling" => "Penanganan berkas", "Maximum upload size" => "Ukuran pengunggahan maksimum", "max. possible: " => "Kemungkinan maks.:", diff --git a/apps/files/l10n/is.php b/apps/files/l10n/is.php index 00503028e0..b8e23b6a30 100644 --- a/apps/files/l10n/is.php +++ b/apps/files/l10n/is.php @@ -27,7 +27,6 @@ $TRANSLATIONS = array( "Name" => "Nafn", "Size" => "Stærð", "Modified" => "Breytt", -"Upload" => "Senda inn", "File handling" => "Meðhöndlun skrár", "Maximum upload size" => "Hámarks stærð innsendingar", "max. possible: " => "hámark mögulegt: ", diff --git a/apps/files/l10n/it.php b/apps/files/l10n/it.php index 9539496a3f..c77fb57c59 100644 --- a/apps/files/l10n/it.php +++ b/apps/files/l10n/it.php @@ -35,7 +35,6 @@ $TRANSLATIONS = array( "Could not get result from server." => "Impossibile ottenere il risultato dal server.", "File upload is in progress. Leaving the page now will cancel the upload." => "Caricamento del file in corso. La chiusura della pagina annullerà il caricamento.", "URL cannot be empty" => "L'URL non può essere vuoto.", -"In the home folder 'Shared' is a reserved filename" => "Nella cartella home 'Shared' è un nome riservato", "{new_name} already exists" => "{new_name} esiste già", "Could not create file" => "Impossibile creare il file", "Could not create folder" => "Impossibile creare la cartella", @@ -62,9 +61,8 @@ $TRANSLATIONS = array( "Name" => "Nome", "Size" => "Dimensione", "Modified" => "Modificato", -"Invalid folder name. Usage of 'Shared' is reserved." => "Nome della cartella non valido. L'uso di 'Shared' è riservato.", "%s could not be renamed" => "%s non può essere rinominato", -"Upload" => "Carica", +"Upload (max. %s)" => "Carica (massimo %s)", "File handling" => "Gestione file", "Maximum upload size" => "Dimensione massima upload", "max. possible: " => "numero mass.: ", diff --git a/apps/files/l10n/ja.php b/apps/files/l10n/ja.php index dd8d4e4e3f..361fd835ce 100644 --- a/apps/files/l10n/ja.php +++ b/apps/files/l10n/ja.php @@ -35,7 +35,6 @@ $TRANSLATIONS = array( "Could not get result from server." => "サーバーから結果を取得できませんでした。", "File upload is in progress. Leaving the page now will cancel the upload." => "ファイル転送を実行中です。今このページから移動するとアップロードが中止されます。", "URL cannot be empty" => "URL は空にできません", -"In the home folder 'Shared' is a reserved filename" => "ホームフォルダーでは、'Shared' はシステムが使用する予約済みのファイル名です", "{new_name} already exists" => "{new_name} はすでに存在します", "Could not create file" => "ファイルを作成できませんでした", "Could not create folder" => "フォルダーを作成できませんでした", @@ -62,9 +61,7 @@ $TRANSLATIONS = array( "Name" => "名前", "Size" => "サイズ", "Modified" => "更新日時", -"Invalid folder name. Usage of 'Shared' is reserved." => "無効なフォルダー名。「Shared」の利用は予約されています。", "%s could not be renamed" => "%sの名前を変更できませんでした", -"Upload" => "アップロード", "File handling" => "ファイル操作", "Maximum upload size" => "最大アップロードサイズ", "max. possible: " => "最大容量: ", diff --git a/apps/files/l10n/ka_GE.php b/apps/files/l10n/ka_GE.php index f9749d72bb..ad3a4bff1f 100644 --- a/apps/files/l10n/ka_GE.php +++ b/apps/files/l10n/ka_GE.php @@ -32,7 +32,6 @@ $TRANSLATIONS = array( "Name" => "სახელი", "Size" => "ზომა", "Modified" => "შეცვლილია", -"Upload" => "ატვირთვა", "File handling" => "ფაილის დამუშავება", "Maximum upload size" => "მაქსიმუმ ატვირთის ზომა", "max. possible: " => "მაქს. შესაძლებელი:", diff --git a/apps/files/l10n/km.php b/apps/files/l10n/km.php index a7a01ccab9..30bb2998af 100644 --- a/apps/files/l10n/km.php +++ b/apps/files/l10n/km.php @@ -8,7 +8,6 @@ $TRANSLATIONS = array( "_Uploading %n file_::_Uploading %n files_" => array(""), "Name" => "ឈ្មោះ", "Size" => "ទំហំ", -"Upload" => "ផ្ទុក​ឡើង", "Save" => "រក្សាទុក", "New folder" => "ថត​ថ្មី", "Folder" => "ថត", diff --git a/apps/files/l10n/ko.php b/apps/files/l10n/ko.php index c0f0d7d445..21c7ebcdc4 100644 --- a/apps/files/l10n/ko.php +++ b/apps/files/l10n/ko.php @@ -31,7 +31,6 @@ $TRANSLATIONS = array( "Could not get result from server." => "서버에서 결과를 가져올 수 없습니다.", "File upload is in progress. Leaving the page now will cancel the upload." => "파일 업로드가 진행 중입니다. 이 페이지를 벗어나면 업로드가 취소됩니다.", "URL cannot be empty" => "URL이 비어있을 수 없음", -"In the home folder 'Shared' is a reserved filename" => "'공유됨'은 홈 폴더의 예약된 파일 이름임", "{new_name} already exists" => "{new_name}이(가) 이미 존재함", "Could not create file" => "파일을 만들 수 없음", "Could not create folder" => "폴더를 만들 수 없음", @@ -57,9 +56,7 @@ $TRANSLATIONS = array( "Name" => "이름", "Size" => "크기", "Modified" => "수정됨", -"Invalid folder name. Usage of 'Shared' is reserved." => "폴더 이름이 잘못되었습니다. '공유됨'은 예약된 폴더 이름입니다.", "%s could not be renamed" => "%s의 이름을 변경할 수 없습니다", -"Upload" => "업로드", "File handling" => "파일 처리", "Maximum upload size" => "최대 업로드 크기", "max. possible: " => "최대 가능:", diff --git a/apps/files/l10n/ku_IQ.php b/apps/files/l10n/ku_IQ.php index 6ec5819d38..1c9d615ee7 100644 --- a/apps/files/l10n/ku_IQ.php +++ b/apps/files/l10n/ku_IQ.php @@ -7,7 +7,6 @@ $TRANSLATIONS = array( "_%n file_::_%n files_" => array("",""), "_Uploading %n file_::_Uploading %n files_" => array("",""), "Name" => "ناو", -"Upload" => "بارکردن", "Save" => "پاشکه‌وتکردن", "Folder" => "بوخچه", "Download" => "داگرتن" diff --git a/apps/files/l10n/lb.php b/apps/files/l10n/lb.php index 38b5d672d0..27dc936600 100644 --- a/apps/files/l10n/lb.php +++ b/apps/files/l10n/lb.php @@ -18,7 +18,6 @@ $TRANSLATIONS = array( "Name" => "Numm", "Size" => "Gréisst", "Modified" => "Geännert", -"Upload" => "Eroplueden", "File handling" => "Fichier handling", "Maximum upload size" => "Maximum Upload Gréisst ", "max. possible: " => "max. méiglech:", diff --git a/apps/files/l10n/lt_LT.php b/apps/files/l10n/lt_LT.php index 50097e5f36..a8c30016d3 100644 --- a/apps/files/l10n/lt_LT.php +++ b/apps/files/l10n/lt_LT.php @@ -31,7 +31,6 @@ $TRANSLATIONS = array( "Could not get result from server." => "Nepavyko gauti rezultato iš serverio.", "File upload is in progress. Leaving the page now will cancel the upload." => "Failo įkėlimas pradėtas. Jei paliksite šį puslapį, įkėlimas nutrūks.", "URL cannot be empty" => "URL negali būti tuščias.", -"In the home folder 'Shared' is a reserved filename" => "Pradiniame aplanke failo pavadinimas „Shared“ yra rezervuotas", "{new_name} already exists" => "{new_name} jau egzistuoja", "Could not create file" => "Neįmanoma sukurti failo", "Could not create folder" => "Neįmanoma sukurti aplanko", @@ -57,9 +56,7 @@ $TRANSLATIONS = array( "Name" => "Pavadinimas", "Size" => "Dydis", "Modified" => "Pakeista", -"Invalid folder name. Usage of 'Shared' is reserved." => "Netinkamas aplanko pavadinimas. „Shared“ pavadinimas yra rezervuotas.", "%s could not be renamed" => "%s negali būti pervadintas", -"Upload" => "Įkelti", "File handling" => "Failų tvarkymas", "Maximum upload size" => "Maksimalus įkeliamo failo dydis", "max. possible: " => "maks. galima:", diff --git a/apps/files/l10n/lv.php b/apps/files/l10n/lv.php index fcb1a59aa3..71f3976816 100644 --- a/apps/files/l10n/lv.php +++ b/apps/files/l10n/lv.php @@ -36,7 +36,6 @@ $TRANSLATIONS = array( "Size" => "Izmērs", "Modified" => "Mainīts", "%s could not be renamed" => "%s nevar tikt pārsaukts", -"Upload" => "Augšupielādēt", "File handling" => "Datņu pārvaldība", "Maximum upload size" => "Maksimālais datņu augšupielādes apjoms", "max. possible: " => "maksimālais iespējamais:", diff --git a/apps/files/l10n/mk.php b/apps/files/l10n/mk.php index fa6efd1aff..e1ff2c0bc2 100644 --- a/apps/files/l10n/mk.php +++ b/apps/files/l10n/mk.php @@ -27,7 +27,6 @@ $TRANSLATIONS = array( "Could not get result from server." => "Не можам да добијам резултат од серверот.", "File upload is in progress. Leaving the page now will cancel the upload." => "Подигање на датотека е во тек. Напуштење на страницата ќе го прекине.", "URL cannot be empty" => "URL-то не може да биде празно", -"In the home folder 'Shared' is a reserved filename" => "Во домашната папка, 'Shared' е резервирано има на датотека/папка", "{new_name} already exists" => "{new_name} веќе постои", "Could not create file" => "Не множам да креирам датотека", "Could not create folder" => "Не можам да креирам папка", @@ -49,7 +48,6 @@ $TRANSLATIONS = array( "Size" => "Големина", "Modified" => "Променето", "%s could not be renamed" => "%s не може да биде преименуван", -"Upload" => "Подигни", "File handling" => "Ракување со датотеки", "Maximum upload size" => "Максимална големина за подигање", "max. possible: " => "макс. можно:", diff --git a/apps/files/l10n/ms_MY.php b/apps/files/l10n/ms_MY.php index df0054c3d0..af42a3838b 100644 --- a/apps/files/l10n/ms_MY.php +++ b/apps/files/l10n/ms_MY.php @@ -19,7 +19,6 @@ $TRANSLATIONS = array( "Name" => "Nama", "Size" => "Saiz", "Modified" => "Dimodifikasi", -"Upload" => "Muat naik", "File handling" => "Pengendalian fail", "Maximum upload size" => "Saiz maksimum muat naik", "max. possible: " => "maksimum:", diff --git a/apps/files/l10n/nb_NO.php b/apps/files/l10n/nb_NO.php index f1e2c2edee..9887087cca 100644 --- a/apps/files/l10n/nb_NO.php +++ b/apps/files/l10n/nb_NO.php @@ -31,7 +31,6 @@ $TRANSLATIONS = array( "Could not get result from server." => "Fikk ikke resultat fra serveren.", "File upload is in progress. Leaving the page now will cancel the upload." => "Filopplasting pågår. Forlater du siden nå avbrytes opplastingen.", "URL cannot be empty" => "URL kan ikke være tom", -"In the home folder 'Shared' is a reserved filename" => "I hjemmemappen er 'Shared' et reservert filnavn", "{new_name} already exists" => "{new_name} finnes allerede", "Could not create file" => "Klarte ikke å opprette fil", "Could not create folder" => "Klarte ikke å opprette mappe", @@ -57,9 +56,7 @@ $TRANSLATIONS = array( "Name" => "Navn", "Size" => "Størrelse", "Modified" => "Endret", -"Invalid folder name. Usage of 'Shared' is reserved." => "Ulovlig mappenavn. Bruken av 'Shared' er reservert.", "%s could not be renamed" => "Kunne ikke gi nytt navn til %s", -"Upload" => "Last opp", "File handling" => "Filhåndtering", "Maximum upload size" => "Maksimum opplastingsstørrelse", "max. possible: " => "max. mulige:", diff --git a/apps/files/l10n/nl.php b/apps/files/l10n/nl.php index 946c7905b2..e33d26e38d 100644 --- a/apps/files/l10n/nl.php +++ b/apps/files/l10n/nl.php @@ -35,7 +35,6 @@ $TRANSLATIONS = array( "Could not get result from server." => "Kon het resultaat van de server niet terugkrijgen.", "File upload is in progress. Leaving the page now will cancel the upload." => "Bestandsupload is bezig. Wanneer de pagina nu verlaten wordt, stopt de upload.", "URL cannot be empty" => "URL mag niet leeg zijn", -"In the home folder 'Shared' is a reserved filename" => "in de home map 'Shared' is een gereserveerde bestandsnaam", "{new_name} already exists" => "{new_name} bestaat al", "Could not create file" => "Kon bestand niet creëren", "Could not create folder" => "Kon niet creëren map", @@ -62,9 +61,8 @@ $TRANSLATIONS = array( "Name" => "Naam", "Size" => "Grootte", "Modified" => "Aangepast", -"Invalid folder name. Usage of 'Shared' is reserved." => "Ongeldige mapnaam. Gebruik van 'Shared' is gereserveerd.", "%s could not be renamed" => "%s kon niet worden hernoemd", -"Upload" => "Uploaden", +"Upload (max. %s)" => "Upload (max. %s)", "File handling" => "Bestand", "Maximum upload size" => "Maximale bestandsgrootte voor uploads", "max. possible: " => "max. mogelijk: ", diff --git a/apps/files/l10n/nn_NO.php b/apps/files/l10n/nn_NO.php index bd17fa3386..693bfccb09 100644 --- a/apps/files/l10n/nn_NO.php +++ b/apps/files/l10n/nn_NO.php @@ -42,7 +42,6 @@ $TRANSLATIONS = array( "Size" => "Storleik", "Modified" => "Endra", "%s could not be renamed" => "Klarte ikkje å omdøypa på %s", -"Upload" => "Last opp", "File handling" => "Filhandtering", "Maximum upload size" => "Maksimal opplastingsstorleik", "max. possible: " => "maks. moglege:", diff --git a/apps/files/l10n/oc.php b/apps/files/l10n/oc.php index 7a24c81974..f3d790a533 100644 --- a/apps/files/l10n/oc.php +++ b/apps/files/l10n/oc.php @@ -19,7 +19,6 @@ $TRANSLATIONS = array( "Name" => "Nom", "Size" => "Talha", "Modified" => "Modificat", -"Upload" => "Amontcarga", "File handling" => "Manejament de fichièr", "Maximum upload size" => "Talha maximum d'amontcargament", "max. possible: " => "max. possible: ", diff --git a/apps/files/l10n/or_IN.php b/apps/files/l10n/or_IN.php new file mode 100644 index 0000000000..0157af093e --- /dev/null +++ b/apps/files/l10n/or_IN.php @@ -0,0 +1,7 @@ + array("",""), +"_%n file_::_%n files_" => array("",""), +"_Uploading %n file_::_Uploading %n files_" => array("","") +); +$PLURAL_FORMS = "nplurals=2; plural=(n != 1);"; diff --git a/apps/files/l10n/pa.php b/apps/files/l10n/pa.php index b18d2071e0..3cf8724249 100644 --- a/apps/files/l10n/pa.php +++ b/apps/files/l10n/pa.php @@ -7,7 +7,6 @@ $TRANSLATIONS = array( "_%n folder_::_%n folders_" => array("",""), "_%n file_::_%n files_" => array("",""), "_Uploading %n file_::_Uploading %n files_" => array("",""), -"Upload" => "ਅੱਪਲੋਡ", "Cancel upload" => "ਅੱਪਲੋਡ ਰੱਦ ਕਰੋ", "Download" => "ਡਾਊਨਲੋਡ", "Delete" => "ਹਟਾਓ" diff --git a/apps/files/l10n/pl.php b/apps/files/l10n/pl.php index 83126b3ea0..30c3496db3 100644 --- a/apps/files/l10n/pl.php +++ b/apps/files/l10n/pl.php @@ -35,7 +35,6 @@ $TRANSLATIONS = array( "Could not get result from server." => "Nie można uzyskać wyniku z serwera.", "File upload is in progress. Leaving the page now will cancel the upload." => "Wysyłanie pliku jest w toku. Jeśli opuścisz tę stronę, wysyłanie zostanie przerwane.", "URL cannot be empty" => "URL nie może być pusty", -"In the home folder 'Shared' is a reserved filename" => "W katalogu domowym \"Shared\" jest zarezerwowana nazwa pliku", "{new_name} already exists" => "{new_name} już istnieje", "Could not create file" => "Nie można utworzyć pliku", "Could not create folder" => "Nie można utworzyć folderu", @@ -62,9 +61,8 @@ $TRANSLATIONS = array( "Name" => "Nazwa", "Size" => "Rozmiar", "Modified" => "Modyfikacja", -"Invalid folder name. Usage of 'Shared' is reserved." => "Niepoprawna nazwa folderu. Wykorzystanie \"Shared\" jest zarezerwowane.", "%s could not be renamed" => "%s nie można zmienić nazwy", -"Upload" => "Wyślij", +"Upload (max. %s)" => "Wysyłka (max. %s)", "File handling" => "Zarządzanie plikami", "Maximum upload size" => "Maksymalny rozmiar wysyłanego pliku", "max. possible: " => "maks. możliwy:", diff --git a/apps/files/l10n/pt_BR.php b/apps/files/l10n/pt_BR.php index 48c32e8887..10d8892e18 100644 --- a/apps/files/l10n/pt_BR.php +++ b/apps/files/l10n/pt_BR.php @@ -35,7 +35,6 @@ $TRANSLATIONS = array( "Could not get result from server." => "Não foi possível obter o resultado do servidor.", "File upload is in progress. Leaving the page now will cancel the upload." => "Upload em andamento. Sair da página agora resultará no cancelamento do envio.", "URL cannot be empty" => "URL não pode estar vazia", -"In the home folder 'Shared' is a reserved filename" => "Na pasta home 'Shared- Compartilhada' é um nome reservado", "{new_name} already exists" => "{new_name} já existe", "Could not create file" => "Não foi possível criar o arquivo", "Could not create folder" => "Não foi possível criar a pasta", @@ -62,9 +61,8 @@ $TRANSLATIONS = array( "Name" => "Nome", "Size" => "Tamanho", "Modified" => "Modificado", -"Invalid folder name. Usage of 'Shared' is reserved." => "Nome da pasta inválido. Uso de 'Shared' é reservado.", "%s could not be renamed" => "%s não pode ser renomeado", -"Upload" => "Upload", +"Upload (max. %s)" => "Envio (max. %s)", "File handling" => "Tratamento de Arquivo", "Maximum upload size" => "Tamanho máximo para carregar", "max. possible: " => "max. possível:", diff --git a/apps/files/l10n/pt_PT.php b/apps/files/l10n/pt_PT.php index 0afb6b5015..c24d2cf8f3 100644 --- a/apps/files/l10n/pt_PT.php +++ b/apps/files/l10n/pt_PT.php @@ -31,7 +31,6 @@ $TRANSLATIONS = array( "Could not get result from server." => "Não foi possível obter o resultado do servidor.", "File upload is in progress. Leaving the page now will cancel the upload." => "Envio de ficheiro em progresso. Irá cancelar o envio se sair da página agora.", "URL cannot be empty" => "URL não pode estar vazio", -"In the home folder 'Shared' is a reserved filename" => "Na pasta pessoal \"Partilhado\" é um nome de ficheiro reservado", "{new_name} already exists" => "O nome {new_name} já existe", "Could not create file" => "Não pôde criar ficheiro", "Could not create folder" => "Não pôde criar pasta", @@ -57,9 +56,7 @@ $TRANSLATIONS = array( "Name" => "Nome", "Size" => "Tamanho", "Modified" => "Modificado", -"Invalid folder name. Usage of 'Shared' is reserved." => "Nome de pasta inválido. Utilização de \"Partilhado\" está reservada.", "%s could not be renamed" => "%s não pode ser renomeada", -"Upload" => "Carregar", "File handling" => "Manuseamento de ficheiros", "Maximum upload size" => "Tamanho máximo de envio", "max. possible: " => "max. possivel: ", diff --git a/apps/files/l10n/ro.php b/apps/files/l10n/ro.php index 6cda724df4..d3927f5daf 100644 --- a/apps/files/l10n/ro.php +++ b/apps/files/l10n/ro.php @@ -3,7 +3,10 @@ $TRANSLATIONS = array( "Could not move %s - File with this name already exists" => "%s nu se poate muta - Fișierul cu acest nume există deja ", "Could not move %s" => "Nu se poate muta %s", "File name cannot be empty." => "Numele fișierului nu poate rămâne gol.", +"\"%s\" is an invalid file name." => "\"%s\" este un nume de fișier nevalid", "Invalid name, '\\', '/', '<', '>', ':', '\"', '|', '?' and '*' are not allowed." => "Nume nevalide, '\\', '/', '<', '>', ':', '\"', '|', '?' și '*' nu sunt permise.", +"The target folder has been moved or deleted." => "Dosarul țintă a fost mutat sau șters.", +"Not a valid source" => "Sursă nevalidă", "Error while downloading %s to %s" => "Eroare la descarcarea %s in %s", "Error when creating the file" => "Eroare la crearea fisierului", "Folder name cannot be empty." => "Numele folderului nu poate fi liber.", @@ -24,6 +27,8 @@ $TRANSLATIONS = array( "Invalid directory." => "Dosar nevalid.", "Files" => "Fișiere", "Unable to upload {filename} as it is a directory or has 0 bytes" => "Nu se poate încărca {filename} deoarece este un director sau are mărimea de 0 octeți", +"Total file size {size1} exceeds upload limit {size2}" => "Mărimea fișierului este {size1} ce depășește limita de incarcare de {size2}", +"Not enough free space, you are uploading {size1} but only {size2} is left" => "Spațiu liber insuficient, încărcați {size1} însă doar {size2} disponibil rămas", "Upload cancelled." => "Încărcare anulată.", "Could not get result from server." => "Nu se poate obține rezultatul de la server.", "File upload is in progress. Leaving the page now will cancel the upload." => "Fișierul este în curs de încărcare. Părăsirea paginii va întrerupe încărcarea.", @@ -31,6 +36,7 @@ $TRANSLATIONS = array( "{new_name} already exists" => "{new_name} există deja", "Could not create file" => "Nu s-a putut crea fisierul", "Could not create folder" => "Nu s-a putut crea folderul", +"Error fetching URL" => "Eroare încarcare URL", "Share" => "Partajează", "Delete permanently" => "Șterge permanent", "Rename" => "Redenumește", @@ -38,10 +44,12 @@ $TRANSLATIONS = array( "Error" => "Eroare", "Pending" => "În așteptare", "Could not rename file" => "Nu s-a putut redenumi fisierul", +"Error deleting file." => "Eroare la ștergerea fisierului.", "_%n folder_::_%n folders_" => array("%n director","%n directoare","%n directoare"), "_%n file_::_%n files_" => array("%n fișier","%n fișiere","%n fișiere"), "{dirs} and {files}" => "{dirs} și {files}", "_Uploading %n file_::_Uploading %n files_" => array("Se încarcă %n fișier.","Se încarcă %n fișiere.","Se încarcă %n fișiere."), +"\"{name}\" is an invalid file name." => "\"{name}\" este un nume de fișier nevalid.", "Your storage is full, files can not be updated or synced anymore!" => "Spațiul de stocare este plin, fișierele nu mai pot fi actualizate sau sincronizate!", "Your storage is almost full ({usedSpacePercent}%)" => "Spațiul de stocare este aproape plin ({usedSpacePercent}%)", "Encryption was disabled but your files are still encrypted. Please go to your personal settings to decrypt your files." => "criptarea a fost disactivata dar fisierele sant inca criptate.va rog intrati in setarile personale pentru a decripta fisierele", @@ -50,7 +58,7 @@ $TRANSLATIONS = array( "Size" => "Mărime", "Modified" => "Modificat", "%s could not be renamed" => "%s nu a putut fi redenumit", -"Upload" => "Încărcă", +"Upload (max. %s)" => "Încarcă (max. %s)", "File handling" => "Manipulare fișiere", "Maximum upload size" => "Dimensiune maximă admisă la încărcare", "max. possible: " => "max. posibil:", @@ -60,7 +68,9 @@ $TRANSLATIONS = array( "Maximum input size for ZIP files" => "Dimensiunea maximă de intrare pentru fișierele ZIP", "Save" => "Salvează", "New" => "Nou", +"New text file" => "Un nou fișier text", "Text file" => "Fișier text", +"New folder" => "Un nou dosar", "Folder" => "Dosar", "From link" => "De la adresa", "Deleted files" => "Fișiere șterse", diff --git a/apps/files/l10n/ru.php b/apps/files/l10n/ru.php index 17f06c6a20..d10a7e2eca 100644 --- a/apps/files/l10n/ru.php +++ b/apps/files/l10n/ru.php @@ -35,7 +35,6 @@ $TRANSLATIONS = array( "Could not get result from server." => "Не удалось получить ответ от сервера.", "File upload is in progress. Leaving the page now will cancel the upload." => "Идёт загрузка файла. Покинув страницу, вы прервёте загрузку.", "URL cannot be empty" => "Ссылка не может быть пустой.", -"In the home folder 'Shared' is a reserved filename" => "'Shared' - это зарезервированное имя файла в домашнем каталоге", "{new_name} already exists" => "{new_name} уже существует", "Could not create file" => "Не удалось создать файл", "Could not create folder" => "Не удалось создать каталог", @@ -62,9 +61,8 @@ $TRANSLATIONS = array( "Name" => "Имя", "Size" => "Размер", "Modified" => "Дата изменения", -"Invalid folder name. Usage of 'Shared' is reserved." => "Неправильное имя каталога. Имя 'Shared' зарезервировано.", "%s could not be renamed" => "%s не может быть переименован", -"Upload" => "Загрузка", +"Upload (max. %s)" => "Загружено (max. %s)", "File handling" => "Управление файлами", "Maximum upload size" => "Максимальный размер загружаемого файла", "max. possible: " => "макс. возможно: ", diff --git a/apps/files/l10n/si_LK.php b/apps/files/l10n/si_LK.php index a2809ee2f5..ff6672f711 100644 --- a/apps/files/l10n/si_LK.php +++ b/apps/files/l10n/si_LK.php @@ -19,7 +19,6 @@ $TRANSLATIONS = array( "Name" => "නම", "Size" => "ප්‍රමාණය", "Modified" => "වෙනස් කළ", -"Upload" => "උඩුගත කරන්න", "File handling" => "ගොනු පරිහරණය", "Maximum upload size" => "උඩුගත කිරීමක උපරිම ප්‍රමාණය", "max. possible: " => "හැකි උපරිමය:", diff --git a/apps/files/l10n/sk_SK.php b/apps/files/l10n/sk_SK.php index cffb89c294..3d92dc1b5a 100644 --- a/apps/files/l10n/sk_SK.php +++ b/apps/files/l10n/sk_SK.php @@ -35,7 +35,6 @@ $TRANSLATIONS = array( "Could not get result from server." => "Nepodarilo sa dostať výsledky zo servera.", "File upload is in progress. Leaving the page now will cancel the upload." => "Opustenie stránky zruší práve prebiehajúce odosielanie súboru.", "URL cannot be empty" => "URL nemôže byť prázdna", -"In the home folder 'Shared' is a reserved filename" => "V domovskom priečinku je názov \"Shared\" vyhradený názov súboru", "{new_name} already exists" => "{new_name} už existuje", "Could not create file" => "Nemožno vytvoriť súbor", "Could not create folder" => "Nemožno vytvoriť priečinok", @@ -62,9 +61,8 @@ $TRANSLATIONS = array( "Name" => "Názov", "Size" => "Veľkosť", "Modified" => "Upravené", -"Invalid folder name. Usage of 'Shared' is reserved." => "Názov priečinka je chybný. Použitie názvu 'Shared' nie je povolené.", "%s could not be renamed" => "%s nemohol byť premenovaný", -"Upload" => "Odoslať", +"Upload (max. %s)" => "Nahrať (max. %s)", "File handling" => "Nastavenie správania sa k súborom", "Maximum upload size" => "Maximálna veľkosť odosielaného súboru", "max. possible: " => "najväčšie možné:", diff --git a/apps/files/l10n/sl.php b/apps/files/l10n/sl.php index fcb358bd7b..ac304d992c 100644 --- a/apps/files/l10n/sl.php +++ b/apps/files/l10n/sl.php @@ -35,7 +35,6 @@ $TRANSLATIONS = array( "Could not get result from server." => "Ni mogoče pridobiti podatkov s strežnika.", "File upload is in progress. Leaving the page now will cancel the upload." => "V teku je pošiljanje datoteke. Če zapustite to stran zdaj, bo pošiljanje preklicano.", "URL cannot be empty" => "Polje naslova URL ne sme biti prazno", -"In the home folder 'Shared' is a reserved filename" => "V domači mapi ni dovoljeno ustvariti mape z imenom 'Souporabe', saj je ime zadržano za javno mapo.", "{new_name} already exists" => "{new_name} že obstaja", "Could not create file" => "Ni mogoče ustvariti datoteke", "Could not create folder" => "Ni mogoče ustvariti mape", @@ -62,9 +61,8 @@ $TRANSLATIONS = array( "Name" => "Ime", "Size" => "Velikost", "Modified" => "Spremenjeno", -"Invalid folder name. Usage of 'Shared' is reserved." => "Neveljavno ime mape. Ime 'Souporaba' je zadržana za javno mapo.", "%s could not be renamed" => "%s ni mogoče preimenovati", -"Upload" => "Pošlji", +"Upload (max. %s)" => "Pošiljanje (omejitev %s)", "File handling" => "Upravljanje z datotekami", "Maximum upload size" => "Največja velikost za pošiljanja", "max. possible: " => "največ mogoče:", diff --git a/apps/files/l10n/sq.php b/apps/files/l10n/sq.php index ade4f769fa..a7f1cb3413 100644 --- a/apps/files/l10n/sq.php +++ b/apps/files/l10n/sq.php @@ -40,7 +40,6 @@ $TRANSLATIONS = array( "Size" => "Madhësia", "Modified" => "Ndryshuar", "%s could not be renamed" => "Nuk është i mundur riemërtimi i %s", -"Upload" => "Ngarko", "File handling" => "Trajtimi i Skedarëve", "Maximum upload size" => "Madhësia maksimale e nagarkimit", "max. possible: " => "maks i mundshëm", diff --git a/apps/files/l10n/sr.php b/apps/files/l10n/sr.php index 44669e8167..866d8dbdd0 100644 --- a/apps/files/l10n/sr.php +++ b/apps/files/l10n/sr.php @@ -32,7 +32,6 @@ $TRANSLATIONS = array( "Name" => "Име", "Size" => "Величина", "Modified" => "Измењено", -"Upload" => "Отпреми", "File handling" => "Управљање датотекама", "Maximum upload size" => "Највећа величина датотеке", "max. possible: " => "највећа величина:", diff --git a/apps/files/l10n/sr@latin.php b/apps/files/l10n/sr@latin.php index a5c74860f7..38039a19fc 100644 --- a/apps/files/l10n/sr@latin.php +++ b/apps/files/l10n/sr@latin.php @@ -15,7 +15,6 @@ $TRANSLATIONS = array( "Name" => "Ime", "Size" => "Veličina", "Modified" => "Zadnja izmena", -"Upload" => "Pošalji", "Maximum upload size" => "Maksimalna veličina pošiljke", "Save" => "Snimi", "Nothing in here. Upload something!" => "Ovde nema ničeg. Pošaljite nešto!", diff --git a/apps/files/l10n/sv.php b/apps/files/l10n/sv.php index f420216228..f3d0170a30 100644 --- a/apps/files/l10n/sv.php +++ b/apps/files/l10n/sv.php @@ -35,7 +35,6 @@ $TRANSLATIONS = array( "Could not get result from server." => "Gick inte att hämta resultat från server.", "File upload is in progress. Leaving the page now will cancel the upload." => "Filuppladdning pågår. Lämnar du sidan så avbryts uppladdningen.", "URL cannot be empty" => "URL kan ej vara tomt", -"In the home folder 'Shared' is a reserved filename" => "I hemma katalogen 'Delat' är ett reserverat filnamn", "{new_name} already exists" => "{new_name} finns redan", "Could not create file" => "Kunde ej skapa fil", "Could not create folder" => "Kunde ej skapa katalog", @@ -62,9 +61,8 @@ $TRANSLATIONS = array( "Name" => "Namn", "Size" => "Storlek", "Modified" => "Ändrad", -"Invalid folder name. Usage of 'Shared' is reserved." => "Ogiltigt mappnamn. Användande av 'Shared' är reserverat av ownCloud", "%s could not be renamed" => "%s kunde inte namnändras", -"Upload" => "Ladda upp", +"Upload (max. %s)" => "Ladda upp (max. %s)", "File handling" => "Filhantering", "Maximum upload size" => "Maximal storlek att ladda upp", "max. possible: " => "max. möjligt:", diff --git a/apps/files/l10n/ta_LK.php b/apps/files/l10n/ta_LK.php index 257aacf147..0ab17785b9 100644 --- a/apps/files/l10n/ta_LK.php +++ b/apps/files/l10n/ta_LK.php @@ -22,7 +22,6 @@ $TRANSLATIONS = array( "Name" => "பெயர்", "Size" => "அளவு", "Modified" => "மாற்றப்பட்டது", -"Upload" => "பதிவேற்றுக", "File handling" => "கோப்பு கையாளுதல்", "Maximum upload size" => "பதிவேற்றக்கூடிய ஆகக்கூடிய அளவு ", "max. possible: " => "ஆகக் கூடியது:", diff --git a/apps/files/l10n/th_TH.php b/apps/files/l10n/th_TH.php index 8f5f15f2a3..f0fd29da7e 100644 --- a/apps/files/l10n/th_TH.php +++ b/apps/files/l10n/th_TH.php @@ -31,7 +31,6 @@ $TRANSLATIONS = array( "Name" => "ชื่อ", "Size" => "ขนาด", "Modified" => "แก้ไขแล้ว", -"Upload" => "อัพโหลด", "File handling" => "การจัดกาไฟล์", "Maximum upload size" => "ขนาดไฟล์สูงสุดที่อัพโหลดได้", "max. possible: " => "จำนวนสูงสุดที่สามารถทำได้: ", diff --git a/apps/files/l10n/tr.php b/apps/files/l10n/tr.php index 87c664cc17..77c310fc9e 100644 --- a/apps/files/l10n/tr.php +++ b/apps/files/l10n/tr.php @@ -1,6 +1,6 @@ "%s taşınamadı - Bu isimde dosya zaten var", +"Could not move %s - File with this name already exists" => "%s taşınamadı. Bu isimde dosya zaten mevcut", "Could not move %s" => "%s taşınamadı", "File name cannot be empty." => "Dosya adı boş olamaz.", "\"%s\" is an invalid file name." => "'%s' geçersiz bir dosya adı.", @@ -35,7 +35,6 @@ $TRANSLATIONS = array( "Could not get result from server." => "Sunucudan sonuç alınamadı.", "File upload is in progress. Leaving the page now will cancel the upload." => "Dosya yükleme işlemi sürüyor. Şimdi sayfadan ayrılırsanız işleminiz iptal olur.", "URL cannot be empty" => "URL boş olamaz", -"In the home folder 'Shared' is a reserved filename" => "Ev klasöründeki 'Paylaşılan', ayrılmış bir dosya adıdır", "{new_name} already exists" => "{new_name} zaten mevcut", "Could not create file" => "Dosya oluşturulamadı", "Could not create folder" => "Klasör oluşturulamadı", @@ -62,9 +61,8 @@ $TRANSLATIONS = array( "Name" => "İsim", "Size" => "Boyut", "Modified" => "Değiştirilme", -"Invalid folder name. Usage of 'Shared' is reserved." => "Geçersiz klasör adı. 'Shared' ismi ayrılmıştır.", "%s could not be renamed" => "%s yeniden adlandırılamadı", -"Upload" => "Yükle", +"Upload (max. %s)" => "Yükle (azami: %s)", "File handling" => "Dosya işlemleri", "Maximum upload size" => "Maksimum yükleme boyutu", "max. possible: " => "mümkün olan en fazla: ", diff --git a/apps/files/l10n/ug.php b/apps/files/l10n/ug.php index 13354c153b..58ccba94c1 100644 --- a/apps/files/l10n/ug.php +++ b/apps/files/l10n/ug.php @@ -21,7 +21,6 @@ $TRANSLATIONS = array( "Name" => "ئاتى", "Size" => "چوڭلۇقى", "Modified" => "ئۆزگەرتكەن", -"Upload" => "يۈكلە", "Save" => "ساقلا", "New" => "يېڭى", "Text file" => "تېكىست ھۆججەت", diff --git a/apps/files/l10n/uk.php b/apps/files/l10n/uk.php index 905d27c3ee..5643dedb9d 100644 --- a/apps/files/l10n/uk.php +++ b/apps/files/l10n/uk.php @@ -40,7 +40,6 @@ $TRANSLATIONS = array( "Size" => "Розмір", "Modified" => "Змінено", "%s could not be renamed" => "%s не може бути перейменований", -"Upload" => "Вивантажити", "File handling" => "Робота з файлами", "Maximum upload size" => "Максимальний розмір відвантажень", "max. possible: " => "макс.можливе:", diff --git a/apps/files/l10n/vi.php b/apps/files/l10n/vi.php index 55f9bd2594..058add4bb7 100644 --- a/apps/files/l10n/vi.php +++ b/apps/files/l10n/vi.php @@ -55,7 +55,6 @@ $TRANSLATIONS = array( "Size" => "Kích cỡ", "Modified" => "Thay đổi", "%s could not be renamed" => "%s không thể đổi tên", -"Upload" => "Tải lên", "File handling" => "Xử lý tập tin", "Maximum upload size" => "Kích thước tối đa ", "max. possible: " => "tối đa cho phép:", diff --git a/apps/files/l10n/zh_CN.php b/apps/files/l10n/zh_CN.php index 068f97c1dd..82cc68a499 100644 --- a/apps/files/l10n/zh_CN.php +++ b/apps/files/l10n/zh_CN.php @@ -31,7 +31,6 @@ $TRANSLATIONS = array( "Could not get result from server." => "不能从服务器得到结果", "File upload is in progress. Leaving the page now will cancel the upload." => "文件正在上传中。现在离开此页会导致上传动作被取消。", "URL cannot be empty" => "URL不能为空", -"In the home folder 'Shared' is a reserved filename" => "主目录里 'Shared' 是系统预留目录名", "{new_name} already exists" => "{new_name} 已存在", "Could not create file" => "不能创建文件", "Could not create folder" => "不能创建文件夹", @@ -57,9 +56,7 @@ $TRANSLATIONS = array( "Name" => "名称", "Size" => "大小", "Modified" => "修改日期", -"Invalid folder name. Usage of 'Shared' is reserved." => "无效的文件夹名。”Shared“ 是 Owncloud 预留的文件夹", "%s could not be renamed" => "%s 不能被重命名", -"Upload" => "上传", "File handling" => "文件处理", "Maximum upload size" => "最大上传大小", "max. possible: " => "最大允许: ", diff --git a/apps/files/l10n/zh_HK.php b/apps/files/l10n/zh_HK.php index eaa32cd537..13aa6ac3e3 100644 --- a/apps/files/l10n/zh_HK.php +++ b/apps/files/l10n/zh_HK.php @@ -7,8 +7,9 @@ $TRANSLATIONS = array( "_%n file_::_%n files_" => array(""), "_Uploading %n file_::_Uploading %n files_" => array(""), "Name" => "名稱", -"Upload" => "上傳", +"Size" => "大小", "Save" => "儲存", +"New folder" => "新文件夾", "Download" => "下載", "Delete" => "刪除" ); diff --git a/apps/files/l10n/zh_TW.php b/apps/files/l10n/zh_TW.php index 154efd563f..92bc13189d 100644 --- a/apps/files/l10n/zh_TW.php +++ b/apps/files/l10n/zh_TW.php @@ -30,7 +30,6 @@ $TRANSLATIONS = array( "Could not get result from server." => "無法從伺服器取回結果", "File upload is in progress. Leaving the page now will cancel the upload." => "檔案上傳中,離開此頁面將會取消上傳。", "URL cannot be empty" => "URL 不能留空", -"In the home folder 'Shared' is a reserved filename" => "在家目錄中不能使用「共享」作為檔名", "{new_name} already exists" => "{new_name} 已經存在", "Could not create file" => "無法建立檔案", "Could not create folder" => "無法建立資料夾", @@ -55,7 +54,6 @@ $TRANSLATIONS = array( "Size" => "大小", "Modified" => "修改時間", "%s could not be renamed" => "無法重新命名 %s", -"Upload" => "上傳", "File handling" => "檔案處理", "Maximum upload size" => "上傳限制", "max. possible: " => "最大允許:", diff --git a/apps/files/lib/app.php b/apps/files/lib/app.php index adfca66957..ed4aa32c66 100644 --- a/apps/files/lib/app.php +++ b/apps/files/lib/app.php @@ -54,13 +54,8 @@ class App { 'data' => NULL ); - // rename to "/Shared" is denied - if( $dir === '/' and $newname === 'Shared' ) { - $result['data'] = array( - 'message' => $this->l10n->t("Invalid folder name. Usage of 'Shared' is reserved.") - ); // rename to non-existing folder is denied - } else if (!$this->view->file_exists($dir)) { + if (!$this->view->file_exists($dir)) { $result['data'] = array('message' => (string)$this->l10n->t( 'The target folder has been moved or deleted.', array($dir)), @@ -68,7 +63,7 @@ class App { ); // rename to existing file is denied } else if ($this->view->file_exists($dir . '/' . $newname)) { - + $result['data'] = array( 'message' => $this->l10n->t( "The name %s is already used in the folder %s. Please choose a different name.", @@ -77,8 +72,6 @@ class App { } else if ( // rename to "." is denied $newname !== '.' and - // rename of "/Shared" is denied - !($dir === '/' and $oldname === 'Shared') and // THEN try to rename $this->view->rename($dir . '/' . $oldname, $dir . '/' . $newname) ) { diff --git a/apps/files/lib/helper.php b/apps/files/lib/helper.php index b765fdaf3e..0ae87d12fb 100644 --- a/apps/files/lib/helper.php +++ b/apps/files/lib/helper.php @@ -11,7 +11,7 @@ class Helper $l = new \OC_L10N('files'); $maxUploadFilesize = \OCP\Util::maxUploadFilesize($dir, $storageInfo['free']); $maxHumanFilesize = \OCP\Util::humanFileSize($maxUploadFilesize); - $maxHumanFilesize = $l->t('Upload') . ' max. ' . $maxHumanFilesize; + $maxHumanFilesize = $l->t('Upload (max. %s)', array($maxHumanFilesize)); return array('uploadMaxFilesize' => $maxUploadFilesize, 'maxHumanFilesize' => $maxHumanFilesize, @@ -37,8 +37,7 @@ class Helper $sid = explode(':', $sid); if ($sid[0] === 'shared') { $icon = \OC_Helper::mimetypeIcon('dir-shared'); - } - if ($sid[0] !== 'local' and $sid[0] !== 'home') { + } elseif ($sid[0] !== 'local' and $sid[0] !== 'home') { $icon = \OC_Helper::mimetypeIcon('dir-external'); } } @@ -73,13 +72,14 @@ class Helper /** * Formats the file info to be returned as JSON to the client. * - * @param \OCP\Files\FileInfo file info + * @param \OCP\Files\FileInfo $i * @return array formatted file info */ public static function formatFileInfo($i) { $entry = array(); $entry['id'] = $i['fileid']; + $entry['parentId'] = $i['parent']; $entry['date'] = \OCP\Util::formatDate($i['mtime']); $entry['mtime'] = $i['mtime'] * 1000; // only pick out the needed attributes @@ -96,6 +96,9 @@ class Helper if (isset($i['displayname_owner'])) { $entry['shareOwner'] = $i['displayname_owner']; } + if (isset($i['is_share_mount_point'])) { + $entry['isShareMountPoint'] = $i['is_share_mount_point']; + } return $entry; } diff --git a/apps/files/templates/index.php b/apps/files/templates/index.php index 95edd625cb..a8437835d9 100644 --- a/apps/files/templates/index.php +++ b/apps/files/templates/index.php @@ -19,7 +19,7 @@
+ title="t('Upload (max. %s)', array($_['uploadMaxHumanFilesize']))) ?>"> = 0):?> diff --git a/apps/files/tests/ajax_rename.php b/apps/files/tests/ajax_rename.php index cb62d22a7e..74ca1e4495 100644 --- a/apps/files/tests/ajax_rename.php +++ b/apps/files/tests/ajax_rename.php @@ -55,88 +55,6 @@ class Test_OC_Files_App_Rename extends \PHPUnit_Framework_TestCase { \OC\Files\Filesystem::tearDown(); } - /** - * @brief test rename of file/folder named "Shared" - */ - function testRenameSharedFolder() { - $dir = '/'; - $oldname = 'Shared'; - $newname = 'new_name'; - - $this->viewMock->expects($this->at(0)) - ->method('file_exists') - ->with('/') - ->will($this->returnValue(true)); - - $result = $this->files->rename($dir, $oldname, $newname); - $expected = array( - 'success' => false, - 'data' => array('message' => '%s could not be renamed') - ); - - $this->assertEquals($expected, $result); - } - - /** - * @brief test rename of file/folder named "Shared" - */ - function testRenameSharedFolderInSubdirectory() { - $dir = '/test'; - $oldname = 'Shared'; - $newname = 'new_name'; - - $this->viewMock->expects($this->at(0)) - ->method('file_exists') - ->with('/test') - ->will($this->returnValue(true)); - - $this->viewMock->expects($this->any()) - ->method('getFileInfo') - ->will($this->returnValue(new \OC\Files\FileInfo( - '/test', - null, - '/test', - array( - 'fileid' => 123, - 'type' => 'dir', - 'mimetype' => 'httpd/unix-directory', - 'mtime' => 0, - 'permissions' => 31, - 'size' => 18, - 'etag' => 'abcdef', - 'directory' => '/', - 'name' => 'new_name', - )))); - - $result = $this->files->rename($dir, $oldname, $newname); - - $this->assertTrue($result['success']); - $this->assertEquals(123, $result['data']['id']); - $this->assertEquals('new_name', $result['data']['name']); - $this->assertEquals(18, $result['data']['size']); - $this->assertEquals('httpd/unix-directory', $result['data']['mimetype']); - $icon = \OC_Helper::mimetypeIcon('dir'); - $icon = substr($icon, 0, -3) . 'svg'; - $this->assertEquals($icon, $result['data']['icon']); - } - - /** - * @brief test rename of file/folder to "Shared" - */ - function testRenameFolderToShared() { - $dir = '/'; - $oldname = 'oldname'; - $newname = 'Shared'; - - $result = $this->files->rename($dir, $oldname, $newname); - $expected = array( - 'success' => false, - 'data' => array('message' => "Invalid folder name. Usage of 'Shared' is reserved.") - ); - - $this->assertEquals($expected, $result); - } - /** * @brief test rename of file/folder */ diff --git a/apps/files/tests/js/filesSpec.js b/apps/files/tests/js/filesSpec.js index 95bf87e03e..018c8ef0f3 100644 --- a/apps/files/tests/js/filesSpec.js +++ b/apps/files/tests/js/filesSpec.js @@ -48,41 +48,6 @@ describe('Files tests', function() { expect(error).toEqual(false); } }); - it('Validates correct file names do not create Shared folder in root', function() { - // create shared file in subfolder - var error = false; - try { - expect(Files.isFileNameValid('shared', '/foo')).toEqual(true); - expect(Files.isFileNameValid('Shared', '/foo')).toEqual(true); - } - catch (e) { - error = e; - } - expect(error).toEqual(false); - - // create shared file in root - var threwException = false; - try { - Files.isFileNameValid('Shared', '/'); - console.error('Invalid file name not detected'); - } - catch (e) { - threwException = true; - } - expect(threwException).toEqual(true); - - // create shared file in root - var threwException = false; - try { - Files.isFileNameValid('shared', '/'); - console.error('Invalid file name not detected'); - } - catch (e) { - threwException = true; - } - expect(threwException).toEqual(true); - - }); it('Detects invalid file names', function() { var fileNames = [ '', diff --git a/apps/files_encryption/hooks/hooks.php b/apps/files_encryption/hooks/hooks.php index 0b6c5adf3f..5f0494e62c 100644 --- a/apps/files_encryption/hooks/hooks.php +++ b/apps/files_encryption/hooks/hooks.php @@ -302,25 +302,6 @@ class Hooks { */ public static function postShared($params) { - // NOTE: $params has keys: - // [itemType] => file - // itemSource -> int, filecache file ID - // [parent] => - // [itemTarget] => /13 - // shareWith -> string, uid of user being shared to - // fileTarget -> path of file being shared - // uidOwner -> owner of the original file being shared - // [shareType] => 0 - // [shareWith] => test1 - // [uidOwner] => admin - // [permissions] => 17 - // [fileSource] => 13 - // [fileTarget] => /test8 - // [id] => 10 - // [token] => - // [run] => whether emitting script should continue to run - // TODO: Should other kinds of item be encrypted too? - if (\OCP\App::isEnabled('files_encryption') === false) { return true; } @@ -331,71 +312,22 @@ class Hooks { $session = new \OCA\Encryption\Session($view); $userId = \OCP\User::getUser(); $util = new Util($view, $userId); - $path = $util->fileIdToPath($params['itemSource']); - - $share = $util->getParentFromShare($params['id']); - //if parent is set, then this is a re-share action - if ($share['parent'] !== null) { - - // get the parent from current share - $parent = $util->getShareParent($params['parent']); - - // if parent has the same type than the child it is a 1:1 share - if ($parent['item_type'] === $params['itemType']) { - - // prefix path with Shared - $path = '/Shared' . $parent['file_target']; - } else { - - // NOTE: parent is folder but shared was a file! - // we try to rebuild the missing path - // some examples we face here - // user1 share folder1 with user2 folder1 has - // the following structure - // /folder1/subfolder1/subsubfolder1/somefile.txt - // user2 re-share subfolder2 with user3 - // user3 re-share somefile.txt user4 - // so our path should be - // /Shared/subfolder1/subsubfolder1/somefile.txt - // while user3 is sharing - - if ($params['itemType'] === 'file') { - // get target path - $targetPath = $util->fileIdToPath($params['fileSource']); - $targetPathSplit = array_reverse(explode('/', $targetPath)); - - // init values - $path = ''; - $sharedPart = ltrim($parent['file_target'], '/'); - - // rebuild path - foreach ($targetPathSplit as $pathPart) { - if ($pathPart !== $sharedPart) { - $path = '/' . $pathPart . $path; - } else { - break; - } - } - // prefix path with Shared - $path = '/Shared' . $parent['file_target'] . $path; - } else { - // prefix path with Shared - $path = '/Shared' . $parent['file_target'] . $params['fileTarget']; - } - } - } + $path = \OC\Files\Filesystem::getPath($params['fileSource']); $sharingEnabled = \OCP\Share::isEnabled(); // get the path including mount point only if not a shared folder - if (strncmp($path, '/Shared', strlen('/Shared') !== 0)) { - // get path including the the storage mount point - $path = $util->getPathWithMountPoint($params['itemSource']); + list($storage, ) = \OC\Files\Filesystem::resolvePath('/' . $userId . '/files' . $path); + + if (!($storage instanceof \OC\Files\Storage\Local)) { + $mountPoint = 'files' . $storage->getMountPoint(); + } else { + $mountPoint = ''; } // if a folder was shared, get a list of all (sub-)folders if ($params['itemType'] === 'folder') { - $allFiles = $util->getAllFiles($path); + $allFiles = $util->getAllFiles($path, $mountPoint); } else { $allFiles = array($path); } @@ -412,13 +344,6 @@ class Hooks { */ public static function postUnshare($params) { - // NOTE: $params has keys: - // [itemType] => file - // [itemSource] => 13 - // [shareType] => 0 - // [shareWith] => test1 - // [itemParent] => - if (\OCP\App::isEnabled('files_encryption') === false) { return true; } @@ -428,34 +353,7 @@ class Hooks { $view = new \OC_FilesystemView('/'); $userId = \OCP\User::getUser(); $util = new Util($view, $userId); - $path = $util->fileIdToPath($params['itemSource']); - - // check if this is a re-share - if ($params['itemParent']) { - - // get the parent from current share - $parent = $util->getShareParent($params['itemParent']); - - // get target path - $targetPath = $util->fileIdToPath($params['itemSource']); - $targetPathSplit = array_reverse(explode('/', $targetPath)); - - // init values - $path = ''; - $sharedPart = ltrim($parent['file_target'], '/'); - - // rebuild path - foreach ($targetPathSplit as $pathPart) { - if ($pathPart !== $sharedPart) { - $path = '/' . $pathPart . $path; - } else { - break; - } - } - - // prefix path with Shared - $path = '/Shared' . $parent['file_target'] . $path; - } + $path = \OC\Files\Filesystem::getPath($params['fileSource']); // for group shares get a list of the group members if ($params['shareType'] === \OCP\Share::SHARE_TYPE_GROUP) { @@ -469,14 +367,17 @@ class Hooks { } // get the path including mount point only if not a shared folder - if (strncmp($path, '/Shared', strlen('/Shared') !== 0)) { - // get path including the the storage mount point - $path = $util->getPathWithMountPoint($params['itemSource']); + list($storage, ) = \OC\Files\Filesystem::resolvePath('/' . $userId . '/files' . $path); + + if (!($storage instanceof \OC\Files\Storage\Local)) { + $mountPoint = 'files' . $storage->getMountPoint(); + } else { + $mountPoint = ''; } // if we unshare a folder we need a list of all (sub-)files if ($params['itemType'] === 'folder') { - $allFiles = $util->getAllFiles($path); + $allFiles = $util->getAllFiles($path, $mountPoint); } else { $allFiles = array($path); } @@ -510,6 +411,8 @@ class Hooks { // otherwise we perform a stream copy, so we get a new set of keys $mp1 = $view->getMountPoint('/' . $user . '/files/' . $params['oldpath']); $mp2 = $view->getMountPoint('/' . $user . '/files/' . $params['newpath']); + list($storage1, ) = Filesystem::resolvePath($params['oldpath']); + if ($mp1 === $mp2) { self::$renamedFiles[$params['oldpath']] = array( 'uid' => $ownerOld, diff --git a/apps/files_encryption/l10n/es_AR.php b/apps/files_encryption/l10n/es_AR.php index bc1eedbf5e..b885716f89 100644 --- a/apps/files_encryption/l10n/es_AR.php +++ b/apps/files_encryption/l10n/es_AR.php @@ -16,6 +16,7 @@ $TRANSLATIONS = array( "Please make sure that PHP 5.3.3 or newer is installed and that OpenSSL together with the PHP extension is enabled and configured properly. For now, the encryption app has been disabled." => "Por favor, asegúrese de que PHP 5.3.3 o una versión más reciente esté instalado y que OpenSSL junto con la extensión PHP esté habilitado y configurado apropiadamente. Por ahora, la aplicación de encriptación ha sido deshabilitada.", "Following users are not set up for encryption:" => "Los siguientes usuarios no fueron configurados para encriptar:", "Initial encryption started... This can take some time. Please wait." => "Encriptación inicial comenzada... Esto puede durar un tiempo. Por favor espere.", +"Initial encryption running... Please try again later." => "Encriptación inicial corriendo... Por favor intente mas tarde. ", "Go directly to your " => "Ve directamente a tu", "personal settings" => "Configuración personal", "Encryption" => "Encriptación", diff --git a/apps/files_encryption/l10n/pt_PT.php b/apps/files_encryption/l10n/pt_PT.php index d6d3c26036..9a1963953e 100644 --- a/apps/files_encryption/l10n/pt_PT.php +++ b/apps/files_encryption/l10n/pt_PT.php @@ -7,8 +7,11 @@ $TRANSLATIONS = array( "Password successfully changed." => "Password alterada com sucesso.", "Could not change the password. Maybe the old password was not correct." => "Não foi possivel alterar a password. Possivelmente a password antiga não está correcta.", "Could not update the private key password. Maybe the old password was not correct." => "Não foi possível alterar a chave. Possivelmente a password antiga não está correcta.", +"Can not decrypt this file, probably this is a shared file. Please ask the file owner to reshare the file with you." => "Não foi possível desencriptar este ficheiro, possivelmente é um ficheiro partilhado. Peça ao proprietário do ficheiro para voltar a partilhar o ficheiro consigo.", +"Unknown error please check your system settings or contact your administrator" => "Erro desconhecido. Verifique por favor as definições do sistema ou contacte o seu administrador", "Missing requirements." => "Faltam alguns requisitos.", "Following users are not set up for encryption:" => "Os utilizadores seguintes não estão marcados para cifragem:", +"Go directly to your " => "Ir directamente para o seu", "personal settings" => "configurações personalizadas ", "Encryption" => "Encriptação", "Enable recovery key (allow to recover users files in case of password loss):" => "Active a chave de recuperação (permite recuperar os ficheiros no caso de perda da password):", diff --git a/apps/files_encryption/l10n/sk_SK.php b/apps/files_encryption/l10n/sk_SK.php index 5fcd0a9f06..53ee2bc030 100644 --- a/apps/files_encryption/l10n/sk_SK.php +++ b/apps/files_encryption/l10n/sk_SK.php @@ -16,6 +16,7 @@ $TRANSLATIONS = array( "Please make sure that PHP 5.3.3 or newer is installed and that OpenSSL together with the PHP extension is enabled and configured properly. For now, the encryption app has been disabled." => "Prosím uistite sa, že PHP verzie 5.3.3 alebo novšej je nainštalované a tiež, že OpenSSL knižnica spolu z PHP rozšírením je povolená a konfigurovaná správne. Nateraz bola aplikácia šifrovania zablokovaná.", "Following users are not set up for encryption:" => "Nasledujúci používatelia nie sú nastavení pre šifrovanie:", "Initial encryption started... This can take some time. Please wait." => "Počiatočné šifrovanie započalo ... To môže nejakú dobu trvať. Čakajte prosím.", +"Initial encryption running... Please try again later." => "Počiatočné šifrovanie beží... Skúste to neskôr znovu.", "Go directly to your " => "Choďte priamo do vášho", "personal settings" => "osobné nastavenia", "Encryption" => "Šifrovanie", diff --git a/apps/files_encryption/l10n/zh_TW.php b/apps/files_encryption/l10n/zh_TW.php index 390e6aff8c..84146437ac 100644 --- a/apps/files_encryption/l10n/zh_TW.php +++ b/apps/files_encryption/l10n/zh_TW.php @@ -15,6 +15,8 @@ $TRANSLATIONS = array( "Missing requirements." => "遺失必要條件。", "Please make sure that PHP 5.3.3 or newer is installed and that OpenSSL together with the PHP extension is enabled and configured properly. For now, the encryption app has been disabled." => "請確認已安裝 PHP 5.3.3 或是更新的版本以及 OpenSSL 也一併安裝在 PHP extension 裡面並啟用及設置完成。現在,加密功能是停用的。", "Following users are not set up for encryption:" => "以下的使用者無法設定加密:", +"Initial encryption started... This can take some time. Please wait." => "加密初始已啟用...這個需要一些時間。請稍等。", +"Initial encryption running... Please try again later." => "加密初始執行中...請晚點再試。", "Go directly to your " => "直接到您的", "personal settings" => "個人設定", "Encryption" => "加密", diff --git a/apps/files_encryption/lib/util.php b/apps/files_encryption/lib/util.php index b86815021a..6372ab31b6 100644 --- a/apps/files_encryption/lib/util.php +++ b/apps/files_encryption/lib/util.php @@ -969,33 +969,6 @@ class Util { } - /** - * @brief get path of a file. - * @param int $fileId id of the file - * @return string path of the file - */ - public static function fileIdToPath($fileId) { - - $sql = 'SELECT `path` FROM `*PREFIX*filecache` WHERE `fileid` = ?'; - - $query = \OCP\DB::prepare($sql); - - $result = $query->execute(array($fileId)); - - $path = false; - if (\OCP\DB::isError($result)) { - \OCP\Util::writeLog('Encryption library', \OC_DB::getErrorMessage($result), \OCP\Util::ERROR); - } else { - $row = $result->fetchRow(); - if ($row) { - $path = substr($row['path'], strlen('files')); - } - } - - return $path; - - } - /** * @brief Filter an array of UIDs to return only ones ready for sharing * @param array $unfilteredUsers users to be checked for sharing readiness @@ -1398,7 +1371,7 @@ class Util { * @param string $dir relative to the users files folder * @return array with list of files relative to the users files folder */ - public function getAllFiles($dir) { + public function getAllFiles($dir, $mountPoint = '') { $result = array(); $dirList = array($dir); @@ -1408,11 +1381,13 @@ class Util { $this->userFilesDir . '/' . $dir)); foreach ($content as $c) { - $usersPath = isset($c['usersPath']) ? $c['usersPath'] : $c['path']; + // getDirectoryContent() returns the paths relative to the mount points, so we need + // to re-construct the complete path + $path = ($mountPoint !== '') ? $mountPoint . '/' . $c['path'] : $c['path']; if ($c['type'] === 'dir') { - $dirList[] = substr($usersPath, strlen("files")); + $dirList[] = substr($path, strlen("files")); } else { - $result[] = substr($usersPath, strlen("files")); + $result[] = substr($path, strlen("files")); } } @@ -1421,54 +1396,6 @@ class Util { return $result; } - /** - * @brief get shares parent. - * @param int $id of the current share - * @return array of the parent - */ - public static function getShareParent($id) { - - $sql = 'SELECT `file_target`, `item_type` FROM `*PREFIX*share` WHERE `id` = ?'; - - $query = \OCP\DB::prepare($sql); - - $result = $query->execute(array($id)); - - $row = array(); - if (\OCP\DB::isError($result)) { - \OCP\Util::writeLog('Encryption library', \OC_DB::getErrorMessage($result), \OCP\Util::ERROR); - } else { - $row = $result->fetchRow(); - } - - return $row; - - } - - /** - * @brief get shares parent. - * @param int $id of the current share - * @return array of the parent - */ - public static function getParentFromShare($id) { - - $sql = 'SELECT `parent` FROM `*PREFIX*share` WHERE `id` = ?'; - - $query = \OCP\DB::prepare($sql); - - $result = $query->execute(array($id)); - - $row = array(); - if (\OCP\DB::isError($result)) { - \OCP\Util::writeLog('Encryption library', \OC_DB::getErrorMessage($result), \OCP\Util::ERROR); - } else { - $row = $result->fetchRow(); - } - - return $row; - - } - /** * @brief get owner of the shared files. * @param $id @@ -1710,23 +1637,6 @@ class Util { $this->recoverAllFiles('/', $privateKey); } - /** - * Get the path including the storage mount point - * @param int $id - * @return string the path including the mount point like AmazonS3/folder/file.txt - */ - public function getPathWithMountPoint($id) { - list($storage, $internalPath) = \OC\Files\Cache\Cache::getById($id); - $mount = \OC\Files\Filesystem::getMountByStorageId($storage); - $mountPoint = $mount[0]->getMountPoint(); - $path = \OC\Files\Filesystem::normalizePath($mountPoint . '/' . $internalPath); - - // reformat the path to be relative e.g. /user/files/folder becomes /folder/ - $relativePath = \OCA\Encryption\Helper::stripUserFilesPath($path); - - return $relativePath; - } - /** * @brief check if the file is stored on a system wide mount point * @param $path relative to /data/user with leading '/' diff --git a/apps/files_encryption/tests/hooks.php b/apps/files_encryption/tests/hooks.php index d0e4b5f732..047084ca2c 100644 --- a/apps/files_encryption/tests/hooks.php +++ b/apps/files_encryption/tests/hooks.php @@ -219,18 +219,20 @@ class Test_Encryption_Hooks extends \PHPUnit_Framework_TestCase { \Test_Encryption_Util::loginHelper(\Test_Encryption_Hooks::TEST_ENCRYPTION_HOOKS_USER2); \OC_User::setUserId(\Test_Encryption_Hooks::TEST_ENCRYPTION_HOOKS_USER2); - // user2 has a local file with the same name + // user2 update the shared file $this->user2View->file_put_contents($this->filename, $this->data); - // check if all keys are generated - $this->assertTrue($this->rootView->file_exists( + // keys should be stored at user1s dir, not in user2s + $this->assertFalse($this->rootView->file_exists( self::TEST_ENCRYPTION_HOOKS_USER2 . '/files_encryption/share-keys/' . $this->filename . '.' . \Test_Encryption_Hooks::TEST_ENCRYPTION_HOOKS_USER2 . '.shareKey')); - $this->assertTrue($this->rootView->file_exists( + $this->assertFalse($this->rootView->file_exists( self::TEST_ENCRYPTION_HOOKS_USER2 . '/files_encryption/keyfiles/' . $this->filename . '.key')); // delete the Shared file from user1 in data/user2/files/Shared - $this->user2View->unlink('/Shared/' . $this->filename); + $result = $this->user2View->unlink($this->filename); + + $this->assertTrue($result); // now keys from user1s home should be gone $this->assertFalse($this->rootView->file_exists( @@ -242,26 +244,12 @@ class Test_Encryption_Hooks extends \PHPUnit_Framework_TestCase { $this->assertFalse($this->rootView->file_exists( self::TEST_ENCRYPTION_HOOKS_USER1 . '/files_encryption/keyfiles/' . $this->filename . '.key')); - // but user2 keys should still exist - $this->assertTrue($this->rootView->file_exists( - self::TEST_ENCRYPTION_HOOKS_USER2 . '/files_encryption/share-keys/' - . $this->filename . '.' . \Test_Encryption_Hooks::TEST_ENCRYPTION_HOOKS_USER2 . '.shareKey')); - $this->assertTrue($this->rootView->file_exists( - self::TEST_ENCRYPTION_HOOKS_USER2 . '/files_encryption/keyfiles/' . $this->filename . '.key')); - // cleanup - $this->user2View->unlink($this->filename); - \Test_Encryption_Util::logoutHelper(); \Test_Encryption_Util::loginHelper(\Test_Encryption_Hooks::TEST_ENCRYPTION_HOOKS_USER1); \OC_User::setUserId(\Test_Encryption_Hooks::TEST_ENCRYPTION_HOOKS_USER1); - // unshare the file - \OCP\Share::unshare('file', $fileInfo['fileid'], \OCP\Share::SHARE_TYPE_USER, self::TEST_ENCRYPTION_HOOKS_USER2); - - $this->user1View->unlink($this->filename); - if ($stateFilesTrashbin) { OC_App::enable('files_trashbin'); } diff --git a/apps/files_encryption/tests/share.php b/apps/files_encryption/tests/share.php index 1f57d7cb63..512671c576 100755 --- a/apps/files_encryption/tests/share.php +++ b/apps/files_encryption/tests/share.php @@ -175,7 +175,7 @@ class Test_Encryption_Share extends \PHPUnit_Framework_TestCase { // get file contents $retrievedCryptedFile = $this->view->file_get_contents( - '/' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER2 . '/files/Shared/' . $this->filename); + '/' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER2 . '/files/' . $this->filename); // check if data is the same as we previously written $this->assertEquals($this->dataShort, $retrievedCryptedFile); @@ -213,14 +213,14 @@ class Test_Encryption_Share extends \PHPUnit_Framework_TestCase { function testReShareFile($withTeardown = true) { $this->testShareFile(false); - // login as user1 + // login as user2 \Test_Encryption_Util::loginHelper(\Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER2); // get the file info $fileInfo = $this->view->getFileInfo( - '/' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER2 . '/files/Shared/' . $this->filename); + '/' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER2 . '/files/' . $this->filename); - // share the file with user2 + // share the file with user3 \OCP\Share::shareItem('file', $fileInfo['fileid'], \OCP\Share::SHARE_TYPE_USER, \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER3, OCP\PERMISSION_ALL); // login as admin @@ -236,7 +236,7 @@ class Test_Encryption_Share extends \PHPUnit_Framework_TestCase { // get file contents $retrievedCryptedFile = $this->view->file_get_contents( - '/' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER3 . '/files/Shared/' . $this->filename); + '/' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER3 . '/files/' . $this->filename); // check if data is the same as previously written $this->assertEquals($this->dataShort, $retrievedCryptedFile); @@ -333,7 +333,7 @@ class Test_Encryption_Share extends \PHPUnit_Framework_TestCase { // get file contents $retrievedCryptedFile = $this->view->file_get_contents( - '/' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER2 . '/files/Shared' . $this->folder1 + '/' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER2 . '/files/' . $this->folder1 . $this->subfolder . $this->subsubfolder . '/' . $this->filename); // check if data is the same @@ -376,7 +376,7 @@ class Test_Encryption_Share extends \PHPUnit_Framework_TestCase { function testReShareFolder($withTeardown = true) { $fileInfoFolder1 = $this->testShareFolder(false); - // login as user1 + // login as user2 \Test_Encryption_Util::loginHelper(\Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER2); // disable encryption proxy to prevent recursive calls @@ -385,7 +385,7 @@ class Test_Encryption_Share extends \PHPUnit_Framework_TestCase { // get the file info from previous created folder $fileInfoSubFolder = $this->view->getFileInfo( - '/' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER2 . '/files/Shared' . $this->folder1 + '/' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER2 . '/files/' . $this->folder1 . $this->subfolder); // check if we have a valid file info @@ -394,24 +394,24 @@ class Test_Encryption_Share extends \PHPUnit_Framework_TestCase { // re-enable the file proxy \OC_FileProxy::$enabled = $proxyStatus; - // share the file with user2 + // share the file with user3 \OCP\Share::shareItem('folder', $fileInfoSubFolder['fileid'], \OCP\Share::SHARE_TYPE_USER, \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER3, OCP\PERMISSION_ALL); // login as admin \Test_Encryption_Util::loginHelper(\Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER1); - // check if share key for user2 exists + // check if share key for user3 exists $this->assertTrue($this->view->file_exists( '/' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER1 . '/files_encryption/share-keys' . $this->folder1 . $this->subfolder . $this->subsubfolder . '/' . $this->filename . '.' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER3 . '.shareKey')); - // login as user2 + // login as user3 \Test_Encryption_Util::loginHelper(\Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER3); // get file contents $retrievedCryptedFile = $this->view->file_get_contents( - '/' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER3 . '/files/Shared' . $this->subfolder + '/' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER3 . '/files/' . $this->subfolder . $this->subsubfolder . '/' . $this->filename); // check if data is the same @@ -419,7 +419,7 @@ class Test_Encryption_Share extends \PHPUnit_Framework_TestCase { // get the file info $fileInfo = $this->view->getFileInfo( - '/' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER3 . '/files/Shared' . $this->subfolder + '/' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER3 . '/files/' . $this->subfolder . $this->subsubfolder . '/' . $this->filename); // check if we have fileInfos @@ -442,7 +442,7 @@ class Test_Encryption_Share extends \PHPUnit_Framework_TestCase { // get file contents $retrievedCryptedFile = $this->view->file_get_contents( - '/' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER4 . '/files/Shared/' . $this->filename); + '/' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER4 . '/files/' . $this->filename); // check if data is the same $this->assertEquals($this->dataShort, $retrievedCryptedFile); @@ -624,7 +624,7 @@ class Test_Encryption_Share extends \PHPUnit_Framework_TestCase { // get file contents $retrievedCryptedFile = $this->view->file_get_contents( - '/' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER3 . '/files/Shared/' . $this->filename); + '/' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER3 . '/files/' . $this->filename); // check if data is the same as we previously written $this->assertEquals($this->dataShort, $retrievedCryptedFile); @@ -676,6 +676,7 @@ class Test_Encryption_Share extends \PHPUnit_Framework_TestCase { // enable recovery for admin $this->assertTrue($util->setRecoveryForUser(1)); + $util->addRecoveryKeys(); // create folder structure $this->view->mkdir('/' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER1 . '/files' . $this->folder1); @@ -981,7 +982,7 @@ class Test_Encryption_Share extends \PHPUnit_Framework_TestCase { // share the file \OCP\Share::shareItem('file', $fileInfo['fileid'], \OCP\Share::SHARE_TYPE_USER, \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER2, OCP\PERMISSION_ALL); - // check if share key for user2exists + // check if share key for user2 exists $this->assertTrue($this->view->file_exists( '/' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER1 . '/files_encryption/share-keys/' . $this->filename . '.' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER2 . '.shareKey')); @@ -990,31 +991,29 @@ class Test_Encryption_Share extends \PHPUnit_Framework_TestCase { // login as user2 \Test_Encryption_Util::loginHelper(\Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER2); - $this->assertTrue($this->view->file_exists('/' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER2 . '/files/Shared/' . $this->filename)); + $this->assertTrue($this->view->file_exists('/' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER2 . '/files/' . $this->filename)); // get file contents $retrievedCryptedFile = $this->view->file_get_contents( - '/' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER2 . '/files/Shared/' . $this->filename); + '/' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER2 . '/files/' . $this->filename); // check if data is the same as we previously written $this->assertEquals($this->dataShort, $retrievedCryptedFile); - // move the file out of the shared folder - $this->view->rename('/' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER2 . '/files/Shared/' . $this->filename, - '/' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER2 . '/files/' . $this->filename); + // move the file to a subfolder + $this->view->rename('/' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER2 . '/files/' . $this->filename, + '/' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER2 . '/files/' . $this->folder1 . $this->filename); // check if we can read the moved file $retrievedRenamedFile = $this->view->file_get_contents( - '/' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER2 . '/files/' . $this->filename); + '/' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER2 . '/files/' . $this->folder1 . $this->filename); // check if data is the same as we previously written $this->assertEquals($this->dataShort, $retrievedRenamedFile); - // the owners file should be deleted - $this->assertFalse($this->view->file_exists('/' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER1 . '/files/' . $this->filename)); - // cleanup - $this->view->unlink('/' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER2 . '/files/' . $this->filename); + \Test_Encryption_Util::loginHelper(\Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER1); + $this->view->unlink('/' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER1 . '/files/' . $this->filename); } } diff --git a/apps/files_encryption/tests/util.php b/apps/files_encryption/tests/util.php index 203ba55dbf..a29ef831a5 100755 --- a/apps/files_encryption/tests/util.php +++ b/apps/files_encryption/tests/util.php @@ -510,7 +510,11 @@ class Test_Encryption_Util extends \PHPUnit_Framework_TestCase { */ public static function loginHelper($user, $create = false, $password = false) { if ($create) { - \OC_User::createUser($user, $user); + try { + \OC_User::createUser($user, $user); + } catch(\Exception $e) { // catch username is already being used from previous aborted runs + + } } if ($password === false) { @@ -520,8 +524,8 @@ class Test_Encryption_Util extends \PHPUnit_Framework_TestCase { \OC_Util::tearDownFS(); \OC_User::setUserId(''); \OC\Files\Filesystem::tearDown(); - \OC_Util::setupFS($user); \OC_User::setUserId($user); + \OC_Util::setupFS($user); $params['uid'] = $user; $params['password'] = $password; diff --git a/apps/files_encryption/tests/webdav.php b/apps/files_encryption/tests/webdav.php index 8e8b9c53ce..1fe4c13d59 100755 --- a/apps/files_encryption/tests/webdav.php +++ b/apps/files_encryption/tests/webdav.php @@ -33,6 +33,7 @@ use OCA\Encryption; /** * Class Test_Encryption_Webdav + * * @brief this class provide basic webdav tests for PUT,GET and DELETE */ class Test_Encryption_Webdav extends \PHPUnit_Framework_TestCase { @@ -48,6 +49,8 @@ class Test_Encryption_Webdav extends \PHPUnit_Framework_TestCase { public $dataShort; public $stateFilesTrashbin; + private static $storage; + public static function setUpBeforeClass() { // reset backend \OC_User::clearBackends(); @@ -65,6 +68,8 @@ class Test_Encryption_Webdav extends \PHPUnit_Framework_TestCase { // create test user \Test_Encryption_Util::loginHelper(\Test_Encryption_Webdav::TEST_ENCRYPTION_WEBDAV_USER1, true); + + self::$storage = new \OC\Files\Storage\Temporary(array()); } function setUp() { @@ -96,8 +101,7 @@ class Test_Encryption_Webdav extends \PHPUnit_Framework_TestCase { // reset app files_trashbin if ($this->stateFilesTrashbin) { OC_App::enable('files_trashbin'); - } - else { + } else { OC_App::disable('files_trashbin'); } } @@ -153,7 +157,7 @@ class Test_Encryption_Webdav extends \PHPUnit_Framework_TestCase { $this->assertTrue(Encryption\Crypt::isCatfileContent($encryptedContent)); // get decrypted file contents - $decrypt = file_get_contents('crypt:///' . $this->userId . '/files'. $filename); + $decrypt = file_get_contents('crypt:///' . $this->userId . '/files' . $filename); // check if file content match with the written content $this->assertEquals($this->dataShort, $decrypt); @@ -225,7 +229,12 @@ class Test_Encryption_Webdav extends \PHPUnit_Framework_TestCase { $requestBackend = new OC_Connector_Sabre_Request(); // Create ownCloud Dir - $publicDir = new OC_Connector_Sabre_Directory(''); + $root = '/' . $this->userId . '/files'; + \OC\Files\Filesystem::mount(self::$storage, array(), $root); + $view = new \OC\Files\View($root); + $publicDir = new OC_Connector_Sabre_Directory($view, $view->getFileInfo('')); + $objectTree = new \OC\Connector\Sabre\ObjectTree(); + $objectTree->init($publicDir, $view); // Fire up server $server = new Sabre_DAV_Server($publicDir); @@ -236,8 +245,9 @@ class Test_Encryption_Webdav extends \PHPUnit_Framework_TestCase { $server->addPlugin(new Sabre_DAV_Auth_Plugin($authBackend, 'ownCloud')); $server->addPlugin(new Sabre_DAV_Locks_Plugin($lockBackend)); $server->addPlugin(new Sabre_DAV_Browser_Plugin(false)); // Show something in the Browser, but no upload - $server->addPlugin(new OC_Connector_Sabre_QuotaPlugin()); + $server->addPlugin(new OC_Connector_Sabre_QuotaPlugin($view)); $server->addPlugin(new OC_Connector_Sabre_MaintenancePlugin()); + $server->debugExceptions = true; // And off we go! if ($body) { diff --git a/apps/files_external/css/settings.css b/apps/files_external/css/settings.css index 1d3489f7f5..ee2c0aae64 100644 --- a/apps/files_external/css/settings.css +++ b/apps/files_external/css/settings.css @@ -6,7 +6,7 @@ td.status > span { } td.mountPoint, td.backend { width:160px; } -td.remove>img { visibility:hidden; padding-top:13px; } +td.remove>img { visibility:hidden; padding-top:7px; } tr:hover>td.remove>img { visibility:visible; cursor:pointer; } #addMountPoint>td { border:none; } #addMountPoint>td.applicable { visibility:hidden; } @@ -15,3 +15,8 @@ tr:hover>td.remove>img { visibility:visible; cursor:pointer; } #externalStorage label > input[type="checkbox"] { margin-right: 3px; } + +#externalStorage td.applicable div.chzn-container { + position: relative; + top: 3px; +} diff --git a/apps/files_external/l10n/ast.php b/apps/files_external/l10n/ast.php index 7061cffaa8..e2c02408f3 100644 --- a/apps/files_external/l10n/ast.php +++ b/apps/files_external/l10n/ast.php @@ -1,6 +1,7 @@ "Nome de la carpeta", +"Configuration" => "Configuración", "Options" => "Opciones", "Groups" => "Grupos", "Users" => "Usuarios", diff --git a/apps/files_external/l10n/ca.php b/apps/files_external/l10n/ca.php index 50b6f55231..8411d79306 100644 --- a/apps/files_external/l10n/ca.php +++ b/apps/files_external/l10n/ca.php @@ -6,12 +6,19 @@ $TRANSLATIONS = array( "Please provide a valid Dropbox app key and secret." => "Proporcioneu una clau d'aplicació i secret vàlids per a Dropbox", "Error configuring Google Drive storage" => "Error en configurar l'emmagatzemament Google Drive", "Saved" => "Desat", +"Note: " => "Nota: ", +" and " => "i", +"Note: The cURL support in PHP is not enabled or installed. Mounting of %s is not possible. Please ask your system administrator to install it." => "Nota: El suport cURL no està activat o instal·lat a PHP. No es pot muntar %s. Demaneu a l'administrador del sistema que l'instal·li.", +"Note: The FTP support in PHP is not enabled or installed. Mounting of %s is not possible. Please ask your system administrator to install it." => "Nota: El suport FTP per PHP no està activat o no està instal·lat. No es pot muntar %s. Demaneu a l'administrador del sistema que l'instal·li.", +"Note: \"%s\" is not installed. Mounting of %s is not possible. Please ask your system administrator to install it." => "Nota: %s no està instal·lat. No es pot muntar %s. Demaneu a l'administrador del sistema que l'instal·li.", "External Storage" => "Emmagatzemament extern", "Folder name" => "Nom de la carpeta", "External storage" => "Emmagatzemament extern", "Configuration" => "Configuració", "Options" => "Options", +"Available for" => "Disponible per", "Add storage" => "Afegeix emmagatzemament", +"No user or group" => "Sense usuaris o grups", "All Users" => "Tots els usuaris", "Groups" => "Grups", "Users" => "Usuaris", diff --git a/apps/files_external/l10n/cs_CZ.php b/apps/files_external/l10n/cs_CZ.php index 6a4e09aa3a..90d64529b1 100644 --- a/apps/files_external/l10n/cs_CZ.php +++ b/apps/files_external/l10n/cs_CZ.php @@ -6,12 +6,19 @@ $TRANSLATIONS = array( "Please provide a valid Dropbox app key and secret." => "Zadejte, prosím, platný klíč a bezpečnostní frázi aplikace Dropbox.", "Error configuring Google Drive storage" => "Chyba při nastavení úložiště Google Drive", "Saved" => "Uloženo", +"Note: " => "Poznámka:", +" and " => "a", +"Note: The cURL support in PHP is not enabled or installed. Mounting of %s is not possible. Please ask your system administrator to install it." => "Poznámka: cURL podpora v PHP není povolena nebo nainstalována. Není možné připojení %s. Prosím požádejte svého správce systému ať ji nainstaluje.", +"Note: The FTP support in PHP is not enabled or installed. Mounting of %s is not possible. Please ask your system administrator to install it." => "Poznámka: FTP podpora v PHP není povolena nebo nainstalována. Není možné připojení %s. Prosím požádejte svého správce systému ať ji nainstaluje.", +"Note: \"%s\" is not installed. Mounting of %s is not possible. Please ask your system administrator to install it." => "Poznámka: \"%s\" není instalováno. Není možné připojení %s. Prosím požádejte svého správce systému o instalaci.", "External Storage" => "Externí úložiště", "Folder name" => "Název složky", "External storage" => "Externí úložiště", "Configuration" => "Nastavení", "Options" => "Možnosti", +"Available for" => "Dostupné pro", "Add storage" => "Přidat úložiště", +"No user or group" => "Žádný uživatel nebo skupina.", "All Users" => "Všichni uživatelé", "Groups" => "Skupiny", "Users" => "Uživatelé", diff --git a/apps/files_external/l10n/es_AR.php b/apps/files_external/l10n/es_AR.php index f184dbdb7d..9bcff39f01 100644 --- a/apps/files_external/l10n/es_AR.php +++ b/apps/files_external/l10n/es_AR.php @@ -5,6 +5,7 @@ $TRANSLATIONS = array( "Grant access" => "Permitir acceso", "Please provide a valid Dropbox app key and secret." => "Por favor, proporcioná un secreto y una contraseña válida para la aplicación Dropbox.", "Error configuring Google Drive storage" => "Error al configurar el almacenamiento de Google Drive", +"Saved" => "Guardado", "External Storage" => "Almacenamiento externo", "Folder name" => "Nombre de la carpeta", "External storage" => "Almacenamiento externo", diff --git a/apps/files_external/l10n/es_CL.php b/apps/files_external/l10n/es_CL.php new file mode 100644 index 0000000000..f52482cad4 --- /dev/null +++ b/apps/files_external/l10n/es_CL.php @@ -0,0 +1,5 @@ + "Nombre del directorio" +); +$PLURAL_FORMS = "nplurals=2; plural=(n != 1);"; diff --git a/apps/files_external/l10n/et_EE.php b/apps/files_external/l10n/et_EE.php index 0589d9fd51..4da749b155 100644 --- a/apps/files_external/l10n/et_EE.php +++ b/apps/files_external/l10n/et_EE.php @@ -6,6 +6,11 @@ $TRANSLATIONS = array( "Please provide a valid Dropbox app key and secret." => "Palun sisesta korrektne Dropboxi rakenduse võti ja salasõna.", "Error configuring Google Drive storage" => "Viga Google Drive'i salvestusruumi seadistamisel", "Saved" => "Salvestatud", +"Note: " => "Märkus:", +" and " => "ja", +"Note: The cURL support in PHP is not enabled or installed. Mounting of %s is not possible. Please ask your system administrator to install it." => "Märkus: cURL tugi puudub PHP paigalduses. FTP %s hoidla ühendamine pole võimalik. Palu oma süsteemihalduril paigaldata cURL tugi.", +"Note: The FTP support in PHP is not enabled or installed. Mounting of %s is not possible. Please ask your system administrator to install it." => "Märkus: FTP tugi puudub PHP paigalduses. FTP %s hoidla ühendamine pole võimalik. Palu oma süsteemihalduril paigaldata FTP tugi.", +"Note: \"%s\" is not installed. Mounting of %s is not possible. Please ask your system administrator to install it." => "Märkus: \"%s\" pole paigaldatud. Hoidla %s ühendamine pole võimalik. Palu oma süsteemihalduril paigaldata vajalik tugi.", "External Storage" => "Väline salvestuskoht", "Folder name" => "Kausta nimi", "External storage" => "Väline andmehoidla", diff --git a/apps/files_external/l10n/ia.php b/apps/files_external/l10n/ia.php index 47b0f89b8b..1873f4ad6f 100644 --- a/apps/files_external/l10n/ia.php +++ b/apps/files_external/l10n/ia.php @@ -1,5 +1,6 @@ "Nomine de dossier", "Groups" => "Gruppos", "Users" => "Usatores", "Delete" => "Deler" diff --git a/apps/files_external/l10n/sk_SK.php b/apps/files_external/l10n/sk_SK.php index de32fb5ffb..96f6241e5f 100644 --- a/apps/files_external/l10n/sk_SK.php +++ b/apps/files_external/l10n/sk_SK.php @@ -6,17 +6,25 @@ $TRANSLATIONS = array( "Please provide a valid Dropbox app key and secret." => "Zadajte platný kľúč aplikácie a heslo Dropbox", "Error configuring Google Drive storage" => "Chyba pri konfigurácii úložiska Google drive", "Saved" => "Uložené", +"Note: " => "Poznámka: ", +" and " => "a", +"Note: The cURL support in PHP is not enabled or installed. Mounting of %s is not possible. Please ask your system administrator to install it." => "Poznámka: cURL podpora v PHP nie je zapnutá alebo nainštalovaná. Pripojenie %s nie je možné. Požiadajte správcu systému, aby ju nainštaloval.", +"Note: The FTP support in PHP is not enabled or installed. Mounting of %s is not possible. Please ask your system administrator to install it." => "Poznámka: FTP podpora v PHP nie je zapnutá alebo nainštalovaná. Pripojenie %s nie je možné. Požiadajte správcu systému, aby ju nainštaloval.", +"Note: \"%s\" is not installed. Mounting of %s is not possible. Please ask your system administrator to install it." => "Poznámka: \"%s\" nie je nainštalovaná. Pripojenie %s nie je možné. Požiadajte správcu systému, aby ju nainštaloval.", "External Storage" => "Externé úložisko", "Folder name" => "Názov priečinka", "External storage" => "Externé úložisko", "Configuration" => "Nastavenia", "Options" => "Možnosti", +"Available for" => "K dispozícii pre", "Add storage" => "Pridať úložisko", +"No user or group" => "Žiadny používateľ alebo skupina", "All Users" => "Všetci používatelia", "Groups" => "Skupiny", "Users" => "Používatelia", "Delete" => "Zmazať", "Enable User External Storage" => "Povoliť externé úložisko", +"Allow users to mount the following external storage" => "Povoliť používateľom pripojiť tieto externé úložiská", "SSL root certificates" => "Koreňové SSL certifikáty", "Import Root Certificate" => "Importovať koreňový certifikát" ); diff --git a/apps/files_external/l10n/sl.php b/apps/files_external/l10n/sl.php index bb34494a5d..baab272d1d 100644 --- a/apps/files_external/l10n/sl.php +++ b/apps/files_external/l10n/sl.php @@ -6,6 +6,11 @@ $TRANSLATIONS = array( "Please provide a valid Dropbox app key and secret." => "Vpisati je treba veljaven ključ programa in kodo za Dropbox", "Error configuring Google Drive storage" => "Napaka nastavljanja shrambe Google Drive", "Saved" => "Shranjeno", +"Note: " => "Opomba: ", +" and " => "in", +"Note: The cURL support in PHP is not enabled or installed. Mounting of %s is not possible. Please ask your system administrator to install it." => "Opomba: Podpora za naslove cURL v PHP ni omogočena, ali pa ni ustrezno nameščenih programov. Priklapljanje %s ni mogoče. Za pomoč pri namestitvi se obrnite na sistemskega skrbnika.", +"Note: The FTP support in PHP is not enabled or installed. Mounting of %s is not possible. Please ask your system administrator to install it." => "Opomba: Podpora za protokol FTP v PHP ni omogočena, ali pa ni ustrezno nameščenih programov. Priklapljanje %s ni mogoče. Za pomoč pri namestitvi se obrnite na sistemskega skrbnika.", +"Note: \"%s\" is not installed. Mounting of %s is not possible. Please ask your system administrator to install it." => "Opomba: Program \"%s\" ni nameščen. Priklapljanje %s ni mogoče. Za pomoč pri namestitvi se obrnite na sistemskega skrbnika.", "External Storage" => "Zunanja podatkovna shramba", "Folder name" => "Ime mape", "External storage" => "Zunanja shramba", diff --git a/apps/files_external/l10n/sv.php b/apps/files_external/l10n/sv.php index 2992460d28..761e8f880c 100644 --- a/apps/files_external/l10n/sv.php +++ b/apps/files_external/l10n/sv.php @@ -8,9 +8,9 @@ $TRANSLATIONS = array( "Saved" => "Sparad", "Note: " => " OBS: ", " and " => "och", -"Note: The cURL support in PHP is not enabled or installed. Mounting of %s is not possible. Please ask your system administrator to install it." => " OBS: cURL stöd i PHP inte är aktiverat eller installeras. Montering av %s är inte möjlig. Be din systemadministratör för att installera det.", -"Note: The FTP support in PHP is not enabled or installed. Mounting of %s is not possible. Please ask your system administrator to install it." => " OBS: Den FTP-stöd i PHP inte är aktiverat eller installeras. Montering av %s är inte möjlig. Be din systemadministratör för att installera det.", -"Note: \"%s\" is not installed. Mounting of %s is not possible. Please ask your system administrator to install it." => " OBS: \"%s\" är inte installerat. Montering av %s är inte möjlig. Be din systemadministratör för att installera det.", +"Note: The cURL support in PHP is not enabled or installed. Mounting of %s is not possible. Please ask your system administrator to install it." => " OBS: cURL stöd i PHP inte är aktiverat eller installeras. Montering av %s är inte möjlig. Be din systemadministratör att installera det.", +"Note: The FTP support in PHP is not enabled or installed. Mounting of %s is not possible. Please ask your system administrator to install it." => " OBS: Den FTP-stöd i PHP inte är aktiverat eller installeras. Montering av %s är inte möjlig. Be din systemadministratör att installera det.", +"Note: \"%s\" is not installed. Mounting of %s is not possible. Please ask your system administrator to install it." => " OBS: \"%s\" är inte installerat. Montering av %s är inte möjlig. Be din systemadministratör att installera det.", "External Storage" => "Extern lagring", "Folder name" => "Mappnamn", "External storage" => "Extern lagring", diff --git a/apps/files_external/l10n/zh_HK.php b/apps/files_external/l10n/zh_HK.php index 76e0ed69d2..7a6c4765f9 100644 --- a/apps/files_external/l10n/zh_HK.php +++ b/apps/files_external/l10n/zh_HK.php @@ -1,5 +1,6 @@ "資料夾名稱", "Groups" => "群組", "Users" => "用戶", "Delete" => "刪除" diff --git a/apps/files_external/l10n/zh_TW.php b/apps/files_external/l10n/zh_TW.php index ac480e405f..9a99a12aaa 100644 --- a/apps/files_external/l10n/zh_TW.php +++ b/apps/files_external/l10n/zh_TW.php @@ -5,6 +5,7 @@ $TRANSLATIONS = array( "Grant access" => "允許存取", "Please provide a valid Dropbox app key and secret." => "請提供有效的 Dropbox app key 和 app secret 。", "Error configuring Google Drive storage" => "設定 Google Drive 儲存時發生錯誤", +"Saved" => "已儲存", "External Storage" => "外部儲存", "Folder name" => "資料夾名稱", "External storage" => "外部儲存", diff --git a/apps/files_external/lib/config.php b/apps/files_external/lib/config.php index 71f6ae7887..99eca2f38c 100755 --- a/apps/files_external/lib/config.php +++ b/apps/files_external/lib/config.php @@ -373,8 +373,8 @@ class OC_Mount_Config { $isPersonal = false) { $backends = self::getBackends(); $mountPoint = OC\Files\Filesystem::normalizePath($mountPoint); - if ($mountPoint === '' || $mountPoint === '/' || $mountPoint == '/Shared') { - // can't mount at root or "Shared" folder + if ($mountPoint === '' || $mountPoint === '/') { + // can't mount at root folder return false; } diff --git a/apps/files_external/tests/mountconfig.php b/apps/files_external/tests/mountconfig.php index c89874c94d..1921ec76af 100644 --- a/apps/files_external/tests/mountconfig.php +++ b/apps/files_external/tests/mountconfig.php @@ -128,9 +128,6 @@ class Test_Mount_Config extends \PHPUnit_Framework_TestCase { $isPersonal = false; $this->assertFalse(OC_Mount_Config::addMountPoint('', $storageClass, array(), $mountType, $applicable, $isPersonal)); $this->assertFalse(OC_Mount_Config::addMountPoint('/', $storageClass, array(), $mountType, $applicable, $isPersonal)); - $this->assertFalse(OC_Mount_Config::addMountPoint('Shared', $storageClass, array(), $mountType, $applicable, $isPersonal)); - $this->assertFalse(OC_Mount_Config::addMountPoint('/Shared', $storageClass, array(), $mountType, $applicable, $isPersonal)); - } /** @@ -488,7 +485,7 @@ class Test_Mount_Config extends \PHPUnit_Framework_TestCase { 'root' => 'someroot' ); - // add mount point as "test" user + // add mount point as "test" user $this->assertTrue( OC_Mount_Config::addMountPoint( '/ext', diff --git a/apps/files_sharing/appinfo/app.php b/apps/files_sharing/appinfo/app.php index 217bc005fa..0ef3457811 100644 --- a/apps/files_sharing/appinfo/app.php +++ b/apps/files_sharing/appinfo/app.php @@ -17,6 +17,4 @@ OCP\Util::addScript('files_sharing', 'share'); \OC_Hook::connect('OC_Filesystem', 'post_delete', '\OC\Files\Cache\Shared_Updater', 'postDeleteHook'); \OC_Hook::connect('OC_Filesystem', 'delete', '\OC\Files\Cache\Shared_Updater', 'deleteHook'); \OC_Hook::connect('OC_Filesystem', 'post_rename', '\OC\Files\Cache\Shared_Updater', 'renameHook'); -\OC_Hook::connect('OCP\Share', 'post_shared', '\OC\Files\Cache\Shared_Updater', 'shareHook'); -\OC_Hook::connect('OCP\Share', 'pre_unshare', '\OC\Files\Cache\Shared_Updater', 'shareHook'); \OC_Hook::connect('OC_Appconfig', 'post_set_value', '\OCA\Files\Share\Maintainer', 'configChangeHook'); diff --git a/apps/files_sharing/appinfo/update.php b/apps/files_sharing/appinfo/update.php index ab32108ea2..bc8cda4231 100644 --- a/apps/files_sharing/appinfo/update.php +++ b/apps/files_sharing/appinfo/update.php @@ -1,76 +1,77 @@ execute(); - $groupShares = array(); - //we need to set up user backends, otherwise creating the shares will fail with "because user does not exist" - OC_User::useBackend(new OC_User_Database()); - OC_Group::useBackend(new OC_Group_Database()); - OC_App::loadApps(array('authentication')); - $rootView = new \OC\Files\View(''); - while ($row = $result->fetchRow()) { - $meta = $rootView->getFileInfo($$row['source']); - $itemSource = $meta['fileid']; - if ($itemSource != -1) { - $file = $meta; - if ($file['mimetype'] == 'httpd/unix-directory') { - $itemType = 'folder'; - } else { - $itemType = 'file'; - } - if ($row['permissions'] == 0) { - $permissions = OCP\PERMISSION_READ | OCP\PERMISSION_SHARE; - } else { - $permissions = OCP\PERMISSION_READ | OCP\PERMISSION_UPDATE | OCP\PERMISSION_SHARE; - if ($itemType == 'folder') { - $permissions |= OCP\PERMISSION_CREATE; - } - } - $pos = strrpos($row['uid_shared_with'], '@'); - if ($pos !== false && OC_Group::groupExists(substr($row['uid_shared_with'], $pos + 1))) { - $shareType = OCP\Share::SHARE_TYPE_GROUP; - $shareWith = substr($row['uid_shared_with'], 0, $pos); - if (isset($groupShares[$shareWith][$itemSource])) { - continue; - } else { - $groupShares[$shareWith][$itemSource] = true; - } - } else if ($row['uid_shared_with'] == 'public') { - $shareType = OCP\Share::SHARE_TYPE_LINK; - $shareWith = null; - } else { - $shareType = OCP\Share::SHARE_TYPE_USER; - $shareWith = $row['uid_shared_with']; - } - OCP\JSON::checkUserExists($row['uid_owner']); - OC_User::setUserId($row['uid_owner']); - //we need to setup the filesystem for the user, otherwise OC_FileSystem::getRoot will fail and break - OC_Util::setupFS($row['uid_owner']); - try { - OCP\Share::shareItem($itemType, $itemSource, $shareType, $shareWith, $permissions); - } - catch (Exception $e) { - $update_error = true; - OCP\Util::writeLog('files_sharing', - 'Upgrade Routine: Skipping sharing "'.$row['source'].'" to "'.$shareWith - .'" (error is "'.$e->getMessage().'")', - OCP\Util::WARN); - } - OC_Util::tearDownFS(); - } - } - OC_User::setUserId(null); - if ($update_error) { - OCP\Util::writeLog('files_sharing', 'There were some problems upgrading the sharing of files', OCP\Util::ERROR); - } - // NOTE: Let's drop the table after more testing -// $query = OCP\DB::prepare('DROP TABLE `*PREFIX*sharing`'); -// $query->execute(); +if (version_compare($installedVersion, '0.4', '<')) { + removeSharedFolder(); } // clean up oc_share table from files which are no longer exists if (version_compare($installedVersion, '0.3.5.6', '<')) { \OC\Files\Cache\Shared_Updater::fixBrokenSharesOnAppUpdate(); } + + +/** + * update script for the removal of the logical "Shared" folder, we create physical "Shared" folder and + * update the users file_target so that it doesn't make any difference for the user + * @note parameters are just for testing, please ignore them + */ +function removeSharedFolder($mkdirs = true, $chunkSize = 99) { + $query = OCP\DB::prepare('SELECT * FROM `*PREFIX*share`'); + $result = $query->execute(); + $view = new \OC\Files\View('/'); + $users = array(); + $shares = array(); + //we need to set up user backends + OC_User::useBackend(new OC_User_Database()); + OC_Group::useBackend(new OC_Group_Database()); + OC_App::loadApps(array('authentication')); + //we need to set up user backends, otherwise creating the shares will fail with "because user does not exist" + while ($row = $result->fetchRow()) { + //collect all user shares + if ((int)$row['share_type'] === 0 && ($row['item_type'] === 'file' || $row['item_type'] === 'folder')) { + $users[] = $row['share_with']; + $shares[$row['id']] = $row['file_target']; + } else if ((int)$row['share_type'] === 1 && ($row['item_type'] === 'file' || $row['item_type'] === 'folder')) { + //collect all group shares + $users = array_merge($users, \OC_group::usersInGroup($row['share_with'])); + $shares[$row['id']] = $row['file_target']; + } else if ((int)$row['share_type'] === 2) { + $shares[$row['id']] = $row['file_target']; + } + } + + $unique_users = array_unique($users); + + if (!empty($unique_users) && !empty($shares)) { + + // create folder Shared for each user + + if ($mkdirs) { + foreach ($unique_users as $user) { + \OC\Files\Filesystem::initMountPoints($user); + if (!$view->file_exists('/' . $user . '/files/Shared')) { + $view->mkdir('/' . $user . '/files/Shared'); + } + } + } + + $chunkedShareList = array_chunk($shares, $chunkSize, true); + + foreach ($chunkedShareList as $subList) { + + $statement = "UPDATE `*PREFIX*share` SET `file_target` = CASE `id` "; + //update share table + $ids = implode(',', array_keys($subList)); + foreach ($subList as $id => $target) { + $statement .= "WHEN " . $id . " THEN '/Shared" . $target . "' "; + } + $statement .= ' END WHERE `id` IN (' . $ids . ')'; + + $query = OCP\DB::prepare($statement); + + $query->execute(array()); + } + + } +} diff --git a/apps/files_sharing/appinfo/version b/apps/files_sharing/appinfo/version index 8f91d33378..bd73f47072 100644 --- a/apps/files_sharing/appinfo/version +++ b/apps/files_sharing/appinfo/version @@ -1 +1 @@ -0.3.5.6 +0.4 diff --git a/apps/files_sharing/css/public.css b/apps/files_sharing/css/public.css index f0b9b04491..efd8d4950d 100644 --- a/apps/files_sharing/css/public.css +++ b/apps/files_sharing/css/public.css @@ -1,32 +1,3 @@ -body { - height: auto; -} - -#header { - background-color: #1d2d44; - height:32px; - left:0; - line-height:32px; - position:fixed; - right:0; - top:0; - z-index:100; - padding:7px; -} - -.header-right { - padding: 0; - height: 32px; -} - -#details { - color:#fff; - -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=50)"; - filter: alpha(opacity=50); - opacity: .5; - padding-right: 5px; -} - #controls { left: 0; } @@ -99,13 +70,26 @@ thead { margin: 0; } + .directDownload, .directLink { margin-bottom: 20px; } + +/* keep long file names in one line to not overflow download button on mobile */ +.directDownload #download { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + max-width: 90%; + display: inline-block; + margin-left: auto; + margin-right: auto; +} .directDownload .button img { vertical-align: text-bottom; } + .directLink label { font-weight: normal; -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=50)"; @@ -115,4 +99,5 @@ thead { .directLink input { margin-left: 5px; width: 300px; + max-width: 90%; } diff --git a/apps/files_sharing/js/public.js b/apps/files_sharing/js/public.js index 9ce8985f1f..ae2412f6a3 100644 --- a/apps/files_sharing/js/public.js +++ b/apps/files_sharing/js/public.js @@ -62,11 +62,17 @@ $(document).ready(function() { var file_upload_start = $('#file_upload_start'); file_upload_start.on('fileuploadadd', function(e, data) { + var fileDirectory = ''; + if(typeof data.files[0].relativePath !== 'undefined') { + fileDirectory = data.files[0].relativePath; + } + // Add custom data to the upload handler data.formData = { requesttoken: $('#publicUploadRequestToken').val(), dirToken: $('#dirToken').val(), - subdir: $('input#dir').val() + subdir: $('input#dir').val(), + file_directory: fileDirectory }; }); diff --git a/apps/files_sharing/l10n/bg_BG.php b/apps/files_sharing/l10n/bg_BG.php index 4fe5ba6b16..37e2ca3337 100644 --- a/apps/files_sharing/l10n/bg_BG.php +++ b/apps/files_sharing/l10n/bg_BG.php @@ -1,5 +1,6 @@ "Парола" +"Password" => "Парола", +"shared by %s" => "споделено от %s" ); $PLURAL_FORMS = "nplurals=2; plural=(n != 1);"; diff --git a/apps/files_sharing/lib/api.php b/apps/files_sharing/lib/api.php index de3c1cd263..438d3cc4ba 100644 --- a/apps/files_sharing/lib/api.php +++ b/apps/files_sharing/lib/api.php @@ -184,7 +184,6 @@ class Api { $receivedFrom = \OCP\Share::getItemSharedWithBySource($itemType, $file['fileid']); reset($share); $key = key($share); - $share[$key]['path'] = self::correctPath($share[$key]['path'], $path); if ($receivedFrom) { $share[$key]['received_from'] = $receivedFrom['uid_owner']; $share[$key]['received_from_displayname'] = \OCP\User::getDisplayName($receivedFrom['uid_owner']); @@ -531,15 +530,4 @@ class Api { } - /** - * @brief make sure that the path has the correct root - * - * @param string $path path returned from the share API - * @param string $folder current root folder - * @return string the correct path - */ - protected static function correctPath($path, $folder) { - return \OC_Filesystem::normalizePath('/' . $folder . '/' . basename($path)); - } - } diff --git a/apps/files_sharing/lib/cache.php b/apps/files_sharing/lib/cache.php index b9c5e8f60a..e1cbc49d86 100644 --- a/apps/files_sharing/lib/cache.php +++ b/apps/files_sharing/lib/cache.php @@ -2,8 +2,9 @@ /** * ownCloud * - * @author Michael Gapczynski - * @copyright 2012 Michael Gapczynski mtgap@owncloud.com + * @author Bjoern Schiessle, Michael Gapczynski + * @copyright 2012 Michael Gapczynski + * 2014 Bjoern Schiessle * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE @@ -46,12 +47,15 @@ class Shared_Cache extends Cache { * @return \OC\Files\Cache\Cache */ private function getSourceCache($target) { - $source = \OC_Share_Backend_File::getSource($target); + if ($target === false || $target === $this->storage->getMountPoint()) { + $target = ''; + } + $source = \OC_Share_Backend_File::getSource($target, $this->storage->getMountPoint(), $this->storage->getItemType()); if (isset($source['path']) && isset($source['fileOwner'])) { \OC\Files\Filesystem::initMountPoints($source['fileOwner']); - $mount = \OC\Files\Filesystem::getMountByNumericId($source['storage']); - if (is_array($mount)) { - $fullPath = $mount[key($mount)]->getMountPoint() . $source['path']; + $mounts = \OC\Files\Filesystem::getMountByNumericId($source['storage']); + if (is_array($mounts) and count($mounts)) { + $fullPath = $mounts[0]->getMountPoint() . $source['path']; list($storage, $internalPath) = \OC\Files\Filesystem::resolvePath($fullPath); if ($storage) { $this->files[$target] = $internalPath; @@ -80,23 +84,26 @@ class Shared_Cache extends Cache { * @return array */ public function get($file) { - if ($file == '') { - $data = \OCP\Share::getItemsSharedWith('file', \OC_Share_Backend_File::FORMAT_FILE_APP_ROOT); - $etag = \OCP\Config::getUserValue(\OCP\User::getUser(), 'files_sharing', 'etag'); - if (!isset($etag)) { - $etag = $this->storage->getETag(''); - \OCP\Config::setUserValue(\OCP\User::getUser(), 'files_sharing', 'etag', $etag); - } - $data['etag'] = $etag; - return $data; - } else if (is_string($file)) { + if (is_string($file)) { if ($cache = $this->getSourceCache($file)) { - return $cache->get($this->files[$file]); + $data = $cache->get($this->files[$file]); + $data['displayname_owner'] = \OC_User::getDisplayName($this->storage->getSharedFrom()); + $data['path'] = $file; + if ($file === '') { + $data['is_share_mount_point'] = true; + } + $data['uid_owner'] = $this->storage->getOwner($file); + return $data; } } else { + // if we are at the root of the mount point we want to return the + // cache information for the source item + if (!is_int($file) || $file === 0) { + $file = $this->storage->getSourceId(); + } $query = \OC_DB::prepare( 'SELECT `fileid`, `storage`, `path`, `parent`, `name`, `mimetype`, `mimepart`,' - . ' `size`, `mtime`, `encrypted`, `unencrypted_size`' + . ' `size`, `mtime`, `encrypted`, `unencrypted_size`, `storage_mtime`, `etag`' . ' FROM `*PREFIX*filecache` WHERE `fileid` = ?'); $result = $query->execute(array($file)); $data = $result->fetchRow(); @@ -115,6 +122,10 @@ class Shared_Cache extends Cache { } else { $data['size'] = (int)$data['size']; } + if (!is_int($file) || $file === 0) { + $data['path'] = ''; + $data['is_share_mount_point'] = true; + } return $data; } return false; @@ -127,28 +138,26 @@ class Shared_Cache extends Cache { * @return array */ public function getFolderContents($folder) { - if ($folder == '') { - $files = \OCP\Share::getItemsSharedWith('file', \OC_Share_Backend_File::FORMAT_GET_FOLDER_CONTENTS); - foreach ($files as &$file) { - $file['mimetype'] = $this->getMimetype($file['mimetype']); - $file['mimepart'] = $this->getMimetype($file['mimepart']); - $file['usersPath'] = 'files/Shared/' . ltrim($file['path'], '/'); - } - return $files; - } else { - $cache = $this->getSourceCache($folder); - if ($cache) { - $parent = $this->storage->getFile($folder); - $sourceFolderContent = $cache->getFolderContents($this->files[$folder]); - foreach ($sourceFolderContent as $key => $c) { - $sourceFolderContent[$key]['usersPath'] = 'files/Shared/' . $folder . '/' . $c['name']; - $sourceFolderContent[$key]['uid_owner'] = $parent['uid_owner']; - $sourceFolderContent[$key]['displayname_owner'] = $parent['uid_owner']; - } - return $sourceFolderContent; - } + if ($folder === false) { + $folder = ''; } + + $dir = ($folder !== '') ? $folder . '/' : ''; + + $cache = $this->getSourceCache($folder); + if ($cache) { + $parent = $this->storage->getFile($folder); + $sourceFolderContent = $cache->getFolderContents($this->files[$folder]); + foreach ($sourceFolderContent as $key => $c) { + $sourceFolderContent[$key]['path'] = $dir . $c['name']; + $sourceFolderContent[$key]['uid_owner'] = $parent['uid_owner']; + $sourceFolderContent[$key]['displayname_owner'] = $parent['uid_owner']; + } + + return $sourceFolderContent; + } + return false; } @@ -161,9 +170,8 @@ class Shared_Cache extends Cache { * @return int file id */ public function put($file, array $data) { - if ($file === '' && isset($data['etag'])) { - return \OCP\Config::setUserValue(\OCP\User::getUser(), 'files_sharing', 'etag', $data['etag']); - } else if ($cache = $this->getSourceCache($file)) { + $file = ($file === false) ? '' : $file; + if ($cache = $this->getSourceCache($file)) { return $cache->put($this->files[$file], $data); } return false; @@ -176,7 +184,11 @@ class Shared_Cache extends Cache { * @return int */ public function getId($file) { - if ($cache = $this->getSourceCache($file)) { + if ($file === false) { + return $this->storage->getSourceId(); + } + $cache = $this->getSourceCache($file); + if ($cache) { return $cache->getId($this->files[$file]); } return -1; @@ -201,6 +213,7 @@ class Shared_Cache extends Cache { * @param string $file */ public function remove($file) { + $file = ($file === false) ? '' : $file; if ($cache = $this->getSourceCache($file)) { $cache->remove($this->files[$file]); } @@ -214,7 +227,7 @@ class Shared_Cache extends Cache { */ public function move($source, $target) { if ($cache = $this->getSourceCache($source)) { - $file = \OC_Share_Backend_File::getSource($target); + $file = \OC_Share_Backend_File::getSource($target, $this->storage->getMountPoint(), $this->storage->getItemType()); if ($file && isset($file['path'])) { $cache->move($this->files[$source], $file['path']); } @@ -284,15 +297,17 @@ class Shared_Cache extends Cache { $files = $this->getFolderContents($dir); // no results? if (!$files) { + // maybe it's a single shared file + $file = $this->get(''); + if (($mimepart && $file['mimepart'] === $mimepart) || ($mimetype && $file['mimetype'] === $mimetype)) { + $result[] = $file; + } continue; } foreach ($files as $file) { if ($file['mimetype'] === 'httpd/unix-directory') { $exploreDirs[] = ltrim($dir . '/' . $file['name'], '/'); } else if (($mimepart && $file['mimepart'] === $mimepart) || ($mimetype && $file['mimetype'] === $mimetype)) { - // usersPath not reliable - //$file['path'] = $file['usersPath']; - $file['path'] = ltrim($dir . '/' . $file['name'], '/'); $result[] = $file; } } @@ -359,6 +374,7 @@ class Shared_Cache extends Cache { * @return int */ public function calculateFolderSize($path, $entry = null) { + $path = ($path === false) ? '' : $path; if ($cache = $this->getSourceCache($path)) { return $cache->calculateFolderSize($this->files[$path]); } @@ -401,7 +417,7 @@ class Shared_Cache extends Cache { } /** - * get the path of a file on this storage by it's id + * get the path of a file on this storage relative to the mount point by it's id * * @param int $id * @param string $pathEnd (optional) used internally for recursive calls @@ -409,8 +425,9 @@ class Shared_Cache extends Cache { */ public function getPathById($id, $pathEnd = '') { // direct shares are easy - if ($path = $this->getShareById($id)) { - return $path . $pathEnd; + $path = $this->getShareById($id); + if (is_string($path)) { + return ltrim($pathEnd, '/'); } else { // if the item is a direct share we try and get the path of the parent and append the name of the item to it list($parent, $name) = $this->getParentInfo($id); diff --git a/apps/files_sharing/lib/helper.php b/apps/files_sharing/lib/helper.php index cba0ef2a0a..aecb9c771b 100644 --- a/apps/files_sharing/lib/helper.php +++ b/apps/files_sharing/lib/helper.php @@ -2,6 +2,9 @@ namespace OCA\Files_Sharing; +use OC_Config; +use PasswordHash; + class Helper { /** @@ -26,9 +29,6 @@ class Helper { exit; } - $type = $linkItem['item_type']; - $fileSource = $linkItem['file_source']; - $shareOwner = $linkItem['uid_owner']; $rootLinkItem = \OCP\Share::resolveReShare($linkItem); $path = null; if (isset($rootLinkItem['uid_owner'])) { @@ -61,7 +61,6 @@ class Helper { } $basePath = $path; - $rootName = basename($path); if ($relativePath !== null && \OC\Files\Filesystem::isReadable($basePath . $relativePath)) { $path .= \OC\Files\Filesystem::normalizePath($relativePath); @@ -111,4 +110,70 @@ class Helper { } return true; } + + public static function getSharesFromItem($target) { + $result = array(); + $owner = \OC\Files\Filesystem::getOwner($target); + \OC\Files\Filesystem::initMountPoints($owner); + $info = \OC\Files\Filesystem::getFileInfo($target); + $ownerView = new \OC\Files\View('/'.$owner.'/files'); + if ( $owner != \OCP\User::getUser() ) { + $path = $ownerView->getPath($info['fileid']); + } else { + $path = $target; + } + + + $ids = array(); + while ($path !== '' && $path !== '.' && $path !== '/') { + $info = $ownerView->getFileInfo($path); + $ids[] = $info['fileid']; + $path = dirname($path); + } + + if (!empty($ids)) { + + $idList = array_chunk($ids, 99, true); + + foreach ($idList as $subList) { + $statement = "SELECT `share_with`, `share_type`, `file_target` FROM `*PREFIX*share` WHERE `file_source` IN (" . implode(',', $subList) . ") AND `share_type` IN (0, 1, 2)"; + $query = \OCP\DB::prepare($statement); + $r = $query->execute(); + $result = array_merge($result, $r->fetchAll()); + } + } + + return $result; + } + + public static function getUidAndFilename($filename) { + $uid = \OC\Files\Filesystem::getOwner($filename); + \OC\Files\Filesystem::initMountPoints($uid); + if ( $uid != \OCP\User::getUser() ) { + $info = \OC\Files\Filesystem::getFileInfo($filename); + $ownerView = new \OC\Files\View('/'.$uid.'/files'); + $filename = $ownerView->getPath($info['fileid']); + } + return array($uid, $filename); + } + + /** + * @brief Format a path to be relative to the /user/files/ directory + * @param string $path the absolute path + * @return string e.g. turns '/admin/files/test.txt' into 'test.txt' + */ + public static function stripUserFilesPath($path) { + $trimmed = ltrim($path, '/'); + $split = explode('/', $trimmed); + + // it is not a file relative to data/user/files + if (count($split) < 3 || $split[1] !== 'files') { + return false; + } + + $sliced = array_slice($split, 2); + $relPath = implode('/', $sliced); + + return $relPath; + } } diff --git a/apps/files_sharing/lib/permissions.php b/apps/files_sharing/lib/permissions.php index 31b7ac361a..c3ad63e2fd 100644 --- a/apps/files_sharing/lib/permissions.php +++ b/apps/files_sharing/lib/permissions.php @@ -31,7 +31,9 @@ class Shared_Permissions extends Permissions { */ public function get($fileId, $user) { if ($fileId == -1) { - return \OCP\PERMISSION_READ; + // if we ask for the mount point return -1 so that we can get the correct + // permissions by the path, with the root fileId we have no idea which share is meant + return -1; } $source = \OCP\Share::getItemSharedWithBySource('file', $fileId, \OC_Share_Backend_File::FORMAT_SHARED_STORAGE, null, true); diff --git a/apps/files_sharing/lib/share/file.php b/apps/files_sharing/lib/share/file.php index c375579b1a..2c59bd3bf9 100644 --- a/apps/files_sharing/lib/share/file.php +++ b/apps/files_sharing/lib/share/file.php @@ -2,8 +2,9 @@ /** * ownCloud * -* @author Michael Gapczynski -* @copyright 2012 Michael Gapczynski mtgap@owncloud.com +* @author Bjoern Schiessle, Michael Gapczynski +* @copyright 2012 Michael Gapczynski + * 2014 Bjoern Schiessle * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE @@ -27,6 +28,7 @@ class OC_Share_Backend_File implements OCP\Share_Backend_File_Dependent { const FORMAT_OPENDIR = 3; const FORMAT_GET_ALL = 4; const FORMAT_PERMISSIONS = 5; + const FORMAT_TARGET_NAMES = 6; private $path; @@ -49,24 +51,37 @@ class OC_Share_Backend_File implements OCP\Share_Backend_File_Dependent { return false; } + /** + * @brief create unique target + * @param string $filePath + * @param string $shareWith + * @param string $exclude + * @return string + */ public function generateTarget($filePath, $shareWith, $exclude = null) { $target = '/'.basename($filePath); - if (isset($exclude)) { - if ($pos = strrpos($target, '.')) { - $name = substr($target, 0, $pos); - $ext = substr($target, $pos); - } else { - $name = $target; - $ext = ''; - } - $i = 2; - $append = ''; - while (in_array($name.$append.$ext, $exclude)) { - $append = ' ('.$i.')'; - $i++; - } - $target = $name.$append.$ext; + + // for group shares we return the target right away + if ($shareWith === false) { + return $target; } + + \OC\Files\Filesystem::initMountPoints($shareWith); + $view = new \OC\Files\View('/' . $shareWith . '/files'); + $excludeList = \OCP\Share::getItemsSharedWithUser('file', $shareWith, self::FORMAT_TARGET_NAMES); + if (is_array($exclude)) { + $excludeList = array_merge($excludeList, $exclude); + } + + $pathinfo = pathinfo($target); + $ext = (isset($pathinfo['extension'])) ? '.'.$pathinfo['extension'] : ''; + $name = $pathinfo['filename']; + $i = 2; + while ($view->file_exists($target) || in_array($target, $excludeList)) { + $target = '/' . $name . ' ('.$i.')' . $ext; + $i++; + } + return $target; } @@ -78,7 +93,7 @@ class OC_Share_Backend_File implements OCP\Share_Backend_File_Dependent { 'path' => $items[key($items)]['path'], 'storage' => $items[key($items)]['storage'], 'permissions' => $items[key($items)]['permissions'], - 'uid_owner' => $items[key($items)]['uid_owner'] + 'uid_owner' => $items[key($items)]['uid_owner'], ); } else if ($format == self::FORMAT_GET_FOLDER_CONTENTS) { $files = array(); @@ -108,22 +123,6 @@ class OC_Share_Backend_File implements OCP\Share_Backend_File_Dependent { $files[] = $file; } return $files; - } else if ($format == self::FORMAT_FILE_APP_ROOT) { - $mtime = 0; - $size = 0; - foreach ($items as $item) { - if ($item['mtime'] > $mtime) { - $mtime = $item['mtime']; - } - $size += (int)$item['size']; - } - return array( - 'fileid' => -1, - 'name' => 'Shared', - 'mtime' => $mtime, - 'mimetype' => 'httpd/unix-directory', - 'size' => $size - ); } else if ($format == self::FORMAT_OPENDIR) { $files = array(); foreach ($items as $item) { @@ -142,49 +141,65 @@ class OC_Share_Backend_File implements OCP\Share_Backend_File_Dependent { $filePermissions[$item['file_source']] = $item['permissions']; } return $filePermissions; + } else if ($format === self::FORMAT_TARGET_NAMES) { + $targets = array(); + foreach ($items as $item) { + $targets[] = $item['file_target']; + } + return $targets; } return array(); } /** - * @param string $target + * @brief resolve reshares to return the correct source item + * @param array $source + * @return array source item */ - public static function getSource($target) { - if ($target == '') { - return false; - } - $target = '/'.$target; - $target = rtrim($target, '/'); - $pos = strpos($target, '/', 1); - // Get shared folder name - if ($pos !== false) { - $folder = substr($target, 0, $pos); - $source = \OCP\Share::getItemSharedWith('folder', $folder, \OC_Share_Backend_File::FORMAT_SHARED_STORAGE); - if ($source) { - $source['path'] = $source['path'].substr($target, strlen($folder)); + protected static function resolveReshares($source) { + if (isset($source['parent'])) { + $parent = $source['parent']; + while (isset($parent)) { + $query = \OC_DB::prepare('SELECT `parent`, `uid_owner` FROM `*PREFIX*share` WHERE `id` = ?', 1); + $item = $query->execute(array($parent))->fetchRow(); + if (isset($item['parent'])) { + $parent = $item['parent']; + } else { + $fileOwner = $item['uid_owner']; + break; + } } } else { - $source = \OCP\Share::getItemSharedWith('file', $target, \OC_Share_Backend_File::FORMAT_SHARED_STORAGE); + $fileOwner = $source['uid_owner']; + } + if (isset($fileOwner)) { + $source['fileOwner'] = $fileOwner; + } else { + \OCP\Util::writeLog('files_sharing', "No owner found for reshare", \OCP\Util::ERROR); + } + + return $source; + } + + /** + * @param string $target + * @param string $mountPoint + * @param string $itemType + * @return array|false source item + */ + public static function getSource($target, $mountPoint, $itemType) { + if ($itemType === 'folder') { + $source = \OCP\Share::getItemSharedWith('folder', $mountPoint, \OC_Share_Backend_File::FORMAT_SHARED_STORAGE); + if ($source && $target !== '') { + $source['path'] = $source['path'].'/'.$target; + } + } else { + $source = \OCP\Share::getItemSharedWith('file', $mountPoint, \OC_Share_Backend_File::FORMAT_SHARED_STORAGE); } if ($source) { - if (isset($source['parent'])) { - $parent = $source['parent']; - while (isset($parent)) { - $query = \OC_DB::prepare('SELECT `parent`, `uid_owner` FROM `*PREFIX*share` WHERE `id` = ?', 1); - $item = $query->execute(array($parent))->fetchRow(); - if (isset($item['parent'])) { - $parent = $item['parent']; - } else { - $fileOwner = $item['uid_owner']; - break; - } - } - } else { - $fileOwner = $source['uid_owner']; - } - $source['fileOwner'] = $fileOwner; - return $source; + return self::resolveReshares($source); } + \OCP\Util::writeLog('files_sharing', 'File source not found for: '.$target, \OCP\Util::DEBUG); return false; } diff --git a/apps/files_sharing/lib/sharedstorage.php b/apps/files_sharing/lib/sharedstorage.php index b922654e5e..5e478d5ead 100644 --- a/apps/files_sharing/lib/sharedstorage.php +++ b/apps/files_sharing/lib/sharedstorage.php @@ -2,8 +2,9 @@ /** * ownCloud * - * @author Michael Gapczynski - * @copyright 2011 Michael Gapczynski mtgap@owncloud.com + * @author Bjoern Schiessle, Michael Gapczynski + * @copyright 2011 Michael Gapczynski + * 2014 Bjoern Schiessle * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE @@ -27,15 +28,27 @@ namespace OC\Files\Storage; */ class Shared extends \OC\Files\Storage\Common { - private $sharedFolder; + private $share; // the shared resource private $files = array(); public function __construct($arguments) { - $this->sharedFolder = $arguments['sharedFolder']; + $this->share = $arguments['share']; } + /** + * @breif get id of the mount point + * @return string + */ public function getId() { - return 'shared::' . $this->sharedFolder; + return 'shared::' . $this->getMountPoint(); + } + + /** + * @breif get file cache of the shared item source + * @return string + */ + public function getSourceId() { + return $this->share['file_source']; } /** @@ -48,14 +61,14 @@ class Shared extends \OC\Files\Storage\Common { if (!isset($this->files[$target])) { // Check for partial files if (pathinfo($target, PATHINFO_EXTENSION) === 'part') { - $source = \OC_Share_Backend_File::getSource(substr($target, 0, -5)); + $source = \OC_Share_Backend_File::getSource(substr($target, 0, -5), $this->getMountPoint(), $this->getItemType()); if ($source) { $source['path'] .= '.part'; // All partial files have delete permission $source['permissions'] |= \OCP\PERMISSION_DELETE; } } else { - $source = \OC_Share_Backend_File::getSource($target); + $source = \OC_Share_Backend_File::getSource($target, $this->getMountPoint(), $this->getItemType()); } $this->files[$target] = $source; } @@ -117,25 +130,15 @@ class Shared extends \OC\Files\Storage\Common { } public function opendir($path) { - if ($path == '' || $path == '/') { - $files = \OCP\Share::getItemsSharedWith('file', \OC_Share_Backend_Folder::FORMAT_OPENDIR); - \OC\Files\Stream\Dir::register('shared', $files); - return opendir('fakedir://shared'); - } else if ($source = $this->getSourcePath($path)) { - list($storage, $internalPath) = \OC\Files\Filesystem::resolvePath($source); - return $storage->opendir($internalPath); - } - return false; + $source = $this->getSourcePath($path); + list($storage, $internalPath) = \OC\Files\Filesystem::resolvePath($source); + return $storage->opendir($internalPath); } public function is_dir($path) { - if ($path == '' || $path == '/') { - return true; - } else if ($source = $this->getSourcePath($path)) { - list($storage, $internalPath) = \OC\Files\Filesystem::resolvePath($source); - return $storage->is_dir($internalPath); - } - return false; + $source = $this->getSourcePath($path); + list($storage, $internalPath) = \OC\Files\Filesystem::resolvePath($source); + return $storage->is_dir($internalPath); } public function is_file($path) { @@ -180,7 +183,7 @@ class Shared extends \OC\Files\Storage\Common { public function isCreatable($path) { if ($path == '') { - return false; + $path = $this->getMountPoint(); } return ($this->getPermissions($path) & \OCP\PERMISSION_CREATE); } @@ -191,21 +194,21 @@ class Shared extends \OC\Files\Storage\Common { public function isUpdatable($path) { if ($path == '') { - return false; + $path = $this->getMountPoint(); } return ($this->getPermissions($path) & \OCP\PERMISSION_UPDATE); } public function isDeletable($path) { if ($path == '') { - return true; + $path = $this->getMountPoint(); } return ($this->getPermissions($path) & \OCP\PERMISSION_DELETE); } public function isSharable($path) { if ($path == '') { - return false; + $path = $this->getMountPoint(); } return ($this->getPermissions($path) & \OCP\PERMISSION_SHARE); } @@ -221,32 +224,16 @@ class Shared extends \OC\Files\Storage\Common { } public function filemtime($path) { - if ($path == '' || $path == '/') { - $mtime = 0; - $dh = $this->opendir($path); - if (is_resource($dh)) { - while (($filename = readdir($dh)) !== false) { - $tempmtime = $this->filemtime($filename); - if ($tempmtime > $mtime) { - $mtime = $tempmtime; - } - } - } - return $mtime; - } else { - $source = $this->getSourcePath($path); - if ($source) { - list($storage, $internalPath) = \OC\Files\Filesystem::resolvePath($source); - return $storage->filemtime($internalPath); - } - } + $source = $this->getSourcePath($path); + list($storage, $internalPath) = \OC\Files\Filesystem::resolvePath($source); + return $storage->filemtime($internalPath); } public function file_get_contents($path) { $source = $this->getSourcePath($path); if ($source) { $info = array( - 'target' => $this->sharedFolder . $path, + 'target' => $this->getMountPoint() . $path, 'source' => $source, ); \OCP\Util::emitHook('\OC\Files\Storage\Shared', 'file_get_contents', $info); @@ -264,7 +251,7 @@ class Shared extends \OC\Files\Storage\Common { return false; } $info = array( - 'target' => $this->sharedFolder . $path, + 'target' => $this->getMountPoint() . '/' . $path, 'source' => $source, ); \OCP\Util::emitHook('\OC\Files\Storage\Shared', 'file_put_contents', $info); @@ -277,6 +264,7 @@ class Shared extends \OC\Files\Storage\Common { public function unlink($path) { // Delete the file if DELETE permission is granted + $path = ($path === false) ? '' : $path; if ($source = $this->getSourcePath($path)) { if ($this->isDeletable($path)) { list($storage, $internalPath) = \OC\Files\Filesystem::resolvePath($source); @@ -286,23 +274,117 @@ class Shared extends \OC\Files\Storage\Common { return false; } - public function rename($path1, $path2) { - // Renaming/moving is only allowed within shared folders - $pos1 = strpos($path1, '/', 1); - $pos2 = strpos($path2, '/', 1); - if ($pos1 !== false && $pos2 !== false && ($oldSource = $this->getSourcePath($path1))) { - $newSource = $this->getSourcePath(dirname($path2)) . '/' . basename($path2); - // Within the same folder, we only need UPDATE permissions - if (dirname($path1) == dirname($path2) and $this->isUpdatable($path1)) { - list($storage, $oldInternalPath) = \OC\Files\Filesystem::resolvePath($oldSource); - list(, $newInternalPath) = \OC\Files\Filesystem::resolvePath($newSource); - return $storage->rename($oldInternalPath, $newInternalPath); - // otherwise DELETE and CREATE permissions required - } elseif ($this->isDeletable($path1) && $this->isCreatable(dirname($path2))) { - $rootView = new \OC\Files\View(''); - return $rootView->rename($oldSource, $newSource); - } + /** + * @brief Format a path to be relative to the /user/files/ directory + * @param string $path the absolute path + * @return string e.g. turns '/admin/files/test.txt' into '/test.txt' + */ + private static function stripUserFilesPath($path) { + $trimmed = ltrim($path, '/'); + $split = explode('/', $trimmed); + + // it is not a file relative to data/user/files + if (count($split) < 3 || $split[1] !== 'files') { + \OCP\Util::writeLog('file sharing', + 'Can not strip userid and "files/" from path: ' . $path, + \OCP\Util::DEBUG); + return false; } + + // skip 'user' and 'files' + $sliced = array_slice($split, 2); + $relPath = implode('/', $sliced); + + return '/' . $relPath; + } + + /** + * @brief rename a shared folder/file + * @param string $sourcePath + * @param string $targetPath + * @return bool + */ + private function renameMountPoint($sourcePath, $targetPath) { + + // it shouldn't be possible to move a Shared storage into another one + list($targetStorage, ) = \OC\Files\Filesystem::resolvePath($targetPath); + if ($targetStorage instanceof \OC\Files\Storage\Shared) { + \OCP\Util::writeLog('file sharing', + 'It is not allowed to move one mount point into another one', + \OCP\Util::DEBUG); + return false; + } + + $relTargetPath = $this->stripUserFilesPath($targetPath); + + // if the user renames a mount point from a group share we need to create a new db entry + // for the unique name + if ($this->getShareType() === \OCP\Share::SHARE_TYPE_GROUP && $this->uniqueNameSet() === false) { + $query = \OC_DB::prepare('INSERT INTO `*PREFIX*share` (`item_type`, `item_source`, `item_target`,' + .' `share_type`, `share_with`, `uid_owner`, `permissions`, `stime`, `file_source`,' + .' `file_target`, `token`, `parent`) VALUES (?,?,?,?,?,?,?,?,?,?,?,?)'); + $arguments = array($this->share['item_type'], $this->share['item_source'], $this->share['item_target'], + 2, \OCP\User::getUser(), $this->share['uid_owner'], $this->share['permissions'], $this->share['stime'], $this->share['file_source'], + $relTargetPath, $this->share['token'], $this->share['id']); + + } else { + // rename mount point + $query = \OC_DB::prepare( + 'Update `*PREFIX*share` + SET `file_target` = ? + WHERE `id` = ?' + ); + $arguments = array($relTargetPath, $this->getShareId()); + } + + $result = $query->execute($arguments); + + if ($result) { + // update the mount manager with the new paths + $mountManager = \OC\Files\Filesystem::getMountManager(); + $mount = $mountManager->find($sourcePath); + $mount->setMountPoint($targetPath . '/'); + $mountManager->addMount($mount); + $mountManager->removeMount($sourcePath . '/'); + $this->setUniqueName(); + $this->setMountPoint($relTargetPath); + + } else { + \OCP\Util::writeLog('file sharing', + 'Could not rename mount point for shared folder "' . $sourcePath . '" to "' . $targetPath . '"', + \OCP\Util::ERROR); + } + + return $result; + } + + + public function rename($path1, $path2) { + + $sourceMountPoint = \OC\Files\Filesystem::getMountPoint($path1); + $targetMountPoint = \OC\Files\Filesystem::getMountPoint($path2); + $relPath1 = \OCA\Files_Sharing\Helper::stripUserFilesPath($path1); + $relPath2 = \OCA\Files_Sharing\Helper::stripUserFilesPath($path2); + + // if we renamed the mount point we need to adjust the file_target in the + // database + if (\OC\Files\Filesystem::normalizePath($sourceMountPoint) === \OC\Files\Filesystem::normalizePath($path1)) { + return $this->renameMountPoint($path1, $path2); + } + + + if ( // Within the same mount point, we only need UPDATE permissions + ($sourceMountPoint === $targetMountPoint && $this->isUpdatable($sourceMountPoint)) || + // otherwise DELETE and CREATE permissions required + ($this->isDeletable($path1) && $this->isCreatable(dirname($path2)))) { + + list($user1, $path1) = \OCA\Files_Sharing\Helper::getUidAndFilename($relPath1); + $targetFilename = basename($relPath2); + list($user2, $path2) = \OCA\Files_Sharing\Helper::getUidAndFilename(dirname($relPath2)); + $rootView = new \OC\Files\View(''); + return $rootView->rename('/' . $user1 . '/files/' . $path1, '/' . $user2 . '/files/' . $path2 . '/' . $targetFilename); + } + return false; } @@ -343,7 +425,7 @@ class Shared extends \OC\Files\Storage\Common { } } $info = array( - 'target' => $this->sharedFolder . $path, + 'target' => $this->getMountPoint() . $path, 'source' => $source, 'mode' => $mode, ); @@ -355,9 +437,6 @@ class Shared extends \OC\Files\Storage\Common { } public function getMimeType($path) { - if ($path == '' || $path == '/') { - return 'httpd/unix-directory'; - } if ($source = $this->getSourcePath($path)) { list($storage, $internalPath) = \OC\Files\Filesystem::resolvePath($source); return $storage->getMimeType($internalPath); @@ -367,13 +446,14 @@ class Shared extends \OC\Files\Storage\Common { public function free_space($path) { if ($path == '') { - return \OC\Files\SPACE_UNKNOWN; + $path = $this->getMountPoint(); } $source = $this->getSourcePath($path); if ($source) { list($storage, $internalPath) = \OC\Files\Filesystem::resolvePath($source); return $storage->free_space($internalPath); } + return \OC\Files\SPACE_UNKNOWN; } public function getLocalFile($path) { @@ -393,20 +473,80 @@ class Shared extends \OC\Files\Storage\Common { } public static function setup($options) { + $shares = \OCP\Share::getItemsSharedWith('file'); if (!\OCP\User::isLoggedIn() || \OCP\User::getUser() != $options['user'] - || \OCP\Share::getItemsSharedWith('file') + || $shares ) { - $user_dir = $options['user_dir']; - \OC\Files\Filesystem::mount('\OC\Files\Storage\Shared', - array('sharedFolder' => '/Shared'), - $user_dir . '/Shared/'); + foreach ($shares as $share) { + \OC\Files\Filesystem::mount('\OC\Files\Storage\Shared', + array( + 'share' => $share, + ), + $options['user_dir'] . '/' . $share['file_target']); + } } } + /** + * @brief return mount point of share, relative to data/user/files + * @return string + */ + public function getMountPoint() { + return $this->share['file_target']; + } + + /** + * @brief get share type + * @return integer can be single user share (0) group share (1), unique group share name (2) + */ + private function getShareType() { + return $this->share['share_type']; + } + + private function setMountPoint($path) { + $this->share['file_target'] = $path; + } + + /** + * @brief does the group share already has a user specific unique name + * @return bool + */ + private function uniqueNameSet() { + return (isset($this->share['unique_name']) && $this->share['unique_name']); + } + + /** + * @brief the share now uses a unique name of this user + */ + private function setUniqueName() { + $this->share['unique_name'] = true; + } + + /** + * @brief get share ID + * @return integer unique share ID + */ + private function getShareId() { + return $this->share['id']; + } + + /** + * @brief get the user who shared the file + * @return string + */ + public function getSharedFrom() { + return $this->share['uid_owner']; + } + + /** + * @brief return share type, can be "file" or "folder" + * @return string + */ + public function getItemType() { + return $this->share['item_type']; + } + public function hasUpdated($path, $time) { - if ($path == '') { - return false; - } return $this->filemtime($path) > $time; } @@ -428,7 +568,7 @@ class Shared extends \OC\Files\Storage\Common { public function getOwner($path) { if ($path == '') { - return false; + $path = $this->getMountPoint(); } $source = $this->getFile($path); if ($source) { @@ -439,7 +579,7 @@ class Shared extends \OC\Files\Storage\Common { public function getETag($path) { if ($path == '') { - return parent::getETag($path); + $path = $this->getMountPoint(); } if ($source = $this->getSourcePath($path)) { list($storage, $internalPath) = \OC\Files\Filesystem::resolvePath($source); diff --git a/apps/files_sharing/lib/updater.php b/apps/files_sharing/lib/updater.php index e3a7679292..f7c0a75aee 100644 --- a/apps/files_sharing/lib/updater.php +++ b/apps/files_sharing/lib/updater.php @@ -26,31 +26,48 @@ class Shared_Updater { // shares which can be removed from oc_share after the delete operation was successful static private $toRemove = array(); + /** + * @brief walk up the users file tree and update the etags + * @param string $user + * @param string $path + */ + static private function correctUsersFolder($user, $path) { + // $path points to the mount point which is a virtual folder, so we start with + // the parent + $path = '/files' . dirname($path); + \OC\Files\Filesystem::initMountPoints($user); + $view = new \OC\Files\View('/' . $user); + if ($view->file_exists($path)) { + while ($path !== '/') { + $etag = $view->getETag($path); + $view->putFileInfo($path, array('etag' => $etag)); + $path = dirname($path); + } + } else { + error_log("error!" . 'can not update etags on ' . $path . ' for user ' . $user); + \OCP\Util::writeLog('files_sharing', 'can not update etags on ' . $path . ' for user ' . $user, \OCP\Util::ERROR); + } + } + /** * Correct the parent folders' ETags for all users shared the file at $target * * @param string $target */ static public function correctFolders($target) { - $uid = \OCP\User::getUser(); - $uidOwner = \OC\Files\Filesystem::getOwner($target); - $info = \OC\Files\Filesystem::getFileInfo($target); - $checkedUser = array($uidOwner); // Correct Shared folders of other users shared with - $users = \OCP\Share::getUsersItemShared('file', $info['fileid'], $uidOwner, true); - if (!empty($users)) { - while (!empty($users)) { - $reshareUsers = array(); + $shares = \OCA\Files_Sharing\Helper::getSharesFromItem($target); + + foreach ($shares as $share) { + if ((int)$share['share_type'] === \OCP\Share::SHARE_TYPE_USER) { + self::correctUsersFolder($share['share_with'], $share['file_target']); + } elseif ((int)$share['share_type'] === \OCP\Share::SHARE_TYPE_GROUP) { + $users = \OC_Group::usersInGroup($share['share_with']); foreach ($users as $user) { - if ( !in_array($user, $checkedUser) ) { - $etag = \OC\Files\Filesystem::getETag(''); - \OCP\Config::setUserValue($user, 'files_sharing', 'etag', $etag); - // Look for reshares - $reshareUsers = array_merge($reshareUsers, \OCP\Share::getUsersItemShared('file', $info['fileid'], $user, true)); - $checkedUser[] = $user; - } + self::correctUsersFolder($user, $share['file_target']); } - $users = $reshareUsers; + } else { //unique name for group share + self::correctUsersFolder($share['share_with'], $share['file_target']); } } } @@ -107,34 +124,6 @@ class Shared_Updater { self::removeShare($params['path']); } - /** - * @param array $params - */ - static public function shareHook($params) { - if ($params['itemType'] === 'file' || $params['itemType'] === 'folder') { - if (isset($params['uidOwner'])) { - $uidOwner = $params['uidOwner']; - } else { - $uidOwner = \OCP\User::getUser(); - } - $users = \OCP\Share::getUsersItemShared($params['itemType'], $params['fileSource'], $uidOwner, true, false); - if (!empty($users)) { - while (!empty($users)) { - $reshareUsers = array(); - foreach ($users as $user) { - if ($user !== $uidOwner) { - $etag = \OC\Files\Filesystem::getETag(''); - \OCP\Config::setUserValue($user, 'files_sharing', 'etag', $etag); - // Look for reshares - $reshareUsers = array_merge($reshareUsers, \OCP\Share::getUsersItemShared('file', $params['fileSource'], $user, true)); - } - } - $users = $reshareUsers; - } - } - } - } - /** * clean up oc_share table from files which are no longer exists * diff --git a/apps/files_sharing/lib/watcher.php b/apps/files_sharing/lib/watcher.php index 285b1a58c6..11d3ce1cab 100644 --- a/apps/files_sharing/lib/watcher.php +++ b/apps/files_sharing/lib/watcher.php @@ -32,7 +32,7 @@ class Shared_Watcher extends Watcher { * @param string $path */ public function checkUpdate($path) { - if ($path != '' && parent::checkUpdate($path) === true) { + if (parent::checkUpdate($path) === true) { // since checkUpdate() has already updated the size of the subdirs, // only apply the update to the owner's parent dirs diff --git a/apps/files_sharing/tests/api.php b/apps/files_sharing/tests/api.php index d9a21fa959..b2f05d10ac 100644 --- a/apps/files_sharing/tests/api.php +++ b/apps/files_sharing/tests/api.php @@ -324,10 +324,10 @@ class Test_Files_Sharing_Api extends Test_Files_Sharing_Base { $this->assertTrue(is_string($result)); $testValues=array( - array('query' => 'Shared/' . $this->folder, - 'expectedResult' => '/Shared' . $this->folder . $this->filename), - array('query' => 'Shared/' . $this->folder . $this->subfolder, - 'expectedResult' => '/Shared' . $this->folder . $this->subfolder . $this->filename), + array('query' => $this->folder, + 'expectedResult' => $this->folder . $this->filename), + array('query' => $this->folder . $this->subfolder, + 'expectedResult' => $this->folder . $this->subfolder . $this->filename), ); foreach ($testValues as $value) { @@ -382,7 +382,7 @@ class Test_Files_Sharing_Api extends Test_Files_Sharing_Base { // share was successful? $this->assertTrue(is_string($result)); - $_GET['path'] = '/Shared'; + $_GET['path'] = '/'; $_GET['subfiles'] = 'true'; $result = Share\Api::getAllShares(array()); @@ -395,7 +395,7 @@ class Test_Files_Sharing_Api extends Test_Files_Sharing_Base { // we should get exactly one result $this->assertEquals(1, count($data)); - $expectedPath = '/Shared' . $this->subfolder; + $expectedPath = $this->subfolder; $this->assertEquals($expectedPath, $data[0]['path']); // cleanup @@ -444,7 +444,7 @@ class Test_Files_Sharing_Api extends Test_Files_Sharing_Base { $this->assertTrue(is_string($result)); - $_GET['path'] = '/Shared'; + $_GET['path'] = '/'; $_GET['subfiles'] = 'true'; $result = Share\Api::getAllShares(array()); @@ -457,7 +457,7 @@ class Test_Files_Sharing_Api extends Test_Files_Sharing_Base { // we should get exactly one result $this->assertEquals(1, count($data)); - $expectedPath = '/Shared' . $this->subsubfolder; + $expectedPath = $this->subsubfolder; $this->assertEquals($expectedPath, $data[0]['path']); @@ -512,8 +512,8 @@ class Test_Files_Sharing_Api extends Test_Files_Sharing_Base { $this->assertTrue(is_string($result)); - // ask for shared/subfolder - $expectedPath1 = '/Shared' . $this->subfolder; + // ask for subfolder + $expectedPath1 = $this->subfolder; $_GET['path'] = $expectedPath1; $result1 = Share\Api::getAllShares(array()); @@ -524,8 +524,8 @@ class Test_Files_Sharing_Api extends Test_Files_Sharing_Base { $data1 = $result1->getData(); $share1 = reset($data1); - // ask for shared/folder/subfolder - $expectedPath2 = '/Shared' . $this->folder . $this->subfolder; + // ask for folder/subfolder + $expectedPath2 = $this->folder . $this->subfolder; $_GET['path'] = $expectedPath2; $result2 = Share\Api::getAllShares(array()); @@ -595,7 +595,7 @@ class Test_Files_Sharing_Api extends Test_Files_Sharing_Base { $this->assertTrue(is_string($result)); - $_GET['path'] = '/Shared'; + $_GET['path'] = '/'; $_GET['subfiles'] = 'true'; $result = Share\Api::getAllShares(array()); @@ -608,7 +608,7 @@ class Test_Files_Sharing_Api extends Test_Files_Sharing_Base { // we should get exactly one result $this->assertEquals(1, count($data)); - $expectedPath = '/Shared' . $this->filename; + $expectedPath = $this->filename; $this->assertEquals($expectedPath, $data[0]['path']); @@ -866,16 +866,66 @@ class Test_Files_Sharing_Api extends Test_Files_Sharing_Base { $this->assertTrue($result3->succeeded()); + // cleanup + \Test_Files_Sharing_Api::loginHelper(\Test_Files_Sharing_Api::TEST_FILES_SHARING_API_USER1); + + $result = \OCP\Share::unshare('folder', $fileInfo1['fileid'], \OCP\Share::SHARE_TYPE_USER, + \Test_Files_Sharing_Api::TEST_FILES_SHARING_API_USER2); + + $this->assertTrue($result); + + + } - function testCorrectPath() { - $path = "/foo/bar/test.txt"; - $folder = "/correct/path"; - $expectedResult = "/correct/path/test.txt"; + /** + * @brief share a folder which contains a share mount point, should be forbidden + */ + public function testShareFolderWithAMountPoint() { + // user 1 shares a folder with user2 + \Test_Files_Sharing_Api::loginHelper(\Test_Files_Sharing_Api::TEST_FILES_SHARING_API_USER1); - $shareApiDummy = new TestShareApi(); + $fileInfo = $this->view->getFileInfo($this->folder); - $this->assertSame($expectedResult, $shareApiDummy->correctPathTest($path, $folder)); + $result = \OCP\Share::shareItem('folder', $fileInfo['fileid'], \OCP\Share::SHARE_TYPE_USER, + \Test_Files_Sharing_Api::TEST_FILES_SHARING_API_USER2, 31); + + $this->assertTrue($result); + + // user2 shares a file from the folder as link + \Test_Files_Sharing_Api::loginHelper(\Test_Files_Sharing_Api::TEST_FILES_SHARING_API_USER2); + + $view = new \OC\Files\View('/' . \Test_Files_Sharing_Api::TEST_FILES_SHARING_API_USER2 . '/files'); + $view->mkdir("localDir"); + + // move mount point to the folder "localDir" + $result = $view->rename($this->folder, 'localDir/'.$this->folder); + $this->assertTrue($result !== false); + + // try to share "localDir" + $fileInfo2 = $view->getFileInfo('localDir'); + + $this->assertTrue($fileInfo2 instanceof \OC\Files\FileInfo); + + try { + $result2 = \OCP\Share::shareItem('folder', $fileInfo2['fileid'], \OCP\Share::SHARE_TYPE_USER, + \Test_Files_Sharing_Api::TEST_FILES_SHARING_API_USER3, 31); + } catch (\Exception $e) { + $result2 = false; + } + + $this->assertFalse($result2); + + //cleanup + + $result = $view->rename('localDir/' . $this->folder, $this->folder); + $this->assertTrue($result !== false); + $view->unlink('localDir'); + + \Test_Files_Sharing_Api::loginHelper(\Test_Files_Sharing_Api::TEST_FILES_SHARING_API_USER1); + + \OCP\Share::unshare('folder', $fileInfo['fileid'], \OCP\Share::SHARE_TYPE_USER, + \Test_Files_Sharing_Api::TEST_FILES_SHARING_API_USER2); } /** @@ -902,17 +952,3 @@ class Test_Files_Sharing_Api extends Test_Files_Sharing_Base { } } - -/** - * @brief dumnmy class to test protected methods - */ -class TestShareApi extends \OCA\Files\Share\Api { - - /** - * @param string $path - * @param string $folder - */ - public function correctPathTest($path, $folder) { - return self::correctPath($path, $folder); - } -} diff --git a/apps/files_sharing/tests/base.php b/apps/files_sharing/tests/base.php index d44972d01f..495dca072c 100644 --- a/apps/files_sharing/tests/base.php +++ b/apps/files_sharing/tests/base.php @@ -102,22 +102,20 @@ abstract class Test_Files_Sharing_Base extends \PHPUnit_Framework_TestCase { * @param bool $password */ protected static function loginHelper($user, $create = false, $password = false) { - if ($create) { - \OC_User::createUser($user, $user); - } if ($password === false) { $password = $user; } + if ($create) { + \OC_User::createUser($user, $password); + } + \OC_Util::tearDownFS(); \OC_User::setUserId(''); \OC\Files\Filesystem::tearDown(); - \OC_Util::setupFS($user); \OC_User::setUserId($user); - - $params['uid'] = $user; - $params['password'] = $password; + \OC_Util::setupFS($user); } /** diff --git a/apps/files_sharing/tests/cache.php b/apps/files_sharing/tests/cache.php index 47969833ab..1af73c558d 100644 --- a/apps/files_sharing/tests/cache.php +++ b/apps/files_sharing/tests/cache.php @@ -68,7 +68,7 @@ class Test_Files_Sharing_Cache extends Test_Files_Sharing_Base { // retrieve the shared storage $secondView = new \OC\Files\View('/' . self::TEST_FILES_SHARING_API_USER2); - list($this->sharedStorage, $internalPath) = $secondView->resolvePath('files/Shared/shareddir'); + list($this->sharedStorage, $internalPath) = $secondView->resolvePath('files/shareddir'); $this->sharedCache = $this->sharedStorage->getCache(); } @@ -98,46 +98,46 @@ class Test_Files_Sharing_Cache extends Test_Files_Sharing_Base { function testSearchByMime() { $results = $this->sharedStorage->getCache()->searchByMime('text'); $check = array( - array( - 'name' => 'shared single file.txt', - 'path' => 'shared single file.txt' - ), array( 'name' => 'bar.txt', - 'path' => 'shareddir/bar.txt' + 'path' => 'bar.txt' ), array( 'name' => 'another too.txt', - 'path' => 'shareddir/subdir/another too.txt' + 'path' => 'subdir/another too.txt' ), array( 'name' => 'another.txt', - 'path' => 'shareddir/subdir/another.txt' + 'path' => 'subdir/another.txt' ), ); $this->verifyFiles($check, $results); - $results2 = $this->sharedStorage->getCache()->searchByMime('text/plain'); - $this->verifyFiles($check, $results); } function testGetFolderContentsInRoot() { - $results = $this->user2View->getDirectoryContent('/Shared/'); + $results = $this->user2View->getDirectoryContent('/'); + // we should get the shared items "shareddir" and "shared single file.txt" + // additional root will always contain the example file "welcome.txt", + // so this will be part of the result $this->verifyFiles( array( + array( + 'name' => 'welcome.txt', + 'path' => 'files/welcome.txt', + 'mimetype' => 'text/plain', + ), array( 'name' => 'shareddir', - 'path' => '/shareddir', + 'path' => 'files/shareddir', 'mimetype' => 'httpd/unix-directory', - 'usersPath' => 'files/Shared/shareddir' ), array( 'name' => 'shared single file.txt', - 'path' => '/shared single file.txt', + 'path' => 'files/shared single file.txt', 'mimetype' => 'text/plain', - 'usersPath' => 'files/Shared/shared single file.txt' ), ), $results @@ -145,27 +145,24 @@ class Test_Files_Sharing_Cache extends Test_Files_Sharing_Base { } function testGetFolderContentsInSubdir() { - $results = $this->user2View->getDirectoryContent('/Shared/shareddir'); + $results = $this->user2View->getDirectoryContent('/shareddir'); $this->verifyFiles( array( array( 'name' => 'bar.txt', - 'path' => 'files/container/shareddir/bar.txt', + 'path' => 'bar.txt', 'mimetype' => 'text/plain', - 'usersPath' => 'files/Shared/shareddir/bar.txt' ), array( 'name' => 'emptydir', - 'path' => 'files/container/shareddir/emptydir', + 'path' => 'emptydir', 'mimetype' => 'httpd/unix-directory', - 'usersPath' => 'files/Shared/shareddir/emptydir' ), array( 'name' => 'subdir', - 'path' => 'files/container/shareddir/subdir', + 'path' => 'subdir', 'mimetype' => 'httpd/unix-directory', - 'usersPath' => 'files/Shared/shareddir/subdir' ), ), $results @@ -182,27 +179,24 @@ class Test_Files_Sharing_Cache extends Test_Files_Sharing_Base { self::loginHelper(self::TEST_FILES_SHARING_API_USER3); $thirdView = new \OC\Files\View('/' . self::TEST_FILES_SHARING_API_USER3 . '/files'); - $results = $thirdView->getDirectoryContent('/Shared/subdir'); + $results = $thirdView->getDirectoryContent('/subdir'); $this->verifyFiles( array( array( 'name' => 'another too.txt', - 'path' => 'files/container/shareddir/subdir/another too.txt', + 'path' => 'another too.txt', 'mimetype' => 'text/plain', - 'usersPath' => 'files/Shared/subdir/another too.txt' ), array( 'name' => 'another.txt', - 'path' => 'files/container/shareddir/subdir/another.txt', + 'path' => 'another.txt', 'mimetype' => 'text/plain', - 'usersPath' => 'files/Shared/subdir/another.txt' ), array( 'name' => 'not a text file.xml', - 'path' => 'files/container/shareddir/subdir/not a text file.xml', + 'path' => 'not a text file.xml', 'mimetype' => 'application/xml', - 'usersPath' => 'files/Shared/subdir/not a text file.xml' ), ), $results @@ -254,14 +248,14 @@ class Test_Files_Sharing_Cache extends Test_Files_Sharing_Base { \OC_Util::tearDownFS(); self::loginHelper(self::TEST_FILES_SHARING_API_USER2); - $this->assertTrue(\OC\Files\Filesystem::file_exists('/Shared/test.txt')); - list($sharedStorage) = \OC\Files\Filesystem::resolvePath('/' . self::TEST_FILES_SHARING_API_USER2 . '/files/Shared/test.txt'); + $this->assertTrue(\OC\Files\Filesystem::file_exists('/test.txt')); + list($sharedStorage) = \OC\Files\Filesystem::resolvePath('/' . self::TEST_FILES_SHARING_API_USER2 . '/files/test.txt'); /** * @var \OC\Files\Storage\Shared $sharedStorage */ $sharedCache = $sharedStorage->getCache(); - $this->assertEquals('test.txt', $sharedCache->getPathById($info->getId())); + $this->assertEquals('', $sharedCache->getPathById($info->getId())); } public function testGetPathByIdShareSubFolder() { @@ -275,14 +269,14 @@ class Test_Files_Sharing_Cache extends Test_Files_Sharing_Base { \OC_Util::tearDownFS(); self::loginHelper(self::TEST_FILES_SHARING_API_USER2); - $this->assertTrue(\OC\Files\Filesystem::file_exists('/Shared/foo')); - list($sharedStorage) = \OC\Files\Filesystem::resolvePath('/' . self::TEST_FILES_SHARING_API_USER2 . '/files/Shared/foo'); + $this->assertTrue(\OC\Files\Filesystem::file_exists('/foo')); + list($sharedStorage) = \OC\Files\Filesystem::resolvePath('/' . self::TEST_FILES_SHARING_API_USER2 . '/files/foo'); /** * @var \OC\Files\Storage\Shared $sharedStorage */ $sharedCache = $sharedStorage->getCache(); - $this->assertEquals('foo', $sharedCache->getPathById($folderInfo->getId())); - $this->assertEquals('foo/bar/test.txt', $sharedCache->getPathById($fileInfo->getId())); + $this->assertEquals('', $sharedCache->getPathById($folderInfo->getId())); + $this->assertEquals('bar/test.txt', $sharedCache->getPathById($fileInfo->getId())); } } diff --git a/apps/files_sharing/tests/permissions.php b/apps/files_sharing/tests/permissions.php index e301d384a4..5ac251b052 100644 --- a/apps/files_sharing/tests/permissions.php +++ b/apps/files_sharing/tests/permissions.php @@ -23,6 +23,9 @@ require_once __DIR__ . '/base.php'; class Test_Files_Sharing_Permissions extends Test_Files_Sharing_Base { + private $sharedStorageRestrictedShare; + private $sharedCacheRestrictedShare; + function setUp() { parent::setUp(); @@ -55,8 +58,10 @@ class Test_Files_Sharing_Permissions extends Test_Files_Sharing_Base { // retrieve the shared storage $this->secondView = new \OC\Files\View('/' . self::TEST_FILES_SHARING_API_USER2); - list($this->sharedStorage, $internalPath) = $this->secondView->resolvePath('files/Shared/shareddir'); + list($this->sharedStorage, $internalPath) = $this->secondView->resolvePath('files/shareddir'); + list($this->sharedStorageRestrictedShare, $internalPath) = $this->secondView->resolvePath('files/shareddirrestricted'); $this->sharedCache = $this->sharedStorage->getCache(); + $this->sharedCacheRestrictedShare = $this->sharedStorageRestrictedShare->getCache(); } function tearDown() { @@ -86,9 +91,9 @@ class Test_Files_Sharing_Permissions extends Test_Files_Sharing_Base { $this->assertEquals(31, $sharedDirPerms); $sharedDirPerms = $this->sharedStorage->getPermissions('shareddir/textfile.txt'); $this->assertEquals(31, $sharedDirPerms); - $sharedDirRestrictedPerms = $this->sharedStorage->getPermissions('shareddirrestricted'); + $sharedDirRestrictedPerms = $this->sharedStorageRestrictedShare->getPermissions('shareddirrestricted'); $this->assertEquals(7, $sharedDirRestrictedPerms); - $sharedDirRestrictedPerms = $this->sharedStorage->getPermissions('shareddirrestricted/textfile.txt'); + $sharedDirRestrictedPerms = $this->sharedStorageRestrictedShare->getPermissions('shareddirrestricted/textfile.txt'); $this->assertEquals(7, $sharedDirRestrictedPerms); } @@ -96,12 +101,12 @@ class Test_Files_Sharing_Permissions extends Test_Files_Sharing_Base { * Test that the permissions of shared directory are returned correctly */ function testGetDirectoryPermissions() { - $contents = $this->secondView->getDirectoryContent('files/Shared/shareddir'); + $contents = $this->secondView->getDirectoryContent('files/shareddir'); $this->assertEquals('subdir', $contents[0]['name']); $this->assertEquals(31, $contents[0]['permissions']); $this->assertEquals('textfile.txt', $contents[1]['name']); $this->assertEquals(31, $contents[1]['permissions']); - $contents = $this->secondView->getDirectoryContent('files/Shared/shareddirrestricted'); + $contents = $this->secondView->getDirectoryContent('files/shareddirrestricted'); $this->assertEquals('subdir', $contents[0]['name']); $this->assertEquals(7, $contents[0]['permissions']); $this->assertEquals('textfile1.txt', $contents[1]['name']); diff --git a/apps/files_sharing/tests/updater.php b/apps/files_sharing/tests/updater.php index 79ae4879b6..3427cfe388 100644 --- a/apps/files_sharing/tests/updater.php +++ b/apps/files_sharing/tests/updater.php @@ -20,6 +20,8 @@ * */ +require_once __DIR__ . '/../appinfo/update.php'; + /** * Class Test_Files_Sharing_Updater */ @@ -88,4 +90,57 @@ class Test_Files_Sharing_Updater extends \PHPUnit_Framework_TestCase { $result = $countItems->execute()->fetchOne(); $this->assertEquals(2, $result); } + + /** + * test update for the removal of the logical "Shared" folder. It should update + * the file_target for every share and create a physical "Shared" folder for each user + */ + function testRemoveSharedFolder() { + self::prepareDB(); + // run the update routine to remove the shared folder and replace it with a real folder + removeSharedFolder(false, 2); + + // verify results + $query = \OC_DB::prepare('SELECT * FROM `*PREFIX*share`'); + $result = $query->execute(array()); + + $newDBContent = $result->fetchAll(); + + foreach ($newDBContent as $row) { + if ((int)$row['share_type'] === \OCP\Share::SHARE_TYPE_USER) { + $this->assertSame('/Shared', substr($row['file_target'], 0, strlen('/Shared'))); + } else { + $this->assertSame('/ShouldNotChange', $row['file_target']); + } + } + + $this->cleanupSharedTable(); + + } + + private function cleanupSharedTable() { + $query = \OC_DB::prepare('DELETE FROM `*PREFIX*share`'); + $query->execute(); + } + + private function prepareDB() { + $this->cleanupSharedTable(); + // add items except one - because this is the test case for the broken share table + $addItems = \OC_DB::prepare('INSERT INTO `*PREFIX*share` (`share_type`, `item_type`, ' . + '`share_with`, `uid_owner` , `file_target`) ' . + 'VALUES (?, ?, ?, ?, ?)'); + $items = array( + array(\OCP\Share::SHARE_TYPE_USER, 'file', 'user1', 'admin' , '/foo'), + array(\OCP\Share::SHARE_TYPE_USER, 'folder', 'user2', 'admin', '/foo2'), + array(\OCP\Share::SHARE_TYPE_USER, 'file', 'user3', 'admin', '/foo3'), + array(\OCP\Share::SHARE_TYPE_USER, 'folder', 'user4', 'admin', '/foo4'), + array(\OCP\Share::SHARE_TYPE_LINK, 'file', 'user1', 'admin', '/ShouldNotChange'), + array(\OCP\Share::SHARE_TYPE_CONTACT, 'contact', 'admin', 'user1', '/ShouldNotChange'), + + ); + foreach($items as $item) { + // the number is used as path_hash + $addItems->execute($item); + } + } } diff --git a/apps/files_sharing/tests/watcher.php b/apps/files_sharing/tests/watcher.php index 5ab716e829..bce93c80a6 100644 --- a/apps/files_sharing/tests/watcher.php +++ b/apps/files_sharing/tests/watcher.php @@ -48,7 +48,7 @@ class Test_Files_Sharing_Watcher extends Test_Files_Sharing_Base { // retrieve the shared storage $secondView = new \OC\Files\View('/' . self::TEST_FILES_SHARING_API_USER2); - list($this->sharedStorage, $internalPath) = $secondView->resolvePath('files/Shared/shareddir'); + list($this->sharedStorage, $internalPath) = $secondView->resolvePath('files/shareddir'); $this->sharedCache = $this->sharedStorage->getCache(); } @@ -77,12 +77,12 @@ class Test_Files_Sharing_Watcher extends Test_Files_Sharing_Base { $textData = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'; $dataLen = strlen($textData); - $this->sharedCache->put('shareddir/bar.txt', array('storage_mtime' => 10)); - $this->sharedStorage->file_put_contents('shareddir/bar.txt', $textData); - $this->sharedCache->put('shareddir', array('storage_mtime' => 10)); + $this->sharedCache->put('bar.txt', array('mtime' => 10, 'storage_mtime' => 10, 'size' => $dataLen, 'mimetype' => 'text/plain')); + $this->sharedStorage->file_put_contents('bar.txt', $textData); + $this->sharedCache->put('', array('mtime' => 10, 'storage_mtime' => 10, 'size' => '-1', 'mimetype' => 'httpd/unix-directory')); // run the propagation code - $result = $this->sharedStorage->getWatcher()->checkUpdate('shareddir'); + $result = $this->sharedStorage->getWatcher()->checkUpdate(''); $this->assertTrue($result); @@ -94,7 +94,7 @@ class Test_Files_Sharing_Watcher extends Test_Files_Sharing_Base { $this->assertEquals($initialSizes['files/container/shareddir'] + $dataLen, $newSizes['files/container/shareddir']); // no more updates - $result = $this->sharedStorage->getWatcher()->checkUpdate('shareddir'); + $result = $this->sharedStorage->getWatcher()->checkUpdate(''); $this->assertFalse($result); } @@ -108,12 +108,12 @@ class Test_Files_Sharing_Watcher extends Test_Files_Sharing_Base { $textData = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'; $dataLen = strlen($textData); - $this->sharedCache->put('shareddir/subdir/bar.txt', array('storage_mtime' => 10)); - $this->sharedStorage->file_put_contents('shareddir/subdir/bar.txt', $textData); - $this->sharedCache->put('shareddir/subdir', array('storage_mtime' => 10)); + $this->sharedCache->put('subdir/bar.txt', array('mtime' => 10, 'storage_mtime' => 10, 'size' => $dataLen, 'mimetype' => 'text/plain')); + $this->sharedStorage->file_put_contents('subdir/bar.txt', $textData); + $this->sharedCache->put('subdir', array('mtime' => 10, 'storage_mtime' => 10, 'size' => $dataLen, 'mimetype' => 'text/plain')); // run the propagation code - $result = $this->sharedStorage->getWatcher()->checkUpdate('shareddir/subdir'); + $result = $this->sharedStorage->getWatcher()->checkUpdate('subdir'); $this->assertTrue($result); @@ -126,22 +126,11 @@ class Test_Files_Sharing_Watcher extends Test_Files_Sharing_Base { $this->assertEquals($initialSizes['files/container/shareddir/subdir'] + $dataLen, $newSizes['files/container/shareddir/subdir']); // no more updates - $result = $this->sharedStorage->getWatcher()->checkUpdate('shareddir/subdir'); + $result = $this->sharedStorage->getWatcher()->checkUpdate('subdir'); $this->assertFalse($result); } - function testNoUpdateOnRoot() { - // no updates when called for root path - $result = $this->sharedStorage->getWatcher()->checkUpdate(''); - - $this->assertFalse($result); - - // FIXME: for some reason when running this "naked" test, - // there will be remaining nonsensical entries in the - // database with a path "test-share-user1/container/..." - } - /** * Returns the sizes of the path and its parent dirs in a hash * where the key is the path and the value is the size. diff --git a/apps/files_trashbin/js/trash.js b/apps/files_trashbin/js/trash.js index 5cd49e19aa..f7724d07d2 100644 --- a/apps/files_trashbin/js/trash.js +++ b/apps/files_trashbin/js/trash.js @@ -30,7 +30,7 @@ $(document).ready(function() { function removeCallback(result) { if (result.status !== 'success') { - OC.dialogs.alert(result.data.message, t('core', 'Error')); + OC.dialogs.alert(result.data.message, t('files_trashbin', 'Error')); } var files = result.data.success; @@ -59,7 +59,7 @@ $(document).ready(function() { }, removeCallback ); - }); + }, t('files_trashbin', 'Restore')); }; FileActions.register('all', 'Delete', OC.PERMISSION_READ, function() { @@ -121,7 +121,7 @@ $(document).ready(function() { function(result) { if (allFiles) { if (result.status !== 'success') { - OC.dialogs.alert(result.data.message, t('core', 'Error')); + OC.dialogs.alert(result.data.message, t('files_trashbin', 'Error')); } FileList.hideMask(); // simply remove all files @@ -170,7 +170,7 @@ $(document).ready(function() { function(result) { if (allFiles) { if (result.status !== 'success') { - OC.dialogs.alert(result.data.message, t('core', 'Error')); + OC.dialogs.alert(result.data.message, t('files_trashbin', 'Error')); } FileList.hideMask(); // simply remove all files diff --git a/apps/files_trashbin/l10n/ar.php b/apps/files_trashbin/l10n/ar.php index 4084daa127..b3abc7df86 100644 --- a/apps/files_trashbin/l10n/ar.php +++ b/apps/files_trashbin/l10n/ar.php @@ -4,12 +4,12 @@ $TRANSLATIONS = array( "Couldn't restore %s" => "تعذّر استرجاع %s ", "Deleted files" => "حذف الملفات", "Error" => "خطأ", +"Deleted Files" => "الملفات المحذوفه", "restored" => "تمت الاستعادة", "Nothing in here. Your trash bin is empty!" => "لا يوجد شيء هنا. سلة المهملات خاليه.", "Name" => "اسم", "Restore" => "استعيد", "Deleted" => "تم الحذف", -"Delete" => "إلغاء", -"Deleted Files" => "الملفات المحذوفه" +"Delete" => "إلغاء" ); $PLURAL_FORMS = "nplurals=6; plural=n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 && n%100<=10 ? 3 : n%100>=11 && n%100<=99 ? 4 : 5;"; diff --git a/apps/files_trashbin/l10n/ast.php b/apps/files_trashbin/l10n/ast.php index 91c122bd4d..688e1ce3d8 100644 --- a/apps/files_trashbin/l10n/ast.php +++ b/apps/files_trashbin/l10n/ast.php @@ -1,8 +1,13 @@ "Nun pudo desaniciase %s dafechu", +"Couldn't restore %s" => "Nun pudo restaurase %s", "Error" => "Fallu", +"Deleted Files" => "Ficheros desaniciaos", +"Nothing in here. Your trash bin is empty!" => "Nun hai un res equí. La papelera ta balera!", "Name" => "Nome", "Restore" => "Restaurar", +"Deleted" => "Desaniciáu", "Delete" => "Desaniciar" ); $PLURAL_FORMS = "nplurals=2; plural=(n != 1);"; diff --git a/apps/files_trashbin/l10n/bg_BG.php b/apps/files_trashbin/l10n/bg_BG.php index 2f1521feaa..8c9e658068 100644 --- a/apps/files_trashbin/l10n/bg_BG.php +++ b/apps/files_trashbin/l10n/bg_BG.php @@ -3,11 +3,11 @@ $TRANSLATIONS = array( "Couldn't delete %s permanently" => "Невъзможно перманентното изтриване на %s", "Couldn't restore %s" => "Невъзможно възтановяване на %s", "Error" => "Грешка", +"Deleted Files" => "Изтрити файлове", "Nothing in here. Your trash bin is empty!" => "Няма нищо. Кофата е празна!", "Name" => "Име", "Restore" => "Възтановяване", "Deleted" => "Изтрито", -"Delete" => "Изтриване", -"Deleted Files" => "Изтрити файлове" +"Delete" => "Изтриване" ); $PLURAL_FORMS = "nplurals=2; plural=(n != 1);"; diff --git a/apps/files_trashbin/l10n/ca.php b/apps/files_trashbin/l10n/ca.php index c99b414c7a..196d6ac00a 100644 --- a/apps/files_trashbin/l10n/ca.php +++ b/apps/files_trashbin/l10n/ca.php @@ -4,12 +4,12 @@ $TRANSLATIONS = array( "Couldn't restore %s" => "No s'ha pogut restaurar %s", "Deleted files" => "Fitxers esborrats", "Error" => "Error", +"Deleted Files" => "Fitxers eliminats", "restored" => "restaurat", "Nothing in here. Your trash bin is empty!" => "La paperera està buida!", "Name" => "Nom", "Restore" => "Recupera", "Deleted" => "Eliminat", -"Delete" => "Esborra", -"Deleted Files" => "Fitxers eliminats" +"Delete" => "Esborra" ); $PLURAL_FORMS = "nplurals=2; plural=(n != 1);"; diff --git a/apps/files_trashbin/l10n/cs_CZ.php b/apps/files_trashbin/l10n/cs_CZ.php index e0c46c5137..ed795582e4 100644 --- a/apps/files_trashbin/l10n/cs_CZ.php +++ b/apps/files_trashbin/l10n/cs_CZ.php @@ -4,12 +4,12 @@ $TRANSLATIONS = array( "Couldn't restore %s" => "Nelze obnovit %s", "Deleted files" => "Odstraněné soubory", "Error" => "Chyba", +"Deleted Files" => "Smazané soubory", "restored" => "obnoveno", "Nothing in here. Your trash bin is empty!" => "Žádný obsah. Váš koš je prázdný.", "Name" => "Název", "Restore" => "Obnovit", "Deleted" => "Smazáno", -"Delete" => "Smazat", -"Deleted Files" => "Smazané soubory" +"Delete" => "Smazat" ); $PLURAL_FORMS = "nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2;"; diff --git a/apps/files_trashbin/l10n/cy_GB.php b/apps/files_trashbin/l10n/cy_GB.php index bc8a318733..7b1405777d 100644 --- a/apps/files_trashbin/l10n/cy_GB.php +++ b/apps/files_trashbin/l10n/cy_GB.php @@ -4,11 +4,11 @@ $TRANSLATIONS = array( "Couldn't restore %s" => "Methwyd adfer %s", "Deleted files" => "Ffeiliau ddilewyd", "Error" => "Gwall", +"Deleted Files" => "Ffeiliau Ddilewyd", "Nothing in here. Your trash bin is empty!" => "Does dim byd yma. Mae eich bin sbwriel yn wag!", "Name" => "Enw", "Restore" => "Adfer", "Deleted" => "Wedi dileu", -"Delete" => "Dileu", -"Deleted Files" => "Ffeiliau Ddilewyd" +"Delete" => "Dileu" ); $PLURAL_FORMS = "nplurals=4; plural=(n==1) ? 0 : (n==2) ? 1 : (n != 8 && n != 11) ? 2 : 3;"; diff --git a/apps/files_trashbin/l10n/da.php b/apps/files_trashbin/l10n/da.php index 831ba6067d..7f7b65bca2 100644 --- a/apps/files_trashbin/l10n/da.php +++ b/apps/files_trashbin/l10n/da.php @@ -4,12 +4,12 @@ $TRANSLATIONS = array( "Couldn't restore %s" => "Kunne ikke gendanne %s", "Deleted files" => "Slettede filer", "Error" => "Fejl", +"Deleted Files" => "Slettede filer", "restored" => "Gendannet", "Nothing in here. Your trash bin is empty!" => "Intet at se her. Din papirkurv er tom!", "Name" => "Navn", "Restore" => "Gendan", "Deleted" => "Slettet", -"Delete" => "Slet", -"Deleted Files" => "Slettede filer" +"Delete" => "Slet" ); $PLURAL_FORMS = "nplurals=2; plural=(n != 1);"; diff --git a/apps/files_trashbin/l10n/de.php b/apps/files_trashbin/l10n/de.php index fd00b4c433..4778e159e1 100644 --- a/apps/files_trashbin/l10n/de.php +++ b/apps/files_trashbin/l10n/de.php @@ -4,12 +4,12 @@ $TRANSLATIONS = array( "Couldn't restore %s" => "Konnte %s nicht wiederherstellen", "Deleted files" => "Gelöschte Dateien", "Error" => "Fehler", +"Deleted Files" => "Gelöschte Dateien", "restored" => "Wiederhergestellt", "Nothing in here. Your trash bin is empty!" => "Nichts zu löschen, der Papierkorb ist leer!", "Name" => "Name", "Restore" => "Wiederherstellen", "Deleted" => "gelöscht", -"Delete" => "Löschen", -"Deleted Files" => "Gelöschte Dateien" +"Delete" => "Löschen" ); $PLURAL_FORMS = "nplurals=2; plural=(n != 1);"; diff --git a/apps/files_trashbin/l10n/de_CH.php b/apps/files_trashbin/l10n/de_CH.php index 665be5d5ee..603d82f5c7 100644 --- a/apps/files_trashbin/l10n/de_CH.php +++ b/apps/files_trashbin/l10n/de_CH.php @@ -4,12 +4,12 @@ $TRANSLATIONS = array( "Couldn't restore %s" => "Konnte %s nicht wiederherstellen", "Deleted files" => "Gelöschte Dateien", "Error" => "Fehler", +"Deleted Files" => "Gelöschte Dateien", "restored" => "Wiederhergestellt", "Nothing in here. Your trash bin is empty!" => "Nichts zu löschen, Ihr Papierkorb ist leer!", "Name" => "Name", "Restore" => "Wiederherstellen", "Deleted" => "Gelöscht", -"Delete" => "Löschen", -"Deleted Files" => "Gelöschte Dateien" +"Delete" => "Löschen" ); $PLURAL_FORMS = "nplurals=2; plural=(n != 1);"; diff --git a/apps/files_trashbin/l10n/de_DE.php b/apps/files_trashbin/l10n/de_DE.php index 665be5d5ee..603d82f5c7 100644 --- a/apps/files_trashbin/l10n/de_DE.php +++ b/apps/files_trashbin/l10n/de_DE.php @@ -4,12 +4,12 @@ $TRANSLATIONS = array( "Couldn't restore %s" => "Konnte %s nicht wiederherstellen", "Deleted files" => "Gelöschte Dateien", "Error" => "Fehler", +"Deleted Files" => "Gelöschte Dateien", "restored" => "Wiederhergestellt", "Nothing in here. Your trash bin is empty!" => "Nichts zu löschen, Ihr Papierkorb ist leer!", "Name" => "Name", "Restore" => "Wiederherstellen", "Deleted" => "Gelöscht", -"Delete" => "Löschen", -"Deleted Files" => "Gelöschte Dateien" +"Delete" => "Löschen" ); $PLURAL_FORMS = "nplurals=2; plural=(n != 1);"; diff --git a/apps/files_trashbin/l10n/el.php b/apps/files_trashbin/l10n/el.php index 567958de90..23b23ed93a 100644 --- a/apps/files_trashbin/l10n/el.php +++ b/apps/files_trashbin/l10n/el.php @@ -4,12 +4,12 @@ $TRANSLATIONS = array( "Couldn't restore %s" => "Αδυναμία επαναφοράς %s", "Deleted files" => "Διαγραμμένα αρχεία", "Error" => "Σφάλμα", +"Deleted Files" => "Διαγραμμένα Αρχεία", "restored" => "επαναφέρθηκαν", "Nothing in here. Your trash bin is empty!" => "Δεν υπάρχει τίποτα εδώ. Ο κάδος σας είναι άδειος!", "Name" => "Όνομα", "Restore" => "Επαναφορά", "Deleted" => "Διαγραμμένα", -"Delete" => "Διαγραφή", -"Deleted Files" => "Διαγραμμένα Αρχεία" +"Delete" => "Διαγραφή" ); $PLURAL_FORMS = "nplurals=2; plural=(n != 1);"; diff --git a/apps/files_trashbin/l10n/en_GB.php b/apps/files_trashbin/l10n/en_GB.php index 04abf2d6f2..a660b4b1ca 100644 --- a/apps/files_trashbin/l10n/en_GB.php +++ b/apps/files_trashbin/l10n/en_GB.php @@ -4,12 +4,12 @@ $TRANSLATIONS = array( "Couldn't restore %s" => "Couldn't restore %s", "Deleted files" => "Deleted files", "Error" => "Error", +"Deleted Files" => "Deleted Files", "restored" => "restored", "Nothing in here. Your trash bin is empty!" => "Nothing in here. Your recycle bin is empty!", "Name" => "Name", "Restore" => "Restore", "Deleted" => "Deleted", -"Delete" => "Delete", -"Deleted Files" => "Deleted Files" +"Delete" => "Delete" ); $PLURAL_FORMS = "nplurals=2; plural=(n != 1);"; diff --git a/apps/files_trashbin/l10n/eo.php b/apps/files_trashbin/l10n/eo.php index b3248406f5..d644f0f642 100644 --- a/apps/files_trashbin/l10n/eo.php +++ b/apps/files_trashbin/l10n/eo.php @@ -4,12 +4,12 @@ $TRANSLATIONS = array( "Couldn't restore %s" => "Ne povis restaŭriĝi %s", "Deleted files" => "Forigitaj dosieroj", "Error" => "Eraro", +"Deleted Files" => "Forigitaj dosieroj", "restored" => "restaŭrita", "Nothing in here. Your trash bin is empty!" => "Nenio estas ĉi tie. Via rubujo malplenas!", "Name" => "Nomo", "Restore" => "Restaŭri", "Deleted" => "Forigita", -"Delete" => "Forigi", -"Deleted Files" => "Forigitaj dosieroj" +"Delete" => "Forigi" ); $PLURAL_FORMS = "nplurals=2; plural=(n != 1);"; diff --git a/apps/files_trashbin/l10n/es.php b/apps/files_trashbin/l10n/es.php index 79f122ad35..c0dc6bb45c 100644 --- a/apps/files_trashbin/l10n/es.php +++ b/apps/files_trashbin/l10n/es.php @@ -4,12 +4,12 @@ $TRANSLATIONS = array( "Couldn't restore %s" => "No se puede restaurar %s", "Deleted files" => "Archivos eliminados", "Error" => "Error", +"Deleted Files" => "Archivos Eliminados", "restored" => "recuperado", "Nothing in here. Your trash bin is empty!" => "No hay nada aquí. ¡Tu papelera esta vacía!", "Name" => "Nombre", "Restore" => "Recuperar", "Deleted" => "Eliminado", -"Delete" => "Eliminar", -"Deleted Files" => "Archivos Eliminados" +"Delete" => "Eliminar" ); $PLURAL_FORMS = "nplurals=2; plural=(n != 1);"; diff --git a/apps/files_trashbin/l10n/es_AR.php b/apps/files_trashbin/l10n/es_AR.php index 50b337b814..b354dd656b 100644 --- a/apps/files_trashbin/l10n/es_AR.php +++ b/apps/files_trashbin/l10n/es_AR.php @@ -4,12 +4,12 @@ $TRANSLATIONS = array( "Couldn't restore %s" => "No se pudo restaurar %s", "Deleted files" => "Archivos borrados", "Error" => "Error", +"Deleted Files" => "Archivos eliminados", "restored" => "recuperado", "Nothing in here. Your trash bin is empty!" => "No hay nada acá. ¡La papelera está vacía!", "Name" => "Nombre", "Restore" => "Recuperar", "Deleted" => "Borrado", -"Delete" => "Borrar", -"Deleted Files" => "Archivos eliminados" +"Delete" => "Borrar" ); $PLURAL_FORMS = "nplurals=2; plural=(n != 1);"; diff --git a/apps/files_trashbin/l10n/es_MX.php b/apps/files_trashbin/l10n/es_MX.php index 79f122ad35..c0dc6bb45c 100644 --- a/apps/files_trashbin/l10n/es_MX.php +++ b/apps/files_trashbin/l10n/es_MX.php @@ -4,12 +4,12 @@ $TRANSLATIONS = array( "Couldn't restore %s" => "No se puede restaurar %s", "Deleted files" => "Archivos eliminados", "Error" => "Error", +"Deleted Files" => "Archivos Eliminados", "restored" => "recuperado", "Nothing in here. Your trash bin is empty!" => "No hay nada aquí. ¡Tu papelera esta vacía!", "Name" => "Nombre", "Restore" => "Recuperar", "Deleted" => "Eliminado", -"Delete" => "Eliminar", -"Deleted Files" => "Archivos Eliminados" +"Delete" => "Eliminar" ); $PLURAL_FORMS = "nplurals=2; plural=(n != 1);"; diff --git a/apps/files_trashbin/l10n/et_EE.php b/apps/files_trashbin/l10n/et_EE.php index 69ed5cee76..69d20cb195 100644 --- a/apps/files_trashbin/l10n/et_EE.php +++ b/apps/files_trashbin/l10n/et_EE.php @@ -4,12 +4,12 @@ $TRANSLATIONS = array( "Couldn't restore %s" => "%s ei saa taastada", "Deleted files" => "Kustutatud failid", "Error" => "Viga", +"Deleted Files" => "Kustutatud failid", "restored" => "taastatud", "Nothing in here. Your trash bin is empty!" => "Siin pole midagi. Sinu prügikast on tühi!", "Name" => "Nimi", "Restore" => "Taasta", "Deleted" => "Kustutatud", -"Delete" => "Kustuta", -"Deleted Files" => "Kustutatud failid" +"Delete" => "Kustuta" ); $PLURAL_FORMS = "nplurals=2; plural=(n != 1);"; diff --git a/apps/files_trashbin/l10n/eu.php b/apps/files_trashbin/l10n/eu.php index 648aaaf738..42476bccfe 100644 --- a/apps/files_trashbin/l10n/eu.php +++ b/apps/files_trashbin/l10n/eu.php @@ -4,12 +4,12 @@ $TRANSLATIONS = array( "Couldn't restore %s" => "Ezin izan da %s berreskuratu", "Deleted files" => "Ezabatutako fitxategiak", "Error" => "Errorea", +"Deleted Files" => "Ezabatutako Fitxategiak", "restored" => "Berrezarrita", "Nothing in here. Your trash bin is empty!" => "Ez dago ezer ez. Zure zakarrontzia hutsik dago!", "Name" => "Izena", "Restore" => "Berrezarri", "Deleted" => "Ezabatuta", -"Delete" => "Ezabatu", -"Deleted Files" => "Ezabatutako Fitxategiak" +"Delete" => "Ezabatu" ); $PLURAL_FORMS = "nplurals=2; plural=(n != 1);"; diff --git a/apps/files_trashbin/l10n/fa.php b/apps/files_trashbin/l10n/fa.php index 40cdec8a48..407524eb62 100644 --- a/apps/files_trashbin/l10n/fa.php +++ b/apps/files_trashbin/l10n/fa.php @@ -4,11 +4,11 @@ $TRANSLATIONS = array( "Couldn't restore %s" => "%s را نمی توان بازگرداند", "Deleted files" => "فایل های حذف شده", "Error" => "خطا", +"Deleted Files" => "فایلهای حذف شده", "Nothing in here. Your trash bin is empty!" => "هیچ چیزی اینجا نیست. سطل زباله ی شما خالی است.", "Name" => "نام", "Restore" => "بازیابی", "Deleted" => "حذف شده", -"Delete" => "حذف", -"Deleted Files" => "فایلهای حذف شده" +"Delete" => "حذف" ); $PLURAL_FORMS = "nplurals=1; plural=0;"; diff --git a/apps/files_trashbin/l10n/fi_FI.php b/apps/files_trashbin/l10n/fi_FI.php index e7b62dbb43..da56baf0bd 100644 --- a/apps/files_trashbin/l10n/fi_FI.php +++ b/apps/files_trashbin/l10n/fi_FI.php @@ -4,12 +4,12 @@ $TRANSLATIONS = array( "Couldn't restore %s" => "Kohteen %s palautus epäonnistui", "Deleted files" => "Poistetut tiedostot", "Error" => "Virhe", +"Deleted Files" => "Poistetut tiedostot", "restored" => "palautettu", "Nothing in here. Your trash bin is empty!" => "Tyhjää täynnä! Roskakorissa ei ole mitään.", "Name" => "Nimi", "Restore" => "Palauta", "Deleted" => "Poistettu", -"Delete" => "Poista", -"Deleted Files" => "Poistetut tiedostot" +"Delete" => "Poista" ); $PLURAL_FORMS = "nplurals=2; plural=(n != 1);"; diff --git a/apps/files_trashbin/l10n/fr.php b/apps/files_trashbin/l10n/fr.php index 9e74657fc1..b71fbea96a 100644 --- a/apps/files_trashbin/l10n/fr.php +++ b/apps/files_trashbin/l10n/fr.php @@ -4,12 +4,12 @@ $TRANSLATIONS = array( "Couldn't restore %s" => "Impossible de restaurer %s", "Deleted files" => "Fichiers supprimés", "Error" => "Erreur", +"Deleted Files" => "Fichiers effacés", "restored" => "restauré", "Nothing in here. Your trash bin is empty!" => "Il n'y a rien ici. Votre corbeille est vide !", "Name" => "Nom", "Restore" => "Restaurer", "Deleted" => "Effacé", -"Delete" => "Supprimer", -"Deleted Files" => "Fichiers effacés" +"Delete" => "Supprimer" ); $PLURAL_FORMS = "nplurals=2; plural=(n > 1);"; diff --git a/apps/files_trashbin/l10n/gl.php b/apps/files_trashbin/l10n/gl.php index bf26936be0..fe74ab34a0 100644 --- a/apps/files_trashbin/l10n/gl.php +++ b/apps/files_trashbin/l10n/gl.php @@ -4,12 +4,12 @@ $TRANSLATIONS = array( "Couldn't restore %s" => "Non foi posíbel restaurar %s", "Deleted files" => "Ficheiros eliminados", "Error" => "Erro", +"Deleted Files" => "Ficheiros eliminados", "restored" => "restaurado", "Nothing in here. Your trash bin is empty!" => "Aquí non hai nada. O cesto do lixo está baleiro!", "Name" => "Nome", "Restore" => "Restablecer", "Deleted" => "Eliminado", -"Delete" => "Eliminar", -"Deleted Files" => "Ficheiros eliminados" +"Delete" => "Eliminar" ); $PLURAL_FORMS = "nplurals=2; plural=(n != 1);"; diff --git a/apps/files_trashbin/l10n/he.php b/apps/files_trashbin/l10n/he.php index 6d2d184bec..6cdc5c05c9 100644 --- a/apps/files_trashbin/l10n/he.php +++ b/apps/files_trashbin/l10n/he.php @@ -4,12 +4,12 @@ $TRANSLATIONS = array( "Couldn't restore %s" => "לא ניתן לשחזר את %s", "Deleted files" => "קבצים שנמחקו", "Error" => "שגיאה", +"Deleted Files" => "קבצים שנמחקו", "restored" => "שוחזר", "Nothing in here. Your trash bin is empty!" => "אין כאן שום דבר. סל המיחזור שלך ריק!", "Name" => "שם", "Restore" => "שחזור", "Deleted" => "נמחק", -"Delete" => "מחיקה", -"Deleted Files" => "קבצים שנמחקו" +"Delete" => "מחיקה" ); $PLURAL_FORMS = "nplurals=2; plural=(n != 1);"; diff --git a/apps/files_trashbin/l10n/hu_HU.php b/apps/files_trashbin/l10n/hu_HU.php index 69a2f5d5d4..2912821d96 100644 --- a/apps/files_trashbin/l10n/hu_HU.php +++ b/apps/files_trashbin/l10n/hu_HU.php @@ -4,12 +4,12 @@ $TRANSLATIONS = array( "Couldn't restore %s" => "Nem sikerült %s visszaállítása", "Deleted files" => "Törölt fájlok", "Error" => "Hiba", +"Deleted Files" => "Törölt fájlok", "restored" => "visszaállítva", "Nothing in here. Your trash bin is empty!" => "Itt nincs semmi. Az Ön szemetes mappája üres!", "Name" => "Név", "Restore" => "Visszaállítás", "Deleted" => "Törölve", -"Delete" => "Törlés", -"Deleted Files" => "Törölt fájlok" +"Delete" => "Törlés" ); $PLURAL_FORMS = "nplurals=2; plural=(n != 1);"; diff --git a/apps/files_trashbin/l10n/id.php b/apps/files_trashbin/l10n/id.php index 11e1675074..166b9aa811 100644 --- a/apps/files_trashbin/l10n/id.php +++ b/apps/files_trashbin/l10n/id.php @@ -4,11 +4,11 @@ $TRANSLATIONS = array( "Couldn't restore %s" => "Tidak dapat memulihkan %s", "Deleted files" => "Berkas yang dihapus", "Error" => "Galat", +"Deleted Files" => "Berkas yang Dihapus", "Nothing in here. Your trash bin is empty!" => "Tempat sampah anda kosong!", "Name" => "Nama", "Restore" => "Pulihkan", "Deleted" => "Dihapus", -"Delete" => "Hapus", -"Deleted Files" => "Berkas yang Dihapus" +"Delete" => "Hapus" ); $PLURAL_FORMS = "nplurals=1; plural=0;"; diff --git a/apps/files_trashbin/l10n/it.php b/apps/files_trashbin/l10n/it.php index 739dda2456..057305ac51 100644 --- a/apps/files_trashbin/l10n/it.php +++ b/apps/files_trashbin/l10n/it.php @@ -4,12 +4,12 @@ $TRANSLATIONS = array( "Couldn't restore %s" => "Impossibile ripristinare %s", "Deleted files" => "File eliminati", "Error" => "Errore", +"Deleted Files" => "File eliminati", "restored" => "ripristinati", "Nothing in here. Your trash bin is empty!" => "Qui non c'è niente. Il tuo cestino è vuoto.", "Name" => "Nome", "Restore" => "Ripristina", "Deleted" => "Eliminati", -"Delete" => "Elimina", -"Deleted Files" => "File eliminati" +"Delete" => "Elimina" ); $PLURAL_FORMS = "nplurals=2; plural=(n != 1);"; diff --git a/apps/files_trashbin/l10n/ja.php b/apps/files_trashbin/l10n/ja.php index 0aab7d0575..13ca95e6fb 100644 --- a/apps/files_trashbin/l10n/ja.php +++ b/apps/files_trashbin/l10n/ja.php @@ -4,12 +4,12 @@ $TRANSLATIONS = array( "Couldn't restore %s" => "%s を復元できませんでした", "Deleted files" => "ゴミ箱", "Error" => "エラー", +"Deleted Files" => "ゴミ箱", "restored" => "復元済", "Nothing in here. Your trash bin is empty!" => "ここには何もありません。ゴミ箱は空です!", "Name" => "名前", "Restore" => "復元", "Deleted" => "削除済み", -"Delete" => "削除", -"Deleted Files" => "ゴミ箱" +"Delete" => "削除" ); $PLURAL_FORMS = "nplurals=1; plural=0;"; diff --git a/apps/files_trashbin/l10n/ka_GE.php b/apps/files_trashbin/l10n/ka_GE.php index f36bbea59b..4e3ad4260e 100644 --- a/apps/files_trashbin/l10n/ka_GE.php +++ b/apps/files_trashbin/l10n/ka_GE.php @@ -4,11 +4,11 @@ $TRANSLATIONS = array( "Couldn't restore %s" => "%s–ის აღდგენა ვერ მოხერხდა", "Deleted files" => "წაშლილი ფაილები", "Error" => "შეცდომა", +"Deleted Files" => "წაშლილი ფაილები", "Nothing in here. Your trash bin is empty!" => "აქ არაფერი არ არის. სანაგვე ყუთი ცარიელია!", "Name" => "სახელი", "Restore" => "აღდგენა", "Deleted" => "წაშლილი", -"Delete" => "წაშლა", -"Deleted Files" => "წაშლილი ფაილები" +"Delete" => "წაშლა" ); $PLURAL_FORMS = "nplurals=1; plural=0;"; diff --git a/apps/files_trashbin/l10n/ko.php b/apps/files_trashbin/l10n/ko.php index f95d143245..d9d8707080 100644 --- a/apps/files_trashbin/l10n/ko.php +++ b/apps/files_trashbin/l10n/ko.php @@ -4,12 +4,12 @@ $TRANSLATIONS = array( "Couldn't restore %s" => "%s을(를) 복원할 수 없습니다", "Deleted files" => "삭제된 파일", "Error" => "오류", +"Deleted Files" => "삭제된 파일", "restored" => "복원됨", "Nothing in here. Your trash bin is empty!" => "휴지통이 비어 있습니다!", "Name" => "이름", "Restore" => "복원", "Deleted" => "삭제됨", -"Delete" => "삭제", -"Deleted Files" => "삭제된 파일" +"Delete" => "삭제" ); $PLURAL_FORMS = "nplurals=1; plural=0;"; diff --git a/apps/files_trashbin/l10n/lt_LT.php b/apps/files_trashbin/l10n/lt_LT.php index 07325665d7..2bf545483f 100644 --- a/apps/files_trashbin/l10n/lt_LT.php +++ b/apps/files_trashbin/l10n/lt_LT.php @@ -4,12 +4,12 @@ $TRANSLATIONS = array( "Couldn't restore %s" => "Nepavyko atkurti %s", "Deleted files" => "Ištrinti failai", "Error" => "Klaida", +"Deleted Files" => "Ištrinti failai", "restored" => "atstatyta", "Nothing in here. Your trash bin is empty!" => "Nieko nėra. Jūsų šiukšliadėžė tuščia!", "Name" => "Pavadinimas", "Restore" => "Atstatyti", "Deleted" => "Ištrinti", -"Delete" => "Ištrinti", -"Deleted Files" => "Ištrinti failai" +"Delete" => "Ištrinti" ); $PLURAL_FORMS = "nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && (n%100<10 || n%100>=20) ? 1 : 2);"; diff --git a/apps/files_trashbin/l10n/lv.php b/apps/files_trashbin/l10n/lv.php index d356b34437..c173d05014 100644 --- a/apps/files_trashbin/l10n/lv.php +++ b/apps/files_trashbin/l10n/lv.php @@ -4,12 +4,12 @@ $TRANSLATIONS = array( "Couldn't restore %s" => "Nevarēja atjaunot %s", "Deleted files" => "Dzēstās datnes", "Error" => "Kļūda", +"Deleted Files" => "Dzēstās datnes", "restored" => "atjaunots", "Nothing in here. Your trash bin is empty!" => "Šeit nekā nav. Jūsu miskaste ir tukša!", "Name" => "Nosaukums", "Restore" => "Atjaunot", "Deleted" => "Dzēsts", -"Delete" => "Dzēst", -"Deleted Files" => "Dzēstās datnes" +"Delete" => "Dzēst" ); $PLURAL_FORMS = "nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n != 0 ? 1 : 2);"; diff --git a/apps/files_trashbin/l10n/mk.php b/apps/files_trashbin/l10n/mk.php index f025a13db1..910b11e21e 100644 --- a/apps/files_trashbin/l10n/mk.php +++ b/apps/files_trashbin/l10n/mk.php @@ -4,12 +4,12 @@ $TRANSLATIONS = array( "Couldn't restore %s" => "Не можеше да се поврати %s", "Deleted files" => "Избришани датотеки", "Error" => "Грешка", +"Deleted Files" => "Избришани датотеки", "restored" => "повратени", "Nothing in here. Your trash bin is empty!" => "Тука нема ништо. Вашата корпа за отпадоци е празна!", "Name" => "Име", "Restore" => "Поврати", "Deleted" => "Избришан", -"Delete" => "Избриши", -"Deleted Files" => "Избришани датотеки" +"Delete" => "Избриши" ); $PLURAL_FORMS = "nplurals=2; plural=(n % 10 == 1 && n % 100 != 11) ? 0 : 1;"; diff --git a/apps/files_trashbin/l10n/ms_MY.php b/apps/files_trashbin/l10n/ms_MY.php index e772131391..f084f58465 100644 --- a/apps/files_trashbin/l10n/ms_MY.php +++ b/apps/files_trashbin/l10n/ms_MY.php @@ -3,12 +3,12 @@ $TRANSLATIONS = array( "Couldn't delete %s permanently" => "Tidak dapat menghapuskan %s secara kekal", "Couldn't restore %s" => "Tidak dapat memulihkan %s", "Error" => "Ralat", +"Deleted Files" => "Fail Dihapus", "restored" => "dipulihkan", "Nothing in here. Your trash bin is empty!" => "Tiada apa disini. Tong sampah anda kosong!", "Name" => "Nama", "Restore" => "Pulihkan", "Deleted" => "Dihapuskan", -"Delete" => "Padam", -"Deleted Files" => "Fail Dihapus" +"Delete" => "Padam" ); $PLURAL_FORMS = "nplurals=1; plural=0;"; diff --git a/apps/files_trashbin/l10n/nb_NO.php b/apps/files_trashbin/l10n/nb_NO.php index 66e61ee72a..2293e5a4e7 100644 --- a/apps/files_trashbin/l10n/nb_NO.php +++ b/apps/files_trashbin/l10n/nb_NO.php @@ -4,12 +4,12 @@ $TRANSLATIONS = array( "Couldn't restore %s" => "Kunne ikke gjenopprette %s", "Deleted files" => "Slettede filer", "Error" => "Feil", +"Deleted Files" => "Slettede filer", "restored" => "gjenopprettet", "Nothing in here. Your trash bin is empty!" => "Ingenting her. Søppelkassen din er tom!", "Name" => "Navn", "Restore" => "Gjenopprett", "Deleted" => "Slettet", -"Delete" => "Slett", -"Deleted Files" => "Slettede filer" +"Delete" => "Slett" ); $PLURAL_FORMS = "nplurals=2; plural=(n != 1);"; diff --git a/apps/files_trashbin/l10n/nl.php b/apps/files_trashbin/l10n/nl.php index 533571fd9e..c8fb128853 100644 --- a/apps/files_trashbin/l10n/nl.php +++ b/apps/files_trashbin/l10n/nl.php @@ -4,12 +4,12 @@ $TRANSLATIONS = array( "Couldn't restore %s" => "Kon %s niet herstellen", "Deleted files" => "Verwijderde bestanden", "Error" => "Fout", +"Deleted Files" => "Verwijderde bestanden", "restored" => "hersteld", "Nothing in here. Your trash bin is empty!" => "Niets te vinden. Uw prullenbak is leeg!", "Name" => "Naam", "Restore" => "Herstellen", "Deleted" => "Verwijderd", -"Delete" => "Verwijder", -"Deleted Files" => "Verwijderde bestanden" +"Delete" => "Verwijder" ); $PLURAL_FORMS = "nplurals=2; plural=(n != 1);"; diff --git a/apps/files_trashbin/l10n/nn_NO.php b/apps/files_trashbin/l10n/nn_NO.php index 9282d9d49d..38bc64e6ce 100644 --- a/apps/files_trashbin/l10n/nn_NO.php +++ b/apps/files_trashbin/l10n/nn_NO.php @@ -4,12 +4,12 @@ $TRANSLATIONS = array( "Couldn't restore %s" => "Klarte ikkje gjenoppretta %s", "Deleted files" => "Sletta filer", "Error" => "Feil", +"Deleted Files" => "Sletta filer", "restored" => "gjenoppretta", "Nothing in here. Your trash bin is empty!" => "Ingenting her. Papirkorga di er tom!", "Name" => "Namn", "Restore" => "Gjenopprett", "Deleted" => "Sletta", -"Delete" => "Slett", -"Deleted Files" => "Sletta filer" +"Delete" => "Slett" ); $PLURAL_FORMS = "nplurals=2; plural=(n != 1);"; diff --git a/apps/files_trashbin/l10n/pl.php b/apps/files_trashbin/l10n/pl.php index 5075dad271..b961efd7da 100644 --- a/apps/files_trashbin/l10n/pl.php +++ b/apps/files_trashbin/l10n/pl.php @@ -4,12 +4,12 @@ $TRANSLATIONS = array( "Couldn't restore %s" => "Nie można przywrócić %s", "Deleted files" => "Pliki usunięte", "Error" => "Błąd", +"Deleted Files" => "Usunięte pliki", "restored" => "przywrócony", "Nothing in here. Your trash bin is empty!" => "Nic tu nie ma. Twój kosz jest pusty!", "Name" => "Nazwa", "Restore" => "Przywróć", "Deleted" => "Usunięte", -"Delete" => "Usuń", -"Deleted Files" => "Usunięte pliki" +"Delete" => "Usuń" ); $PLURAL_FORMS = "nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);"; diff --git a/apps/files_trashbin/l10n/pt_BR.php b/apps/files_trashbin/l10n/pt_BR.php index 300235452b..d524d8879e 100644 --- a/apps/files_trashbin/l10n/pt_BR.php +++ b/apps/files_trashbin/l10n/pt_BR.php @@ -4,12 +4,12 @@ $TRANSLATIONS = array( "Couldn't restore %s" => "Não foi possível restaurar %s", "Deleted files" => "Arquivos apagados", "Error" => "Erro", +"Deleted Files" => "Arquivos Apagados", "restored" => "restaurado", "Nothing in here. Your trash bin is empty!" => "Nada aqui. Sua lixeira está vazia!", "Name" => "Nome", "Restore" => "Restaurar", "Deleted" => "Excluído", -"Delete" => "Excluir", -"Deleted Files" => "Arquivos Apagados" +"Delete" => "Excluir" ); $PLURAL_FORMS = "nplurals=2; plural=(n > 1);"; diff --git a/apps/files_trashbin/l10n/pt_PT.php b/apps/files_trashbin/l10n/pt_PT.php index 69f83e8a0e..94dd0eb707 100644 --- a/apps/files_trashbin/l10n/pt_PT.php +++ b/apps/files_trashbin/l10n/pt_PT.php @@ -4,12 +4,12 @@ $TRANSLATIONS = array( "Couldn't restore %s" => "Não foi possível restaurar %s", "Deleted files" => "Ficheiros eliminados", "Error" => "Erro", +"Deleted Files" => "Ficheiros Apagados", "restored" => "Restaurado", "Nothing in here. Your trash bin is empty!" => "Não hà ficheiros. O lixo está vazio!", "Name" => "Nome", "Restore" => "Restaurar", "Deleted" => "Apagado", -"Delete" => "Eliminar", -"Deleted Files" => "Ficheiros Apagados" +"Delete" => "Eliminar" ); $PLURAL_FORMS = "nplurals=2; plural=(n != 1);"; diff --git a/apps/files_trashbin/l10n/ru.php b/apps/files_trashbin/l10n/ru.php index 3210ad7290..d10369b9ca 100644 --- a/apps/files_trashbin/l10n/ru.php +++ b/apps/files_trashbin/l10n/ru.php @@ -4,12 +4,12 @@ $TRANSLATIONS = array( "Couldn't restore %s" => "%s не может быть восстановлен", "Deleted files" => "Удалённые файлы", "Error" => "Ошибка", +"Deleted Files" => "Удаленные файлы", "restored" => "восстановлен", "Nothing in here. Your trash bin is empty!" => "Здесь ничего нет. Ваша корзина пуста!", "Name" => "Имя", "Restore" => "Восстановить", "Deleted" => "Удалён", -"Delete" => "Удалить", -"Deleted Files" => "Удаленные файлы" +"Delete" => "Удалить" ); $PLURAL_FORMS = "nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);"; diff --git a/apps/files_trashbin/l10n/sk_SK.php b/apps/files_trashbin/l10n/sk_SK.php index 6bb39bb89d..3badd3a423 100644 --- a/apps/files_trashbin/l10n/sk_SK.php +++ b/apps/files_trashbin/l10n/sk_SK.php @@ -4,12 +4,12 @@ $TRANSLATIONS = array( "Couldn't restore %s" => "Nemožno obnoviť %s", "Deleted files" => "Zmazané súbory", "Error" => "Chyba", +"Deleted Files" => "Zmazané súbory", "restored" => "obnovené", "Nothing in here. Your trash bin is empty!" => "Žiadny obsah. Kôš je prázdny!", "Name" => "Názov", "Restore" => "Obnoviť", "Deleted" => "Zmazané", -"Delete" => "Zmazať", -"Deleted Files" => "Zmazané súbory" +"Delete" => "Zmazať" ); $PLURAL_FORMS = "nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2;"; diff --git a/apps/files_trashbin/l10n/sl.php b/apps/files_trashbin/l10n/sl.php index ca69c8d278..08da9b1c6e 100644 --- a/apps/files_trashbin/l10n/sl.php +++ b/apps/files_trashbin/l10n/sl.php @@ -4,12 +4,12 @@ $TRANSLATIONS = array( "Couldn't restore %s" => "Ni mogoče obnoviti %s", "Deleted files" => "Izbrisane datoteke", "Error" => "Napaka", +"Deleted Files" => "Izbrisane datoteke", "restored" => "obnovljeno", "Nothing in here. Your trash bin is empty!" => "Mapa smeti je prazna.", "Name" => "Ime", "Restore" => "Obnovi", "Deleted" => "Izbrisano", -"Delete" => "Izbriši", -"Deleted Files" => "Izbrisane datoteke" +"Delete" => "Izbriši" ); $PLURAL_FORMS = "nplurals=4; plural=(n%100==1 ? 0 : n%100==2 ? 1 : n%100==3 || n%100==4 ? 2 : 3);"; diff --git a/apps/files_trashbin/l10n/sq.php b/apps/files_trashbin/l10n/sq.php index 10354e4540..60d16f9b91 100644 --- a/apps/files_trashbin/l10n/sq.php +++ b/apps/files_trashbin/l10n/sq.php @@ -4,12 +4,12 @@ $TRANSLATIONS = array( "Couldn't restore %s" => "Nuk munda ta rivendos %s", "Deleted files" => "Skedarë të fshirë ", "Error" => "Veprim i gabuar", +"Deleted Files" => "Skedarë të eliminuar", "restored" => "rivendosur", "Nothing in here. Your trash bin is empty!" => "Këtu nuk ka asgjë. Koshi juaj është bosh!", "Name" => "Emri", "Restore" => "Rivendos", "Deleted" => "Eliminuar", -"Delete" => "Elimino", -"Deleted Files" => "Skedarë të eliminuar" +"Delete" => "Elimino" ); $PLURAL_FORMS = "nplurals=2; plural=(n != 1);"; diff --git a/apps/files_trashbin/l10n/sv.php b/apps/files_trashbin/l10n/sv.php index ec9de3acaa..fd9ca8653f 100644 --- a/apps/files_trashbin/l10n/sv.php +++ b/apps/files_trashbin/l10n/sv.php @@ -4,12 +4,12 @@ $TRANSLATIONS = array( "Couldn't restore %s" => "Kunde inte återställa %s", "Deleted files" => "Raderade filer", "Error" => "Fel", +"Deleted Files" => "Raderade filer", "restored" => "återställd", "Nothing in here. Your trash bin is empty!" => "Ingenting här. Din papperskorg är tom!", "Name" => "Namn", "Restore" => "Återskapa", "Deleted" => "Raderad", -"Delete" => "Radera", -"Deleted Files" => "Raderade filer" +"Delete" => "Radera" ); $PLURAL_FORMS = "nplurals=2; plural=(n != 1);"; diff --git a/apps/files_trashbin/l10n/th_TH.php b/apps/files_trashbin/l10n/th_TH.php index 65fd081a95..857737c59e 100644 --- a/apps/files_trashbin/l10n/th_TH.php +++ b/apps/files_trashbin/l10n/th_TH.php @@ -1,11 +1,11 @@ "ข้อผิดพลาด", +"Deleted Files" => "ไฟล์ที่ลบทิ้ง", "Nothing in here. Your trash bin is empty!" => "ไม่มีอะไรอยู่ในนี้ ถังขยะของคุณยังว่างอยู่", "Name" => "ชื่อ", "Restore" => "คืนค่า", "Deleted" => "ลบแล้ว", -"Delete" => "ลบ", -"Deleted Files" => "ไฟล์ที่ลบทิ้ง" +"Delete" => "ลบ" ); $PLURAL_FORMS = "nplurals=1; plural=0;"; diff --git a/apps/files_trashbin/l10n/uk.php b/apps/files_trashbin/l10n/uk.php index 19d4bda97c..fa523fa321 100644 --- a/apps/files_trashbin/l10n/uk.php +++ b/apps/files_trashbin/l10n/uk.php @@ -4,12 +4,12 @@ $TRANSLATIONS = array( "Couldn't restore %s" => "Неможливо відновити %s", "Deleted files" => "Видалено файлів", "Error" => "Помилка", +"Deleted Files" => "Видалено Файлів", "restored" => "відновлено", "Nothing in here. Your trash bin is empty!" => "Нічого немає. Ваший кошик для сміття пустий!", "Name" => "Ім'я", "Restore" => "Відновити", "Deleted" => "Видалено", -"Delete" => "Видалити", -"Deleted Files" => "Видалено Файлів" +"Delete" => "Видалити" ); $PLURAL_FORMS = "nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);"; diff --git a/apps/files_trashbin/l10n/vi.php b/apps/files_trashbin/l10n/vi.php index 81a0cf14bc..57c82cea5f 100644 --- a/apps/files_trashbin/l10n/vi.php +++ b/apps/files_trashbin/l10n/vi.php @@ -4,12 +4,12 @@ $TRANSLATIONS = array( "Couldn't restore %s" => "Không thể khôi phục %s", "Deleted files" => "File đã bị xóa", "Error" => "Lỗi", +"Deleted Files" => "File đã xóa", "restored" => "khôi phục", "Nothing in here. Your trash bin is empty!" => "Không có gì ở đây. Thùng rác của bạn rỗng!", "Name" => "Tên", "Restore" => "Khôi phục", "Deleted" => "Đã xóa", -"Delete" => "Xóa", -"Deleted Files" => "File đã xóa" +"Delete" => "Xóa" ); $PLURAL_FORMS = "nplurals=1; plural=0;"; diff --git a/apps/files_trashbin/l10n/zh_CN.php b/apps/files_trashbin/l10n/zh_CN.php index 4c61aef33b..ef6a63b295 100644 --- a/apps/files_trashbin/l10n/zh_CN.php +++ b/apps/files_trashbin/l10n/zh_CN.php @@ -4,12 +4,12 @@ $TRANSLATIONS = array( "Couldn't restore %s" => "无法恢复%s", "Deleted files" => "已删除文件", "Error" => "错误", +"Deleted Files" => "已删除文件", "restored" => "已恢复", "Nothing in here. Your trash bin is empty!" => "这里没有东西. 你的回收站是空的!", "Name" => "名称", "Restore" => "恢复", "Deleted" => "已删除", -"Delete" => "删除", -"Deleted Files" => "已删除文件" +"Delete" => "删除" ); $PLURAL_FORMS = "nplurals=1; plural=0;"; diff --git a/apps/files_trashbin/l10n/zh_TW.php b/apps/files_trashbin/l10n/zh_TW.php index 65b954e2c6..c42d70790e 100644 --- a/apps/files_trashbin/l10n/zh_TW.php +++ b/apps/files_trashbin/l10n/zh_TW.php @@ -4,12 +4,12 @@ $TRANSLATIONS = array( "Couldn't restore %s" => "無法還原 %s", "Deleted files" => "回收桶", "Error" => "錯誤", +"Deleted Files" => "已刪除的檔案", "restored" => "已還原", "Nothing in here. Your trash bin is empty!" => "您的回收桶是空的!", "Name" => "名稱", "Restore" => "還原", "Deleted" => "已刪除", -"Delete" => "刪除", -"Deleted Files" => "已刪除的檔案" +"Delete" => "刪除" ); $PLURAL_FORMS = "nplurals=1; plural=0;"; diff --git a/apps/files_trashbin/lib/trashbin.php b/apps/files_trashbin/lib/trashbin.php index 7b14a4ec08..9b931333b7 100644 --- a/apps/files_trashbin/lib/trashbin.php +++ b/apps/files_trashbin/lib/trashbin.php @@ -824,13 +824,15 @@ class Trashbin { $matches = glob($escapedVersionsName . '*'); } - foreach ($matches as $ma) { - if ($timestamp) { - $parts = explode('.v', substr($ma, 0, $offset)); - $versions[] = (end($parts)); - } else { - $parts = explode('.v', $ma); - $versions[] = (end($parts)); + if (is_array($matches)) { + foreach ($matches as $ma) { + if ($timestamp) { + $parts = explode('.v', substr($ma, 0, $offset)); + $versions[] = (end($parts)); + } else { + $parts = explode('.v', $ma); + $versions[] = (end($parts)); + } } } return $versions; @@ -921,13 +923,11 @@ class Trashbin { public static function isEmpty($user) { $view = new \OC\Files\View('/' . $user . '/files_trashbin'); - $dh = $view->opendir('/files'); - if (!$dh) { - return false; - } - while ($file = readdir($dh)) { - if ($file !== '.' and $file !== '..') { - return false; + if ($view->is_dir('/files') && $dh = $view->opendir('/files')) { + while ($file = readdir($dh)) { + if ($file !== '.' and $file !== '..') { + return false; + } } } return true; diff --git a/apps/files_versions/l10n/ast.php b/apps/files_versions/l10n/ast.php index 4869d70b54..31e6e72b2d 100644 --- a/apps/files_versions/l10n/ast.php +++ b/apps/files_versions/l10n/ast.php @@ -1,6 +1,8 @@ "Nun pudo revertise: %s", "Versions" => "Versiones", +"Failed to revert {file} to revision {timestamp}." => "Fallu al revertir {file} a la revisión {timestamp}.", "More versions..." => "Más versiones...", "No other versions available" => "Nun hai otres versiones disponibles", "Restore" => "Restaurar" diff --git a/apps/user_ldap/group_ldap.php b/apps/user_ldap/group_ldap.php index 40d9dec141..3b5f377e10 100644 --- a/apps/user_ldap/group_ldap.php +++ b/apps/user_ldap/group_ldap.php @@ -88,6 +88,10 @@ class GROUP_LDAP extends BackendUtility implements \OCP\GroupInterface { return $isInGroup; } + /** + * @param string $dnGroup + * @param array|null &$seen + */ private function _groupMembers($dnGroup, &$seen = null) { if ($seen === null) { $seen = array(); @@ -163,6 +167,10 @@ class GROUP_LDAP extends BackendUtility implements \OCP\GroupInterface { return $groups; } + /** + * @param string $dn + * @param array|null &$seen + */ private function getGroupsByMember($dn, &$seen = null) { if ($seen === null) { $seen = array(); @@ -454,6 +462,9 @@ class GROUP_LDAP extends BackendUtility implements \OCP\GroupInterface { return $allGroups; } + /** + * @param string $group + */ public function groupMatchesFilter($group) { return (strripos($group, $this->groupSearch) !== false); } diff --git a/apps/user_ldap/js/ldapFilter.js b/apps/user_ldap/js/ldapFilter.js new file mode 100644 index 0000000000..df3bd67aec --- /dev/null +++ b/apps/user_ldap/js/ldapFilter.js @@ -0,0 +1,100 @@ +/* global LdapWizard */ + +function LdapFilter(target) { + this.locked = true; + this.target = false; + this.mode = LdapWizard.filterModeAssisted; + this.lazyRunCompose = false; + + if( target === 'User' || + target === 'Login' || + target === 'Group') { + this.target = target; + this.determineMode(); + } +} + +LdapFilter.prototype.compose = function() { + var action; + + if(this.locked) { + this.lazyRunCompose = true; + return false; + } + + if(this.target === 'User') { + action = 'getUserListFilter'; + } else if(this.target === 'Login') { + action = 'getUserLoginFilter'; + } else if(this.target === 'Group') { + action = 'getGroupFilter'; + } + + if(!$('#raw'+this.target+'FilterContainer').hasClass('invisible')) { + //Raw filter editing, i.e. user defined filter, don't compose + return; + } + + var param = 'action='+action+ + '&ldap_serverconfig_chooser='+ + encodeURIComponent($('#ldap_serverconfig_chooser').val()); + + var filter = this; + + LdapWizard.ajax(param, + function(result) { + LdapWizard.applyChanges(result); + if(filter.target === 'User') { + LdapWizard.countUsers(); + } else if(filter.target === 'Group') { + LdapWizard.countGroups(); + LdapWizard.detectGroupMemberAssoc(); + } + }, + function () { + console.log('LDAP Wizard: could not compose filter. '+ + 'Please check owncloud.log'); + } + ); +}; + +LdapFilter.prototype.determineMode = function() { + var param = 'action=get'+encodeURIComponent(this.target)+'FilterMode'+ + '&ldap_serverconfig_chooser='+ + encodeURIComponent($('#ldap_serverconfig_chooser').val()); + + var filter = this; + LdapWizard.ajax(param, + function(result) { + var property = 'ldap' + filter.target + 'FilterMode'; + filter.mode = parseInt(result.changes[property], 10); + if(filter.mode === LdapWizard.filterModeRaw && + $('#raw'+filter.target+'FilterContainer').hasClass('invisible')) { + LdapWizard['toggleRaw'+filter.target+'Filter'](); + } else if(filter.mode === LdapWizard.filterModeAssisted && + !$('#raw'+filter.target+'FilterContainer').hasClass('invisible')) { + LdapWizard['toggleRaw'+filter.target+'Filter'](); + } else { + console.log('LDAP Wizard determineMode: returned mode was »' + + filter.mode + '« of type ' + typeof filter.mode); + } + filter.unlock(); + }, + function () { + //on error case get back to default i.e. Assisted + if(!$('#raw'+filter.target+'FilterContainer').hasClass('invisible')) { + LdapWizard['toggleRaw'+filter.target+'Filter'](); + filter.mode = LdapWizard.filterModeAssisted; + } + filter.unlock(); + } + ); +}; + +LdapFilter.prototype.unlock = function() { + this.locked = false; + if(this.lazyRunCompose) { + this.lazyRunCompose = false; + this.compose(); + } +}; diff --git a/apps/user_ldap/js/settings.js b/apps/user_ldap/js/settings.js index 792638f2b5..fca2dc13d1 100644 --- a/apps/user_ldap/js/settings.js +++ b/apps/user_ldap/js/settings.js @@ -14,7 +14,7 @@ var LdapConfiguration = { //deal with Checkboxes if($(elementID).is('input[type=checkbox]')) { - if(parseInt(configvalue) === 1) { + if(parseInt(configvalue, 10) === 1) { $(elementID).attr('checked', 'checked'); } else { $(elementID).removeAttr('checked'); @@ -145,6 +145,9 @@ var LdapWizard = { spinner: '', filterModeAssisted: 0, filterModeRaw: 1, + userFilter: false, + loginFilter: false, + groupFilter: false, ajax: function(param, fnOnSuccess, fnOnError) { $.post( @@ -276,41 +279,6 @@ var LdapWizard = { } }, - composeFilter: function(type) { - subject = type.charAt(0).toUpperCase() + type.substr(1); - if(!$('#raw'+subject+'FilterContainer').hasClass('invisible')) { - //Raw filter editing, i.e. user defined filter, don't compose - return; - } - - if(type == 'user') { - action = 'getUserListFilter'; - } else if(type == 'login') { - action = 'getUserLoginFilter'; - } else if(type == 'group') { - action = 'getGroupFilter'; - } - - param = 'action='+action+ - '&ldap_serverconfig_chooser='+ - encodeURIComponent($('#ldap_serverconfig_chooser').val()); - - LdapWizard.ajax(param, - function(result) { - LdapWizard.applyChanges(result); - if(type == 'user') { - LdapWizard.countUsers(); - } else if(type == 'group') { - LdapWizard.countGroups(); - LdapWizard.detectGroupMemberAssoc(); - } - }, - function (result) { - // error handling - } - ); - }, - controlBack: function() { curTabIndex = $('#ldapSettings').tabs('option', 'active'); if(curTabIndex == 0) { @@ -510,7 +478,7 @@ var LdapWizard = { }, functionalityCheck: function() { - //criterias to enable the connection: + //criteria to enable the connection: // - host, port, basedn, user filter, login filter host = $('#ldap_host').val(); port = $('#ldap_port').val(); @@ -560,7 +528,7 @@ var LdapWizard = { }, initGroupFilter: function() { - LdapWizard.regardFilterMode('Group'); + LdapWizard.groupFilter = new LdapFilter('Group'); LdapWizard.findObjectClasses('ldap_groupfilter_objectclass', 'Group'); LdapWizard.findAvailableGroups('ldap_groupfilter_groups', 'Groups'); LdapWizard.countGroups(); @@ -569,13 +537,13 @@ var LdapWizard = { /** init login filter tab section **/ initLoginFilter: function() { - LdapWizard.regardFilterMode('Login'); + LdapWizard.loginFilter = new LdapFilter('Login'); LdapWizard.findAttributes(); }, postInitLoginFilter: function() { if($('#rawLoginFilterContainer').hasClass('invisible')) { - LdapWizard.composeFilter('login'); + LdapWizard.loginFilter.compose(); } }, @@ -588,7 +556,7 @@ var LdapWizard = { noneSelectedText: caption, click: function(event, ui) { LdapWizard.saveMultiSelect(id, - $('#'+id).multiselect("getChecked")); + $('#'+id).multiselect("getChecked")); } }); }, @@ -601,15 +569,15 @@ var LdapWizard = { initUserFilter: function() { LdapWizard.userFilterObjectClassesHasRun = false; LdapWizard.userFilterAvailableGroupsHasRun = false; - LdapWizard.regardFilterMode('User'); + LdapWizard.userFilter = new LdapFilter('User'); LdapWizard.findObjectClasses('ldap_userfilter_objectclass', 'User'); LdapWizard.findAvailableGroups('ldap_userfilter_groups', 'Users'); }, postInitUserFilter: function() { - if(LdapWizard.userFilterObjectClassesHasRun - && LdapWizard.userFilterAvailableGroupsHasRun) { - LdapWizard.composeFilter('user'); + if(LdapWizard.userFilterObjectClassesHasRun && + LdapWizard.userFilterAvailableGroupsHasRun) { + LdapWizard.userFilter.compose(); LdapWizard.countUsers(); } }, @@ -658,7 +626,7 @@ var LdapWizard = { if(triggerObj.id == 'ldap_loginfilter_username' || triggerObj.id == 'ldap_loginfilter_email') { - LdapWizard.composeFilter('login'); + LdapWizard.loginFilter.compose(); } if($('#ldapSettings').tabs('option', 'active') == 0) { @@ -667,32 +635,6 @@ var LdapWizard = { } }, - regardFilterMode: function(subject) { - param = 'action=get'+encodeURIComponent(subject)+'FilterMode'+ - '&ldap_serverconfig_chooser='+ - encodeURIComponent($('#ldap_serverconfig_chooser').val()); - - LdapWizard.ajax(param, - function(result) { - property = 'ldap' + subject + 'FilterMode'; - mode = result.changes[property]; - if(mode == LdapWizard.filterModeRaw - && $('#raw'+subject+'FilterContainer').hasClass('invisible')) { - LdapWizard['toggleRaw'+subject+'Filter'](); - } else if(mode == LdapWizard.filterModeAssisted - && !$('#raw'+subject+'FilterContainer').hasClass('invisible')) { - LdapWizard['toggleRaw'+subject+'Filter'](); - } - }, - function (result) { - //on error case get back to default i.e. Assisted - if(!$('#raw'+subject+'FilterContainer').hasClass('invisible')) { - LdapWizard['toggleRaw'+subject+'Filter'](); - } - } - ); - }, - save: function(inputObj) { if(LdapWizard.blacklistRemove(inputObj.id)) { return; @@ -714,15 +656,15 @@ var LdapWizard = { LdapWizard._save($('#'+originalObj)[0], $.trim(values)); if(originalObj == 'ldap_userfilter_objectclass' || originalObj == 'ldap_userfilter_groups') { - LdapWizard.composeFilter('user'); + LdapWizard.userFilter.compose(); //when user filter is changed afterwards, login filter needs to //be adjusted, too - LdapWizard.composeFilter('login'); + LdapWizard.loginFilter.compose(); } else if(originalObj == 'ldap_loginfilter_attributes') { - LdapWizard.composeFilter('login'); + LdapWizard.loginFilter.compose(); } else if(originalObj == 'ldap_groupfilter_objectclass' || originalObj == 'ldap_groupfilter_groups') { - LdapWizard.composeFilter('group'); + LdapWizard.groupFilter.compose(); } }, @@ -778,10 +720,10 @@ var LdapWizard = { LdapWizard._save({ id: modeKey }, LdapWizard.filterModeAssisted); if(moc.indexOf('user') >= 0) { LdapWizard.blacklistRemove('ldap_userlist_filter'); - LdapWizard.composeFilter('user'); + LdapWizard.userFilter.compose(); } else { LdapWizard.blacklistRemove('ldap_group_filter'); - LdapWizard.composeFilter('group'); + LdapWizard.groupFilter.compose(); } } }, @@ -815,7 +757,7 @@ var LdapWizard = { $('#ldap_loginfilter_username').prop('disabled', property); LdapWizard._save({ id: 'ldapLoginFilterMode' }, mode); if(action == 'enable') { - LdapWizard.composeFilter('login'); + LdapWizard.loginFilter.compose(); } }, diff --git a/apps/user_ldap/l10n/af_ZA.php b/apps/user_ldap/l10n/af_ZA.php index 130e471e0e..483a30b8e4 100644 --- a/apps/user_ldap/l10n/af_ZA.php +++ b/apps/user_ldap/l10n/af_ZA.php @@ -3,6 +3,7 @@ $TRANSLATIONS = array( "_%s group found_::_%s groups found_" => array("",""), "_%s user found_::_%s users found_" => array("",""), "Help" => "Hulp", -"Password" => "Wagwoord" +"Password" => "Wagwoord", +"Continue" => "Gaan voort" ); $PLURAL_FORMS = "nplurals=2; plural=(n != 1);"; diff --git a/apps/user_ldap/l10n/ast.php b/apps/user_ldap/l10n/ast.php index 2c02288001..032de8aedb 100644 --- a/apps/user_ldap/l10n/ast.php +++ b/apps/user_ldap/l10n/ast.php @@ -1,11 +1,41 @@ "La configuración nun ye válida. Por favor, écha-y un güeyu a los rexistros pa más detalles.", +"No action specified" => "Nun s'especificó l'aición", +"No configuration specified" => "Nun s'especificó la configuración", +"No data specified" => "Nun s'especificaron los datos", +" Could not set configuration %s" => "Nun pudo afitase la configuración %s", "Deletion failed" => "Falló'l borráu", +"Keep settings?" => "¿Caltener los axustes?", +"Cannot add server configuration" => "Nun pue amestase la configuración del sirvidor", +"Success" => "Con ésitu", "Error" => "Fallu", -"_%s group found_::_%s groups found_" => array("",""), -"_%s user found_::_%s users found_" => array("",""), +"Configuration incorrect" => "Configuración incorreuta", +"Configuration incomplete" => "Configuración incompleta", +"Select groups" => "Esbillar grupos", +"Select attributes" => "Esbillar atributos", +"_%s group found_::_%s groups found_" => array("%s grupu alcontráu","%s grupos alcontraos"), +"_%s user found_::_%s users found_" => array("%s usuariu alcontráu","%s usuarios alcontraos"), +"Could not find the desired feature" => "Nun pudo alcontrase la carauterística deseyada", "Save" => "Guardar", +"groups found" => "grupos alcontraos", +"Users login with this attribute:" => "Aniciu de sesión d'usuarios con esti atributu:", +"LDAP Username:" => "Nome d'usuariu LDAP", +"Other Attributes:" => "Otros atributos:", +"Add Server Configuration" => "Amestar configuración del sirvidor", +"Host" => "Equipu", +"Port" => "Puertu", "Password" => "Contraseña", -"Continue" => "Continuar" +"For anonymous access, leave DN and Password empty." => "Pa un accesu anónimu, dexar el DN y la contraseña baleros.", +"users found" => "usuarios alcontraos", +"Continue" => "Continuar", +"Connection Settings" => "Axustes de conexón", +"Configuration Active" => "Configuración activa", +"When unchecked, this configuration will be skipped." => "Cuando nun tea conseñáu, saltaráse esta configuración.", +"Disable Main Server" => "Deshabilitar sirvidor principal", +"Turn off SSL certificate validation." => "Apagar la validación del certificáu SSL.", +"Directory Settings" => "Axustes del direutoriu", +"in bytes" => "en bytes", +"Internal Username" => "Nome d'usuariu internu" ); $PLURAL_FORMS = "nplurals=2; plural=(n != 1);"; diff --git a/apps/user_ldap/l10n/ca.php b/apps/user_ldap/l10n/ca.php index 98b1cf7464..c8542586f8 100644 --- a/apps/user_ldap/l10n/ca.php +++ b/apps/user_ldap/l10n/ca.php @@ -70,6 +70,7 @@ $TRANSLATIONS = array( "Backup (Replica) Port" => "Port de la còpia de seguretat (rèplica)", "Disable Main Server" => "Desactiva el servidor principal", "Only connect to the replica server." => "Connecta només al servidor rèplica.", +"Case insensitive LDAP server (Windows)" => "Servidor LDAP sense distinció entre majúscules i minúscules (Windows)", "Turn off SSL certificate validation." => "Desactiva la validació de certificat SSL.", "Not recommended, use it for testing only! If connection only works with this option, import the LDAP server's SSL certificate in your %s server." => "No es recomana, useu-ho només com a prova! Importeu el certificat SSL del servidor LDAP al servidor %s només si la connexió funciona amb aquesta opció.", "Cache Time-To-Live" => "Memòria de cau Time-To-Live", @@ -89,6 +90,8 @@ $TRANSLATIONS = array( "Group-Member association" => "Associació membres-grup", "Nested Groups" => "Grups imbricats", "When switched on, groups that contain groups are supported. (Only works if the group member attribute contains DNs.)" => "Quan està activat, els grups que contenen grups estan permesos. (Només funciona si l'atribut del grup membre conté DNs.)", +"Paging chunksize" => "Mida de la pàgina", +"Chunksize used for paged LDAP searches that may return bulky results like user or group enumeration. (Setting it 0 disables paged LDAP searches in those situations.)" => "Mida usada per cerques LDAP paginades que podrien retornar respostes de volcat com enumeració d'usuari o grup. (Establint-ho a 0 desactiva les cerques LDAP paginades en aquestes situacions.)", "Special Attributes" => "Atributs especials", "Quota Field" => "Camp de quota", "Quota Default" => "Quota per defecte", diff --git a/apps/user_ldap/l10n/cs_CZ.php b/apps/user_ldap/l10n/cs_CZ.php index d4039e9ef1..b7dfa5a4c4 100644 --- a/apps/user_ldap/l10n/cs_CZ.php +++ b/apps/user_ldap/l10n/cs_CZ.php @@ -70,6 +70,7 @@ $TRANSLATIONS = array( "Backup (Replica) Port" => "Záložní (kopie) port", "Disable Main Server" => "Zakázat hlavní server", "Only connect to the replica server." => "Připojit jen k záložnímu serveru.", +"Case insensitive LDAP server (Windows)" => "LDAP server nerozlišující velikost znaků (Windows)", "Turn off SSL certificate validation." => "Vypnout ověřování SSL certifikátu.", "Not recommended, use it for testing only! If connection only works with this option, import the LDAP server's SSL certificate in your %s server." => "Nedoporučuje se, určeno pouze k testovacímu použití. Pokud spojení funguje jen s touto volbou, importujte SSL certifikát vašeho LDAP serveru na server %s.", "Cache Time-To-Live" => "TTL vyrovnávací paměti", diff --git a/apps/user_ldap/l10n/en_GB.php b/apps/user_ldap/l10n/en_GB.php index 6dfc3e53f0..cb0ac1a549 100644 --- a/apps/user_ldap/l10n/en_GB.php +++ b/apps/user_ldap/l10n/en_GB.php @@ -70,6 +70,7 @@ $TRANSLATIONS = array( "Backup (Replica) Port" => "Backup (Replica) Port", "Disable Main Server" => "Disable Main Server", "Only connect to the replica server." => "Only connect to the replica server.", +"Case insensitive LDAP server (Windows)" => "Case insensitive LDAP server (Windows)", "Turn off SSL certificate validation." => "Turn off SSL certificate validation.", "Not recommended, use it for testing only! If connection only works with this option, import the LDAP server's SSL certificate in your %s server." => "Not recommended, use it for testing only! If connection only works with this option, import the LDAP server's SSL certificate in your %s server.", "Cache Time-To-Live" => "Cache Time-To-Live", diff --git a/apps/user_ldap/l10n/es.php b/apps/user_ldap/l10n/es.php index 2c294aff78..bb1c9acb2a 100644 --- a/apps/user_ldap/l10n/es.php +++ b/apps/user_ldap/l10n/es.php @@ -90,6 +90,8 @@ $TRANSLATIONS = array( "Group-Member association" => "Asociación Grupo-Miembro", "Nested Groups" => "Grupos anidados", "When switched on, groups that contain groups are supported. (Only works if the group member attribute contains DNs.)" => "Cuando se active, se permitirán grupos que contenga otros grupos (solo funciona si el atributo de miembro de grupo contiene DNs).", +"Paging chunksize" => "Tamaño de los fragmentos de paginación", +"Chunksize used for paged LDAP searches that may return bulky results like user or group enumeration. (Setting it 0 disables paged LDAP searches in those situations.)" => "Tamaño de los fragmentos usado para búsquedas LDAP paginadas que pueden devolver resultados voluminosos, como enumeración de usuarios o de grupos. (Si se establece en 0, se deshabilitan las búsquedas LDAP paginadas en esas situaciones.)", "Special Attributes" => "Atributos especiales", "Quota Field" => "Cuota", "Quota Default" => "Cuota por defecto", diff --git a/apps/user_ldap/l10n/es_AR.php b/apps/user_ldap/l10n/es_AR.php index 877141bcdc..4a8047c6d9 100644 --- a/apps/user_ldap/l10n/es_AR.php +++ b/apps/user_ldap/l10n/es_AR.php @@ -33,12 +33,14 @@ $TRANSLATIONS = array( "Save" => "Guardar", "Test Configuration" => "Probar configuración", "Help" => "Ayuda", +"Groups meeting these criteria are available in %s:" => "Los grupos que cumplen con estos criterios están disponibles en %s:", "only those object classes:" => "solo estos objetos de clases:", "only from those groups:" => "solo provenientes de estos grupos:", "Edit raw filter instead" => "Editar filtro en bruto", "Raw LDAP filter" => "Filtro LDAP en bruto", "The filter specifies which LDAP groups shall have access to the %s instance." => "El filtro especifica qué grupos LDAP deben tener acceso a la instancia %s.", "groups found" => "grupos encontrados", +"Users login with this attribute:" => "Los usuarios inician sesión con este atributo:", "LDAP Username:" => "Nombre de usuario LDAP:", "LDAP Email Address:" => "Correo electrónico LDAP:", "Other Attributes:" => "Otros atributos:", @@ -53,6 +55,7 @@ $TRANSLATIONS = array( "For anonymous access, leave DN and Password empty." => "Para acceso anónimo, dejá DN y contraseña vacíos.", "One Base DN per line" => "Una DN base por línea", "You can specify Base DN for users and groups in the Advanced tab" => "Podés especificar el DN base para usuarios y grupos en la pestaña \"Avanzado\"", +"Limit %s access to users meeting these criteria:" => "Limitar acceso %s a los usuarios que cumplen con este criterio:", "The filter specifies which LDAP users shall have access to the %s instance." => "El filtro especifica cuáles usuarios LDAP deben tener acceso a la instancia %s.", "users found" => "usuarios encontrados", "Back" => "Volver", @@ -67,6 +70,7 @@ $TRANSLATIONS = array( "Backup (Replica) Port" => "Puerto para copia de seguridad (réplica)", "Disable Main Server" => "Deshabilitar el Servidor Principal", "Only connect to the replica server." => "Conectarse únicamente al servidor de réplica.", +"Case insensitive LDAP server (Windows)" => "Servidor de LDAP insensible a mayúsculas/minúsculas (Windows)", "Turn off SSL certificate validation." => "Desactivar la validación por certificado SSL.", "Not recommended, use it for testing only! If connection only works with this option, import the LDAP server's SSL certificate in your %s server." => "No es recomendado, ¡Usalo solamente para pruebas! Si la conexión únicamente funciona con esta opción, importá el certificado SSL del servidor LDAP en tu servidor %s.", "Cache Time-To-Live" => "Tiempo de vida del caché", @@ -84,6 +88,9 @@ $TRANSLATIONS = array( "One Group Base DN per line" => "Una DN base de grupo por línea", "Group Search Attributes" => "Atributos de búsqueda de grupo", "Group-Member association" => "Asociación Grupo-Miembro", +"Nested Groups" => "Grupos Anidados", +"When switched on, groups that contain groups are supported. (Only works if the group member attribute contains DNs.)" => "Cuando se activa, grupos que contienen grupos son soportados. (Solo funciona si el atributo de miembro del grupo contiene DNs)", +"Paging chunksize" => "Tamaño del fragmento de paginación", "Special Attributes" => "Atributos Especiales", "Quota Field" => "Campo de cuota", "Quota Default" => "Cuota por defecto", diff --git a/apps/user_ldap/l10n/es_CR.php b/apps/user_ldap/l10n/es_CR.php new file mode 100644 index 0000000000..3a1e002311 --- /dev/null +++ b/apps/user_ldap/l10n/es_CR.php @@ -0,0 +1,6 @@ + array("",""), +"_%s user found_::_%s users found_" => array("","") +); +$PLURAL_FORMS = "nplurals=2; plural=(n != 1);"; diff --git a/apps/user_ldap/l10n/et_EE.php b/apps/user_ldap/l10n/et_EE.php index f52449bda8..11941bf4ac 100644 --- a/apps/user_ldap/l10n/et_EE.php +++ b/apps/user_ldap/l10n/et_EE.php @@ -70,6 +70,7 @@ $TRANSLATIONS = array( "Backup (Replica) Port" => "Varuserveri (replika) port", "Disable Main Server" => "Ära kasuta peaserverit", "Only connect to the replica server." => "Ühendu ainult replitseeriva serveriga.", +"Case insensitive LDAP server (Windows)" => "Tõusutundetu LDAP server (Windows)", "Turn off SSL certificate validation." => "Lülita SSL sertifikaadi kontrollimine välja.", "Not recommended, use it for testing only! If connection only works with this option, import the LDAP server's SSL certificate in your %s server." => "Pole soovitatav, kasuta seda ainult testimiseks! Kui ühendus toimib ainult selle valikuga, siis impordi LDAP serveri SSL sertifikaat oma %s serverisse.", "Cache Time-To-Live" => "Puhvri iga", diff --git a/apps/user_ldap/l10n/ia.php b/apps/user_ldap/l10n/ia.php index e138fd835f..b29ecbb1d2 100644 --- a/apps/user_ldap/l10n/ia.php +++ b/apps/user_ldap/l10n/ia.php @@ -7,6 +7,7 @@ $TRANSLATIONS = array( "Save" => "Salveguardar", "Help" => "Adjuta", "Password" => "Contrasigno", -"Back" => "Retro" +"Back" => "Retro", +"Continue" => "Continuar" ); $PLURAL_FORMS = "nplurals=2; plural=(n != 1);"; diff --git a/apps/user_ldap/l10n/or_IN.php b/apps/user_ldap/l10n/or_IN.php new file mode 100644 index 0000000000..3a1e002311 --- /dev/null +++ b/apps/user_ldap/l10n/or_IN.php @@ -0,0 +1,6 @@ + array("",""), +"_%s user found_::_%s users found_" => array("","") +); +$PLURAL_FORMS = "nplurals=2; plural=(n != 1);"; diff --git a/apps/user_ldap/l10n/ru.php b/apps/user_ldap/l10n/ru.php index 2cf331d24c..e719f01c37 100644 --- a/apps/user_ldap/l10n/ru.php +++ b/apps/user_ldap/l10n/ru.php @@ -53,6 +53,7 @@ $TRANSLATIONS = array( "For anonymous access, leave DN and Password empty." => "Для анонимного доступа оставьте DN и пароль пустыми.", "One Base DN per line" => "По одной базе поиска (Base DN) в строке.", "You can specify Base DN for users and groups in the Advanced tab" => "Вы можете задать Base DN для пользователей и групп на вкладке \"Расширенное\"", +"Limit %s access to users meeting these criteria:" => "Ограничить доступ к %s пользователям, удовлетворяющим этому критерию:", "The filter specifies which LDAP users shall have access to the %s instance." => "Этот фильтр указывает, какие пользователи LDAP должны иметь доступ к %s.", "users found" => "пользователей найдено", "Back" => "Назад", @@ -67,6 +68,7 @@ $TRANSLATIONS = array( "Backup (Replica) Port" => "Порт резервного сервера", "Disable Main Server" => "Отключить главный сервер", "Only connect to the replica server." => "Подключаться только к серверу-реплике.", +"Case insensitive LDAP server (Windows)" => "Нечувствительный к регистру сервер LDAP (Windows)", "Turn off SSL certificate validation." => "Отключить проверку сертификата SSL.", "Not recommended, use it for testing only! If connection only works with this option, import the LDAP server's SSL certificate in your %s server." => "Не рекомендуется, используйте только в режиме тестирования! Если соединение работает только с этой опцией, импортируйте на ваш %s сервер SSL-сертификат сервера LDAP.", "Cache Time-To-Live" => "Кэш времени жизни", diff --git a/apps/user_ldap/l10n/sk_SK.php b/apps/user_ldap/l10n/sk_SK.php index edda424433..3c6b51824f 100644 --- a/apps/user_ldap/l10n/sk_SK.php +++ b/apps/user_ldap/l10n/sk_SK.php @@ -33,12 +33,14 @@ $TRANSLATIONS = array( "Save" => "Uložiť", "Test Configuration" => "Test nastavenia", "Help" => "Pomoc", +"Groups meeting these criteria are available in %s:" => "Skupiny spĺňajúce tieto kritériá sú k dispozícii v %s:", "only those object classes:" => "len tieto triedy objektov:", "only from those groups:" => "len z týchto skupín:", "Edit raw filter instead" => "Miesto pre úpravu raw filtra", "Raw LDAP filter" => "Raw LDAP filter", "The filter specifies which LDAP groups shall have access to the %s instance." => "Tento filter LDAP určuje, ktoré skupiny budú mať prístup k %s inštancii.", "groups found" => "nájdené skupiny", +"Users login with this attribute:" => "Používateľov prihlásiť pomocou tohto atribútu:", "LDAP Username:" => "LDAP používateľské meno:", "LDAP Email Address:" => "LDAP emailová adresa:", "Other Attributes:" => "Iné atribúty:", @@ -53,6 +55,7 @@ $TRANSLATIONS = array( "For anonymous access, leave DN and Password empty." => "Pre anonymný prístup ponechajte údaje DN a Heslo prázdne.", "One Base DN per line" => "Jedno základné DN na riadok", "You can specify Base DN for users and groups in the Advanced tab" => "V rozšírenom nastavení môžete zadať základné DN pre používateľov a skupiny", +"Limit %s access to users meeting these criteria:" => "Obmedziť %s prístup na používateľov spĺňajúcich tieto kritériá:", "The filter specifies which LDAP users shall have access to the %s instance." => "Tento filter LDAP určuje, ktorí používatelia majú prístup k %s inštancii.", "users found" => "nájdení používatelia", "Back" => "Späť", @@ -67,6 +70,7 @@ $TRANSLATIONS = array( "Backup (Replica) Port" => "Záložný server (kópia) port", "Disable Main Server" => "Zakázať hlavný server", "Only connect to the replica server." => "Pripojiť sa len k záložnému serveru.", +"Case insensitive LDAP server (Windows)" => "LDAP server je citlivý na veľkosť písmen (Windows)", "Turn off SSL certificate validation." => "Vypnúť overovanie SSL certifikátu.", "Not recommended, use it for testing only! If connection only works with this option, import the LDAP server's SSL certificate in your %s server." => "Neodporúčané, použite iba pri testovaní! Pokiaľ spojenie funguje iba z daným nastavením, importujte SSL certifikát LDAP servera do vášho %s servera.", "Cache Time-To-Live" => "Životnosť objektov vo vyrovnávacej pamäti", @@ -84,6 +88,7 @@ $TRANSLATIONS = array( "One Group Base DN per line" => "Jedna skupinová základná DN na riadok", "Group Search Attributes" => "Atribúty vyhľadávania skupín", "Group-Member association" => "Priradenie člena skupiny", +"Nested Groups" => "Vnorené skupiny", "Special Attributes" => "Špeciálne atribúty", "Quota Field" => "Pole kvóty", "Quota Default" => "Predvolená kvóta", diff --git a/apps/user_ldap/lib/access.php b/apps/user_ldap/lib/access.php index 4d187bab8d..712407505f 100644 --- a/apps/user_ldap/lib/access.php +++ b/apps/user_ldap/lib/access.php @@ -63,7 +63,6 @@ class Access extends LDAPUtility { return false; } //all or nothing! otherwise we get in trouble with. - $this->initPagedSearch($filter, array($dn), $attr, 99999, 0); $dn = $this->DNasBaseParameter($dn); $rr = @$this->ldap->read($cr, $dn, $filter, array($attr)); if(!$this->ldap->isResource($rr)) { @@ -434,12 +433,27 @@ class Access extends LDAPUtility { $ocname = $this->dn2ocname($ldapObject['dn'], $nameByLDAP, $isUsers); if($ocname) { $ownCloudNames[] = $ocname; + if($isUsers) { + //cache the user names so it does not need to be retrieved + //again later (e.g. sharing dialogue). + $this->cacheUserDisplayName($ocname, $nameByLDAP); + } } continue; } return $ownCloudNames; } + /** + * @brief caches the user display name + * @param string the internal owncloud username + * @param string the display name + */ + public function cacheUserDisplayName($ocname, $displayName) { + $cacheKeyTrunk = 'getDisplayName'; + $this->connection->writeToCache($cacheKeyTrunk.$ocname, $displayName); + } + /** * @brief creates a unique name for internal ownCloud use for users. Don't call it directly. * @param $name the display name of the object @@ -659,7 +673,7 @@ class Access extends LDAPUtility { * @param string $filter */ public function countUsers($filter, $attr = array('dn'), $limit = null, $offset = null) { - return $this->count($filter, $this->connection->ldapBaseGroups, $attr, $limit, $offset); + return $this->count($filter, $this->connection->ldapBaseUsers, $attr, $limit, $offset); } /** @@ -707,6 +721,9 @@ class Access extends LDAPUtility { $linkResources = array_pad(array(), count($base), $cr); $sr = $this->ldap->search($linkResources, $base, $filter, $attr); $error = $this->ldap->errno($cr); + if ($pagedSearchOK) { + $this->ldap->controlPagedResult($cr, 999999, false, ""); + } if(!is_array($sr) || $error !== 0) { \OCP\Util::writeLog('user_ldap', 'Error when searching: '.$this->ldap->error($cr). @@ -775,22 +792,47 @@ class Access extends LDAPUtility { */ private function count($filter, $base, $attr = null, $limit = null, $offset = null, $skipHandling = false) { \OCP\Util::writeLog('user_ldap', 'Count filter: '.print_r($filter, true), \OCP\Util::DEBUG); - $search = $this->executeSearch($filter, $base, $attr, $limit, $offset); - if($search === false) { - return false; - } - list($sr, $pagedSearchOK) = $search; - $cr = $this->connection->getConnectionResource(); - $counter = 0; - foreach($sr as $key => $res) { - $count = $this->ldap->countEntries($cr, $res); - if($count !== false) { - $counter += $count; - } + + if(is_null($limit)) { + $limit = $this->connection->ldapPagingSize; } - $this->processPagedSearchStatus($sr, $filter, $base, $counter, $limit, + $counter = 0; + $count = null; + $cr = $this->connection->getConnectionResource(); + + do { + $continue = false; + $search = $this->executeSearch($filter, $base, $attr, + $limit, $offset); + if($search === false) { + return $counter > 0 ? $counter : false; + } + list($sr, $pagedSearchOK) = $search; + + $count = $this->countEntriesInSearchResults($sr, $limit, $continue); + $counter += $count; + + $this->processPagedSearchStatus($sr, $filter, $base, $count, $limit, $offset, $pagedSearchOK, $skipHandling); + $offset += $limit; + } while($continue); + + return $counter; + } + + private function countEntriesInSearchResults($searchResults, $limit, + &$hasHitLimit) { + $cr = $this->connection->getConnectionResource(); + $count = 0; + + foreach($searchResults as $res) { + $count = intval($this->ldap->countEntries($cr, $res)); + $counter += $count; + if($count === $limit) { + $hasHitLimit = true; + } + } return $counter; } @@ -891,7 +933,7 @@ class Access extends LDAPUtility { //we slice the findings, when //a) paged search insuccessful, though attempted //b) no paged search, but limit set - if((!$this->pagedSearchedSuccessful + if((!$this->getPagedSearchResultState() && $pagedSearchOK) || ( !$pagedSearchOK @@ -923,8 +965,8 @@ class Access extends LDAPUtility { /** * @brief escapes (user provided) parts for LDAP filter - * @param String $input, the provided value - * @returns the escaped string + * @param string $input, the provided value + * @return the escaped string */ public function escapeFilterPart($input) { $search = array('*', '\\', '(', ')'); @@ -1184,7 +1226,7 @@ class Access extends LDAPUtility { } $offset -= $limit; //we work with cache here - $cachekey = 'lc' . crc32($base) . '-' . crc32($filter) . '-' . $limit . '-' . $offset; + $cachekey = 'lc' . crc32($base) . '-' . crc32($filter) . '-' . intval($limit) . '-' . intval($offset); $cookie = ''; if(isset($this->cookies[$cachekey])) { $cookie = $this->cookies[$cachekey]; @@ -1206,7 +1248,7 @@ class Access extends LDAPUtility { */ private function setPagedResultCookie($base, $filter, $limit, $offset, $cookie) { if(!empty($cookie)) { - $cachekey = 'lc' . crc32($base) . '-' . crc32($filter) . '-' .$limit . '-' . $offset; + $cachekey = 'lc' . crc32($base) . '-' . crc32($filter) . '-' .intval($limit) . '-' . intval($offset); $this->cookies[$cachekey] = $cookie; } } @@ -1236,9 +1278,9 @@ class Access extends LDAPUtility { if($this->connection->hasPagedResultSupport && !is_null($limit)) { $offset = intval($offset); //can be null \OCP\Util::writeLog('user_ldap', - 'initializing paged search for Filter'.$filter.' base '.print_r($bases, true) + 'initializing paged search for Filter '.$filter.' base '.print_r($bases, true) .' attr '.print_r($attr, true). ' limit ' .$limit.' offset '.$offset, - \OCP\Util::INFO); + \OCP\Util::DEBUG); //get the cookie from the search for the previous search, required by LDAP foreach($bases as $base) { @@ -1260,7 +1302,7 @@ class Access extends LDAPUtility { } if(!is_null($cookie)) { if($offset > 0) { - \OCP\Util::writeLog('user_ldap', 'Cookie '.$cookie, \OCP\Util::INFO); + \OCP\Util::writeLog('user_ldap', 'Cookie '.CRC32($cookie), \OCP\Util::INFO); } $pagedSearchOK = $this->ldap->controlPagedResult( $this->connection->getConnectionResource(), $limit, diff --git a/apps/user_ldap/lib/connection.php b/apps/user_ldap/lib/connection.php index 08ac4ac626..173c4ebcc2 100644 --- a/apps/user_ldap/lib/connection.php +++ b/apps/user_ldap/lib/connection.php @@ -134,6 +134,9 @@ class Connection extends LDAPUtility { return $this->ldapConnectionRes; } + /** + * @param string|null $key + */ private function getCacheKey($key) { $prefix = 'LDAP-'.$this->configID.'-'.$this->configPrefix.'-'; if(is_null($key)) { diff --git a/apps/user_ldap/lib/ldap.php b/apps/user_ldap/lib/ldap.php index de9b7481c1..d1ca91045b 100644 --- a/apps/user_ldap/lib/ldap.php +++ b/apps/user_ldap/lib/ldap.php @@ -139,7 +139,9 @@ class LDAP implements ILDAPWrapper { if(function_exists($func)) { $this->preFunctionCall($func, $arguments); $result = call_user_func_array($func, $arguments); - $this->postFunctionCall(); + if ($result === FALSE) { + $this->postFunctionCall(); + } return $result; } } diff --git a/apps/user_ldap/lib/proxy.php b/apps/user_ldap/lib/proxy.php index b27233bcd1..0eb294eb7a 100644 --- a/apps/user_ldap/lib/proxy.php +++ b/apps/user_ldap/lib/proxy.php @@ -80,6 +80,9 @@ abstract class Proxy { return $result; } + /** + * @param string|null $key + */ private function getCacheKey($key) { $prefix = 'LDAP-Proxy-'; if(is_null($key)) { diff --git a/apps/user_ldap/lib/wizard.php b/apps/user_ldap/lib/wizard.php index 3854af617c..8406b2d42a 100644 --- a/apps/user_ldap/lib/wizard.php +++ b/apps/user_ldap/lib/wizard.php @@ -485,7 +485,7 @@ class Wizard extends LDAPUtility { /** * @brief sets the found value for the configuration key in the WizardResult * as well as in the Configuration instance - * @param $key the configuration key + * @param string $key the configuration key * @param $value the (detected) value * @return null * @@ -799,6 +799,7 @@ class Wizard extends LDAPUtility { \OCP\Util::writeLog('user_ldap', 'Wiz: Setting LDAP Options ', \OCP\Util::DEBUG); //set LDAP options $this->ldap->setOption($cr, LDAP_OPT_PROTOCOL_VERSION, 3); + $this->ldap->setOption($cr, LDAP_OPT_REFERRALS, 0); $this->ldap->setOption($cr, LDAP_OPT_NETWORK_TIMEOUT, self::LDAP_NW_TIMEOUT); if($tls) { $isTlsWorking = @$this->ldap->startTls($cr); @@ -1000,7 +1001,7 @@ class Wizard extends LDAPUtility { /** * @brief appends a list of values fr * @param $result resource, the return value from ldap_get_attributes - * @param $attribute string, the attribute values to look for + * @param string $attribute the attribute values to look for * @param &$known array, new values will be appended here * @return int, state on of the class constants LRESULT_PROCESSED_OK, * LRESULT_PROCESSED_INVALID or LRESULT_PROCESSED_SKIP diff --git a/apps/user_ldap/lib/wizardresult.php b/apps/user_ldap/lib/wizardresult.php index 542f106cad..9e0936faa6 100644 --- a/apps/user_ldap/lib/wizardresult.php +++ b/apps/user_ldap/lib/wizardresult.php @@ -36,6 +36,10 @@ class WizardResult { $this->markedChange = true; } + /** + * @param string $key + * @param array|string $values + */ public function addOptions($key, $values) { if(!is_array($values)) { $values = array($values); diff --git a/apps/user_ldap/settings.php b/apps/user_ldap/settings.php index d077eafdde..6b7d8e6f53 100644 --- a/apps/user_ldap/settings.php +++ b/apps/user_ldap/settings.php @@ -25,6 +25,7 @@ OC_Util::checkAdminUser(); +OCP\Util::addScript('user_ldap', 'ldapFilter'); OCP\Util::addScript('user_ldap', 'settings'); OCP\Util::addScript('core', 'jquery.multiselect'); OCP\Util::addStyle('user_ldap', 'settings'); diff --git a/apps/user_ldap/templates/settings.php b/apps/user_ldap/templates/settings.php index 0a111225a7..03f2b8db09 100644 --- a/apps/user_ldap/templates/settings.php +++ b/apps/user_ldap/templates/settings.php @@ -37,7 +37,7 @@

-

+

t('Special Attributes'));?>

diff --git a/config/.htaccess b/config/.htaccess new file mode 100644 index 0000000000..2421e9a163 --- /dev/null +++ b/config/.htaccess @@ -0,0 +1,12 @@ +# line below if for Apache 2.4 + +Require all denied + + +# line below if for Apache 2.2 + +deny from all + + +# section for Apache 2.2 and 2.4 +IndexIgnore * diff --git a/core/css/mobile.css b/core/css/mobile.css index 821da61929..fd0628d7e2 100644 --- a/core/css/mobile.css +++ b/core/css/mobile.css @@ -7,6 +7,11 @@ background-position: right 26px; padding-right: 16px !important; } +/* do not show menu toggle on public share links as there is no menu */ +#body-public #owncloud.menutoggle { + background-image: none; + padding-right: 0 !important; +} /* compress search box on mobile, expand when focused */ .searchbox input[type="search"] { @@ -103,4 +108,12 @@ } +/* fix error display on smaller screens */ +.error-wide { + width: 100%; + margin-left: 0 !important; + -moz-box-sizing: border-box; + box-sizing: border-box; +} + } diff --git a/core/css/styles.css b/core/css/styles.css index 57e2c4479a..c26c556825 100644 --- a/core/css/styles.css +++ b/core/css/styles.css @@ -12,13 +12,30 @@ table, td, th { vertical-align:middle; } a { border:0; color:#000; text-decoration:none;} a, a *, input, input *, select, .button span, label { cursor:pointer; } ul { list-style:none; } -body { background:#fefefe; font:normal .8em/1.6em "Helvetica Neue",Helvetica,Arial,FreeSans,sans-serif; color:#000; } + + +body { + background: #fefefe; + font: normal .8em/1.6em "Helvetica Neue",Helvetica,Arial,FreeSans,sans-serif; + color: #000; + height: auto; +} /* HEADERS */ -#body-user #header, #body-settings #header { - position:fixed; top:0; left:0; right:0; z-index:100; height:45px; line-height:2.5em; - background:#1d2d44 url('../img/noise.png') repeat; +#body-user #header, +#body-settings #header, +#body-public #header { + position: fixed; + top: 0; + left: 0; + right: 0; + z-index: 100; + height: 45px; + line-height: 2.5em; + background: #1d2d44 url('../img/noise.png') repeat; + -moz-box-sizing: border-box; + box-sizing: border-box; } #body-login { @@ -33,9 +50,34 @@ body { background:#fefefe; font:normal .8em/1.6em "Helvetica Neue",Helvetica,Ari filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#35537a', endColorstr='#1d2d44',GradientType=0 ); /* IE6-9 */ } -#owncloud { position:absolute; top:0; left:0; padding:6px; padding-bottom:0; } -.header-right { float:right; vertical-align:middle; padding:0.5em; } -.header-right > * { vertical-align:middle; } +#owncloud { + position: absolute; + top: 0; + left: 0; + padding: 6px; + padding-bottom: 0; +} + +/* info part on the right, used e.g. for info on who shared something */ +.header-right { + position: absolute; + right: 0; + padding-right: 10px; + color: #fff; + -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=50)"; + filter: alpha(opacity=50); + opacity: .5; + height: 100%; + max-width: 40%; + white-space: nowrap; +} +.header-right #details { + display: inline-block; + margin-top: 6px; + width: 100%; + text-overflow: ellipsis; + overflow: hidden; +} /* Profile picture in header */ #header .avatardiv { @@ -406,14 +448,29 @@ input[type="submit"].enabled { } /* Icons for username and password fields to better recognize them */ -#adminlogin, #adminpass, #user, #password { width:11.7em!important; padding-left:1.8em; } -#adminlogin+label+img, #adminpass-icon, #user+label+img, #password-icon { - position:absolute; left:1.25em; top:1.65em; - -ms-filter:"progid:DXImageTransform.Microsoft.Alpha(Opacity=30)"; filter:alpha(opacity=30); opacity:.3; +#adminlogin, +#adminpass, +input[name='adminpass-clone'], +#user, +#password, +input[name='password-clone'] { + width: 223px !important; + padding-left: 36px !important; +} +#adminlogin+label+img, +#adminpass-icon, +#user+label+img, +#password-icon { + position: absolute; + left: 1.25em; + top: 1.65em; + -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=30)"; + filter: alpha(opacity=30); + opacity: .3; +} +#adminpass-icon, #password-icon { + top: 1.1em; } -#adminpass-icon, #password-icon { top:1.1em; } -input[name="password-clone"] { padding-left:1.8em; width:11.7em !important; } -input[name="adminpass-clone"] { padding-left:1.8em; width:11.7em !important; } /* General new input field look */ #body-login input[type="text"], @@ -452,15 +509,22 @@ input[name="adminpass-clone"] { padding-left:1.8em; width:11.7em !important; } p.infield { position:relative; } label.infield { cursor:text !important; top:1.05em; left:.85em; } #body-login form label.infield { /* labels are ellipsized when too long, keep them short */ - position:absolute; width:90%; padding-left:1.4em; - font-size:19px; color:#aaa; - white-space:nowrap; overflow:hidden; text-overflow:ellipsis; + position: absolute; + width: 82%; + margin-left: 26px; + font-size: 19px; + color: #aaa; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} +#body-login #databaseField .infield { + margin-left: 0; } -#body-login #databaseField .infield { padding-left:0; } #body-login form input[type="checkbox"]+label { position: relative; margin: 0; - font-size: 1em; + font-size: 13px; padding: 14px; padding-left: 28px; margin-left: -28px; @@ -586,8 +650,8 @@ label.infield { cursor:text !important; top:1.05em; left:.85em; } } .error-wide { - width: 800px; - margin-left: -250px; + width: 700px; + margin-left: -200px !important; } /* Fixes for log in page, TODO should be removed some time */ @@ -614,11 +678,13 @@ label.infield { cursor:text !important; top:1.05em; left:.85em; } /* Log in and install button */ #body-login input { - font-size: 1.5em; + font-size: 20px; + margin: 5px; + padding: 12px 10px 8px; } #body-login input[type="text"], #body-login input[type="password"] { - width: 13em; + width: 249px; } #body-login input.login { width: auto; @@ -628,7 +694,7 @@ label.infield { cursor:text !important; top:1.05em; left:.85em; } padding: 10px 20px; /* larger log in and installation buttons */ } #remember_login { - margin: 18px 5px 0 18px; + margin: 24px 5px 0 16px !important; vertical-align: text-bottom; } diff --git a/core/img/actions/add.png b/core/img/actions/add.png index 3c051e4d73..8ae17cfe11 100644 Binary files a/core/img/actions/add.png and b/core/img/actions/add.png differ diff --git a/core/img/actions/add.svg b/core/img/actions/add.svg index 250746e166..ecbab6f13a 100644 --- a/core/img/actions/add.svg +++ b/core/img/actions/add.svg @@ -1,6 +1,5 @@ - - - + + diff --git a/core/img/actions/close.png b/core/img/actions/close.png index 3389c66e03..ece33258e5 100644 Binary files a/core/img/actions/close.png and b/core/img/actions/close.png differ diff --git a/core/img/actions/close.svg b/core/img/actions/close.svg index ef564bfd48..4471dbc630 100644 --- a/core/img/actions/close.svg +++ b/core/img/actions/close.svg @@ -1,6 +1,3 @@ - - - - + diff --git a/core/img/actions/delete-hover.png b/core/img/actions/delete-hover.png index 48e6c089c9..3f8cb6eff9 100644 Binary files a/core/img/actions/delete-hover.png and b/core/img/actions/delete-hover.png differ diff --git a/core/img/actions/delete-hover.svg b/core/img/actions/delete-hover.svg index 9e5150359d..9583ec15b3 100644 --- a/core/img/actions/delete-hover.svg +++ b/core/img/actions/delete-hover.svg @@ -1,4 +1,4 @@ - + diff --git a/core/img/actions/delete.png b/core/img/actions/delete.png index 3389c66e03..e891b370cc 100644 Binary files a/core/img/actions/delete.png and b/core/img/actions/delete.png differ diff --git a/core/img/actions/delete.svg b/core/img/actions/delete.svg index ef564bfd48..f0a3cd4db8 100644 --- a/core/img/actions/delete.svg +++ b/core/img/actions/delete.svg @@ -1,6 +1,4 @@ - - - + diff --git a/core/img/actions/download.png b/core/img/actions/download.png index 0f71a5a776..1f8e1a4f7e 100644 Binary files a/core/img/actions/download.png and b/core/img/actions/download.png differ diff --git a/core/img/actions/download.svg b/core/img/actions/download.svg index a469c3b8a0..0d698bca8d 100644 --- a/core/img/actions/download.svg +++ b/core/img/actions/download.svg @@ -1,6 +1,5 @@ - - + diff --git a/core/img/actions/upload.png b/core/img/actions/upload.png index f6a0c4cfa8..a6969c23fa 100644 Binary files a/core/img/actions/upload.png and b/core/img/actions/upload.png differ diff --git a/core/img/actions/upload.svg b/core/img/actions/upload.svg index eae4515c72..80231797c9 100644 --- a/core/img/actions/upload.svg +++ b/core/img/actions/upload.svg @@ -1,6 +1,4 @@ - - - - + + diff --git a/core/img/actions/view-close.png b/core/img/actions/view-close.png index 8422b73346..c21f6ee30e 100644 Binary files a/core/img/actions/view-close.png and b/core/img/actions/view-close.png differ diff --git a/core/img/actions/view-close.svg b/core/img/actions/view-close.svg index 1d5b1a9f49..89d1fab88d 100644 --- a/core/img/actions/view-close.svg +++ b/core/img/actions/view-close.svg @@ -1,6 +1,3 @@ - - - - + diff --git a/core/js/eventsource.js b/core/js/eventsource.js index 536b180bc8..70f4a2a9aa 100644 --- a/core/js/eventsource.js +++ b/core/js/eventsource.js @@ -20,16 +20,16 @@ */ /** - * wrapper for server side events (http://en.wikipedia.org/wiki/Server-sent_events) + * Wrapper for server side events (http://en.wikipedia.org/wiki/Server-sent_events) * includes a fallback for older browsers and IE * - * use server side events with causion, to many open requests can hang the server + * Use server side events with caution, too many open requests can hang the server */ /** - * create a new event source - * @param string src - * @param object data to be send as GET + * Create a new event source + * @param {string} src + * @param {object} [data] to be send as GET */ OC.EventSource=function(src,data){ var dataStr=''; @@ -74,12 +74,12 @@ OC.EventSource=function(src,data){ this.close(); } }.bind(this)); -} +}; OC.EventSource.fallBackSources=[]; OC.EventSource.iframeCount=0;//number of fallback iframes OC.EventSource.fallBackCallBack=function(id,type,data){ OC.EventSource.fallBackSources[id].fallBackCallBack(type,data); -} +}; OC.EventSource.prototype={ typelessListeners:[], iframe:null, @@ -127,4 +127,4 @@ OC.EventSource.prototype={ this.source.close(); } } -} +}; diff --git a/core/js/jquery-1.10.0.js b/core/js/jquery-1.10.0.js new file mode 100644 index 0000000000..f38148cf12 --- /dev/null +++ b/core/js/jquery-1.10.0.js @@ -0,0 +1,9800 @@ +/*! + * jQuery JavaScript Library v1.10.0 + * http://jquery.com/ + * + * Includes Sizzle.js + * http://sizzlejs.com/ + * + * Copyright 2005, 2013 jQuery Foundation, Inc. and other contributors + * Released under the MIT license + * http://jquery.org/license + * + * Date: 2013-05-24T18:39Z + */ +(function( window, undefined ) { + +// Can't do this because several apps including ASP.NET trace +// the stack via arguments.caller.callee and Firefox dies if +// you try to trace through "use strict" call chains. (#13335) +// Support: Firefox 18+ +//"use strict"; +var + // The deferred used on DOM ready + readyList, + + // A central reference to the root jQuery(document) + rootjQuery, + + // Support: IE<10 + // For `typeof xmlNode.method` instead of `xmlNode.method !== undefined` + core_strundefined = typeof undefined, + + // Use the correct document accordingly with window argument (sandbox) + location = window.location, + document = window.document, + docElem = document.documentElement, + + // Map over jQuery in case of overwrite + _jQuery = window.jQuery, + + // Map over the $ in case of overwrite + _$ = window.$, + + // [[Class]] -> type pairs + class2type = {}, + + // List of deleted data cache ids, so we can reuse them + core_deletedIds = [], + + core_version = "1.10.0", + + // Save a reference to some core methods + core_concat = core_deletedIds.concat, + core_push = core_deletedIds.push, + core_slice = core_deletedIds.slice, + core_indexOf = core_deletedIds.indexOf, + core_toString = class2type.toString, + core_hasOwn = class2type.hasOwnProperty, + core_trim = core_version.trim, + + // Define a local copy of jQuery + jQuery = function( selector, context ) { + // The jQuery object is actually just the init constructor 'enhanced' + return new jQuery.fn.init( selector, context, rootjQuery ); + }, + + // Used for matching numbers + core_pnum = /[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/.source, + + // Used for splitting on whitespace + core_rnotwhite = /\S+/g, + + // Make sure we trim BOM and NBSP (here's looking at you, Safari 5.0 and IE) + rtrim = /^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, + + // A simple way to check for HTML strings + // Prioritize #id over to avoid XSS via location.hash (#9521) + // Strict HTML recognition (#11290: must start with <) + rquickExpr = /^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/, + + // Match a standalone tag + rsingleTag = /^<(\w+)\s*\/?>(?:<\/\1>|)$/, + + // JSON RegExp + rvalidchars = /^[\],:{}\s]*$/, + rvalidbraces = /(?:^|:|,)(?:\s*\[)+/g, + rvalidescape = /\\(?:["\\\/bfnrt]|u[\da-fA-F]{4})/g, + rvalidtokens = /"[^"\\\r\n]*"|true|false|null|-?(?:\d+\.|)\d+(?:[eE][+-]?\d+|)/g, + + // Matches dashed string for camelizing + rmsPrefix = /^-ms-/, + rdashAlpha = /-([\da-z])/gi, + + // Used by jQuery.camelCase as callback to replace() + fcamelCase = function( all, letter ) { + return letter.toUpperCase(); + }, + + // The ready event handler + completed = function( event ) { + + // readyState === "complete" is good enough for us to call the dom ready in oldIE + if ( document.addEventListener || event.type === "load" || document.readyState === "complete" ) { + detach(); + jQuery.ready(); + } + }, + // Clean-up method for dom ready events + detach = function() { + if ( document.addEventListener ) { + document.removeEventListener( "DOMContentLoaded", completed, false ); + window.removeEventListener( "load", completed, false ); + + } else { + document.detachEvent( "onreadystatechange", completed ); + window.detachEvent( "onload", completed ); + } + }; + +jQuery.fn = jQuery.prototype = { + // The current version of jQuery being used + jquery: core_version, + + constructor: jQuery, + init: function( selector, context, rootjQuery ) { + var match, elem; + + // HANDLE: $(""), $(null), $(undefined), $(false) + if ( !selector ) { + return this; + } + + // Handle HTML strings + if ( typeof selector === "string" ) { + if ( selector.charAt(0) === "<" && selector.charAt( selector.length - 1 ) === ">" && selector.length >= 3 ) { + // Assume that strings that start and end with <> are HTML and skip the regex check + match = [ null, selector, null ]; + + } else { + match = rquickExpr.exec( selector ); + } + + // Match html or make sure no context is specified for #id + if ( match && (match[1] || !context) ) { + + // HANDLE: $(html) -> $(array) + if ( match[1] ) { + context = context instanceof jQuery ? context[0] : context; + + // scripts is true for back-compat + jQuery.merge( this, jQuery.parseHTML( + match[1], + context && context.nodeType ? context.ownerDocument || context : document, + true + ) ); + + // HANDLE: $(html, props) + if ( rsingleTag.test( match[1] ) && jQuery.isPlainObject( context ) ) { + for ( match in context ) { + // Properties of context are called as methods if possible + if ( jQuery.isFunction( this[ match ] ) ) { + this[ match ]( context[ match ] ); + + // ...and otherwise set as attributes + } else { + this.attr( match, context[ match ] ); + } + } + } + + return this; + + // HANDLE: $(#id) + } else { + elem = document.getElementById( match[2] ); + + // Check parentNode to catch when Blackberry 4.6 returns + // nodes that are no longer in the document #6963 + if ( elem && elem.parentNode ) { + // Handle the case where IE and Opera return items + // by name instead of ID + if ( elem.id !== match[2] ) { + return rootjQuery.find( selector ); + } + + // Otherwise, we inject the element directly into the jQuery object + this.length = 1; + this[0] = elem; + } + + this.context = document; + this.selector = selector; + return this; + } + + // HANDLE: $(expr, $(...)) + } else if ( !context || context.jquery ) { + return ( context || rootjQuery ).find( selector ); + + // HANDLE: $(expr, context) + // (which is just equivalent to: $(context).find(expr) + } else { + return this.constructor( context ).find( selector ); + } + + // HANDLE: $(DOMElement) + } else if ( selector.nodeType ) { + this.context = this[0] = selector; + this.length = 1; + return this; + + // HANDLE: $(function) + // Shortcut for document ready + } else if ( jQuery.isFunction( selector ) ) { + return rootjQuery.ready( selector ); + } + + if ( selector.selector !== undefined ) { + this.selector = selector.selector; + this.context = selector.context; + } + + return jQuery.makeArray( selector, this ); + }, + + // Start with an empty selector + selector: "", + + // The default length of a jQuery object is 0 + length: 0, + + toArray: function() { + return core_slice.call( this ); + }, + + // Get the Nth element in the matched element set OR + // Get the whole matched element set as a clean array + get: function( num ) { + return num == null ? + + // Return a 'clean' array + this.toArray() : + + // Return just the object + ( num < 0 ? this[ this.length + num ] : this[ num ] ); + }, + + // Take an array of elements and push it onto the stack + // (returning the new matched element set) + pushStack: function( elems ) { + + // Build a new jQuery matched element set + var ret = jQuery.merge( this.constructor(), elems ); + + // Add the old object onto the stack (as a reference) + ret.prevObject = this; + ret.context = this.context; + + // Return the newly-formed element set + return ret; + }, + + // Execute a callback for every element in the matched set. + // (You can seed the arguments with an array of args, but this is + // only used internally.) + each: function( callback, args ) { + return jQuery.each( this, callback, args ); + }, + + ready: function( fn ) { + // Add the callback + jQuery.ready.promise().done( fn ); + + return this; + }, + + slice: function() { + return this.pushStack( core_slice.apply( this, arguments ) ); + }, + + first: function() { + return this.eq( 0 ); + }, + + last: function() { + return this.eq( -1 ); + }, + + eq: function( i ) { + var len = this.length, + j = +i + ( i < 0 ? len : 0 ); + return this.pushStack( j >= 0 && j < len ? [ this[j] ] : [] ); + }, + + map: function( callback ) { + return this.pushStack( jQuery.map(this, function( elem, i ) { + return callback.call( elem, i, elem ); + })); + }, + + end: function() { + return this.prevObject || this.constructor(null); + }, + + // For internal use only. + // Behaves like an Array's method, not like a jQuery method. + push: core_push, + sort: [].sort, + splice: [].splice +}; + +// Give the init function the jQuery prototype for later instantiation +jQuery.fn.init.prototype = jQuery.fn; + +jQuery.extend = jQuery.fn.extend = function() { + var src, copyIsArray, copy, name, options, clone, + target = arguments[0] || {}, + i = 1, + length = arguments.length, + deep = false; + + // Handle a deep copy situation + if ( typeof target === "boolean" ) { + deep = target; + target = arguments[1] || {}; + // skip the boolean and the target + i = 2; + } + + // Handle case when target is a string or something (possible in deep copy) + if ( typeof target !== "object" && !jQuery.isFunction(target) ) { + target = {}; + } + + // extend jQuery itself if only one argument is passed + if ( length === i ) { + target = this; + --i; + } + + for ( ; i < length; i++ ) { + // Only deal with non-null/undefined values + if ( (options = arguments[ i ]) != null ) { + // Extend the base object + for ( name in options ) { + src = target[ name ]; + copy = options[ name ]; + + // Prevent never-ending loop + if ( target === copy ) { + continue; + } + + // Recurse if we're merging plain objects or arrays + if ( deep && copy && ( jQuery.isPlainObject(copy) || (copyIsArray = jQuery.isArray(copy)) ) ) { + if ( copyIsArray ) { + copyIsArray = false; + clone = src && jQuery.isArray(src) ? src : []; + + } else { + clone = src && jQuery.isPlainObject(src) ? src : {}; + } + + // Never move original objects, clone them + target[ name ] = jQuery.extend( deep, clone, copy ); + + // Don't bring in undefined values + } else if ( copy !== undefined ) { + target[ name ] = copy; + } + } + } + } + + // Return the modified object + return target; +}; + +jQuery.extend({ + // Unique for each copy of jQuery on the page + // Non-digits removed to match rinlinejQuery + expando: "jQuery" + ( core_version + Math.random() ).replace( /\D/g, "" ), + + noConflict: function( deep ) { + if ( window.$ === jQuery ) { + window.$ = _$; + } + + if ( deep && window.jQuery === jQuery ) { + window.jQuery = _jQuery; + } + + return jQuery; + }, + + // Is the DOM ready to be used? Set to true once it occurs. + isReady: false, + + // A counter to track how many items to wait for before + // the ready event fires. See #6781 + readyWait: 1, + + // Hold (or release) the ready event + holdReady: function( hold ) { + if ( hold ) { + jQuery.readyWait++; + } else { + jQuery.ready( true ); + } + }, + + // Handle when the DOM is ready + ready: function( wait ) { + + // Abort if there are pending holds or we're already ready + if ( wait === true ? --jQuery.readyWait : jQuery.isReady ) { + return; + } + + // Make sure body exists, at least, in case IE gets a little overzealous (ticket #5443). + if ( !document.body ) { + return setTimeout( jQuery.ready ); + } + + // Remember that the DOM is ready + jQuery.isReady = true; + + // If a normal DOM Ready event fired, decrement, and wait if need be + if ( wait !== true && --jQuery.readyWait > 0 ) { + return; + } + + // If there are functions bound, to execute + readyList.resolveWith( document, [ jQuery ] ); + + // Trigger any bound ready events + if ( jQuery.fn.trigger ) { + jQuery( document ).trigger("ready").off("ready"); + } + }, + + // See test/unit/core.js for details concerning isFunction. + // Since version 1.3, DOM methods and functions like alert + // aren't supported. They return false on IE (#2968). + isFunction: function( obj ) { + return jQuery.type(obj) === "function"; + }, + + isArray: Array.isArray || function( obj ) { + return jQuery.type(obj) === "array"; + }, + + isWindow: function( obj ) { + /* jshint eqeqeq: false */ + return obj != null && obj == obj.window; + }, + + isNumeric: function( obj ) { + return !isNaN( parseFloat(obj) ) && isFinite( obj ); + }, + + type: function( obj ) { + if ( obj == null ) { + return String( obj ); + } + return typeof obj === "object" || typeof obj === "function" ? + class2type[ core_toString.call(obj) ] || "object" : + typeof obj; + }, + + isPlainObject: function( obj ) { + var key; + + // Must be an Object. + // Because of IE, we also have to check the presence of the constructor property. + // Make sure that DOM nodes and window objects don't pass through, as well + if ( !obj || jQuery.type(obj) !== "object" || obj.nodeType || jQuery.isWindow( obj ) ) { + return false; + } + + try { + // Not own constructor property must be Object + if ( obj.constructor && + !core_hasOwn.call(obj, "constructor") && + !core_hasOwn.call(obj.constructor.prototype, "isPrototypeOf") ) { + return false; + } + } catch ( e ) { + // IE8,9 Will throw exceptions on certain host objects #9897 + return false; + } + + // Support: IE<9 + // Handle iteration over inherited properties before own properties. + if ( jQuery.support.ownLast ) { + for ( key in obj ) { + return core_hasOwn.call( obj, key ); + } + } + + // Own properties are enumerated firstly, so to speed up, + // if last one is own, then all properties are own. + for ( key in obj ) {} + + return key === undefined || core_hasOwn.call( obj, key ); + }, + + isEmptyObject: function( obj ) { + var name; + for ( name in obj ) { + return false; + } + return true; + }, + + error: function( msg ) { + throw new Error( msg ); + }, + + // data: string of html + // context (optional): If specified, the fragment will be created in this context, defaults to document + // keepScripts (optional): If true, will include scripts passed in the html string + parseHTML: function( data, context, keepScripts ) { + if ( !data || typeof data !== "string" ) { + return null; + } + if ( typeof context === "boolean" ) { + keepScripts = context; + context = false; + } + context = context || document; + + var parsed = rsingleTag.exec( data ), + scripts = !keepScripts && []; + + // Single tag + if ( parsed ) { + return [ context.createElement( parsed[1] ) ]; + } + + parsed = jQuery.buildFragment( [ data ], context, scripts ); + if ( scripts ) { + jQuery( scripts ).remove(); + } + return jQuery.merge( [], parsed.childNodes ); + }, + + parseJSON: function( data ) { + // Attempt to parse using the native JSON parser first + if ( window.JSON && window.JSON.parse ) { + return window.JSON.parse( data ); + } + + if ( data === null ) { + return data; + } + + if ( typeof data === "string" ) { + + // Make sure leading/trailing whitespace is removed (IE can't handle it) + data = jQuery.trim( data ); + + if ( data ) { + // Make sure the incoming data is actual JSON + // Logic borrowed from http://json.org/json2.js + if ( rvalidchars.test( data.replace( rvalidescape, "@" ) + .replace( rvalidtokens, "]" ) + .replace( rvalidbraces, "")) ) { + + return ( new Function( "return " + data ) )(); + } + } + } + + jQuery.error( "Invalid JSON: " + data ); + }, + + // Cross-browser xml parsing + parseXML: function( data ) { + var xml, tmp; + if ( !data || typeof data !== "string" ) { + return null; + } + try { + if ( window.DOMParser ) { // Standard + tmp = new DOMParser(); + xml = tmp.parseFromString( data , "text/xml" ); + } else { // IE + xml = new ActiveXObject( "Microsoft.XMLDOM" ); + xml.async = "false"; + xml.loadXML( data ); + } + } catch( e ) { + xml = undefined; + } + if ( !xml || !xml.documentElement || xml.getElementsByTagName( "parsererror" ).length ) { + jQuery.error( "Invalid XML: " + data ); + } + return xml; + }, + + noop: function() {}, + + // Evaluates a script in a global context + // Workarounds based on findings by Jim Driscoll + // http://weblogs.java.net/blog/driscoll/archive/2009/09/08/eval-javascript-global-context + globalEval: function( data ) { + if ( data && jQuery.trim( data ) ) { + // We use execScript on Internet Explorer + // We use an anonymous function so that context is window + // rather than jQuery in Firefox + ( window.execScript || function( data ) { + window[ "eval" ].call( window, data ); + } )( data ); + } + }, + + // Convert dashed to camelCase; used by the css and data modules + // Microsoft forgot to hump their vendor prefix (#9572) + camelCase: function( string ) { + return string.replace( rmsPrefix, "ms-" ).replace( rdashAlpha, fcamelCase ); + }, + + nodeName: function( elem, name ) { + return elem.nodeName && elem.nodeName.toLowerCase() === name.toLowerCase(); + }, + + // args is for internal usage only + each: function( obj, callback, args ) { + var value, + i = 0, + length = obj.length, + isArray = isArraylike( obj ); + + if ( args ) { + if ( isArray ) { + for ( ; i < length; i++ ) { + value = callback.apply( obj[ i ], args ); + + if ( value === false ) { + break; + } + } + } else { + for ( i in obj ) { + value = callback.apply( obj[ i ], args ); + + if ( value === false ) { + break; + } + } + } + + // A special, fast, case for the most common use of each + } else { + if ( isArray ) { + for ( ; i < length; i++ ) { + value = callback.call( obj[ i ], i, obj[ i ] ); + + if ( value === false ) { + break; + } + } + } else { + for ( i in obj ) { + value = callback.call( obj[ i ], i, obj[ i ] ); + + if ( value === false ) { + break; + } + } + } + } + + return obj; + }, + + // Use native String.trim function wherever possible + trim: core_trim && !core_trim.call("\uFEFF\xA0") ? + function( text ) { + return text == null ? + "" : + core_trim.call( text ); + } : + + // Otherwise use our own trimming functionality + function( text ) { + return text == null ? + "" : + ( text + "" ).replace( rtrim, "" ); + }, + + // results is for internal usage only + makeArray: function( arr, results ) { + var ret = results || []; + + if ( arr != null ) { + if ( isArraylike( Object(arr) ) ) { + jQuery.merge( ret, + typeof arr === "string" ? + [ arr ] : arr + ); + } else { + core_push.call( ret, arr ); + } + } + + return ret; + }, + + inArray: function( elem, arr, i ) { + var len; + + if ( arr ) { + if ( core_indexOf ) { + return core_indexOf.call( arr, elem, i ); + } + + len = arr.length; + i = i ? i < 0 ? Math.max( 0, len + i ) : i : 0; + + for ( ; i < len; i++ ) { + // Skip accessing in sparse arrays + if ( i in arr && arr[ i ] === elem ) { + return i; + } + } + } + + return -1; + }, + + merge: function( first, second ) { + var l = second.length, + i = first.length, + j = 0; + + if ( typeof l === "number" ) { + for ( ; j < l; j++ ) { + first[ i++ ] = second[ j ]; + } + } else { + while ( second[j] !== undefined ) { + first[ i++ ] = second[ j++ ]; + } + } + + first.length = i; + + return first; + }, + + grep: function( elems, callback, inv ) { + var retVal, + ret = [], + i = 0, + length = elems.length; + inv = !!inv; + + // Go through the array, only saving the items + // that pass the validator function + for ( ; i < length; i++ ) { + retVal = !!callback( elems[ i ], i ); + if ( inv !== retVal ) { + ret.push( elems[ i ] ); + } + } + + return ret; + }, + + // arg is for internal usage only + map: function( elems, callback, arg ) { + var value, + i = 0, + length = elems.length, + isArray = isArraylike( elems ), + ret = []; + + // Go through the array, translating each of the items to their + if ( isArray ) { + for ( ; i < length; i++ ) { + value = callback( elems[ i ], i, arg ); + + if ( value != null ) { + ret[ ret.length ] = value; + } + } + + // Go through every key on the object, + } else { + for ( i in elems ) { + value = callback( elems[ i ], i, arg ); + + if ( value != null ) { + ret[ ret.length ] = value; + } + } + } + + // Flatten any nested arrays + return core_concat.apply( [], ret ); + }, + + // A global GUID counter for objects + guid: 1, + + // Bind a function to a context, optionally partially applying any + // arguments. + proxy: function( fn, context ) { + var args, proxy, tmp; + + if ( typeof context === "string" ) { + tmp = fn[ context ]; + context = fn; + fn = tmp; + } + + // Quick check to determine if target is callable, in the spec + // this throws a TypeError, but we will just return undefined. + if ( !jQuery.isFunction( fn ) ) { + return undefined; + } + + // Simulated bind + args = core_slice.call( arguments, 2 ); + proxy = function() { + return fn.apply( context || this, args.concat( core_slice.call( arguments ) ) ); + }; + + // Set the guid of unique handler to the same of original handler, so it can be removed + proxy.guid = fn.guid = fn.guid || jQuery.guid++; + + return proxy; + }, + + // Multifunctional method to get and set values of a collection + // The value/s can optionally be executed if it's a function + access: function( elems, fn, key, value, chainable, emptyGet, raw ) { + var i = 0, + length = elems.length, + bulk = key == null; + + // Sets many values + if ( jQuery.type( key ) === "object" ) { + chainable = true; + for ( i in key ) { + jQuery.access( elems, fn, i, key[i], true, emptyGet, raw ); + } + + // Sets one value + } else if ( value !== undefined ) { + chainable = true; + + if ( !jQuery.isFunction( value ) ) { + raw = true; + } + + if ( bulk ) { + // Bulk operations run against the entire set + if ( raw ) { + fn.call( elems, value ); + fn = null; + + // ...except when executing function values + } else { + bulk = fn; + fn = function( elem, key, value ) { + return bulk.call( jQuery( elem ), value ); + }; + } + } + + if ( fn ) { + for ( ; i < length; i++ ) { + fn( elems[i], key, raw ? value : value.call( elems[i], i, fn( elems[i], key ) ) ); + } + } + } + + return chainable ? + elems : + + // Gets + bulk ? + fn.call( elems ) : + length ? fn( elems[0], key ) : emptyGet; + }, + + now: function() { + return ( new Date() ).getTime(); + }, + + // A method for quickly swapping in/out CSS properties to get correct calculations. + // Note: this method belongs to the css module but it's needed here for the support module. + // If support gets modularized, this method should be moved back to the css module. + swap: function( elem, options, callback, args ) { + var ret, name, + old = {}; + + // Remember the old values, and insert the new ones + for ( name in options ) { + old[ name ] = elem.style[ name ]; + elem.style[ name ] = options[ name ]; + } + + ret = callback.apply( elem, args || [] ); + + // Revert the old values + for ( name in options ) { + elem.style[ name ] = old[ name ]; + } + + return ret; + } +}); + +jQuery.ready.promise = function( obj ) { + if ( !readyList ) { + + readyList = jQuery.Deferred(); + + // Catch cases where $(document).ready() is called after the browser event has already occurred. + // we once tried to use readyState "interactive" here, but it caused issues like the one + // discovered by ChrisS here: http://bugs.jquery.com/ticket/12282#comment:15 + if ( document.readyState === "complete" ) { + // Handle it asynchronously to allow scripts the opportunity to delay ready + setTimeout( jQuery.ready ); + + // Standards-based browsers support DOMContentLoaded + } else if ( document.addEventListener ) { + // Use the handy event callback + document.addEventListener( "DOMContentLoaded", completed, false ); + + // A fallback to window.onload, that will always work + window.addEventListener( "load", completed, false ); + + // If IE event model is used + } else { + // Ensure firing before onload, maybe late but safe also for iframes + document.attachEvent( "onreadystatechange", completed ); + + // A fallback to window.onload, that will always work + window.attachEvent( "onload", completed ); + + // If IE and not a frame + // continually check to see if the document is ready + var top = false; + + try { + top = window.frameElement == null && document.documentElement; + } catch(e) {} + + if ( top && top.doScroll ) { + (function doScrollCheck() { + if ( !jQuery.isReady ) { + + try { + // Use the trick by Diego Perini + // http://javascript.nwbox.com/IEContentLoaded/ + top.doScroll("left"); + } catch(e) { + return setTimeout( doScrollCheck, 50 ); + } + + // detach all dom ready events + detach(); + + // and execute any waiting functions + jQuery.ready(); + } + })(); + } + } + } + return readyList.promise( obj ); +}; + +// Populate the class2type map +jQuery.each("Boolean Number String Function Array Date RegExp Object Error".split(" "), function(i, name) { + class2type[ "[object " + name + "]" ] = name.toLowerCase(); +}); + +function isArraylike( obj ) { + var length = obj.length, + type = jQuery.type( obj ); + + if ( jQuery.isWindow( obj ) ) { + return false; + } + + if ( obj.nodeType === 1 && length ) { + return true; + } + + return type === "array" || type !== "function" && + ( length === 0 || + typeof length === "number" && length > 0 && ( length - 1 ) in obj ); +} + +// All jQuery objects should point back to these +rootjQuery = jQuery(document); +/*! + * Sizzle CSS Selector Engine v1.9.4-pre + * http://sizzlejs.com/ + * + * Copyright 2013 jQuery Foundation, Inc. and other contributors + * Released under the MIT license + * http://jquery.org/license + * + * Date: 2013-05-15 + */ +(function( window, undefined ) { + +var i, + support, + cachedruns, + Expr, + getText, + isXML, + compile, + outermostContext, + sortInput, + + // Local document vars + setDocument, + document, + docElem, + documentIsHTML, + rbuggyQSA, + rbuggyMatches, + matches, + contains, + + // Instance-specific data + expando = "sizzle" + -(new Date()), + preferredDoc = window.document, + dirruns = 0, + done = 0, + classCache = createCache(), + tokenCache = createCache(), + compilerCache = createCache(), + hasDuplicate = false, + sortOrder = function() { return 0; }, + + // General-purpose constants + strundefined = typeof undefined, + MAX_NEGATIVE = 1 << 31, + + // Instance methods + hasOwn = ({}).hasOwnProperty, + arr = [], + pop = arr.pop, + push_native = arr.push, + push = arr.push, + slice = arr.slice, + // Use a stripped-down indexOf if we can't use a native one + indexOf = arr.indexOf || function( elem ) { + var i = 0, + len = this.length; + for ( ; i < len; i++ ) { + if ( this[i] === elem ) { + return i; + } + } + return -1; + }, + + booleans = "checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped", + + // Regular expressions + + // Whitespace characters http://www.w3.org/TR/css3-selectors/#whitespace + whitespace = "[\\x20\\t\\r\\n\\f]", + // http://www.w3.org/TR/css3-syntax/#characters + characterEncoding = "(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+", + + // Loosely modeled on CSS identifier characters + // An unquoted value should be a CSS identifier http://www.w3.org/TR/css3-selectors/#attribute-selectors + // Proper syntax: http://www.w3.org/TR/CSS21/syndata.html#value-def-identifier + identifier = characterEncoding.replace( "w", "w#" ), + + // Acceptable operators http://www.w3.org/TR/selectors/#attribute-selectors + attributes = "\\[" + whitespace + "*(" + characterEncoding + ")" + whitespace + + "*(?:([*^$|!~]?=)" + whitespace + "*(?:(['\"])((?:\\\\.|[^\\\\])*?)\\3|(" + identifier + ")|)|)" + whitespace + "*\\]", + + // Prefer arguments quoted, + // then not containing pseudos/brackets, + // then attribute selectors/non-parenthetical expressions, + // then anything else + // These preferences are here to reduce the number of selectors + // needing tokenize in the PSEUDO preFilter + pseudos = ":(" + characterEncoding + ")(?:\\(((['\"])((?:\\\\.|[^\\\\])*?)\\3|((?:\\\\.|[^\\\\()[\\]]|" + attributes.replace( 3, 8 ) + ")*)|.*)\\)|)", + + // Leading and non-escaped trailing whitespace, capturing some non-whitespace characters preceding the latter + rtrim = new RegExp( "^" + whitespace + "+|((?:^|[^\\\\])(?:\\\\.)*)" + whitespace + "+$", "g" ), + + rcomma = new RegExp( "^" + whitespace + "*," + whitespace + "*" ), + rcombinators = new RegExp( "^" + whitespace + "*([>+~]|" + whitespace + ")" + whitespace + "*" ), + + rsibling = new RegExp( whitespace + "*[+~]" ), + rattributeQuotes = new RegExp( "=" + whitespace + "*([^\\]'\"]*)" + whitespace + "*\\]", "g" ), + + rpseudo = new RegExp( pseudos ), + ridentifier = new RegExp( "^" + identifier + "$" ), + + matchExpr = { + "ID": new RegExp( "^#(" + characterEncoding + ")" ), + "CLASS": new RegExp( "^\\.(" + characterEncoding + ")" ), + "TAG": new RegExp( "^(" + characterEncoding.replace( "w", "w*" ) + ")" ), + "ATTR": new RegExp( "^" + attributes ), + "PSEUDO": new RegExp( "^" + pseudos ), + "CHILD": new RegExp( "^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\(" + whitespace + + "*(even|odd|(([+-]|)(\\d*)n|)" + whitespace + "*(?:([+-]|)" + whitespace + + "*(\\d+)|))" + whitespace + "*\\)|)", "i" ), + "bool": new RegExp( "^(?:" + booleans + ")$", "i" ), + // For use in libraries implementing .is() + // We use this for POS matching in `select` + "needsContext": new RegExp( "^" + whitespace + "*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\(" + + whitespace + "*((?:-\\d)?\\d*)" + whitespace + "*\\)|)(?=[^-]|$)", "i" ) + }, + + rnative = /^[^{]+\{\s*\[native \w/, + + // Easily-parseable/retrievable ID or TAG or CLASS selectors + rquickExpr = /^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/, + + rinputs = /^(?:input|select|textarea|button)$/i, + rheader = /^h\d$/i, + + rescape = /'|\\/g, + + // CSS escapes http://www.w3.org/TR/CSS21/syndata.html#escaped-characters + runescape = new RegExp( "\\\\([\\da-f]{1,6}" + whitespace + "?|(" + whitespace + ")|.)", "ig" ), + funescape = function( _, escaped, escapedWhitespace ) { + var high = "0x" + escaped - 0x10000; + // NaN means non-codepoint + // Support: Firefox + // Workaround erroneous numeric interpretation of +"0x" + return high !== high || escapedWhitespace ? + escaped : + // BMP codepoint + high < 0 ? + String.fromCharCode( high + 0x10000 ) : + // Supplemental Plane codepoint (surrogate pair) + String.fromCharCode( high >> 10 | 0xD800, high & 0x3FF | 0xDC00 ); + }; + +// Optimize for push.apply( _, NodeList ) +try { + push.apply( + (arr = slice.call( preferredDoc.childNodes )), + preferredDoc.childNodes + ); + // Support: Android<4.0 + // Detect silently failing push.apply + arr[ preferredDoc.childNodes.length ].nodeType; +} catch ( e ) { + push = { apply: arr.length ? + + // Leverage slice if possible + function( target, els ) { + push_native.apply( target, slice.call(els) ); + } : + + // Support: IE<9 + // Otherwise append directly + function( target, els ) { + var j = target.length, + i = 0; + // Can't trust NodeList.length + while ( (target[j++] = els[i++]) ) {} + target.length = j - 1; + } + }; +} + +function Sizzle( selector, context, results, seed ) { + var match, elem, m, nodeType, + // QSA vars + i, groups, old, nid, newContext, newSelector; + + if ( ( context ? context.ownerDocument || context : preferredDoc ) !== document ) { + setDocument( context ); + } + + context = context || document; + results = results || []; + + if ( !selector || typeof selector !== "string" ) { + return results; + } + + if ( (nodeType = context.nodeType) !== 1 && nodeType !== 9 ) { + return []; + } + + if ( documentIsHTML && !seed ) { + + // Shortcuts + if ( (match = rquickExpr.exec( selector )) ) { + // Speed-up: Sizzle("#ID") + if ( (m = match[1]) ) { + if ( nodeType === 9 ) { + elem = context.getElementById( m ); + // Check parentNode to catch when Blackberry 4.6 returns + // nodes that are no longer in the document #6963 + if ( elem && elem.parentNode ) { + // Handle the case where IE, Opera, and Webkit return items + // by name instead of ID + if ( elem.id === m ) { + results.push( elem ); + return results; + } + } else { + return results; + } + } else { + // Context is not a document + if ( context.ownerDocument && (elem = context.ownerDocument.getElementById( m )) && + contains( context, elem ) && elem.id === m ) { + results.push( elem ); + return results; + } + } + + // Speed-up: Sizzle("TAG") + } else if ( match[2] ) { + push.apply( results, context.getElementsByTagName( selector ) ); + return results; + + // Speed-up: Sizzle(".CLASS") + } else if ( (m = match[3]) && support.getElementsByClassName && context.getElementsByClassName ) { + push.apply( results, context.getElementsByClassName( m ) ); + return results; + } + } + + // QSA path + if ( support.qsa && (!rbuggyQSA || !rbuggyQSA.test( selector )) ) { + nid = old = expando; + newContext = context; + newSelector = nodeType === 9 && selector; + + // qSA works strangely on Element-rooted queries + // We can work around this by specifying an extra ID on the root + // and working up from there (Thanks to Andrew Dupont for the technique) + // IE 8 doesn't work on object elements + if ( nodeType === 1 && context.nodeName.toLowerCase() !== "object" ) { + groups = tokenize( selector ); + + if ( (old = context.getAttribute("id")) ) { + nid = old.replace( rescape, "\\$&" ); + } else { + context.setAttribute( "id", nid ); + } + nid = "[id='" + nid + "'] "; + + i = groups.length; + while ( i-- ) { + groups[i] = nid + toSelector( groups[i] ); + } + newContext = rsibling.test( selector ) && context.parentNode || context; + newSelector = groups.join(","); + } + + if ( newSelector ) { + try { + push.apply( results, + newContext.querySelectorAll( newSelector ) + ); + return results; + } catch(qsaError) { + } finally { + if ( !old ) { + context.removeAttribute("id"); + } + } + } + } + } + + // All others + return select( selector.replace( rtrim, "$1" ), context, results, seed ); +} + +/** + * For feature detection + * @param {Function} fn The function to test for native support + */ +function isNative( fn ) { + return rnative.test( fn + "" ); +} + +/** + * Create key-value caches of limited size + * @returns {Function(string, Object)} Returns the Object data after storing it on itself with + * property name the (space-suffixed) string and (if the cache is larger than Expr.cacheLength) + * deleting the oldest entry + */ +function createCache() { + var keys = []; + + function cache( key, value ) { + // Use (key + " ") to avoid collision with native prototype properties (see Issue #157) + if ( keys.push( key += " " ) > Expr.cacheLength ) { + // Only keep the most recent entries + delete cache[ keys.shift() ]; + } + return (cache[ key ] = value); + } + return cache; +} + +/** + * Mark a function for special use by Sizzle + * @param {Function} fn The function to mark + */ +function markFunction( fn ) { + fn[ expando ] = true; + return fn; +} + +/** + * Support testing using an element + * @param {Function} fn Passed the created div and expects a boolean result + */ +function assert( fn ) { + var div = document.createElement("div"); + + try { + return !!fn( div ); + } catch (e) { + return false; + } finally { + // Remove from its parent by default + if ( div.parentNode ) { + div.parentNode.removeChild( div ); + } + // release memory in IE + div = null; + } +} + +/** + * Adds the same handler for all of the specified attrs + * @param {String} attrs Pipe-separated list of attributes + * @param {Function} handler The method that will be applied if the test fails + * @param {Boolean} test The result of a test. If true, null will be set as the handler in leiu of the specified handler + */ +function addHandle( attrs, handler, test ) { + attrs = attrs.split("|"); + var current, + i = attrs.length, + setHandle = test ? null : handler; + + while ( i-- ) { + // Don't override a user's handler + if ( !(current = Expr.attrHandle[ attrs[i] ]) || current === handler ) { + Expr.attrHandle[ attrs[i] ] = setHandle; + } + } +} + +/** + * Fetches boolean attributes by node + * @param {Element} elem + * @param {String} name + */ +function boolHandler( elem, name ) { + // XML does not need to be checked as this will not be assigned for XML documents + var val = elem.getAttributeNode( name ); + return val && val.specified ? + val.value : + elem[ name ] === true ? name.toLowerCase() : null; +} + +/** + * Fetches attributes without interpolation + * http://msdn.microsoft.com/en-us/library/ms536429%28VS.85%29.aspx + * @param {Element} elem + * @param {String} name + */ +function interpolationHandler( elem, name ) { + // XML does not need to be checked as this will not be assigned for XML documents + return elem.getAttribute( name, name.toLowerCase() === "type" ? 1 : 2 ); +} + +/** + * Uses defaultValue to retrieve value in IE6/7 + * @param {Element} elem + * @param {String} name + */ +function valueHandler( elem ) { + // Ignore the value *property* on inputs by using defaultValue + // Fallback to Sizzle.attr by returning undefined where appropriate + // XML does not need to be checked as this will not be assigned for XML documents + if ( elem.nodeName.toLowerCase() === "input" ) { + return elem.defaultValue; + } +} + +/** + * Checks document order of two siblings + * @param {Element} a + * @param {Element} b + * @returns Returns -1 if a precedes b, 1 if a follows b + */ +function siblingCheck( a, b ) { + var cur = b && a, + diff = cur && a.nodeType === 1 && b.nodeType === 1 && + ( ~b.sourceIndex || MAX_NEGATIVE ) - + ( ~a.sourceIndex || MAX_NEGATIVE ); + + // Use IE sourceIndex if available on both nodes + if ( diff ) { + return diff; + } + + // Check if b follows a + if ( cur ) { + while ( (cur = cur.nextSibling) ) { + if ( cur === b ) { + return -1; + } + } + } + + return a ? 1 : -1; +} + +/** + * Returns a function to use in pseudos for input types + * @param {String} type + */ +function createInputPseudo( type ) { + return function( elem ) { + var name = elem.nodeName.toLowerCase(); + return name === "input" && elem.type === type; + }; +} + +/** + * Returns a function to use in pseudos for buttons + * @param {String} type + */ +function createButtonPseudo( type ) { + return function( elem ) { + var name = elem.nodeName.toLowerCase(); + return (name === "input" || name === "button") && elem.type === type; + }; +} + +/** + * Returns a function to use in pseudos for positionals + * @param {Function} fn + */ +function createPositionalPseudo( fn ) { + return markFunction(function( argument ) { + argument = +argument; + return markFunction(function( seed, matches ) { + var j, + matchIndexes = fn( [], seed.length, argument ), + i = matchIndexes.length; + + // Match elements found at the specified indexes + while ( i-- ) { + if ( seed[ (j = matchIndexes[i]) ] ) { + seed[j] = !(matches[j] = seed[j]); + } + } + }); + }); +} + +/** + * Detect xml + * @param {Element|Object} elem An element or a document + */ +isXML = Sizzle.isXML = function( elem ) { + // documentElement is verified for cases where it doesn't yet exist + // (such as loading iframes in IE - #4833) + var documentElement = elem && (elem.ownerDocument || elem).documentElement; + return documentElement ? documentElement.nodeName !== "HTML" : false; +}; + +// Expose support vars for convenience +support = Sizzle.support = {}; + +/** + * Sets document-related variables once based on the current document + * @param {Element|Object} [doc] An element or document object to use to set the document + * @returns {Object} Returns the current document + */ +setDocument = Sizzle.setDocument = function( node ) { + var doc = node ? node.ownerDocument || node : preferredDoc; + + // If no document and documentElement is available, return + if ( doc === document || doc.nodeType !== 9 || !doc.documentElement ) { + return document; + } + + // Set our document + document = doc; + docElem = doc.documentElement; + + // Support tests + documentIsHTML = !isXML( doc ); + + /* Attributes + ---------------------------------------------------------------------- */ + + // Support: IE<8 + // Verify that getAttribute really returns attributes and not properties (excepting IE8 booleans) + support.attributes = assert(function( div ) { + + // Support: IE<8 + // Prevent attribute/property "interpolation" + div.innerHTML = ""; + addHandle( "type|href|height|width", interpolationHandler, div.firstChild.getAttribute("href") === "#" ); + + // Support: IE<9 + // Use getAttributeNode to fetch booleans when getAttribute lies + addHandle( booleans, boolHandler, div.getAttribute("disabled") == null ); + + div.className = "i"; + return !div.getAttribute("className"); + }); + + // Support: IE<9 + // Retrieving value should defer to defaultValue + support.input = assert(function( div ) { + div.innerHTML = ""; + div.firstChild.setAttribute( "value", "" ); + return div.firstChild.getAttribute( "value" ) === ""; + }); + + // IE6/7 still return empty string for value, + // but are actually retrieving the property + addHandle( "value", valueHandler, support.attributes && support.input ); + + /* getElement(s)By* + ---------------------------------------------------------------------- */ + + // Check if getElementsByTagName("*") returns only elements + support.getElementsByTagName = assert(function( div ) { + div.appendChild( doc.createComment("") ); + return !div.getElementsByTagName("*").length; + }); + + // Check if getElementsByClassName can be trusted + support.getElementsByClassName = assert(function( div ) { + div.innerHTML = "
"; + + // Support: Safari<4 + // Catch class over-caching + div.firstChild.className = "i"; + // Support: Opera<10 + // Catch gEBCN failure to find non-leading classes + return div.getElementsByClassName("i").length === 2; + }); + + // Support: IE<10 + // Check if getElementById returns elements by name + // The broken getElementById methods don't pick up programatically-set names, + // so use a roundabout getElementsByName test + support.getById = assert(function( div ) { + docElem.appendChild( div ).id = expando; + return !doc.getElementsByName || !doc.getElementsByName( expando ).length; + }); + + // ID find and filter + if ( support.getById ) { + Expr.find["ID"] = function( id, context ) { + if ( typeof context.getElementById !== strundefined && documentIsHTML ) { + var m = context.getElementById( id ); + // Check parentNode to catch when Blackberry 4.6 returns + // nodes that are no longer in the document #6963 + return m && m.parentNode ? [m] : []; + } + }; + Expr.filter["ID"] = function( id ) { + var attrId = id.replace( runescape, funescape ); + return function( elem ) { + return elem.getAttribute("id") === attrId; + }; + }; + } else { + // Support: IE6/7 + // getElementById is not reliable as a find shortcut + delete Expr.find["ID"]; + + Expr.filter["ID"] = function( id ) { + var attrId = id.replace( runescape, funescape ); + return function( elem ) { + var node = typeof elem.getAttributeNode !== strundefined && elem.getAttributeNode("id"); + return node && node.value === attrId; + }; + }; + } + + // Tag + Expr.find["TAG"] = support.getElementsByTagName ? + function( tag, context ) { + if ( typeof context.getElementsByTagName !== strundefined ) { + return context.getElementsByTagName( tag ); + } + } : + function( tag, context ) { + var elem, + tmp = [], + i = 0, + results = context.getElementsByTagName( tag ); + + // Filter out possible comments + if ( tag === "*" ) { + while ( (elem = results[i++]) ) { + if ( elem.nodeType === 1 ) { + tmp.push( elem ); + } + } + + return tmp; + } + return results; + }; + + // Class + Expr.find["CLASS"] = support.getElementsByClassName && function( className, context ) { + if ( typeof context.getElementsByClassName !== strundefined && documentIsHTML ) { + return context.getElementsByClassName( className ); + } + }; + + /* QSA/matchesSelector + ---------------------------------------------------------------------- */ + + // QSA and matchesSelector support + + // matchesSelector(:active) reports false when true (IE9/Opera 11.5) + rbuggyMatches = []; + + // qSa(:focus) reports false when true (Chrome 21) + // We allow this because of a bug in IE8/9 that throws an error + // whenever `document.activeElement` is accessed on an iframe + // So, we allow :focus to pass through QSA all the time to avoid the IE error + // See http://bugs.jquery.com/ticket/13378 + rbuggyQSA = []; + + if ( (support.qsa = isNative(doc.querySelectorAll)) ) { + // Build QSA regex + // Regex strategy adopted from Diego Perini + assert(function( div ) { + // Select is set to empty string on purpose + // This is to test IE's treatment of not explicitly + // setting a boolean content attribute, + // since its presence should be enough + // http://bugs.jquery.com/ticket/12359 + div.innerHTML = ""; + + // Support: IE8 + // Boolean attributes and "value" are not treated correctly + if ( !div.querySelectorAll("[selected]").length ) { + rbuggyQSA.push( "\\[" + whitespace + "*(?:value|" + booleans + ")" ); + } + + // Webkit/Opera - :checked should return selected option elements + // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked + // IE8 throws error here and will not see later tests + if ( !div.querySelectorAll(":checked").length ) { + rbuggyQSA.push(":checked"); + } + }); + + assert(function( div ) { + + // Support: Opera 10-12/IE8 + // ^= $= *= and empty values + // Should not select anything + // Support: Windows 8 Native Apps + // The type attribute is restricted during .innerHTML assignment + var input = doc.createElement("input"); + input.setAttribute( "type", "hidden" ); + div.appendChild( input ).setAttribute( "t", "" ); + + if ( div.querySelectorAll("[t^='']").length ) { + rbuggyQSA.push( "[*^$]=" + whitespace + "*(?:''|\"\")" ); + } + + // FF 3.5 - :enabled/:disabled and hidden elements (hidden elements are still enabled) + // IE8 throws error here and will not see later tests + if ( !div.querySelectorAll(":enabled").length ) { + rbuggyQSA.push( ":enabled", ":disabled" ); + } + + // Opera 10-11 does not throw on post-comma invalid pseudos + div.querySelectorAll("*,:x"); + rbuggyQSA.push(",.*:"); + }); + } + + if ( (support.matchesSelector = isNative( (matches = docElem.webkitMatchesSelector || + docElem.mozMatchesSelector || + docElem.oMatchesSelector || + docElem.msMatchesSelector) )) ) { + + assert(function( div ) { + // Check to see if it's possible to do matchesSelector + // on a disconnected node (IE 9) + support.disconnectedMatch = matches.call( div, "div" ); + + // This should fail with an exception + // Gecko does not error, returns false instead + matches.call( div, "[s!='']:x" ); + rbuggyMatches.push( "!=", pseudos ); + }); + } + + rbuggyQSA = rbuggyQSA.length && new RegExp( rbuggyQSA.join("|") ); + rbuggyMatches = rbuggyMatches.length && new RegExp( rbuggyMatches.join("|") ); + + /* Contains + ---------------------------------------------------------------------- */ + + // Element contains another + // Purposefully does not implement inclusive descendent + // As in, an element does not contain itself + contains = isNative(docElem.contains) || docElem.compareDocumentPosition ? + function( a, b ) { + var adown = a.nodeType === 9 ? a.documentElement : a, + bup = b && b.parentNode; + return a === bup || !!( bup && bup.nodeType === 1 && ( + adown.contains ? + adown.contains( bup ) : + a.compareDocumentPosition && a.compareDocumentPosition( bup ) & 16 + )); + } : + function( a, b ) { + if ( b ) { + while ( (b = b.parentNode) ) { + if ( b === a ) { + return true; + } + } + } + return false; + }; + + /* Sorting + ---------------------------------------------------------------------- */ + + // Support: Webkit<537.32 - Safari 6.0.3/Chrome 25 (fixed in Chrome 27) + // Detached nodes confoundingly follow *each other* + support.sortDetached = assert(function( div1 ) { + // Should return 1, but returns 4 (following) + return div1.compareDocumentPosition( doc.createElement("div") ) & 1; + }); + + // Document order sorting + sortOrder = docElem.compareDocumentPosition ? + function( a, b ) { + + // Flag for duplicate removal + if ( a === b ) { + hasDuplicate = true; + return 0; + } + + var compare = b.compareDocumentPosition && a.compareDocumentPosition && a.compareDocumentPosition( b ); + + if ( compare ) { + // Disconnected nodes + if ( compare & 1 || + (!support.sortDetached && b.compareDocumentPosition( a ) === compare) ) { + + // Choose the first element that is related to our preferred document + if ( a === doc || contains(preferredDoc, a) ) { + return -1; + } + if ( b === doc || contains(preferredDoc, b) ) { + return 1; + } + + // Maintain original order + return sortInput ? + ( indexOf.call( sortInput, a ) - indexOf.call( sortInput, b ) ) : + 0; + } + + return compare & 4 ? -1 : 1; + } + + // Not directly comparable, sort on existence of method + return a.compareDocumentPosition ? -1 : 1; + } : + function( a, b ) { + var cur, + i = 0, + aup = a.parentNode, + bup = b.parentNode, + ap = [ a ], + bp = [ b ]; + + // Exit early if the nodes are identical + if ( a === b ) { + hasDuplicate = true; + return 0; + + // Parentless nodes are either documents or disconnected + } else if ( !aup || !bup ) { + return a === doc ? -1 : + b === doc ? 1 : + aup ? -1 : + bup ? 1 : + sortInput ? + ( indexOf.call( sortInput, a ) - indexOf.call( sortInput, b ) ) : + 0; + + // If the nodes are siblings, we can do a quick check + } else if ( aup === bup ) { + return siblingCheck( a, b ); + } + + // Otherwise we need full lists of their ancestors for comparison + cur = a; + while ( (cur = cur.parentNode) ) { + ap.unshift( cur ); + } + cur = b; + while ( (cur = cur.parentNode) ) { + bp.unshift( cur ); + } + + // Walk down the tree looking for a discrepancy + while ( ap[i] === bp[i] ) { + i++; + } + + return i ? + // Do a sibling check if the nodes have a common ancestor + siblingCheck( ap[i], bp[i] ) : + + // Otherwise nodes in our document sort first + ap[i] === preferredDoc ? -1 : + bp[i] === preferredDoc ? 1 : + 0; + }; + + return doc; +}; + +Sizzle.matches = function( expr, elements ) { + return Sizzle( expr, null, null, elements ); +}; + +Sizzle.matchesSelector = function( elem, expr ) { + // Set document vars if needed + if ( ( elem.ownerDocument || elem ) !== document ) { + setDocument( elem ); + } + + // Make sure that attribute selectors are quoted + expr = expr.replace( rattributeQuotes, "='$1']" ); + + if ( support.matchesSelector && documentIsHTML && + ( !rbuggyMatches || !rbuggyMatches.test( expr ) ) && + ( !rbuggyQSA || !rbuggyQSA.test( expr ) ) ) { + + try { + var ret = matches.call( elem, expr ); + + // IE 9's matchesSelector returns false on disconnected nodes + if ( ret || support.disconnectedMatch || + // As well, disconnected nodes are said to be in a document + // fragment in IE 9 + elem.document && elem.document.nodeType !== 11 ) { + return ret; + } + } catch(e) {} + } + + return Sizzle( expr, document, null, [elem] ).length > 0; +}; + +Sizzle.contains = function( context, elem ) { + // Set document vars if needed + if ( ( context.ownerDocument || context ) !== document ) { + setDocument( context ); + } + return contains( context, elem ); +}; + +Sizzle.attr = function( elem, name ) { + // Set document vars if needed + if ( ( elem.ownerDocument || elem ) !== document ) { + setDocument( elem ); + } + + var fn = Expr.attrHandle[ name.toLowerCase() ], + // Don't get fooled by Object.prototype properties (jQuery #13807) + val = ( fn && hasOwn.call( Expr.attrHandle, name.toLowerCase() ) ? + fn( elem, name, !documentIsHTML ) : + undefined ); + + return val === undefined ? + support.attributes || !documentIsHTML ? + elem.getAttribute( name ) : + (val = elem.getAttributeNode(name)) && val.specified ? + val.value : + null : + val; +}; + +Sizzle.error = function( msg ) { + throw new Error( "Syntax error, unrecognized expression: " + msg ); +}; + +/** + * Document sorting and removing duplicates + * @param {ArrayLike} results + */ +Sizzle.uniqueSort = function( results ) { + var elem, + duplicates = [], + j = 0, + i = 0; + + // Unless we *know* we can detect duplicates, assume their presence + hasDuplicate = !support.detectDuplicates; + sortInput = !support.sortStable && results.slice( 0 ); + results.sort( sortOrder ); + + if ( hasDuplicate ) { + while ( (elem = results[i++]) ) { + if ( elem === results[ i ] ) { + j = duplicates.push( i ); + } + } + while ( j-- ) { + results.splice( duplicates[ j ], 1 ); + } + } + + return results; +}; + +/** + * Utility function for retrieving the text value of an array of DOM nodes + * @param {Array|Element} elem + */ +getText = Sizzle.getText = function( elem ) { + var node, + ret = "", + i = 0, + nodeType = elem.nodeType; + + if ( !nodeType ) { + // If no nodeType, this is expected to be an array + for ( ; (node = elem[i]); i++ ) { + // Do not traverse comment nodes + ret += getText( node ); + } + } else if ( nodeType === 1 || nodeType === 9 || nodeType === 11 ) { + // Use textContent for elements + // innerText usage removed for consistency of new lines (see #11153) + if ( typeof elem.textContent === "string" ) { + return elem.textContent; + } else { + // Traverse its children + for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) { + ret += getText( elem ); + } + } + } else if ( nodeType === 3 || nodeType === 4 ) { + return elem.nodeValue; + } + // Do not include comment or processing instruction nodes + + return ret; +}; + +Expr = Sizzle.selectors = { + + // Can be adjusted by the user + cacheLength: 50, + + createPseudo: markFunction, + + match: matchExpr, + + attrHandle: {}, + + find: {}, + + relative: { + ">": { dir: "parentNode", first: true }, + " ": { dir: "parentNode" }, + "+": { dir: "previousSibling", first: true }, + "~": { dir: "previousSibling" } + }, + + preFilter: { + "ATTR": function( match ) { + match[1] = match[1].replace( runescape, funescape ); + + // Move the given value to match[3] whether quoted or unquoted + match[3] = ( match[4] || match[5] || "" ).replace( runescape, funescape ); + + if ( match[2] === "~=" ) { + match[3] = " " + match[3] + " "; + } + + return match.slice( 0, 4 ); + }, + + "CHILD": function( match ) { + /* matches from matchExpr["CHILD"] + 1 type (only|nth|...) + 2 what (child|of-type) + 3 argument (even|odd|\d*|\d*n([+-]\d+)?|...) + 4 xn-component of xn+y argument ([+-]?\d*n|) + 5 sign of xn-component + 6 x of xn-component + 7 sign of y-component + 8 y of y-component + */ + match[1] = match[1].toLowerCase(); + + if ( match[1].slice( 0, 3 ) === "nth" ) { + // nth-* requires argument + if ( !match[3] ) { + Sizzle.error( match[0] ); + } + + // numeric x and y parameters for Expr.filter.CHILD + // remember that false/true cast respectively to 0/1 + match[4] = +( match[4] ? match[5] + (match[6] || 1) : 2 * ( match[3] === "even" || match[3] === "odd" ) ); + match[5] = +( ( match[7] + match[8] ) || match[3] === "odd" ); + + // other types prohibit arguments + } else if ( match[3] ) { + Sizzle.error( match[0] ); + } + + return match; + }, + + "PSEUDO": function( match ) { + var excess, + unquoted = !match[5] && match[2]; + + if ( matchExpr["CHILD"].test( match[0] ) ) { + return null; + } + + // Accept quoted arguments as-is + if ( match[3] && match[4] !== undefined ) { + match[2] = match[4]; + + // Strip excess characters from unquoted arguments + } else if ( unquoted && rpseudo.test( unquoted ) && + // Get excess from tokenize (recursively) + (excess = tokenize( unquoted, true )) && + // advance to the next closing parenthesis + (excess = unquoted.indexOf( ")", unquoted.length - excess ) - unquoted.length) ) { + + // excess is a negative index + match[0] = match[0].slice( 0, excess ); + match[2] = unquoted.slice( 0, excess ); + } + + // Return only captures needed by the pseudo filter method (type and argument) + return match.slice( 0, 3 ); + } + }, + + filter: { + + "TAG": function( nodeNameSelector ) { + var nodeName = nodeNameSelector.replace( runescape, funescape ).toLowerCase(); + return nodeNameSelector === "*" ? + function() { return true; } : + function( elem ) { + return elem.nodeName && elem.nodeName.toLowerCase() === nodeName; + }; + }, + + "CLASS": function( className ) { + var pattern = classCache[ className + " " ]; + + return pattern || + (pattern = new RegExp( "(^|" + whitespace + ")" + className + "(" + whitespace + "|$)" )) && + classCache( className, function( elem ) { + return pattern.test( typeof elem.className === "string" && elem.className || typeof elem.getAttribute !== strundefined && elem.getAttribute("class") || "" ); + }); + }, + + "ATTR": function( name, operator, check ) { + return function( elem ) { + var result = Sizzle.attr( elem, name ); + + if ( result == null ) { + return operator === "!="; + } + if ( !operator ) { + return true; + } + + result += ""; + + return operator === "=" ? result === check : + operator === "!=" ? result !== check : + operator === "^=" ? check && result.indexOf( check ) === 0 : + operator === "*=" ? check && result.indexOf( check ) > -1 : + operator === "$=" ? check && result.slice( -check.length ) === check : + operator === "~=" ? ( " " + result + " " ).indexOf( check ) > -1 : + operator === "|=" ? result === check || result.slice( 0, check.length + 1 ) === check + "-" : + false; + }; + }, + + "CHILD": function( type, what, argument, first, last ) { + var simple = type.slice( 0, 3 ) !== "nth", + forward = type.slice( -4 ) !== "last", + ofType = what === "of-type"; + + return first === 1 && last === 0 ? + + // Shortcut for :nth-*(n) + function( elem ) { + return !!elem.parentNode; + } : + + function( elem, context, xml ) { + var cache, outerCache, node, diff, nodeIndex, start, + dir = simple !== forward ? "nextSibling" : "previousSibling", + parent = elem.parentNode, + name = ofType && elem.nodeName.toLowerCase(), + useCache = !xml && !ofType; + + if ( parent ) { + + // :(first|last|only)-(child|of-type) + if ( simple ) { + while ( dir ) { + node = elem; + while ( (node = node[ dir ]) ) { + if ( ofType ? node.nodeName.toLowerCase() === name : node.nodeType === 1 ) { + return false; + } + } + // Reverse direction for :only-* (if we haven't yet done so) + start = dir = type === "only" && !start && "nextSibling"; + } + return true; + } + + start = [ forward ? parent.firstChild : parent.lastChild ]; + + // non-xml :nth-child(...) stores cache data on `parent` + if ( forward && useCache ) { + // Seek `elem` from a previously-cached index + outerCache = parent[ expando ] || (parent[ expando ] = {}); + cache = outerCache[ type ] || []; + nodeIndex = cache[0] === dirruns && cache[1]; + diff = cache[0] === dirruns && cache[2]; + node = nodeIndex && parent.childNodes[ nodeIndex ]; + + while ( (node = ++nodeIndex && node && node[ dir ] || + + // Fallback to seeking `elem` from the start + (diff = nodeIndex = 0) || start.pop()) ) { + + // When found, cache indexes on `parent` and break + if ( node.nodeType === 1 && ++diff && node === elem ) { + outerCache[ type ] = [ dirruns, nodeIndex, diff ]; + break; + } + } + + // Use previously-cached element index if available + } else if ( useCache && (cache = (elem[ expando ] || (elem[ expando ] = {}))[ type ]) && cache[0] === dirruns ) { + diff = cache[1]; + + // xml :nth-child(...) or :nth-last-child(...) or :nth(-last)?-of-type(...) + } else { + // Use the same loop as above to seek `elem` from the start + while ( (node = ++nodeIndex && node && node[ dir ] || + (diff = nodeIndex = 0) || start.pop()) ) { + + if ( ( ofType ? node.nodeName.toLowerCase() === name : node.nodeType === 1 ) && ++diff ) { + // Cache the index of each encountered element + if ( useCache ) { + (node[ expando ] || (node[ expando ] = {}))[ type ] = [ dirruns, diff ]; + } + + if ( node === elem ) { + break; + } + } + } + } + + // Incorporate the offset, then check against cycle size + diff -= last; + return diff === first || ( diff % first === 0 && diff / first >= 0 ); + } + }; + }, + + "PSEUDO": function( pseudo, argument ) { + // pseudo-class names are case-insensitive + // http://www.w3.org/TR/selectors/#pseudo-classes + // Prioritize by case sensitivity in case custom pseudos are added with uppercase letters + // Remember that setFilters inherits from pseudos + var args, + fn = Expr.pseudos[ pseudo ] || Expr.setFilters[ pseudo.toLowerCase() ] || + Sizzle.error( "unsupported pseudo: " + pseudo ); + + // The user may use createPseudo to indicate that + // arguments are needed to create the filter function + // just as Sizzle does + if ( fn[ expando ] ) { + return fn( argument ); + } + + // But maintain support for old signatures + if ( fn.length > 1 ) { + args = [ pseudo, pseudo, "", argument ]; + return Expr.setFilters.hasOwnProperty( pseudo.toLowerCase() ) ? + markFunction(function( seed, matches ) { + var idx, + matched = fn( seed, argument ), + i = matched.length; + while ( i-- ) { + idx = indexOf.call( seed, matched[i] ); + seed[ idx ] = !( matches[ idx ] = matched[i] ); + } + }) : + function( elem ) { + return fn( elem, 0, args ); + }; + } + + return fn; + } + }, + + pseudos: { + // Potentially complex pseudos + "not": markFunction(function( selector ) { + // Trim the selector passed to compile + // to avoid treating leading and trailing + // spaces as combinators + var input = [], + results = [], + matcher = compile( selector.replace( rtrim, "$1" ) ); + + return matcher[ expando ] ? + markFunction(function( seed, matches, context, xml ) { + var elem, + unmatched = matcher( seed, null, xml, [] ), + i = seed.length; + + // Match elements unmatched by `matcher` + while ( i-- ) { + if ( (elem = unmatched[i]) ) { + seed[i] = !(matches[i] = elem); + } + } + }) : + function( elem, context, xml ) { + input[0] = elem; + matcher( input, null, xml, results ); + return !results.pop(); + }; + }), + + "has": markFunction(function( selector ) { + return function( elem ) { + return Sizzle( selector, elem ).length > 0; + }; + }), + + "contains": markFunction(function( text ) { + return function( elem ) { + return ( elem.textContent || elem.innerText || getText( elem ) ).indexOf( text ) > -1; + }; + }), + + // "Whether an element is represented by a :lang() selector + // is based solely on the element's language value + // being equal to the identifier C, + // or beginning with the identifier C immediately followed by "-". + // The matching of C against the element's language value is performed case-insensitively. + // The identifier C does not have to be a valid language name." + // http://www.w3.org/TR/selectors/#lang-pseudo + "lang": markFunction( function( lang ) { + // lang value must be a valid identifier + if ( !ridentifier.test(lang || "") ) { + Sizzle.error( "unsupported lang: " + lang ); + } + lang = lang.replace( runescape, funescape ).toLowerCase(); + return function( elem ) { + var elemLang; + do { + if ( (elemLang = documentIsHTML ? + elem.lang : + elem.getAttribute("xml:lang") || elem.getAttribute("lang")) ) { + + elemLang = elemLang.toLowerCase(); + return elemLang === lang || elemLang.indexOf( lang + "-" ) === 0; + } + } while ( (elem = elem.parentNode) && elem.nodeType === 1 ); + return false; + }; + }), + + // Miscellaneous + "target": function( elem ) { + var hash = window.location && window.location.hash; + return hash && hash.slice( 1 ) === elem.id; + }, + + "root": function( elem ) { + return elem === docElem; + }, + + "focus": function( elem ) { + return elem === document.activeElement && (!document.hasFocus || document.hasFocus()) && !!(elem.type || elem.href || ~elem.tabIndex); + }, + + // Boolean properties + "enabled": function( elem ) { + return elem.disabled === false; + }, + + "disabled": function( elem ) { + return elem.disabled === true; + }, + + "checked": function( elem ) { + // In CSS3, :checked should return both checked and selected elements + // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked + var nodeName = elem.nodeName.toLowerCase(); + return (nodeName === "input" && !!elem.checked) || (nodeName === "option" && !!elem.selected); + }, + + "selected": function( elem ) { + // Accessing this property makes selected-by-default + // options in Safari work properly + if ( elem.parentNode ) { + elem.parentNode.selectedIndex; + } + + return elem.selected === true; + }, + + // Contents + "empty": function( elem ) { + // http://www.w3.org/TR/selectors/#empty-pseudo + // :empty is only affected by element nodes and content nodes(including text(3), cdata(4)), + // not comment, processing instructions, or others + // Thanks to Diego Perini for the nodeName shortcut + // Greater than "@" means alpha characters (specifically not starting with "#" or "?") + for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) { + if ( elem.nodeName > "@" || elem.nodeType === 3 || elem.nodeType === 4 ) { + return false; + } + } + return true; + }, + + "parent": function( elem ) { + return !Expr.pseudos["empty"]( elem ); + }, + + // Element/input types + "header": function( elem ) { + return rheader.test( elem.nodeName ); + }, + + "input": function( elem ) { + return rinputs.test( elem.nodeName ); + }, + + "button": function( elem ) { + var name = elem.nodeName.toLowerCase(); + return name === "input" && elem.type === "button" || name === "button"; + }, + + "text": function( elem ) { + var attr; + // IE6 and 7 will map elem.type to 'text' for new HTML5 types (search, etc) + // use getAttribute instead to test this case + return elem.nodeName.toLowerCase() === "input" && + elem.type === "text" && + ( (attr = elem.getAttribute("type")) == null || attr.toLowerCase() === elem.type ); + }, + + // Position-in-collection + "first": createPositionalPseudo(function() { + return [ 0 ]; + }), + + "last": createPositionalPseudo(function( matchIndexes, length ) { + return [ length - 1 ]; + }), + + "eq": createPositionalPseudo(function( matchIndexes, length, argument ) { + return [ argument < 0 ? argument + length : argument ]; + }), + + "even": createPositionalPseudo(function( matchIndexes, length ) { + var i = 0; + for ( ; i < length; i += 2 ) { + matchIndexes.push( i ); + } + return matchIndexes; + }), + + "odd": createPositionalPseudo(function( matchIndexes, length ) { + var i = 1; + for ( ; i < length; i += 2 ) { + matchIndexes.push( i ); + } + return matchIndexes; + }), + + "lt": createPositionalPseudo(function( matchIndexes, length, argument ) { + var i = argument < 0 ? argument + length : argument; + for ( ; --i >= 0; ) { + matchIndexes.push( i ); + } + return matchIndexes; + }), + + "gt": createPositionalPseudo(function( matchIndexes, length, argument ) { + var i = argument < 0 ? argument + length : argument; + for ( ; ++i < length; ) { + matchIndexes.push( i ); + } + return matchIndexes; + }) + } +}; + +// Add button/input type pseudos +for ( i in { radio: true, checkbox: true, file: true, password: true, image: true } ) { + Expr.pseudos[ i ] = createInputPseudo( i ); +} +for ( i in { submit: true, reset: true } ) { + Expr.pseudos[ i ] = createButtonPseudo( i ); +} + +function tokenize( selector, parseOnly ) { + var matched, match, tokens, type, + soFar, groups, preFilters, + cached = tokenCache[ selector + " " ]; + + if ( cached ) { + return parseOnly ? 0 : cached.slice( 0 ); + } + + soFar = selector; + groups = []; + preFilters = Expr.preFilter; + + while ( soFar ) { + + // Comma and first run + if ( !matched || (match = rcomma.exec( soFar )) ) { + if ( match ) { + // Don't consume trailing commas as valid + soFar = soFar.slice( match[0].length ) || soFar; + } + groups.push( tokens = [] ); + } + + matched = false; + + // Combinators + if ( (match = rcombinators.exec( soFar )) ) { + matched = match.shift(); + tokens.push({ + value: matched, + // Cast descendant combinators to space + type: match[0].replace( rtrim, " " ) + }); + soFar = soFar.slice( matched.length ); + } + + // Filters + for ( type in Expr.filter ) { + if ( (match = matchExpr[ type ].exec( soFar )) && (!preFilters[ type ] || + (match = preFilters[ type ]( match ))) ) { + matched = match.shift(); + tokens.push({ + value: matched, + type: type, + matches: match + }); + soFar = soFar.slice( matched.length ); + } + } + + if ( !matched ) { + break; + } + } + + // Return the length of the invalid excess + // if we're just parsing + // Otherwise, throw an error or return tokens + return parseOnly ? + soFar.length : + soFar ? + Sizzle.error( selector ) : + // Cache the tokens + tokenCache( selector, groups ).slice( 0 ); +} + +function toSelector( tokens ) { + var i = 0, + len = tokens.length, + selector = ""; + for ( ; i < len; i++ ) { + selector += tokens[i].value; + } + return selector; +} + +function addCombinator( matcher, combinator, base ) { + var dir = combinator.dir, + checkNonElements = base && dir === "parentNode", + doneName = done++; + + return combinator.first ? + // Check against closest ancestor/preceding element + function( elem, context, xml ) { + while ( (elem = elem[ dir ]) ) { + if ( elem.nodeType === 1 || checkNonElements ) { + return matcher( elem, context, xml ); + } + } + } : + + // Check against all ancestor/preceding elements + function( elem, context, xml ) { + var data, cache, outerCache, + dirkey = dirruns + " " + doneName; + + // We can't set arbitrary data on XML nodes, so they don't benefit from dir caching + if ( xml ) { + while ( (elem = elem[ dir ]) ) { + if ( elem.nodeType === 1 || checkNonElements ) { + if ( matcher( elem, context, xml ) ) { + return true; + } + } + } + } else { + while ( (elem = elem[ dir ]) ) { + if ( elem.nodeType === 1 || checkNonElements ) { + outerCache = elem[ expando ] || (elem[ expando ] = {}); + if ( (cache = outerCache[ dir ]) && cache[0] === dirkey ) { + if ( (data = cache[1]) === true || data === cachedruns ) { + return data === true; + } + } else { + cache = outerCache[ dir ] = [ dirkey ]; + cache[1] = matcher( elem, context, xml ) || cachedruns; + if ( cache[1] === true ) { + return true; + } + } + } + } + } + }; +} + +function elementMatcher( matchers ) { + return matchers.length > 1 ? + function( elem, context, xml ) { + var i = matchers.length; + while ( i-- ) { + if ( !matchers[i]( elem, context, xml ) ) { + return false; + } + } + return true; + } : + matchers[0]; +} + +function condense( unmatched, map, filter, context, xml ) { + var elem, + newUnmatched = [], + i = 0, + len = unmatched.length, + mapped = map != null; + + for ( ; i < len; i++ ) { + if ( (elem = unmatched[i]) ) { + if ( !filter || filter( elem, context, xml ) ) { + newUnmatched.push( elem ); + if ( mapped ) { + map.push( i ); + } + } + } + } + + return newUnmatched; +} + +function setMatcher( preFilter, selector, matcher, postFilter, postFinder, postSelector ) { + if ( postFilter && !postFilter[ expando ] ) { + postFilter = setMatcher( postFilter ); + } + if ( postFinder && !postFinder[ expando ] ) { + postFinder = setMatcher( postFinder, postSelector ); + } + return markFunction(function( seed, results, context, xml ) { + var temp, i, elem, + preMap = [], + postMap = [], + preexisting = results.length, + + // Get initial elements from seed or context + elems = seed || multipleContexts( selector || "*", context.nodeType ? [ context ] : context, [] ), + + // Prefilter to get matcher input, preserving a map for seed-results synchronization + matcherIn = preFilter && ( seed || !selector ) ? + condense( elems, preMap, preFilter, context, xml ) : + elems, + + matcherOut = matcher ? + // If we have a postFinder, or filtered seed, or non-seed postFilter or preexisting results, + postFinder || ( seed ? preFilter : preexisting || postFilter ) ? + + // ...intermediate processing is necessary + [] : + + // ...otherwise use results directly + results : + matcherIn; + + // Find primary matches + if ( matcher ) { + matcher( matcherIn, matcherOut, context, xml ); + } + + // Apply postFilter + if ( postFilter ) { + temp = condense( matcherOut, postMap ); + postFilter( temp, [], context, xml ); + + // Un-match failing elements by moving them back to matcherIn + i = temp.length; + while ( i-- ) { + if ( (elem = temp[i]) ) { + matcherOut[ postMap[i] ] = !(matcherIn[ postMap[i] ] = elem); + } + } + } + + if ( seed ) { + if ( postFinder || preFilter ) { + if ( postFinder ) { + // Get the final matcherOut by condensing this intermediate into postFinder contexts + temp = []; + i = matcherOut.length; + while ( i-- ) { + if ( (elem = matcherOut[i]) ) { + // Restore matcherIn since elem is not yet a final match + temp.push( (matcherIn[i] = elem) ); + } + } + postFinder( null, (matcherOut = []), temp, xml ); + } + + // Move matched elements from seed to results to keep them synchronized + i = matcherOut.length; + while ( i-- ) { + if ( (elem = matcherOut[i]) && + (temp = postFinder ? indexOf.call( seed, elem ) : preMap[i]) > -1 ) { + + seed[temp] = !(results[temp] = elem); + } + } + } + + // Add elements to results, through postFinder if defined + } else { + matcherOut = condense( + matcherOut === results ? + matcherOut.splice( preexisting, matcherOut.length ) : + matcherOut + ); + if ( postFinder ) { + postFinder( null, results, matcherOut, xml ); + } else { + push.apply( results, matcherOut ); + } + } + }); +} + +function matcherFromTokens( tokens ) { + var checkContext, matcher, j, + len = tokens.length, + leadingRelative = Expr.relative[ tokens[0].type ], + implicitRelative = leadingRelative || Expr.relative[" "], + i = leadingRelative ? 1 : 0, + + // The foundational matcher ensures that elements are reachable from top-level context(s) + matchContext = addCombinator( function( elem ) { + return elem === checkContext; + }, implicitRelative, true ), + matchAnyContext = addCombinator( function( elem ) { + return indexOf.call( checkContext, elem ) > -1; + }, implicitRelative, true ), + matchers = [ function( elem, context, xml ) { + return ( !leadingRelative && ( xml || context !== outermostContext ) ) || ( + (checkContext = context).nodeType ? + matchContext( elem, context, xml ) : + matchAnyContext( elem, context, xml ) ); + } ]; + + for ( ; i < len; i++ ) { + if ( (matcher = Expr.relative[ tokens[i].type ]) ) { + matchers = [ addCombinator(elementMatcher( matchers ), matcher) ]; + } else { + matcher = Expr.filter[ tokens[i].type ].apply( null, tokens[i].matches ); + + // Return special upon seeing a positional matcher + if ( matcher[ expando ] ) { + // Find the next relative operator (if any) for proper handling + j = ++i; + for ( ; j < len; j++ ) { + if ( Expr.relative[ tokens[j].type ] ) { + break; + } + } + return setMatcher( + i > 1 && elementMatcher( matchers ), + i > 1 && toSelector( + // If the preceding token was a descendant combinator, insert an implicit any-element `*` + tokens.slice( 0, i - 1 ).concat({ value: tokens[ i - 2 ].type === " " ? "*" : "" }) + ).replace( rtrim, "$1" ), + matcher, + i < j && matcherFromTokens( tokens.slice( i, j ) ), + j < len && matcherFromTokens( (tokens = tokens.slice( j )) ), + j < len && toSelector( tokens ) + ); + } + matchers.push( matcher ); + } + } + + return elementMatcher( matchers ); +} + +function matcherFromGroupMatchers( elementMatchers, setMatchers ) { + // A counter to specify which element is currently being matched + var matcherCachedRuns = 0, + bySet = setMatchers.length > 0, + byElement = elementMatchers.length > 0, + superMatcher = function( seed, context, xml, results, expandContext ) { + var elem, j, matcher, + setMatched = [], + matchedCount = 0, + i = "0", + unmatched = seed && [], + outermost = expandContext != null, + contextBackup = outermostContext, + // We must always have either seed elements or context + elems = seed || byElement && Expr.find["TAG"]( "*", expandContext && context.parentNode || context ), + // Use integer dirruns iff this is the outermost matcher + dirrunsUnique = (dirruns += contextBackup == null ? 1 : Math.random() || 0.1); + + if ( outermost ) { + outermostContext = context !== document && context; + cachedruns = matcherCachedRuns; + } + + // Add elements passing elementMatchers directly to results + // Keep `i` a string if there are no elements so `matchedCount` will be "00" below + for ( ; (elem = elems[i]) != null; i++ ) { + if ( byElement && elem ) { + j = 0; + while ( (matcher = elementMatchers[j++]) ) { + if ( matcher( elem, context, xml ) ) { + results.push( elem ); + break; + } + } + if ( outermost ) { + dirruns = dirrunsUnique; + cachedruns = ++matcherCachedRuns; + } + } + + // Track unmatched elements for set filters + if ( bySet ) { + // They will have gone through all possible matchers + if ( (elem = !matcher && elem) ) { + matchedCount--; + } + + // Lengthen the array for every element, matched or not + if ( seed ) { + unmatched.push( elem ); + } + } + } + + // Apply set filters to unmatched elements + matchedCount += i; + if ( bySet && i !== matchedCount ) { + j = 0; + while ( (matcher = setMatchers[j++]) ) { + matcher( unmatched, setMatched, context, xml ); + } + + if ( seed ) { + // Reintegrate element matches to eliminate the need for sorting + if ( matchedCount > 0 ) { + while ( i-- ) { + if ( !(unmatched[i] || setMatched[i]) ) { + setMatched[i] = pop.call( results ); + } + } + } + + // Discard index placeholder values to get only actual matches + setMatched = condense( setMatched ); + } + + // Add matches to results + push.apply( results, setMatched ); + + // Seedless set matches succeeding multiple successful matchers stipulate sorting + if ( outermost && !seed && setMatched.length > 0 && + ( matchedCount + setMatchers.length ) > 1 ) { + + Sizzle.uniqueSort( results ); + } + } + + // Override manipulation of globals by nested matchers + if ( outermost ) { + dirruns = dirrunsUnique; + outermostContext = contextBackup; + } + + return unmatched; + }; + + return bySet ? + markFunction( superMatcher ) : + superMatcher; +} + +compile = Sizzle.compile = function( selector, group /* Internal Use Only */ ) { + var i, + setMatchers = [], + elementMatchers = [], + cached = compilerCache[ selector + " " ]; + + if ( !cached ) { + // Generate a function of recursive functions that can be used to check each element + if ( !group ) { + group = tokenize( selector ); + } + i = group.length; + while ( i-- ) { + cached = matcherFromTokens( group[i] ); + if ( cached[ expando ] ) { + setMatchers.push( cached ); + } else { + elementMatchers.push( cached ); + } + } + + // Cache the compiled function + cached = compilerCache( selector, matcherFromGroupMatchers( elementMatchers, setMatchers ) ); + } + return cached; +}; + +function multipleContexts( selector, contexts, results ) { + var i = 0, + len = contexts.length; + for ( ; i < len; i++ ) { + Sizzle( selector, contexts[i], results ); + } + return results; +} + +function select( selector, context, results, seed ) { + var i, tokens, token, type, find, + match = tokenize( selector ); + + if ( !seed ) { + // Try to minimize operations if there is only one group + if ( match.length === 1 ) { + + // Take a shortcut and set the context if the root selector is an ID + tokens = match[0] = match[0].slice( 0 ); + if ( tokens.length > 2 && (token = tokens[0]).type === "ID" && + support.getById && context.nodeType === 9 && documentIsHTML && + Expr.relative[ tokens[1].type ] ) { + + context = ( Expr.find["ID"]( token.matches[0].replace(runescape, funescape), context ) || [] )[0]; + if ( !context ) { + return results; + } + selector = selector.slice( tokens.shift().value.length ); + } + + // Fetch a seed set for right-to-left matching + i = matchExpr["needsContext"].test( selector ) ? 0 : tokens.length; + while ( i-- ) { + token = tokens[i]; + + // Abort if we hit a combinator + if ( Expr.relative[ (type = token.type) ] ) { + break; + } + if ( (find = Expr.find[ type ]) ) { + // Search, expanding context for leading sibling combinators + if ( (seed = find( + token.matches[0].replace( runescape, funescape ), + rsibling.test( tokens[0].type ) && context.parentNode || context + )) ) { + + // If seed is empty or no tokens remain, we can return early + tokens.splice( i, 1 ); + selector = seed.length && toSelector( tokens ); + if ( !selector ) { + push.apply( results, seed ); + return results; + } + + break; + } + } + } + } + } + + // Compile and execute a filtering function + // Provide `match` to avoid retokenization if we modified the selector above + compile( selector, match )( + seed, + context, + !documentIsHTML, + results, + rsibling.test( selector ) + ); + return results; +} + +// Deprecated +Expr.pseudos["nth"] = Expr.pseudos["eq"]; + +// Easy API for creating new setFilters +function setFilters() {} +setFilters.prototype = Expr.filters = Expr.pseudos; +Expr.setFilters = new setFilters(); + +// One-time assignments + +// Sort stability +support.sortStable = expando.split("").sort( sortOrder ).join("") === expando; + +// Initialize against the default document +setDocument(); + +// Support: Chrome<<14 +// Always assume duplicates if they aren't passed to the comparison function +[0, 0].sort( sortOrder ); +support.detectDuplicates = hasDuplicate; + +jQuery.find = Sizzle; +jQuery.expr = Sizzle.selectors; +jQuery.expr[":"] = jQuery.expr.pseudos; +jQuery.unique = Sizzle.uniqueSort; +jQuery.text = Sizzle.getText; +jQuery.isXMLDoc = Sizzle.isXML; +jQuery.contains = Sizzle.contains; + + +})( window ); +// String to Object options format cache +var optionsCache = {}; + +// Convert String-formatted options into Object-formatted ones and store in cache +function createOptions( options ) { + var object = optionsCache[ options ] = {}; + jQuery.each( options.match( core_rnotwhite ) || [], function( _, flag ) { + object[ flag ] = true; + }); + return object; +} + +/* + * Create a callback list using the following parameters: + * + * options: an optional list of space-separated options that will change how + * the callback list behaves or a more traditional option object + * + * By default a callback list will act like an event callback list and can be + * "fired" multiple times. + * + * Possible options: + * + * once: will ensure the callback list can only be fired once (like a Deferred) + * + * memory: will keep track of previous values and will call any callback added + * after the list has been fired right away with the latest "memorized" + * values (like a Deferred) + * + * unique: will ensure a callback can only be added once (no duplicate in the list) + * + * stopOnFalse: interrupt callings when a callback returns false + * + */ +jQuery.Callbacks = function( options ) { + + // Convert options from String-formatted to Object-formatted if needed + // (we check in cache first) + options = typeof options === "string" ? + ( optionsCache[ options ] || createOptions( options ) ) : + jQuery.extend( {}, options ); + + var // Flag to know if list is currently firing + firing, + // Last fire value (for non-forgettable lists) + memory, + // Flag to know if list was already fired + fired, + // End of the loop when firing + firingLength, + // Index of currently firing callback (modified by remove if needed) + firingIndex, + // First callback to fire (used internally by add and fireWith) + firingStart, + // Actual callback list + list = [], + // Stack of fire calls for repeatable lists + stack = !options.once && [], + // Fire callbacks + fire = function( data ) { + memory = options.memory && data; + fired = true; + firingIndex = firingStart || 0; + firingStart = 0; + firingLength = list.length; + firing = true; + for ( ; list && firingIndex < firingLength; firingIndex++ ) { + if ( list[ firingIndex ].apply( data[ 0 ], data[ 1 ] ) === false && options.stopOnFalse ) { + memory = false; // To prevent further calls using add + break; + } + } + firing = false; + if ( list ) { + if ( stack ) { + if ( stack.length ) { + fire( stack.shift() ); + } + } else if ( memory ) { + list = []; + } else { + self.disable(); + } + } + }, + // Actual Callbacks object + self = { + // Add a callback or a collection of callbacks to the list + add: function() { + if ( list ) { + // First, we save the current length + var start = list.length; + (function add( args ) { + jQuery.each( args, function( _, arg ) { + var type = jQuery.type( arg ); + if ( type === "function" ) { + if ( !options.unique || !self.has( arg ) ) { + list.push( arg ); + } + } else if ( arg && arg.length && type !== "string" ) { + // Inspect recursively + add( arg ); + } + }); + })( arguments ); + // Do we need to add the callbacks to the + // current firing batch? + if ( firing ) { + firingLength = list.length; + // With memory, if we're not firing then + // we should call right away + } else if ( memory ) { + firingStart = start; + fire( memory ); + } + } + return this; + }, + // Remove a callback from the list + remove: function() { + if ( list ) { + jQuery.each( arguments, function( _, arg ) { + var index; + while( ( index = jQuery.inArray( arg, list, index ) ) > -1 ) { + list.splice( index, 1 ); + // Handle firing indexes + if ( firing ) { + if ( index <= firingLength ) { + firingLength--; + } + if ( index <= firingIndex ) { + firingIndex--; + } + } + } + }); + } + return this; + }, + // Check if a given callback is in the list. + // If no argument is given, return whether or not list has callbacks attached. + has: function( fn ) { + return fn ? jQuery.inArray( fn, list ) > -1 : !!( list && list.length ); + }, + // Remove all callbacks from the list + empty: function() { + list = []; + firingLength = 0; + return this; + }, + // Have the list do nothing anymore + disable: function() { + list = stack = memory = undefined; + return this; + }, + // Is it disabled? + disabled: function() { + return !list; + }, + // Lock the list in its current state + lock: function() { + stack = undefined; + if ( !memory ) { + self.disable(); + } + return this; + }, + // Is it locked? + locked: function() { + return !stack; + }, + // Call all callbacks with the given context and arguments + fireWith: function( context, args ) { + args = args || []; + args = [ context, args.slice ? args.slice() : args ]; + if ( list && ( !fired || stack ) ) { + if ( firing ) { + stack.push( args ); + } else { + fire( args ); + } + } + return this; + }, + // Call all the callbacks with the given arguments + fire: function() { + self.fireWith( this, arguments ); + return this; + }, + // To know if the callbacks have already been called at least once + fired: function() { + return !!fired; + } + }; + + return self; +}; +jQuery.extend({ + + Deferred: function( func ) { + var tuples = [ + // action, add listener, listener list, final state + [ "resolve", "done", jQuery.Callbacks("once memory"), "resolved" ], + [ "reject", "fail", jQuery.Callbacks("once memory"), "rejected" ], + [ "notify", "progress", jQuery.Callbacks("memory") ] + ], + state = "pending", + promise = { + state: function() { + return state; + }, + always: function() { + deferred.done( arguments ).fail( arguments ); + return this; + }, + then: function( /* fnDone, fnFail, fnProgress */ ) { + var fns = arguments; + return jQuery.Deferred(function( newDefer ) { + jQuery.each( tuples, function( i, tuple ) { + var action = tuple[ 0 ], + fn = jQuery.isFunction( fns[ i ] ) && fns[ i ]; + // deferred[ done | fail | progress ] for forwarding actions to newDefer + deferred[ tuple[1] ](function() { + var returned = fn && fn.apply( this, arguments ); + if ( returned && jQuery.isFunction( returned.promise ) ) { + returned.promise() + .done( newDefer.resolve ) + .fail( newDefer.reject ) + .progress( newDefer.notify ); + } else { + newDefer[ action + "With" ]( this === promise ? newDefer.promise() : this, fn ? [ returned ] : arguments ); + } + }); + }); + fns = null; + }).promise(); + }, + // Get a promise for this deferred + // If obj is provided, the promise aspect is added to the object + promise: function( obj ) { + return obj != null ? jQuery.extend( obj, promise ) : promise; + } + }, + deferred = {}; + + // Keep pipe for back-compat + promise.pipe = promise.then; + + // Add list-specific methods + jQuery.each( tuples, function( i, tuple ) { + var list = tuple[ 2 ], + stateString = tuple[ 3 ]; + + // promise[ done | fail | progress ] = list.add + promise[ tuple[1] ] = list.add; + + // Handle state + if ( stateString ) { + list.add(function() { + // state = [ resolved | rejected ] + state = stateString; + + // [ reject_list | resolve_list ].disable; progress_list.lock + }, tuples[ i ^ 1 ][ 2 ].disable, tuples[ 2 ][ 2 ].lock ); + } + + // deferred[ resolve | reject | notify ] + deferred[ tuple[0] ] = function() { + deferred[ tuple[0] + "With" ]( this === deferred ? promise : this, arguments ); + return this; + }; + deferred[ tuple[0] + "With" ] = list.fireWith; + }); + + // Make the deferred a promise + promise.promise( deferred ); + + // Call given func if any + if ( func ) { + func.call( deferred, deferred ); + } + + // All done! + return deferred; + }, + + // Deferred helper + when: function( subordinate /* , ..., subordinateN */ ) { + var i = 0, + resolveValues = core_slice.call( arguments ), + length = resolveValues.length, + + // the count of uncompleted subordinates + remaining = length !== 1 || ( subordinate && jQuery.isFunction( subordinate.promise ) ) ? length : 0, + + // the master Deferred. If resolveValues consist of only a single Deferred, just use that. + deferred = remaining === 1 ? subordinate : jQuery.Deferred(), + + // Update function for both resolve and progress values + updateFunc = function( i, contexts, values ) { + return function( value ) { + contexts[ i ] = this; + values[ i ] = arguments.length > 1 ? core_slice.call( arguments ) : value; + if( values === progressValues ) { + deferred.notifyWith( contexts, values ); + } else if ( !( --remaining ) ) { + deferred.resolveWith( contexts, values ); + } + }; + }, + + progressValues, progressContexts, resolveContexts; + + // add listeners to Deferred subordinates; treat others as resolved + if ( length > 1 ) { + progressValues = new Array( length ); + progressContexts = new Array( length ); + resolveContexts = new Array( length ); + for ( ; i < length; i++ ) { + if ( resolveValues[ i ] && jQuery.isFunction( resolveValues[ i ].promise ) ) { + resolveValues[ i ].promise() + .done( updateFunc( i, resolveContexts, resolveValues ) ) + .fail( deferred.reject ) + .progress( updateFunc( i, progressContexts, progressValues ) ); + } else { + --remaining; + } + } + } + + // if we're not waiting on anything, resolve the master + if ( !remaining ) { + deferred.resolveWith( resolveContexts, resolveValues ); + } + + return deferred.promise(); + } +}); +jQuery.support = (function( support ) { + + var all, a, input, select, fragment, opt, eventName, isSupported, i, + div = document.createElement("div"); + + // Setup + div.setAttribute( "className", "t" ); + div.innerHTML = "
a"; + + // Finish early in limited (non-browser) environments + all = div.getElementsByTagName("*") || []; + a = div.getElementsByTagName("a")[ 0 ]; + if ( !a || !a.style || !all.length ) { + return support; + } + + // First batch of tests + select = document.createElement("select"); + opt = select.appendChild( document.createElement("option") ); + input = div.getElementsByTagName("input")[ 0 ]; + + a.style.cssText = "top:1px;float:left;opacity:.5"; + + // Test setAttribute on camelCase class. If it works, we need attrFixes when doing get/setAttribute (ie6/7) + support.getSetAttribute = div.className !== "t"; + + // IE strips leading whitespace when .innerHTML is used + support.leadingWhitespace = div.firstChild.nodeType === 3; + + // Make sure that tbody elements aren't automatically inserted + // IE will insert them into empty tables + support.tbody = !div.getElementsByTagName("tbody").length; + + // Make sure that link elements get serialized correctly by innerHTML + // This requires a wrapper element in IE + support.htmlSerialize = !!div.getElementsByTagName("link").length; + + // Get the style information from getAttribute + // (IE uses .cssText instead) + support.style = /top/.test( a.getAttribute("style") ); + + // Make sure that URLs aren't manipulated + // (IE normalizes it by default) + support.hrefNormalized = a.getAttribute("href") === "/a"; + + // Make sure that element opacity exists + // (IE uses filter instead) + // Use a regex to work around a WebKit issue. See #5145 + support.opacity = /^0.5/.test( a.style.opacity ); + + // Verify style float existence + // (IE uses styleFloat instead of cssFloat) + support.cssFloat = !!a.style.cssFloat; + + // Check the default checkbox/radio value ("" on WebKit; "on" elsewhere) + support.checkOn = !!input.value; + + // Make sure that a selected-by-default option has a working selected property. + // (WebKit defaults to false instead of true, IE too, if it's in an optgroup) + support.optSelected = opt.selected; + + // Tests for enctype support on a form (#6743) + support.enctype = !!document.createElement("form").enctype; + + // Makes sure cloning an html5 element does not cause problems + // Where outerHTML is undefined, this still works + support.html5Clone = document.createElement("nav").cloneNode( true ).outerHTML !== "<:nav>"; + + // Will be defined later + support.inlineBlockNeedsLayout = false; + support.shrinkWrapBlocks = false; + support.pixelPosition = false; + support.deleteExpando = true; + support.noCloneEvent = true; + support.reliableMarginRight = true; + support.boxSizingReliable = true; + + // Make sure checked status is properly cloned + input.checked = true; + support.noCloneChecked = input.cloneNode( true ).checked; + + // Make sure that the options inside disabled selects aren't marked as disabled + // (WebKit marks them as disabled) + select.disabled = true; + support.optDisabled = !opt.disabled; + + // Support: IE<9 + try { + delete div.test; + } catch( e ) { + support.deleteExpando = false; + } + + // Check if we can trust getAttribute("value") + input = document.createElement("input"); + input.setAttribute( "value", "" ); + support.input = input.getAttribute( "value" ) === ""; + + // Check if an input maintains its value after becoming a radio + input.value = "t"; + input.setAttribute( "type", "radio" ); + support.radioValue = input.value === "t"; + + // #11217 - WebKit loses check when the name is after the checked attribute + input.setAttribute( "checked", "t" ); + input.setAttribute( "name", "t" ); + + fragment = document.createDocumentFragment(); + fragment.appendChild( input ); + + // Check if a disconnected checkbox will retain its checked + // value of true after appended to the DOM (IE6/7) + support.appendChecked = input.checked; + + // WebKit doesn't clone checked state correctly in fragments + support.checkClone = fragment.cloneNode( true ).cloneNode( true ).lastChild.checked; + + // Support: IE<9 + // Opera does not clone events (and typeof div.attachEvent === undefined). + // IE9-10 clones events bound via attachEvent, but they don't trigger with .click() + if ( div.attachEvent ) { + div.attachEvent( "onclick", function() { + support.noCloneEvent = false; + }); + + div.cloneNode( true ).click(); + } + + // Support: IE<9 (lack submit/change bubble), Firefox 17+ (lack focusin event) + // Beware of CSP restrictions (https://developer.mozilla.org/en/Security/CSP) + for ( i in { submit: true, change: true, focusin: true }) { + div.setAttribute( eventName = "on" + i, "t" ); + + support[ i + "Bubbles" ] = eventName in window || div.attributes[ eventName ].expando === false; + } + + div.style.backgroundClip = "content-box"; + div.cloneNode( true ).style.backgroundClip = ""; + support.clearCloneStyle = div.style.backgroundClip === "content-box"; + + // Support: IE<9 + // Iteration over object's inherited properties before its own. + for ( i in jQuery( support ) ) { + break; + } + support.ownLast = i !== "0"; + + // Run tests that need a body at doc ready + jQuery(function() { + var container, marginDiv, tds, + divReset = "padding:0;margin:0;border:0;display:block;box-sizing:content-box;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;", + body = document.getElementsByTagName("body")[0]; + + if ( !body ) { + // Return for frameset docs that don't have a body + return; + } + + container = document.createElement("div"); + container.style.cssText = "border:0;width:0;height:0;position:absolute;top:0;left:-9999px;margin-top:1px"; + + body.appendChild( container ).appendChild( div ); + + // Support: IE8 + // Check if table cells still have offsetWidth/Height when they are set + // to display:none and there are still other visible table cells in a + // table row; if so, offsetWidth/Height are not reliable for use when + // determining if an element has been hidden directly using + // display:none (it is still safe to use offsets if a parent element is + // hidden; don safety goggles and see bug #4512 for more information). + div.innerHTML = "
t
"; + tds = div.getElementsByTagName("td"); + tds[ 0 ].style.cssText = "padding:0;margin:0;border:0;display:none"; + isSupported = ( tds[ 0 ].offsetHeight === 0 ); + + tds[ 0 ].style.display = ""; + tds[ 1 ].style.display = "none"; + + // Support: IE8 + // Check if empty table cells still have offsetWidth/Height + support.reliableHiddenOffsets = isSupported && ( tds[ 0 ].offsetHeight === 0 ); + + // Check box-sizing and margin behavior. + div.innerHTML = ""; + div.style.cssText = "box-sizing:border-box;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;padding:1px;border:1px;display:block;width:4px;margin-top:1%;position:absolute;top:1%;"; + + // Workaround failing boxSizing test due to offsetWidth returning wrong value + // with some non-1 values of body zoom, ticket #13543 + jQuery.swap( body, body.style.zoom != null ? { zoom: 1 } : {}, function() { + support.boxSizing = div.offsetWidth === 4; + }); + + // Use window.getComputedStyle because jsdom on node.js will break without it. + if ( window.getComputedStyle ) { + support.pixelPosition = ( window.getComputedStyle( div, null ) || {} ).top !== "1%"; + support.boxSizingReliable = ( window.getComputedStyle( div, null ) || { width: "4px" } ).width === "4px"; + + // Check if div with explicit width and no margin-right incorrectly + // gets computed margin-right based on width of container. (#3333) + // Fails in WebKit before Feb 2011 nightlies + // WebKit Bug 13343 - getComputedStyle returns wrong value for margin-right + marginDiv = div.appendChild( document.createElement("div") ); + marginDiv.style.cssText = div.style.cssText = divReset; + marginDiv.style.marginRight = marginDiv.style.width = "0"; + div.style.width = "1px"; + + support.reliableMarginRight = + !parseFloat( ( window.getComputedStyle( marginDiv, null ) || {} ).marginRight ); + } + + if ( typeof div.style.zoom !== core_strundefined ) { + // Support: IE<8 + // Check if natively block-level elements act like inline-block + // elements when setting their display to 'inline' and giving + // them layout + div.innerHTML = ""; + div.style.cssText = divReset + "width:1px;padding:1px;display:inline;zoom:1"; + support.inlineBlockNeedsLayout = ( div.offsetWidth === 3 ); + + // Support: IE6 + // Check if elements with layout shrink-wrap their children + div.style.display = "block"; + div.innerHTML = "
"; + div.firstChild.style.width = "5px"; + support.shrinkWrapBlocks = ( div.offsetWidth !== 3 ); + + if ( support.inlineBlockNeedsLayout ) { + // Prevent IE 6 from affecting layout for positioned elements #11048 + // Prevent IE from shrinking the body in IE 7 mode #12869 + // Support: IE<8 + body.style.zoom = 1; + } + } + + body.removeChild( container ); + + // Null elements to avoid leaks in IE + container = div = tds = marginDiv = null; + }); + + // Null elements to avoid leaks in IE + all = select = fragment = opt = a = input = null; + + return support; +})({}); + +var rbrace = /(?:\{[\s\S]*\}|\[[\s\S]*\])$/, + rmultiDash = /([A-Z])/g; + +function internalData( elem, name, data, pvt /* Internal Use Only */ ){ + if ( !jQuery.acceptData( elem ) ) { + return; + } + + var ret, thisCache, + internalKey = jQuery.expando, + + // We have to handle DOM nodes and JS objects differently because IE6-7 + // can't GC object references properly across the DOM-JS boundary + isNode = elem.nodeType, + + // Only DOM nodes need the global jQuery cache; JS object data is + // attached directly to the object so GC can occur automatically + cache = isNode ? jQuery.cache : elem, + + // Only defining an ID for JS objects if its cache already exists allows + // the code to shortcut on the same path as a DOM node with no cache + id = isNode ? elem[ internalKey ] : elem[ internalKey ] && internalKey; + + // Avoid doing any more work than we need to when trying to get data on an + // object that has no data at all + if ( (!id || !cache[id] || (!pvt && !cache[id].data)) && data === undefined && typeof name === "string" ) { + return; + } + + if ( !id ) { + // Only DOM nodes need a new unique ID for each element since their data + // ends up in the global cache + if ( isNode ) { + id = elem[ internalKey ] = core_deletedIds.pop() || jQuery.guid++; + } else { + id = internalKey; + } + } + + if ( !cache[ id ] ) { + // Avoid exposing jQuery metadata on plain JS objects when the object + // is serialized using JSON.stringify + cache[ id ] = isNode ? {} : { toJSON: jQuery.noop }; + } + + // An object can be passed to jQuery.data instead of a key/value pair; this gets + // shallow copied over onto the existing cache + if ( typeof name === "object" || typeof name === "function" ) { + if ( pvt ) { + cache[ id ] = jQuery.extend( cache[ id ], name ); + } else { + cache[ id ].data = jQuery.extend( cache[ id ].data, name ); + } + } + + thisCache = cache[ id ]; + + // jQuery data() is stored in a separate object inside the object's internal data + // cache in order to avoid key collisions between internal data and user-defined + // data. + if ( !pvt ) { + if ( !thisCache.data ) { + thisCache.data = {}; + } + + thisCache = thisCache.data; + } + + if ( data !== undefined ) { + thisCache[ jQuery.camelCase( name ) ] = data; + } + + // Check for both converted-to-camel and non-converted data property names + // If a data property was specified + if ( typeof name === "string" ) { + + // First Try to find as-is property data + ret = thisCache[ name ]; + + // Test for null|undefined property data + if ( ret == null ) { + + // Try to find the camelCased property + ret = thisCache[ jQuery.camelCase( name ) ]; + } + } else { + ret = thisCache; + } + + return ret; +} + +function internalRemoveData( elem, name, pvt ) { + if ( !jQuery.acceptData( elem ) ) { + return; + } + + var thisCache, i, + isNode = elem.nodeType, + + // See jQuery.data for more information + cache = isNode ? jQuery.cache : elem, + id = isNode ? elem[ jQuery.expando ] : jQuery.expando; + + // If there is already no cache entry for this object, there is no + // purpose in continuing + if ( !cache[ id ] ) { + return; + } + + if ( name ) { + + thisCache = pvt ? cache[ id ] : cache[ id ].data; + + if ( thisCache ) { + + // Support array or space separated string names for data keys + if ( !jQuery.isArray( name ) ) { + + // try the string as a key before any manipulation + if ( name in thisCache ) { + name = [ name ]; + } else { + + // split the camel cased version by spaces unless a key with the spaces exists + name = jQuery.camelCase( name ); + if ( name in thisCache ) { + name = [ name ]; + } else { + name = name.split(" "); + } + } + } else { + // If "name" is an array of keys... + // When data is initially created, via ("key", "val") signature, + // keys will be converted to camelCase. + // Since there is no way to tell _how_ a key was added, remove + // both plain key and camelCase key. #12786 + // This will only penalize the array argument path. + name = name.concat( jQuery.map( name, jQuery.camelCase ) ); + } + + i = name.length; + while ( i-- ) { + delete thisCache[ name[i] ]; + } + + // If there is no data left in the cache, we want to continue + // and let the cache object itself get destroyed + if ( pvt ? !isEmptyDataObject(thisCache) : !jQuery.isEmptyObject(thisCache) ) { + return; + } + } + } + + // See jQuery.data for more information + if ( !pvt ) { + delete cache[ id ].data; + + // Don't destroy the parent cache unless the internal data object + // had been the only thing left in it + if ( !isEmptyDataObject( cache[ id ] ) ) { + return; + } + } + + // Destroy the cache + if ( isNode ) { + jQuery.cleanData( [ elem ], true ); + + // Use delete when supported for expandos or `cache` is not a window per isWindow (#10080) + /* jshint eqeqeq: false */ + } else if ( jQuery.support.deleteExpando || cache != cache.window ) { + /* jshint eqeqeq: true */ + delete cache[ id ]; + + // When all else fails, null + } else { + cache[ id ] = null; + } +} + +jQuery.extend({ + cache: {}, + + // The following elements throw uncatchable exceptions if you + // attempt to add expando properties to them. + noData: { + "applet": true, + "embed": true, + // Ban all objects except for Flash (which handle expandos) + "object": "clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" + }, + + hasData: function( elem ) { + elem = elem.nodeType ? jQuery.cache[ elem[jQuery.expando] ] : elem[ jQuery.expando ]; + return !!elem && !isEmptyDataObject( elem ); + }, + + data: function( elem, name, data ) { + return internalData( elem, name, data ); + }, + + removeData: function( elem, name ) { + return internalRemoveData( elem, name ); + }, + + // For internal use only. + _data: function( elem, name, data ) { + return internalData( elem, name, data, true ); + }, + + _removeData: function( elem, name ) { + return internalRemoveData( elem, name, true ); + }, + + // A method for determining if a DOM node can handle the data expando + acceptData: function( elem ) { + // Do not set data on non-element because it will not be cleared (#8335). + if ( elem.nodeType && elem.nodeType !== 1 && elem.nodeType !== 9 ) { + return false; + } + + var noData = elem.nodeName && jQuery.noData[ elem.nodeName.toLowerCase() ]; + + // nodes accept data unless otherwise specified; rejection can be conditional + return !noData || noData !== true && elem.getAttribute("classid") === noData; + } +}); + +jQuery.fn.extend({ + data: function( key, value ) { + var attrs, name, + data = null, + i = 0, + elem = this[0]; + + // Special expections of .data basically thwart jQuery.access, + // so implement the relevant behavior ourselves + + // Gets all values + if ( key === undefined ) { + if ( this.length ) { + data = jQuery.data( elem ); + + if ( elem.nodeType === 1 && !jQuery._data( elem, "parsedAttrs" ) ) { + attrs = elem.attributes; + for ( ; i < attrs.length; i++ ) { + name = attrs[i].name; + + if ( name.indexOf("data-") === 0 ) { + name = jQuery.camelCase( name.slice(5) ); + + dataAttr( elem, name, data[ name ] ); + } + } + jQuery._data( elem, "parsedAttrs", true ); + } + } + + return data; + } + + // Sets multiple values + if ( typeof key === "object" ) { + return this.each(function() { + jQuery.data( this, key ); + }); + } + + return arguments.length > 1 ? + + // Sets one value + this.each(function() { + jQuery.data( this, key, value ); + }) : + + // Gets one value + // Try to fetch any internally stored data first + elem ? dataAttr( elem, key, jQuery.data( elem, key ) ) : null; + }, + + removeData: function( key ) { + return this.each(function() { + jQuery.removeData( this, key ); + }); + } +}); + +function dataAttr( elem, key, data ) { + // If nothing was found internally, try to fetch any + // data from the HTML5 data-* attribute + if ( data === undefined && elem.nodeType === 1 ) { + + var name = "data-" + key.replace( rmultiDash, "-$1" ).toLowerCase(); + + data = elem.getAttribute( name ); + + if ( typeof data === "string" ) { + try { + data = data === "true" ? true : + data === "false" ? false : + data === "null" ? null : + // Only convert to a number if it doesn't change the string + +data + "" === data ? +data : + rbrace.test( data ) ? jQuery.parseJSON( data ) : + data; + } catch( e ) {} + + // Make sure we set the data so it isn't changed later + jQuery.data( elem, key, data ); + + } else { + data = undefined; + } + } + + return data; +} + +// checks a cache object for emptiness +function isEmptyDataObject( obj ) { + var name; + for ( name in obj ) { + + // if the public data object is empty, the private is still empty + if ( name === "data" && jQuery.isEmptyObject( obj[name] ) ) { + continue; + } + if ( name !== "toJSON" ) { + return false; + } + } + + return true; +} +jQuery.extend({ + queue: function( elem, type, data ) { + var queue; + + if ( elem ) { + type = ( type || "fx" ) + "queue"; + queue = jQuery._data( elem, type ); + + // Speed up dequeue by getting out quickly if this is just a lookup + if ( data ) { + if ( !queue || jQuery.isArray(data) ) { + queue = jQuery._data( elem, type, jQuery.makeArray(data) ); + } else { + queue.push( data ); + } + } + return queue || []; + } + }, + + dequeue: function( elem, type ) { + type = type || "fx"; + + var queue = jQuery.queue( elem, type ), + startLength = queue.length, + fn = queue.shift(), + hooks = jQuery._queueHooks( elem, type ), + next = function() { + jQuery.dequeue( elem, type ); + }; + + // If the fx queue is dequeued, always remove the progress sentinel + if ( fn === "inprogress" ) { + fn = queue.shift(); + startLength--; + } + + hooks.cur = fn; + if ( fn ) { + + // Add a progress sentinel to prevent the fx queue from being + // automatically dequeued + if ( type === "fx" ) { + queue.unshift( "inprogress" ); + } + + // clear up the last queue stop function + delete hooks.stop; + fn.call( elem, next, hooks ); + } + + if ( !startLength && hooks ) { + hooks.empty.fire(); + } + }, + + // not intended for public consumption - generates a queueHooks object, or returns the current one + _queueHooks: function( elem, type ) { + var key = type + "queueHooks"; + return jQuery._data( elem, key ) || jQuery._data( elem, key, { + empty: jQuery.Callbacks("once memory").add(function() { + jQuery._removeData( elem, type + "queue" ); + jQuery._removeData( elem, key ); + }) + }); + } +}); + +jQuery.fn.extend({ + queue: function( type, data ) { + var setter = 2; + + if ( typeof type !== "string" ) { + data = type; + type = "fx"; + setter--; + } + + if ( arguments.length < setter ) { + return jQuery.queue( this[0], type ); + } + + return data === undefined ? + this : + this.each(function() { + var queue = jQuery.queue( this, type, data ); + + // ensure a hooks for this queue + jQuery._queueHooks( this, type ); + + if ( type === "fx" && queue[0] !== "inprogress" ) { + jQuery.dequeue( this, type ); + } + }); + }, + dequeue: function( type ) { + return this.each(function() { + jQuery.dequeue( this, type ); + }); + }, + // Based off of the plugin by Clint Helfers, with permission. + // http://blindsignals.com/index.php/2009/07/jquery-delay/ + delay: function( time, type ) { + time = jQuery.fx ? jQuery.fx.speeds[ time ] || time : time; + type = type || "fx"; + + return this.queue( type, function( next, hooks ) { + var timeout = setTimeout( next, time ); + hooks.stop = function() { + clearTimeout( timeout ); + }; + }); + }, + clearQueue: function( type ) { + return this.queue( type || "fx", [] ); + }, + // Get a promise resolved when queues of a certain type + // are emptied (fx is the type by default) + promise: function( type, obj ) { + var tmp, + count = 1, + defer = jQuery.Deferred(), + elements = this, + i = this.length, + resolve = function() { + if ( !( --count ) ) { + defer.resolveWith( elements, [ elements ] ); + } + }; + + if ( typeof type !== "string" ) { + obj = type; + type = undefined; + } + type = type || "fx"; + + while( i-- ) { + tmp = jQuery._data( elements[ i ], type + "queueHooks" ); + if ( tmp && tmp.empty ) { + count++; + tmp.empty.add( resolve ); + } + } + resolve(); + return defer.promise( obj ); + } +}); +var nodeHook, boolHook, + rclass = /[\t\r\n\f]/g, + rreturn = /\r/g, + rfocusable = /^(?:input|select|textarea|button|object)$/i, + rclickable = /^(?:a|area)$/i, + ruseDefault = /^(?:checked|selected)$/i, + getSetAttribute = jQuery.support.getSetAttribute, + getSetInput = jQuery.support.input; + +jQuery.fn.extend({ + attr: function( name, value ) { + return jQuery.access( this, jQuery.attr, name, value, arguments.length > 1 ); + }, + + removeAttr: function( name ) { + return this.each(function() { + jQuery.removeAttr( this, name ); + }); + }, + + prop: function( name, value ) { + return jQuery.access( this, jQuery.prop, name, value, arguments.length > 1 ); + }, + + removeProp: function( name ) { + name = jQuery.propFix[ name ] || name; + return this.each(function() { + // try/catch handles cases where IE balks (such as removing a property on window) + try { + this[ name ] = undefined; + delete this[ name ]; + } catch( e ) {} + }); + }, + + addClass: function( value ) { + var classes, elem, cur, clazz, j, + i = 0, + len = this.length, + proceed = typeof value === "string" && value; + + if ( jQuery.isFunction( value ) ) { + return this.each(function( j ) { + jQuery( this ).addClass( value.call( this, j, this.className ) ); + }); + } + + if ( proceed ) { + // The disjunction here is for better compressibility (see removeClass) + classes = ( value || "" ).match( core_rnotwhite ) || []; + + for ( ; i < len; i++ ) { + elem = this[ i ]; + cur = elem.nodeType === 1 && ( elem.className ? + ( " " + elem.className + " " ).replace( rclass, " " ) : + " " + ); + + if ( cur ) { + j = 0; + while ( (clazz = classes[j++]) ) { + if ( cur.indexOf( " " + clazz + " " ) < 0 ) { + cur += clazz + " "; + } + } + elem.className = jQuery.trim( cur ); + + } + } + } + + return this; + }, + + removeClass: function( value ) { + var classes, elem, cur, clazz, j, + i = 0, + len = this.length, + proceed = arguments.length === 0 || typeof value === "string" && value; + + if ( jQuery.isFunction( value ) ) { + return this.each(function( j ) { + jQuery( this ).removeClass( value.call( this, j, this.className ) ); + }); + } + if ( proceed ) { + classes = ( value || "" ).match( core_rnotwhite ) || []; + + for ( ; i < len; i++ ) { + elem = this[ i ]; + // This expression is here for better compressibility (see addClass) + cur = elem.nodeType === 1 && ( elem.className ? + ( " " + elem.className + " " ).replace( rclass, " " ) : + "" + ); + + if ( cur ) { + j = 0; + while ( (clazz = classes[j++]) ) { + // Remove *all* instances + while ( cur.indexOf( " " + clazz + " " ) >= 0 ) { + cur = cur.replace( " " + clazz + " ", " " ); + } + } + elem.className = value ? jQuery.trim( cur ) : ""; + } + } + } + + return this; + }, + + toggleClass: function( value, stateVal ) { + var type = typeof value, + isBool = typeof stateVal === "boolean"; + + if ( jQuery.isFunction( value ) ) { + return this.each(function( i ) { + jQuery( this ).toggleClass( value.call(this, i, this.className, stateVal), stateVal ); + }); + } + + return this.each(function() { + if ( type === "string" ) { + // toggle individual class names + var className, + i = 0, + self = jQuery( this ), + state = stateVal, + classNames = value.match( core_rnotwhite ) || []; + + while ( (className = classNames[ i++ ]) ) { + // check each className given, space separated list + state = isBool ? state : !self.hasClass( className ); + self[ state ? "addClass" : "removeClass" ]( className ); + } + + // Toggle whole class name + } else if ( type === core_strundefined || type === "boolean" ) { + if ( this.className ) { + // store className if set + jQuery._data( this, "__className__", this.className ); + } + + // If the element has a class name or if we're passed "false", + // then remove the whole classname (if there was one, the above saved it). + // Otherwise bring back whatever was previously saved (if anything), + // falling back to the empty string if nothing was stored. + this.className = this.className || value === false ? "" : jQuery._data( this, "__className__" ) || ""; + } + }); + }, + + hasClass: function( selector ) { + var className = " " + selector + " ", + i = 0, + l = this.length; + for ( ; i < l; i++ ) { + if ( this[i].nodeType === 1 && (" " + this[i].className + " ").replace(rclass, " ").indexOf( className ) >= 0 ) { + return true; + } + } + + return false; + }, + + val: function( value ) { + var ret, hooks, isFunction, + elem = this[0]; + + if ( !arguments.length ) { + if ( elem ) { + hooks = jQuery.valHooks[ elem.type ] || jQuery.valHooks[ elem.nodeName.toLowerCase() ]; + + if ( hooks && "get" in hooks && (ret = hooks.get( elem, "value" )) !== undefined ) { + return ret; + } + + ret = elem.value; + + return typeof ret === "string" ? + // handle most common string cases + ret.replace(rreturn, "") : + // handle cases where value is null/undef or number + ret == null ? "" : ret; + } + + return; + } + + isFunction = jQuery.isFunction( value ); + + return this.each(function( i ) { + var val; + + if ( this.nodeType !== 1 ) { + return; + } + + if ( isFunction ) { + val = value.call( this, i, jQuery( this ).val() ); + } else { + val = value; + } + + // Treat null/undefined as ""; convert numbers to string + if ( val == null ) { + val = ""; + } else if ( typeof val === "number" ) { + val += ""; + } else if ( jQuery.isArray( val ) ) { + val = jQuery.map(val, function ( value ) { + return value == null ? "" : value + ""; + }); + } + + hooks = jQuery.valHooks[ this.type ] || jQuery.valHooks[ this.nodeName.toLowerCase() ]; + + // If set returns undefined, fall back to normal setting + if ( !hooks || !("set" in hooks) || hooks.set( this, val, "value" ) === undefined ) { + this.value = val; + } + }); + } +}); + +jQuery.extend({ + valHooks: { + option: { + get: function( elem ) { + // Use proper attribute retrieval(#6932, #12072) + var val = jQuery.find.attr( elem, "value" ); + return val != null ? + val : + elem.text; + } + }, + select: { + get: function( elem ) { + var value, option, + options = elem.options, + index = elem.selectedIndex, + one = elem.type === "select-one" || index < 0, + values = one ? null : [], + max = one ? index + 1 : options.length, + i = index < 0 ? + max : + one ? index : 0; + + // Loop through all the selected options + for ( ; i < max; i++ ) { + option = options[ i ]; + + // oldIE doesn't update selected after form reset (#2551) + if ( ( option.selected || i === index ) && + // Don't return options that are disabled or in a disabled optgroup + ( jQuery.support.optDisabled ? !option.disabled : option.getAttribute("disabled") === null ) && + ( !option.parentNode.disabled || !jQuery.nodeName( option.parentNode, "optgroup" ) ) ) { + + // Get the specific value for the option + value = jQuery( option ).val(); + + // We don't need an array for one selects + if ( one ) { + return value; + } + + // Multi-Selects return an array + values.push( value ); + } + } + + return values; + }, + + set: function( elem, value ) { + var optionSet, option, + options = elem.options, + values = jQuery.makeArray( value ), + i = options.length; + + while ( i-- ) { + option = options[ i ]; + if ( (option.selected = jQuery.inArray( jQuery(option).val(), values ) >= 0) ) { + optionSet = true; + } + } + + // force browsers to behave consistently when non-matching value is set + if ( !optionSet ) { + elem.selectedIndex = -1; + } + return values; + } + } + }, + + attr: function( elem, name, value ) { + var hooks, ret, + nType = elem.nodeType; + + // don't get/set attributes on text, comment and attribute nodes + if ( !elem || nType === 3 || nType === 8 || nType === 2 ) { + return; + } + + // Fallback to prop when attributes are not supported + if ( typeof elem.getAttribute === core_strundefined ) { + return jQuery.prop( elem, name, value ); + } + + // All attributes are lowercase + // Grab necessary hook if one is defined + if ( nType !== 1 || !jQuery.isXMLDoc( elem ) ) { + name = name.toLowerCase(); + hooks = jQuery.attrHooks[ name ] || + ( jQuery.expr.match.bool.test( name ) ? boolHook : nodeHook ); + } + + if ( value !== undefined ) { + + if ( value === null ) { + jQuery.removeAttr( elem, name ); + + } else if ( hooks && "set" in hooks && (ret = hooks.set( elem, value, name )) !== undefined ) { + return ret; + + } else { + elem.setAttribute( name, value + "" ); + return value; + } + + } else if ( hooks && "get" in hooks && (ret = hooks.get( elem, name )) !== null ) { + return ret; + + } else { + ret = jQuery.find.attr( elem, name ); + + // Non-existent attributes return null, we normalize to undefined + return ret == null ? + undefined : + ret; + } + }, + + removeAttr: function( elem, value ) { + var name, propName, + i = 0, + attrNames = value && value.match( core_rnotwhite ); + + if ( attrNames && elem.nodeType === 1 ) { + while ( (name = attrNames[i++]) ) { + propName = jQuery.propFix[ name ] || name; + + // Boolean attributes get special treatment (#10870) + if ( jQuery.expr.match.bool.test( name ) ) { + // Set corresponding property to false + if ( getSetInput && getSetAttribute || !ruseDefault.test( name ) ) { + elem[ propName ] = false; + // Support: IE<9 + // Also clear defaultChecked/defaultSelected (if appropriate) + } else { + elem[ jQuery.camelCase( "default-" + name ) ] = + elem[ propName ] = false; + } + + // See #9699 for explanation of this approach (setting first, then removal) + } else { + jQuery.attr( elem, name, "" ); + } + + elem.removeAttribute( getSetAttribute ? name : propName ); + } + } + }, + + attrHooks: { + type: { + set: function( elem, value ) { + if ( !jQuery.support.radioValue && value === "radio" && jQuery.nodeName(elem, "input") ) { + // Setting the type on a radio button after the value resets the value in IE6-9 + // Reset value to default in case type is set after value during creation + var val = elem.value; + elem.setAttribute( "type", value ); + if ( val ) { + elem.value = val; + } + return value; + } + } + } + }, + + propFix: { + "for": "htmlFor", + "class": "className" + }, + + prop: function( elem, name, value ) { + var ret, hooks, notxml, + nType = elem.nodeType; + + // don't get/set properties on text, comment and attribute nodes + if ( !elem || nType === 3 || nType === 8 || nType === 2 ) { + return; + } + + notxml = nType !== 1 || !jQuery.isXMLDoc( elem ); + + if ( notxml ) { + // Fix name and attach hooks + name = jQuery.propFix[ name ] || name; + hooks = jQuery.propHooks[ name ]; + } + + if ( value !== undefined ) { + return hooks && "set" in hooks && (ret = hooks.set( elem, value, name )) !== undefined ? + ret : + ( elem[ name ] = value ); + + } else { + return hooks && "get" in hooks && (ret = hooks.get( elem, name )) !== null ? + ret : + elem[ name ]; + } + }, + + propHooks: { + tabIndex: { + get: function( elem ) { + // elem.tabIndex doesn't always return the correct value when it hasn't been explicitly set + // http://fluidproject.org/blog/2008/01/09/getting-setting-and-removing-tabindex-values-with-javascript/ + // Use proper attribute retrieval(#12072) + var tabindex = jQuery.find.attr( elem, "tabindex" ); + + return tabindex ? + parseInt( tabindex, 10 ) : + rfocusable.test( elem.nodeName ) || rclickable.test( elem.nodeName ) && elem.href ? + 0 : + -1; + } + } + } +}); + +// Hooks for boolean attributes +boolHook = { + set: function( elem, value, name ) { + if ( value === false ) { + // Remove boolean attributes when set to false + jQuery.removeAttr( elem, name ); + } else if ( getSetInput && getSetAttribute || !ruseDefault.test( name ) ) { + // IE<8 needs the *property* name + elem.setAttribute( !getSetAttribute && jQuery.propFix[ name ] || name, name ); + + // Use defaultChecked and defaultSelected for oldIE + } else { + elem[ jQuery.camelCase( "default-" + name ) ] = elem[ name ] = true; + } + + return name; + } +}; +jQuery.each( jQuery.expr.match.bool.source.match( /\w+/g ), function( i, name ) { + var getter = jQuery.expr.attrHandle[ name ] || jQuery.find.attr; + + jQuery.expr.attrHandle[ name ] = getSetInput && getSetAttribute || !ruseDefault.test( name ) ? + function( elem, name, isXML ) { + var fn = jQuery.expr.attrHandle[ name ], + ret = isXML ? + undefined : + /* jshint eqeqeq: false */ + (jQuery.expr.attrHandle[ name ] = undefined) != + getter( elem, name, isXML ) ? + + name.toLowerCase() : + null; + jQuery.expr.attrHandle[ name ] = fn; + return ret; + } : + function( elem, name, isXML ) { + return isXML ? + undefined : + elem[ jQuery.camelCase( "default-" + name ) ] ? + name.toLowerCase() : + null; + }; +}); + +// fix oldIE attroperties +if ( !getSetInput || !getSetAttribute ) { + jQuery.attrHooks.value = { + set: function( elem, value, name ) { + if ( jQuery.nodeName( elem, "input" ) ) { + // Does not return so that setAttribute is also used + elem.defaultValue = value; + } else { + // Use nodeHook if defined (#1954); otherwise setAttribute is fine + return nodeHook && nodeHook.set( elem, value, name ); + } + } + }; +} + +// IE6/7 do not support getting/setting some attributes with get/setAttribute +if ( !getSetAttribute ) { + + // Use this for any attribute in IE6/7 + // This fixes almost every IE6/7 issue + nodeHook = { + set: function( elem, value, name ) { + // Set the existing or create a new attribute node + var ret = elem.getAttributeNode( name ); + if ( !ret ) { + elem.setAttributeNode( + (ret = elem.ownerDocument.createAttribute( name )) + ); + } + + ret.value = value += ""; + + // Break association with cloned elements by also using setAttribute (#9646) + return name === "value" || value === elem.getAttribute( name ) ? + value : + undefined; + } + }; + jQuery.expr.attrHandle.id = jQuery.expr.attrHandle.name = jQuery.expr.attrHandle.coords = + // Some attributes are constructed with empty-string values when not defined + function( elem, name, isXML ) { + var ret; + return isXML ? + undefined : + (ret = elem.getAttributeNode( name )) && ret.value !== "" ? + ret.value : + null; + }; + jQuery.valHooks.button = { + get: function( elem, name ) { + var ret = elem.getAttributeNode( name ); + return ret && ret.specified ? + ret.value : + undefined; + }, + set: nodeHook.set + }; + + // Set contenteditable to false on removals(#10429) + // Setting to empty string throws an error as an invalid value + jQuery.attrHooks.contenteditable = { + set: function( elem, value, name ) { + nodeHook.set( elem, value === "" ? false : value, name ); + } + }; + + // Set width and height to auto instead of 0 on empty string( Bug #8150 ) + // This is for removals + jQuery.each([ "width", "height" ], function( i, name ) { + jQuery.attrHooks[ name ] = { + set: function( elem, value ) { + if ( value === "" ) { + elem.setAttribute( name, "auto" ); + return value; + } + } + }; + }); +} + + +// Some attributes require a special call on IE +// http://msdn.microsoft.com/en-us/library/ms536429%28VS.85%29.aspx +if ( !jQuery.support.hrefNormalized ) { + // href/src property should get the full normalized URL (#10299/#12915) + jQuery.each([ "href", "src" ], function( i, name ) { + jQuery.propHooks[ name ] = { + get: function( elem ) { + return elem.getAttribute( name, 4 ); + } + }; + }); +} + +if ( !jQuery.support.style ) { + jQuery.attrHooks.style = { + get: function( elem ) { + // Return undefined in the case of empty string + // Note: IE uppercases css property names, but if we were to .toLowerCase() + // .cssText, that would destroy case senstitivity in URL's, like in "background" + return elem.style.cssText || undefined; + }, + set: function( elem, value ) { + return ( elem.style.cssText = value + "" ); + } + }; +} + +// Safari mis-reports the default selected property of an option +// Accessing the parent's selectedIndex property fixes it +if ( !jQuery.support.optSelected ) { + jQuery.propHooks.selected = { + get: function( elem ) { + var parent = elem.parentNode; + + if ( parent ) { + parent.selectedIndex; + + // Make sure that it also works with optgroups, see #5701 + if ( parent.parentNode ) { + parent.parentNode.selectedIndex; + } + } + return null; + } + }; +} + +jQuery.each([ + "tabIndex", + "readOnly", + "maxLength", + "cellSpacing", + "cellPadding", + "rowSpan", + "colSpan", + "useMap", + "frameBorder", + "contentEditable" +], function() { + jQuery.propFix[ this.toLowerCase() ] = this; +}); + +// IE6/7 call enctype encoding +if ( !jQuery.support.enctype ) { + jQuery.propFix.enctype = "encoding"; +} + +// Radios and checkboxes getter/setter +jQuery.each([ "radio", "checkbox" ], function() { + jQuery.valHooks[ this ] = { + set: function( elem, value ) { + if ( jQuery.isArray( value ) ) { + return ( elem.checked = jQuery.inArray( jQuery(elem).val(), value ) >= 0 ); + } + } + }; + if ( !jQuery.support.checkOn ) { + jQuery.valHooks[ this ].get = function( elem ) { + // Support: Webkit + // "" is returned instead of "on" if a value isn't specified + return elem.getAttribute("value") === null ? "on" : elem.value; + }; + } +}); +var rformElems = /^(?:input|select|textarea)$/i, + rkeyEvent = /^key/, + rmouseEvent = /^(?:mouse|contextmenu)|click/, + rfocusMorph = /^(?:focusinfocus|focusoutblur)$/, + rtypenamespace = /^([^.]*)(?:\.(.+)|)$/; + +function returnTrue() { + return true; +} + +function returnFalse() { + return false; +} + +function safeActiveElement() { + try { + return document.activeElement; + } catch ( err ) { } +} + +/* + * Helper functions for managing events -- not part of the public interface. + * Props to Dean Edwards' addEvent library for many of the ideas. + */ +jQuery.event = { + + global: {}, + + add: function( elem, types, handler, data, selector ) { + var tmp, events, t, handleObjIn, + special, eventHandle, handleObj, + handlers, type, namespaces, origType, + elemData = jQuery._data( elem ); + + // Don't attach events to noData or text/comment nodes (but allow plain objects) + if ( !elemData ) { + return; + } + + // Caller can pass in an object of custom data in lieu of the handler + if ( handler.handler ) { + handleObjIn = handler; + handler = handleObjIn.handler; + selector = handleObjIn.selector; + } + + // Make sure that the handler has a unique ID, used to find/remove it later + if ( !handler.guid ) { + handler.guid = jQuery.guid++; + } + + // Init the element's event structure and main handler, if this is the first + if ( !(events = elemData.events) ) { + events = elemData.events = {}; + } + if ( !(eventHandle = elemData.handle) ) { + eventHandle = elemData.handle = function( e ) { + // Discard the second event of a jQuery.event.trigger() and + // when an event is called after a page has unloaded + return typeof jQuery !== core_strundefined && (!e || jQuery.event.triggered !== e.type) ? + jQuery.event.dispatch.apply( eventHandle.elem, arguments ) : + undefined; + }; + // Add elem as a property of the handle fn to prevent a memory leak with IE non-native events + eventHandle.elem = elem; + } + + // Handle multiple events separated by a space + types = ( types || "" ).match( core_rnotwhite ) || [""]; + t = types.length; + while ( t-- ) { + tmp = rtypenamespace.exec( types[t] ) || []; + type = origType = tmp[1]; + namespaces = ( tmp[2] || "" ).split( "." ).sort(); + + // There *must* be a type, no attaching namespace-only handlers + if ( !type ) { + continue; + } + + // If event changes its type, use the special event handlers for the changed type + special = jQuery.event.special[ type ] || {}; + + // If selector defined, determine special event api type, otherwise given type + type = ( selector ? special.delegateType : special.bindType ) || type; + + // Update special based on newly reset type + special = jQuery.event.special[ type ] || {}; + + // handleObj is passed to all event handlers + handleObj = jQuery.extend({ + type: type, + origType: origType, + data: data, + handler: handler, + guid: handler.guid, + selector: selector, + needsContext: selector && jQuery.expr.match.needsContext.test( selector ), + namespace: namespaces.join(".") + }, handleObjIn ); + + // Init the event handler queue if we're the first + if ( !(handlers = events[ type ]) ) { + handlers = events[ type ] = []; + handlers.delegateCount = 0; + + // Only use addEventListener/attachEvent if the special events handler returns false + if ( !special.setup || special.setup.call( elem, data, namespaces, eventHandle ) === false ) { + // Bind the global event handler to the element + if ( elem.addEventListener ) { + elem.addEventListener( type, eventHandle, false ); + + } else if ( elem.attachEvent ) { + elem.attachEvent( "on" + type, eventHandle ); + } + } + } + + if ( special.add ) { + special.add.call( elem, handleObj ); + + if ( !handleObj.handler.guid ) { + handleObj.handler.guid = handler.guid; + } + } + + // Add to the element's handler list, delegates in front + if ( selector ) { + handlers.splice( handlers.delegateCount++, 0, handleObj ); + } else { + handlers.push( handleObj ); + } + + // Keep track of which events have ever been used, for event optimization + jQuery.event.global[ type ] = true; + } + + // Nullify elem to prevent memory leaks in IE + elem = null; + }, + + // Detach an event or set of events from an element + remove: function( elem, types, handler, selector, mappedTypes ) { + var j, handleObj, tmp, + origCount, t, events, + special, handlers, type, + namespaces, origType, + elemData = jQuery.hasData( elem ) && jQuery._data( elem ); + + if ( !elemData || !(events = elemData.events) ) { + return; + } + + // Once for each type.namespace in types; type may be omitted + types = ( types || "" ).match( core_rnotwhite ) || [""]; + t = types.length; + while ( t-- ) { + tmp = rtypenamespace.exec( types[t] ) || []; + type = origType = tmp[1]; + namespaces = ( tmp[2] || "" ).split( "." ).sort(); + + // Unbind all events (on this namespace, if provided) for the element + if ( !type ) { + for ( type in events ) { + jQuery.event.remove( elem, type + types[ t ], handler, selector, true ); + } + continue; + } + + special = jQuery.event.special[ type ] || {}; + type = ( selector ? special.delegateType : special.bindType ) || type; + handlers = events[ type ] || []; + tmp = tmp[2] && new RegExp( "(^|\\.)" + namespaces.join("\\.(?:.*\\.|)") + "(\\.|$)" ); + + // Remove matching events + origCount = j = handlers.length; + while ( j-- ) { + handleObj = handlers[ j ]; + + if ( ( mappedTypes || origType === handleObj.origType ) && + ( !handler || handler.guid === handleObj.guid ) && + ( !tmp || tmp.test( handleObj.namespace ) ) && + ( !selector || selector === handleObj.selector || selector === "**" && handleObj.selector ) ) { + handlers.splice( j, 1 ); + + if ( handleObj.selector ) { + handlers.delegateCount--; + } + if ( special.remove ) { + special.remove.call( elem, handleObj ); + } + } + } + + // Remove generic event handler if we removed something and no more handlers exist + // (avoids potential for endless recursion during removal of special event handlers) + if ( origCount && !handlers.length ) { + if ( !special.teardown || special.teardown.call( elem, namespaces, elemData.handle ) === false ) { + jQuery.removeEvent( elem, type, elemData.handle ); + } + + delete events[ type ]; + } + } + + // Remove the expando if it's no longer used + if ( jQuery.isEmptyObject( events ) ) { + delete elemData.handle; + + // removeData also checks for emptiness and clears the expando if empty + // so use it instead of delete + jQuery._removeData( elem, "events" ); + } + }, + + trigger: function( event, data, elem, onlyHandlers ) { + var handle, ontype, cur, + bubbleType, special, tmp, i, + eventPath = [ elem || document ], + type = core_hasOwn.call( event, "type" ) ? event.type : event, + namespaces = core_hasOwn.call( event, "namespace" ) ? event.namespace.split(".") : []; + + cur = tmp = elem = elem || document; + + // Don't do events on text and comment nodes + if ( elem.nodeType === 3 || elem.nodeType === 8 ) { + return; + } + + // focus/blur morphs to focusin/out; ensure we're not firing them right now + if ( rfocusMorph.test( type + jQuery.event.triggered ) ) { + return; + } + + if ( type.indexOf(".") >= 0 ) { + // Namespaced trigger; create a regexp to match event type in handle() + namespaces = type.split("."); + type = namespaces.shift(); + namespaces.sort(); + } + ontype = type.indexOf(":") < 0 && "on" + type; + + // Caller can pass in a jQuery.Event object, Object, or just an event type string + event = event[ jQuery.expando ] ? + event : + new jQuery.Event( type, typeof event === "object" && event ); + + // Trigger bitmask: & 1 for native handlers; & 2 for jQuery (always true) + event.isTrigger = onlyHandlers ? 2 : 3; + event.namespace = namespaces.join("."); + event.namespace_re = event.namespace ? + new RegExp( "(^|\\.)" + namespaces.join("\\.(?:.*\\.|)") + "(\\.|$)" ) : + null; + + // Clean up the event in case it is being reused + event.result = undefined; + if ( !event.target ) { + event.target = elem; + } + + // Clone any incoming data and prepend the event, creating the handler arg list + data = data == null ? + [ event ] : + jQuery.makeArray( data, [ event ] ); + + // Allow special events to draw outside the lines + special = jQuery.event.special[ type ] || {}; + if ( !onlyHandlers && special.trigger && special.trigger.apply( elem, data ) === false ) { + return; + } + + // Determine event propagation path in advance, per W3C events spec (#9951) + // Bubble up to document, then to window; watch for a global ownerDocument var (#9724) + if ( !onlyHandlers && !special.noBubble && !jQuery.isWindow( elem ) ) { + + bubbleType = special.delegateType || type; + if ( !rfocusMorph.test( bubbleType + type ) ) { + cur = cur.parentNode; + } + for ( ; cur; cur = cur.parentNode ) { + eventPath.push( cur ); + tmp = cur; + } + + // Only add window if we got to document (e.g., not plain obj or detached DOM) + if ( tmp === (elem.ownerDocument || document) ) { + eventPath.push( tmp.defaultView || tmp.parentWindow || window ); + } + } + + // Fire handlers on the event path + i = 0; + while ( (cur = eventPath[i++]) && !event.isPropagationStopped() ) { + + event.type = i > 1 ? + bubbleType : + special.bindType || type; + + // jQuery handler + handle = ( jQuery._data( cur, "events" ) || {} )[ event.type ] && jQuery._data( cur, "handle" ); + if ( handle ) { + handle.apply( cur, data ); + } + + // Native handler + handle = ontype && cur[ ontype ]; + if ( handle && jQuery.acceptData( cur ) && handle.apply && handle.apply( cur, data ) === false ) { + event.preventDefault(); + } + } + event.type = type; + + // If nobody prevented the default action, do it now + if ( !onlyHandlers && !event.isDefaultPrevented() ) { + + if ( (!special._default || special._default.apply( eventPath.pop(), data ) === false) && + jQuery.acceptData( elem ) ) { + + // Call a native DOM method on the target with the same name name as the event. + // Can't use an .isFunction() check here because IE6/7 fails that test. + // Don't do default actions on window, that's where global variables be (#6170) + if ( ontype && elem[ type ] && !jQuery.isWindow( elem ) ) { + + // Don't re-trigger an onFOO event when we call its FOO() method + tmp = elem[ ontype ]; + + if ( tmp ) { + elem[ ontype ] = null; + } + + // Prevent re-triggering of the same event, since we already bubbled it above + jQuery.event.triggered = type; + try { + elem[ type ](); + } catch ( e ) { + // IE<9 dies on focus/blur to hidden element (#1486,#12518) + // only reproducible on winXP IE8 native, not IE9 in IE8 mode + } + jQuery.event.triggered = undefined; + + if ( tmp ) { + elem[ ontype ] = tmp; + } + } + } + } + + return event.result; + }, + + dispatch: function( event ) { + + // Make a writable jQuery.Event from the native event object + event = jQuery.event.fix( event ); + + var i, ret, handleObj, matched, j, + handlerQueue = [], + args = core_slice.call( arguments ), + handlers = ( jQuery._data( this, "events" ) || {} )[ event.type ] || [], + special = jQuery.event.special[ event.type ] || {}; + + // Use the fix-ed jQuery.Event rather than the (read-only) native event + args[0] = event; + event.delegateTarget = this; + + // Call the preDispatch hook for the mapped type, and let it bail if desired + if ( special.preDispatch && special.preDispatch.call( this, event ) === false ) { + return; + } + + // Determine handlers + handlerQueue = jQuery.event.handlers.call( this, event, handlers ); + + // Run delegates first; they may want to stop propagation beneath us + i = 0; + while ( (matched = handlerQueue[ i++ ]) && !event.isPropagationStopped() ) { + event.currentTarget = matched.elem; + + j = 0; + while ( (handleObj = matched.handlers[ j++ ]) && !event.isImmediatePropagationStopped() ) { + + // Triggered event must either 1) have no namespace, or + // 2) have namespace(s) a subset or equal to those in the bound event (both can have no namespace). + if ( !event.namespace_re || event.namespace_re.test( handleObj.namespace ) ) { + + event.handleObj = handleObj; + event.data = handleObj.data; + + ret = ( (jQuery.event.special[ handleObj.origType ] || {}).handle || handleObj.handler ) + .apply( matched.elem, args ); + + if ( ret !== undefined ) { + if ( (event.result = ret) === false ) { + event.preventDefault(); + event.stopPropagation(); + } + } + } + } + } + + // Call the postDispatch hook for the mapped type + if ( special.postDispatch ) { + special.postDispatch.call( this, event ); + } + + return event.result; + }, + + handlers: function( event, handlers ) { + var sel, handleObj, matches, i, + handlerQueue = [], + delegateCount = handlers.delegateCount, + cur = event.target; + + // Find delegate handlers + // Black-hole SVG instance trees (#13180) + // Avoid non-left-click bubbling in Firefox (#3861) + if ( delegateCount && cur.nodeType && (!event.button || event.type !== "click") ) { + + /* jshint eqeqeq: false */ + for ( ; cur != this; cur = cur.parentNode || this ) { + /* jshint eqeqeq: true */ + + // Don't check non-elements (#13208) + // Don't process clicks on disabled elements (#6911, #8165, #11382, #11764) + if ( cur.nodeType === 1 && (cur.disabled !== true || event.type !== "click") ) { + matches = []; + for ( i = 0; i < delegateCount; i++ ) { + handleObj = handlers[ i ]; + + // Don't conflict with Object.prototype properties (#13203) + sel = handleObj.selector + " "; + + if ( matches[ sel ] === undefined ) { + matches[ sel ] = handleObj.needsContext ? + jQuery( sel, this ).index( cur ) >= 0 : + jQuery.find( sel, this, null, [ cur ] ).length; + } + if ( matches[ sel ] ) { + matches.push( handleObj ); + } + } + if ( matches.length ) { + handlerQueue.push({ elem: cur, handlers: matches }); + } + } + } + } + + // Add the remaining (directly-bound) handlers + if ( delegateCount < handlers.length ) { + handlerQueue.push({ elem: this, handlers: handlers.slice( delegateCount ) }); + } + + return handlerQueue; + }, + + fix: function( event ) { + if ( event[ jQuery.expando ] ) { + return event; + } + + // Create a writable copy of the event object and normalize some properties + var i, prop, copy, + type = event.type, + originalEvent = event, + fixHook = this.fixHooks[ type ]; + + if ( !fixHook ) { + this.fixHooks[ type ] = fixHook = + rmouseEvent.test( type ) ? this.mouseHooks : + rkeyEvent.test( type ) ? this.keyHooks : + {}; + } + copy = fixHook.props ? this.props.concat( fixHook.props ) : this.props; + + event = new jQuery.Event( originalEvent ); + + i = copy.length; + while ( i-- ) { + prop = copy[ i ]; + event[ prop ] = originalEvent[ prop ]; + } + + // Support: IE<9 + // Fix target property (#1925) + if ( !event.target ) { + event.target = originalEvent.srcElement || document; + } + + // Support: Chrome 23+, Safari? + // Target should not be a text node (#504, #13143) + if ( event.target.nodeType === 3 ) { + event.target = event.target.parentNode; + } + + // Support: IE<9 + // For mouse/key events, metaKey==false if it's undefined (#3368, #11328) + event.metaKey = !!event.metaKey; + + return fixHook.filter ? fixHook.filter( event, originalEvent ) : event; + }, + + // Includes some event props shared by KeyEvent and MouseEvent + props: "altKey bubbles cancelable ctrlKey currentTarget eventPhase metaKey relatedTarget shiftKey target timeStamp view which".split(" "), + + fixHooks: {}, + + keyHooks: { + props: "char charCode key keyCode".split(" "), + filter: function( event, original ) { + + // Add which for key events + if ( event.which == null ) { + event.which = original.charCode != null ? original.charCode : original.keyCode; + } + + return event; + } + }, + + mouseHooks: { + props: "button buttons clientX clientY fromElement offsetX offsetY pageX pageY screenX screenY toElement".split(" "), + filter: function( event, original ) { + var body, eventDoc, doc, + button = original.button, + fromElement = original.fromElement; + + // Calculate pageX/Y if missing and clientX/Y available + if ( event.pageX == null && original.clientX != null ) { + eventDoc = event.target.ownerDocument || document; + doc = eventDoc.documentElement; + body = eventDoc.body; + + event.pageX = original.clientX + ( doc && doc.scrollLeft || body && body.scrollLeft || 0 ) - ( doc && doc.clientLeft || body && body.clientLeft || 0 ); + event.pageY = original.clientY + ( doc && doc.scrollTop || body && body.scrollTop || 0 ) - ( doc && doc.clientTop || body && body.clientTop || 0 ); + } + + // Add relatedTarget, if necessary + if ( !event.relatedTarget && fromElement ) { + event.relatedTarget = fromElement === event.target ? original.toElement : fromElement; + } + + // Add which for click: 1 === left; 2 === middle; 3 === right + // Note: button is not normalized, so don't use it + if ( !event.which && button !== undefined ) { + event.which = ( button & 1 ? 1 : ( button & 2 ? 3 : ( button & 4 ? 2 : 0 ) ) ); + } + + return event; + } + }, + + special: { + load: { + // Prevent triggered image.load events from bubbling to window.load + noBubble: true + }, + focus: { + // Fire native event if possible so blur/focus sequence is correct + trigger: function() { + if ( this !== safeActiveElement() && this.focus ) { + try { + this.focus(); + return false; + } catch ( e ) { + // Support: IE<9 + // If we error on focus to hidden element (#1486, #12518), + // let .trigger() run the handlers + } + } + }, + delegateType: "focusin" + }, + blur: { + trigger: function() { + if ( this === safeActiveElement() && this.blur ) { + this.blur(); + return false; + } + }, + delegateType: "focusout" + }, + click: { + // For checkbox, fire native event so checked state will be right + trigger: function() { + if ( jQuery.nodeName( this, "input" ) && this.type === "checkbox" && this.click ) { + this.click(); + return false; + } + }, + + // For cross-browser consistency, don't fire native .click() on links + _default: function( event ) { + return jQuery.nodeName( event.target, "a" ); + } + }, + + beforeunload: { + postDispatch: function( event ) { + + // Even when returnValue equals to undefined Firefox will still show alert + if ( event.result !== undefined ) { + event.originalEvent.returnValue = event.result; + } + } + } + }, + + simulate: function( type, elem, event, bubble ) { + // Piggyback on a donor event to simulate a different one. + // Fake originalEvent to avoid donor's stopPropagation, but if the + // simulated event prevents default then we do the same on the donor. + var e = jQuery.extend( + new jQuery.Event(), + event, + { + type: type, + isSimulated: true, + originalEvent: {} + } + ); + if ( bubble ) { + jQuery.event.trigger( e, null, elem ); + } else { + jQuery.event.dispatch.call( elem, e ); + } + if ( e.isDefaultPrevented() ) { + event.preventDefault(); + } + } +}; + +jQuery.removeEvent = document.removeEventListener ? + function( elem, type, handle ) { + if ( elem.removeEventListener ) { + elem.removeEventListener( type, handle, false ); + } + } : + function( elem, type, handle ) { + var name = "on" + type; + + if ( elem.detachEvent ) { + + // #8545, #7054, preventing memory leaks for custom events in IE6-8 + // detachEvent needed property on element, by name of that event, to properly expose it to GC + if ( typeof elem[ name ] === core_strundefined ) { + elem[ name ] = null; + } + + elem.detachEvent( name, handle ); + } + }; + +jQuery.Event = function( src, props ) { + // Allow instantiation without the 'new' keyword + if ( !(this instanceof jQuery.Event) ) { + return new jQuery.Event( src, props ); + } + + // Event object + if ( src && src.type ) { + this.originalEvent = src; + this.type = src.type; + + // Events bubbling up the document may have been marked as prevented + // by a handler lower down the tree; reflect the correct value. + this.isDefaultPrevented = ( src.defaultPrevented || src.returnValue === false || + src.getPreventDefault && src.getPreventDefault() ) ? returnTrue : returnFalse; + + // Event type + } else { + this.type = src; + } + + // Put explicitly provided properties onto the event object + if ( props ) { + jQuery.extend( this, props ); + } + + // Create a timestamp if incoming event doesn't have one + this.timeStamp = src && src.timeStamp || jQuery.now(); + + // Mark it as fixed + this[ jQuery.expando ] = true; +}; + +// jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding +// http://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html +jQuery.Event.prototype = { + isDefaultPrevented: returnFalse, + isPropagationStopped: returnFalse, + isImmediatePropagationStopped: returnFalse, + + preventDefault: function() { + var e = this.originalEvent; + + this.isDefaultPrevented = returnTrue; + if ( !e ) { + return; + } + + // If preventDefault exists, run it on the original event + if ( e.preventDefault ) { + e.preventDefault(); + + // Support: IE + // Otherwise set the returnValue property of the original event to false + } else { + e.returnValue = false; + } + }, + stopPropagation: function() { + var e = this.originalEvent; + + this.isPropagationStopped = returnTrue; + if ( !e ) { + return; + } + // If stopPropagation exists, run it on the original event + if ( e.stopPropagation ) { + e.stopPropagation(); + } + + // Support: IE + // Set the cancelBubble property of the original event to true + e.cancelBubble = true; + }, + stopImmediatePropagation: function() { + this.isImmediatePropagationStopped = returnTrue; + this.stopPropagation(); + } +}; + +// Create mouseenter/leave events using mouseover/out and event-time checks +jQuery.each({ + mouseenter: "mouseover", + mouseleave: "mouseout" +}, function( orig, fix ) { + jQuery.event.special[ orig ] = { + delegateType: fix, + bindType: fix, + + handle: function( event ) { + var ret, + target = this, + related = event.relatedTarget, + handleObj = event.handleObj; + + // For mousenter/leave call the handler if related is outside the target. + // NB: No relatedTarget if the mouse left/entered the browser window + if ( !related || (related !== target && !jQuery.contains( target, related )) ) { + event.type = handleObj.origType; + ret = handleObj.handler.apply( this, arguments ); + event.type = fix; + } + return ret; + } + }; +}); + +// IE submit delegation +if ( !jQuery.support.submitBubbles ) { + + jQuery.event.special.submit = { + setup: function() { + // Only need this for delegated form submit events + if ( jQuery.nodeName( this, "form" ) ) { + return false; + } + + // Lazy-add a submit handler when a descendant form may potentially be submitted + jQuery.event.add( this, "click._submit keypress._submit", function( e ) { + // Node name check avoids a VML-related crash in IE (#9807) + var elem = e.target, + form = jQuery.nodeName( elem, "input" ) || jQuery.nodeName( elem, "button" ) ? elem.form : undefined; + if ( form && !jQuery._data( form, "submitBubbles" ) ) { + jQuery.event.add( form, "submit._submit", function( event ) { + event._submit_bubble = true; + }); + jQuery._data( form, "submitBubbles", true ); + } + }); + // return undefined since we don't need an event listener + }, + + postDispatch: function( event ) { + // If form was submitted by the user, bubble the event up the tree + if ( event._submit_bubble ) { + delete event._submit_bubble; + if ( this.parentNode && !event.isTrigger ) { + jQuery.event.simulate( "submit", this.parentNode, event, true ); + } + } + }, + + teardown: function() { + // Only need this for delegated form submit events + if ( jQuery.nodeName( this, "form" ) ) { + return false; + } + + // Remove delegated handlers; cleanData eventually reaps submit handlers attached above + jQuery.event.remove( this, "._submit" ); + } + }; +} + +// IE change delegation and checkbox/radio fix +if ( !jQuery.support.changeBubbles ) { + + jQuery.event.special.change = { + + setup: function() { + + if ( rformElems.test( this.nodeName ) ) { + // IE doesn't fire change on a check/radio until blur; trigger it on click + // after a propertychange. Eat the blur-change in special.change.handle. + // This still fires onchange a second time for check/radio after blur. + if ( this.type === "checkbox" || this.type === "radio" ) { + jQuery.event.add( this, "propertychange._change", function( event ) { + if ( event.originalEvent.propertyName === "checked" ) { + this._just_changed = true; + } + }); + jQuery.event.add( this, "click._change", function( event ) { + if ( this._just_changed && !event.isTrigger ) { + this._just_changed = false; + } + // Allow triggered, simulated change events (#11500) + jQuery.event.simulate( "change", this, event, true ); + }); + } + return false; + } + // Delegated event; lazy-add a change handler on descendant inputs + jQuery.event.add( this, "beforeactivate._change", function( e ) { + var elem = e.target; + + if ( rformElems.test( elem.nodeName ) && !jQuery._data( elem, "changeBubbles" ) ) { + jQuery.event.add( elem, "change._change", function( event ) { + if ( this.parentNode && !event.isSimulated && !event.isTrigger ) { + jQuery.event.simulate( "change", this.parentNode, event, true ); + } + }); + jQuery._data( elem, "changeBubbles", true ); + } + }); + }, + + handle: function( event ) { + var elem = event.target; + + // Swallow native change events from checkbox/radio, we already triggered them above + if ( this !== elem || event.isSimulated || event.isTrigger || (elem.type !== "radio" && elem.type !== "checkbox") ) { + return event.handleObj.handler.apply( this, arguments ); + } + }, + + teardown: function() { + jQuery.event.remove( this, "._change" ); + + return !rformElems.test( this.nodeName ); + } + }; +} + +// Create "bubbling" focus and blur events +if ( !jQuery.support.focusinBubbles ) { + jQuery.each({ focus: "focusin", blur: "focusout" }, function( orig, fix ) { + + // Attach a single capturing handler while someone wants focusin/focusout + var attaches = 0, + handler = function( event ) { + jQuery.event.simulate( fix, event.target, jQuery.event.fix( event ), true ); + }; + + jQuery.event.special[ fix ] = { + setup: function() { + if ( attaches++ === 0 ) { + document.addEventListener( orig, handler, true ); + } + }, + teardown: function() { + if ( --attaches === 0 ) { + document.removeEventListener( orig, handler, true ); + } + } + }; + }); +} + +jQuery.fn.extend({ + + on: function( types, selector, data, fn, /*INTERNAL*/ one ) { + var type, origFn; + + // Types can be a map of types/handlers + if ( typeof types === "object" ) { + // ( types-Object, selector, data ) + if ( typeof selector !== "string" ) { + // ( types-Object, data ) + data = data || selector; + selector = undefined; + } + for ( type in types ) { + this.on( type, selector, data, types[ type ], one ); + } + return this; + } + + if ( data == null && fn == null ) { + // ( types, fn ) + fn = selector; + data = selector = undefined; + } else if ( fn == null ) { + if ( typeof selector === "string" ) { + // ( types, selector, fn ) + fn = data; + data = undefined; + } else { + // ( types, data, fn ) + fn = data; + data = selector; + selector = undefined; + } + } + if ( fn === false ) { + fn = returnFalse; + } else if ( !fn ) { + return this; + } + + if ( one === 1 ) { + origFn = fn; + fn = function( event ) { + // Can use an empty set, since event contains the info + jQuery().off( event ); + return origFn.apply( this, arguments ); + }; + // Use same guid so caller can remove using origFn + fn.guid = origFn.guid || ( origFn.guid = jQuery.guid++ ); + } + return this.each( function() { + jQuery.event.add( this, types, fn, data, selector ); + }); + }, + one: function( types, selector, data, fn ) { + return this.on( types, selector, data, fn, 1 ); + }, + off: function( types, selector, fn ) { + var handleObj, type; + if ( types && types.preventDefault && types.handleObj ) { + // ( event ) dispatched jQuery.Event + handleObj = types.handleObj; + jQuery( types.delegateTarget ).off( + handleObj.namespace ? handleObj.origType + "." + handleObj.namespace : handleObj.origType, + handleObj.selector, + handleObj.handler + ); + return this; + } + if ( typeof types === "object" ) { + // ( types-object [, selector] ) + for ( type in types ) { + this.off( type, selector, types[ type ] ); + } + return this; + } + if ( selector === false || typeof selector === "function" ) { + // ( types [, fn] ) + fn = selector; + selector = undefined; + } + if ( fn === false ) { + fn = returnFalse; + } + return this.each(function() { + jQuery.event.remove( this, types, fn, selector ); + }); + }, + + trigger: function( type, data ) { + return this.each(function() { + jQuery.event.trigger( type, data, this ); + }); + }, + triggerHandler: function( type, data ) { + var elem = this[0]; + if ( elem ) { + return jQuery.event.trigger( type, data, elem, true ); + } + } +}); +var isSimple = /^.[^:#\[\.,]*$/, + rparentsprev = /^(?:parents|prev(?:Until|All))/, + rneedsContext = jQuery.expr.match.needsContext, + // methods guaranteed to produce a unique set when starting from a unique set + guaranteedUnique = { + children: true, + contents: true, + next: true, + prev: true + }; + +jQuery.fn.extend({ + find: function( selector ) { + var i, + ret = [], + self = this, + len = self.length; + + if ( typeof selector !== "string" ) { + return this.pushStack( jQuery( selector ).filter(function() { + for ( i = 0; i < len; i++ ) { + if ( jQuery.contains( self[ i ], this ) ) { + return true; + } + } + }) ); + } + + for ( i = 0; i < len; i++ ) { + jQuery.find( selector, self[ i ], ret ); + } + + // Needed because $( selector, context ) becomes $( context ).find( selector ) + ret = this.pushStack( len > 1 ? jQuery.unique( ret ) : ret ); + ret.selector = this.selector ? this.selector + " " + selector : selector; + return ret; + }, + + has: function( target ) { + var i, + targets = jQuery( target, this ), + len = targets.length; + + return this.filter(function() { + for ( i = 0; i < len; i++ ) { + if ( jQuery.contains( this, targets[i] ) ) { + return true; + } + } + }); + }, + + not: function( selector ) { + return this.pushStack( winnow(this, selector || [], true) ); + }, + + filter: function( selector ) { + return this.pushStack( winnow(this, selector || [], false) ); + }, + + is: function( selector ) { + return !!winnow( + this, + + // If this is a positional/relative selector, check membership in the returned set + // so $("p:first").is("p:last") won't return true for a doc with two "p". + typeof selector === "string" && rneedsContext.test( selector ) ? + jQuery( selector ) : + selector || [], + false + ).length; + }, + + closest: function( selectors, context ) { + var cur, + i = 0, + l = this.length, + ret = [], + pos = rneedsContext.test( selectors ) || typeof selectors !== "string" ? + jQuery( selectors, context || this.context ) : + 0; + + for ( ; i < l; i++ ) { + for ( cur = this[i]; cur && cur !== context; cur = cur.parentNode ) { + // Always skip document fragments + if ( cur.nodeType < 11 && (pos ? + pos.index(cur) > -1 : + + // Don't pass non-elements to Sizzle + cur.nodeType === 1 && + jQuery.find.matchesSelector(cur, selectors)) ) { + + cur = ret.push( cur ); + break; + } + } + } + + return this.pushStack( ret.length > 1 ? jQuery.unique( ret ) : ret ); + }, + + // Determine the position of an element within + // the matched set of elements + index: function( elem ) { + + // No argument, return index in parent + if ( !elem ) { + return ( this[0] && this[0].parentNode ) ? this.first().prevAll().length : -1; + } + + // index in selector + if ( typeof elem === "string" ) { + return jQuery.inArray( this[0], jQuery( elem ) ); + } + + // Locate the position of the desired element + return jQuery.inArray( + // If it receives a jQuery object, the first element is used + elem.jquery ? elem[0] : elem, this ); + }, + + add: function( selector, context ) { + var set = typeof selector === "string" ? + jQuery( selector, context ) : + jQuery.makeArray( selector && selector.nodeType ? [ selector ] : selector ), + all = jQuery.merge( this.get(), set ); + + return this.pushStack( jQuery.unique(all) ); + }, + + addBack: function( selector ) { + return this.add( selector == null ? + this.prevObject : this.prevObject.filter(selector) + ); + } +}); + +function sibling( cur, dir ) { + do { + cur = cur[ dir ]; + } while ( cur && cur.nodeType !== 1 ); + + return cur; +} + +jQuery.each({ + parent: function( elem ) { + var parent = elem.parentNode; + return parent && parent.nodeType !== 11 ? parent : null; + }, + parents: function( elem ) { + return jQuery.dir( elem, "parentNode" ); + }, + parentsUntil: function( elem, i, until ) { + return jQuery.dir( elem, "parentNode", until ); + }, + next: function( elem ) { + return sibling( elem, "nextSibling" ); + }, + prev: function( elem ) { + return sibling( elem, "previousSibling" ); + }, + nextAll: function( elem ) { + return jQuery.dir( elem, "nextSibling" ); + }, + prevAll: function( elem ) { + return jQuery.dir( elem, "previousSibling" ); + }, + nextUntil: function( elem, i, until ) { + return jQuery.dir( elem, "nextSibling", until ); + }, + prevUntil: function( elem, i, until ) { + return jQuery.dir( elem, "previousSibling", until ); + }, + siblings: function( elem ) { + return jQuery.sibling( ( elem.parentNode || {} ).firstChild, elem ); + }, + children: function( elem ) { + return jQuery.sibling( elem.firstChild ); + }, + contents: function( elem ) { + return jQuery.nodeName( elem, "iframe" ) ? + elem.contentDocument || elem.contentWindow.document : + jQuery.merge( [], elem.childNodes ); + } +}, function( name, fn ) { + jQuery.fn[ name ] = function( until, selector ) { + var ret = jQuery.map( this, fn, until ); + + if ( name.slice( -5 ) !== "Until" ) { + selector = until; + } + + if ( selector && typeof selector === "string" ) { + ret = jQuery.filter( selector, ret ); + } + + if ( this.length > 1 ) { + // Remove duplicates + if ( !guaranteedUnique[ name ] ) { + ret = jQuery.unique( ret ); + } + + // Reverse order for parents* and prev-derivatives + if ( rparentsprev.test( name ) ) { + ret = ret.reverse(); + } + } + + return this.pushStack( ret ); + }; +}); + +jQuery.extend({ + filter: function( expr, elems, not ) { + var elem = elems[ 0 ]; + + if ( not ) { + expr = ":not(" + expr + ")"; + } + + return elems.length === 1 && elem.nodeType === 1 ? + jQuery.find.matchesSelector( elem, expr ) ? [ elem ] : [] : + jQuery.find.matches( expr, jQuery.grep( elems, function( elem ) { + return elem.nodeType === 1; + })); + }, + + dir: function( elem, dir, until ) { + var matched = [], + cur = elem[ dir ]; + + while ( cur && cur.nodeType !== 9 && (until === undefined || cur.nodeType !== 1 || !jQuery( cur ).is( until )) ) { + if ( cur.nodeType === 1 ) { + matched.push( cur ); + } + cur = cur[dir]; + } + return matched; + }, + + sibling: function( n, elem ) { + var r = []; + + for ( ; n; n = n.nextSibling ) { + if ( n.nodeType === 1 && n !== elem ) { + r.push( n ); + } + } + + return r; + } +}); + +// Implement the identical functionality for filter and not +function winnow( elements, qualifier, not ) { + if ( jQuery.isFunction( qualifier ) ) { + return jQuery.grep( elements, function( elem, i ) { + /* jshint -W018 */ + return !!qualifier.call( elem, i, elem ) !== not; + }); + + } + + if ( qualifier.nodeType ) { + return jQuery.grep( elements, function( elem ) { + return ( elem === qualifier ) !== not; + }); + + } + + if ( typeof qualifier === "string" ) { + if ( isSimple.test( qualifier ) ) { + return jQuery.filter( qualifier, elements, not ); + } + + qualifier = jQuery.filter( qualifier, elements ); + } + + return jQuery.grep( elements, function( elem ) { + return ( jQuery.inArray( elem, qualifier ) >= 0 ) !== not; + }); +} +function createSafeFragment( document ) { + var list = nodeNames.split( "|" ), + safeFrag = document.createDocumentFragment(); + + if ( safeFrag.createElement ) { + while ( list.length ) { + safeFrag.createElement( + list.pop() + ); + } + } + return safeFrag; +} + +var nodeNames = "abbr|article|aside|audio|bdi|canvas|data|datalist|details|figcaption|figure|footer|" + + "header|hgroup|mark|meter|nav|output|progress|section|summary|time|video", + rinlinejQuery = / jQuery\d+="(?:null|\d+)"/g, + rnoshimcache = new RegExp("<(?:" + nodeNames + ")[\\s/>]", "i"), + rleadingWhitespace = /^\s+/, + rxhtmlTag = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi, + rtagName = /<([\w:]+)/, + rtbody = /\s*$/g, + + // We have to close these tags to support XHTML (#13200) + wrapMap = { + option: [ 1, "" ], + legend: [ 1, "
", "
" ], + area: [ 1, "", "" ], + param: [ 1, "", "" ], + thead: [ 1, "", "
" ], + tr: [ 2, "", "
" ], + col: [ 2, "", "
" ], + td: [ 3, "", "
" ], + + // IE6-8 can't serialize link, script, style, or any html5 (NoScope) tags, + // unless wrapped in a div with non-breaking characters in front of it. + _default: jQuery.support.htmlSerialize ? [ 0, "", "" ] : [ 1, "X
", "
" ] + }, + safeFragment = createSafeFragment( document ), + fragmentDiv = safeFragment.appendChild( document.createElement("div") ); + +wrapMap.optgroup = wrapMap.option; +wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead; +wrapMap.th = wrapMap.td; + +jQuery.fn.extend({ + text: function( value ) { + return jQuery.access( this, function( value ) { + return value === undefined ? + jQuery.text( this ) : + this.empty().append( ( this[0] && this[0].ownerDocument || document ).createTextNode( value ) ); + }, null, value, arguments.length ); + }, + + append: function() { + return this.domManip( arguments, function( elem ) { + if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { + var target = manipulationTarget( this, elem ); + target.appendChild( elem ); + } + }); + }, + + prepend: function() { + return this.domManip( arguments, function( elem ) { + if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { + var target = manipulationTarget( this, elem ); + target.insertBefore( elem, target.firstChild ); + } + }); + }, + + before: function() { + return this.domManip( arguments, function( elem ) { + if ( this.parentNode ) { + this.parentNode.insertBefore( elem, this ); + } + }); + }, + + after: function() { + return this.domManip( arguments, function( elem ) { + if ( this.parentNode ) { + this.parentNode.insertBefore( elem, this.nextSibling ); + } + }); + }, + + // keepData is for internal use only--do not document + remove: function( selector, keepData ) { + var elem, + elems = selector ? jQuery.filter( selector, this ) : this, + i = 0; + + for ( ; (elem = elems[i]) != null; i++ ) { + + if ( !keepData && elem.nodeType === 1 ) { + jQuery.cleanData( getAll( elem ) ); + } + + if ( elem.parentNode ) { + if ( keepData && jQuery.contains( elem.ownerDocument, elem ) ) { + setGlobalEval( getAll( elem, "script" ) ); + } + elem.parentNode.removeChild( elem ); + } + } + + return this; + }, + + empty: function() { + var elem, + i = 0; + + for ( ; (elem = this[i]) != null; i++ ) { + // Remove element nodes and prevent memory leaks + if ( elem.nodeType === 1 ) { + jQuery.cleanData( getAll( elem, false ) ); + } + + // Remove any remaining nodes + while ( elem.firstChild ) { + elem.removeChild( elem.firstChild ); + } + + // If this is a select, ensure that it displays empty (#12336) + // Support: IE<9 + if ( elem.options && jQuery.nodeName( elem, "select" ) ) { + elem.options.length = 0; + } + } + + return this; + }, + + clone: function( dataAndEvents, deepDataAndEvents ) { + dataAndEvents = dataAndEvents == null ? false : dataAndEvents; + deepDataAndEvents = deepDataAndEvents == null ? dataAndEvents : deepDataAndEvents; + + return this.map( function () { + return jQuery.clone( this, dataAndEvents, deepDataAndEvents ); + }); + }, + + html: function( value ) { + return jQuery.access( this, function( value ) { + var elem = this[0] || {}, + i = 0, + l = this.length; + + if ( value === undefined ) { + return elem.nodeType === 1 ? + elem.innerHTML.replace( rinlinejQuery, "" ) : + undefined; + } + + // See if we can take a shortcut and just use innerHTML + if ( typeof value === "string" && !rnoInnerhtml.test( value ) && + ( jQuery.support.htmlSerialize || !rnoshimcache.test( value ) ) && + ( jQuery.support.leadingWhitespace || !rleadingWhitespace.test( value ) ) && + !wrapMap[ ( rtagName.exec( value ) || ["", ""] )[1].toLowerCase() ] ) { + + value = value.replace( rxhtmlTag, "<$1>" ); + + try { + for (; i < l; i++ ) { + // Remove element nodes and prevent memory leaks + elem = this[i] || {}; + if ( elem.nodeType === 1 ) { + jQuery.cleanData( getAll( elem, false ) ); + elem.innerHTML = value; + } + } + + elem = 0; + + // If using innerHTML throws an exception, use the fallback method + } catch(e) {} + } + + if ( elem ) { + this.empty().append( value ); + } + }, null, value, arguments.length ); + }, + + replaceWith: function() { + var + // Snapshot the DOM in case .domManip sweeps something relevant into its fragment + args = jQuery.map( this, function( elem ) { + return [ elem.nextSibling, elem.parentNode ]; + }), + i = 0; + + // Make the changes, replacing each context element with the new content + this.domManip( arguments, function( elem ) { + var next = args[ i++ ], + parent = args[ i++ ]; + + if ( parent ) { + // Don't use the snapshot next if it has moved (#13810) + if ( next && next.parentNode !== parent ) { + next = this.nextSibling; + } + jQuery( this ).remove(); + parent.insertBefore( elem, next ); + } + // Allow new content to include elements from the context set + }, true ); + + // Force removal if there was no new content (e.g., from empty arguments) + return i ? this : this.remove(); + }, + + detach: function( selector ) { + return this.remove( selector, true ); + }, + + domManip: function( args, callback, allowIntersection ) { + + // Flatten any nested arrays + args = core_concat.apply( [], args ); + + var first, node, hasScripts, + scripts, doc, fragment, + i = 0, + l = this.length, + set = this, + iNoClone = l - 1, + value = args[0], + isFunction = jQuery.isFunction( value ); + + // We can't cloneNode fragments that contain checked, in WebKit + if ( isFunction || !( l <= 1 || typeof value !== "string" || jQuery.support.checkClone || !rchecked.test( value ) ) ) { + return this.each(function( index ) { + var self = set.eq( index ); + if ( isFunction ) { + args[0] = value.call( this, index, self.html() ); + } + self.domManip( args, callback, allowIntersection ); + }); + } + + if ( l ) { + fragment = jQuery.buildFragment( args, this[ 0 ].ownerDocument, false, !allowIntersection && this ); + first = fragment.firstChild; + + if ( fragment.childNodes.length === 1 ) { + fragment = first; + } + + if ( first ) { + scripts = jQuery.map( getAll( fragment, "script" ), disableScript ); + hasScripts = scripts.length; + + // Use the original fragment for the last item instead of the first because it can end up + // being emptied incorrectly in certain situations (#8070). + for ( ; i < l; i++ ) { + node = fragment; + + if ( i !== iNoClone ) { + node = jQuery.clone( node, true, true ); + + // Keep references to cloned scripts for later restoration + if ( hasScripts ) { + jQuery.merge( scripts, getAll( node, "script" ) ); + } + } + + callback.call( this[i], node, i ); + } + + if ( hasScripts ) { + doc = scripts[ scripts.length - 1 ].ownerDocument; + + // Reenable scripts + jQuery.map( scripts, restoreScript ); + + // Evaluate executable scripts on first document insertion + for ( i = 0; i < hasScripts; i++ ) { + node = scripts[ i ]; + if ( rscriptType.test( node.type || "" ) && + !jQuery._data( node, "globalEval" ) && jQuery.contains( doc, node ) ) { + + if ( node.src ) { + // Hope ajax is available... + jQuery._evalUrl( node.src ); + } else { + jQuery.globalEval( ( node.text || node.textContent || node.innerHTML || "" ).replace( rcleanScript, "" ) ); + } + } + } + } + + // Fix #11809: Avoid leaking memory + fragment = first = null; + } + } + + return this; + } +}); + +// Support: IE<8 +// Manipulating tables requires a tbody +function manipulationTarget( elem, content ) { + return jQuery.nodeName( elem, "table" ) && + jQuery.nodeName( content.nodeType === 1 ? content : content.firstChild, "tr" ) ? + + elem.getElementsByTagName("tbody")[0] || + elem.appendChild( elem.ownerDocument.createElement("tbody") ) : + elem; +} + +// Replace/restore the type attribute of script elements for safe DOM manipulation +function disableScript( elem ) { + elem.type = (jQuery.find.attr( elem, "type" ) !== null) + "/" + elem.type; + return elem; +} +function restoreScript( elem ) { + var match = rscriptTypeMasked.exec( elem.type ); + if ( match ) { + elem.type = match[1]; + } else { + elem.removeAttribute("type"); + } + return elem; +} + +// Mark scripts as having already been evaluated +function setGlobalEval( elems, refElements ) { + var elem, + i = 0; + for ( ; (elem = elems[i]) != null; i++ ) { + jQuery._data( elem, "globalEval", !refElements || jQuery._data( refElements[i], "globalEval" ) ); + } +} + +function cloneCopyEvent( src, dest ) { + + if ( dest.nodeType !== 1 || !jQuery.hasData( src ) ) { + return; + } + + var type, i, l, + oldData = jQuery._data( src ), + curData = jQuery._data( dest, oldData ), + events = oldData.events; + + if ( events ) { + delete curData.handle; + curData.events = {}; + + for ( type in events ) { + for ( i = 0, l = events[ type ].length; i < l; i++ ) { + jQuery.event.add( dest, type, events[ type ][ i ] ); + } + } + } + + // make the cloned public data object a copy from the original + if ( curData.data ) { + curData.data = jQuery.extend( {}, curData.data ); + } +} + +function fixCloneNodeIssues( src, dest ) { + var nodeName, e, data; + + // We do not need to do anything for non-Elements + if ( dest.nodeType !== 1 ) { + return; + } + + nodeName = dest.nodeName.toLowerCase(); + + // IE6-8 copies events bound via attachEvent when using cloneNode. + if ( !jQuery.support.noCloneEvent && dest[ jQuery.expando ] ) { + data = jQuery._data( dest ); + + for ( e in data.events ) { + jQuery.removeEvent( dest, e, data.handle ); + } + + // Event data gets referenced instead of copied if the expando gets copied too + dest.removeAttribute( jQuery.expando ); + } + + // IE blanks contents when cloning scripts, and tries to evaluate newly-set text + if ( nodeName === "script" && dest.text !== src.text ) { + disableScript( dest ).text = src.text; + restoreScript( dest ); + + // IE6-10 improperly clones children of object elements using classid. + // IE10 throws NoModificationAllowedError if parent is null, #12132. + } else if ( nodeName === "object" ) { + if ( dest.parentNode ) { + dest.outerHTML = src.outerHTML; + } + + // This path appears unavoidable for IE9. When cloning an object + // element in IE9, the outerHTML strategy above is not sufficient. + // If the src has innerHTML and the destination does not, + // copy the src.innerHTML into the dest.innerHTML. #10324 + if ( jQuery.support.html5Clone && ( src.innerHTML && !jQuery.trim(dest.innerHTML) ) ) { + dest.innerHTML = src.innerHTML; + } + + } else if ( nodeName === "input" && manipulation_rcheckableType.test( src.type ) ) { + // IE6-8 fails to persist the checked state of a cloned checkbox + // or radio button. Worse, IE6-7 fail to give the cloned element + // a checked appearance if the defaultChecked value isn't also set + + dest.defaultChecked = dest.checked = src.checked; + + // IE6-7 get confused and end up setting the value of a cloned + // checkbox/radio button to an empty string instead of "on" + if ( dest.value !== src.value ) { + dest.value = src.value; + } + + // IE6-8 fails to return the selected option to the default selected + // state when cloning options + } else if ( nodeName === "option" ) { + dest.defaultSelected = dest.selected = src.defaultSelected; + + // IE6-8 fails to set the defaultValue to the correct value when + // cloning other types of input fields + } else if ( nodeName === "input" || nodeName === "textarea" ) { + dest.defaultValue = src.defaultValue; + } +} + +jQuery.each({ + appendTo: "append", + prependTo: "prepend", + insertBefore: "before", + insertAfter: "after", + replaceAll: "replaceWith" +}, function( name, original ) { + jQuery.fn[ name ] = function( selector ) { + var elems, + i = 0, + ret = [], + insert = jQuery( selector ), + last = insert.length - 1; + + for ( ; i <= last; i++ ) { + elems = i === last ? this : this.clone(true); + jQuery( insert[i] )[ original ]( elems ); + + // Modern browsers can apply jQuery collections as arrays, but oldIE needs a .get() + core_push.apply( ret, elems.get() ); + } + + return this.pushStack( ret ); + }; +}); + +function getAll( context, tag ) { + var elems, elem, + i = 0, + found = typeof context.getElementsByTagName !== core_strundefined ? context.getElementsByTagName( tag || "*" ) : + typeof context.querySelectorAll !== core_strundefined ? context.querySelectorAll( tag || "*" ) : + undefined; + + if ( !found ) { + for ( found = [], elems = context.childNodes || context; (elem = elems[i]) != null; i++ ) { + if ( !tag || jQuery.nodeName( elem, tag ) ) { + found.push( elem ); + } else { + jQuery.merge( found, getAll( elem, tag ) ); + } + } + } + + return tag === undefined || tag && jQuery.nodeName( context, tag ) ? + jQuery.merge( [ context ], found ) : + found; +} + +// Used in buildFragment, fixes the defaultChecked property +function fixDefaultChecked( elem ) { + if ( manipulation_rcheckableType.test( elem.type ) ) { + elem.defaultChecked = elem.checked; + } +} + +jQuery.extend({ + clone: function( elem, dataAndEvents, deepDataAndEvents ) { + var destElements, node, clone, i, srcElements, + inPage = jQuery.contains( elem.ownerDocument, elem ); + + if ( jQuery.support.html5Clone || jQuery.isXMLDoc(elem) || !rnoshimcache.test( "<" + elem.nodeName + ">" ) ) { + clone = elem.cloneNode( true ); + + // IE<=8 does not properly clone detached, unknown element nodes + } else { + fragmentDiv.innerHTML = elem.outerHTML; + fragmentDiv.removeChild( clone = fragmentDiv.firstChild ); + } + + if ( (!jQuery.support.noCloneEvent || !jQuery.support.noCloneChecked) && + (elem.nodeType === 1 || elem.nodeType === 11) && !jQuery.isXMLDoc(elem) ) { + + // We eschew Sizzle here for performance reasons: http://jsperf.com/getall-vs-sizzle/2 + destElements = getAll( clone ); + srcElements = getAll( elem ); + + // Fix all IE cloning issues + for ( i = 0; (node = srcElements[i]) != null; ++i ) { + // Ensure that the destination node is not null; Fixes #9587 + if ( destElements[i] ) { + fixCloneNodeIssues( node, destElements[i] ); + } + } + } + + // Copy the events from the original to the clone + if ( dataAndEvents ) { + if ( deepDataAndEvents ) { + srcElements = srcElements || getAll( elem ); + destElements = destElements || getAll( clone ); + + for ( i = 0; (node = srcElements[i]) != null; i++ ) { + cloneCopyEvent( node, destElements[i] ); + } + } else { + cloneCopyEvent( elem, clone ); + } + } + + // Preserve script evaluation history + destElements = getAll( clone, "script" ); + if ( destElements.length > 0 ) { + setGlobalEval( destElements, !inPage && getAll( elem, "script" ) ); + } + + destElements = srcElements = node = null; + + // Return the cloned set + return clone; + }, + + buildFragment: function( elems, context, scripts, selection ) { + var j, elem, contains, + tmp, tag, tbody, wrap, + l = elems.length, + + // Ensure a safe fragment + safe = createSafeFragment( context ), + + nodes = [], + i = 0; + + for ( ; i < l; i++ ) { + elem = elems[ i ]; + + if ( elem || elem === 0 ) { + + // Add nodes directly + if ( jQuery.type( elem ) === "object" ) { + jQuery.merge( nodes, elem.nodeType ? [ elem ] : elem ); + + // Convert non-html into a text node + } else if ( !rhtml.test( elem ) ) { + nodes.push( context.createTextNode( elem ) ); + + // Convert html into DOM nodes + } else { + tmp = tmp || safe.appendChild( context.createElement("div") ); + + // Deserialize a standard representation + tag = ( rtagName.exec( elem ) || ["", ""] )[1].toLowerCase(); + wrap = wrapMap[ tag ] || wrapMap._default; + + tmp.innerHTML = wrap[1] + elem.replace( rxhtmlTag, "<$1>" ) + wrap[2]; + + // Descend through wrappers to the right content + j = wrap[0]; + while ( j-- ) { + tmp = tmp.lastChild; + } + + // Manually add leading whitespace removed by IE + if ( !jQuery.support.leadingWhitespace && rleadingWhitespace.test( elem ) ) { + nodes.push( context.createTextNode( rleadingWhitespace.exec( elem )[0] ) ); + } + + // Remove IE's autoinserted from table fragments + if ( !jQuery.support.tbody ) { + + // String was a , *may* have spurious + elem = tag === "table" && !rtbody.test( elem ) ? + tmp.firstChild : + + // String was a bare or + wrap[1] === "
" && !rtbody.test( elem ) ? + tmp : + 0; + + j = elem && elem.childNodes.length; + while ( j-- ) { + if ( jQuery.nodeName( (tbody = elem.childNodes[j]), "tbody" ) && !tbody.childNodes.length ) { + elem.removeChild( tbody ); + } + } + } + + jQuery.merge( nodes, tmp.childNodes ); + + // Fix #12392 for WebKit and IE > 9 + tmp.textContent = ""; + + // Fix #12392 for oldIE + while ( tmp.firstChild ) { + tmp.removeChild( tmp.firstChild ); + } + + // Remember the top-level container for proper cleanup + tmp = safe.lastChild; + } + } + } + + // Fix #11356: Clear elements from fragment + if ( tmp ) { + safe.removeChild( tmp ); + } + + // Reset defaultChecked for any radios and checkboxes + // about to be appended to the DOM in IE 6/7 (#8060) + if ( !jQuery.support.appendChecked ) { + jQuery.grep( getAll( nodes, "input" ), fixDefaultChecked ); + } + + i = 0; + while ( (elem = nodes[ i++ ]) ) { + + // #4087 - If origin and destination elements are the same, and this is + // that element, do not do anything + if ( selection && jQuery.inArray( elem, selection ) !== -1 ) { + continue; + } + + contains = jQuery.contains( elem.ownerDocument, elem ); + + // Append to fragment + tmp = getAll( safe.appendChild( elem ), "script" ); + + // Preserve script evaluation history + if ( contains ) { + setGlobalEval( tmp ); + } + + // Capture executables + if ( scripts ) { + j = 0; + while ( (elem = tmp[ j++ ]) ) { + if ( rscriptType.test( elem.type || "" ) ) { + scripts.push( elem ); + } + } + } + } + + tmp = null; + + return safe; + }, + + cleanData: function( elems, /* internal */ acceptData ) { + var elem, type, id, data, + i = 0, + internalKey = jQuery.expando, + cache = jQuery.cache, + deleteExpando = jQuery.support.deleteExpando, + special = jQuery.event.special; + + for ( ; (elem = elems[i]) != null; i++ ) { + + if ( acceptData || jQuery.acceptData( elem ) ) { + + id = elem[ internalKey ]; + data = id && cache[ id ]; + + if ( data ) { + if ( data.events ) { + for ( type in data.events ) { + if ( special[ type ] ) { + jQuery.event.remove( elem, type ); + + // This is a shortcut to avoid jQuery.event.remove's overhead + } else { + jQuery.removeEvent( elem, type, data.handle ); + } + } + } + + // Remove cache only if it was not already removed by jQuery.event.remove + if ( cache[ id ] ) { + + delete cache[ id ]; + + // IE does not allow us to delete expando properties from nodes, + // nor does it have a removeAttribute function on Document nodes; + // we must handle all of these cases + if ( deleteExpando ) { + delete elem[ internalKey ]; + + } else if ( typeof elem.removeAttribute !== core_strundefined ) { + elem.removeAttribute( internalKey ); + + } else { + elem[ internalKey ] = null; + } + + core_deletedIds.push( id ); + } + } + } + } + }, + + _evalUrl: function( url ) { + return jQuery.ajax({ + url: url, + type: "GET", + dataType: "script", + async: false, + global: false, + "throws": true + }); + } +}); +jQuery.fn.extend({ + wrapAll: function( html ) { + if ( jQuery.isFunction( html ) ) { + return this.each(function(i) { + jQuery(this).wrapAll( html.call(this, i) ); + }); + } + + if ( this[0] ) { + // The elements to wrap the target around + var wrap = jQuery( html, this[0].ownerDocument ).eq(0).clone(true); + + if ( this[0].parentNode ) { + wrap.insertBefore( this[0] ); + } + + wrap.map(function() { + var elem = this; + + while ( elem.firstChild && elem.firstChild.nodeType === 1 ) { + elem = elem.firstChild; + } + + return elem; + }).append( this ); + } + + return this; + }, + + wrapInner: function( html ) { + if ( jQuery.isFunction( html ) ) { + return this.each(function(i) { + jQuery(this).wrapInner( html.call(this, i) ); + }); + } + + return this.each(function() { + var self = jQuery( this ), + contents = self.contents(); + + if ( contents.length ) { + contents.wrapAll( html ); + + } else { + self.append( html ); + } + }); + }, + + wrap: function( html ) { + var isFunction = jQuery.isFunction( html ); + + return this.each(function(i) { + jQuery( this ).wrapAll( isFunction ? html.call(this, i) : html ); + }); + }, + + unwrap: function() { + return this.parent().each(function() { + if ( !jQuery.nodeName( this, "body" ) ) { + jQuery( this ).replaceWith( this.childNodes ); + } + }).end(); + } +}); +var iframe, getStyles, curCSS, + ralpha = /alpha\([^)]*\)/i, + ropacity = /opacity\s*=\s*([^)]*)/, + rposition = /^(top|right|bottom|left)$/, + // swappable if display is none or starts with table except "table", "table-cell", or "table-caption" + // see here for display values: https://developer.mozilla.org/en-US/docs/CSS/display + rdisplayswap = /^(none|table(?!-c[ea]).+)/, + rmargin = /^margin/, + rnumsplit = new RegExp( "^(" + core_pnum + ")(.*)$", "i" ), + rnumnonpx = new RegExp( "^(" + core_pnum + ")(?!px)[a-z%]+$", "i" ), + rrelNum = new RegExp( "^([+-])=(" + core_pnum + ")", "i" ), + elemdisplay = { BODY: "block" }, + + cssShow = { position: "absolute", visibility: "hidden", display: "block" }, + cssNormalTransform = { + letterSpacing: 0, + fontWeight: 400 + }, + + cssExpand = [ "Top", "Right", "Bottom", "Left" ], + cssPrefixes = [ "Webkit", "O", "Moz", "ms" ]; + +// return a css property mapped to a potentially vendor prefixed property +function vendorPropName( style, name ) { + + // shortcut for names that are not vendor prefixed + if ( name in style ) { + return name; + } + + // check for vendor prefixed names + var capName = name.charAt(0).toUpperCase() + name.slice(1), + origName = name, + i = cssPrefixes.length; + + while ( i-- ) { + name = cssPrefixes[ i ] + capName; + if ( name in style ) { + return name; + } + } + + return origName; +} + +function isHidden( elem, el ) { + // isHidden might be called from jQuery#filter function; + // in that case, element will be second argument + elem = el || elem; + return jQuery.css( elem, "display" ) === "none" || !jQuery.contains( elem.ownerDocument, elem ); +} + +function showHide( elements, show ) { + var display, elem, hidden, + values = [], + index = 0, + length = elements.length; + + for ( ; index < length; index++ ) { + elem = elements[ index ]; + if ( !elem.style ) { + continue; + } + + values[ index ] = jQuery._data( elem, "olddisplay" ); + display = elem.style.display; + if ( show ) { + // Reset the inline display of this element to learn if it is + // being hidden by cascaded rules or not + if ( !values[ index ] && display === "none" ) { + elem.style.display = ""; + } + + // Set elements which have been overridden with display: none + // in a stylesheet to whatever the default browser style is + // for such an element + if ( elem.style.display === "" && isHidden( elem ) ) { + values[ index ] = jQuery._data( elem, "olddisplay", css_defaultDisplay(elem.nodeName) ); + } + } else { + + if ( !values[ index ] ) { + hidden = isHidden( elem ); + + if ( display && display !== "none" || !hidden ) { + jQuery._data( elem, "olddisplay", hidden ? display : jQuery.css( elem, "display" ) ); + } + } + } + } + + // Set the display of most of the elements in a second loop + // to avoid the constant reflow + for ( index = 0; index < length; index++ ) { + elem = elements[ index ]; + if ( !elem.style ) { + continue; + } + if ( !show || elem.style.display === "none" || elem.style.display === "" ) { + elem.style.display = show ? values[ index ] || "" : "none"; + } + } + + return elements; +} + +jQuery.fn.extend({ + css: function( name, value ) { + return jQuery.access( this, function( elem, name, value ) { + var len, styles, + map = {}, + i = 0; + + if ( jQuery.isArray( name ) ) { + styles = getStyles( elem ); + len = name.length; + + for ( ; i < len; i++ ) { + map[ name[ i ] ] = jQuery.css( elem, name[ i ], false, styles ); + } + + return map; + } + + return value !== undefined ? + jQuery.style( elem, name, value ) : + jQuery.css( elem, name ); + }, name, value, arguments.length > 1 ); + }, + show: function() { + return showHide( this, true ); + }, + hide: function() { + return showHide( this ); + }, + toggle: function( state ) { + var bool = typeof state === "boolean"; + + return this.each(function() { + if ( bool ? state : isHidden( this ) ) { + jQuery( this ).show(); + } else { + jQuery( this ).hide(); + } + }); + } +}); + +jQuery.extend({ + // Add in style property hooks for overriding the default + // behavior of getting and setting a style property + cssHooks: { + opacity: { + get: function( elem, computed ) { + if ( computed ) { + // We should always get a number back from opacity + var ret = curCSS( elem, "opacity" ); + return ret === "" ? "1" : ret; + } + } + } + }, + + // Don't automatically add "px" to these possibly-unitless properties + cssNumber: { + "columnCount": true, + "fillOpacity": true, + "fontWeight": true, + "lineHeight": true, + "opacity": true, + "orphans": true, + "widows": true, + "zIndex": true, + "zoom": true + }, + + // Add in properties whose names you wish to fix before + // setting or getting the value + cssProps: { + // normalize float css property + "float": jQuery.support.cssFloat ? "cssFloat" : "styleFloat" + }, + + // Get and set the style property on a DOM Node + style: function( elem, name, value, extra ) { + // Don't set styles on text and comment nodes + if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 || !elem.style ) { + return; + } + + // Make sure that we're working with the right name + var ret, type, hooks, + origName = jQuery.camelCase( name ), + style = elem.style; + + name = jQuery.cssProps[ origName ] || ( jQuery.cssProps[ origName ] = vendorPropName( style, origName ) ); + + // gets hook for the prefixed version + // followed by the unprefixed version + hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ]; + + // Check if we're setting a value + if ( value !== undefined ) { + type = typeof value; + + // convert relative number strings (+= or -=) to relative numbers. #7345 + if ( type === "string" && (ret = rrelNum.exec( value )) ) { + value = ( ret[1] + 1 ) * ret[2] + parseFloat( jQuery.css( elem, name ) ); + // Fixes bug #9237 + type = "number"; + } + + // Make sure that NaN and null values aren't set. See: #7116 + if ( value == null || type === "number" && isNaN( value ) ) { + return; + } + + // If a number was passed in, add 'px' to the (except for certain CSS properties) + if ( type === "number" && !jQuery.cssNumber[ origName ] ) { + value += "px"; + } + + // Fixes #8908, it can be done more correctly by specifing setters in cssHooks, + // but it would mean to define eight (for every problematic property) identical functions + if ( !jQuery.support.clearCloneStyle && value === "" && name.indexOf("background") === 0 ) { + style[ name ] = "inherit"; + } + + // If a hook was provided, use that value, otherwise just set the specified value + if ( !hooks || !("set" in hooks) || (value = hooks.set( elem, value, extra )) !== undefined ) { + + // Wrapped to prevent IE from throwing errors when 'invalid' values are provided + // Fixes bug #5509 + try { + style[ name ] = value; + } catch(e) {} + } + + } else { + // If a hook was provided get the non-computed value from there + if ( hooks && "get" in hooks && (ret = hooks.get( elem, false, extra )) !== undefined ) { + return ret; + } + + // Otherwise just get the value from the style object + return style[ name ]; + } + }, + + css: function( elem, name, extra, styles ) { + var num, val, hooks, + origName = jQuery.camelCase( name ); + + // Make sure that we're working with the right name + name = jQuery.cssProps[ origName ] || ( jQuery.cssProps[ origName ] = vendorPropName( elem.style, origName ) ); + + // gets hook for the prefixed version + // followed by the unprefixed version + hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ]; + + // If a hook was provided get the computed value from there + if ( hooks && "get" in hooks ) { + val = hooks.get( elem, true, extra ); + } + + // Otherwise, if a way to get the computed value exists, use that + if ( val === undefined ) { + val = curCSS( elem, name, styles ); + } + + //convert "normal" to computed value + if ( val === "normal" && name in cssNormalTransform ) { + val = cssNormalTransform[ name ]; + } + + // Return, converting to number if forced or a qualifier was provided and val looks numeric + if ( extra === "" || extra ) { + num = parseFloat( val ); + return extra === true || jQuery.isNumeric( num ) ? num || 0 : val; + } + return val; + } +}); + +// NOTE: we've included the "window" in window.getComputedStyle +// because jsdom on node.js will break without it. +if ( window.getComputedStyle ) { + getStyles = function( elem ) { + return window.getComputedStyle( elem, null ); + }; + + curCSS = function( elem, name, _computed ) { + var width, minWidth, maxWidth, + computed = _computed || getStyles( elem ), + + // getPropertyValue is only needed for .css('filter') in IE9, see #12537 + ret = computed ? computed.getPropertyValue( name ) || computed[ name ] : undefined, + style = elem.style; + + if ( computed ) { + + if ( ret === "" && !jQuery.contains( elem.ownerDocument, elem ) ) { + ret = jQuery.style( elem, name ); + } + + // A tribute to the "awesome hack by Dean Edwards" + // Chrome < 17 and Safari 5.0 uses "computed value" instead of "used value" for margin-right + // Safari 5.1.7 (at least) returns percentage for a larger set of values, but width seems to be reliably pixels + // this is against the CSSOM draft spec: http://dev.w3.org/csswg/cssom/#resolved-values + if ( rnumnonpx.test( ret ) && rmargin.test( name ) ) { + + // Remember the original values + width = style.width; + minWidth = style.minWidth; + maxWidth = style.maxWidth; + + // Put in the new values to get a computed value out + style.minWidth = style.maxWidth = style.width = ret; + ret = computed.width; + + // Revert the changed values + style.width = width; + style.minWidth = minWidth; + style.maxWidth = maxWidth; + } + } + + return ret; + }; +} else if ( document.documentElement.currentStyle ) { + getStyles = function( elem ) { + return elem.currentStyle; + }; + + curCSS = function( elem, name, _computed ) { + var left, rs, rsLeft, + computed = _computed || getStyles( elem ), + ret = computed ? computed[ name ] : undefined, + style = elem.style; + + // Avoid setting ret to empty string here + // so we don't default to auto + if ( ret == null && style && style[ name ] ) { + ret = style[ name ]; + } + + // From the awesome hack by Dean Edwards + // http://erik.eae.net/archives/2007/07/27/18.54.15/#comment-102291 + + // If we're not dealing with a regular pixel number + // but a number that has a weird ending, we need to convert it to pixels + // but not position css attributes, as those are proportional to the parent element instead + // and we can't measure the parent instead because it might trigger a "stacking dolls" problem + if ( rnumnonpx.test( ret ) && !rposition.test( name ) ) { + + // Remember the original values + left = style.left; + rs = elem.runtimeStyle; + rsLeft = rs && rs.left; + + // Put in the new values to get a computed value out + if ( rsLeft ) { + rs.left = elem.currentStyle.left; + } + style.left = name === "fontSize" ? "1em" : ret; + ret = style.pixelLeft + "px"; + + // Revert the changed values + style.left = left; + if ( rsLeft ) { + rs.left = rsLeft; + } + } + + return ret === "" ? "auto" : ret; + }; +} + +function setPositiveNumber( elem, value, subtract ) { + var matches = rnumsplit.exec( value ); + return matches ? + // Guard against undefined "subtract", e.g., when used as in cssHooks + Math.max( 0, matches[ 1 ] - ( subtract || 0 ) ) + ( matches[ 2 ] || "px" ) : + value; +} + +function augmentWidthOrHeight( elem, name, extra, isBorderBox, styles ) { + var i = extra === ( isBorderBox ? "border" : "content" ) ? + // If we already have the right measurement, avoid augmentation + 4 : + // Otherwise initialize for horizontal or vertical properties + name === "width" ? 1 : 0, + + val = 0; + + for ( ; i < 4; i += 2 ) { + // both box models exclude margin, so add it if we want it + if ( extra === "margin" ) { + val += jQuery.css( elem, extra + cssExpand[ i ], true, styles ); + } + + if ( isBorderBox ) { + // border-box includes padding, so remove it if we want content + if ( extra === "content" ) { + val -= jQuery.css( elem, "padding" + cssExpand[ i ], true, styles ); + } + + // at this point, extra isn't border nor margin, so remove border + if ( extra !== "margin" ) { + val -= jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles ); + } + } else { + // at this point, extra isn't content, so add padding + val += jQuery.css( elem, "padding" + cssExpand[ i ], true, styles ); + + // at this point, extra isn't content nor padding, so add border + if ( extra !== "padding" ) { + val += jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles ); + } + } + } + + return val; +} + +function getWidthOrHeight( elem, name, extra ) { + + // Start with offset property, which is equivalent to the border-box value + var valueIsBorderBox = true, + val = name === "width" ? elem.offsetWidth : elem.offsetHeight, + styles = getStyles( elem ), + isBorderBox = jQuery.support.boxSizing && jQuery.css( elem, "boxSizing", false, styles ) === "border-box"; + + // some non-html elements return undefined for offsetWidth, so check for null/undefined + // svg - https://bugzilla.mozilla.org/show_bug.cgi?id=649285 + // MathML - https://bugzilla.mozilla.org/show_bug.cgi?id=491668 + if ( val <= 0 || val == null ) { + // Fall back to computed then uncomputed css if necessary + val = curCSS( elem, name, styles ); + if ( val < 0 || val == null ) { + val = elem.style[ name ]; + } + + // Computed unit is not pixels. Stop here and return. + if ( rnumnonpx.test(val) ) { + return val; + } + + // we need the check for style in case a browser which returns unreliable values + // for getComputedStyle silently falls back to the reliable elem.style + valueIsBorderBox = isBorderBox && ( jQuery.support.boxSizingReliable || val === elem.style[ name ] ); + + // Normalize "", auto, and prepare for extra + val = parseFloat( val ) || 0; + } + + // use the active box-sizing model to add/subtract irrelevant styles + return ( val + + augmentWidthOrHeight( + elem, + name, + extra || ( isBorderBox ? "border" : "content" ), + valueIsBorderBox, + styles + ) + ) + "px"; +} + +// Try to determine the default display value of an element +function css_defaultDisplay( nodeName ) { + var doc = document, + display = elemdisplay[ nodeName ]; + + if ( !display ) { + display = actualDisplay( nodeName, doc ); + + // If the simple way fails, read from inside an iframe + if ( display === "none" || !display ) { + // Use the already-created iframe if possible + iframe = ( iframe || + jQuery("