diff options
115 files changed, 3440 insertions, 489 deletions
diff --git a/apps/files/ajax/list.php b/apps/files/ajax/list.php index 2d76b685018..bae3628402f 100644 --- a/apps/files/ajax/list.php +++ b/apps/files/ajax/list.php @@ -17,8 +17,11 @@ $baseUrl = OCP\Util::linkTo('files', 'index.php') . '?dir='; $permissions = $dirInfo->getPermissions(); +$sortAttribute = isset( $_GET['sort'] ) ? $_GET['sort'] : 'name'; +$sortDirection = isset( $_GET['sortdirection'] ) ? ($_GET['sortdirection'] === 'desc') : false; + // make filelist -$files = \OCA\Files\Helper::getFiles($dir); +$files = \OCA\Files\Helper::getFiles($dir, $sortAttribute, $sortDirection); $data['directory'] = $dir; $data['files'] = \OCA\Files\Helper::formatFileInfos($files); diff --git a/apps/files/css/files.css b/apps/files/css/files.css index 533050691d5..2aae73f8bd0 100644 --- a/apps/files/css/files.css +++ b/apps/files/css/files.css @@ -116,10 +116,29 @@ tr:hover span.extension { table tr.mouseOver td { background-color:#eee; } table th { height:24px; padding:0 8px; color:#999; } -table th .name { - position: absolute; - left: 55px; - top: 15px; +table th .columntitle { + display: inline-block; + padding: 15px; + width: 100%; + height: 50px; + box-sizing: border-box; + -moz-box-sizing: border-box; + vertical-align: middle; +} +table th .columntitle.name { + padding-left: 5px; + margin-left: 50px; + max-width: 300px; +} +/* hover effect on sortable column */ +table th a.columntitle:hover { + background-color: #F0F0F0; +} +table th .sort-indicator { + width: 10px; + height: 8px; + margin-left: 10px; + display: inline-block; } table th, table td { border-bottom:1px solid #ddd; text-align:left; font-weight:normal; } table td { @@ -139,8 +158,11 @@ table th#headerName { } table th#headerSize, table td.filesize { min-width: 48px; - padding: 0 16px; text-align: right; + padding: 0; +} +table table td.filesize { + padding: 0 16px; } table th#headerDate, table td.date { -moz-box-sizing: border-box; @@ -197,10 +219,6 @@ table td.filename input.filename { table td.filename a, table td.login, table td.logout, table td.download, table td.upload, table td.create, table td.delete { padding:3px 8px 8px 3px; } table td.filename .nametext, .uploadtext, .modified { float:left; padding:14px 0; } -#modified { - position: absolute; - top: 15px; -} .modified { position: relative; padding-left: 8px; diff --git a/apps/files/js/filelist.js b/apps/files/js/filelist.js index 8896a8e23a8..73a441368bb 100644 --- a/apps/files/js/filelist.js +++ b/apps/files/js/filelist.js @@ -11,6 +11,9 @@ /* global OC, t, n, FileList, FileActions, Files, FileSummary, BreadCrumb */ /* global dragOptions, folderDropOptions */ window.FileList = { + SORT_INDICATOR_ASC_CLASS: 'icon-triangle-s', + SORT_INDICATOR_DESC_CLASS: 'icon-triangle-n', + appName: t('files', 'Files'), isEmpty: true, useUndo:true, @@ -45,18 +48,19 @@ window.FileList = { _selectionSummary: null, /** - * Compare two file info objects, sorting by - * folders first, then by name. + * Sort attribute */ - _fileInfoCompare: function(fileInfo1, fileInfo2) { - if (fileInfo1.type === 'dir' && fileInfo2.type !== 'dir') { - return -1; - } - if (fileInfo1.type !== 'dir' && fileInfo2.type === 'dir') { - return 1; - } - return fileInfo1.name.localeCompare(fileInfo2.name); - }, + _sort: 'name', + + /** + * Sort direction: 'asc' or 'desc' + */ + _sortDirection: 'asc', + + /** + * Sort comparator function for the current sort + */ + _sortComparator: null, /** * Initialize the file list and its components @@ -76,6 +80,8 @@ window.FileList = { this.fileSummary = this._createSummary(); + this.setSort('name', 'asc'); + this.breadcrumb = new BreadCrumb({ onClick: this._onClickBreadCrumb, onDrop: _.bind(this._onDropOnBreadCrumb, this), @@ -86,6 +92,8 @@ window.FileList = { $('#controls').prepend(this.breadcrumb.$el); + this.$el.find('thead th .columntitle').click(_.bind(this._onClickHeader, this)); + $(window).resize(function() { // TODO: debounce this ? var width = $(this).width(); @@ -237,6 +245,27 @@ window.FileList = { }, /** + * Event handler when clicking on a table header + */ + _onClickHeader: function(e) { + var $target = $(e.target); + var sort; + if (!$target.is('a')) { + $target = $target.closest('a'); + } + sort = $target.attr('data-sort'); + if (sort) { + if (this._sort === sort) { + this.setSort(sort, (this._sortDirection === 'desc')?'asc':'desc'); + } + else { + this.setSort(sort, 'asc'); + } + this.reload(); + } + }, + + /** * Event handler when clicking on a bread crumb */ _onClickBreadCrumb: function(e) { @@ -685,8 +714,6 @@ window.FileList = { previousDir: currentDir } )); - this._selectedFiles = {}; - this._selectionSummary.clear(); this.reload(); }, linkTo: function(dir) { @@ -723,9 +750,33 @@ window.FileList = { this.breadcrumb.setDirectory(this.getCurrentDirectory()); }, /** + * Sets the current sorting and refreshes the list + * + * @param sort sort attribute name + * @param direction sort direction, one of "asc" or "desc" + */ + setSort: function(sort, direction) { + var comparator = this.Comparators[sort] || this.Comparators.name; + this._sort = sort; + this._sortDirection = (direction === 'desc')?'desc':'asc'; + this._sortComparator = comparator; + if (direction === 'desc') { + this._sortComparator = function(fileInfo1, fileInfo2) { + return -comparator(fileInfo1, fileInfo2); + }; + } + this.$el.find('thead th .sort-indicator') + .removeClass(this.SORT_INDICATOR_ASC_CLASS + ' ' + this.SORT_INDICATOR_DESC_CLASS); + this.$el.find('thead th.column-' + sort + ' .sort-indicator') + .addClass(direction === 'desc' ? this.SORT_INDICATOR_DESC_CLASS : this.SORT_INDICATOR_ASC_CLASS); + }, + /** * @brief Reloads the file list using ajax call */ reload: function() { + this._selectedFiles = {}; + this._selectionSummary.clear(); + this.$el.find('#select_all').prop('checked', false); FileList.showMask(); if (FileList._reloadCall) { FileList._reloadCall.abort(); @@ -733,7 +784,9 @@ window.FileList = { FileList._reloadCall = $.ajax({ url: Files.getAjaxUrl('list'), data: { - dir : $('#dir').val() + dir: $('#dir').val(), + sort: FileList._sort, + sortdirection: FileList._sortDirection }, error: function(result) { FileList.reloadCallback(result); @@ -859,7 +912,7 @@ window.FileList = { */ _findInsertionIndex: function(fileData) { var index = 0; - while (index < this.files.length && this._fileInfoCompare(fileData, this.files[index]) > 0) { + while (index < this.files.length && this._sortComparator(fileData, this.files[index]) > 0) { index++; } return index; @@ -924,7 +977,7 @@ window.FileList = { OC.dialogs.alert(t('files', 'Error moving file'), t('files', 'Error')); } $td.css('background-image', oldBackgroundImage); - }); + }); }); }, @@ -1221,15 +1274,15 @@ window.FileList = { updateSelectionSummary: function() { var summary = this._selectionSummary.summary; if (summary.totalFiles === 0 && summary.totalDirs === 0) { - $('#headerName span.name').text(t('files','Name')); - $('#headerSize').text(t('files','Size')); - $('#modified').text(t('files','Modified')); + $('#headerName a.name>span:first').text(t('files','Name')); + $('#headerSize a>span:first').text(t('files','Size')); + $('#modified a>span:first').text(t('files','Modified')); $('table').removeClass('multiselect'); $('.selectedActions').addClass('hidden'); } else { $('.selectedActions').removeClass('hidden'); - $('#headerSize').text(OC.Util.humanFileSize(summary.totalSize)); + $('#headerSize a>span:first').text(OC.Util.humanFileSize(summary.totalSize)); var selection = ''; if (summary.totalDirs > 0) { selection += n('files', '%n folder', '%n folders', summary.totalDirs); @@ -1240,8 +1293,8 @@ window.FileList = { if (summary.totalFiles > 0) { selection += n('files', '%n file', '%n files', summary.totalFiles); } - $('#headerName span.name').text(selection); - $('#modified').text(''); + $('#headerName a.name>span:first').text(selection); + $('#modified a>span:first').text(''); $('table').addClass('multiselect'); } }, @@ -1545,3 +1598,49 @@ $(document).ready(function() { }, 0); }); +/** + * Sort comparators. + */ +FileList.Comparators = { + /** + * Compares two file infos by name, making directories appear + * first. + * + * @param fileInfo1 file info + * @param fileInfo2 file info + * @return -1 if the first file must appear before the second one, + * 0 if they are identify, 1 otherwise. + */ + name: function(fileInfo1, fileInfo2) { + if (fileInfo1.type === 'dir' && fileInfo2.type !== 'dir') { + return -1; + } + if (fileInfo1.type !== 'dir' && fileInfo2.type === 'dir') { + return 1; + } + return fileInfo1.name.localeCompare(fileInfo2.name); + }, + /** + * Compares two file infos by size. + * + * @param fileInfo1 file info + * @param fileInfo2 file info + * @return -1 if the first file must appear before the second one, + * 0 if they are identify, 1 otherwise. + */ + size: function(fileInfo1, fileInfo2) { + return fileInfo1.size - fileInfo2.size; + }, + /** + * Compares two file infos by timestamp. + * + * @param fileInfo1 file info + * @param fileInfo2 file info + * @return -1 if the first file must appear before the second one, + * 0 if they are identify, 1 otherwise. + */ + mtime: function(fileInfo1, fileInfo2) { + return fileInfo1.mtime - fileInfo2.mtime; + } +}; + diff --git a/apps/files/l10n/ast.php b/apps/files/l10n/ast.php index a2568ae7631..7dfeaf8cde7 100644 --- a/apps/files/l10n/ast.php +++ b/apps/files/l10n/ast.php @@ -22,6 +22,7 @@ $TRANSLATIONS = array( "Save" => "Guardar", "New folder" => "Nueva carpeta", "Folder" => "Carpeta", +"Deleted files" => "Ficheros desaniciaos", "Cancel upload" => "Encaboxar xuba", "Download" => "Descargar", "Delete" => "Desaniciar" diff --git a/apps/files/l10n/km.php b/apps/files/l10n/km.php index c1f0e649ef1..d6394241e12 100644 --- a/apps/files/l10n/km.php +++ b/apps/files/l10n/km.php @@ -3,17 +3,35 @@ $TRANSLATIONS = array( "File name cannot be empty." => "ឈ្មោះឯកសារមិនអាចនៅទទេបានឡើយ។", "Invalid name, '\\', '/', '<', '>', ':', '\"', '|', '?' and '*' are not allowed." => "ឈ្មោះមិនត្រឹមត្រូវ, មិនអនុញ្ញាត '\\', '/', '<', '>', ':', '\"', '|', '?' និង '*' ទេ។", "Files" => "ឯកសារ", +"Upload cancelled." => "បានបោះបង់ការផ្ទុកឡើង។", +"{new_name} already exists" => "មានឈ្មោះ {new_name} រួចហើយ", "Share" => "ចែករំលែក", +"Delete permanently" => "លុបជាអចិន្ត្រៃយ៍", +"Rename" => "ប្ដូរឈ្មោះ", +"Your download is being prepared. This might take some time if the files are big." => "ការទាញយករបស់អ្នកកំពុងត្រូវបានរៀបចំហើយ។ នេះអាចចំណាយពេលមួយសំទុះ ប្រសិនបើឯកសារធំ។", +"Pending" => "កំពុងរង់ចាំ", "Error" => "កំហុស", "Name" => "ឈ្មោះ", "Size" => "ទំហំ", +"Modified" => "បានកែប្រែ", "_%n folder_::_%n folders_" => array(""), "_%n file_::_%n files_" => array(""), "_Uploading %n file_::_Uploading %n files_" => array(""), +"Maximum upload size" => "ទំហំផ្ទុកឡើងជាអតិបរមា", +"Enable ZIP-download" => "បើកការទាញយកជា ZIP", +"0 is unlimited" => "0 គឺមិនកំណត់", +"Maximum input size for ZIP files" => "ទំហំចូលជាអតិបរមាសម្រាប់ឯកសារ ZIP", "Save" => "រក្សាទុក", +"New" => "ថ្មី", +"Text file" => "ឯកសារអក្សរ", "New folder" => "ថតថ្មី", "Folder" => "ថត", +"From link" => "ពីតំណ", +"Deleted files" => "បានលុបឯកសារ", +"Cancel upload" => "បោះបង់ការផ្ទុកឡើង", +"Nothing in here. Upload something!" => "គ្មានអ្វីនៅទីនេះទេ។ ផ្ទុកឡើងអ្វីមួយ!", "Download" => "ទាញយក", -"Delete" => "លុប" +"Delete" => "លុប", +"Upload too large" => "ផ្ទុកឡើងធំពេក" ); $PLURAL_FORMS = "nplurals=1; plural=0;"; diff --git a/apps/files/lib/helper.php b/apps/files/lib/helper.php index 0ae87d12fbf..7e0f47bf84f 100644 --- a/apps/files/lib/helper.php +++ b/apps/files/lib/helper.php @@ -1,7 +1,16 @@ <?php +/** + * Copyright (c) 2014 Vincent Petry <pvince81@owncloud.com> + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ namespace OCA\Files; +/** + * Helper class for manipulating file information + */ class Helper { public static function buildFileStorageStatistics($dir) { @@ -9,12 +18,12 @@ class Helper $storageInfo = \OC_Helper::getStorageInfo($dir); $l = new \OC_L10N('files'); - $maxUploadFilesize = \OCP\Util::maxUploadFilesize($dir, $storageInfo['free']); - $maxHumanFilesize = \OCP\Util::humanFileSize($maxUploadFilesize); - $maxHumanFilesize = $l->t('Upload (max. %s)', array($maxHumanFilesize)); + $maxUploadFileSize = \OCP\Util::maxUploadFilesize($dir, $storageInfo['free']); + $maxHumanFileSize = \OCP\Util::humanFileSize($maxUploadFileSize); + $maxHumanFileSize = $l->t('Upload (max. %s)', array($maxHumanFileSize)); - return array('uploadMaxFilesize' => $maxUploadFilesize, - 'maxHumanFilesize' => $maxHumanFilesize, + return array('uploadMaxFilesize' => $maxUploadFileSize, + 'maxHumanFilesize' => $maxHumanFileSize, 'freeSpace' => $storageInfo['free'], 'usedSpacePercent' => (int)$storageInfo['relative']); } @@ -27,7 +36,6 @@ class Helper */ public static function determineIcon($file) { if($file['type'] === 'dir') { - $dir = $file['directory']; $icon = \OC_Helper::mimetypeIcon('dir'); $absPath = $file->getPath(); $mount = \OC\Files\Filesystem::getMountManager()->find($absPath); @@ -57,7 +65,7 @@ class Helper * @param \OCP\Files\FileInfo $b file * @return int -1 if $a must come before $b, 1 otherwise */ - public static function fileCmp($a, $b) { + public static function compareFileNames($a, $b) { $aType = $a->getType(); $bType = $b->getType(); if ($aType === 'dir' and $bType !== 'dir') { @@ -70,6 +78,32 @@ class Helper } /** + * Comparator function to sort files by date + * + * @param \OCP\Files\FileInfo $a file + * @param \OCP\Files\FileInfo $b file + * @return int -1 if $a must come before $b, 1 otherwise + */ + public static function compareTimestamp($a, $b) { + $aTime = $a->getMTime(); + $bTime = $b->getMTime(); + return $aTime - $bTime; + } + + /** + * Comparator function to sort files by size + * + * @param \OCP\Files\FileInfo $a file + * @param \OCP\Files\FileInfo $b file + * @return int -1 if $a must come before $b, 1 otherwise + */ + public static function compareSize($a, $b) { + $aSize = $a->getSize(); + $bSize = $b->getSize(); + return $aSize - $bSize; + } + + /** * Formats the file info to be returned as JSON to the client. * * @param \OCP\Files\FileInfo $i @@ -120,12 +154,35 @@ class Helper * returns it as a sorted array of FileInfo. * * @param string $dir path to the directory + * @param string $sortAttribute attribute to sort on + * @param bool $sortDescending true for descending sort, false otherwise * @return \OCP\Files\FileInfo[] files */ - public static function getFiles($dir) { + public static function getFiles($dir, $sortAttribute = 'name', $sortDescending = false) { $content = \OC\Files\Filesystem::getDirectoryContent($dir); - usort($content, array('\OCA\Files\Helper', 'fileCmp')); - return $content; + return self::sortFiles($content, $sortAttribute, $sortDescending); + } + + /** + * Sort the given file info array + * + * @param \OCP\Files\FileInfo[] files to sort + * @param string $sortAttribute attribute to sort on + * @param bool $sortDescending true for descending sort, false otherwise + * @return \OCP\Files\FileInfo[] sorted files + */ + public static function sortFiles($files, $sortAttribute = 'name', $sortDescending = false) { + $sortFunc = 'compareFileNames'; + if ($sortAttribute === 'mtime') { + $sortFunc = 'compareTimestamp'; + } else if ($sortAttribute === 'size') { + $sortFunc = 'compareSize'; + } + usort($files, array('\OCA\Files\Helper', $sortFunc)); + if ($sortDescending) { + $files = array_reverse($files); + } + return $files; } } diff --git a/apps/files/templates/index.php b/apps/files/templates/index.php index 42263c880a7..9c593a9341d 100644 --- a/apps/files/templates/index.php +++ b/apps/files/templates/index.php @@ -1,3 +1,4 @@ +<?php /** @var $l OC_L10N */ ?> <div id="controls"> <div class="actions creatable hidden"> <?php if(!isset($_['dirToken'])):?> @@ -60,11 +61,11 @@ <table id="filestable" data-allow-public-upload="<?php p($_['publicUploadEnabled'])?>" data-preview-x="36" data-preview-y="36"> <thead> <tr> - <th class="hidden" id='headerName'> + <th id='headerName' class="hidden column-name"> <div id="headerName-container"> <input type="checkbox" id="select_all" /> <label for="select_all"></label> - <span class="name"><?php p($l->t( 'Name' )); ?></span> + <a class="name sort columntitle" data-sort="name"><span><?php p($l->t( 'Name' )); ?></span><span class="sort-indicator"></span></a> <span id="selectedActionsList" class="selectedActions"> <?php if($_['allowZipDownload']) : ?> <a href="" class="download"> @@ -76,9 +77,11 @@ </span> </div> </th> - <th class="hidden" id="headerSize"><?php p($l->t('Size')); ?></th> - <th class="hidden" id="headerDate"> - <span id="modified"><?php p($l->t( 'Modified' )); ?></span> + <th id="headerSize" class="hidden column-size"> + <a class="size sort columntitle" data-sort="size"><span><?php p($l->t('Size')); ?></span><span class="sort-indicator"></span></a> + </th> + <th id="headerDate" class="hidden column-mtime"> + <a id="modified" class="columntitle" data-sort="mtime"><span><?php p($l->t( 'Modified' )); ?></span><span class="sort-indicator"></span></a> <?php if ($_['permissions'] & OCP\PERMISSION_DELETE): ?> <span class="selectedActions"><a href="" class="delete-selected"> <?php p($l->t('Delete'))?> diff --git a/apps/files/tests/helper.php b/apps/files/tests/helper.php new file mode 100644 index 00000000000..9b3603cd563 --- /dev/null +++ b/apps/files/tests/helper.php @@ -0,0 +1,98 @@ +<?php +/** + * Copyright (c) 2014 Vincent Petry <pvince81@owncloud.com> + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +require_once __DIR__ . '/../lib/helper.php'; + +use OCA\Files; + +/** + * Class Test_Files_Helper + */ +class Test_Files_Helper extends \PHPUnit_Framework_TestCase { + + private function makeFileInfo($name, $size, $mtime, $isDir = false) { + return new \OC\Files\FileInfo( + '/', + null, + '/', + array( + 'name' => $name, + 'size' => $size, + 'mtime' => $mtime, + 'type' => $isDir ? 'dir' : 'file', + 'mimetype' => $isDir ? 'httpd/unix-directory' : 'application/octet-stream' + ) + ); + } + + /** + * Returns a file list for testing + */ + private function getTestFileList() { + return array( + self::makeFileInfo('a.txt', 4, 1000), + self::makeFileInfo('q.txt', 5, 150), + self::makeFileInfo('subdir2', 87, 128, true), + self::makeFileInfo('b.txt', 166, 800), + self::makeFileInfo('o.txt', 12, 100), + self::makeFileInfo('subdir', 88, 125, true), + ); + } + + function sortDataProvider() { + return array( + array( + 'name', + false, + array('subdir', 'subdir2', 'a.txt', 'b.txt', 'o.txt', 'q.txt'), + ), + array( + 'name', + true, + array('q.txt', 'o.txt', 'b.txt', 'a.txt', 'subdir2', 'subdir'), + ), + array( + 'size', + false, + array('a.txt', 'q.txt', 'o.txt', 'subdir2', 'subdir', 'b.txt'), + ), + array( + 'size', + true, + array('b.txt', 'subdir', 'subdir2', 'o.txt', 'q.txt', 'a.txt'), + ), + array( + 'mtime', + false, + array('o.txt', 'subdir', 'subdir2', 'q.txt', 'b.txt', 'a.txt'), + ), + array( + 'mtime', + true, + array('a.txt', 'b.txt', 'q.txt', 'subdir2', 'subdir', 'o.txt'), + ), + ); + } + + /** + * @dataProvider sortDataProvider + */ + public function testSortByName($sort, $sortDescending, $expectedOrder) { + $files = self::getTestFileList(); + $files = \OCA\Files\Helper::sortFiles($files, $sort, $sortDescending); + $fileNames = array(); + foreach ($files as $fileInfo) { + $fileNames[] = $fileInfo->getName(); + } + $this->assertEquals( + $expectedOrder, + $fileNames + ); + } + +} diff --git a/apps/files/tests/js/filelistSpec.js b/apps/files/tests/js/filelistSpec.js index 7a2b56d559a..edb9e15e9bc 100644 --- a/apps/files/tests/js/filelistSpec.js +++ b/apps/files/tests/js/filelistSpec.js @@ -77,13 +77,17 @@ describe('FileList tests', function() { // dummy table // TODO: at some point this will be rendered by the FileList class itself! '<table id="filestable">' + - '<thead><tr><th id="headerName" class="hidden">' + + '<thead><tr>' + + '<th id="headerName" class="hidden column-name">' + '<input type="checkbox" id="select_all">' + - '<span class="name">Name</span>' + + '<a class="name columntitle" data-sort="name"><span>Name</span><span class="sort-indicator"></span></a>' + '<span class="selectedActions hidden">' + '<a href class="download">Download</a>' + '<a href class="delete-selected">Delete</a></span>' + - '</th></tr></thead>' + + '</th>' + + '<th class="hidden column-size"><a class="columntitle" data-sort="size"><span class="sort-indicator"></span></a></th>' + + '<th class="hidden column-mtime"><a class="columntitle" data-sort="mtime"><span class="sort-indicator"></span></a></th>' + + '</tr></thead>' + '<tbody id="fileList"></tbody>' + '<tfoot></tfoot>' + '</table>' + @@ -940,7 +944,7 @@ describe('FileList tests', function() { expect(fakeServer.requests.length).toEqual(1); var url = fakeServer.requests[0].url; var query = url.substr(url.indexOf('?') + 1); - expect(OC.parseQueryString(query)).toEqual({'dir': '/subdir'}); + expect(OC.parseQueryString(query)).toEqual({'dir': '/subdir', sort: 'name', sortdirection: 'asc'}); fakeServer.respond(); expect($('#fileList tr').length).toEqual(4); expect(FileList.findFileEl('One.txt').length).toEqual(1); @@ -951,7 +955,7 @@ describe('FileList tests', function() { expect(fakeServer.requests.length).toEqual(1); var url = fakeServer.requests[0].url; var query = url.substr(url.indexOf('?') + 1); - expect(OC.parseQueryString(query)).toEqual({'dir': '/anothersubdir'}); + expect(OC.parseQueryString(query)).toEqual({'dir': '/anothersubdir', sort: 'name', sortdirection: 'asc'}); fakeServer.respond(); }); it('switches to root dir when current directory does not exist', function() { @@ -1260,7 +1264,7 @@ describe('FileList tests', function() { expect(_.pluck(FileList.getSelectedFiles(), 'name').length).toEqual(42); }); it('Selecting files updates selection summary', function() { - var $summary = $('#headerName span.name'); + var $summary = $('#headerName a.name>span:first'); expect($summary.text()).toEqual('Name'); FileList.findFileEl('One.txt').find('input:checkbox').click(); FileList.findFileEl('Three.pdf').find('input:checkbox').click(); @@ -1268,7 +1272,7 @@ describe('FileList tests', function() { expect($summary.text()).toEqual('1 folder & 2 files'); }); it('Unselecting files hides selection summary', function() { - var $summary = $('#headerName span.name'); + var $summary = $('#headerName a.name>span:first'); FileList.findFileEl('One.txt').find('input:checkbox').click().click(); expect($summary.text()).toEqual('Name'); }); @@ -1431,5 +1435,150 @@ describe('FileList tests', function() { }); }); }); + it('resets the file selection on reload', function() { + FileList.$el.find('#select_all').click(); + FileList.reload(); + expect(FileList.$el.find('#select_all').prop('checked')).toEqual(false); + expect(FileList.getSelectedFiles()).toEqual([]); + }); + }); + describe('Sorting files', function() { + it('Sorts by name by default', function() { + FileList.reload(); + expect(fakeServer.requests.length).toEqual(1); + var url = fakeServer.requests[0].url; + var query = OC.parseQueryString(url.substr(url.indexOf('?') + 1)); + expect(query.sort).toEqual('name'); + expect(query.sortdirection).toEqual('asc'); + }); + it('Reloads file list with a different sort when clicking on column header of unsorted column', function() { + FileList.$el.find('.column-size .columntitle').click(); + expect(fakeServer.requests.length).toEqual(1); + var url = fakeServer.requests[0].url; + var query = OC.parseQueryString(url.substr(url.indexOf('?') + 1)); + expect(query.sort).toEqual('size'); + expect(query.sortdirection).toEqual('asc'); + }); + it('Toggles sort direction when clicking on already sorted column', function() { + FileList.$el.find('.column-name .columntitle').click(); + expect(fakeServer.requests.length).toEqual(1); + var url = fakeServer.requests[0].url; + var query = OC.parseQueryString(url.substr(url.indexOf('?') + 1)); + expect(query.sort).toEqual('name'); + expect(query.sortdirection).toEqual('desc'); + }); + it('Toggles the sort indicator when clicking on a column header', function() { + var ASC_CLASS = FileList.SORT_INDICATOR_ASC_CLASS; + var DESC_CLASS = FileList.SORT_INDICATOR_DESC_CLASS; + FileList.$el.find('.column-size .columntitle').click(); + // moves triangle to size column + expect( + FileList.$el.find('.column-name .sort-indicator').hasClass(ASC_CLASS + ' ' + DESC_CLASS) + ).toEqual(false); + expect( + FileList.$el.find('.column-size .sort-indicator').hasClass(ASC_CLASS) + ).toEqual(true); + + // click again on size column, reverses direction + FileList.$el.find('.column-size .columntitle').click(); + expect( + FileList.$el.find('.column-size .sort-indicator').hasClass(DESC_CLASS) + ).toEqual(true); + + // click again on size column, reverses direction + FileList.$el.find('.column-size .columntitle').click(); + expect( + FileList.$el.find('.column-size .sort-indicator').hasClass(ASC_CLASS) + ).toEqual(true); + + // click on mtime column, moves indicator there + FileList.$el.find('.column-mtime .columntitle').click(); + expect( + FileList.$el.find('.column-size .sort-indicator').hasClass(ASC_CLASS + ' ' + DESC_CLASS) + ).toEqual(false); + expect( + FileList.$el.find('.column-mtime .sort-indicator').hasClass(ASC_CLASS) + ).toEqual(true); + }); + it('Uses correct sort comparator when inserting files', function() { + testFiles.sort(FileList.Comparators.size); + // this will make it reload the testFiles with the correct sorting + FileList.$el.find('.column-size .columntitle').click(); + expect(fakeServer.requests.length).toEqual(1); + fakeServer.requests[0].respond( + 200, + { 'Content-Type': 'application/json' }, + JSON.stringify({ + status: 'success', + data: { + files: testFiles, + permissions: 31 + } + }) + ); + var newFileData = { + id: 999, + type: 'file', + name: 'new file.txt', + mimetype: 'text/plain', + size: 40001, + etag: '999' + }; + FileList.add(newFileData); + expect(FileList.files.length).toEqual(5); + expect(FileList.$fileList.find('tr').length).toEqual(5); + expect(FileList.findFileEl('One.txt').index()).toEqual(0); + expect(FileList.findFileEl('somedir').index()).toEqual(1); + expect(FileList.findFileEl('Two.jpg').index()).toEqual(2); + expect(FileList.findFileEl('new file.txt').index()).toEqual(3); + expect(FileList.findFileEl('Three.pdf').index()).toEqual(4); + }); + it('Uses correct reversed sort comparator when inserting files', function() { + testFiles.sort(FileList.Comparators.size); + testFiles.reverse(); + // this will make it reload the testFiles with the correct sorting + FileList.$el.find('.column-size .columntitle').click(); + expect(fakeServer.requests.length).toEqual(1); + fakeServer.requests[0].respond( + 200, + { 'Content-Type': 'application/json' }, + JSON.stringify({ + status: 'success', + data: { + files: testFiles, + permissions: 31 + } + }) + ); + // reverse sort + FileList.$el.find('.column-size .columntitle').click(); + fakeServer.requests[1].respond( + 200, + { 'Content-Type': 'application/json' }, + JSON.stringify({ + status: 'success', + data: { + files: testFiles, + permissions: 31 + } + }) + ); + var newFileData = { + id: 999, + type: 'file', + name: 'new file.txt', + mimetype: 'text/plain', + size: 40001, + etag: '999' + }; + FileList.add(newFileData); + expect(FileList.files.length).toEqual(5); + expect(FileList.$fileList.find('tr').length).toEqual(5); + expect(FileList.findFileEl('One.txt').index()).toEqual(4); + expect(FileList.findFileEl('somedir').index()).toEqual(3); + expect(FileList.findFileEl('Two.jpg').index()).toEqual(2); + expect(FileList.findFileEl('new file.txt').index()).toEqual(1); + expect(FileList.findFileEl('Three.pdf').index()).toEqual(0); + }); }); }); diff --git a/apps/files/triggerupdate.php b/apps/files/triggerupdate.php index a37b9823add..3f85da9913b 100644 --- a/apps/files/triggerupdate.php +++ b/apps/files/triggerupdate.php @@ -6,7 +6,7 @@ if (OC::$CLI) { if (count($argv) === 2) { $file = $argv[1]; list(, $user) = explode('/', $file); - OCP\JSON::checkUserExists($owner); + OCP\JSON::checkUserExists($user); OC_Util::setupFS($user); $view = new \OC\Files\View(''); /** diff --git a/apps/files_external/l10n/km.php b/apps/files_external/l10n/km.php index aac63370245..24cd4cb6e6b 100644 --- a/apps/files_external/l10n/km.php +++ b/apps/files_external/l10n/km.php @@ -5,6 +5,7 @@ $TRANSLATIONS = array( "Username" => "ឈ្មោះអ្នកប្រើ", "Password" => "ពាក្យសម្ងាត់", "Share" => "ចែករំលែក", +"Saved" => "បានរក្សាទុក", "Folder name" => "ឈ្មោះថត", "Options" => "ជម្រើស", "Groups" => "ក្រុ", diff --git a/apps/files_sharing/ajax/list.php b/apps/files_sharing/ajax/list.php index 4b645496253..82bacb3d38d 100644 --- a/apps/files_sharing/ajax/list.php +++ b/apps/files_sharing/ajax/list.php @@ -20,11 +20,6 @@ * */ -// only need filesystem apps -$RUNTIME_APPTYPES=array('filesystem'); - -// Init owncloud - if(!\OC_App::isEnabled('files_sharing')){ exit; } @@ -47,6 +42,9 @@ if (isset($_GET['dir'])) { $relativePath = $_GET['dir']; } +$sortAttribute = isset( $_GET['sort'] ) ? $_GET['sort'] : 'name'; +$sortDirection = isset( $_GET['sortdirection'] ) ? ($_GET['sortdirection'] === 'desc') : false; + $data = \OCA\Files_Sharing\Helper::setupFromToken($token, $relativePath, $password); $linkItem = $data['linkItem']; @@ -64,7 +62,7 @@ $data = array(); $baseUrl = OCP\Util::linkTo('files_sharing', 'index.php') . '?t=' . urlencode($token) . '&dir='; // make filelist -$files = \OCA\Files\Helper::getFiles($dir); +$files = \OCA\Files\Helper::getFiles($dir, $sortAttribute, $sortDirection); $formattedFiles = array(); foreach ($files as $file) { diff --git a/apps/files_trashbin/ajax/list.php b/apps/files_trashbin/ajax/list.php index 89a55114524..e1f52e814bb 100644 --- a/apps/files_trashbin/ajax/list.php +++ b/apps/files_trashbin/ajax/list.php @@ -4,11 +4,13 @@ OCP\JSON::checkLoggedIn(); // Load the files $dir = isset( $_GET['dir'] ) ? $_GET['dir'] : ''; +$sortAttribute = isset( $_GET['sort'] ) ? $_GET['sort'] : 'name'; +$sortDirection = isset( $_GET['sortdirection'] ) ? ($_GET['sortdirection'] === 'desc') : false; $data = array(); // make filelist try { - $files = \OCA\Files_Trashbin\Helper::getTrashFiles($dir); + $files = \OCA\Files_Trashbin\Helper::getTrashFiles($dir, $sortAttribute, $sortDirection); } catch (Exception $e) { header("HTTP/1.0 404 Not Found"); exit(); diff --git a/apps/files_trashbin/js/filelist.js b/apps/files_trashbin/js/filelist.js index 3bb3a92b60d..00fc7e16b88 100644 --- a/apps/files_trashbin/js/filelist.js +++ b/apps/files_trashbin/js/filelist.js @@ -80,6 +80,7 @@ FileList.initialize = function() { var result = oldInit.apply(this, arguments); $('.undelete').click('click', FileList._onClickRestoreSelected); + this.setSort('mtime', 'desc'); return result; }; diff --git a/apps/files_trashbin/l10n/ast.php b/apps/files_trashbin/l10n/ast.php index 688e1ce3d8f..6721e146e77 100644 --- a/apps/files_trashbin/l10n/ast.php +++ b/apps/files_trashbin/l10n/ast.php @@ -2,11 +2,13 @@ $TRANSLATIONS = array( "Couldn't delete %s permanently" => "Nun pudo desaniciase %s dafechu", "Couldn't restore %s" => "Nun pudo restaurase %s", +"Deleted files" => "Ficheros desaniciaos", "Error" => "Fallu", +"Restore" => "Restaurar", "Deleted Files" => "Ficheros desaniciaos", +"restored" => "recuperóse", "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" ); diff --git a/apps/files_trashbin/l10n/km.php b/apps/files_trashbin/l10n/km.php index 1df805de2d6..53e395f6ac6 100644 --- a/apps/files_trashbin/l10n/km.php +++ b/apps/files_trashbin/l10n/km.php @@ -1,8 +1,9 @@ <?php $TRANSLATIONS = array( +"Deleted files" => "បានលុបឯកសារ", "Error" => "កំហុស", -"Name" => "ឈ្មោះ", "Restore" => "ស្ដារមកវិញ", +"Name" => "ឈ្មោះ", "Delete" => "លុប" ); $PLURAL_FORMS = "nplurals=1; plural=0;"; diff --git a/apps/files_trashbin/lib/helper.php b/apps/files_trashbin/lib/helper.php index e6ca73520a6..c98d57586d3 100644 --- a/apps/files_trashbin/lib/helper.php +++ b/apps/files_trashbin/lib/helper.php @@ -8,11 +8,14 @@ class Helper { /** * Retrieves the contents of a trash bin directory. + * * @param string $dir path to the directory inside the trashbin * or empty to retrieve the root of the trashbin + * @param string $sortAttribute attribute to sort on or empty to disable sorting + * @param bool $sortDescending true for descending sort, false otherwise * @return \OCP\Files\FileInfo[] */ - public static function getTrashFiles($dir){ + public static function getTrashFiles($dir, $sortAttribute = '', $sortDescending = false){ $result = array(); $timestamp = null; $user = \OCP\User::getUser(); @@ -57,8 +60,9 @@ class Helper closedir($dirContent); } - usort($result, array('\OCA\Files\Helper', 'fileCmp')); - + if ($sortAttribute !== '') { + return \OCA\Files\Helper::sortFiles($result, $sortAttribute, $sortDescending); + } return $result; } diff --git a/apps/files_trashbin/templates/index.php b/apps/files_trashbin/templates/index.php index 323e7495535..7c673c317e0 100644 --- a/apps/files_trashbin/templates/index.php +++ b/apps/files_trashbin/templates/index.php @@ -1,3 +1,4 @@ +<?php /** @var $l OC_L10N */ ?> <div id="controls"> <div id="file_action_panel"></div> </div> @@ -5,29 +6,29 @@ <div id="emptycontent" class="hidden"><?php p($l->t('Nothing in here. Your trash bin is empty!'))?></div> -<input type="hidden" id="permissions" value="0"></input> -<input type="hidden" id="disableSharing" data-status="<?php p($_['disableSharing']); ?>"></input> +<input type="hidden" id="permissions" value="0"> +<input type="hidden" id="disableSharing" data-status="<?php p($_['disableSharing']); ?>"> <input type="hidden" name="dir" value="<?php p($_['dir']) ?>" id="dir"> <table id="filestable"> <thead> <tr> - <th id='headerName'> + <th id='headerName' class="hidden column-name"> <div id="headerName-container"> - <input type="checkbox" id="select_all" /> - <label for="select_all"></label> - <span class='name'><?php p($l->t( 'Name' )); ?></span> - <span class='selectedActions'> + <input type="checkbox" id="select_all" /> + <label for="select_all"></label> + <a class="name sort columntitle" data-sort="name"><span><?php p($l->t( 'Name' )); ?></span><span class="sort-indicator"></span></a> + <span id="selectedActionsList" class='selectedActions'> <a href="" class="undelete"> <img class="svg" alt="<?php p($l->t( 'Restore' )); ?>" src="<?php print_unescaped(OCP\image_path("core", "actions/history.svg")); ?>" /> <?php p($l->t('Restore'))?> </a> - </span> + </span> </div> </th> - <th id="headerDate"> - <span id="modified"><?php p($l->t( 'Deleted' )); ?></span> + <th id="headerDate" class="hidden column-mtime"> + <a id="modified" class="columntitle" data-sort="mtime"><span><?php p($l->t( 'Deleted' )); ?></span><span class="sort-indicator"></span></a> <span class="selectedActions"> <a href="" class="delete-selected"> <?php p($l->t('Delete'))?> diff --git a/apps/user_ldap/l10n/ast.php b/apps/user_ldap/l10n/ast.php index ad37405ca70..f793847b231 100644 --- a/apps/user_ldap/l10n/ast.php +++ b/apps/user_ldap/l10n/ast.php @@ -23,6 +23,7 @@ $TRANSLATIONS = array( "_%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", +"Help" => "Ayuda", "groups found" => "grupos alcontraos", "Users login with this attribute:" => "Aniciu de sesión d'usuarios con esti atributu:", "LDAP Username:" => "Nome d'usuariu LDAP", diff --git a/apps/user_ldap/l10n/km.php b/apps/user_ldap/l10n/km.php index ad3cc4882c1..ff9ddc539f8 100644 --- a/apps/user_ldap/l10n/km.php +++ b/apps/user_ldap/l10n/km.php @@ -15,6 +15,7 @@ $TRANSLATIONS = array( "Help" => "ជំនួយ", "Add Server Configuration" => "បន្ថែមការកំណត់រចនាសម្ព័ន្ធម៉ាស៊ីនបម្រើ", "Host" => "ម៉ាស៊ីនផ្ទុក", -"Password" => "ពាក្យសម្ងាត់" +"Password" => "ពាក្យសម្ងាត់", +"Continue" => "បន្ត" ); $PLURAL_FORMS = "nplurals=1; plural=0;"; diff --git a/core/css/multiselect.css b/core/css/multiselect.css index 8d949e7cdb7..b3393c450d4 100644 --- a/core/css/multiselect.css +++ b/core/css/multiselect.css @@ -50,7 +50,7 @@ ul.multiselectoptions > li input[type='checkbox']:checked+label { div.multiselect, select.multiselect { display: inline-block; - max-width: 400px; + max-width: 200px; min-width: 150px; padding-right: 10px; min-height: 20px; diff --git a/core/l10n/ast.php b/core/l10n/ast.php index c479b96faab..c813a5deb9e 100644 --- a/core/l10n/ast.php +++ b/core/l10n/ast.php @@ -72,6 +72,7 @@ $TRANSLATIONS = array( "For the best results, please consider using a GNU/Linux server instead." => "Pa los meyores resultaos, por favor considera l'usu d'un sirvidor GNU/Linux nel so llugar.", "Personal" => "Personal", "Users" => "Usuarios", +"Help" => "Ayuda", "Cheers!" => "¡Salú!", "Security Warning" => "Avisu de seguridá", "will be used" => "usaráse", diff --git a/core/l10n/km.php b/core/l10n/km.php index 4833ba70434..76b1492456c 100644 --- a/core/l10n/km.php +++ b/core/l10n/km.php @@ -41,6 +41,14 @@ $TRANSLATIONS = array( "New Files" => "ឯកសារថ្មី", "Already existing files" => "មានឯកសារនេះរួចហើយ", "Cancel" => "លើកលែង", +"Continue" => "បន្ត", +"(all selected)" => "(បានជ្រើសទាំងអស់)", +"({count} selected)" => "(បានជ្រើស {count})", +"Very weak password" => "ពាក្យសម្ងាត់ខ្សោយណាស់", +"Weak password" => "ពាក្យសម្ងាត់ខ្សោយ", +"So-so password" => "ពាក្យសម្ងាត់ធម្មតា", +"Good password" => "ពាក្យសម្ងាត់ល្អ", +"Strong password" => "ពាក្យសម្ងាត់ខ្លាំង", "Shared" => "បានចែករំលែក", "Share" => "ចែករំលែក", "Error" => "កំហុស", @@ -74,6 +82,8 @@ $TRANSLATIONS = array( "The object type is not specified." => "មិនបានកំណត់ប្រភេទវត្ថុ។", "Delete" => "លុប", "Add" => "បញ្ចូល", +"Please reload the page." => "សូមផ្ទុកទំព័រនេះឡើងវិញ។", +"A problem has occurred whilst sending the email, please contact your administrator." => "មានកំហុសបានកើតឡើងនៅពេលលកំពុងផ្ញើអ៊ីមែលចេញ, សូមទាក់ទងអភិបាលរបស់អ្នក។", "Username" => "ឈ្មោះអ្នកប្រើ", "Your password was reset" => "ពាក្យសម្ងាត់របស់អ្នកត្រូវបានកំណត់ឡើងវិញ", "To login page" => "ទៅទំព័រចូល", @@ -88,6 +98,7 @@ $TRANSLATIONS = array( "Cloud not found" => "រកមិនឃើញ Cloud", "Security Warning" => "បម្រាមសុវត្ថិភាព", "Create an <strong>admin account</strong>" => "បង្កើត<strong>គណនីអភិបាល</strong>", +"Storage & database" => "ឃ្លាំងផ្ទុក & មូលដ្ឋានទិន្នន័យ", "Data folder" => "ថតទិន្នន័យ", "Configure the database" => "កំណត់សណ្ឋានមូលដ្ឋានទិន្នន័យ", "will be used" => "នឹងត្រូវបានប្រើ", @@ -96,6 +107,7 @@ $TRANSLATIONS = array( "Database name" => "ឈ្មោះមូលដ្ឋានទិន្នន័យ", "Database host" => "ម៉ាស៊ីនមូលដ្ឋានទិន្នន័យ", "Finish setup" => "បញ្ចប់ការដំឡើង", +"Finishing …" => "កំពុងបញ្ចប់ ...", "Log out" => "ចាកចេញ", "Automatic logon rejected!" => "បានបដិសេធការចូលដោយស្វ័យប្រវត្តិ!", "Please change your password to secure your account again." => "សូមប្ដូរពាក្យសម្ងាត់របស់អ្នក ដើម្បីការពារគណនីរបស់អ្នក។", diff --git a/core/l10n/nl.php b/core/l10n/nl.php index 43748352a7b..961866fc158 100644 --- a/core/l10n/nl.php +++ b/core/l10n/nl.php @@ -94,7 +94,7 @@ $TRANSLATIONS = array( "delete" => "verwijderen", "share" => "deel", "Password protected" => "Wachtwoord beveiligd", -"Error unsetting expiration date" => "Fout tijdens het verwijderen van de verval datum", +"Error unsetting expiration date" => "Fout tijdens het verwijderen van de vervaldatum", "Error setting expiration date" => "Fout tijdens het instellen van de vervaldatum", "Sending ..." => "Versturen ...", "Email sent" => "E-mail verzonden", @@ -107,19 +107,19 @@ $TRANSLATIONS = array( "Error loading dialog template: {error}" => "Fout bij laden dialoog sjabloon: {error}", "No tags selected for deletion." => "Geen tags geselecteerd voor verwijdering.", "Please reload the page." => "Herlaad deze pagina.", -"The update was unsuccessful. Please report this issue to the <a href=\"https://github.com/owncloud/core/issues\" target=\"_blank\">ownCloud community</a>." => "De update is niet geslaagd. Meld dit probleem aan bij de <a href=\"https://github.com/owncloud/core/issues\" target=\"_blank\">ownCloud community</a>.", -"The update was successful. Redirecting you to ownCloud now." => "De update is geslaagd. Je wordt teruggeleid naar je eigen ownCloud.", +"The update was unsuccessful. Please report this issue to the <a href=\"https://github.com/owncloud/core/issues\" target=\"_blank\">ownCloud community</a>." => "De update is niet geslaagd. Meld dit probleem bij de <a href=\"https://github.com/owncloud/core/issues\" target=\"_blank\">ownCloud community</a>.", +"The update was successful. Redirecting you to ownCloud now." => "De update is geslaagd. U wordt teruggeleid naar uw eigen ownCloud.", "%s password reset" => "%s wachtwoord reset", "A problem has occurred whilst sending the email, please contact your administrator." => "Er ontstond een probleem bij het versturen van het e-mailbericht, neem contact op met uw beheerder.", -"Use the following link to reset your password: {link}" => "Gebruik de volgende link om je wachtwoord te resetten: {link}", -"The link to reset your password has been sent to your email.<br>If you do not receive it within a reasonable amount of time, check your spam/junk folders.<br>If it is not there ask your local administrator ." => "De link voor het resetten van je wachtwoord is verzonden naar je e-mailadres.<br>Als je dat bericht niet snel ontvangen hebt, controleer dan uw spambakje.<br>Als het daar ook niet is, vraag dan je beheerder om te helpen.", -"Request failed!<br>Did you make sure your email/username was right?" => "Aanvraag mislukt!<br>Weet je zeker dat je gebruikersnaam en/of wachtwoord goed waren?", -"You will receive a link to reset your password via Email." => "Je ontvangt een link om je wachtwoord opnieuw in te stellen via e-mail.", +"Use the following link to reset your password: {link}" => "Gebruik de volgende link om uw wachtwoord te resetten: {link}", +"The link to reset your password has been sent to your email.<br>If you do not receive it within a reasonable amount of time, check your spam/junk folders.<br>If it is not there ask your local administrator ." => "De link voor het resetten van uw wachtwoord is verzonden naar uw e-mailadres.<br>Als u dat bericht niet snel ontvangen hebt, controleer dan uw spammap.<br>Als het daar ook niet is, vraag dan uw beheerder om hulp.", +"Request failed!<br>Did you make sure your email/username was right?" => "Aanvraag mislukt!<br>Weet u zeker dat uw gebruikersnaam en/of wachtwoord goed waren?", +"You will receive a link to reset your password via Email." => "U ontvangt een link om uw wachtwoord opnieuw in te stellen via e-mail.", "Username" => "Gebruikersnaam", -"Your files are encrypted. If you haven't enabled the recovery key, there will be no way to get your data back after your password is reset. If you are not sure what to do, please contact your administrator before you continue. Do you really want to continue?" => "Je bestanden zijn versleuteld. Als je geen recoverykey hebt ingeschakeld is er geen manier om je data terug te krijgen indien je je wachtwoord reset!\nAls je niet weet wat te doen, neem dan alsjeblieft contact op met je administrator eer je doorgaat.\nWil je echt doorgaan?", +"Your files are encrypted. If you haven't enabled the recovery key, there will be no way to get your data back after your password is reset. If you are not sure what to do, please contact your administrator before you continue. Do you really want to continue?" => "Uw bestanden zijn versleuteld. Als u geen recoverykey hebt ingeschakeld is er geen manier om uw data terug te krijgen na het resetten van uw wachtwoord.\nAls u niet weet wat u moet doen, neem dan alstublieft contact op met uw systeembeheerder voordat u doorgaat.\nWil u echt doorgaan?", "Yes, I really want to reset my password now" => "Ja, ik wil mijn wachtwoord nu echt resetten", "Reset" => "Reset", -"Your password was reset" => "Je wachtwoord is gewijzigd", +"Your password was reset" => "Uw wachtwoord is gewijzigd", "To login page" => "Naar de login-pagina", "New password" => "Nieuw wachtwoord", "Reset password" => "Reset wachtwoord", @@ -139,15 +139,15 @@ $TRANSLATIONS = array( "Error unfavoriting" => "Fout bij verwijderen favorietstatus", "Access forbidden" => "Toegang verboden", "Cloud not found" => "Cloud niet gevonden", -"Hey there,\n\njust letting you know that %s shared %s with you.\nView it: %s\n\n" => "Hallo daar,\n\neven een berichtje dat %s %s met u deelde.\nBekijk het: %s\n\n", +"Hey there,\n\njust letting you know that %s shared %s with you.\nView it: %s\n\n" => "Hallo,\n\n%s deelt %s met u.\nBekijk het: %s\n\n", "The share will expire on %s." => "De share vervalt op %s.", "Cheers!" => "Proficiat!", "Security Warning" => "Beveiligingswaarschuwing", -"Your PHP version is vulnerable to the NULL Byte attack (CVE-2006-7243)" => "Je PHP-versie is kwetsbaar voor de NULL byte aanval (CVE-2006-7243)", +"Your PHP version is vulnerable to the NULL Byte attack (CVE-2006-7243)" => "Uw PHP-versie is kwetsbaar voor de NULL byte aanval (CVE-2006-7243)", "Please update your PHP installation to use %s securely." => "Werk uw PHP installatie bij om %s veilig te kunnen gebruiken.", "No secure random number generator is available, please enable the PHP OpenSSL extension." => "Er kon geen willekeurig nummer worden gegenereerd. Zet de PHP OpenSSL-extentie aan.", "Without a secure random number generator an attacker may be able to predict password reset tokens and take over your account." => "Zonder random nummer generator is het mogelijk voor een aanvaller om de resettokens van wachtwoorden te voorspellen. Dit kan leiden tot het inbreken op uw account.", -"Your data directory and files are probably accessible from the internet because the .htaccess file does not work." => "Je gegevensdirectory en bestanden zijn vermoedelijk bereikbaar vanaf het internet omdat het .htaccess-bestand niet werkt.", +"Your data directory and files are probably accessible from the internet because the .htaccess file does not work." => "Uw gegevensdirectory en bestanden zijn vermoedelijk bereikbaar vanaf het internet omdat het .htaccess-bestand niet functioneert.", "For information how to properly configure your server, please see the <a href=\"%s\" target=\"_blank\">documentation</a>." => "Bekijk de <a href=\"%s\" target=\"_blank\">documentatie</a> voor Informatie over het correct configureren van uw server.", "Create an <strong>admin account</strong>" => "Maak een <strong>beheerdersaccount</strong> aan", "Storage & database" => "Opslag & database", @@ -161,19 +161,19 @@ $TRANSLATIONS = array( "Database host" => "Databaseserver", "Finish setup" => "Installatie afronden", "Finishing …" => "Afronden ...", -"This application requires JavaScript to be enabled for correct operation. Please <a href=\"http://enable-javascript.com/\" target=\"_blank\">enable JavaScript</a> and re-load this interface." => "Deze applicatie heeft een werkend JavaScript nodig. <a href=\"http://enable-javascript.com/\" target=\"_blank\">activeer JavaScript</a> en herlaad deze interface.", +"This application requires JavaScript to be enabled for correct operation. Please <a href=\"http://enable-javascript.com/\" target=\"_blank\">enable JavaScript</a> and re-load this interface." => "Deze applicatie heeft JavaScript nodig om correct te functioneren. <a href=\"http://enable-javascript.com/\" target=\"_blank\">Activeer JavaScript</a> en herlaad deze interface.", "%s is available. Get more information on how to update." => "%s is beschikbaar. Verkrijg meer informatie over het bijwerken.", "Log out" => "Afmelden", "Automatic logon rejected!" => "Automatische aanmelding geweigerd!", -"If you did not change your password recently, your account may be compromised!" => "Als je je wachtwoord niet onlangs heeft aangepast, kan je account overgenomen zijn!", -"Please change your password to secure your account again." => "Wijzig je wachtwoord zodat je account weer beveiligd is.", +"If you did not change your password recently, your account may be compromised!" => "Als u uw wachtwoord niet onlangs heeft aangepast, kan uw account overgenomen zijn!", +"Please change your password to secure your account again." => "Wijzig uw wachtwoord zodat uw account weer beveiligd is.", "Server side authentication failed!" => "Authenticatie bij de server mislukte!", "Please contact your administrator." => "Neem contact op met uw systeembeheerder.", "Lost your password?" => "Wachtwoord vergeten?", "remember" => "onthoud gegevens", -"Log in" => "Meld je aan", +"Log in" => "Meld u aan", "Alternative Logins" => "Alternatieve inlogs", -"Hey there,<br><br>just letting you know that %s shared <strong>%s</strong> with you.<br><a href=\"%s\">View it!</a><br><br>" => "Hallo daar,<br><br>we willen even laten weten dat %s <strong>%s</strong> met u heeft gedeeld.<br><a href=\"%s\">Bekijk het!</a><br><br>", +"Hey there,<br><br>just letting you know that %s shared <strong>%s</strong> with you.<br><a href=\"%s\">View it!</a><br><br>" => "Hallo,<br><br>%s deelt <strong>%s</strong> met u.<br><a href=\"%s\">Bekijk het!</a><br><br>", "This ownCloud instance is currently in single user mode." => "Deze ownCloud werkt momenteel in enkele gebruiker modus.", "This means only administrators can use the instance." => "Dat betekent dat alleen beheerders deze installatie kunnen gebruiken.", "Contact your system administrator if this message persists or appeared unexpectedly." => "Beem contact op met uw systeembeheerder als deze melding aanhoudt of plotseling verscheen.", diff --git a/l10n/ast/core.po b/l10n/ast/core.po index e26d53b602e..da397dbc96e 100644 --- a/l10n/ast/core.po +++ b/l10n/ast/core.po @@ -8,8 +8,8 @@ msgid "" msgstr "" "Project-Id-Version: ownCloud\n" "Report-Msgid-Bugs-To: translations@owncloud.org\n" -"POT-Creation-Date: 2014-05-11 01:55-0400\n" -"PO-Revision-Date: 2014-05-11 05:01+0000\n" +"POT-Creation-Date: 2014-05-12 01:55-0400\n" +"PO-Revision-Date: 2014-05-11 15:20+0000\n" "Last-Translator: Iñigo Varela <ivarela@softastur.org>\n" "Language-Team: Asturian (http://www.transifex.com/projects/p/owncloud/language/ast/)\n" "MIME-Version: 1.0\n" @@ -568,7 +568,7 @@ msgstr "" #: strings.php:9 msgid "Help" -msgstr "" +msgstr "Ayuda" #: tags/controller.php:22 msgid "Error loading tags" diff --git a/l10n/ast/files.po b/l10n/ast/files.po index 6c5b2620c0a..0189fb5d4f9 100644 --- a/l10n/ast/files.po +++ b/l10n/ast/files.po @@ -7,8 +7,8 @@ msgid "" msgstr "" "Project-Id-Version: ownCloud\n" "Report-Msgid-Bugs-To: translations@owncloud.org\n" -"POT-Creation-Date: 2014-05-04 01:55-0400\n" -"PO-Revision-Date: 2014-05-03 06:11+0000\n" +"POT-Creation-Date: 2014-05-12 01:55-0400\n" +"PO-Revision-Date: 2014-05-11 16:10+0000\n" "Last-Translator: I Robot\n" "Language-Team: Asturian (http://www.transifex.com/projects/p/owncloud/language/ast/)\n" "MIME-Version: 1.0\n" @@ -143,49 +143,49 @@ msgstr "" msgid "Files" msgstr "Ficheros" -#: js/file-upload.js:254 +#: js/file-upload.js:257 msgid "Unable to upload {filename} as it is a directory or has 0 bytes" msgstr "" -#: js/file-upload.js:266 +#: js/file-upload.js:270 msgid "Total file size {size1} exceeds upload limit {size2}" msgstr "" -#: js/file-upload.js:276 +#: js/file-upload.js:281 msgid "" "Not enough free space, you are uploading {size1} but only {size2} is left" msgstr "" -#: js/file-upload.js:353 +#: js/file-upload.js:358 msgid "Upload cancelled." msgstr "" -#: js/file-upload.js:398 +#: js/file-upload.js:404 msgid "Could not get result from server." msgstr "" -#: js/file-upload.js:490 +#: js/file-upload.js:493 msgid "" "File upload is in progress. Leaving the page now will cancel the upload." msgstr "" -#: js/file-upload.js:555 +#: js/file-upload.js:558 msgid "URL cannot be empty" msgstr "" -#: js/file-upload.js:559 js/filelist.js:963 +#: js/file-upload.js:562 js/filelist.js:963 msgid "{new_name} already exists" msgstr "" -#: js/file-upload.js:611 +#: js/file-upload.js:617 msgid "Could not create file" msgstr "" -#: js/file-upload.js:624 +#: js/file-upload.js:633 msgid "Could not create folder" msgstr "" -#: js/file-upload.js:664 +#: js/file-upload.js:680 msgid "Error fetching URL" msgstr "" @@ -364,7 +364,7 @@ msgstr "" #: templates/index.php:40 msgid "Deleted files" -msgstr "" +msgstr "Ficheros desaniciaos" #: templates/index.php:45 msgid "Cancel upload" diff --git a/l10n/ast/files_trashbin.po b/l10n/ast/files_trashbin.po index 5e51f4759a0..3f7349d157a 100644 --- a/l10n/ast/files_trashbin.po +++ b/l10n/ast/files_trashbin.po @@ -8,8 +8,8 @@ msgid "" msgstr "" "Project-Id-Version: ownCloud\n" "Report-Msgid-Bugs-To: translations@owncloud.org\n" -"POT-Creation-Date: 2014-04-22 01:54-0400\n" -"PO-Revision-Date: 2014-04-21 16:38+0000\n" +"POT-Creation-Date: 2014-05-12 01:55-0400\n" +"PO-Revision-Date: 2014-05-11 16:10+0000\n" "Last-Translator: Iñigo Varela <ivarela@softastur.org>\n" "Language-Team: Asturian (http://www.transifex.com/projects/p/owncloud/language/ast/)\n" "MIME-Version: 1.0\n" @@ -30,19 +30,23 @@ msgstr "Nun pudo restaurase %s" #: js/filelist.js:3 msgid "Deleted files" -msgstr "" +msgstr "Ficheros desaniciaos" -#: js/trash.js:33 js/trash.js:124 js/trash.js:173 +#: js/filelist.js:88 js/filelist.js:132 js/filelist.js:181 msgid "Error" msgstr "Fallu" -#: js/trash.js:264 +#: js/trash.js:48 templates/index.php:22 templates/index.php:24 +msgid "Restore" +msgstr "Restaurar" + +#: js/trash.js:107 msgid "Deleted Files" msgstr "Ficheros desaniciaos" #: lib/trashbin.php:861 lib/trashbin.php:863 msgid "restored" -msgstr "" +msgstr "recuperóse" #: templates/index.php:6 msgid "Nothing in here. Your trash bin is empty!" @@ -52,10 +56,6 @@ msgstr "Nun hai un res equí. La papelera ta balera!" msgid "Name" msgstr "Nome" -#: templates/index.php:22 templates/index.php:24 -msgid "Restore" -msgstr "Restaurar" - #: templates/index.php:30 msgid "Deleted" msgstr "Desaniciáu" diff --git a/l10n/ast/lib.po b/l10n/ast/lib.po index 1ccb0bb404a..e061eb02dbe 100644 --- a/l10n/ast/lib.po +++ b/l10n/ast/lib.po @@ -8,8 +8,8 @@ msgid "" msgstr "" "Project-Id-Version: ownCloud\n" "Report-Msgid-Bugs-To: translations@owncloud.org\n" -"POT-Creation-Date: 2014-04-28 01:55-0400\n" -"PO-Revision-Date: 2014-04-28 05:55+0000\n" +"POT-Creation-Date: 2014-05-12 01:55-0400\n" +"PO-Revision-Date: 2014-05-11 15:20+0000\n" "Last-Translator: I Robot\n" "Language-Team: Asturian (http://www.transifex.com/projects/p/owncloud/language/ast/)\n" "MIME-Version: 1.0\n" @@ -18,11 +18,11 @@ msgstr "" "Language: ast\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" -#: base.php:723 +#: base.php:713 msgid "You are accessing the server from an untrusted domain." msgstr "" -#: base.php:724 +#: base.php:714 msgid "" "Please contact your administrator. If you are an administrator of this " "instance, configure the \"trusted_domain\" setting in config/config.php. An " @@ -42,7 +42,7 @@ msgstr "" #: private/app.php:353 msgid "Help" -msgstr "" +msgstr "Ayuda" #: private/app.php:366 msgid "Personal" @@ -77,23 +77,23 @@ msgstr "Imaxe inválida" msgid "web services under your control" msgstr "" -#: private/files.php:232 +#: private/files.php:235 msgid "ZIP download is turned off." msgstr "" -#: private/files.php:233 +#: private/files.php:236 msgid "Files need to be downloaded one by one." msgstr "" -#: private/files.php:234 private/files.php:261 +#: private/files.php:237 private/files.php:264 msgid "Back to Files" msgstr "" -#: private/files.php:259 +#: private/files.php:262 msgid "Selected files too large to generate zip file." msgstr "" -#: private/files.php:260 +#: private/files.php:263 msgid "" "Please download the files separately in smaller chunks or kindly ask your " "administrator." @@ -279,13 +279,13 @@ msgstr "Afitar nome d'usuariu p'almin" msgid "Set an admin password." msgstr "Afitar contraseña p'almin" -#: private/setup.php:202 +#: private/setup.php:164 msgid "" "Your web server is not yet properly setup to allow files synchronization " "because the WebDAV interface seems to be broken." msgstr "El sirvidor web entá nun ta configurado pa permitir la sincronización de ficheros yá que la interface WebDAV paez nun tar funcionando." -#: private/setup.php:203 +#: private/setup.php:165 #, php-format msgid "Please double check the <a href='%s'>installation guides</a>." msgstr "" @@ -343,63 +343,63 @@ msgstr "" msgid "Share type %s is not valid for %s" msgstr "" -#: private/share/share.php:773 +#: private/share/share.php:774 #, php-format msgid "" "Setting permissions for %s failed, because the permissions exceed " "permissions granted to %s" msgstr "" -#: private/share/share.php:834 +#: private/share/share.php:835 #, php-format msgid "Setting permissions for %s failed, because the item was not found" msgstr "" -#: private/share/share.php:940 +#: private/share/share.php:941 #, php-format msgid "Sharing backend %s must implement the interface OCP\\Share_Backend" msgstr "" -#: private/share/share.php:947 +#: private/share/share.php:948 #, php-format msgid "Sharing backend %s not found" msgstr "" -#: private/share/share.php:953 +#: private/share/share.php:954 #, php-format msgid "Sharing backend for %s not found" msgstr "" -#: private/share/share.php:1367 +#: private/share/share.php:1368 #, php-format msgid "Sharing %s failed, because the user %s is the original sharer" msgstr "" -#: private/share/share.php:1376 +#: private/share/share.php:1377 #, php-format msgid "" "Sharing %s failed, because the permissions exceed permissions granted to %s" msgstr "" -#: private/share/share.php:1391 +#: private/share/share.php:1392 #, php-format msgid "Sharing %s failed, because resharing is not allowed" msgstr "" -#: private/share/share.php:1403 +#: private/share/share.php:1404 #, php-format msgid "" "Sharing %s failed, because the sharing backend for %s could not find its " "source" msgstr "" -#: private/share/share.php:1417 +#: private/share/share.php:1418 #, php-format msgid "" "Sharing %s failed, because the file could not be found in the file cache" msgstr "" -#: private/tags.php:193 +#: private/tags.php:183 #, php-format msgid "Could not find category \"%s\"" msgstr "Nun pudo alcontrase la estaya \"%s.\"" diff --git a/l10n/ast/user_ldap.po b/l10n/ast/user_ldap.po index a924d32995a..39f3d980f17 100644 --- a/l10n/ast/user_ldap.po +++ b/l10n/ast/user_ldap.po @@ -10,8 +10,8 @@ msgid "" msgstr "" "Project-Id-Version: ownCloud\n" "Report-Msgid-Bugs-To: translations@owncloud.org\n" -"POT-Creation-Date: 2014-05-11 01:55-0400\n" -"PO-Revision-Date: 2014-05-11 05:02+0000\n" +"POT-Creation-Date: 2014-05-12 01:55-0400\n" +"PO-Revision-Date: 2014-05-11 15:20+0000\n" "Last-Translator: dixebra <davidlopezcastanon@gmail.com>\n" "Language-Team: Asturian (http://www.transifex.com/projects/p/owncloud/language/ast/)\n" "MIME-Version: 1.0\n" @@ -161,7 +161,7 @@ msgstr "" #: templates/part.settingcontrols.php:10 templates/part.wizardcontrols.php:14 msgid "Help" -msgstr "" +msgstr "Ayuda" #: templates/part.wizard-groupfilter.php:4 #, php-format diff --git a/l10n/el/settings.po b/l10n/el/settings.po index 3d9505a3cae..0622b53d030 100644 --- a/l10n/el/settings.po +++ b/l10n/el/settings.po @@ -16,9 +16,9 @@ msgid "" msgstr "" "Project-Id-Version: ownCloud\n" "Report-Msgid-Bugs-To: translations@owncloud.org\n" -"POT-Creation-Date: 2014-05-11 01:55-0400\n" -"PO-Revision-Date: 2014-05-11 05:02+0000\n" -"Last-Translator: I Robot\n" +"POT-Creation-Date: 2014-05-12 01:55-0400\n" +"PO-Revision-Date: 2014-05-11 18:20+0000\n" +"Last-Translator: Efstathios Iosifidis <iefstathios@gmail.com>\n" "Language-Team: Greek (http://www.transifex.com/projects/p/owncloud/language/el/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -530,7 +530,7 @@ msgstr "Επιτρέπονται ειδοποιήσεις ηλεκτρονικο #: templates/admin.php:254 msgid "Allow users to send mail notification for shared files" -msgstr "" +msgstr "Επιτρέψτε στους χρήστες να στέλνουν ειδοποιήσεις μέσω ηλεκτρονικού ταχυδρομείου για κοινόχρηστα αρχεία" #: templates/admin.php:262 msgid "Set default expiration date" @@ -542,7 +542,7 @@ msgstr "" #: templates/admin.php:266 msgid "days" -msgstr "" +msgstr "ημέρες" #: templates/admin.php:269 msgid "Enforce expiration date" diff --git a/l10n/km/core.po b/l10n/km/core.po index b64349b31e2..0bd6c1bc9db 100644 --- a/l10n/km/core.po +++ b/l10n/km/core.po @@ -8,8 +8,8 @@ msgid "" msgstr "" "Project-Id-Version: ownCloud\n" "Report-Msgid-Bugs-To: translations@owncloud.org\n" -"POT-Creation-Date: 2014-05-11 01:55-0400\n" -"PO-Revision-Date: 2014-05-11 05:55+0000\n" +"POT-Creation-Date: 2014-05-12 01:55-0400\n" +"PO-Revision-Date: 2014-05-11 14:00+0000\n" "Last-Translator: សុវិចិត្រ Sovichet ទេព Tep\n" "Language-Team: Khmer (http://www.transifex.com/projects/p/owncloud/language/km/)\n" "MIME-Version: 1.0\n" @@ -244,15 +244,15 @@ msgstr "លើកលែង" #: js/oc-dialogs.js:394 msgid "Continue" -msgstr "" +msgstr "បន្ត" #: js/oc-dialogs.js:441 js/oc-dialogs.js:454 msgid "(all selected)" -msgstr "" +msgstr "(បានជ្រើសទាំងអស់)" #: js/oc-dialogs.js:444 js/oc-dialogs.js:458 msgid "({count} selected)" -msgstr "" +msgstr "(បានជ្រើស {count})" #: js/oc-dialogs.js:466 msgid "Error loading file exists template" @@ -260,23 +260,23 @@ msgstr "" #: js/setup.js:84 msgid "Very weak password" -msgstr "" +msgstr "ពាក្យសម្ងាត់ខ្សោយណាស់" #: js/setup.js:85 msgid "Weak password" -msgstr "" +msgstr "ពាក្យសម្ងាត់ខ្សោយ" #: js/setup.js:86 msgid "So-so password" -msgstr "" +msgstr "ពាក្យសម្ងាត់ធម្មតា" #: js/setup.js:87 msgid "Good password" -msgstr "" +msgstr "ពាក្យសម្ងាត់ល្អ" #: js/setup.js:88 msgid "Strong password" -msgstr "" +msgstr "ពាក្យសម្ងាត់ខ្លាំង" #: js/share.js:51 js/share.js:66 js/share.js:106 msgid "Shared" @@ -453,7 +453,7 @@ msgstr "" #: js/update.js:8 msgid "Please reload the page." -msgstr "" +msgstr "សូមផ្ទុកទំព័រនេះឡើងវិញ។" #: js/update.js:17 msgid "" @@ -475,7 +475,7 @@ msgstr "" msgid "" "A problem has occurred whilst sending the email, please contact your " "administrator." -msgstr "" +msgstr "មានកំហុសបានកើតឡើងនៅពេលលកំពុងផ្ញើអ៊ីមែលចេញ, សូមទាក់ទងអភិបាលរបស់អ្នក។" #: lostpassword/templates/email.php:2 msgid "Use the following link to reset your password: {link}" @@ -665,7 +665,7 @@ msgstr "បង្កើត<strong>គណនីអភិបាល</strong>" #: templates/installation.php:70 msgid "Storage & database" -msgstr "" +msgstr "ឃ្លាំងផ្ទុក & មូលដ្ឋានទិន្នន័យ" #: templates/installation.php:77 msgid "Data folder" @@ -705,7 +705,7 @@ msgstr "បញ្ចប់ការដំឡើង" #: templates/installation.php:150 msgid "Finishing …" -msgstr "" +msgstr "កំពុងបញ្ចប់ ..." #: templates/layout.user.php:40 msgid "" diff --git a/l10n/km/files.po b/l10n/km/files.po index 98ef8ef4f64..f24f3f14a80 100644 --- a/l10n/km/files.po +++ b/l10n/km/files.po @@ -3,13 +3,14 @@ # This file is distributed under the same license as the PACKAGE package. # # Translators: +# សុវិចិត្រ Sovichet ទេព Tep, 2014 msgid "" msgstr "" "Project-Id-Version: ownCloud\n" "Report-Msgid-Bugs-To: translations@owncloud.org\n" -"POT-Creation-Date: 2014-05-11 01:55-0400\n" -"PO-Revision-Date: 2014-05-11 05:50+0000\n" -"Last-Translator: I Robot\n" +"POT-Creation-Date: 2014-05-12 01:55-0400\n" +"PO-Revision-Date: 2014-05-11 14:10+0000\n" +"Last-Translator: សុវិចិត្រ Sovichet ទេព Tep\n" "Language-Team: Khmer (http://www.transifex.com/projects/p/owncloud/language/km/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -158,7 +159,7 @@ msgstr "" #: js/file-upload.js:358 msgid "Upload cancelled." -msgstr "" +msgstr "បានបោះបង់ការផ្ទុកឡើង។" #: js/file-upload.js:404 msgid "Could not get result from server." @@ -175,7 +176,7 @@ msgstr "" #: js/file-upload.js:562 js/filelist.js:963 msgid "{new_name} already exists" -msgstr "" +msgstr "មានឈ្មោះ {new_name} រួចហើយ" #: js/file-upload.js:617 msgid "Could not create file" @@ -195,21 +196,21 @@ msgstr "ចែករំលែក" #: js/fileactions.js:173 msgid "Delete permanently" -msgstr "" +msgstr "លុបជាអចិន្ត្រៃយ៍" #: js/fileactions.js:234 msgid "Rename" -msgstr "" +msgstr "ប្ដូរឈ្មោះ" #: js/filelist.js:221 msgid "" "Your download is being prepared. This might take some time if the files are " "big." -msgstr "" +msgstr "ការទាញយករបស់អ្នកកំពុងត្រូវបានរៀបចំហើយ។ នេះអាចចំណាយពេលមួយសំទុះ ប្រសិនបើឯកសារធំ។" #: js/filelist.js:502 js/filelist.js:1422 msgid "Pending" -msgstr "" +msgstr "កំពុងរង់ចាំ" #: js/filelist.js:916 msgid "Error moving file." @@ -241,7 +242,7 @@ msgstr "ទំហំ" #: js/filelist.js:1226 templates/index.php:81 msgid "Modified" -msgstr "" +msgstr "បានកែប្រែ" #: js/filelist.js:1235 js/filesummary.js:141 js/filesummary.js:168 msgid "%n folder" @@ -309,7 +310,7 @@ msgstr "" #: templates/admin.php:6 msgid "Maximum upload size" -msgstr "" +msgstr "ទំហំផ្ទុកឡើងជាអតិបរមា" #: templates/admin.php:9 msgid "max. possible: " @@ -321,15 +322,15 @@ msgstr "" #: templates/admin.php:16 msgid "Enable ZIP-download" -msgstr "" +msgstr "បើកការទាញយកជា ZIP" #: templates/admin.php:19 msgid "0 is unlimited" -msgstr "" +msgstr "0 គឺមិនកំណត់" #: templates/admin.php:21 msgid "Maximum input size for ZIP files" -msgstr "" +msgstr "ទំហំចូលជាអតិបរមាសម្រាប់ឯកសារ ZIP" #: templates/admin.php:25 msgid "Save" @@ -337,7 +338,7 @@ msgstr "រក្សាទុក" #: templates/index.php:5 msgid "New" -msgstr "" +msgstr "ថ្មី" #: templates/index.php:8 msgid "New text file" @@ -345,7 +346,7 @@ msgstr "" #: templates/index.php:9 msgid "Text file" -msgstr "" +msgstr "ឯកសារអក្សរ" #: templates/index.php:12 msgid "New folder" @@ -357,15 +358,15 @@ msgstr "ថត" #: templates/index.php:16 msgid "From link" -msgstr "" +msgstr "ពីតំណ" #: templates/index.php:40 msgid "Deleted files" -msgstr "" +msgstr "បានលុបឯកសារ" #: templates/index.php:45 msgid "Cancel upload" -msgstr "" +msgstr "បោះបង់ការផ្ទុកឡើង" #: templates/index.php:51 msgid "You don’t have permission to upload or create files here" @@ -373,7 +374,7 @@ msgstr "" #: templates/index.php:56 msgid "Nothing in here. Upload something!" -msgstr "" +msgstr "គ្មានអ្វីនៅទីនេះទេ។ ផ្ទុកឡើងអ្វីមួយ!" #: templates/index.php:73 msgid "Download" @@ -385,7 +386,7 @@ msgstr "លុប" #: templates/index.php:98 msgid "Upload too large" -msgstr "" +msgstr "ផ្ទុកឡើងធំពេក" #: templates/index.php:100 msgid "" diff --git a/l10n/km/files_external.po b/l10n/km/files_external.po index d50bb1ff414..539462b278d 100644 --- a/l10n/km/files_external.po +++ b/l10n/km/files_external.po @@ -7,8 +7,8 @@ msgid "" msgstr "" "Project-Id-Version: ownCloud\n" "Report-Msgid-Bugs-To: translations@owncloud.org\n" -"POT-Creation-Date: 2014-05-11 01:55-0400\n" -"PO-Revision-Date: 2014-05-11 05:02+0000\n" +"POT-Creation-Date: 2014-05-12 01:55-0400\n" +"PO-Revision-Date: 2014-05-11 14:00+0000\n" "Last-Translator: I Robot\n" "Language-Team: Khmer (http://www.transifex.com/projects/p/owncloud/language/km/)\n" "MIME-Version: 1.0\n" @@ -195,7 +195,7 @@ msgstr "" #: js/settings.js:318 js/settings.js:325 msgid "Saved" -msgstr "" +msgstr "បានរក្សាទុក" #: lib/config.php:597 msgid "<b>Note:</b> " diff --git a/l10n/km/files_trashbin.po b/l10n/km/files_trashbin.po index a1b7649d1a0..13bc8ae1a64 100644 --- a/l10n/km/files_trashbin.po +++ b/l10n/km/files_trashbin.po @@ -7,8 +7,8 @@ msgid "" msgstr "" "Project-Id-Version: ownCloud\n" "Report-Msgid-Bugs-To: translations@owncloud.org\n" -"POT-Creation-Date: 2014-04-16 01:55-0400\n" -"PO-Revision-Date: 2014-04-16 05:41+0000\n" +"POT-Creation-Date: 2014-05-12 01:55-0400\n" +"PO-Revision-Date: 2014-05-11 14:10+0000\n" "Last-Translator: I Robot\n" "Language-Team: Khmer (http://www.transifex.com/projects/p/owncloud/language/km/)\n" "MIME-Version: 1.0\n" @@ -29,17 +29,21 @@ msgstr "" #: js/filelist.js:3 msgid "Deleted files" -msgstr "" +msgstr "បានលុបឯកសារ" -#: js/trash.js:33 js/trash.js:124 js/trash.js:173 +#: js/filelist.js:88 js/filelist.js:132 js/filelist.js:181 msgid "Error" msgstr "កំហុស" -#: js/trash.js:264 +#: js/trash.js:48 templates/index.php:22 templates/index.php:24 +msgid "Restore" +msgstr "ស្ដារមកវិញ" + +#: js/trash.js:107 msgid "Deleted Files" msgstr "" -#: lib/trashbin.php:859 lib/trashbin.php:861 +#: lib/trashbin.php:861 lib/trashbin.php:863 msgid "restored" msgstr "" @@ -51,10 +55,6 @@ msgstr "" msgid "Name" msgstr "ឈ្មោះ" -#: templates/index.php:22 templates/index.php:24 -msgid "Restore" -msgstr "ស្ដារមកវិញ" - #: templates/index.php:30 msgid "Deleted" msgstr "" diff --git a/l10n/km/settings.po b/l10n/km/settings.po index fece2bba194..7ac02ecb074 100644 --- a/l10n/km/settings.po +++ b/l10n/km/settings.po @@ -9,9 +9,9 @@ msgid "" msgstr "" "Project-Id-Version: ownCloud\n" "Report-Msgid-Bugs-To: translations@owncloud.org\n" -"POT-Creation-Date: 2014-05-11 01:55-0400\n" -"PO-Revision-Date: 2014-05-11 05:02+0000\n" -"Last-Translator: I Robot\n" +"POT-Creation-Date: 2014-05-12 01:55-0400\n" +"PO-Revision-Date: 2014-05-11 14:00+0000\n" +"Last-Translator: សុវិចិត្រ Sovichet ទេព Tep\n" "Language-Team: Khmer (http://www.transifex.com/projects/p/owncloud/language/km/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -26,20 +26,20 @@ msgstr "" #: admin/controller.php:73 msgid "Saved" -msgstr "" +msgstr "បានរក្សាទុក" #: admin/controller.php:90 msgid "test email settings" -msgstr "" +msgstr "សាកល្បងការកំណត់អ៊ីមែល" #: admin/controller.php:91 msgid "If you received this email, the settings seem to be correct." -msgstr "" +msgstr "ប្រសិនបើអ្នកទទួលបានអ៊ីមែលនេះ មានន័យថាការកំណត់គឺបានត្រឹមមត្រូវហើយ។" #: admin/controller.php:94 msgid "" "A problem occurred while sending the e-mail. Please revisit your settings." -msgstr "" +msgstr "មានកំហុសកើតឡើងនៅពេលកំពុងផ្ញើអ៊ីមែលចេញ។ សូមមើលការកំណត់របស់អ្នកម្ដងទៀត។" #: admin/controller.php:99 msgid "Email sent" @@ -47,7 +47,7 @@ msgstr "បានផ្ញើអ៊ីមែល" #: admin/controller.php:101 msgid "You need to set your user email before being able to send test emails." -msgstr "" +msgstr "អ្នកត្រូវតែកំណត់អ៊ីមែលរបស់អ្នកមុននឹងអាចផ្ញើអ៊ីមែលសាកល្បងបាន។" #: admin/controller.php:116 templates/admin.php:316 msgid "Send mode" @@ -144,7 +144,7 @@ msgstr "មិនអាចធ្វើបច្ចុប្បន្ន #: changepassword/controller.php:17 msgid "Wrong password" -msgstr "" +msgstr "ខុសពាក្យសម្ងាត់" #: changepassword/controller.php:36 msgid "No user supplied" @@ -173,7 +173,7 @@ msgstr "" #: js/admin.js:73 msgid "Sending..." -msgstr "" +msgstr "កំពុងផ្ញើ..." #: js/apps.js:45 templates/help.php:4 msgid "User Documentation" @@ -181,7 +181,7 @@ msgstr "ឯកសារសម្រាប់អ្នកប្រើប #: js/apps.js:50 msgid "Admin Documentation" -msgstr "" +msgstr "កម្រងឯកសារអភិបាល" #: js/apps.js:67 msgid "Update to {appversion}" @@ -229,27 +229,27 @@ msgstr "បានធ្វើបច្ចុប្បន្នភាព" #: js/personal.js:243 msgid "Select a profile picture" -msgstr "" +msgstr "ជ្រើសរូបភាពប្រវត្តិរូប" #: js/personal.js:274 msgid "Very weak password" -msgstr "" +msgstr "ពាក្យសម្ងាត់ខ្សោយណាស់" #: js/personal.js:275 msgid "Weak password" -msgstr "" +msgstr "ពាក្យសម្ងាត់ខ្សោយ" #: js/personal.js:276 msgid "So-so password" -msgstr "" +msgstr "ពាក្យសម្ងាត់ធម្មតា" #: js/personal.js:277 msgid "Good password" -msgstr "" +msgstr "ពាក្យសម្ងាត់ល្អ" #: js/personal.js:278 msgid "Strong password" -msgstr "" +msgstr "ពាក្យសម្ងាត់ខ្លាំង" #: js/personal.js:313 msgid "Decrypting files... Please wait, this can take some time." @@ -326,7 +326,7 @@ msgstr "" #: templates/admin.php:16 templates/admin.php:23 msgid "None" -msgstr "" +msgstr "គ្មាន" #: templates/admin.php:17 msgid "Login" @@ -342,11 +342,11 @@ msgstr "" #: templates/admin.php:24 msgid "SSL" -msgstr "" +msgstr "SSL" #: templates/admin.php:25 msgid "TLS" -msgstr "" +msgstr "TLS" #: templates/admin.php:47 templates/admin.php:61 msgid "Security Warning" @@ -494,7 +494,7 @@ msgstr "អនុញ្ញាតឲ្យអ្នកប្រើច #: templates/admin.php:227 msgid "Allow public uploads" -msgstr "" +msgstr "អនុញ្ញាតការផ្ទុកឡើងជាសាធារណៈ" #: templates/admin.php:228 msgid "" @@ -567,7 +567,7 @@ msgstr "" #: templates/admin.php:311 msgid "Email Server" -msgstr "" +msgstr "ម៉ាស៊ីនបម្រើអ៊ីមែល" #: templates/admin.php:313 msgid "This is used for sending out notifications." @@ -575,7 +575,7 @@ msgstr "" #: templates/admin.php:344 msgid "From address" -msgstr "" +msgstr "ពីអាសយដ្ឋាន" #: templates/admin.php:366 msgid "Authentication required" @@ -607,7 +607,7 @@ msgstr "" #: templates/admin.php:388 msgid "Send email" -msgstr "" +msgstr "ផ្ញើអ៊ីមែល" #: templates/admin.php:393 msgid "Log" @@ -706,11 +706,11 @@ msgstr "ពាក្យសម្ងាត់" #: templates/personal.php:39 msgid "Your password was changed" -msgstr "" +msgstr "ពាក្យសម្ងាត់របស់អ្នកត្រូវបានប្ដូរ" #: templates/personal.php:40 msgid "Unable to change your password" -msgstr "" +msgstr "មិនអាចប្ដូរពាក្យសម្ងាត់របស់អ្នកបានទេ" #: templates/personal.php:42 msgid "Current password" @@ -744,19 +744,19 @@ msgstr "" #: templates/personal.php:89 msgid "Profile picture" -msgstr "" +msgstr "រូបភាពប្រវត្តិរូប" #: templates/personal.php:94 msgid "Upload new" -msgstr "" +msgstr "ផ្ទុកឡើងថ្មី" #: templates/personal.php:96 msgid "Select new from Files" -msgstr "" +msgstr "ជ្រើសថ្មីពីឯកសារ" #: templates/personal.php:97 msgid "Remove image" -msgstr "" +msgstr "ដករូបភាពចេញ" #: templates/personal.php:98 msgid "Either png or jpg. Ideally square but you will be able to crop it." @@ -799,7 +799,7 @@ msgstr "" #: templates/personal.php:157 msgid "Log-in password" -msgstr "" +msgstr "ពាក្យសម្ងាត់ចូលគណនី" #: templates/personal.php:162 msgid "Decrypt all Files" @@ -825,7 +825,7 @@ msgstr "" #: templates/users.php:40 msgid "Default Storage" -msgstr "" +msgstr "ឃ្លាំងផ្ទុកលំនាំដើម" #: templates/users.php:42 templates/users.php:137 msgid "Please enter storage quota (ex: \"512 MB\" or \"12 GB\")" @@ -833,7 +833,7 @@ msgstr "" #: templates/users.php:46 templates/users.php:146 msgid "Unlimited" -msgstr "" +msgstr "មិនកំណត់" #: templates/users.php:64 templates/users.php:161 msgid "Other" @@ -845,7 +845,7 @@ msgstr "ឈ្មោះអ្នកប្រើ" #: templates/users.php:92 msgid "Storage" -msgstr "" +msgstr "ឃ្លាំងផ្ទុក" #: templates/users.php:106 msgid "change full name" @@ -853,8 +853,8 @@ msgstr "" #: templates/users.php:110 msgid "set new password" -msgstr "" +msgstr "កំណត់ពាក្យសម្ងាត់ថ្មី" #: templates/users.php:141 msgid "Default" -msgstr "" +msgstr "លំនាំដើម" diff --git a/l10n/km/user_ldap.po b/l10n/km/user_ldap.po index 1855a82583e..63e0f49af6d 100644 --- a/l10n/km/user_ldap.po +++ b/l10n/km/user_ldap.po @@ -8,8 +8,8 @@ msgid "" msgstr "" "Project-Id-Version: ownCloud\n" "Report-Msgid-Bugs-To: translations@owncloud.org\n" -"POT-Creation-Date: 2014-05-11 01:55-0400\n" -"PO-Revision-Date: 2014-05-11 05:02+0000\n" +"POT-Creation-Date: 2014-05-12 01:55-0400\n" +"PO-Revision-Date: 2014-05-11 14:00+0000\n" "Last-Translator: I Robot\n" "Language-Team: Khmer (http://www.transifex.com/projects/p/owncloud/language/km/)\n" "MIME-Version: 1.0\n" @@ -284,7 +284,7 @@ msgstr "" #: templates/part.wizardcontrols.php:8 msgid "Continue" -msgstr "" +msgstr "បន្ត" #: templates/settings.php:11 msgid "" diff --git a/l10n/nl/core.po b/l10n/nl/core.po index 069d159f4ea..ff7a56a481c 100644 --- a/l10n/nl/core.po +++ b/l10n/nl/core.po @@ -4,15 +4,15 @@ # # Translators: # André Koot <meneer@tken.net>, 2013-2014 -# kwillems <kwillems@zonnet.nl>, 2013 +# kwillems <kwillems@zonnet.nl>, 2013-2014 # Jorcee <mail@jordyc.nl>, 2013 msgid "" msgstr "" "Project-Id-Version: ownCloud\n" "Report-Msgid-Bugs-To: translations@owncloud.org\n" -"POT-Creation-Date: 2014-05-11 01:55-0400\n" -"PO-Revision-Date: 2014-05-11 05:02+0000\n" -"Last-Translator: I Robot\n" +"POT-Creation-Date: 2014-05-12 01:55-0400\n" +"PO-Revision-Date: 2014-05-11 19:10+0000\n" +"Last-Translator: kwillems <kwillems@zonnet.nl>\n" "Language-Team: Dutch (http://www.transifex.com/projects/p/owncloud/language/nl/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -412,7 +412,7 @@ msgstr "Wachtwoord beveiligd" #: js/share.js:734 msgid "Error unsetting expiration date" -msgstr "Fout tijdens het verwijderen van de verval datum" +msgstr "Fout tijdens het verwijderen van de vervaldatum" #: js/share.js:752 msgid "Error setting expiration date" @@ -467,11 +467,11 @@ msgid "" "The update was unsuccessful. Please report this issue to the <a " "href=\"https://github.com/owncloud/core/issues\" target=\"_blank\">ownCloud " "community</a>." -msgstr "De update is niet geslaagd. Meld dit probleem aan bij de <a href=\"https://github.com/owncloud/core/issues\" target=\"_blank\">ownCloud community</a>." +msgstr "De update is niet geslaagd. Meld dit probleem bij de <a href=\"https://github.com/owncloud/core/issues\" target=\"_blank\">ownCloud community</a>." #: js/update.js:21 msgid "The update was successful. Redirecting you to ownCloud now." -msgstr "De update is geslaagd. Je wordt teruggeleid naar je eigen ownCloud." +msgstr "De update is geslaagd. U wordt teruggeleid naar uw eigen ownCloud." #: lostpassword/controller.php:70 #, php-format @@ -486,22 +486,22 @@ msgstr "Er ontstond een probleem bij het versturen van het e-mailbericht, neem c #: lostpassword/templates/email.php:2 msgid "Use the following link to reset your password: {link}" -msgstr "Gebruik de volgende link om je wachtwoord te resetten: {link}" +msgstr "Gebruik de volgende link om uw wachtwoord te resetten: {link}" #: lostpassword/templates/lostpassword.php:7 msgid "" "The link to reset your password has been sent to your email.<br>If you do " "not receive it within a reasonable amount of time, check your spam/junk " "folders.<br>If it is not there ask your local administrator ." -msgstr "De link voor het resetten van je wachtwoord is verzonden naar je e-mailadres.<br>Als je dat bericht niet snel ontvangen hebt, controleer dan uw spambakje.<br>Als het daar ook niet is, vraag dan je beheerder om te helpen." +msgstr "De link voor het resetten van uw wachtwoord is verzonden naar uw e-mailadres.<br>Als u dat bericht niet snel ontvangen hebt, controleer dan uw spammap.<br>Als het daar ook niet is, vraag dan uw beheerder om hulp." #: lostpassword/templates/lostpassword.php:15 msgid "Request failed!<br>Did you make sure your email/username was right?" -msgstr "Aanvraag mislukt!<br>Weet je zeker dat je gebruikersnaam en/of wachtwoord goed waren?" +msgstr "Aanvraag mislukt!<br>Weet u zeker dat uw gebruikersnaam en/of wachtwoord goed waren?" #: lostpassword/templates/lostpassword.php:18 msgid "You will receive a link to reset your password via Email." -msgstr "Je ontvangt een link om je wachtwoord opnieuw in te stellen via e-mail." +msgstr "U ontvangt een link om uw wachtwoord opnieuw in te stellen via e-mail." #: lostpassword/templates/lostpassword.php:21 templates/installation.php:53 #: templates/login.php:32 @@ -514,7 +514,7 @@ msgid "" "will be no way to get your data back after your password is reset. If you " "are not sure what to do, please contact your administrator before you " "continue. Do you really want to continue?" -msgstr "Je bestanden zijn versleuteld. Als je geen recoverykey hebt ingeschakeld is er geen manier om je data terug te krijgen indien je je wachtwoord reset!\nAls je niet weet wat te doen, neem dan alsjeblieft contact op met je administrator eer je doorgaat.\nWil je echt doorgaan?" +msgstr "Uw bestanden zijn versleuteld. Als u geen recoverykey hebt ingeschakeld is er geen manier om uw data terug te krijgen na het resetten van uw wachtwoord.\nAls u niet weet wat u moet doen, neem dan alstublieft contact op met uw systeembeheerder voordat u doorgaat.\nWil u echt doorgaan?" #: lostpassword/templates/lostpassword.php:27 msgid "Yes, I really want to reset my password now" @@ -526,7 +526,7 @@ msgstr "Reset" #: lostpassword/templates/resetpassword.php:4 msgid "Your password was reset" -msgstr "Je wachtwoord is gewijzigd" +msgstr "Uw wachtwoord is gewijzigd" #: lostpassword/templates/resetpassword.php:5 msgid "To login page" @@ -616,7 +616,7 @@ msgid "" "just letting you know that %s shared %s with you.\n" "View it: %s\n" "\n" -msgstr "Hallo daar,\n\neven een berichtje dat %s %s met u deelde.\nBekijk het: %s\n\n" +msgstr "Hallo,\n\n%s deelt %s met u.\nBekijk het: %s\n\n" #: templates/altmail.php:4 templates/mail.php:17 #, php-format @@ -634,7 +634,7 @@ msgstr "Beveiligingswaarschuwing" #: templates/installation.php:26 msgid "Your PHP version is vulnerable to the NULL Byte attack (CVE-2006-7243)" -msgstr "Je PHP-versie is kwetsbaar voor de NULL byte aanval (CVE-2006-7243)" +msgstr "Uw PHP-versie is kwetsbaar voor de NULL byte aanval (CVE-2006-7243)" #: templates/installation.php:27 #, php-format @@ -657,7 +657,7 @@ msgstr "Zonder random nummer generator is het mogelijk voor een aanvaller om de msgid "" "Your data directory and files are probably accessible from the internet " "because the .htaccess file does not work." -msgstr "Je gegevensdirectory en bestanden zijn vermoedelijk bereikbaar vanaf het internet omdat het .htaccess-bestand niet werkt." +msgstr "Uw gegevensdirectory en bestanden zijn vermoedelijk bereikbaar vanaf het internet omdat het .htaccess-bestand niet functioneert." #: templates/installation.php:42 #, php-format @@ -719,7 +719,7 @@ msgid "" "This application requires JavaScript to be enabled for correct operation. " "Please <a href=\"http://enable-javascript.com/\" target=\"_blank\">enable " "JavaScript</a> and re-load this interface." -msgstr "Deze applicatie heeft een werkend JavaScript nodig. <a href=\"http://enable-javascript.com/\" target=\"_blank\">activeer JavaScript</a> en herlaad deze interface." +msgstr "Deze applicatie heeft JavaScript nodig om correct te functioneren. <a href=\"http://enable-javascript.com/\" target=\"_blank\">Activeer JavaScript</a> en herlaad deze interface." #: templates/layout.user.php:44 #, php-format @@ -738,11 +738,11 @@ msgstr "Automatische aanmelding geweigerd!" msgid "" "If you did not change your password recently, your account may be " "compromised!" -msgstr "Als je je wachtwoord niet onlangs heeft aangepast, kan je account overgenomen zijn!" +msgstr "Als u uw wachtwoord niet onlangs heeft aangepast, kan uw account overgenomen zijn!" #: templates/login.php:12 msgid "Please change your password to secure your account again." -msgstr "Wijzig je wachtwoord zodat je account weer beveiligd is." +msgstr "Wijzig uw wachtwoord zodat uw account weer beveiligd is." #: templates/login.php:17 msgid "Server side authentication failed!" @@ -762,7 +762,7 @@ msgstr "onthoud gegevens" #: templates/login.php:54 msgid "Log in" -msgstr "Meld je aan" +msgstr "Meld u aan" #: templates/login.php:60 msgid "Alternative Logins" @@ -773,7 +773,7 @@ msgstr "Alternatieve inlogs" msgid "" "Hey there,<br><br>just letting you know that %s shared <strong>%s</strong> " "with you.<br><a href=\"%s\">View it!</a><br><br>" -msgstr "Hallo daar,<br><br>we willen even laten weten dat %s <strong>%s</strong> met u heeft gedeeld.<br><a href=\"%s\">Bekijk het!</a><br><br>" +msgstr "Hallo,<br><br>%s deelt <strong>%s</strong> met u.<br><a href=\"%s\">Bekijk het!</a><br><br>" #: templates/singleuser.user.php:3 msgid "This ownCloud instance is currently in single user mode." diff --git a/l10n/pt_PT/lib.po b/l10n/pt_PT/lib.po index 541c093bdbe..f6f1ff539c9 100644 --- a/l10n/pt_PT/lib.po +++ b/l10n/pt_PT/lib.po @@ -3,7 +3,7 @@ # This file is distributed under the same license as the PACKAGE package. # # Translators: -# Helder Meneses <helder.meneses@gmail.com>, 2013 +# Helder Meneses <helder.meneses@gmail.com>, 2013-2014 # jmruas <jmruas@gmail.com>, 2014 # Drew Melim <nokostya.translation@gmail.com>, 2014 # PapiMigas Migas <papimigas@gmail.com>, 2013 @@ -11,9 +11,9 @@ msgid "" msgstr "" "Project-Id-Version: ownCloud\n" "Report-Msgid-Bugs-To: translations@owncloud.org\n" -"POT-Creation-Date: 2014-05-07 01:55-0400\n" -"PO-Revision-Date: 2014-05-05 15:09+0000\n" -"Last-Translator: Drew Melim <nokostya.translation@gmail.com>\n" +"POT-Creation-Date: 2014-05-12 01:55-0400\n" +"PO-Revision-Date: 2014-05-11 12:00+0000\n" +"Last-Translator: Helder Meneses <helder.meneses@gmail.com>\n" "Language-Team: Portuguese (Portugal) (http://www.transifex.com/projects/p/owncloud/language/pt_PT/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -346,63 +346,63 @@ msgstr "" msgid "Share type %s is not valid for %s" msgstr "" -#: private/share/share.php:773 +#: private/share/share.php:774 #, php-format msgid "" "Setting permissions for %s failed, because the permissions exceed " "permissions granted to %s" msgstr "" -#: private/share/share.php:834 +#: private/share/share.php:835 #, php-format msgid "Setting permissions for %s failed, because the item was not found" msgstr "" -#: private/share/share.php:940 +#: private/share/share.php:941 #, php-format msgid "Sharing backend %s must implement the interface OCP\\Share_Backend" msgstr "" -#: private/share/share.php:947 +#: private/share/share.php:948 #, php-format msgid "Sharing backend %s not found" msgstr "" -#: private/share/share.php:953 +#: private/share/share.php:954 #, php-format msgid "Sharing backend for %s not found" msgstr "" -#: private/share/share.php:1367 +#: private/share/share.php:1368 #, php-format msgid "Sharing %s failed, because the user %s is the original sharer" msgstr "" -#: private/share/share.php:1376 +#: private/share/share.php:1377 #, php-format msgid "" "Sharing %s failed, because the permissions exceed permissions granted to %s" msgstr "" -#: private/share/share.php:1391 +#: private/share/share.php:1392 #, php-format msgid "Sharing %s failed, because resharing is not allowed" msgstr "" -#: private/share/share.php:1403 +#: private/share/share.php:1404 #, php-format msgid "" "Sharing %s failed, because the sharing backend for %s could not find its " "source" msgstr "" -#: private/share/share.php:1417 +#: private/share/share.php:1418 #, php-format msgid "" "Sharing %s failed, because the file could not be found in the file cache" msgstr "" -#: private/tags.php:193 +#: private/tags.php:183 #, php-format msgid "Could not find category \"%s\"" msgstr "Não foi encontrado a categoria \"%s\"" @@ -459,7 +459,7 @@ msgstr "anos atrás" msgid "" "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", " "\"0-9\", and \"_.@-\"" -msgstr "" +msgstr "Apenas os seguintes caracteres são permitidos no nome de utilizador: \"a-z\", \"A-Z\", \"0-9\", e \"_.@-\"" #: private/user/manager.php:237 msgid "A valid username must be provided" @@ -471,4 +471,4 @@ msgstr "Uma password válida deve ser fornecida" #: private/user/manager.php:246 msgid "The username is already being used" -msgstr "" +msgstr "O nome de utilizador já está a ser usado" diff --git a/l10n/templates/core.pot b/l10n/templates/core.pot index 4bc75188e51..c7664166ad4 100644 --- a/l10n/templates/core.pot +++ b/l10n/templates/core.pot @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: ownCloud Core 6.0.0\n" "Report-Msgid-Bugs-To: translations@owncloud.org\n" -"POT-Creation-Date: 2014-05-11 01:55-0400\n" +"POT-Creation-Date: 2014-05-12 01:55-0400\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Language-Team: LANGUAGE <LL@li.org>\n" diff --git a/l10n/templates/files.pot b/l10n/templates/files.pot index 24f8468f4e7..1a34b8ad030 100644 --- a/l10n/templates/files.pot +++ b/l10n/templates/files.pot @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: ownCloud Core 6.0.0\n" "Report-Msgid-Bugs-To: translations@owncloud.org\n" -"POT-Creation-Date: 2014-05-11 01:55-0400\n" +"POT-Creation-Date: 2014-05-12 01:55-0400\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Language-Team: LANGUAGE <LL@li.org>\n" diff --git a/l10n/templates/files_encryption.pot b/l10n/templates/files_encryption.pot index 32aef8dcacb..9fdf9686d4c 100644 --- a/l10n/templates/files_encryption.pot +++ b/l10n/templates/files_encryption.pot @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: ownCloud Core 6.0.0\n" "Report-Msgid-Bugs-To: translations@owncloud.org\n" -"POT-Creation-Date: 2014-05-11 01:55-0400\n" +"POT-Creation-Date: 2014-05-12 01:55-0400\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Language-Team: LANGUAGE <LL@li.org>\n" diff --git a/l10n/templates/files_external.pot b/l10n/templates/files_external.pot index 502b49ffb12..f3048fd5147 100644 --- a/l10n/templates/files_external.pot +++ b/l10n/templates/files_external.pot @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: ownCloud Core 6.0.0\n" "Report-Msgid-Bugs-To: translations@owncloud.org\n" -"POT-Creation-Date: 2014-05-11 01:55-0400\n" +"POT-Creation-Date: 2014-05-12 01:55-0400\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Language-Team: LANGUAGE <LL@li.org>\n" diff --git a/l10n/templates/files_sharing.pot b/l10n/templates/files_sharing.pot index 2527b2606a4..c6269882b54 100644 --- a/l10n/templates/files_sharing.pot +++ b/l10n/templates/files_sharing.pot @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: ownCloud Core 6.0.0\n" "Report-Msgid-Bugs-To: translations@owncloud.org\n" -"POT-Creation-Date: 2014-05-11 01:55-0400\n" +"POT-Creation-Date: 2014-05-12 01:55-0400\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Language-Team: LANGUAGE <LL@li.org>\n" diff --git a/l10n/templates/files_trashbin.pot b/l10n/templates/files_trashbin.pot index 34793ac7874..6fb1a5fcd5e 100644 --- a/l10n/templates/files_trashbin.pot +++ b/l10n/templates/files_trashbin.pot @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: ownCloud Core 6.0.0\n" "Report-Msgid-Bugs-To: translations@owncloud.org\n" -"POT-Creation-Date: 2014-05-11 01:55-0400\n" +"POT-Creation-Date: 2014-05-12 01:55-0400\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Language-Team: LANGUAGE <LL@li.org>\n" diff --git a/l10n/templates/files_versions.pot b/l10n/templates/files_versions.pot index 05692061ee4..b2408e78221 100644 --- a/l10n/templates/files_versions.pot +++ b/l10n/templates/files_versions.pot @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: ownCloud Core 6.0.0\n" "Report-Msgid-Bugs-To: translations@owncloud.org\n" -"POT-Creation-Date: 2014-05-11 01:55-0400\n" +"POT-Creation-Date: 2014-05-12 01:55-0400\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Language-Team: LANGUAGE <LL@li.org>\n" diff --git a/l10n/templates/lib.pot b/l10n/templates/lib.pot index 47dbeeadb7d..ca715cedd12 100644 --- a/l10n/templates/lib.pot +++ b/l10n/templates/lib.pot @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: ownCloud Core 6.0.0\n" "Report-Msgid-Bugs-To: translations@owncloud.org\n" -"POT-Creation-Date: 2014-05-11 01:55-0400\n" +"POT-Creation-Date: 2014-05-12 01:55-0400\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Language-Team: LANGUAGE <LL@li.org>\n" diff --git a/l10n/templates/private.pot b/l10n/templates/private.pot index d6aad8cee26..e7498830947 100644 --- a/l10n/templates/private.pot +++ b/l10n/templates/private.pot @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: ownCloud Core 6.0.0\n" "Report-Msgid-Bugs-To: translations@owncloud.org\n" -"POT-Creation-Date: 2014-05-11 01:55-0400\n" +"POT-Creation-Date: 2014-05-12 01:55-0400\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Language-Team: LANGUAGE <LL@li.org>\n" diff --git a/l10n/templates/settings.pot b/l10n/templates/settings.pot index 1abc4434714..c266ca3240a 100644 --- a/l10n/templates/settings.pot +++ b/l10n/templates/settings.pot @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: ownCloud Core 6.0.0\n" "Report-Msgid-Bugs-To: translations@owncloud.org\n" -"POT-Creation-Date: 2014-05-11 01:55-0400\n" +"POT-Creation-Date: 2014-05-12 01:55-0400\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Language-Team: LANGUAGE <LL@li.org>\n" diff --git a/l10n/templates/user_ldap.pot b/l10n/templates/user_ldap.pot index 67dd3262e1a..0ee28cf2698 100644 --- a/l10n/templates/user_ldap.pot +++ b/l10n/templates/user_ldap.pot @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: ownCloud Core 6.0.0\n" "Report-Msgid-Bugs-To: translations@owncloud.org\n" -"POT-Creation-Date: 2014-05-11 01:55-0400\n" +"POT-Creation-Date: 2014-05-12 01:55-0400\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Language-Team: LANGUAGE <LL@li.org>\n" diff --git a/l10n/templates/user_webdavauth.pot b/l10n/templates/user_webdavauth.pot index d8fc66495d8..f59f5a76abf 100644 --- a/l10n/templates/user_webdavauth.pot +++ b/l10n/templates/user_webdavauth.pot @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: ownCloud Core 6.0.0\n" "Report-Msgid-Bugs-To: translations@owncloud.org\n" -"POT-Creation-Date: 2014-05-11 01:55-0400\n" +"POT-Creation-Date: 2014-05-12 01:55-0400\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Language-Team: LANGUAGE <LL@li.org>\n" diff --git a/lib/base.php b/lib/base.php index 1f7d0c0da65..be613706e64 100644 --- a/lib/base.php +++ b/lib/base.php @@ -212,34 +212,6 @@ class OC { } } - /* - * This function adds some security related headers to all requests served via base.php - * The implementation of this function has to happen here to ensure that all third-party - * components (e.g. SabreDAV) also benefit from this headers. - */ - public static function addSecurityHeaders() { - header('X-XSS-Protection: 1; mode=block'); // Enforce browser based XSS filters - header('X-Content-Type-Options: nosniff'); // Disable sniffing the content type for IE - - // iFrame Restriction Policy - $xFramePolicy = OC_Config::getValue('xframe_restriction', true); - if($xFramePolicy) { - header('X-Frame-Options: Sameorigin'); // Disallow iFraming from other domains - } - - // Content Security Policy - // If you change the standard policy, please also change it in config.sample.php - $policy = OC_Config::getValue('custom_csp_policy', - 'default-src \'self\'; ' - .'script-src \'self\' \'unsafe-eval\'; ' - .'style-src \'self\' \'unsafe-inline\'; ' - .'frame-src *; ' - .'img-src *; ' - .'font-src \'self\' data:; ' - .'media-src *'); - header('Content-Security-Policy:'.$policy); - } - public static function checkSSL() { // redirect to https site if configured if (OC_Config::getValue("forcessl", false)) { @@ -545,7 +517,7 @@ class OC { self::checkConfig(); self::checkInstalled(); self::checkSSL(); - self::addSecurityHeaders(); + OC_Response::addSecurityHeaders(); $errors = OC_Util::checkServer(); if (count($errors) > 0) { @@ -613,7 +585,7 @@ class OC { if (!is_null(self::$REQUESTEDFILE)) { $subdir = OC_App::getAppPath(OC::$REQUESTEDAPP) . '/' . self::$REQUESTEDFILE; $parent = OC_App::getAppPath(OC::$REQUESTEDAPP); - if (!OC_Helper::issubdirectory($subdir, $parent)) { + if (!OC_Helper::isSubDirectory($subdir, $parent)) { self::$REQUESTEDFILE = null; header('HTTP/1.0 404 Not Found'); exit; diff --git a/lib/l10n/ast.php b/lib/l10n/ast.php index 219317c19b9..4eaf734931f 100644 --- a/lib/l10n/ast.php +++ b/lib/l10n/ast.php @@ -1,5 +1,6 @@ <?php $TRANSLATIONS = array( +"Help" => "Ayuda", "Personal" => "Personal", "Settings" => "Axustes", "Users" => "Usuarios", diff --git a/lib/l10n/pt_PT.php b/lib/l10n/pt_PT.php index ad424933331..a1f2633fcc5 100644 --- a/lib/l10n/pt_PT.php +++ b/lib/l10n/pt_PT.php @@ -67,7 +67,9 @@ $TRANSLATIONS = array( "_%n month ago_::_%n months ago_" => array("","%n meses atrás"), "last year" => "ano passado", "years ago" => "anos atrás", +"Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-\"" => "Apenas os seguintes caracteres são permitidos no nome de utilizador: \"a-z\", \"A-Z\", \"0-9\", e \"_.@-\"", "A valid username must be provided" => "Um nome de utilizador válido deve ser fornecido", -"A valid password must be provided" => "Uma password válida deve ser fornecida" +"A valid password must be provided" => "Uma password válida deve ser fornecida", +"The username is already being used" => "O nome de utilizador já está a ser usado" ); $PLURAL_FORMS = "nplurals=2; plural=(n != 1);"; diff --git a/lib/private/appframework/app.php b/lib/private/appframework/app.php index 3b13194862d..baf52d02054 100644 --- a/lib/private/appframework/app.php +++ b/lib/private/appframework/app.php @@ -4,7 +4,7 @@ * ownCloud - App Framework * * @author Bernhard Posselt - * @copyright 2012 Bernhard Posselt nukeawhale@gmail.com + * @copyright 2012 Bernhard Posselt <dev@bernhard-posselt.com> * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE diff --git a/lib/private/appframework/core/api.php b/lib/private/appframework/core/api.php index 8f289a0a047..5f2035389b7 100644 --- a/lib/private/appframework/core/api.php +++ b/lib/private/appframework/core/api.php @@ -4,7 +4,7 @@ * ownCloud - App Framework * * @author Bernhard Posselt - * @copyright 2012 Bernhard Posselt nukeawhale@gmail.com + * @copyright 2012 Bernhard Posselt <dev@bernhard-posselt.com> * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE diff --git a/lib/private/appframework/db/db.php b/lib/private/appframework/db/db.php new file mode 100644 index 00000000000..fc77a38f814 --- /dev/null +++ b/lib/private/appframework/db/db.php @@ -0,0 +1,57 @@ +<?php + +/** + * ownCloud - App Framework + * + * @author Bernhard Posselt + * @copyright 2012 Bernhard Posselt dev@bernhard-posselt.com + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + * License as published by the Free Software Foundation; either + * version 3 of the License, or any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU AFFERO GENERAL PUBLIC LICENSE for more details. + * + * You should have received a copy of the GNU Affero General Public + * License along with this library. If not, see <http://www.gnu.org/licenses/>. + * + */ + +namespace OC\AppFramework\Db; + +use \OCP\IDb; + + +/** + * Small Facade for being able to inject the database connection for tests + */ +class Db implements IDb { + + + /** + * Used to abstract the owncloud database access away + * @param string $sql the sql query with ? placeholder for params + * @param int $limit the maximum number of rows + * @param int $offset from which row we want to start + * @return \OC_DB_StatementWrapper prepared SQL query + */ + public function prepareQuery($sql, $limit=null, $offset=null){ + return \OCP\DB::prepare($sql, $limit, $offset); + } + + + /** + * Used to get the id of the just inserted element + * @param string $tableName the name of the table where we inserted the item + * @return int the id of the inserted element + */ + public function getInsertId($tableName){ + return \OCP\DB::insertid($tableName); + } + + +} diff --git a/lib/private/appframework/dependencyinjection/dicontainer.php b/lib/private/appframework/dependencyinjection/dicontainer.php index e478225a53d..97a6569a0f6 100644 --- a/lib/private/appframework/dependencyinjection/dicontainer.php +++ b/lib/private/appframework/dependencyinjection/dicontainer.php @@ -4,7 +4,7 @@ * ownCloud - App Framework * * @author Bernhard Posselt - * @copyright 2012 Bernhard Posselt nukeawhale@gmail.com + * @copyright 2012 Bernhard Posselt <dev@bernhard-posselt.com> * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE @@ -30,8 +30,10 @@ use OC\AppFramework\Http\Dispatcher; use OC\AppFramework\Core\API; use OC\AppFramework\Middleware\MiddlewareDispatcher; use OC\AppFramework\Middleware\Security\SecurityMiddleware; +use OC\AppFramework\Middleware\Security\CORSMiddleware; use OC\AppFramework\Utility\SimpleContainer; use OC\AppFramework\Utility\TimeFactory; +use OC\AppFramework\Utility\ControllerMethodReflector; use OCP\AppFramework\IApi; use OCP\AppFramework\IAppContainer; use OCP\AppFramework\Middleware; @@ -80,7 +82,12 @@ class DIContainer extends SimpleContainer implements IAppContainer{ }); $this['Dispatcher'] = $this->share(function($c) { - return new Dispatcher($c['Protocol'], $c['MiddlewareDispatcher']); + return new Dispatcher( + $c['Protocol'], + $c['MiddlewareDispatcher'], + $c['ControllerMethodReflector'], + $c['Request'] + ); }); @@ -89,13 +96,25 @@ class DIContainer extends SimpleContainer implements IAppContainer{ */ $app = $this; $this['SecurityMiddleware'] = $this->share(function($c) use ($app){ - return new SecurityMiddleware($app, $c['Request']); + return new SecurityMiddleware( + $app, + $c['Request'], + $c['ControllerMethodReflector'] + ); + }); + + $this['CORSMiddleware'] = $this->share(function($c) { + return new CORSMiddleware( + $c['Request'], + $c['ControllerMethodReflector'] + ); }); $middleWares = &$this->middleWares; $this['MiddlewareDispatcher'] = $this->share(function($c) use (&$middleWares) { $dispatcher = new MiddlewareDispatcher(); $dispatcher->registerMiddleware($c['SecurityMiddleware']); + $dispatcher->registerMiddleware($c['CORSMiddleware']); foreach($middleWares as $middleWare) { $dispatcher->registerMiddleware($c[$middleWare]); @@ -112,6 +131,9 @@ class DIContainer extends SimpleContainer implements IAppContainer{ return new TimeFactory(); }); + $this['ControllerMethodReflector'] = $this->share(function($c) { + return new ControllerMethodReflector(); + }); } diff --git a/lib/private/appframework/http.php b/lib/private/appframework/http.php index 65d926105f1..d09e1d3ce2e 100644 --- a/lib/private/appframework/http.php +++ b/lib/private/appframework/http.php @@ -4,7 +4,7 @@ * ownCloud - App Framework * * @author Bernhard Posselt, Thomas Tanghus, Bart Visscher - * @copyright 2012 Bernhard Posselt nukeawhale@gmail.com + * @copyright 2012 Bernhard Posselt <dev@bernhard-posselt.com> * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE diff --git a/lib/private/appframework/http/dispatcher.php b/lib/private/appframework/http/dispatcher.php index a2afb53f0fa..39ca3398c66 100644 --- a/lib/private/appframework/http/dispatcher.php +++ b/lib/private/appframework/http/dispatcher.php @@ -4,7 +4,7 @@ * ownCloud - App Framework * * @author Bernhard Posselt, Thomas Tanghus, Bart Visscher - * @copyright 2012 Bernhard Posselt nukeawhale@gmail.com + * @copyright 2012 Bernhard Posselt <dev@bernhard-posselt.com> * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE @@ -26,7 +26,11 @@ namespace OC\AppFramework\Http; use \OC\AppFramework\Middleware\MiddlewareDispatcher; use \OC\AppFramework\Http; +use \OC\AppFramework\Utility\ControllerMethodReflector; + use OCP\AppFramework\Controller; +use OCP\AppFramework\Http\Response; +use OCP\IRequest; /** @@ -36,17 +40,25 @@ class Dispatcher { private $middlewareDispatcher; private $protocol; - + private $reflector; + private $request; /** * @param Http $protocol the http protocol with contains all status headers * @param MiddlewareDispatcher $middlewareDispatcher the dispatcher which * runs the middleware + * @param ControllerMethodReflector the reflector that is used to inject + * the arguments for the controller + * @param IRequest $request the incoming request */ public function __construct(Http $protocol, - MiddlewareDispatcher $middlewareDispatcher) { + MiddlewareDispatcher $middlewareDispatcher, + ControllerMethodReflector $reflector, + IRequest $request) { $this->protocol = $protocol; $this->middlewareDispatcher = $middlewareDispatcher; + $this->reflector = $reflector; + $this->request = $request; } @@ -63,10 +75,13 @@ class Dispatcher { $out = array(null, array(), null); try { + // prefill reflector with everything thats needed for the + // middlewares + $this->reflector->reflect($controller, $methodName); $this->middlewareDispatcher->beforeController($controller, $methodName); - $response = $controller->$methodName(); + $response = $this->executeController($controller, $methodName); // if an exception appears, the middleware checks if it can handle the // exception and creates a response. If no response is created, it is @@ -98,4 +113,70 @@ class Dispatcher { } + /** + * Uses the reflected parameters, types and request parameters to execute + * the controller + * @param Controller $controller the controller to be executed + * @param string $methodName the method on the controller that should be executed + * @return Response + */ + private function executeController($controller, $methodName) { + $arguments = array(); + + // valid types that will be casted + $types = array('int', 'integer', 'bool', 'boolean', 'float'); + + foreach($this->reflector->getParameters() as $param) { + + // try to get the parameter from the request object and cast + // it to the type annotated in the @param annotation + $value = $this->request->getParam($param); + $type = $this->reflector->getType($param); + + // if this is submitted using GET or a POST form, 'false' should be + // converted to false + if(($type === 'bool' || $type === 'boolean') && + $value === 'false' && + ( + $this->request->method === 'GET' || + strpos($this->request->getHeader('Content-Type'), + 'application/x-www-form-urlencoded') !== false + ) + ) { + $value = false; + + } elseif(in_array($type, $types)) { + settype($value, $type); + } + + $arguments[] = $value; + } + + $response = call_user_func_array(array($controller, $methodName), $arguments); + + // format response if not of type response + if(!($response instanceof Response)) { + + // get format from the url format or request format parameter + $format = $this->request->getParam('format'); + + // if none is given try the first Accept header + if($format === null) { + $header = $this->request->getHeader('Accept'); + $formats = explode(',', $header); + + if($header !== null && count($formats) > 0) { + $accept = strtolower(trim($formats[0])); + $format = str_replace('application/', '', $accept); + } else { + $format = 'json'; + } + } + + $response = $controller->buildResponse($response, $format); + } + + return $response; + } + } diff --git a/lib/private/appframework/http/request.php b/lib/private/appframework/http/request.php index 643fa685adc..8b68ca486ff 100644 --- a/lib/private/appframework/http/request.php +++ b/lib/private/appframework/http/request.php @@ -3,7 +3,9 @@ * ownCloud - Request * * @author Thomas Tanghus + * @author Bernhard Posselt * @copyright 2013 Thomas Tanghus (thomas@tanghus.net) + * @copyright 2014 Bernhard Posselt <dev@bernhard-posselt.com> * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE diff --git a/lib/private/appframework/middleware/middlewaredispatcher.php b/lib/private/appframework/middleware/middlewaredispatcher.php index 598743e523f..dcb63a8e552 100644 --- a/lib/private/appframework/middleware/middlewaredispatcher.php +++ b/lib/private/appframework/middleware/middlewaredispatcher.php @@ -4,7 +4,7 @@ * ownCloud - App Framework * * @author Bernhard Posselt - * @copyright 2012 Bernhard Posselt nukeawhale@gmail.com + * @copyright 2012 Bernhard Posselt <dev@bernhard-posselt.com> * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE diff --git a/lib/private/appframework/middleware/security/corsmiddleware.php b/lib/private/appframework/middleware/security/corsmiddleware.php new file mode 100644 index 00000000000..dca3996ea2e --- /dev/null +++ b/lib/private/appframework/middleware/security/corsmiddleware.php @@ -0,0 +1,75 @@ +<?php +/** + * ownCloud - App Framework + * + * This file is licensed under the Affero General Public License version 3 or + * later. See the COPYING file. + * + * @author Bernhard Posselt <dev@bernhard-posselt.com> + * @copyright Bernhard Posselt 2014 + */ + +namespace OC\AppFramework\Middleware\Security; + +use OC\AppFramework\Utility\ControllerMethodReflector; +use OCP\IRequest; +use OCP\AppFramework\Http\Response; +use OCP\AppFramework\Middleware; + +/** + * This middleware sets the correct CORS headers on a response if the + * controller has the @CORS annotation. This is needed for webapps that want + * to access an API and dont run on the same domain, see + * https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS + */ +class CORSMiddleware extends Middleware { + + private $request; + private $reflector; + + /** + * @param IRequest $request + * @param ControllerMethodReflector $reflector + */ + public function __construct(IRequest $request, + ControllerMethodReflector $reflector) { + $this->request = $request; + $this->reflector = $reflector; + } + + + /** + * This is being run after a successful controllermethod call and allows + * the manipulation of a Response object. The middleware is run in reverse order + * + * @param Controller $controller the controller that is being called + * @param string $methodName the name of the method that will be called on + * the controller + * @param Response $response the generated response from the controller + * @return Response a Response object + */ + public function afterController($controller, $methodName, Response $response){ + // only react if its a CORS request and if the request sends origin and + + if(isset($this->request->server['HTTP_ORIGIN']) && + $this->reflector->hasAnnotation('CORS')) { + + // allow credentials headers must not be true or CSRF is possible + // otherwise + foreach($response->getHeaders() as $header => $value ) { + if(strtolower($header) === 'access-control-allow-credentials' && + strtolower(trim($value)) === 'true') { + $msg = 'Access-Control-Allow-Credentials must not be '. + 'set to true in order to prevent CSRF'; + throw new SecurityException($msg); + } + } + + $origin = $this->request->server['HTTP_ORIGIN']; + $response->addHeader('Access-Control-Allow-Origin', $origin); + } + return $response; + } + + +} diff --git a/lib/private/appframework/middleware/security/securityexception.php b/lib/private/appframework/middleware/security/securityexception.php index e551675acdf..df37d5e3275 100644 --- a/lib/private/appframework/middleware/security/securityexception.php +++ b/lib/private/appframework/middleware/security/securityexception.php @@ -4,7 +4,7 @@ * ownCloud - App Framework * * @author Bernhard Posselt - * @copyright 2012 Bernhard Posselt nukeawhale@gmail.com + * @copyright 2012 Bernhard Posselt <dev@bernhard-posselt.com> * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE diff --git a/lib/private/appframework/middleware/security/securitymiddleware.php b/lib/private/appframework/middleware/security/securitymiddleware.php index 0f160d224ad..d7e398fe445 100644 --- a/lib/private/appframework/middleware/security/securitymiddleware.php +++ b/lib/private/appframework/middleware/security/securitymiddleware.php @@ -4,7 +4,7 @@ * ownCloud - App Framework * * @author Bernhard Posselt - * @copyright 2012 Bernhard Posselt nukeawhale@gmail.com + * @copyright 2012 Bernhard Posselt <dev@bernhard-posselt.com> * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE @@ -25,7 +25,7 @@ namespace OC\AppFramework\Middleware\Security; use OC\AppFramework\Http; -use OC\AppFramework\Utility\MethodAnnotationReader; +use OC\AppFramework\Utility\ControllerMethodReflector; use OCP\AppFramework\Http\RedirectResponse; use OCP\AppFramework\Middleware; use OCP\AppFramework\Http\Response; @@ -53,12 +53,20 @@ class SecurityMiddleware extends Middleware { private $request; /** + * @var OC\AppFramework\Utility\ControllerMethodReflector + */ + private $reflector; + + /** * @param IAppContainer $app * @param IRequest $request + * @param ControllerMethodReflector $reflector */ - public function __construct(IAppContainer $app, IRequest $request){ + public function __construct(IAppContainer $app, IRequest $request, + ControllerMethodReflector $reflector){ $this->app = $app; $this->request = $request; + $this->reflector = $reflector; } @@ -72,28 +80,25 @@ class SecurityMiddleware extends Middleware { */ public function beforeController($controller, $methodName){ - // get annotations from comments - $annotationReader = new MethodAnnotationReader($controller, $methodName); - // this will set the current navigation entry of the app, use this only // for normal HTML requests and not for AJAX requests $this->app->getServer()->getNavigationManager()->setActiveEntry($this->app->getAppName()); // security checks - $isPublicPage = $annotationReader->hasAnnotation('PublicPage'); + $isPublicPage = $this->reflector->hasAnnotation('PublicPage'); if(!$isPublicPage) { if(!$this->app->isLoggedIn()) { throw new SecurityException('Current user is not logged in', Http::STATUS_UNAUTHORIZED); } - if(!$annotationReader->hasAnnotation('NoAdminRequired')) { + if(!$this->reflector->hasAnnotation('NoAdminRequired')) { if(!$this->app->isAdminUser()) { throw new SecurityException('Logged in user must be an admin', Http::STATUS_FORBIDDEN); } } } - if(!$annotationReader->hasAnnotation('NoCSRFRequired')) { + if(!$this->reflector->hasAnnotation('NoCSRFRequired')) { if(!$this->request->passesCSRFCheck()) { throw new SecurityException('CSRF check failed', Http::STATUS_PRECONDITION_FAILED); } diff --git a/lib/private/appframework/utility/methodannotationreader.php b/lib/private/appframework/utility/controllermethodreflector.php index 42060a08529..c9cdadcca4a 100644 --- a/lib/private/appframework/utility/methodannotationreader.php +++ b/lib/private/appframework/utility/controllermethodreflector.php @@ -4,7 +4,7 @@ * ownCloud - App Framework * * @author Bernhard Posselt - * @copyright 2012 Bernhard Posselt nukeawhale@gmail.com + * @copyright 2014 Bernhard Posselt <dev@bernhard-posselt.com> * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE @@ -28,23 +28,63 @@ namespace OC\AppFramework\Utility; /** * Reads and parses annotations from doc comments */ -class MethodAnnotationReader { +class ControllerMethodReflector { private $annotations; + private $types; + private $parameters; + + public function __construct() { + $this->types = array(); + $this->parameters = array(); + $this->annotations = array(); + } + /** * @param object $object an object or classname - * @param string $method the method which we want to inspect for annotations + * @param string $method the method which we want to inspect */ - public function __construct($object, $method){ - $this->annotations = array(); - + public function reflect($object, $method){ $reflection = new \ReflectionMethod($object, $method); $docs = $reflection->getDocComment(); // extract everything prefixed by @ and first letter uppercase preg_match_all('/@([A-Z]\w+)/', $docs, $matches); $this->annotations = $matches[1]; + + // extract type parameter information + preg_match_all('/@param (?<type>\w+) \$(?<var>\w+)/', $docs, $matches); + $this->types = array_combine($matches['var'], $matches['type']); + + // get method parameters + foreach ($reflection->getParameters() as $param) { + $this->parameters[] = $param->name; + } + } + + + /** + * Inspects the PHPDoc parameters for types + * @param string $parameter the parameter whose type comments should be + * parsed + * @return string|null type in the type parameters (@param int $something) + * would return int or null if not existing + */ + public function getType($parameter) { + if(array_key_exists($parameter, $this->types)) { + return $this->types[$parameter]; + } else { + return null; + } + } + + + /** + * @return array the arguments of the method + */ + public function getParameters() { + return $this->parameters; } diff --git a/lib/private/appframework/utility/timefactory.php b/lib/private/appframework/utility/timefactory.php index 2c3dd6cf5e3..a9b07a356e3 100644 --- a/lib/private/appframework/utility/timefactory.php +++ b/lib/private/appframework/utility/timefactory.php @@ -4,7 +4,7 @@ * ownCloud - App Framework * * @author Bernhard Posselt - * @copyright 2012 Bernhard Posselt nukeawhale@gmail.com + * @copyright 2012 Bernhard Posselt <dev@bernhard-posselt.com> * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE diff --git a/lib/private/helper.php b/lib/private/helper.php index 64da1f6fb12..6bc054bce86 100644 --- a/lib/private/helper.php +++ b/lib/private/helper.php @@ -732,10 +732,22 @@ class OC_Helper { * @param string $parent * @return bool */ - public static function issubdirectory($sub, $parent) { - if (strpos(realpath($sub), realpath($parent)) === 0) { + public static function isSubDirectory($sub, $parent) { + $realpathSub = realpath($sub); + $realpathParent = realpath($parent); + + // realpath() may return false in case the directory does not exist + // since we can not be sure how different PHP versions may behave here + // we do an additional check whether realpath returned false + if($realpathSub === false || $realpathParent === false) { + return false; + } + + // Check whether $sub is a subdirectory of $parent + if (strpos($realpathSub, $realpathParent) === 0) { return true; } + return false; } diff --git a/lib/private/l10n.php b/lib/private/l10n.php index d6680d63445..c1596a64163 100644 --- a/lib/private/l10n.php +++ b/lib/private/l10n.php @@ -134,10 +134,10 @@ class OC_L10N implements \OCP\IL10N { $i18ndir = self::findI18nDir($app); // Localization is in /l10n, Texts are in $i18ndir // (Just no need to define date/time format etc. twice) - if((OC_Helper::issubdirectory($i18ndir.$lang.'.php', OC::$SERVERROOT.'/core/l10n/') - || OC_Helper::issubdirectory($i18ndir.$lang.'.php', OC::$SERVERROOT.'/lib/l10n/') - || OC_Helper::issubdirectory($i18ndir.$lang.'.php', OC::$SERVERROOT.'/settings') - || OC_Helper::issubdirectory($i18ndir.$lang.'.php', OC_App::getAppPath($app).'/l10n/') + if((OC_Helper::isSubDirectory($i18ndir.$lang.'.php', OC::$SERVERROOT.'/core/l10n/') + || OC_Helper::isSubDirectory($i18ndir.$lang.'.php', OC::$SERVERROOT.'/lib/l10n/') + || OC_Helper::isSubDirectory($i18ndir.$lang.'.php', OC::$SERVERROOT.'/settings') + || OC_Helper::isSubDirectory($i18ndir.$lang.'.php', OC_App::getAppPath($app).'/l10n/') ) && file_exists($i18ndir.$lang.'.php')) { // Include the file, save the data from $CONFIG @@ -162,7 +162,7 @@ class OC_L10N implements \OCP\IL10N { } } - if(file_exists(OC::$SERVERROOT.'/core/l10n/l10n-'.$lang.'.php') && OC_Helper::issubdirectory(OC::$SERVERROOT.'/core/l10n/l10n-'.$lang.'.php', OC::$SERVERROOT.'/core/l10n/')) { + if(file_exists(OC::$SERVERROOT.'/core/l10n/l10n-'.$lang.'.php') && OC_Helper::isSubDirectory(OC::$SERVERROOT.'/core/l10n/l10n-'.$lang.'.php', OC::$SERVERROOT.'/core/l10n/')) { // Include the file, save the data from $CONFIG include OC::$SERVERROOT.'/core/l10n/l10n-'.$lang.'.php'; if(isset($LOCALIZATIONS) && is_array($LOCALIZATIONS)) { diff --git a/lib/private/log.php b/lib/private/log.php index e0b9fe3c696..98465ec40ea 100644 --- a/lib/private/log.php +++ b/lib/private/log.php @@ -8,6 +8,8 @@ namespace OC; +use \OCP\ILogger; + /** * logging utilities * @@ -18,8 +20,24 @@ namespace OC; * MonoLog is an example implementing this interface. */ -class Log { - private $logClass; +class Log implements ILogger { + + private $logger; + + /** + * @param string $logger The logger that should be used + */ + public function __construct($logger=null) { + // FIXME: Add this for backwards compatibility, should be fixed at some point probably + if($logger === null) { + $this->logger = 'OC_Log_'.ucfirst(\OC_Config::getValue('log_type', 'owncloud')); + call_user_func(array($this->logger, 'init')); + } else { + $this->logger = $logger; + } + + } + /** * System is unusable. @@ -112,10 +130,6 @@ class Log { $this->log(\OC_Log::DEBUG, $message, $context); } - public function __construct() { - $this->logClass = 'OC_Log_'.ucfirst(\OC_Config::getValue('log_type', 'owncloud')); - call_user_func(array($this->logClass, 'init')); - } /** * Logs with an arbitrary level. @@ -130,7 +144,16 @@ class Log { } else { $app = 'no app in context'; } - $logClass=$this->logClass; - $logClass::write($app, $message, $level); + // interpolate $message as defined in PSR-3 + $replace = array(); + foreach ($context as $key => $val) { + $replace['{' . $key . '}'] = $val; + } + + // interpolate replacement values into the message and return + $message = strtr($message, $replace); + + $logger = $this->logger; + $logger::write($app, $message, $level); } } diff --git a/lib/private/response.php b/lib/private/response.php index 1aa5e629b8b..4905c0a787b 100644 --- a/lib/private/response.php +++ b/lib/private/response.php @@ -186,4 +186,36 @@ class OC_Response { self::setStatus(self::STATUS_NOT_FOUND); } } + + /* + * This function adds some security related headers to all requests served via base.php + * The implementation of this function has to happen here to ensure that all third-party + * components (e.g. SabreDAV) also benefit from this headers. + */ + public static function addSecurityHeaders() { + header('X-XSS-Protection: 1; mode=block'); // Enforce browser based XSS filters + header('X-Content-Type-Options: nosniff'); // Disable sniffing the content type for IE + + // iFrame Restriction Policy + $xFramePolicy = OC_Config::getValue('xframe_restriction', true); + if ($xFramePolicy) { + header('X-Frame-Options: Sameorigin'); // Disallow iFraming from other domains + } + + // Content Security Policy + // If you change the standard policy, please also change it in config.sample.php + $policy = OC_Config::getValue('custom_csp_policy', + 'default-src \'self\'; ' + . 'script-src \'self\' \'unsafe-eval\'; ' + . 'style-src \'self\' \'unsafe-inline\'; ' + . 'frame-src *; ' + . 'img-src *; ' + . 'font-src \'self\' data:; ' + . 'media-src *'); + header('Content-Security-Policy:' . $policy); + + // https://developers.google.com/webmasters/control-crawl-index/docs/robots_meta_tag + header('X-Robots-Tag: none'); + } + } diff --git a/lib/private/server.php b/lib/private/server.php index 4c29092cf44..fd8c2c38ad0 100644 --- a/lib/private/server.php +++ b/lib/private/server.php @@ -3,6 +3,7 @@ namespace OC; use OC\AppFramework\Http\Request; +use OC\AppFramework\Db\Db; use OC\AppFramework\Utility\SimpleContainer; use OC\Cache\UserCache; use OC\DB\ConnectionWrapper; @@ -30,9 +31,9 @@ class Server extends SimpleContainer implements IServerContainer { } if (\OC::$session->exists('requesttoken')) { - $requesttoken = \OC::$session->get('requesttoken'); + $requestToken = \OC::$session->get('requesttoken'); } else { - $requesttoken = false; + $requestToken = false; } if (defined('PHPUNIT_RUN') && PHPUNIT_RUN @@ -54,7 +55,7 @@ class Server extends SimpleContainer implements IServerContainer { ? $_SERVER['REQUEST_METHOD'] : null, 'urlParams' => $urlParams, - 'requesttoken' => $requesttoken, + 'requesttoken' => $requestToken, ), $stream ); }); @@ -158,6 +159,14 @@ class Server extends SimpleContainer implements IServerContainer { $this->registerService('AvatarManager', function($c) { return new AvatarManager(); }); + $this->registerService('Logger', function($c) { + /** @var $c SimpleContainer */ + $logClass = $c->query('AllConfig')->getSystemValue('log_type', 'owncloud'); + $logger = 'OC_Log_' . ucfirst($logClass); + call_user_func(array($logger, 'init')); + + return new Log($logger); + }); $this->registerService('JobList', function ($c) { /** * @var Server $c @@ -177,6 +186,9 @@ class Server extends SimpleContainer implements IServerContainer { } return $router; }); + $this['Db'] = $this->share(function($c){ + return new Db(); + }); } /** @@ -325,14 +337,14 @@ class Server extends SimpleContainer implements IServerContainer { } /** - * @return \OC\URLGenerator + * @return \OCP\IURLGenerator */ function getURLGenerator() { return $this->query('URLGenerator'); } /** - * @return \OC\Helper + * @return \OCP\IHelper */ function getHelper() { return $this->query('AppHelper'); @@ -393,6 +405,15 @@ class Server extends SimpleContainer implements IServerContainer { } /** + * Returns a logger instance + * + * @return \OCP\ILogger + */ + function getLogger() { + return $this->query('Logger'); + } + + /** * Returns a router for generating and matching urls * * @return \OCP\Route\IRouter @@ -400,4 +421,13 @@ class Server extends SimpleContainer implements IServerContainer { function getRouter(){ return $this->query('Router'); } + + + /** + * Returns an instance of the db facade + * @return \OCP\IDb + */ + function getDb() { + return $this->query('Db'); + } } diff --git a/lib/public/appframework/apicontroller.php b/lib/public/appframework/apicontroller.php new file mode 100644 index 00000000000..5272f3ed529 --- /dev/null +++ b/lib/public/appframework/apicontroller.php @@ -0,0 +1,93 @@ +<?php +/** + * ownCloud - App Framework + * + * @author Bernhard Posselt + * @copyright 2012 Bernhard Posselt nukeawhale@gmail.com + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + * License as published by the Free Software Foundation; either + * version 3 of the License, or any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU AFFERO GENERAL PUBLIC LICENSE for more details. + * + * You should have received a copy of the GNU Affero General Public + * License along with this library. If not, see <http://www.gnu.org/licenses/>. + * + */ + +/** + * Public interface of ownCloud for apps to use. + * AppFramework\Controller class + */ + +namespace OCP\AppFramework; + +use OCP\AppFramework\Http\Response; +use OCP\IRequest; + + +/** + * Base class to inherit your controllers from that are used for RESTful APIs + */ +abstract class ApiController extends Controller { + + private $corsMethods; + private $corsAllowedHeaders; + private $corsMaxAge; + + /** + * constructor of the controller + * @param string $appName the name of the app + * @param IRequest $request an instance of the request + * @param string $corsMethods: comma seperated string of HTTP verbs which + * should be allowed for websites or webapps when calling your API, defaults to + * 'PUT, POST, GET, DELETE, PATCH' + * @param string $corsAllowedHeaders: comma seperated string of HTTP headers + * which should be allowed for websites or webapps when calling your API, + * defaults to 'Authorization, Content-Type, Accept' + * @param int $corsMaxAge number in seconds how long a preflighted OPTIONS + * request should be cached, defaults to 1728000 seconds + */ + public function __construct($appName, + IRequest $request, + $corsMethods='PUT, POST, GET, DELETE, PATCH', + $corsAllowedHeaders='Authorization, Content-Type, Accept', + $corsMaxAge=1728000){ + parent::__construct($appName, $request); + $this->corsMethods = $corsMethods; + $this->corsAllowedHeaders = $corsAllowedHeaders; + $this->corsMaxAge = $corsMaxAge; + } + + + /** + * This method implements a preflighted cors response for you that you can + * link to for the options request + * + * @NoAdminRequired + * @NoCSRFRequired + * @PublicPage + */ + public function preflightedCors() { + if(isset($this->request->server['HTTP_ORIGIN'])) { + $origin = $this->request->server['HTTP_ORIGIN']; + } else { + $origin = '*'; + } + + $response = new Response(); + $response->addHeader('Access-Control-Allow-Origin', $origin); + $response->addHeader('Access-Control-Allow-Methods', $this->corsMethods); + $response->addHeader('Access-Control-Max-Age', $this->corsMaxAge); + $response->addHeader('Access-Control-Allow-Headers', $this->corsAllowedHeaders); + $response->addHeader('Access-Control-Allow-Credentials', 'false'); + return $response; + } + + +} diff --git a/lib/public/appframework/controller.php b/lib/public/appframework/controller.php index 758f0a80083..50b5ed3a80d 100644 --- a/lib/public/appframework/controller.php +++ b/lib/public/appframework/controller.php @@ -3,7 +3,7 @@ * ownCloud - App Framework * * @author Bernhard Posselt - * @copyright 2012 Bernhard Posselt nukeawhale@gmail.com + * @copyright 2012, 2014 Bernhard Posselt <dev@bernhard-posselt.com> * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE @@ -28,7 +28,8 @@ namespace OCP\AppFramework; use OCP\AppFramework\Http\TemplateResponse; -use OCP\AppFramework\IAppContainer; +use OCP\AppFramework\Http\JSONResponse; +use OCP\AppFramework\Http\IResponseSerializer; use OCP\IRequest; @@ -49,19 +50,86 @@ abstract class Controller { */ protected $request; + private $serializer; + private $responders; + /** * constructor of the controller * @param string $appName the name of the app * @param IRequest $request an instance of the request + * @param string $corsMethods: comma seperated string of HTTP verbs which + * should be allowed for websites or webapps when calling your API, defaults to + * 'PUT, POST, GET, DELETE, PATCH' + * @param string $corsAllowedHeaders: comma seperated string of HTTP headers + * which should be allowed for websites or webapps when calling your API, + * defaults to 'Authorization, Content-Type, Accept' + * @param int $corsMaxAge number in seconds how long a preflighted OPTIONS + * request should be cached, defaults to 1728000 seconds */ - public function __construct($appName, IRequest $request){ + public function __construct($appName, + IRequest $request){ $this->appName = $appName; $this->request = $request; + + // default responders + $this->responders = array( + 'json' => function ($response) { + return new JSONResponse($response); + } + ); + } + + + /** + * Registers a serializer that is executed before a formatter is being + * called, useful for turning any data into PHP arrays that can be used + * by a JSONResponse for instance + * @param IResponseSerializer $serializer + */ + protected function registerSerializer(IResponseSerializer $serializer) { + $this->serializer = $serializer; + } + + + /** + * Registers a formatter for a type + * @param string $format + * @param \Closure $responder + */ + protected function registerResponder($format, \Closure $responder) { + $this->responders[$format] = $responder; + } + + + /** + * Serializes and formats a response + * @param mixed $response the value that was returned from a controller and + * is not a Response instance + * @param string $format the format for which a formatter has been registered + * @throws \DomainException if format does not match a registered formatter + * @return Response + */ + public function buildResponse($response, $format='json') { + if(array_key_exists($format, $this->responders)) { + + if ($this->serializer) { + $response = $this->serializer->serialize($response); + } + + $responder = $this->responders[$format]; + + return $responder($response); + + } else { + throw new \DomainException('No responder registered for format ' . + $format . '!'); + } } /** * Lets you access post and get parameters by the index + * @deprecated write your parameters as method arguments instead * @param string $key the key which you want to access in the URL Parameter * placeholder, $_POST or $_GET array. * The priority how they're returned is the following: @@ -79,6 +147,7 @@ abstract class Controller { /** * Returns all params that were received, be it from the request * (as GET or POST) or throuh the URL by the route + * @deprecated use $this->request instead * @return array the array with all parameters */ public function getParams() { @@ -88,6 +157,7 @@ abstract class Controller { /** * Returns the method of the request + * @deprecated use $this->request instead * @return string the method of the request (POST, GET, etc) */ public function method() { @@ -97,6 +167,7 @@ abstract class Controller { /** * Shortcut for accessing an uploaded file through the $_FILES array + * @deprecated use $this->request instead * @param string $key the key that will be taken from the $_FILES array * @return array the file in the $_FILES element */ @@ -107,6 +178,7 @@ abstract class Controller { /** * Shortcut for getting env variables + * @deprecated use $this->request instead * @param string $key the key that will be taken from the $_ENV array * @return array the value in the $_ENV element */ @@ -117,6 +189,7 @@ abstract class Controller { /** * Shortcut for getting cookie variables + * @deprecated use $this->request instead * @param string $key the key that will be taken from the $_COOKIE array * @return array the value in the $_COOKIE element */ @@ -127,6 +200,7 @@ abstract class Controller { /** * Shortcut for rendering a template + * @deprecated return a template response instead * @param string $templateName the name of the template * @param array $params the template parameters in key => value structure * @param string $renderAs user renders a full page, blank only your template diff --git a/tests/lib/appframework/utility/MethodAnnotationReaderTest.php b/lib/public/appframework/db/doesnotexistexception.php index c68812aa5c7..5861e74f6c8 100644 --- a/tests/lib/appframework/utility/MethodAnnotationReaderTest.php +++ b/lib/public/appframework/db/doesnotexistexception.php @@ -4,7 +4,7 @@ * ownCloud - App Framework * * @author Bernhard Posselt - * @copyright 2012 Bernhard Posselt nukeawhale@gmail.com + * @copyright 2012 Bernhard Posselt dev@bernhard-posselt.com * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE @@ -22,34 +22,21 @@ */ -namespace OC\AppFramework\Utility; +namespace OCP\AppFramework\Db; -class MethodAnnotationReaderTest extends \PHPUnit_Framework_TestCase { - - - /** - * @Annotation - */ - public function testReadAnnotation(){ - $reader = new MethodAnnotationReader('\OC\AppFramework\Utility\MethodAnnotationReaderTest', - 'testReadAnnotation'); - - $this->assertTrue($reader->hasAnnotation('Annotation')); - } - +/** + * This is returned or should be returned when a find request does not find an + * entry in the database + */ +class DoesNotExistException extends \Exception { /** - * @Annotation - * @param test + * Constructor + * @param string $msg the error message */ - public function testReadAnnotationNoLowercase(){ - $reader = new MethodAnnotationReader('\OC\AppFramework\Utility\MethodAnnotationReaderTest', - 'testReadAnnotationNoLowercase'); - - $this->assertTrue($reader->hasAnnotation('Annotation')); - $this->assertFalse($reader->hasAnnotation('param')); + public function __construct($msg){ + parent::__construct($msg); } - -} +}
\ No newline at end of file diff --git a/lib/public/appframework/db/entity.php b/lib/public/appframework/db/entity.php new file mode 100644 index 00000000000..8ab42bd9153 --- /dev/null +++ b/lib/public/appframework/db/entity.php @@ -0,0 +1,236 @@ +<?php + +/** +* ownCloud - App Framework +* +* @author Bernhard Posselt +* @copyright 2012 Bernhard Posselt dev@bernhard-posselt.com +* +* This library is free software; you can redistribute it and/or +* modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE +* License as published by the Free Software Foundation; either +* version 3 of the License, or any later version. +* +* This library is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU AFFERO GENERAL PUBLIC LICENSE for more details. +* +* You should have received a copy of the GNU Affero General Public +* License along with this library. If not, see <http://www.gnu.org/licenses/>. +* +*/ + +namespace OCP\AppFramework\Db; + + +/** + * @method integer getId() + * @method void setId(integer $id) + */ +abstract class Entity { + + public $id; + + private $_updatedFields = array(); + private $_fieldTypes = array('id' => 'integer'); + + + /** + * Simple alternative constructor for building entities from a request + * @param array $params the array which was obtained via $this->params('key') + * in the controller + * @return Entity + */ + public static function fromParams(array $params) { + $instance = new static(); + + foreach($params as $key => $value) { + $method = 'set' . ucfirst($key); + $instance->$method($value); + } + + return $instance; + } + + + /** + * Maps the keys of the row array to the attributes + * @param array $row the row to map onto the entity + */ + public static function fromRow(array $row){ + $instance = new static(); + + foreach($row as $key => $value){ + $prop = ucfirst($instance->columnToProperty($key)); + $setter = 'set' . $prop; + $instance->$setter($value); + } + + $instance->resetUpdatedFields(); + + return $instance; + } + + + /** + * @return an array with attribute and type + */ + public function getFieldTypes() { + return $this->_fieldTypes; + } + + + /** + * Marks the entity as clean needed for setting the id after the insertion + */ + public function resetUpdatedFields(){ + $this->_updatedFields = array(); + } + + + protected function setter($name, $args) { + // setters should only work for existing attributes + if(property_exists($this, $name)){ + if($this->$name === $args[0]) { + return; + } + $this->markFieldUpdated($name); + + // if type definition exists, cast to correct type + if($args[0] !== null && array_key_exists($name, $this->_fieldTypes)) { + settype($args[0], $this->_fieldTypes[$name]); + } + $this->$name = $args[0]; + + } else { + throw new \BadFunctionCallException($name . + ' is not a valid attribute'); + } + } + + + protected function getter($name) { + // getters should only work for existing attributes + if(property_exists($this, $name)){ + return $this->$name; + } else { + throw new \BadFunctionCallException($name . + ' is not a valid attribute'); + } + } + + + /** + * Each time a setter is called, push the part after set + * into an array: for instance setId will save Id in the + * updated fields array so it can be easily used to create the + * getter method + */ + public function __call($methodName, $args){ + $attr = lcfirst( substr($methodName, 3) ); + + if(strpos($methodName, 'set') === 0){ + $this->setter($attr, $args); + } elseif(strpos($methodName, 'get') === 0) { + return $this->getter($attr); + } else { + throw new \BadFunctionCallException($methodName . + ' does not exist'); + } + + } + + + /** + * Mark am attribute as updated + * @param string $attribute the name of the attribute + */ + protected function markFieldUpdated($attribute){ + $this->_updatedFields[$attribute] = true; + } + + + /** + * Transform a database columnname to a property + * @param string $columnName the name of the column + * @return string the property name + */ + public function columnToProperty($columnName){ + $parts = explode('_', $columnName); + $property = null; + + foreach($parts as $part){ + if($property === null){ + $property = $part; + } else { + $property .= ucfirst($part); + } + } + + return $property; + } + + + /** + * Transform a property to a database column name + * @param string $property the name of the property + * @return string the column name + */ + public function propertyToColumn($property){ + $parts = preg_split('/(?=[A-Z])/', $property); + $column = null; + + foreach($parts as $part){ + if($column === null){ + $column = $part; + } else { + $column .= '_' . lcfirst($part); + } + } + + return $column; + } + + + /** + * @return array array of updated fields for update query + */ + public function getUpdatedFields(){ + return $this->_updatedFields; + } + + + /** + * Adds type information for a field so that its automatically casted to + * that value once its being returned from the database + * @param string $fieldName the name of the attribute + * @param string $type the type which will be used to call settype() + */ + protected function addType($fieldName, $type){ + $this->_fieldTypes[$fieldName] = $type; + } + + + /** + * Slugify the value of a given attribute + * Warning: This doesn't result in a unique value + * @param string $attributeName the name of the attribute, which value should be slugified + * @return string slugified value + */ + public function slugify($attributeName){ + // toSlug should only work for existing attributes + if(property_exists($this, $attributeName)){ + $value = $this->$attributeName; + // replace everything except alphanumeric with a single '-' + $value = preg_replace('/[^A-Za-z0-9]+/', '-', $value); + $value = strtolower($value); + // trim '-' + return trim($value, '-'); + } else { + throw new \BadFunctionCallException($attributeName . + ' is not a valid attribute'); + } + } + +} diff --git a/lib/public/appframework/db/mapper.php b/lib/public/appframework/db/mapper.php new file mode 100644 index 00000000000..21ccb686221 --- /dev/null +++ b/lib/public/appframework/db/mapper.php @@ -0,0 +1,291 @@ +<?php + +/** + * ownCloud - App Framework + * + * @author Bernhard Posselt + * @author Morris Jobke + * @copyright 2012 Bernhard Posselt dev@bernhard-posselt.com + * @copyright 2013 Morris Jobke morris.jobke@gmail.com + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + * License as published by the Free Software Foundation; either + * version 3 of the License, or any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU AFFERO GENERAL PUBLIC LICENSE for more details. + * + * You should have received a copy of the GNU Affero General Public + * License along with this library. If not, see <http://www.gnu.org/licenses/>. + * + */ + + +namespace OCP\AppFramework\Db; + +use \OCP\IDb; + + +/** + * Simple parent class for inheriting your data access layer from. This class + * may be subject to change in the future + */ +abstract class Mapper { + + protected $tableName; + protected $entityClass; + protected $db; + + /** + * @param IDb $db Instance of the Db abstraction layer + * @param string $tableName the name of the table. set this to allow entity + * @param string $entityClass the name of the entity that the sql should be + * mapped to queries without using sql + */ + public function __construct(IDb $db, $tableName, $entityClass=null){ + $this->db = $db; + $this->tableName = '*PREFIX*' . $tableName; + + // if not given set the entity name to the class without the mapper part + // cache it here for later use since reflection is slow + if($entityClass === null) { + $this->entityClass = str_replace('Mapper', '', get_class($this)); + } else { + $this->entityClass = $entityClass; + } + } + + + /** + * @return string the table name + */ + public function getTableName(){ + return $this->tableName; + } + + + /** + * Deletes an entity from the table + * @param Entity $entity the entity that should be deleted + */ + public function delete(Entity $entity){ + $sql = 'DELETE FROM `' . $this->tableName . '` WHERE `id` = ?'; + $this->execute($sql, array($entity->getId())); + } + + + /** + * Creates a new entry in the db from an entity + * @param Entity $entity the entity that should be created + * @return Entity the saved entity with the set id + */ + public function insert(Entity $entity){ + // get updated fields to save, fields have to be set using a setter to + // be saved + $properties = $entity->getUpdatedFields(); + $values = ''; + $columns = ''; + $params = array(); + + // build the fields + $i = 0; + foreach($properties as $property => $updated) { + $column = $entity->propertyToColumn($property); + $getter = 'get' . ucfirst($property); + + $columns .= '`' . $column . '`'; + $values .= '?'; + + // only append colon if there are more entries + if($i < count($properties)-1){ + $columns .= ','; + $values .= ','; + } + + array_push($params, $entity->$getter()); + $i++; + + } + + $sql = 'INSERT INTO `' . $this->tableName . '`(' . + $columns . ') VALUES(' . $values . ')'; + + $this->execute($sql, $params); + + $entity->setId((int) $this->db->getInsertId($this->tableName)); + return $entity; + } + + + + /** + * Updates an entry in the db from an entity + * @throws \InvalidArgumentException if entity has no id + * @param Entity $entity the entity that should be created + */ + public function update(Entity $entity){ + // if entity wasn't changed it makes no sense to run a db query + $properties = $entity->getUpdatedFields(); + if(count($properties) === 0) { + return $entity; + } + + // entity needs an id + $id = $entity->getId(); + if($id === null){ + throw new \InvalidArgumentException( + 'Entity which should be updated has no id'); + } + + // get updated fields to save, fields have to be set using a setter to + // be saved + // dont update the id field + unset($properties['id']); + + $columns = ''; + $params = array(); + + // build the fields + $i = 0; + foreach($properties as $property => $updated) { + + $column = $entity->propertyToColumn($property); + $getter = 'get' . ucfirst($property); + + $columns .= '`' . $column . '` = ?'; + + // only append colon if there are more entries + if($i < count($properties)-1){ + $columns .= ','; + } + + array_push($params, $entity->$getter()); + $i++; + } + + $sql = 'UPDATE `' . $this->tableName . '` SET ' . + $columns . ' WHERE `id` = ?'; + array_push($params, $id); + + $this->execute($sql, $params); + } + + + /** + * Runs an sql query + * @param string $sql the prepare string + * @param array $params the params which should replace the ? in the sql query + * @param int $limit the maximum number of rows + * @param int $offset from which row we want to start + * @return \PDOStatement the database query result + */ + protected function execute($sql, array $params=array(), $limit=null, $offset=null){ + $query = $this->db->prepareQuery($sql, $limit, $offset); + + $index = 1; // bindParam is 1 indexed + foreach($params as $param) { + + switch (gettype($param)) { + case 'integer': + $pdoConstant = \PDO::PARAM_INT; + break; + + case 'boolean': + $pdoConstant = \PDO::PARAM_BOOL; + break; + + default: + $pdoConstant = \PDO::PARAM_STR; + break; + } + + $query->bindValue($index, $param, $pdoConstant); + + $index++; + } + + return $query->execute(); + } + + + /** + * Returns an db result and throws exceptions when there are more or less + * results + * @see findEntity + * @param string $sql the sql query + * @param array $params the parameters of the sql query + * @param int $limit the maximum number of rows + * @param int $offset from which row we want to start + * @throws DoesNotExistException if the item does not exist + * @throws MultipleObjectsReturnedException if more than one item exist + * @return array the result as row + */ + protected function findOneQuery($sql, array $params=array(), $limit=null, $offset=null){ + $result = $this->execute($sql, $params, $limit, $offset); + $row = $result->fetchRow(); + + if($row === false || $row === null){ + throw new DoesNotExistException('No matching entry found'); + } + $row2 = $result->fetchRow(); + //MDB2 returns null, PDO and doctrine false when no row is available + if( ! ($row2 === false || $row2 === null )) { + throw new MultipleObjectsReturnedException('More than one result'); + } else { + return $row; + } + } + + + /** + * Creates an entity from a row. Automatically determines the entity class + * from the current mapper name (MyEntityMapper -> MyEntity) + * @param array $row the row which should be converted to an entity + * @return Entity the entity + */ + protected function mapRowToEntity($row) { + return call_user_func($this->entityClass .'::fromRow', $row); + } + + + /** + * Runs a sql query and returns an array of entities + * @param string $sql the prepare string + * @param array $params the params which should replace the ? in the sql query + * @param int $limit the maximum number of rows + * @param int $offset from which row we want to start + * @return array all fetched entities + */ + protected function findEntities($sql, array $params=array(), $limit=null, $offset=null) { + $result = $this->execute($sql, $params, $limit, $offset); + + $entities = array(); + + while($row = $result->fetchRow()){ + $entities[] = $this->mapRowToEntity($row); + } + + return $entities; + } + + + /** + * Returns an db result and throws exceptions when there are more or less + * results + * @param string $sql the sql query + * @param array $params the parameters of the sql query + * @param int $limit the maximum number of rows + * @param int $offset from which row we want to start + * @throws DoesNotExistException if the item does not exist + * @throws MultipleObjectsReturnedException if more than one item exist + * @return Entity the entity + */ + protected function findEntity($sql, array $params=array(), $limit=null, $offset=null){ + return $this->mapRowToEntity($this->findOneQuery($sql, $params, $limit, $offset)); + } + + +} diff --git a/lib/public/appframework/db/multipleobjectsreturnedexception.php b/lib/public/appframework/db/multipleobjectsreturnedexception.php new file mode 100644 index 00000000000..51d8d6bc7e1 --- /dev/null +++ b/lib/public/appframework/db/multipleobjectsreturnedexception.php @@ -0,0 +1,42 @@ +<?php + +/** + * ownCloud - App Framework + * + * @author Bernhard Posselt + * @copyright 2012 Bernhard Posselt dev@bernhard-posselt.com + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + * License as published by the Free Software Foundation; either + * version 3 of the License, or any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU AFFERO GENERAL PUBLIC LICENSE for more details. + * + * You should have received a copy of the GNU Affero General Public + * License along with this library. If not, see <http://www.gnu.org/licenses/>. + * + */ + + +namespace OCP\AppFramework\Db; + + +/** + * This is returned or should be returned when a find request finds more than one + * row + */ +class MultipleObjectsReturnedException extends \Exception { + + /** + * Constructor + * @param string $msg the error message + */ + public function __construct($msg){ + parent::__construct($msg); + } + +}
\ No newline at end of file diff --git a/lib/public/appframework/http.php b/lib/public/appframework/http.php index 60f314202cc..c6e2ff8846f 100644 --- a/lib/public/appframework/http.php +++ b/lib/public/appframework/http.php @@ -3,7 +3,7 @@ * ownCloud - App Framework * * @author Bernhard Posselt, Thomas Tanghus, Bart Visscher - * @copyright 2012 Bernhard Posselt nukeawhale@gmail.com + * @copyright 2012 Bernhard Posselt <dev@bernhard-posselt.com> * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE diff --git a/lib/public/appframework/http/downloadresponse.php b/lib/public/appframework/http/downloadresponse.php index d3c2818e828..6b61490341e 100644 --- a/lib/public/appframework/http/downloadresponse.php +++ b/lib/public/appframework/http/downloadresponse.php @@ -4,7 +4,7 @@ * ownCloud - App Framework * * @author Bernhard Posselt - * @copyright 2012 Bernhard Posselt nukeawhale@gmail.com + * @copyright 2012 Bernhard Posselt <dev@bernhard-posselt.com> * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE diff --git a/lib/public/appframework/http/iresponseserializer.php b/lib/public/appframework/http/iresponseserializer.php new file mode 100644 index 00000000000..c16e106df34 --- /dev/null +++ b/lib/public/appframework/http/iresponseserializer.php @@ -0,0 +1,27 @@ +<?php +/** + * ownCloud - App Framework + * + * @author Bernhard Posselt + * @copyright 2012 Bernhard Posselt <dev@bernhard-posselt.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + * License as published by the Free Software Foundation; either + * version 3 of the License, or any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU AFFERO GENERAL PUBLIC LICENSE for more details. + * + * You should have received a copy of the GNU Affero General Public + * License along with this library. If not, see <http://www.gnu.org/licenses/>. + * + */ + +namespace OCP\AppFramework\Http; + +interface IResponseSerializer { + function serialize($response); +}
\ No newline at end of file diff --git a/lib/public/appframework/http/jsonresponse.php b/lib/public/appframework/http/jsonresponse.php index 6d029b7464a..c6360e0a0f5 100644 --- a/lib/public/appframework/http/jsonresponse.php +++ b/lib/public/appframework/http/jsonresponse.php @@ -3,7 +3,7 @@ * ownCloud - App Framework * * @author Bernhard Posselt - * @copyright 2012 Bernhard Posselt nukeawhale@gmail.com + * @copyright 2012 Bernhard Posselt <dev@bernhard-posselt.com> * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE diff --git a/lib/public/appframework/http/redirectresponse.php b/lib/public/appframework/http/redirectresponse.php index 416e1533635..a1b482c6b3b 100644 --- a/lib/public/appframework/http/redirectresponse.php +++ b/lib/public/appframework/http/redirectresponse.php @@ -4,7 +4,7 @@ * ownCloud - App Framework * * @author Bernhard Posselt - * @copyright 2012 Bernhard Posselt nukeawhale@gmail.com + * @copyright 2012 Bernhard Posselt <dev@bernhard-posselt.com> * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE diff --git a/lib/public/appframework/http/response.php b/lib/public/appframework/http/response.php index 45402d9b3b3..20e936bb860 100644 --- a/lib/public/appframework/http/response.php +++ b/lib/public/appframework/http/response.php @@ -3,7 +3,7 @@ * ownCloud - App Framework * * @author Bernhard Posselt, Thomas Tanghus, Bart Visscher - * @copyright 2012 Bernhard Posselt nukeawhale@gmail.com + * @copyright 2012 Bernhard Posselt <dev@bernhard-posselt.com> * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE @@ -92,6 +92,10 @@ class Response { * @return Response Reference to this object */ public function addHeader($name, $value) { + $name = trim($name); // always remove leading and trailing whitespace + // to be able to reliably check for security + // headers + if(is_null($value)) { unset($this->headers[$name]); } else { diff --git a/lib/public/appframework/http/templateresponse.php b/lib/public/appframework/http/templateresponse.php index f5baf788ada..02589f4e2a4 100644 --- a/lib/public/appframework/http/templateresponse.php +++ b/lib/public/appframework/http/templateresponse.php @@ -3,7 +3,7 @@ * ownCloud - App Framework * * @author Bernhard Posselt - * @copyright 2012 Bernhard Posselt nukeawhale@gmail.com + * @copyright 2012 Bernhard Posselt <dev@bernhard-posselt.com> * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE @@ -61,12 +61,16 @@ class TemplateResponse extends Response { * constructor of TemplateResponse * @param string $appName the name of the app to load the template from * @param string $templateName the name of the template + * @param array $params an array of parameters which should be passed to the + * template + * @param string $renderAs how the page should be rendered, defaults to user */ - public function __construct($appName, $templateName) { + public function __construct($appName, $templateName, array $params=array(), + $renderAs='user') { $this->templateName = $templateName; $this->appName = $appName; - $this->params = array(); - $this->renderAs = 'user'; + $this->params = $params; + $this->renderAs = $renderAs; } diff --git a/lib/public/appframework/iapi.php b/lib/public/appframework/iapi.php index c4aeea2d4e5..9af251be850 100644 --- a/lib/public/appframework/iapi.php +++ b/lib/public/appframework/iapi.php @@ -3,7 +3,7 @@ * ownCloud - App Framework * * @author Bernhard Posselt - * @copyright 2012 Bernhard Posselt nukeawhale@gmail.com + * @copyright 2012 Bernhard Posselt <dev@bernhard-posselt.com> * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE diff --git a/lib/public/appframework/middleware.php b/lib/public/appframework/middleware.php index 24f31939935..2e1111e9d5d 100644 --- a/lib/public/appframework/middleware.php +++ b/lib/public/appframework/middleware.php @@ -3,7 +3,7 @@ * ownCloud - App Framework * * @author Bernhard Posselt - * @copyright 2012 Bernhard Posselt nukeawhale@gmail.com + * @copyright 2012 Bernhard Posselt <dev@bernhard-posselt.com> * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE diff --git a/lib/public/idb.php b/lib/public/idb.php new file mode 100644 index 00000000000..82a8a681500 --- /dev/null +++ b/lib/public/idb.php @@ -0,0 +1,51 @@ +<?php + +/** + * ownCloud - App Framework + * + * @author Bernhard Posselt + * @copyright 2012 Bernhard Posselt dev@bernhard-posselt.com + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + * License as published by the Free Software Foundation; either + * version 3 of the License, or any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU AFFERO GENERAL PUBLIC LICENSE for more details. + * + * You should have received a copy of the GNU Affero General Public + * License along with this library. If not, see <http://www.gnu.org/licenses/>. + * + */ + +namespace OCP; + + +/** + * Small Facade for being able to inject the database connection for tests + */ +interface IDb { + + + /** + * Used to abstract the owncloud database access away + * @param string $sql the sql query with ? placeholder for params + * @param int $limit the maximum number of rows + * @param int $offset from which row we want to start + * @return \OC_DB_StatementWrapper prepared SQL query + */ + public function prepareQuery($sql, $limit=null, $offset=null); + + + /** + * Used to get the id of the just inserted element + * @param string $tableName the name of the table where we inserted the item + * @return int the id of the inserted element + */ + public function getInsertId($tableName); + + +} diff --git a/lib/public/ilogger.php b/lib/public/ilogger.php new file mode 100644 index 00000000000..ad0fcd05a1d --- /dev/null +++ b/lib/public/ilogger.php @@ -0,0 +1,101 @@ +<?php +/** + * Copyright (c) 2014 Bernhard Posselt <dev@bernhard-posselt.com> + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +namespace OCP; + +/** + * Interface ILogger + * @package OCP + * + * This logger interface follows the design guidelines of PSR-3 + * https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-3-logger-interface.md#3-psrlogloggerinterface + */ +interface ILogger { + /** + * System is unusable. + * + * @param string $message + * @param array $context + * @return null + */ + function emergency($message, array $context = array()); + + /** + * Action must be taken immediately. + * + * @param string $message + * @param array $context + * @return null + */ + function alert($message, array $context = array()); + + /** + * Critical conditions. + * + * @param string $message + * @param array $context + * @return null + */ + function critical($message, array $context = array()); + + /** + * Runtime errors that do not require immediate action but should typically + * be logged and monitored. + * + * @param string $message + * @param array $context + * @return null + */ + function error($message, array $context = array()); + + /** + * Exceptional occurrences that are not errors. + * + * @param string $message + * @param array $context + * @return null + */ + function warning($message, array $context = array()); + + /** + * Normal but significant events. + * + * @param string $message + * @param array $context + * @return null + */ + function notice($message, array $context = array()); + + /** + * Interesting events. + * + * @param string $message + * @param array $context + * @return null + */ + function info($message, array $context = array()); + + /** + * Detailed debug information. + * + * @param string $message + * @param array $context + * @return null + */ + function debug($message, array $context = array()); + + /** + * Logs with an arbitrary level. + * + * @param mixed $level + * @param string $message + * @param array $context + * @return mixed + */ + function log($level, $message, array $context = array()); +} diff --git a/lib/public/iservercontainer.php b/lib/public/iservercontainer.php index 600d81d83af..22176c36b8a 100644 --- a/lib/public/iservercontainer.php +++ b/lib/public/iservercontainer.php @@ -113,6 +113,14 @@ interface IServerContainer { */ function getConfig(); + + /** + * Returns an instance of the db facade + * @return \OCP\IDb + */ + function getDb(); + + /** * Returns the app config manager * diff --git a/settings/l10n/el.php b/settings/l10n/el.php index 1ca97b63da8..9daa0a5e092 100644 --- a/settings/l10n/el.php +++ b/settings/l10n/el.php @@ -111,6 +111,8 @@ $TRANSLATIONS = array( "Allow users to share with anyone" => "Επιτρέπεται στους χρήστες ο διαμοιρασμός με οποιονδήποτε", "Allow users to only share with users in their groups" => "Επιτρέπεται στους χρήστες ο διαμοιρασμός μόνο με χρήστες της ίδιας ομάδας", "Allow mail notification" => "Επιτρέπονται ειδοποιήσεις ηλεκτρονικού ταχυδρομείου", +"Allow users to send mail notification for shared files" => "Επιτρέψτε στους χρήστες να στέλνουν ειδοποιήσεις μέσω ηλεκτρονικού ταχυδρομείου για κοινόχρηστα αρχεία", +"days" => "ημέρες", "Security" => "Ασφάλεια", "Enforce HTTPS" => "Επιβολή χρήσης HTTPS", "Forces the clients to connect to %s via an encrypted connection." => "Επιβάλλει τους πελάτες να συνδέονται στο %s μέσω κρυπτογραφημένης σύνδεσης.", diff --git a/settings/l10n/km.php b/settings/l10n/km.php index 679c7cd3901..3db4708be35 100644 --- a/settings/l10n/km.php +++ b/settings/l10n/km.php @@ -1,6 +1,11 @@ <?php $TRANSLATIONS = array( +"Saved" => "បានរក្សាទុក", +"test email settings" => "សាកល្បងការកំណត់អ៊ីមែល", +"If you received this email, the settings seem to be correct." => "ប្រសិនបើអ្នកទទួលបានអ៊ីមែលនេះ មានន័យថាការកំណត់គឺបានត្រឹមមត្រូវហើយ។", +"A problem occurred while sending the e-mail. Please revisit your settings." => "មានកំហុសកើតឡើងនៅពេលកំពុងផ្ញើអ៊ីមែលចេញ។ សូមមើលការកំណត់របស់អ្នកម្ដងទៀត។", "Email sent" => "បានផ្ញើអ៊ីមែល", +"You need to set your user email before being able to send test emails." => "អ្នកត្រូវតែកំណត់អ៊ីមែលរបស់អ្នកមុននឹងអាចផ្ញើអ៊ីមែលសាកល្បងបាន។", "Encryption" => "កូដនីយកម្ម", "Unable to load list from App Store" => "មិនអាចផ្ទុកបញ្ជីកម្មវិធីពី App Store", "Authentication error" => "កំហុសការផ្ទៀងផ្ទាត់ភាពត្រឹមត្រូវ", @@ -16,7 +21,10 @@ $TRANSLATIONS = array( "Unable to add user to group %s" => "មិនអាចបន្ថែមអ្នកប្រើទៅក្រុម %s", "Unable to remove user from group %s" => "មិនអាចដកអ្នកប្រើចេញពីក្រុម %s", "Couldn't update app." => "មិនអាចធ្វើបច្ចុប្បន្នភាពកម្មវិធី។", +"Wrong password" => "ខុសពាក្យសម្ងាត់", +"Sending..." => "កំពុងផ្ញើ...", "User Documentation" => "ឯកសារសម្រាប់អ្នកប្រើប្រាស់", +"Admin Documentation" => "កម្រងឯកសារអភិបាល", "Update to {appversion}" => "ធ្វើបច្ចុប្បន្នភាពទៅ {appversion}", "Disable" => "បិទ", "Enable" => "បើក", @@ -26,6 +34,12 @@ $TRANSLATIONS = array( "Error" => "កំហុស", "Update" => "ធ្វើបច្ចុប្បន្នភាព", "Updated" => "បានធ្វើបច្ចុប្បន្នភាព", +"Select a profile picture" => "ជ្រើសរូបភាពប្រវត្តិរូប", +"Very weak password" => "ពាក្យសម្ងាត់ខ្សោយណាស់", +"Weak password" => "ពាក្យសម្ងាត់ខ្សោយ", +"So-so password" => "ពាក្យសម្ងាត់ធម្មតា", +"Good password" => "ពាក្យសម្ងាត់ល្អ", +"Strong password" => "ពាក្យសម្ងាត់ខ្លាំង", "deleted" => "បានលុប", "undo" => "មិនធ្វើវិញ", "Unable to remove user" => "មិនអាចដកអ្នកប្រើចេញ", @@ -37,7 +51,10 @@ $TRANSLATIONS = array( "Error creating user" => "មានកំហុសក្នុងការបង្កើតអ្នកប្រើ", "A valid password must be provided" => "ត្រូវផ្ដល់ពាក្យសម្ងាត់ឲ្យបានត្រឹមត្រូវ", "__language_name__" => "__language_name__", +"None" => "គ្មាន", "Login" => "ចូល", +"SSL" => "SSL", +"TLS" => "TLS", "Security Warning" => "បម្រាមសុវត្ថិភាព", "Setup Warning" => "បម្រាមការដំឡើង", "Module 'fileinfo' missing" => "ខ្វះម៉ូឌុល 'fileinfo'", @@ -49,11 +66,15 @@ $TRANSLATIONS = array( "Allow apps to use the Share API" => "អនុញ្ញាតឲ្យកម្មវិធីប្រើ API ចែករំលែក", "Allow links" => "អនុញ្ញាតតំណ", "Allow users to share items to the public with links" => "អនុញ្ញាតឲ្យអ្នកប្រើចែករំលែករបស់ទៅសាធារណៈជាមួយតំណ", +"Allow public uploads" => "អនុញ្ញាតការផ្ទុកឡើងជាសាធារណៈ", "Allow resharing" => "អនុញ្ញាតការចែករំលែកម្ដងទៀត", "Allow users to share with anyone" => "អនុញ្ញាតឲ្យអ្នកប្រើចែករំលែកជាមួយនរណាម្នាក់", "Security" => "សុវត្ថិភាព", "Enforce HTTPS" => "បង្ខំ HTTPS", +"Email Server" => "ម៉ាស៊ីនបម្រើអ៊ីមែល", +"From address" => "ពីអាសយដ្ឋាន", "Server address" => "អាសយដ្ឋានម៉ាស៊ីនបម្រើ", +"Send email" => "ផ្ញើអ៊ីមែល", "Log" => "Log", "Log level" => "កម្រិត Log", "More" => "ច្រើនទៀត", @@ -67,18 +88,30 @@ $TRANSLATIONS = array( "Forum" => "វេទិកាពិភាក្សា", "Bugtracker" => "Bugtracker", "Password" => "ពាក្យសម្ងាត់", +"Your password was changed" => "ពាក្យសម្ងាត់របស់អ្នកត្រូវបានប្ដូរ", +"Unable to change your password" => "មិនអាចប្ដូរពាក្យសម្ងាត់របស់អ្នកបានទេ", "Current password" => "ពាក្យសម្ងាត់បច្ចុប្បន្ន", "New password" => "ពាក្យសម្ងាត់ថ្មី", "Change password" => "ប្តូរពាក្យសម្ងាត់", "Email" => "អ៊ីមែល", "Your email address" => "អ៊ីម៉ែលរបស់អ្នក", +"Profile picture" => "រូបភាពប្រវត្តិរូប", +"Upload new" => "ផ្ទុកឡើងថ្មី", +"Select new from Files" => "ជ្រើសថ្មីពីឯកសារ", +"Remove image" => "ដករូបភាពចេញ", "Cancel" => "លើកលែង", "Language" => "ភាសា", "Help translate" => "ជួយបកប្រែ", "WebDAV" => "WebDAV", +"Log-in password" => "ពាក្យសម្ងាត់ចូលគណនី", "Login Name" => "ចូល", "Create" => "បង្កើត", +"Default Storage" => "ឃ្លាំងផ្ទុកលំនាំដើម", +"Unlimited" => "មិនកំណត់", "Other" => "ផ្សេងៗ", -"Username" => "ឈ្មោះអ្នកប្រើ" +"Username" => "ឈ្មោះអ្នកប្រើ", +"Storage" => "ឃ្លាំងផ្ទុក", +"set new password" => "កំណត់ពាក្យសម្ងាត់ថ្មី", +"Default" => "លំនាំដើម" ); $PLURAL_FORMS = "nplurals=1; plural=0;"; diff --git a/tests/lib/app.php b/tests/lib/app.php index 49f40f089bb..683820cabb6 100644 --- a/tests/lib/app.php +++ b/tests/lib/app.php @@ -1,6 +1,6 @@ <?php /** - * Copyright (c) 2012 Bernhard Posselt <nukeawhale@gmail.com> + * Copyright (c) 2012 Bernhard Posselt <dev@bernhard-posselt.com> * This file is licensed under the Affero General Public License version 3 or * later. * See the COPYING-README file. diff --git a/tests/lib/appframework/AppTest.php b/tests/lib/appframework/AppTest.php index 3628e4ceab2..92fa4838341 100644 --- a/tests/lib/appframework/AppTest.php +++ b/tests/lib/appframework/AppTest.php @@ -4,7 +4,7 @@ * ownCloud - App Framework * * @author Bernhard Posselt - * @copyright 2012 Bernhard Posselt nukeawhale@gmail.com + * @copyright 2012 Bernhard Posselt <dev@bernhard-posselt.com> * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE diff --git a/tests/lib/appframework/controller/ApiControllerTest.php b/tests/lib/appframework/controller/ApiControllerTest.php new file mode 100644 index 00000000000..b772f540ce8 --- /dev/null +++ b/tests/lib/appframework/controller/ApiControllerTest.php @@ -0,0 +1,55 @@ +<?php + +/** + * ownCloud - App Framework + * + * @author Bernhard Posselt + * @copyright 2012 Bernhard Posselt nukeawhale@gmail.com + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + * License as published by the Free Software Foundation; either + * version 3 of the License, or any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU AFFERO GENERAL PUBLIC LICENSE for more details. + * + * You should have received a copy of the GNU Affero General Public + * License along with this library. If not, see <http://www.gnu.org/licenses/>. + * + */ + + +namespace OCP\AppFramework; + +use OC\AppFramework\Http\Request; +use OCP\AppFramework\Http\TemplateResponse; + + +class ChildApiController extends ApiController {}; + + +class ApiControllerTest extends \PHPUnit_Framework_TestCase { + + + public function testCors() { + $request = new Request( + array('server' => array('HTTP_ORIGIN' => 'test')) + ); + $this->controller = new ChildApiController('app', $request, 'verbs', + 'headers', 100); + + $response = $this->controller->preflightedCors(); + + $headers = $response->getHeaders(); + + $this->assertEquals('test', $headers['Access-Control-Allow-Origin']); + $this->assertEquals('verbs', $headers['Access-Control-Allow-Methods']); + $this->assertEquals('headers', $headers['Access-Control-Allow-Headers']); + $this->assertEquals('false', $headers['Access-Control-Allow-Credentials']); + $this->assertEquals(100, $headers['Access-Control-Max-Age']); + } + +} diff --git a/tests/lib/appframework/controller/ControllerTest.php b/tests/lib/appframework/controller/ControllerTest.php index f17d5f24aa5..3c1716a91d8 100644 --- a/tests/lib/appframework/controller/ControllerTest.php +++ b/tests/lib/appframework/controller/ControllerTest.php @@ -4,7 +4,7 @@ * ownCloud - App Framework * * @author Bernhard Posselt - * @copyright 2012 Bernhard Posselt nukeawhale@gmail.com + * @copyright 2012 Bernhard Posselt <dev@bernhard-posselt.com> * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE @@ -22,14 +22,35 @@ */ -namespace Test\AppFramework\Controller; +namespace OCP\AppFramework; use OC\AppFramework\Http\Request; -use OCP\AppFramework\Controller; use OCP\AppFramework\Http\TemplateResponse; +use OCP\AppFramework\Http\JSONResponse; +use OCP\AppFramework\Http\IResponseSerializer; -class ChildController extends Controller {}; +class ToUpperCaseSerializer implements IResponseSerializer { + public function serialize($response) { + return array(strtoupper($response)); + } +} + +class ChildController extends Controller { + public function custom($in) { + $this->registerResponder('json', function ($response) { + return new JSONResponse(array(strlen($response))); + }); + + return $in; + } + + public function serializer($in) { + $this->registerSerializer(new ToUpperCaseSerializer()); + + return $in; + } +}; class ControllerTest extends \PHPUnit_Framework_TestCase { @@ -129,4 +150,36 @@ class ControllerTest extends \PHPUnit_Framework_TestCase { $this->assertEquals('daheim', $this->controller->env('PATH')); } + + /** + * @expectedException \DomainException + */ + public function testFormatResonseInvalidFormat() { + $this->controller->buildResponse(null, 'test'); + } + + + public function testFormat() { + $response = $this->controller->buildResponse(array('hi'), 'json'); + + $this->assertEquals(array('hi'), $response->getData()); + } + + + public function testCustomFormatter() { + $response = $this->controller->custom('hi'); + $response = $this->controller->buildResponse($response, 'json'); + + $this->assertEquals(array(2), $response->getData()); + } + + + public function testCustomSerializer() { + $response = $this->controller->serializer('hi'); + $response = $this->controller->buildResponse($response, 'json'); + + $this->assertEquals(array('HI'), $response->getData()); + } + + } diff --git a/tests/lib/appframework/db/EntityTest.php b/tests/lib/appframework/db/EntityTest.php new file mode 100644 index 00000000000..9de44b9b3ba --- /dev/null +++ b/tests/lib/appframework/db/EntityTest.php @@ -0,0 +1,223 @@ +<?php + +/** +* ownCloud - App Framework +* +* @author Bernhard Posselt +* @copyright 2012 Bernhard Posselt dev@bernhard-posselt.com +* +* This library is free software; you can redistribute it and/or +* modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE +* License as published by the Free Software Foundation; either +* version 3 of the License, or any later version. +* +* This library is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU AFFERO GENERAL PUBLIC LICENSE for more details. +* +* You should have received a copy of the GNU Affero General Public +* License along with this library. If not, see <http://www.gnu.org/licenses/>. +* +*/ + +namespace OCP\AppFramework\Db; + + +/** + * @method integer getId() + * @method void setId(integer $id) + * @method integer getTestId() + * @method void setTestId(integer $id) + * @method string getName() + * @method void setName(string $name) + * @method string getEmail() + * @method void setEmail(string $email) + * @method string getPreName() + * @method void setPreName(string $preName) + */ +class TestEntity extends Entity { + public $name; + public $email; + public $testId; + public $preName; + + public function __construct($name=null){ + $this->addType('testId', 'integer'); + $this->name = $name; + } +}; + + +class EntityTest extends \PHPUnit_Framework_TestCase { + + private $entity; + + protected function setUp(){ + $this->entity = new TestEntity(); + } + + + public function testResetUpdatedFields(){ + $entity = new TestEntity(); + $entity->setId(3); + $entity->resetUpdatedFields(); + + $this->assertEquals(array(), $entity->getUpdatedFields()); + } + + + public function testFromRow(){ + $row = array( + 'pre_name' => 'john', + 'email' => 'john@something.com' + ); + $this->entity = TestEntity::fromRow($row); + + $this->assertEquals($row['pre_name'], $this->entity->getPreName()); + $this->assertEquals($row['email'], $this->entity->getEmail()); + } + + + public function testGetSetId(){ + $id = 3; + $this->entity->setId(3); + + $this->assertEquals($id, $this->entity->getId()); + } + + + public function testColumnToPropertyNoReplacement(){ + $column = 'my'; + $this->assertEquals('my', + $this->entity->columnToProperty($column)); + } + + + public function testColumnToProperty(){ + $column = 'my_attribute'; + $this->assertEquals('myAttribute', + $this->entity->columnToProperty($column)); + } + + + public function testPropertyToColumnNoReplacement(){ + $property = 'my'; + $this->assertEquals('my', + $this->entity->propertyToColumn($property)); + } + + + public function testSetterMarksFieldUpdated(){ + $this->entity->setId(3); + + $this->assertContains('id', $this->entity->getUpdatedFields()); + } + + + public function testCallShouldOnlyWorkForGetterSetter(){ + $this->setExpectedException('\BadFunctionCallException'); + + $this->entity->something(); + } + + + public function testGetterShouldFailIfAttributeNotDefined(){ + $this->setExpectedException('\BadFunctionCallException'); + + $this->entity->getTest(); + } + + + public function testSetterShouldFailIfAttributeNotDefined(){ + $this->setExpectedException('\BadFunctionCallException'); + + $this->entity->setTest(); + } + + + public function testFromRowShouldNotAssignEmptyArray(){ + $row = array(); + $entity2 = new TestEntity(); + + $this->entity = TestEntity::fromRow($row); + $this->assertEquals($entity2, $this->entity); + } + + + public function testIdGetsConvertedToInt(){ + $row = array('id' => '4'); + + $this->entity = TestEntity::fromRow($row); + $this->assertSame(4, $this->entity->getId()); + } + + + public function testSetType(){ + $row = array('testId' => '4'); + + $this->entity = TestEntity::fromRow($row); + $this->assertSame(4, $this->entity->getTestId()); + } + + + public function testFromParams(){ + $params = array( + 'testId' => 4, + 'email' => 'john@doe' + ); + + $entity = TestEntity::fromParams($params); + + $this->assertEquals($params['testId'], $entity->getTestId()); + $this->assertEquals($params['email'], $entity->getEmail()); + $this->assertTrue($entity instanceof TestEntity); + } + + public function testSlugify(){ + $entity = new TestEntity(); + $entity->setName('Slugify this!'); + $this->assertEquals('slugify-this', $entity->slugify('name')); + $entity->setName('°!"§$%&/()=?`´ß\}][{³²#\'+~*-_.:,;<>|äöüÄÖÜSlugify this!'); + $this->assertEquals('slugify-this', $entity->slugify('name')); + } + + + public function testSetterCasts() { + $entity = new TestEntity(); + $entity->setId('3'); + $this->assertSame(3, $entity->getId()); + } + + + public function testSetterDoesNotCastOnNull() { + $entity = new TestEntity(); + $entity->setId(null); + $this->assertSame(null, $entity->getId()); + } + + + public function testGetFieldTypes() { + $entity = new TestEntity(); + $this->assertEquals(array( + 'id' => 'integer', + 'testId' => 'integer' + ), $entity->getFieldTypes()); + } + + + public function testGetItInt() { + $entity = new TestEntity(); + $entity->setId(3); + $this->assertEquals('integer', gettype($entity->getId())); + } + + + public function testFieldsNotMarkedUpdatedIfNothingChanges() { + $entity = new TestEntity('hey'); + $entity->setName('hey'); + $this->assertEquals(0, count($entity->getUpdatedFields())); + } + + +}
\ No newline at end of file diff --git a/tests/lib/appframework/db/MapperTest.php b/tests/lib/appframework/db/MapperTest.php new file mode 100644 index 00000000000..4ddc4ef0422 --- /dev/null +++ b/tests/lib/appframework/db/MapperTest.php @@ -0,0 +1,279 @@ +<?php + +/** + * ownCloud - App Framework + * + * @author Bernhard Posselt + * @copyright 2012 Bernhard Posselt dev@bernhard-posselt.com + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + * License as published by the Free Software Foundation; either + * version 3 of the License, or any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU AFFERO GENERAL PUBLIC LICENSE for more details. + * + * You should have received a copy of the GNU Affero General Public + * License along with this library. If not, see <http://www.gnu.org/licenses/>. + * + */ + + +namespace OCP\AppFramework\Db; + +use \OCP\IDb; + + +require_once __DIR__ . '/MapperTestUtility.php'; + +/** + * @method integer getId() + * @method void setId(integer $id) + * @method string getEmail() + * @method void setEmail(string $email) + * @method string getPreName() + * @method void setPreName(string $preName) + */ +class Example extends Entity { + public $preName; + public $email; +}; + + +class ExampleMapper extends Mapper { + public function __construct(IDb $db){ parent::__construct($db, 'table'); } + public function find($table, $id){ return $this->findOneQuery($table, $id); } + public function findOneEntity($table, $id){ return $this->findEntity($table, $id); } + public function findAllEntities($table){ return $this->findEntities($table); } + public function mapRow($row){ return $this->mapRowToEntity($row); } +} + + +class MapperTest extends MapperTestUtility { + + private $mapper; + + public function setUp(){ + parent::setUp(); + $this->mapper = new ExampleMapper($this->db); + } + + + public function testMapperShouldSetTableName(){ + $this->assertEquals('*PREFIX*table', $this->mapper->getTableName()); + } + + + public function testFindQuery(){ + $sql = 'hi'; + $params = array('jo'); + $rows = array( + array('hi') + ); + $this->setMapperResult($sql, $params, $rows); + $this->mapper->find($sql, $params); + } + + public function testFindEntity(){ + $sql = 'hi'; + $params = array('jo'); + $rows = array( + array('pre_name' => 'hi') + ); + $this->setMapperResult($sql, $params, $rows); + $this->mapper->findOneEntity($sql, $params); + } + + public function testFindNotFound(){ + $sql = 'hi'; + $params = array('jo'); + $rows = array(); + $this->setMapperResult($sql, $params, $rows); + $this->setExpectedException( + '\OCP\AppFramework\Db\DoesNotExistException'); + $this->mapper->find($sql, $params); + } + + public function testFindEntityNotFound(){ + $sql = 'hi'; + $params = array('jo'); + $rows = array(); + $this->setMapperResult($sql, $params, $rows); + $this->setExpectedException( + '\OCP\AppFramework\Db\DoesNotExistException'); + $this->mapper->findOneEntity($sql, $params); + } + + public function testFindMultiple(){ + $sql = 'hi'; + $params = array('jo'); + $rows = array( + array('jo'), array('ho') + ); + $this->setMapperResult($sql, $params, $rows); + $this->setExpectedException( + '\OCP\AppFramework\Db\MultipleObjectsReturnedException'); + $this->mapper->find($sql, $params); + } + + public function testFindEntityMultiple(){ + $sql = 'hi'; + $params = array('jo'); + $rows = array( + array('jo'), array('ho') + ); + $this->setMapperResult($sql, $params, $rows); + $this->setExpectedException( + '\OCP\AppFramework\Db\MultipleObjectsReturnedException'); + $this->mapper->findOneEntity($sql, $params); + } + + + public function testDelete(){ + $sql = 'DELETE FROM `*PREFIX*table` WHERE `id` = ?'; + $params = array(2); + + $this->setMapperResult($sql, $params); + $entity = new Example(); + $entity->setId($params[0]); + + $this->mapper->delete($entity); + } + + + public function testCreate(){ + $this->db->expects($this->once()) + ->method('getInsertId') + ->with($this->equalTo('*PREFIX*table')) + ->will($this->returnValue(3)); + $this->mapper = new ExampleMapper($this->db); + + $sql = 'INSERT INTO `*PREFIX*table`(`pre_name`,`email`) ' . + 'VALUES(?,?)'; + $params = array('john', 'my@email'); + $entity = new Example(); + $entity->setPreName($params[0]); + $entity->setEmail($params[1]); + + $this->setMapperResult($sql, $params); + + $this->mapper->insert($entity); + } + + + public function testCreateShouldReturnItemWithCorrectInsertId(){ + $this->db->expects($this->once()) + ->method('getInsertId') + ->with($this->equalTo('*PREFIX*table')) + ->will($this->returnValue(3)); + $this->mapper = new ExampleMapper($this->db); + + $sql = 'INSERT INTO `*PREFIX*table`(`pre_name`,`email`) ' . + 'VALUES(?,?)'; + $params = array('john', 'my@email'); + $entity = new Example(); + $entity->setPreName($params[0]); + $entity->setEmail($params[1]); + + $this->setMapperResult($sql, $params); + + $result = $this->mapper->insert($entity); + + $this->assertEquals(3, $result->getId()); + } + + + public function testUpdate(){ + $sql = 'UPDATE `*PREFIX*table` ' . + 'SET ' . + '`pre_name` = ?,'. + '`email` = ? ' . + 'WHERE `id` = ?'; + + $params = array('john', 'my@email', 1); + $entity = new Example(); + $entity->setPreName($params[0]); + $entity->setEmail($params[1]); + $entity->setId($params[2]); + + $this->setMapperResult($sql, $params); + + $this->mapper->update($entity); + } + + + public function testUpdateNoId(){ + $params = array('john', 'my@email'); + $entity = new Example(); + $entity->setPreName($params[0]); + $entity->setEmail($params[1]); + + $this->setExpectedException('InvalidArgumentException'); + + $this->mapper->update($entity); + } + + + public function testUpdateNothingChangedNoQuery(){ + $params = array('john', 'my@email'); + $entity = new Example(); + $entity->setId(3); + $entity->setEmail($params[1]); + $entity->resetUpdatedFields(); + + $this->db->expects($this->never()) + ->method('prepareQuery'); + + $this->mapper->update($entity); + } + + + public function testMapRowToEntity(){ + $entity1 = $this->mapper->mapRow(array('pre_name' => 'test1', 'email' => 'test2')); + $entity2 = new Example(); + $entity2->setPreName('test1'); + $entity2->setEmail('test2'); + $entity2->resetUpdatedFields(); + $this->assertEquals($entity2, $entity1); + } + + public function testFindEntities(){ + $sql = 'hi'; + $rows = array( + array('pre_name' => 'hi') + ); + $entity = new Example(); + $entity->setPreName('hi'); + $entity->resetUpdatedFields(); + $this->setMapperResult($sql, array(), $rows); + $result = $this->mapper->findAllEntities($sql); + $this->assertEquals(array($entity), $result); + } + + public function testFindEntitiesNotFound(){ + $sql = 'hi'; + $rows = array(); + $this->setMapperResult($sql, array(), $rows); + $result = $this->mapper->findAllEntities($sql); + $this->assertEquals(array(), $result); + } + + public function testFindEntitiesMultiple(){ + $sql = 'hi'; + $rows = array( + array('pre_name' => 'jo'), array('email' => 'ho') + ); + $entity1 = new Example(); + $entity1->setPreName('jo'); + $entity1->resetUpdatedFields(); + $entity2 = new Example(); + $entity2->setEmail('ho'); + $entity2->resetUpdatedFields(); + $this->setMapperResult($sql, array(), $rows); + $result = $this->mapper->findAllEntities($sql); + $this->assertEquals(array($entity1, $entity2), $result); + } +}
\ No newline at end of file diff --git a/tests/lib/appframework/db/MapperTestUtility.php b/tests/lib/appframework/db/MapperTestUtility.php new file mode 100644 index 00000000000..4c81d4cd27b --- /dev/null +++ b/tests/lib/appframework/db/MapperTestUtility.php @@ -0,0 +1,179 @@ +<?php + +/** + * ownCloud - App Framework + * + * @author Bernhard Posselt + * @copyright 2012 Bernhard Posselt dev@bernhard-posselt.com + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + * License as published by the Free Software Foundation; either + * version 3 of the License, or any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU AFFERO GENERAL PUBLIC LICENSE for more details. + * + * You should have received a copy of the GNU Affero General Public + * License along with this library. If not, see <http://www.gnu.org/licenses/>. + * + */ + + +namespace OCP\AppFramework\Db; + + +/** + * Simple utility class for testing mappers + */ +abstract class MapperTestUtility extends \PHPUnit_Framework_TestCase { + + + protected $db; + private $query; + private $pdoResult; + private $queryAt; + private $prepareAt; + private $fetchAt; + private $iterators; + + + /** + * Run this function before the actual test to either set or initialize the + * db. After this the db can be accessed by using $this->db + */ + protected function setUp(){ + $this->db = $this->getMockBuilder( + '\OCP\IDb') + ->disableOriginalConstructor() + ->getMock(); + + $this->query = $this->getMock('Query', array('execute', 'bindValue')); + $this->pdoResult = $this->getMock('Result', array('fetchRow')); + $this->queryAt = 0; + $this->prepareAt = 0; + $this->iterators = array(); + $this->fetchAt = 0; + } + + + /** + * Create mocks and set expected results for database queries + * @param string $sql the sql query that you expect to receive + * @param array $arguments the expected arguments for the prepare query + * method + * @param array $returnRows the rows that should be returned for the result + * of the database query. If not provided, it wont be assumed that fetchRow + * will be called on the result + */ + protected function setMapperResult($sql, $arguments=array(), $returnRows=array(), + $limit=null, $offset=null){ + + $this->iterators[] = new ArgumentIterator($returnRows); + + $iterators = $this->iterators; + $fetchAt = $this->fetchAt; + + $this->pdoResult->expects($this->any()) + ->method('fetchRow') + ->will($this->returnCallback( + function() use ($iterators, $fetchAt){ + $iterator = $iterators[$fetchAt]; + $result = $iterator->next(); + + if($result === false) { + $fetchAt++; + } + + return $result; + } + )); + + $index = 1; + foreach($arguments as $argument) { + switch (gettype($argument)) { + case 'integer': + $pdoConstant = \PDO::PARAM_INT; + break; + + case 'NULL': + $pdoConstant = \PDO::PARAM_NULL; + break; + + case 'boolean': + $pdoConstant = \PDO::PARAM_BOOL; + break; + + default: + $pdoConstant = \PDO::PARAM_STR; + break; + } + $this->query->expects($this->at($this->queryAt)) + ->method('bindValue') + ->with($this->equalTo($index), + $this->equalTo($argument), + $this->equalTo($pdoConstant)); + $index++; + $this->queryAt++; + } + + $this->query->expects($this->at($this->queryAt)) + ->method('execute') + ->with() + ->will($this->returnValue($this->pdoResult)); + $this->queryAt++; + + if($limit === null && $offset === null) { + $this->db->expects($this->at($this->prepareAt)) + ->method('prepareQuery') + ->with($this->equalTo($sql)) + ->will(($this->returnValue($this->query))); + } elseif($limit !== null && $offset === null) { + $this->db->expects($this->at($this->prepareAt)) + ->method('prepareQuery') + ->with($this->equalTo($sql), $this->equalTo($limit)) + ->will(($this->returnValue($this->query))); + } elseif($limit === null && $offset !== null) { + $this->db->expects($this->at($this->prepareAt)) + ->method('prepareQuery') + ->with($this->equalTo($sql), + $this->equalTo(null), + $this->equalTo($offset)) + ->will(($this->returnValue($this->query))); + } else { + $this->db->expects($this->at($this->prepareAt)) + ->method('prepareQuery') + ->with($this->equalTo($sql), + $this->equalTo($limit), + $this->equalTo($offset)) + ->will(($this->returnValue($this->query))); + } + $this->prepareAt++; + $this->fetchAt++; + + } + + +} + + +class ArgumentIterator { + + private $arguments; + + public function __construct($arguments){ + $this->arguments = $arguments; + } + + public function next(){ + $result = array_shift($this->arguments); + if($result === null){ + return false; + } else { + return $result; + } + } +} + diff --git a/tests/lib/appframework/dependencyinjection/DIContainerTest.php b/tests/lib/appframework/dependencyinjection/DIContainerTest.php index d1bc900fb28..acc5c2e66d8 100644 --- a/tests/lib/appframework/dependencyinjection/DIContainerTest.php +++ b/tests/lib/appframework/dependencyinjection/DIContainerTest.php @@ -5,8 +5,8 @@ * * @author Bernhard Posselt * @author Morris Jobke - * @copyright 2012 Bernhard Posselt nukeawhale@gmail.com - * @copyright 2013 Morris Jobke morris.jobke@gmail.com + * @copyright 2012 Bernhard Posselt <dev@bernhard-posselt.com> + * @copyright 2013 Morris Jobke <morris.jobke@gmail.com> * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE diff --git a/tests/lib/appframework/http/DispatcherTest.php b/tests/lib/appframework/http/DispatcherTest.php index 9841dcaa1f7..2aef2911e2c 100644 --- a/tests/lib/appframework/http/DispatcherTest.php +++ b/tests/lib/appframework/http/DispatcherTest.php @@ -4,7 +4,7 @@ * ownCloud - App Framework * * @author Bernhard Posselt - * @copyright 2012 Bernhard Posselt nukeawhale@gmail.com + * @copyright 2012 Bernhard Posselt <dev@bernhard-posselt.com> * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE @@ -25,8 +25,28 @@ namespace OC\AppFramework\Http; use OC\AppFramework\Middleware\MiddlewareDispatcher; +use OC\AppFramework\Utility\ControllerMethodReflector; use OCP\AppFramework\Http; -//require_once(__DIR__ . "/../classloader.php"); +use OCP\AppFramework\Http\JSONResponse; +use OCP\AppFramework\Controller; + + +class TestController extends Controller { + public function __construct($appName, $request) { + parent::__construct($appName, $request); + } + + /** + * @param int $int + * @param bool $bool + */ + public function exec($int, $bool) { + $this->registerResponder('text', function($in) { + return new JSONResponse(array('text' => $in)); + }); + return array($int, $bool); + } +} class DispatcherTest extends \PHPUnit_Framework_TestCase { @@ -39,6 +59,7 @@ class DispatcherTest extends \PHPUnit_Framework_TestCase { private $lastModified; private $etag; private $http; + private $reflector; protected function setUp() { $this->controllerMethod = 'test'; @@ -64,8 +85,17 @@ class DispatcherTest extends \PHPUnit_Framework_TestCase { '\OCP\AppFramework\Controller', array($this->controllerMethod), array($app, $request)); + $this->request = $this->getMockBuilder( + '\OC\AppFramework\Http\Request') + ->disableOriginalConstructor() + ->getMock(); + + $this->reflector = new ControllerMethodReflector(); + $this->dispatcher = new Dispatcher( - $this->http, $this->middlewareDispatcher); + $this->http, $this->middlewareDispatcher, $this->reflector, + $this->request + ); $this->response = $this->getMockBuilder( '\OCP\AppFramework\Http\Response') @@ -81,7 +111,7 @@ class DispatcherTest extends \PHPUnit_Framework_TestCase { * @param string $out * @param string $httpHeaders */ - private function setMiddlewareExpections($out=null, + private function setMiddlewareExpectations($out=null, $httpHeaders=null, $responseHeaders=array(), $ex=false, $catchEx=true) { @@ -159,14 +189,12 @@ class DispatcherTest extends \PHPUnit_Framework_TestCase { ->with($this->equalTo($this->controller), $this->equalTo($this->controllerMethod), $this->equalTo($out)) - ->will($this->returnValue($out)); - - + ->will($this->returnValue($out)); } public function testDispatcherReturnsArrayWith2Entries() { - $this->setMiddlewareExpections(); + $this->setMiddlewareExpectations(); $response = $this->dispatcher->dispatch($this->controller, $this->controllerMethod); @@ -180,7 +208,7 @@ class DispatcherTest extends \PHPUnit_Framework_TestCase { $out = 'yo'; $httpHeaders = 'Http'; $responseHeaders = array('hell' => 'yeah'); - $this->setMiddlewareExpections($out, $httpHeaders, $responseHeaders); + $this->setMiddlewareExpectations($out, $httpHeaders, $responseHeaders); $response = $this->dispatcher->dispatch($this->controller, $this->controllerMethod); @@ -195,7 +223,7 @@ class DispatcherTest extends \PHPUnit_Framework_TestCase { $out = 'yo'; $httpHeaders = 'Http'; $responseHeaders = array('hell' => 'yeah'); - $this->setMiddlewareExpections($out, $httpHeaders, $responseHeaders, true); + $this->setMiddlewareExpectations($out, $httpHeaders, $responseHeaders, true); $response = $this->dispatcher->dispatch($this->controller, $this->controllerMethod); @@ -210,7 +238,7 @@ class DispatcherTest extends \PHPUnit_Framework_TestCase { $out = 'yo'; $httpHeaders = 'Http'; $responseHeaders = array('hell' => 'yeah'); - $this->setMiddlewareExpections($out, $httpHeaders, $responseHeaders, true, false); + $this->setMiddlewareExpectations($out, $httpHeaders, $responseHeaders, true, false); $this->setExpectedException('\Exception'); $response = $this->dispatcher->dispatch($this->controller, @@ -218,4 +246,120 @@ class DispatcherTest extends \PHPUnit_Framework_TestCase { } + + private function dispatcherPassthrough() { + $this->middlewareDispatcher->expects($this->once()) + ->method('beforeController'); + $this->middlewareDispatcher->expects($this->once()) + ->method('afterController') + ->will($this->returnCallback(function($a, $b, $in) { + return $in; + })); + $this->middlewareDispatcher->expects($this->once()) + ->method('beforeOutput') + ->will($this->returnCallback(function($a, $b, $in) { + return $in; + })); + } + + public function testControllerParametersInjected() { + $this->request = new Request(array( + 'post' => array( + 'int' => '3', + 'bool' => 'false' + ), + 'method' => 'POST' + )); + $this->dispatcher = new Dispatcher( + $this->http, $this->middlewareDispatcher, $this->reflector, + $this->request + ); + $controller = new TestController('app', $this->request); + + // reflector is supposed to be called once + $this->dispatcherPassthrough(); + $response = $this->dispatcher->dispatch($controller, 'exec'); + + $this->assertEquals('[3,true]', $response[2]); + } + + + public function testResponseTransformedByUrlFormat() { + $this->request = new Request(array( + 'post' => array( + 'int' => '3', + 'bool' => 'false' + ), + 'urlParams' => array( + 'format' => 'text' + ), + 'method' => 'GET' + )); + $this->dispatcher = new Dispatcher( + $this->http, $this->middlewareDispatcher, $this->reflector, + $this->request + ); + $controller = new TestController('app', $this->request); + + // reflector is supposed to be called once + $this->dispatcherPassthrough(); + $response = $this->dispatcher->dispatch($controller, 'exec'); + + $this->assertEquals('{"text":[3,false]}', $response[2]); + } + + + public function testResponseTransformedByAcceptHeader() { + $this->request = new Request(array( + 'post' => array( + 'int' => '3', + 'bool' => 'false' + ), + 'server' => array( + 'HTTP_ACCEPT' => 'application/text, test', + 'HTTP_CONTENT_TYPE' => 'application/x-www-form-urlencoded' + ), + 'method' => 'PUT' + )); + $this->dispatcher = new Dispatcher( + $this->http, $this->middlewareDispatcher, $this->reflector, + $this->request + ); + $controller = new TestController('app', $this->request); + + // reflector is supposed to be called once + $this->dispatcherPassthrough(); + $response = $this->dispatcher->dispatch($controller, 'exec'); + + $this->assertEquals('{"text":[3,false]}', $response[2]); + } + + + public function testResponsePrimarilyTransformedByParameterFormat() { + $this->request = new Request(array( + 'post' => array( + 'int' => '3', + 'bool' => 'false' + ), + 'get' => array( + 'format' => 'text' + ), + 'server' => array( + 'HTTP_ACCEPT' => 'application/json, test' + ), + 'method' => 'POST' + )); + $this->dispatcher = new Dispatcher( + $this->http, $this->middlewareDispatcher, $this->reflector, + $this->request + ); + $controller = new TestController('app', $this->request); + + // reflector is supposed to be called once + $this->dispatcherPassthrough(); + $response = $this->dispatcher->dispatch($controller, 'exec'); + + $this->assertEquals('{"text":[3,true]}', $response[2]); + } + } diff --git a/tests/lib/appframework/http/DownloadResponseTest.php b/tests/lib/appframework/http/DownloadResponseTest.php index b305c63ad4d..5be16ce3c49 100644 --- a/tests/lib/appframework/http/DownloadResponseTest.php +++ b/tests/lib/appframework/http/DownloadResponseTest.php @@ -4,7 +4,7 @@ * ownCloud - App Framework * * @author Bernhard Posselt - * @copyright 2012 Bernhard Posselt nukeawhale@gmail.com + * @copyright 2012 Bernhard Posselt <dev@bernhard-posselt.com> * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE diff --git a/tests/lib/appframework/http/HttpTest.php b/tests/lib/appframework/http/HttpTest.php index 0bdcee24c99..c62fa43863a 100644 --- a/tests/lib/appframework/http/HttpTest.php +++ b/tests/lib/appframework/http/HttpTest.php @@ -4,7 +4,7 @@ * ownCloud - App Framework * * @author Bernhard Posselt - * @copyright 2012 Bernhard Posselt nukeawhale@gmail.com + * @copyright 2012 Bernhard Posselt <dev@bernhard-posselt.com> * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE diff --git a/tests/lib/appframework/http/JSONResponseTest.php b/tests/lib/appframework/http/JSONResponseTest.php index fbaae1b9227..c0c58ebf761 100644 --- a/tests/lib/appframework/http/JSONResponseTest.php +++ b/tests/lib/appframework/http/JSONResponseTest.php @@ -5,8 +5,8 @@ * * @author Bernhard Posselt * @author Morris Jobke - * @copyright 2012 Bernhard Posselt nukeawhale@gmail.com - * @copyright 2013 Morris Jobke morris.jobke@gmail.com + * @copyright 2012 Bernhard Posselt <dev@bernhard-posselt.com> + * @copyright 2013 Morris Jobke <morris.jobke@gmail.com> * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE diff --git a/tests/lib/appframework/http/RedirectResponseTest.php b/tests/lib/appframework/http/RedirectResponseTest.php index f62b420f4ee..dfd0d7ee7dc 100644 --- a/tests/lib/appframework/http/RedirectResponseTest.php +++ b/tests/lib/appframework/http/RedirectResponseTest.php @@ -4,7 +4,7 @@ * ownCloud - App Framework * * @author Bernhard Posselt - * @copyright 2012 Bernhard Posselt nukeawhale@gmail.com + * @copyright 2012 Bernhard Posselt <dev@bernhard-posselt.com> * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE diff --git a/tests/lib/appframework/http/ResponseTest.php b/tests/lib/appframework/http/ResponseTest.php index 27350725d79..e83fe9e2d84 100644 --- a/tests/lib/appframework/http/ResponseTest.php +++ b/tests/lib/appframework/http/ResponseTest.php @@ -4,7 +4,7 @@ * ownCloud - App Framework * * @author Bernhard Posselt - * @copyright 2012 Bernhard Posselt nukeawhale@gmail.com + * @copyright 2012 Bernhard Posselt <dev@bernhard-posselt.com> * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE @@ -42,7 +42,7 @@ class ResponseTest extends \PHPUnit_Framework_TestCase { public function testAddHeader(){ - $this->childResponse->addHeader('hello', 'world'); + $this->childResponse->addHeader(' hello ', 'world'); $headers = $this->childResponse->getHeaders(); $this->assertEquals('world', $headers['hello']); } diff --git a/tests/lib/appframework/http/TemplateResponseTest.php b/tests/lib/appframework/http/TemplateResponseTest.php index 0b158edff6f..afdcf322b85 100644 --- a/tests/lib/appframework/http/TemplateResponseTest.php +++ b/tests/lib/appframework/http/TemplateResponseTest.php @@ -4,7 +4,7 @@ * ownCloud - App Framework * * @author Bernhard Posselt - * @copyright 2012 Bernhard Posselt nukeawhale@gmail.com + * @copyright 2012 Bernhard Posselt <dev@bernhard-posselt.com> * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE @@ -51,6 +51,22 @@ class TemplateResponseTest extends \PHPUnit_Framework_TestCase { } + public function testSetParamsConstructor(){ + $params = array('hi' => 'yo'); + $this->tpl = new TemplateResponse($this->api, 'home', $params); + + $this->assertEquals(array('hi' => 'yo'), $this->tpl->getParams()); + } + + + public function testSetRenderAsConstructor(){ + $renderAs = 'myrender'; + $this->tpl = new TemplateResponse($this->api, 'home', array(), $renderAs); + + $this->assertEquals($renderAs, $this->tpl->getRenderAs()); + } + + public function testSetParams(){ $params = array('hi' => 'yo'); $this->tpl->setParams($params); @@ -63,36 +79,6 @@ class TemplateResponseTest extends \PHPUnit_Framework_TestCase { $this->assertEquals('home', $this->tpl->getTemplateName()); } - -// public function testRender(){ -// $ocTpl = $this->getMock('Template', array('fetchPage')); -// $ocTpl->expects($this->once()) -// ->method('fetchPage'); -// -// $tpl = new TemplateResponse('core', 'error'); -// -// $tpl->render(); -// } -// -// -// public function testRenderAssignsParams(){ -// $params = array('john' => 'doe'); -// -// $tpl = new TemplateResponse('app', 'home'); -// $tpl->setParams($params); -// -// $tpl->render(); -// } -// -// -// public function testRenderDifferentApp(){ -// -// $tpl = new TemplateResponse('app', 'home', 'app2'); -// -// $tpl->render(); -// } - - public function testGetRenderAs(){ $render = 'myrender'; $this->tpl->renderAs($render); diff --git a/tests/lib/appframework/middleware/MiddlewareDispatcherTest.php b/tests/lib/appframework/middleware/MiddlewareDispatcherTest.php index 935f97d2a6f..b1a58e21289 100644 --- a/tests/lib/appframework/middleware/MiddlewareDispatcherTest.php +++ b/tests/lib/appframework/middleware/MiddlewareDispatcherTest.php @@ -4,7 +4,7 @@ * ownCloud - App Framework * * @author Bernhard Posselt - * @copyright 2012 Bernhard Posselt nukeawhale@gmail.com + * @copyright 2012 Bernhard Posselt <dev@bernhard-posselt.com> * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE diff --git a/tests/lib/appframework/middleware/MiddlewareTest.php b/tests/lib/appframework/middleware/MiddlewareTest.php index 7a93c0d4dda..814efdd8118 100644 --- a/tests/lib/appframework/middleware/MiddlewareTest.php +++ b/tests/lib/appframework/middleware/MiddlewareTest.php @@ -4,7 +4,7 @@ * ownCloud - App Framework * * @author Bernhard Posselt - * @copyright 2012 Bernhard Posselt nukeawhale@gmail.com + * @copyright 2012 Bernhard Posselt <dev@bernhard-posselt.com> * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE diff --git a/tests/lib/appframework/middleware/security/CORSMiddlewareTest.php b/tests/lib/appframework/middleware/security/CORSMiddlewareTest.php new file mode 100644 index 00000000000..79cd3b278af --- /dev/null +++ b/tests/lib/appframework/middleware/security/CORSMiddlewareTest.php @@ -0,0 +1,87 @@ +<?php +/** + * ownCloud - App Framework + * + * This file is licensed under the Affero General Public License version 3 or + * later. See the COPYING file. + * + * @author Bernhard Posselt <dev@bernhard-posselt.com> + * @copyright Bernhard Posselt 2014 + */ + + +namespace OC\AppFramework\Middleware\Security; + +use OC\AppFramework\Http\Request; +use OC\AppFramework\Utility\ControllerMethodReflector; + +use OCP\AppFramework\Http\Response; + + +class CORSMiddlewareTest extends \PHPUnit_Framework_TestCase { + + private $reflector; + + protected function setUp() { + $this->reflector = new ControllerMethodReflector(); + } + + /** + * @CORS + */ + public function testSetCORSAPIHeader() { + $request = new Request( + array('server' => array('HTTP_ORIGIN' => 'test')) + ); + $this->reflector->reflect($this, __FUNCTION__); + $middleware = new CORSMiddleware($request, $this->reflector); + + $response = $middleware->afterController($this, __FUNCTION__, new Response()); + $headers = $response->getHeaders(); + $this->assertEquals('test', $headers['Access-Control-Allow-Origin']); + } + + + public function testNoAnnotationNoCORSHEADER() { + $request = new Request( + array('server' => array('HTTP_ORIGIN' => 'test')) + ); + $middleware = new CORSMiddleware($request, $this->reflector); + + $response = $middleware->afterController($this, __FUNCTION__, new Response()); + $headers = $response->getHeaders(); + $this->assertFalse(array_key_exists('Access-Control-Allow-Origin', $headers)); + } + + + /** + * @CORS + */ + public function testNoOriginHeaderNoCORSHEADER() { + $request = new Request(); + $this->reflector->reflect($this, __FUNCTION__); + $middleware = new CORSMiddleware($request, $this->reflector); + + $response = $middleware->afterController($this, __FUNCTION__, new Response()); + $headers = $response->getHeaders(); + $this->assertFalse(array_key_exists('Access-Control-Allow-Origin', $headers)); + } + + + /** + * @CORS + * @expectedException \OC\AppFramework\Middleware\Security\SecurityException + */ + public function testCorsIgnoredIfWithCredentialsHeaderPresent() { + $request = new Request( + array('server' => array('HTTP_ORIGIN' => 'test')) + ); + $this->reflector->reflect($this, __FUNCTION__); + $middleware = new CORSMiddleware($request, $this->reflector); + + $response = new Response(); + $response->addHeader('AcCess-control-Allow-Credentials ', 'TRUE'); + $response = $middleware->afterController($this, __FUNCTION__, $response); + } + +} diff --git a/tests/lib/appframework/middleware/security/SecurityMiddlewareTest.php b/tests/lib/appframework/middleware/security/SecurityMiddlewareTest.php index 19e8a68c388..6a1bbf72c13 100644 --- a/tests/lib/appframework/middleware/security/SecurityMiddlewareTest.php +++ b/tests/lib/appframework/middleware/security/SecurityMiddlewareTest.php @@ -4,7 +4,7 @@ * ownCloud - App Framework * * @author Bernhard Posselt - * @copyright 2012 Bernhard Posselt nukeawhale@gmail.com + * @copyright 2012 Bernhard Posselt <dev@bernhard-posselt.com> * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE @@ -26,6 +26,7 @@ namespace OC\AppFramework\Middleware\Security; use OC\AppFramework\Http; use OC\AppFramework\Http\Request; +use OC\AppFramework\Utility\ControllerMethodReflector; use OCP\AppFramework\Http\RedirectResponse; use OCP\AppFramework\Http\JSONResponse; @@ -37,14 +38,16 @@ class SecurityMiddlewareTest extends \PHPUnit_Framework_TestCase { private $secException; private $secAjaxException; private $request; + private $reader; public function setUp() { $api = $this->getMock('OC\AppFramework\DependencyInjection\DIContainer', array(), array('test')); $this->controller = $this->getMock('OCP\AppFramework\Controller', array(), array($api, new Request())); + $this->reader = new ControllerMethodReflector(); $this->request = new Request(); - $this->middleware = new SecurityMiddleware($api, $this->request); + $this->middleware = new SecurityMiddleware($api, $this->request, $this->reader); $this->secException = new SecurityException('hey', false); $this->secAjaxException = new SecurityException('hey', true); } @@ -68,7 +71,8 @@ class SecurityMiddlewareTest extends \PHPUnit_Framework_TestCase { $api->expects($this->any())->method('getServer') ->will($this->returnValue($serverMock)); - $sec = new SecurityMiddleware($api, $this->request); + $sec = new SecurityMiddleware($api, $this->request, $this->reader); + $this->reader->reflect('\OC\AppFramework\Middleware\Security\SecurityMiddlewareTest', $method); $sec->beforeController('\OC\AppFramework\Middleware\Security\SecurityMiddlewareTest', $method); } @@ -99,11 +103,12 @@ class SecurityMiddlewareTest extends \PHPUnit_Framework_TestCase { ->will($this->returnValue(true)); } - $sec = new SecurityMiddleware($api, $this->request); + $sec = new SecurityMiddleware($api, $this->request, $this->reader); try { - $sec->beforeController('\OC\AppFramework\Middleware\Security\SecurityMiddlewareTest', - $method); + $controller = '\OC\AppFramework\Middleware\Security\SecurityMiddlewareTest'; + $this->reader->reflect($controller, $method); + $sec->beforeController($controller, $method); } catch (SecurityException $ex){ $this->assertEquals($status, $ex->getCode()); } @@ -184,7 +189,9 @@ class SecurityMiddlewareTest extends \PHPUnit_Framework_TestCase { ->method('isLoggedIn') ->will($this->returnValue(true)); - $sec = new SecurityMiddleware($api, $this->request); + $sec = new SecurityMiddleware($api, $this->request, $this->reader); + $this->reader->reflect('\OC\AppFramework\Middleware\Security\SecurityMiddlewareTest', + 'testNoChecks'); $sec->beforeController('\OC\AppFramework\Middleware\Security\SecurityMiddlewareTest', 'testNoChecks'); } @@ -207,7 +214,7 @@ class SecurityMiddlewareTest extends \PHPUnit_Framework_TestCase { ->will($this->returnValue(true)); } - $sec = new SecurityMiddleware($api, $this->request); + $sec = new SecurityMiddleware($api, $this->request, $this->reader); if($shouldFail){ $this->setExpectedException('\OC\AppFramework\Middleware\Security\SecurityException'); @@ -215,6 +222,7 @@ class SecurityMiddlewareTest extends \PHPUnit_Framework_TestCase { $this->setExpectedException(null); } + $this->reader->reflect('\OC\AppFramework\Middleware\Security\SecurityMiddlewareTest', $method); $sec->beforeController('\OC\AppFramework\Middleware\Security\SecurityMiddlewareTest', $method); } @@ -230,7 +238,8 @@ class SecurityMiddlewareTest extends \PHPUnit_Framework_TestCase { ->method('passesCSRFCheck') ->will($this->returnValue(false)); - $sec = new SecurityMiddleware($api, $request); + $sec = new SecurityMiddleware($api, $request, $this->reader); + $this->reader->reflect('\OC\AppFramework\Middleware\Security\SecurityMiddlewareTest', 'testCsrfCheck'); $sec->beforeController('\OC\AppFramework\Middleware\Security\SecurityMiddlewareTest', 'testCsrfCheck'); } @@ -246,7 +255,8 @@ class SecurityMiddlewareTest extends \PHPUnit_Framework_TestCase { ->method('passesCSRFCheck') ->will($this->returnValue(false)); - $sec = new SecurityMiddleware($api, $request); + $sec = new SecurityMiddleware($api, $request, $this->reader); + $this->reader->reflect('\OC\AppFramework\Middleware\Security\SecurityMiddlewareTest', 'testNoCsrfCheck'); $sec->beforeController('\OC\AppFramework\Middleware\Security\SecurityMiddlewareTest', 'testNoCsrfCheck'); } @@ -261,7 +271,8 @@ class SecurityMiddlewareTest extends \PHPUnit_Framework_TestCase { ->method('passesCSRFCheck') ->will($this->returnValue(true)); - $sec = new SecurityMiddleware($api, $request); + $sec = new SecurityMiddleware($api, $request, $this->reader); + $this->reader->reflect('\OC\AppFramework\Middleware\Security\SecurityMiddlewareTest', 'testFailCsrfCheck'); $sec->beforeController('\OC\AppFramework\Middleware\Security\SecurityMiddlewareTest', 'testFailCsrfCheck'); } @@ -318,7 +329,7 @@ class SecurityMiddlewareTest extends \PHPUnit_Framework_TestCase { $this->request = new Request( array('server' => array('HTTP_ACCEPT' => 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8'))); - $this->middleware = new SecurityMiddleware($api, $this->request); + $this->middleware = new SecurityMiddleware($api, $this->request, $this->reader); $response = $this->middleware->afterException($this->controller, 'test', $this->secException); diff --git a/tests/lib/appframework/utility/ControllerMethodReflectorTest.php b/tests/lib/appframework/utility/ControllerMethodReflectorTest.php new file mode 100644 index 00000000000..cccc7688884 --- /dev/null +++ b/tests/lib/appframework/utility/ControllerMethodReflectorTest.php @@ -0,0 +1,115 @@ +<?php + +/** + * ownCloud - App Framework + * + * @author Bernhard Posselt + * @copyright 2012 Bernhard Posselt <dev@bernhard-posselt.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + * License as published by the Free Software Foundation; either + * version 3 of the License, or any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU AFFERO GENERAL PUBLIC LICENSE for more details. + * + * You should have received a copy of the GNU Affero General Public + * License along with this library. If not, see <http://www.gnu.org/licenses/>. + * + */ + + +namespace OC\AppFramework\Utility; + + +class ControllerMethodReflectorTest extends \PHPUnit_Framework_TestCase { + + + /** + * @Annotation + */ + public function testReadAnnotation(){ + $reader = new ControllerMethodReflector(); + $reader->reflect( + '\OC\AppFramework\Utility\ControllerMethodReflectorTest', + 'testReadAnnotation' + ); + + $this->assertTrue($reader->hasAnnotation('Annotation')); + } + + + /** + * @Annotation + * @param test + */ + public function testReadAnnotationNoLowercase(){ + $reader = new ControllerMethodReflector(); + $reader->reflect( + '\OC\AppFramework\Utility\ControllerMethodReflectorTest', + 'testReadAnnotationNoLowercase' + ); + + $this->assertTrue($reader->hasAnnotation('Annotation')); + $this->assertFalse($reader->hasAnnotation('param')); + } + + + /** + * @Annotation + * @param int $test + */ + public function testReadTypeIntAnnotations(){ + $reader = new ControllerMethodReflector(); + $reader->reflect( + '\OC\AppFramework\Utility\ControllerMethodReflectorTest', + 'testReadTypeIntAnnotations' + ); + + $this->assertEquals('int', $reader->getType('test')); + } + + + /** + * @Annotation + * @param double $test something special + */ + public function testReadTypeDoubleAnnotations(){ + $reader = new ControllerMethodReflector(); + $reader->reflect( + '\OC\AppFramework\Utility\ControllerMethodReflectorTest', + 'testReadTypeDoubleAnnotations' + ); + + $this->assertEquals('double', $reader->getType('test')); + } + + + public function arguments($arg, $arg2) {} + public function testReflectParameters() { + $reader = new ControllerMethodReflector(); + $reader->reflect( + '\OC\AppFramework\Utility\ControllerMethodReflectorTest', + 'arguments' + ); + + $this->assertEquals(array('arg', 'arg2'), $reader->getParameters()); + } + + + public function arguments2($arg) {} + public function testReflectParameters2() { + $reader = new ControllerMethodReflector(); + $reader->reflect( + '\OC\AppFramework\Utility\ControllerMethodReflectorTest', + 'arguments2' + ); + + $this->assertEquals(array('arg',), $reader->getParameters()); + } + + +} diff --git a/tests/lib/group.php b/tests/lib/group.php index 26232187c36..724e723b187 100644 --- a/tests/lib/group.php +++ b/tests/lib/group.php @@ -4,8 +4,8 @@ * * @author Robin Appelman * @author Bernhard Posselt - * @copyright 2012 Robin Appelman icewind@owncloud.com - * @copyright 2012 Bernhard Posselt nukeawhale@gmail.com + * @copyright 2012 Robin Appelman <icewind@owncloud.com> + * @copyright 2012 Bernhard Posselt <dev@bernhard-posselt.com> * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE diff --git a/tests/lib/helper.php b/tests/lib/helper.php index 5663df7c9ae..59db30a73f6 100644 --- a/tests/lib/helper.php +++ b/tests/lib/helper.php @@ -120,15 +120,15 @@ class Test_Helper extends PHPUnit_Framework_TestCase { $this->assertEquals($result, $expected); } - function testIssubdirectory() { - $result = OC_Helper::issubdirectory("./data/", "/anotherDirectory/"); + function testIsSubDirectory() { + $result = OC_Helper::isSubDirectory("./data/", "/anotherDirectory/"); $this->assertFalse($result); - $result = OC_Helper::issubdirectory("./data/", "./data/"); + $result = OC_Helper::isSubDirectory("./data/", "./data/"); $this->assertTrue($result); mkdir("data/TestSubdirectory", 0777); - $result = OC_Helper::issubdirectory("data/TestSubdirectory/", "data"); + $result = OC_Helper::isSubDirectory("data/TestSubdirectory/", "data"); rmdir("data/TestSubdirectory"); $this->assertTrue($result); } diff --git a/tests/lib/logger.php b/tests/lib/logger.php new file mode 100644 index 00000000000..7d5d4049b28 --- /dev/null +++ b/tests/lib/logger.php @@ -0,0 +1,40 @@ +<?php +/** + * Copyright (c) 2014 Thomas Müller <thomas.mueller@tmit.eu> + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +namespace Test; + +use OC\Log; + +class Logger extends \PHPUnit_Framework_TestCase { + /** + * @var \OCP\ILogger + */ + private $logger; + static private $logs = array(); + + public function setUp() { + self::$logs = array(); + $this->logger = new Log($this); + } + + public function testInterpolation() { + $logger = $this->logger; + $logger->info('{Message {nothing} {user} {foo.bar} a}', array('user' => 'Bob', 'foo.bar' => 'Bar')); + + $expected = array('1 {Message {nothing} Bob Bar a}'); + $this->assertEquals($expected, $this->getLogs()); + } + + private function getLogs() { + return self::$logs; + } + + public static function write($app, $message, $level) { + self::$logs[]= "$level $message"; + } +} diff --git a/tests/lib/template.php b/tests/lib/template.php index eedf688721d..819d592aacf 100644 --- a/tests/lib/template.php +++ b/tests/lib/template.php @@ -3,7 +3,7 @@ * ownCloud * * @author Bernhard Posselt -* @copyright 2012 Bernhard Posselt nukeawhale@gmail.com +* @copyright 2012 Bernhard Posselt <dev@bernhard-posselt.com> * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE |