summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.htaccess4
-rw-r--r--apps/dav/appinfo/database.xml387
-rw-r--r--apps/dav/appinfo/info.xml2
-rw-r--r--apps/dav/appinfo/register_command.php2
-rw-r--r--apps/dav/command/createcalendar.php52
-rw-r--r--apps/dav/lib/caldav/caldavbackend.php1175
-rw-r--r--apps/dav/lib/connector/sabre/directory.php8
-rw-r--r--apps/dav/lib/connector/sabre/exception/forbidden.php64
-rw-r--r--apps/dav/lib/connector/sabre/file.php12
-rw-r--r--apps/dav/lib/connector/sabre/filesplugin.php20
-rw-r--r--apps/dav/lib/connector/sabre/lockplugin.php16
-rw-r--r--apps/dav/lib/connector/sabre/node.php11
-rw-r--r--apps/dav/lib/connector/sabre/objecttree.php6
-rw-r--r--apps/dav/lib/connector/sabre/serverfactory.php2
-rw-r--r--apps/dav/lib/rootcollection.php19
-rw-r--r--apps/dav/lib/server.php17
-rw-r--r--apps/dav/tests/unit/caldav/caldavbackendtest.php348
-rw-r--r--apps/dav/tests/unit/connector/sabre/directory.php21
-rw-r--r--apps/dav/tests/unit/connector/sabre/exception/forbiddentest.php44
-rw-r--r--apps/dav/tests/unit/connector/sabre/exception/invalidpathtest.php10
-rw-r--r--apps/dav/tests/unit/connector/sabre/file.php46
-rw-r--r--apps/dav/tests/unit/connector/sabre/filesplugin.php62
-rw-r--r--apps/files/appinfo/app.php4
-rw-r--r--apps/files/appinfo/routes.php7
-rw-r--r--apps/files/controller/viewcontroller.php224
-rw-r--r--apps/files/index.php161
-rw-r--r--apps/files/l10n/hu_HU.js2
-rw-r--r--apps/files/l10n/hu_HU.json2
-rw-r--r--apps/files/l10n/nl.js2
-rw-r--r--apps/files/l10n/nl.json2
-rw-r--r--apps/files/tests/controller/ViewControllerTest.php250
-rw-r--r--apps/files_sharing/api/remote.php2
-rw-r--r--apps/files_sharing/lib/external/storage.php7
-rw-r--r--apps/provisioning_api/appinfo/routes.php3
-rw-r--r--apps/provisioning_api/lib/groups.php43
-rw-r--r--apps/provisioning_api/tests/groupstest.php377
-rw-r--r--apps/user_ldap/l10n/tr.js1
-rw-r--r--apps/user_ldap/l10n/tr.json1
-rw-r--r--build/integration/features/bootstrap/FeatureContext.php76
-rw-r--r--core/l10n/hu_HU.js1
-rw-r--r--core/l10n/hu_HU.json1
-rw-r--r--core/l10n/nl.js1
-rw-r--r--core/l10n/nl.json1
-rw-r--r--core/l10n/ru.js2
-rw-r--r--core/l10n/ru.json2
-rw-r--r--lib/l10n/fr.js2
-rw-r--r--lib/l10n/fr.json2
-rw-r--r--lib/l10n/hu_HU.js12
-rw-r--r--lib/l10n/hu_HU.json12
-rw-r--r--lib/private/appframework/dependencyinjection/dicontainer.php4
-rw-r--r--lib/private/cache/file.php2
-rw-r--r--lib/private/files.php5
-rw-r--r--lib/private/files/cache/scanner.php2
-rw-r--r--lib/private/files/view.php25
-rw-r--r--lib/private/log/owncloud.php7
-rw-r--r--lib/private/memcache/memcached.php38
-rw-r--r--lib/private/notification/action.php50
-rw-r--r--lib/private/notification/iaction.php28
-rw-r--r--lib/private/notification/inotification.php14
-rw-r--r--lib/private/notification/notification.php46
-rw-r--r--lib/public/files/forbiddenexception.php55
-rw-r--r--lib/public/files/node.php8
-rw-r--r--lib/public/files/storage.php6
-rw-r--r--settings/l10n/zh_TW.js2
-rw-r--r--settings/l10n/zh_TW.json2
-rw-r--r--settings/templates/admin.php19
-rw-r--r--tests/lib/helper.php28
-rw-r--r--tests/lib/notification/actiontest.php49
-rw-r--r--tests/lib/notification/notificationtest.php86
-rw-r--r--tests/lib/urlgenerator.php10
70 files changed, 3458 insertions, 556 deletions
diff --git a/.htaccess b/.htaccess
index 918fcbd18e8..615f5e7fc96 100644
--- a/.htaccess
+++ b/.htaccess
@@ -38,8 +38,8 @@
RewriteRule .* - [env=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
RewriteRule ^\.well-known/host-meta /public.php?service=host-meta [QSA,L]
RewriteRule ^\.well-known/host-meta\.json /public.php?service=host-meta-json [QSA,L]
- RewriteRule ^\.well-known/carddav /remote.php/carddav/ [R=301,L]
- RewriteRule ^\.well-known/caldav /remote.php/caldav/ [R=301,L]
+ RewriteRule ^\.well-known/carddav /remote.php/dav/ [R=301,L]
+ RewriteRule ^\.well-known/caldav /remote.php/dav/ [R=301,L]
RewriteRule ^remote/(.*) remote.php [QSA,L]
RewriteRule ^(build|tests|config|lib|3rdparty|templates)/.* - [R=404,L]
RewriteRule ^(\.|autotest|occ|issue|indie|db_|console).* - [R=404,L]
diff --git a/apps/dav/appinfo/database.xml b/apps/dav/appinfo/database.xml
index f3fd5079949..5e2dad097e4 100644
--- a/apps/dav/appinfo/database.xml
+++ b/apps/dav/appinfo/database.xml
@@ -183,4 +183,391 @@ CREATE TABLE addressbookchanges (
</declaration>
</table>
+
+<!--
+CREATE TABLE calendarobjects (
+ id INT(11) UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
+ calendardata MEDIUMBLOB,
+ uri VARBINARY(200),
+ calendarid INTEGER UNSIGNED NOT NULL,
+ lastmodified INT(11) UNSIGNED,
+ etag VARBINARY(32),
+ size INT(11) UNSIGNED NOT NULL,
+ componenttype VARBINARY(8),
+ firstoccurence INT(11) UNSIGNED,
+ lastoccurence INT(11) UNSIGNED,
+ uid VARBINARY(200),
+ UNIQUE(calendarid, uri)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
+-->
+<table>
+ <name>*dbprefix*calendarobjects</name>
+ <declaration>
+ <field>
+ <name>id</name>
+ <type>integer</type>
+ <default>0</default>
+ <notnull>true</notnull>
+ <autoincrement>1</autoincrement>
+ <unsigned>true</unsigned>
+ <length>11</length>
+ </field>
+ <field>
+ <name>calendardata</name>
+ <type>blob</type>
+ </field>
+ <field>
+ <name>uri</name>
+ <type>text</type>
+ </field>
+ <field>
+ <name>calendarid</name>
+ <type>integer</type>
+ <unsigned>true</unsigned>
+ <notnull>true</notnull>
+ </field>
+ <field>
+ <name>lastmodified</name>
+ <type>integer</type>
+ <unsigned>true</unsigned>
+ </field>
+ <field>
+ <name>etag</name>
+ <type>text</type>
+ <length>32</length>
+ </field>
+ <field>
+ <name>size</name>
+ <type>integer</type>
+ <notnull>true</notnull>
+ <unsigned>true</unsigned>
+ <length>11</length>
+ </field>
+ <field>
+ <name>componenttype</name>
+ <type>text</type>
+ </field>
+ <field>
+ <name>firstoccurence</name>
+ <type>integer</type>
+ <unsigned>true</unsigned>
+ </field>
+ <field>
+ <name>lastoccurence</name>
+ <type>integer</type>
+ <unsigned>true</unsigned>
+ </field>
+ <field>
+ <name>uid</name>
+ <type>text</type>
+ </field>
+ <index>
+ <name>calobjects_index</name>
+ <unique>true</unique>
+ <field>
+ <name>calendarid</name>
+ </field>
+ <field>
+ <name>uri</name>
+ </field>
+ </index>
+ </declaration>
+</table>
+ <!--
+ CREATE TABLE calendars (
+ id INTEGER UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
+ principaluri VARBINARY(100),
+ displayname VARCHAR(100),
+ uri VARBINARY(200),
+ synctoken INTEGER UNSIGNED NOT NULL DEFAULT '1',
+ description TEXT,
+ calendarorder INT(11) UNSIGNED NOT NULL DEFAULT '0',
+ calendarcolor VARBINARY(10),
+ timezone TEXT,
+ components VARBINARY(20),
+ transparent TINYINT(1) NOT NULL DEFAULT '0',
+ UNIQUE(principaluri, uri)
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
+ -->
+<table>
+ <name>*dbprefix*calendars</name>
+ <declaration>
+ <field>
+ <name>id</name>
+ <type>integer</type>
+ <default>0</default>
+ <notnull>true</notnull>
+ <autoincrement>1</autoincrement>
+ <unsigned>true</unsigned>
+ <length>11</length>
+ </field>
+ <field>
+ <name>principaluri</name>
+ <type>text</type>
+ </field>
+ <field>
+ <name>displayname</name>
+ <type>text</type>
+ </field>
+ <field>
+ <name>uri</name>
+ <type>text</type>
+ </field>
+ <field>
+ <name>synctoken</name>
+ <type>integer</type>
+ <default>1</default>
+ <notnull>true</notnull>
+ <unsigned>true</unsigned>
+ </field>
+ <field>
+ <name>description</name>
+ <type>text</type>
+ </field>
+ <field>
+ <name>calendarorder</name>
+ <type>integer</type>
+ <default>0</default>
+ <notnull>true</notnull>
+ <unsigned>true</unsigned>
+ </field>
+ <field>
+ <name>calendarcolor</name>
+ <type>text</type>
+ </field>
+ <field>
+ <name>timezone</name>
+ <type>text</type>
+ </field>
+ <field>
+ <name>components</name>
+ <type>text</type>
+ </field>
+ <field>
+ <name>transparent</name>
+ <type>integer</type>
+ <length>1</length>
+ <notnull>true</notnull>
+ <default>0</default>
+ </field>
+ <index>
+ <name>calendars_index</name>
+ <unique>true</unique>
+ <field>
+ <name>principaluri</name>
+ </field>
+ <field>
+ <name>uri</name>
+ </field>
+ </index>
+ </declaration>
+</table>
+ <!--
+ CREATE TABLE calendarchanges (
+ id INT(11) UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
+ uri VARBINARY(200) NOT NULL,
+ synctoken INT(11) UNSIGNED NOT NULL,
+ calendarid INT(11) UNSIGNED NOT NULL,
+ operation TINYINT(1) NOT NULL,
+ INDEX calendarid_synctoken (calendarid, synctoken)
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
+ -->
+ <table>
+ <name>*dbprefix*calendarchanges</name>
+ <declaration>
+ <field>
+ <name>id</name>
+ <type>integer</type>
+ <default>0</default>
+ <notnull>true</notnull>
+ <autoincrement>1</autoincrement>
+ <unsigned>true</unsigned>
+ <length>11</length>
+ </field>
+ <field>
+ <name>uri</name>
+ <type>text</type>
+ </field>
+ <field>
+ <name>synctoken</name>
+ <type>integer</type>
+ <default>1</default>
+ <notnull>true</notnull>
+ <unsigned>true</unsigned>
+ </field>
+ <field>
+ <name>calendarid</name>
+ <type>integer</type>
+ <notnull>true</notnull>
+ </field>
+ <field>
+ <name>operation</name>
+ <type>integer</type>
+ <notnull>true</notnull>
+ <length>1</length>
+ </field>
+
+ <index>
+ <name>calendarid_synctoken</name>
+ <field>
+ <name>calendarid</name>
+ </field>
+ <field>
+ <name>synctoken</name>
+ </field>
+ </index>
+
+ </declaration>
+ </table>
+
+ <!--
+ CREATE TABLE calendarsubscriptions (
+ id INT(11) UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
+ uri VARBINARY(200) NOT NULL,
+ principaluri VARBINARY(100) NOT NULL,
+ source TEXT,
+ displayname VARCHAR(100),
+ refreshrate VARCHAR(10),
+ calendarorder INT(11) UNSIGNED NOT NULL DEFAULT '0',
+ calendarcolor VARBINARY(10),
+ striptodos TINYINT(1) NULL,
+ stripalarms TINYINT(1) NULL,
+ stripattachments TINYINT(1) NULL,
+ lastmodified INT(11) UNSIGNED,
+ UNIQUE(principaluri, uri)
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
+ -->
+<table>
+ <name>*dbprefix*calendarsubscriptions</name>
+ <declaration>
+ <field>
+ <name>id</name>
+ <type>integer</type>
+ <default>0</default>
+ <notnull>true</notnull>
+ <autoincrement>1</autoincrement>
+ <unsigned>true</unsigned>
+ <length>11</length>
+ </field>
+ <field>
+ <name>uri</name>
+ <type>text</type>
+ </field>
+ <field>
+ <name>principaluri</name>
+ <type>text</type>
+ </field>
+ <field>
+ <name>source</name>
+ <type>text</type>
+ </field>
+ <field>
+ <name>displayname</name>
+ <type>text</type>
+ <length>100</length>
+ </field>
+ <field>
+ <name>refreshrate</name>
+ <type>text</type>
+ <length>10</length>
+ </field>
+ <field>
+ <name>calendarorder</name>
+ <type>integer</type>
+ <default>0</default>
+ <notnull>true</notnull>
+ <unsigned>true</unsigned>
+ </field>
+ <field>
+ <name>calendarcolor</name>
+ <type>text</type>
+ </field>
+ <field>
+ <name>striptodos</name>
+ <type>integer</type>
+ <length>1</length>
+ </field>
+ <field>
+ <name>stripalarms</name>
+ <type>integer</type>
+ <length>1</length>
+ </field>
+ <field>
+ <name>stripattachments</name>
+ <type>integer</type>
+ <length>1</length>
+ </field>
+ <field>
+ <name>lastmodified</name>
+ <type>integer</type>
+ <unsigned>true</unsigned>
+ </field>
+ <index>
+ <name>calsub_index</name>
+ <unique>true</unique>
+ <field>
+ <name>principaluri</name>
+ </field>
+ <field>
+ <name>uri</name>
+ </field>
+ </index>
+ </declaration>
+</table>
+ <!--
+ CREATE TABLE schedulingobjects (
+ id INT(11) UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
+ principaluri VARBINARY(255),
+ calendardata MEDIUMBLOB,
+ uri VARBINARY(200),
+ lastmodified INT(11) UNSIGNED,
+ etag VARBINARY(32),
+ size INT(11) UNSIGNED NOT NULL
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
+ -->
+
+ <table>
+ <name>*dbprefix*schedulingobjects</name>
+ <declaration>
+ <field>
+ <name>id</name>
+ <type>integer</type>
+ <default>0</default>
+ <notnull>true</notnull>
+ <autoincrement>1</autoincrement>
+ <unsigned>true</unsigned>
+ <length>11</length>
+ </field>
+ <field>
+ <name>principaluri</name>
+ <type>text</type>
+ </field>
+ <field>
+ <name>calendardata</name>
+ <type>blob</type>
+ </field>
+ <field>
+ <name>uri</name>
+ <type>text</type>
+ </field>
+ <field>
+ <name>lastmodified</name>
+ <type>integer</type>
+ <unsigned>true</unsigned>
+ </field>
+ <field>
+ <name>etag</name>
+ <type>text</type>
+ <length>32</length>
+ </field>
+ <field>
+ <name>size</name>
+ <type>integer</type>
+ <notnull>true</notnull>
+ <unsigned>true</unsigned>
+ <length>11</length>
+ </field>
+
+ </declaration>
+ </table>
</database>
diff --git a/apps/dav/appinfo/info.xml b/apps/dav/appinfo/info.xml
index 11025115691..5f681e784fc 100644
--- a/apps/dav/appinfo/info.xml
+++ b/apps/dav/appinfo/info.xml
@@ -5,7 +5,7 @@
<description>ownCloud WebDAV endpoint</description>
<licence>AGPL</licence>
<author>owncloud.org</author>
- <version>0.1.2</version>
+ <version>0.1.3</version>
<requiremin>9.0</requiremin>
<shipped>true</shipped>
<standalone/>
diff --git a/apps/dav/appinfo/register_command.php b/apps/dav/appinfo/register_command.php
index c996dd44063..7d57b944fb2 100644
--- a/apps/dav/appinfo/register_command.php
+++ b/apps/dav/appinfo/register_command.php
@@ -1,8 +1,10 @@
<?php
use OCA\DAV\Command\CreateAddressBook;
+use OCA\DAV\Command\CreateCalendar;
$dbConnection = \OC::$server->getDatabaseConnection();
$userManager = OC::$server->getUserManager();
/** @var Symfony\Component\Console\Application $application */
$application->add(new CreateAddressBook($userManager, $dbConnection));
+$application->add(new CreateCalendar($userManager, $dbConnection));
diff --git a/apps/dav/command/createcalendar.php b/apps/dav/command/createcalendar.php
new file mode 100644
index 00000000000..da4f248e51d
--- /dev/null
+++ b/apps/dav/command/createcalendar.php
@@ -0,0 +1,52 @@
+<?php
+
+namespace OCA\DAV\Command;
+
+use OCA\DAV\CalDAV\CalDavBackend;
+use OCP\IDBConnection;
+use OCP\IUserManager;
+use Symfony\Component\Console\Command\Command;
+use Symfony\Component\Console\Input\InputArgument;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Output\OutputInterface;
+
+class CreateCalendar extends Command {
+
+ /** @var IUserManager */
+ protected $userManager;
+
+ /** @var \OCP\IDBConnection */
+ protected $dbConnection;
+
+ /**
+ * @param IUserManager $userManager
+ * @param IDBConnection $dbConnection
+ */
+ function __construct(IUserManager $userManager, IDBConnection $dbConnection) {
+ parent::__construct();
+ $this->userManager = $userManager;
+ $this->dbConnection = $dbConnection;
+ }
+
+ protected function configure() {
+ $this
+ ->setName('dav:create-calendar')
+ ->setDescription('Create a dav calendar')
+ ->addArgument('user',
+ InputArgument::REQUIRED,
+ 'User for whom the calendar will be created')
+ ->addArgument('name',
+ InputArgument::REQUIRED,
+ 'Name of the calendar');
+ }
+
+ protected function execute(InputInterface $input, OutputInterface $output) {
+ $user = $input->getArgument('user');
+ if (!$this->userManager->userExists($user)) {
+ throw new \InvalidArgumentException("User <$user> in unknown.");
+ }
+ $name = $input->getArgument('name');
+ $caldav = new CalDavBackend($this->dbConnection);
+ $caldav->createCalendar("principals/$user", $name, []);
+ }
+}
diff --git a/apps/dav/lib/caldav/caldavbackend.php b/apps/dav/lib/caldav/caldavbackend.php
new file mode 100644
index 00000000000..08a2a70c56d
--- /dev/null
+++ b/apps/dav/lib/caldav/caldavbackend.php
@@ -0,0 +1,1175 @@
+<?php
+/**
+ * @author Thomas Müller <thomas.mueller@tmit.eu>
+ *
+ * @copyright Copyright (c) 2015, ownCloud, Inc.
+ * @license AGPL-3.0
+ *
+ * This code is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License, version 3,
+ * along with this program. If not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+namespace OCA\DAV\CalDAV;
+
+use Sabre\CalDAV\Backend\AbstractBackend;
+use Sabre\CalDAV\Backend\SchedulingSupport;
+use Sabre\CalDAV\Backend\SubscriptionSupport;
+use Sabre\CalDAV\Backend\SyncSupport;
+use Sabre\CalDAV\Plugin;
+use Sabre\CalDAV\Property\ScheduleCalendarTransp;
+use Sabre\CalDAV\Property\SupportedCalendarComponentSet;
+use Sabre\DAV;
+use Sabre\DAV\Exception\Forbidden;
+use Sabre\VObject\DateTimeParser;
+use Sabre\VObject\Reader;
+use Sabre\VObject\RecurrenceIterator;
+
+/**
+ * Class CalDavBackend
+ *
+ * Code is heavily inspired by https://github.com/fruux/sabre-dav/blob/master/lib/CalDAV/Backend/PDO.php
+ *
+ * @package OCA\DAV\CalDAV
+ */
+class CalDavBackend extends AbstractBackend implements SyncSupport, SubscriptionSupport, SchedulingSupport {
+
+ /**
+ * We need to specify a max date, because we need to stop *somewhere*
+ *
+ * On 32 bit system the maximum for a signed integer is 2147483647, so
+ * MAX_DATE cannot be higher than date('Y-m-d', 2147483647) which results
+ * in 2038-01-19 to avoid problems when the date is converted
+ * to a unix timestamp.
+ */
+ const MAX_DATE = '2038-01-01';
+
+ /**
+ * List of CalDAV properties, and how they map to database fieldnames
+ * Add your own properties by simply adding on to this array.
+ *
+ * Note that only string-based properties are supported here.
+ *
+ * @var array
+ */
+ public $propertyMap = [
+ '{DAV:}displayname' => 'displayname',
+ '{urn:ietf:params:xml:ns:caldav}calendar-description' => 'description',
+ '{urn:ietf:params:xml:ns:caldav}calendar-timezone' => 'timezone',
+ '{http://apple.com/ns/ical/}calendar-order' => 'calendarorder',
+ '{http://apple.com/ns/ical/}calendar-color' => 'calendarcolor',
+ ];
+
+ /**
+ * List of subscription properties, and how they map to database fieldnames.
+ *
+ * @var array
+ */
+ public $subscriptionPropertyMap = [
+ '{DAV:}displayname' => 'displayname',
+ '{http://apple.com/ns/ical/}refreshrate' => 'refreshrate',
+ '{http://apple.com/ns/ical/}calendar-order' => 'calendarorder',
+ '{http://apple.com/ns/ical/}calendar-color' => 'calendarcolor',
+ '{http://calendarserver.org/ns/}subscribed-strip-todos' => 'striptodos',
+ '{http://calendarserver.org/ns/}subscribed-strip-alarms' => 'stripalarms',
+ '{http://calendarserver.org/ns/}subscribed-strip-attachments' => 'stripattachments',
+ ];
+
+ public function __construct(\OCP\IDBConnection $db) {
+ $this->db = $db;
+ }
+
+ /**
+ * Returns a list of calendars for a principal.
+ *
+ * Every project is an array with the following keys:
+ * * id, a unique id that will be used by other functions to modify the
+ * calendar. This can be the same as the uri or a database key.
+ * * uri, which the basename of the uri with which the calendar is
+ * accessed.
+ * * principaluri. The owner of the calendar. Almost always the same as
+ * principalUri passed to this method.
+ *
+ * Furthermore it can contain webdav properties in clark notation. A very
+ * common one is '{DAV:}displayname'.
+ *
+ * Many clients also require:
+ * {urn:ietf:params:xml:ns:caldav}supported-calendar-component-set
+ * For this property, you can just return an instance of
+ * Sabre\CalDAV\Property\SupportedCalendarComponentSet.
+ *
+ * If you return {http://sabredav.org/ns}read-only and set the value to 1,
+ * ACL will automatically be put in read-only mode.
+ *
+ * @param string $principalUri
+ * @return array
+ */
+ function getCalendarsForUser($principalUri) {
+ $fields = array_values($this->propertyMap);
+ $fields[] = 'id';
+ $fields[] = 'uri';
+ $fields[] = 'synctoken';
+ $fields[] = 'components';
+ $fields[] = 'principaluri';
+ $fields[] = 'transparent';
+
+ // Making fields a comma-delimited list
+ $query = $this->db->getQueryBuilder();
+ $query->select($fields)->from('calendars')
+ ->where($query->expr()->eq('principaluri', $query->createNamedParameter($principalUri)))
+ ->orderBy('calendarorder', 'ASC');
+ $stmt = $query->execute();
+
+ $calendars = [];
+ while($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
+
+ $components = [];
+ if ($row['components']) {
+ $components = explode(',',$row['components']);
+ }
+
+ $calendar = [
+ 'id' => $row['id'],
+ 'uri' => $row['uri'],
+ 'principaluri' => $row['principaluri'],
+ '{' . Plugin::NS_CALENDARSERVER . '}getctag' => 'http://sabre.io/ns/sync/' . ($row['synctoken']?$row['synctoken']:'0'),
+ '{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
+ '{' . Plugin::NS_CALDAV . '}supported-calendar-component-set' => new SupportedCalendarComponentSet($components),
+ '{' . Plugin::NS_CALDAV . '}schedule-calendar-transp' => new ScheduleCalendarTransp($row['transparent']?'transparent':'opaque'),
+ ];
+
+ foreach($this->propertyMap as $xmlName=>$dbName) {
+ $calendar[$xmlName] = $row[$dbName];
+ }
+
+ $calendars[] = $calendar;
+ }
+
+ return $calendars;
+ }
+
+ /**
+ * Creates a new calendar for a principal.
+ *
+ * If the creation was a success, an id must be returned that can be used to reference
+ * this calendar in other methods, such as updateCalendar.
+ *
+ * @param string $principalUri
+ * @param string $calendarUri
+ * @param array $properties
+ * @return void
+ */
+ function createCalendar($principalUri, $calendarUri, array $properties) {
+ $values = [
+ 'principaluri' => $principalUri,
+ 'uri' => $calendarUri,
+ 'synctoken' => 1,
+ 'transparent' => 0,
+ 'components' => 'VEVENT,VTODO',
+ 'displayname' => $calendarUri
+ ];
+
+ // Default value
+ $sccs = '{urn:ietf:params:xml:ns:caldav}supported-calendar-component-set';
+ if (isset($properties[$sccs])) {
+ if (!($properties[$sccs] instanceof SupportedCalendarComponentSet)) {
+ throw new DAV\Exception('The ' . $sccs . ' property must be of type: \Sabre\CalDAV\Property\SupportedCalendarComponentSet');
+ }
+ $values['components'] = implode(',',$properties[$sccs]->getValue());
+ }
+ $transp = '{' . Plugin::NS_CALDAV . '}schedule-calendar-transp';
+ if (isset($properties[$transp])) {
+ $values['transparent'] = $properties[$transp]->getValue()==='transparent';
+ }
+
+ foreach($this->propertyMap as $xmlName=>$dbName) {
+ if (isset($properties[$xmlName])) {
+ $values[$dbName] = $properties[$xmlName];
+ }
+ }
+
+ $query = $this->db->getQueryBuilder();
+ $query->insert('calendars');
+ foreach($values as $column => $value) {
+ $query->setValue($column, $query->createNamedParameter($value));
+ }
+ $query->execute();
+ }
+
+ /**
+ * Updates properties for a calendar.
+ *
+ * The list of mutations is stored in a Sabre\DAV\PropPatch object.
+ * To do the actual updates, you must tell this object which properties
+ * you're going to process with the handle() method.
+ *
+ * Calling the handle method is like telling the PropPatch object "I
+ * promise I can handle updating this property".
+ *
+ * Read the PropPatch documentation for more info and examples.
+ *
+ * @param string $path
+ * @param \Sabre\DAV\PropPatch $propPatch
+ * @return void
+ */
+ function updateCalendar($calendarId, \Sabre\DAV\PropPatch $propPatch) {
+ $supportedProperties = array_keys($this->propertyMap);
+ $supportedProperties[] = '{' . Plugin::NS_CALDAV . '}schedule-calendar-transp';
+
+ $propPatch->handle($supportedProperties, function($mutations) use ($calendarId) {
+ $newValues = [];
+ foreach ($mutations as $propertyName => $propertyValue) {
+
+ switch ($propertyName) {
+ case '{' . Plugin::NS_CALDAV . '}schedule-calendar-transp' :
+ $fieldName = 'transparent';
+ $newValues[$fieldName] = $propertyValue->getValue() === 'transparent';
+ break;
+ default :
+ $fieldName = $this->propertyMap[$propertyName];
+ $newValues[$fieldName] = $propertyValue;
+ break;
+ }
+
+ }
+ $query = $this->db->getQueryBuilder();
+ $query->update('calendars');
+ foreach ($newValues as $fieldName => $value) {
+ $query->set($fieldName, $query->createNamedParameter($value));
+ }
+ $query->where($query->expr()->eq('id', $query->createNamedParameter($calendarId)));
+ $query->execute();
+
+ $this->addChange($calendarId, "", 2);
+
+ return true;
+ });
+ }
+
+ /**
+ * Delete a calendar and all it's objects
+ *
+ * @param mixed $calendarId
+ * @return void
+ */
+ function deleteCalendar($calendarId) {
+ $stmt = $this->db->prepare('DELETE FROM `*PREFIX*calendarobjects` WHERE `calendarid` = ?');
+ $stmt->execute([$calendarId]);
+
+ $stmt = $this->db->prepare('DELETE FROM `*PREFIX*calendars` WHERE `id` = ?');
+ $stmt->execute([$calendarId]);
+
+ $stmt = $this->db->prepare('DELETE FROM `*PREFIX*calendarchanges` WHERE `calendarid` = ?');
+ $stmt->execute([$calendarId]);
+ }
+
+ /**
+ * Returns all calendar objects within a calendar.
+ *
+ * Every item contains an array with the following keys:
+ * * calendardata - The iCalendar-compatible calendar data
+ * * uri - a unique key which will be used to construct the uri. This can
+ * be any arbitrary string, but making sure it ends with '.ics' is a
+ * good idea. This is only the basename, or filename, not the full
+ * path.
+ * * lastmodified - a timestamp of the last modification time
+ * * etag - An arbitrary string, surrounded by double-quotes. (e.g.:
+ * '"abcdef"')
+ * * size - The size of the calendar objects, in bytes.
+ * * component - optional, a string containing the type of object, such
+ * as 'vevent' or 'vtodo'. If specified, this will be used to populate
+ * the Content-Type header.
+ *
+ * Note that the etag is optional, but it's highly encouraged to return for
+ * speed reasons.
+ *
+ * The calendardata is also optional. If it's not returned
+ * 'getCalendarObject' will be called later, which *is* expected to return
+ * calendardata.
+ *
+ * If neither etag or size are specified, the calendardata will be
+ * used/fetched to determine these numbers. If both are specified the
+ * amount of times this is needed is reduced by a great degree.
+ *
+ * @param mixed $calendarId
+ * @return array
+ */
+ function getCalendarObjects($calendarId) {
+ $query = $this->db->getQueryBuilder();
+ $query->select(['id', 'uri', 'lastmodified', 'etag', 'calendarid', 'size', 'componenttype'])
+ ->from('calendarobjects')
+ ->where($query->expr()->eq('calendarid', $query->createNamedParameter($calendarId)));
+ $stmt = $query->execute();
+
+ $result = [];
+ foreach($stmt->fetchAll(\PDO::FETCH_ASSOC) as $row) {
+ $result[] = [
+ 'id' => $row['id'],
+ 'uri' => $row['uri'],
+ 'lastmodified' => $row['lastmodified'],
+ 'etag' => '"' . $row['etag'] . '"',
+ 'calendarid' => $row['calendarid'],
+ 'size' => (int)$row['size'],
+ 'component' => strtolower($row['componenttype']),
+ ];
+ }
+
+ return $result;
+ }
+
+ /**
+ * Returns information from a single calendar object, based on it's object
+ * uri.
+ *
+ * The object uri is only the basename, or filename and not a full path.
+ *
+ * The returned array must have the same keys as getCalendarObjects. The
+ * 'calendardata' object is required here though, while it's not required
+ * for getCalendarObjects.
+ *
+ * This method must return null if the object did not exist.
+ *
+ * @param mixed $calendarId
+ * @param string $objectUri
+ * @return array|null
+ */
+ function getCalendarObject($calendarId, $objectUri) {
+
+ $query = $this->db->getQueryBuilder();
+ $query->select(['id', 'uri', 'lastmodified', 'etag', 'calendarid', 'size', 'calendardata', 'componenttype'])
+ ->from('calendarobjects')
+ ->where($query->expr()->eq('calendarid', $query->createNamedParameter($calendarId)))
+ ->andWhere($query->expr()->eq('uri', $query->createNamedParameter($objectUri)));
+ $stmt = $query->execute();
+ $row = $stmt->fetch(\PDO::FETCH_ASSOC);
+
+ if(!$row) return null;
+
+ return [
+ 'id' => $row['id'],
+ 'uri' => $row['uri'],
+ 'lastmodified' => $row['lastmodified'],
+ 'etag' => '"' . $row['etag'] . '"',
+ 'calendarid' => $row['calendarid'],
+ 'size' => (int)$row['size'],
+ 'calendardata' => $this->readBlob($row['calendardata']),
+ 'component' => strtolower($row['componenttype']),
+ ];
+ }
+
+ /**
+ * Returns a list of calendar objects.
+ *
+ * This method should work identical to getCalendarObject, but instead
+ * return all the calendar objects in the list as an array.
+ *
+ * If the backend supports this, it may allow for some speed-ups.
+ *
+ * @param mixed $calendarId
+ * @param array $uris
+ * @return array
+ */
+ function getMultipleCalendarObjects($calendarId, array $uris) {
+ $query = $this->db->getQueryBuilder();
+ $query->select(['id', 'uri', 'lastmodified', 'etag', 'calendarid', 'size', 'calendardata', 'componenttype'])
+ ->from('calendarobjects')
+ ->where($query->expr()->eq('calendarid', $query->createNamedParameter($calendarId)))
+ ->andWhere($query->expr()->in('uri', $query->createParameter('uri')))
+ ->setParameter('uri', $uris, \Doctrine\DBAL\Connection::PARAM_STR_ARRAY);
+
+ $stmt = $query->execute();
+
+ $result = [];
+ while($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
+
+ $result[] = [
+ 'id' => $row['id'],
+ 'uri' => $row['uri'],
+ 'lastmodified' => $row['lastmodified'],
+ 'etag' => '"' . $row['etag'] . '"',
+ 'calendarid' => $row['calendarid'],
+ 'size' => (int)$row['size'],
+ 'calendardata' => $this->readBlob($row['calendardata']),
+ 'component' => strtolower($row['componenttype']),
+ ];
+
+ }
+ return $result;
+ }
+
+ /**
+ * Creates a new calendar object.
+ *
+ * The object uri is only the basename, or filename and not a full path.
+ *
+ * It is possible return an etag from this function, which will be used in
+ * the response to this PUT request. Note that the ETag must be surrounded
+ * by double-quotes.
+ *
+ * However, you should only really return this ETag if you don't mangle the
+ * calendar-data. If the result of a subsequent GET to this object is not
+ * the exact same as this request body, you should omit the ETag.
+ *
+ * @param mixed $calendarId
+ * @param string $objectUri
+ * @param string $calendarData
+ * @return string|null
+ */
+ function createCalendarObject($calendarId, $objectUri, $calendarData) {
+ $extraData = $this->getDenormalizedData($calendarData);
+
+ $query = $this->db->getQueryBuilder();
+ $query->insert('calendarobjects')
+ ->values([
+ 'calendarid' => $query->createNamedParameter($calendarId),
+ 'uri' => $query->createNamedParameter($objectUri),
+ 'calendardata' => $query->createNamedParameter($calendarData, \PDO::PARAM_LOB),
+ 'lastmodified' => $query->createNamedParameter(time()),
+ 'etag' => $query->createNamedParameter($extraData['etag']),
+ 'size' => $query->createNamedParameter($extraData['size']),
+ 'componenttype' => $query->createNamedParameter($extraData['componentType']),
+ 'firstoccurence' => $query->createNamedParameter($extraData['firstOccurence']),
+ 'lastoccurence' => $query->createNamedParameter($extraData['lastOccurence']),
+ 'uid' => $query->createNamedParameter($extraData['uid']),
+ ])
+ ->execute();
+
+ $this->addChange($calendarId, $objectUri, 1);
+
+ return '"' . $extraData['etag'] . '"';
+ }
+
+ /**
+ * Updates an existing calendarobject, based on it's uri.
+ *
+ * The object uri is only the basename, or filename and not a full path.
+ *
+ * It is possible return an etag from this function, which will be used in
+ * the response to this PUT request. Note that the ETag must be surrounded
+ * by double-quotes.
+ *
+ * However, you should only really return this ETag if you don't mangle the
+ * calendar-data. If the result of a subsequent GET to this object is not
+ * the exact same as this request body, you should omit the ETag.
+ *
+ * @param mixed $calendarId
+ * @param string $objectUri
+ * @param string $calendarData
+ * @return string|null
+ */
+ function updateCalendarObject($calendarId, $objectUri, $calendarData) {
+ $extraData = $this->getDenormalizedData($calendarData);
+
+ $query = $this->db->getQueryBuilder();
+ $query->update('calendarobjects')
+ ->set('calendardata', $query->createNamedParameter($calendarData, \PDO::PARAM_LOB))
+ ->set('lastmodified', $query->createNamedParameter(time()))
+ ->set('etag', $query->createNamedParameter($extraData['etag']))
+ ->set('size', $query->createNamedParameter($extraData['size']))
+ ->set('componenttype', $query->createNamedParameter($extraData['componentType']))
+ ->set('firstoccurence', $query->createNamedParameter($extraData['firstOccurence']))
+ ->set('lastoccurence', $query->createNamedParameter($extraData['lastOccurence']))
+ ->set('uid', $query->createNamedParameter($extraData['uid']))
+ ->where($query->expr()->eq('calendarid', $query->createNamedParameter($calendarId)))
+ ->andWhere($query->expr()->eq('uri', $query->createNamedParameter($objectUri)))
+ ->execute();
+
+ $this->addChange($calendarId, $objectUri, 2);
+
+ return '"' . $extraData['etag'] . '"';
+ }
+
+ /**
+ * Deletes an existing calendar object.
+ *
+ * The object uri is only the basename, or filename and not a full path.
+ *
+ * @param mixed $calendarId
+ * @param string $objectUri
+ * @return void
+ */
+ function deleteCalendarObject($calendarId, $objectUri) {
+ $stmt = $this->db->prepare('DELETE FROM `*PREFIX*calendarobjects` WHERE `calendarid` = ? AND `uri` = ?');
+ $stmt->execute([$calendarId, $objectUri]);
+
+ $this->addChange($calendarId, $objectUri, 3);
+ }
+
+ /**
+ * Performs a calendar-query on the contents of this calendar.
+ *
+ * The calendar-query is defined in RFC4791 : CalDAV. Using the
+ * calendar-query it is possible for a client to request a specific set of
+ * object, based on contents of iCalendar properties, date-ranges and
+ * iCalendar component types (VTODO, VEVENT).
+ *
+ * This method should just return a list of (relative) urls that match this
+ * query.
+ *
+ * The list of filters are specified as an array. The exact array is
+ * documented by Sabre\CalDAV\CalendarQueryParser.
+ *
+ * Note that it is extremely likely that getCalendarObject for every path
+ * returned from this method will be called almost immediately after. You
+ * may want to anticipate this to speed up these requests.
+ *
+ * This method provides a default implementation, which parses *all* the
+ * iCalendar objects in the specified calendar.
+ *
+ * This default may well be good enough for personal use, and calendars
+ * that aren't very large. But if you anticipate high usage, big calendars
+ * or high loads, you are strongly adviced to optimize certain paths.
+ *
+ * The best way to do so is override this method and to optimize
+ * specifically for 'common filters'.
+ *
+ * Requests that are extremely common are:
+ * * requests for just VEVENTS
+ * * requests for just VTODO
+ * * requests with a time-range-filter on either VEVENT or VTODO.
+ *
+ * ..and combinations of these requests. It may not be worth it to try to
+ * handle every possible situation and just rely on the (relatively
+ * easy to use) CalendarQueryValidator to handle the rest.
+ *
+ * Note that especially time-range-filters may be difficult to parse. A
+ * time-range filter specified on a VEVENT must for instance also handle
+ * recurrence rules correctly.
+ * A good example of how to interprete all these filters can also simply
+ * be found in Sabre\CalDAV\CalendarQueryFilter. This class is as correct
+ * as possible, so it gives you a good idea on what type of stuff you need
+ * to think of.
+ *
+ * @param mixed $calendarId
+ * @param array $filters
+ * @return array
+ */
+ function calendarQuery($calendarId, array $filters) {
+ $componentType = null;
+ $requirePostFilter = true;
+ $timeRange = null;
+
+ // if no filters were specified, we don't need to filter after a query
+ if (!$filters['prop-filters'] && !$filters['comp-filters']) {
+ $requirePostFilter = false;
+ }
+
+ // Figuring out if there's a component filter
+ if (count($filters['comp-filters']) > 0 && !$filters['comp-filters'][0]['is-not-defined']) {
+ $componentType = $filters['comp-filters'][0]['name'];
+
+ // Checking if we need post-filters
+ if (!$filters['prop-filters'] && !$filters['comp-filters'][0]['comp-filters'] && !$filters['comp-filters'][0]['time-range'] && !$filters['comp-filters'][0]['prop-filters']) {
+ $requirePostFilter = false;
+ }
+ // There was a time-range filter
+ if ($componentType == 'VEVENT' && isset($filters['comp-filters'][0]['time-range'])) {
+ $timeRange = $filters['comp-filters'][0]['time-range'];
+
+ // If start time OR the end time is not specified, we can do a
+ // 100% accurate mysql query.
+ if (!$filters['prop-filters'] && !$filters['comp-filters'][0]['comp-filters'] && !$filters['comp-filters'][0]['prop-filters'] && (!$timeRange['start'] || !$timeRange['end'])) {
+ $requirePostFilter = false;
+ }
+ }
+
+ }
+ $columns = ['uri'];
+ if ($requirePostFilter) {
+ $columns = ['uri', 'calendardata'];
+ }
+ $query = $this->db->getQueryBuilder();
+ $query->select($columns)
+ ->from('calendarobjects')
+ ->where($query->expr()->eq('calendarid', $query->createNamedParameter($calendarId)));
+
+ if ($componentType) {
+ $query->andWhere($query->expr()->eq('componenttype', $query->createNamedParameter($componentType)));
+ }
+
+ if ($timeRange && $timeRange['start']) {
+ $query->andWhere($query->expr()->gt('lastoccurence', $query->createNamedParameter($timeRange['start']->getTimeStamp())));
+ }
+ if ($timeRange && $timeRange['end']) {
+ $query->andWhere($query->expr()->lt('firstoccurence', $query->createNamedParameter($timeRange['end']->getTimeStamp())));
+ }
+
+ $stmt = $query->execute();
+
+ $result = [];
+ while($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
+ if ($requirePostFilter) {
+ if (!$this->validateFilterForObject($row, $filters)) {
+ continue;
+ }
+ }
+ $result[] = $row['uri'];
+ }
+
+ return $result;
+ }
+
+ /**
+ * Searches through all of a users calendars and calendar objects to find
+ * an object with a specific UID.
+ *
+ * This method should return the path to this object, relative to the
+ * calendar home, so this path usually only contains two parts:
+ *
+ * calendarpath/objectpath.ics
+ *
+ * If the uid is not found, return null.
+ *
+ * This method should only consider * objects that the principal owns, so
+ * any calendars owned by other principals that also appear in this
+ * collection should be ignored.
+ *
+ * @param string $principalUri
+ * @param string $uid
+ * @return string|null
+ */
+ function getCalendarObjectByUID($principalUri, $uid) {
+
+ $query = $this->db->getQueryBuilder();
+ $query->select([$query->createFunction('c.`uri` AS `calendaruri`'), $query->createFunction('co.`uri` AS `objecturi`')])
+ ->from('calendarobjects', 'co')
+ ->leftJoin('co', 'calendars', 'c', 'co.`calendarid` = c.`id`')
+ ->where($query->expr()->eq('c.principaluri', $query->createNamedParameter($principalUri)))
+ ->andWhere($query->expr()->eq('co.uid', $query->createNamedParameter($uid)));
+
+ $stmt = $query->execute();
+
+ if ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
+ return $row['calendaruri'] . '/' . $row['objecturi'];
+ }
+
+ return null;
+ }
+
+ /**
+ * The getChanges method returns all the changes that have happened, since
+ * the specified syncToken in the specified calendar.
+ *
+ * This function should return an array, such as the following:
+ *
+ * [
+ * 'syncToken' => 'The current synctoken',
+ * 'added' => [
+ * 'new.txt',
+ * ],
+ * 'modified' => [
+ * 'modified.txt',
+ * ],
+ * 'deleted' => [
+ * 'foo.php.bak',
+ * 'old.txt'
+ * ]
+ * );
+ *
+ * The returned syncToken property should reflect the *current* syncToken
+ * of the calendar, as reported in the {http://sabredav.org/ns}sync-token
+ * property This is * needed here too, to ensure the operation is atomic.
+ *
+ * If the $syncToken argument is specified as null, this is an initial
+ * sync, and all members should be reported.
+ *
+ * The modified property is an array of nodenames that have changed since
+ * the last token.
+ *
+ * The deleted property is an array with nodenames, that have been deleted
+ * from collection.
+ *
+ * The $syncLevel argument is basically the 'depth' of the report. If it's
+ * 1, you only have to report changes that happened only directly in
+ * immediate descendants. If it's 2, it should also include changes from
+ * the nodes below the child collections. (grandchildren)
+ *
+ * The $limit argument allows a client to specify how many results should
+ * be returned at most. If the limit is not specified, it should be treated
+ * as infinite.
+ *
+ * If the limit (infinite or not) is higher than you're willing to return,
+ * you should throw a Sabre\DAV\Exception\TooMuchMatches() exception.
+ *
+ * If the syncToken is expired (due to data cleanup) or unknown, you must
+ * return null.
+ *
+ * The limit is 'suggestive'. You are free to ignore it.
+ *
+ * @param string $calendarId
+ * @param string $syncToken
+ * @param int $syncLevel
+ * @param int $limit
+ * @return array
+ */
+ function getChangesForCalendar($calendarId, $syncToken, $syncLevel, $limit = null) {
+ // Current synctoken
+ $stmt = $this->db->prepare('SELECT `synctoken` FROM `*PREFIX*calendars` WHERE `id` = ?');
+ $stmt->execute([ $calendarId ]);
+ $currentToken = $stmt->fetchColumn(0);
+
+ if (is_null($currentToken)) {
+ return null;
+ }
+
+ $result = [
+ 'syncToken' => $currentToken,
+ 'added' => [],
+ 'modified' => [],
+ 'deleted' => [],
+ ];
+
+ if ($syncToken) {
+
+ $query = "SELECT `uri`, `operation` FROM `*PREFIX*calendarchanges` WHERE `synctoken` >= ? AND `synctoken` < ? AND `calendarid` = ? ORDER BY `synctoken`";
+ if ($limit>0) {
+ $query.= " `LIMIT` " . (int)$limit;
+ }
+
+ // Fetching all changes
+ $stmt = $this->db->prepare($query);
+ $stmt->execute([$syncToken, $currentToken, $calendarId]);
+
+ $changes = [];
+
+ // This loop ensures that any duplicates are overwritten, only the
+ // last change on a node is relevant.
+ while($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
+
+ $changes[$row['uri']] = $row['operation'];
+
+ }
+
+ foreach($changes as $uri => $operation) {
+
+ switch($operation) {
+ case 1 :
+ $result['added'][] = $uri;
+ break;
+ case 2 :
+ $result['modified'][] = $uri;
+ break;
+ case 3 :
+ $result['deleted'][] = $uri;
+ break;
+ }
+
+ }
+ } else {
+ // No synctoken supplied, this is the initial sync.
+ $query = "SELECT `uri` FROM `*PREFIX*calendarobjects` WHERE `calendarid` = ?";
+ $stmt = $this->db->prepare($query);
+ $stmt->execute([$calendarId]);
+
+ $result['added'] = $stmt->fetchAll(\PDO::FETCH_COLUMN);
+ }
+ return $result;
+
+ }
+
+ /**
+ * Returns a list of subscriptions for a principal.
+ *
+ * Every subscription is an array with the following keys:
+ * * id, a unique id that will be used by other functions to modify the
+ * subscription. This can be the same as the uri or a database key.
+ * * uri. This is just the 'base uri' or 'filename' of the subscription.
+ * * principaluri. The owner of the subscription. Almost always the same as
+ * principalUri passed to this method.
+ *
+ * Furthermore, all the subscription info must be returned too:
+ *
+ * 1. {DAV:}displayname
+ * 2. {http://apple.com/ns/ical/}refreshrate
+ * 3. {http://calendarserver.org/ns/}subscribed-strip-todos (omit if todos
+ * should not be stripped).
+ * 4. {http://calendarserver.org/ns/}subscribed-strip-alarms (omit if alarms
+ * should not be stripped).
+ * 5. {http://calendarserver.org/ns/}subscribed-strip-attachments (omit if
+ * attachments should not be stripped).
+ * 6. {http://calendarserver.org/ns/}source (Must be a
+ * Sabre\DAV\Property\Href).
+ * 7. {http://apple.com/ns/ical/}calendar-color
+ * 8. {http://apple.com/ns/ical/}calendar-order
+ * 9. {urn:ietf:params:xml:ns:caldav}supported-calendar-component-set
+ * (should just be an instance of
+ * Sabre\CalDAV\Property\SupportedCalendarComponentSet, with a bunch of
+ * default components).
+ *
+ * @param string $principalUri
+ * @return array
+ */
+ function getSubscriptionsForUser($principalUri) {
+ $fields = array_values($this->subscriptionPropertyMap);
+ $fields[] = 'id';
+ $fields[] = 'uri';
+ $fields[] = 'source';
+ $fields[] = 'principaluri';
+ $fields[] = 'lastmodified';
+
+ $query = $this->db->getQueryBuilder();
+ $query->select($fields)
+ ->from('calendarsubscriptions')
+ ->where($query->expr()->eq('principaluri', $query->createNamedParameter($principalUri)))
+ ->orderBy('calendarorder', 'asc');
+ $stmt =$query->execute();
+
+ $subscriptions = [];
+ while($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
+
+ $subscription = [
+ 'id' => $row['id'],
+ 'uri' => $row['uri'],
+ 'principaluri' => $row['principaluri'],
+ 'source' => $row['source'],
+ 'lastmodified' => $row['lastmodified'],
+
+ '{' . Plugin::NS_CALDAV . '}supported-calendar-component-set' => new SupportedCalendarComponentSet(['VTODO', 'VEVENT']),
+ ];
+
+ foreach($this->subscriptionPropertyMap as $xmlName=>$dbName) {
+ if (!is_null($row[$dbName])) {
+ $subscription[$xmlName] = $row[$dbName];
+ }
+ }
+
+ $subscriptions[] = $subscription;
+
+ }
+
+ return $subscriptions;
+ }
+
+ /**
+ * Creates a new subscription for a principal.
+ *
+ * If the creation was a success, an id must be returned that can be used to reference
+ * this subscription in other methods, such as updateSubscription.
+ *
+ * @param string $principalUri
+ * @param string $uri
+ * @param array $properties
+ * @return mixed
+ */
+ function createSubscription($principalUri, $uri, array $properties) {
+
+ if (!isset($properties['{http://calendarserver.org/ns/}source'])) {
+ throw new Forbidden('The {http://calendarserver.org/ns/}source property is required when creating subscriptions');
+ }
+
+ $values = [
+ 'principaluri' => $principalUri,
+ 'uri' => $uri,
+ 'source' => $properties['{http://calendarserver.org/ns/}source']->getHref(),
+ 'lastmodified' => time(),
+ ];
+
+ foreach($this->subscriptionPropertyMap as $xmlName=>$dbName) {
+ if (isset($properties[$xmlName])) {
+
+ $values[$dbName] = $properties[$xmlName];
+ $fieldNames[] = $dbName;
+ }
+ }
+
+ $query = $this->db->getQueryBuilder();
+ $query->insert('calendarsubscriptions')
+ ->values([
+ 'principaluri' => $query->createNamedParameter($values['principaluri']),
+ 'uri' => $query->createNamedParameter($values['uri']),
+ 'source' => $query->createNamedParameter($values['source']),
+ 'lastmodified' => $query->createNamedParameter($values['lastmodified']),
+ ])
+ ->execute();
+
+ return $this->db->lastInsertId('*PREFIX*calendarsubscriptions');
+ }
+
+ /**
+ * Updates a subscription
+ *
+ * The list of mutations is stored in a Sabre\DAV\PropPatch object.
+ * To do the actual updates, you must tell this object which properties
+ * you're going to process with the handle() method.
+ *
+ * Calling the handle method is like telling the PropPatch object "I
+ * promise I can handle updating this property".
+ *
+ * Read the PropPatch documentation for more info and examples.
+ *
+ * @param mixed $subscriptionId
+ * @param \Sabre\DAV\PropPatch $propPatch
+ * @return void
+ */
+ function updateSubscription($subscriptionId, DAV\PropPatch $propPatch) {
+ $supportedProperties = array_keys($this->subscriptionPropertyMap);
+ $supportedProperties[] = '{http://calendarserver.org/ns/}source';
+
+ $propPatch->handle($supportedProperties, function($mutations) use ($subscriptionId) {
+
+ $newValues = [];
+
+ foreach($mutations as $propertyName=>$propertyValue) {
+ if ($propertyName === '{http://calendarserver.org/ns/}source') {
+ $newValues['source'] = $propertyValue->getHref();
+ } else {
+ $fieldName = $this->subscriptionPropertyMap[$propertyName];
+ $newValues[$fieldName] = $propertyValue;
+ }
+ }
+
+ $query = $this->db->getQueryBuilder();
+ $query->update('calendarsubscriptions')
+ ->set('lastmodified', $query->createNamedParameter(time()));
+ foreach($newValues as $fieldName=>$value) {
+ $query->set($fieldName, $query->createNamedParameter($value));
+ }
+ $query->where($query->expr()->eq('id', $query->createNamedParameter($subscriptionId)))
+ ->execute();
+
+ return true;
+
+ });
+ }
+
+ /**
+ * Deletes a subscription.
+ *
+ * @param mixed $subscriptionId
+ * @return void
+ */
+ function deleteSubscription($subscriptionId) {
+ $query = $this->db->getQueryBuilder();
+ $query->delete('calendarsubscriptions')
+ ->where($query->expr()->eq('id', $query->createNamedParameter($subscriptionId)))
+ ->execute();
+ }
+
+ /**
+ * Returns a single scheduling object for the inbox collection.
+ *
+ * The returned array should contain the following elements:
+ * * uri - A unique basename for the object. This will be used to
+ * construct a full uri.
+ * * calendardata - The iCalendar object
+ * * lastmodified - The last modification date. Can be an int for a unix
+ * timestamp, or a PHP DateTime object.
+ * * etag - A unique token that must change if the object changed.
+ * * size - The size of the object, in bytes.
+ *
+ * @param string $principalUri
+ * @param string $objectUri
+ * @return array
+ */
+ function getSchedulingObject($principalUri, $objectUri) {
+ $query = $this->db->getQueryBuilder();
+ $stmt = $query->select(['uri', 'calendardata', 'lastmodified', 'etag', 'size'])
+ ->from('schedulingobjects')
+ ->where($query->expr()->eq('principaluri', $query->createNamedParameter($principalUri)))
+ ->andWhere($query->expr()->eq('uri', $query->createNamedParameter($objectUri)))
+ ->execute();
+
+ $row = $stmt->fetch(\PDO::FETCH_ASSOC);
+
+ if(!$row) {
+ return null;
+ }
+
+ return [
+ 'uri' => $row['uri'],
+ 'calendardata' => $row['calendardata'],
+ 'lastmodified' => $row['lastmodified'],
+ 'etag' => '"' . $row['etag'] . '"',
+ 'size' => (int)$row['size'],
+ ];
+ }
+
+ /**
+ * Returns all scheduling objects for the inbox collection.
+ *
+ * These objects should be returned as an array. Every item in the array
+ * should follow the same structure as returned from getSchedulingObject.
+ *
+ * The main difference is that 'calendardata' is optional.
+ *
+ * @param string $principalUri
+ * @return array
+ */
+ function getSchedulingObjects($principalUri) {
+ $query = $this->db->getQueryBuilder();
+ $stmt = $query->select(['uri', 'calendardata', 'lastmodified', 'etag', 'size'])
+ ->from('schedulingobjects')
+ ->where($query->expr()->eq('principaluri', $query->createNamedParameter($principalUri)))
+ ->execute();
+
+ $result = [];
+ foreach($stmt->fetchAll(\PDO::FETCH_ASSOC) as $row) {
+ $result[] = [
+ 'calendardata' => $row['calendardata'],
+ 'uri' => $row['uri'],
+ 'lastmodified' => $row['lastmodified'],
+ 'etag' => '"' . $row['etag'] . '"',
+ 'size' => (int)$row['size'],
+ ];
+ }
+
+ return $result;
+ }
+
+ /**
+ * Deletes a scheduling object from the inbox collection.
+ *
+ * @param string $principalUri
+ * @param string $objectUri
+ * @return void
+ */
+ function deleteSchedulingObject($principalUri, $objectUri) {
+ $query = $this->db->getQueryBuilder();
+ $query->delete('schedulingobjects')
+ ->where($query->expr()->eq('principaluri', $query->createNamedParameter($principalUri)))
+ ->andWhere($query->expr()->eq('uri', $query->createNamedParameter($objectUri)))
+ ->execute();
+ }
+
+ /**
+ * Creates a new scheduling object. This should land in a users' inbox.
+ *
+ * @param string $principalUri
+ * @param string $objectUri
+ * @param string $objectData
+ * @return void
+ */
+ function createSchedulingObject($principalUri, $objectUri, $objectData) {
+ $query = $this->db->getQueryBuilder();
+ $query->insert('schedulingobjects')
+ ->values([
+ 'principaluri' => $query->createNamedParameter($principalUri),
+ 'calendardata' => $query->createNamedParameter($objectData),
+ 'uri' => $query->createNamedParameter($objectUri),
+ 'lastmodified' => $query->createNamedParameter(time()),
+ 'etag' => $query->createNamedParameter(md5($objectData)),
+ 'size' => $query->createNamedParameter(strlen($objectData))
+ ])
+ ->execute();
+ }
+
+ /**
+ * Adds a change record to the calendarchanges table.
+ *
+ * @param mixed $calendarId
+ * @param string $objectUri
+ * @param int $operation 1 = add, 2 = modify, 3 = delete.
+ * @return void
+ */
+ protected function addChange($calendarId, $objectUri, $operation) {
+
+ $stmt = $this->db->prepare('INSERT INTO `*PREFIX*calendarchanges` (`uri`, `synctoken`, `calendarid`, `operation`) SELECT ?, `synctoken`, ?, ? FROM `*PREFIX*calendars` WHERE `id` = ?');
+ $stmt->execute([
+ $objectUri,
+ $calendarId,
+ $operation,
+ $calendarId
+ ]);
+ $stmt = $this->db->prepare('UPDATE `*PREFIX*calendars` SET `synctoken` = `synctoken` + 1 WHERE `id` = ?');
+ $stmt->execute([
+ $calendarId
+ ]);
+
+ }
+
+ /**
+ * Parses some information from calendar objects, used for optimized
+ * calendar-queries.
+ *
+ * Returns an array with the following keys:
+ * * etag - An md5 checksum of the object without the quotes.
+ * * size - Size of the object in bytes
+ * * componentType - VEVENT, VTODO or VJOURNAL
+ * * firstOccurence
+ * * lastOccurence
+ * * uid - value of the UID property
+ *
+ * @param string $calendarData
+ * @return array
+ */
+ protected function getDenormalizedData($calendarData) {
+
+ $vObject = Reader::read($calendarData);
+ $componentType = null;
+ $component = null;
+ $firstOccurence = null;
+ $lastOccurence = null;
+ $uid = null;
+ foreach($vObject->getComponents() as $component) {
+ if ($component->name!=='VTIMEZONE') {
+ $componentType = $component->name;
+ $uid = (string)$component->UID;
+ break;
+ }
+ }
+ if (!$componentType) {
+ throw new \Sabre\DAV\Exception\BadRequest('Calendar objects must have a VJOURNAL, VEVENT or VTODO component');
+ }
+ if ($componentType === 'VEVENT') {
+ $firstOccurence = $component->DTSTART->getDateTime()->getTimeStamp();
+ // Finding the last occurence is a bit harder
+ if (!isset($component->RRULE)) {
+ if (isset($component->DTEND)) {
+ $lastOccurence = $component->DTEND->getDateTime()->getTimeStamp();
+ } elseif (isset($component->DURATION)) {
+ $endDate = clone $component->DTSTART->getDateTime();
+ $endDate->add(DateTimeParser::parse($component->DURATION->getValue()));
+ $lastOccurence = $endDate->getTimeStamp();
+ } elseif (!$component->DTSTART->hasTime()) {
+ $endDate = clone $component->DTSTART->getDateTime();
+ $endDate->modify('+1 day');
+ $lastOccurence = $endDate->getTimeStamp();
+ } else {
+ $lastOccurence = $firstOccurence;
+ }
+ } else {
+ $it = new RecurrenceIterator($vObject, (string)$component->UID);
+ $maxDate = new \DateTime(self::MAX_DATE);
+ if ($it->isInfinite()) {
+ $lastOccurence = $maxDate->getTimeStamp();
+ } else {
+ $end = $it->getDtEnd();
+ while($it->valid() && $end < $maxDate) {
+ $end = $it->getDtEnd();
+ $it->next();
+
+ }
+ $lastOccurence = $end->getTimeStamp();
+ }
+
+ }
+ }
+
+ return [
+ 'etag' => md5($calendarData),
+ 'size' => strlen($calendarData),
+ 'componentType' => $componentType,
+ 'firstOccurence' => $firstOccurence,
+ 'lastOccurence' => $lastOccurence,
+ 'uid' => $uid,
+ ];
+
+ }
+
+ private function readBlob($cardData) {
+ if (is_resource($cardData)) {
+ return stream_get_contents($cardData);
+ }
+
+ return $cardData;
+ }
+}
diff --git a/apps/dav/lib/connector/sabre/directory.php b/apps/dav/lib/connector/sabre/directory.php
index 8c736ea0108..b602dd2f7b1 100644
--- a/apps/dav/lib/connector/sabre/directory.php
+++ b/apps/dav/lib/connector/sabre/directory.php
@@ -28,8 +28,10 @@
*/
namespace OCA\DAV\Connector\Sabre;
+use OCA\DAV\Connector\Sabre\Exception\Forbidden;
use OCA\DAV\Connector\Sabre\Exception\InvalidPath;
use OCA\DAV\Connector\Sabre\Exception\FileLocked;
+use OCP\Files\ForbiddenException;
use OCP\Lock\ILockingProvider;
use OCP\Lock\LockedException;
use Sabre\DAV\Exception\Locked;
@@ -117,6 +119,8 @@ class Directory extends \OCA\DAV\Connector\Sabre\Node
throw new \Sabre\DAV\Exception\ServiceUnavailable($e->getMessage());
} catch (\OCP\Files\InvalidPathException $ex) {
throw new InvalidPath($ex->getMessage());
+ } catch (ForbiddenException $ex) {
+ throw new Forbidden($ex->getMessage(), $ex->getRetry());
} catch (LockedException $e) {
throw new FileLocked($e->getMessage(), $e->getCode(), $e);
}
@@ -146,6 +150,8 @@ class Directory extends \OCA\DAV\Connector\Sabre\Node
throw new \Sabre\DAV\Exception\ServiceUnavailable($e->getMessage());
} catch (\OCP\Files\InvalidPathException $ex) {
throw new InvalidPath($ex->getMessage());
+ } catch (ForbiddenException $ex) {
+ throw new Forbidden($ex->getMessage(), $ex->getRetry());
} catch (LockedException $e) {
throw new FileLocked($e->getMessage(), $e->getCode(), $e);
}
@@ -247,6 +253,8 @@ class Directory extends \OCA\DAV\Connector\Sabre\Node
// assume it wasn't possible to remove due to permission issue
throw new \Sabre\DAV\Exception\Forbidden();
}
+ } catch (ForbiddenException $ex) {
+ throw new Forbidden($ex->getMessage(), $ex->getRetry());
} catch (LockedException $e) {
throw new FileLocked($e->getMessage(), $e->getCode(), $e);
}
diff --git a/apps/dav/lib/connector/sabre/exception/forbidden.php b/apps/dav/lib/connector/sabre/exception/forbidden.php
new file mode 100644
index 00000000000..673958349f3
--- /dev/null
+++ b/apps/dav/lib/connector/sabre/exception/forbidden.php
@@ -0,0 +1,64 @@
+<?php
+/**
+ * @author Joas Schilling <nickvergessen@owncloud.com>
+ *
+ * @copyright Copyright (c) 2015, ownCloud, Inc.
+ * @license AGPL-3.0
+ *
+ * This code is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License, version 3,
+ * along with this program. If not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+namespace OCA\DAV\Connector\Sabre\Exception;
+
+class Forbidden extends \Sabre\DAV\Exception\Forbidden {
+
+ const NS_OWNCLOUD = 'http://owncloud.org/ns';
+
+ /**
+ * @var bool
+ */
+ private $retry;
+
+ /**
+ * @param string $message
+ * @param bool $retry
+ * @param \Exception $previous
+ */
+ public function __construct($message, $retry = false, \Exception $previous = null) {
+ parent::__construct($message, 0, $previous);
+ $this->retry = $retry;
+ }
+
+ /**
+ * This method allows the exception to include additional information
+ * into the WebDAV error response
+ *
+ * @param \Sabre\DAV\Server $server
+ * @param \DOMElement $errorNode
+ * @return void
+ */
+ public function serialize(\Sabre\DAV\Server $server,\DOMElement $errorNode) {
+
+ // set ownCloud namespace
+ $errorNode->setAttribute('xmlns:o', self::NS_OWNCLOUD);
+
+ // adding the retry node
+ $error = $errorNode->ownerDocument->createElementNS('o:','o:retry', var_export($this->retry, true));
+ $errorNode->appendChild($error);
+
+ // adding the message node
+ $error = $errorNode->ownerDocument->createElementNS('o:','o:reason', $this->getMessage());
+ $errorNode->appendChild($error);
+ }
+}
diff --git a/apps/dav/lib/connector/sabre/file.php b/apps/dav/lib/connector/sabre/file.php
index 961532daf50..a0c35fb2baf 100644
--- a/apps/dav/lib/connector/sabre/file.php
+++ b/apps/dav/lib/connector/sabre/file.php
@@ -35,9 +35,11 @@ namespace OCA\DAV\Connector\Sabre;
use OC\Files\Filesystem;
use OCA\DAV\Connector\Sabre\Exception\EntityTooLarge;
use OCA\DAV\Connector\Sabre\Exception\FileLocked;
+use OCA\DAV\Connector\Sabre\Exception\Forbidden as DAVForbiddenException;
use OCA\DAV\Connector\Sabre\Exception\UnsupportedMediaType;
use OCP\Encryption\Exceptions\GenericEncryptionException;
use OCP\Files\EntityTooLargeException;
+use OCP\Files\ForbiddenException;
use OCP\Files\InvalidContentException;
use OCP\Files\InvalidPathException;
use OCP\Files\LockNotAcquiredException;
@@ -175,6 +177,8 @@ class File extends Node implements IFile {
\OCP\Util::writeLog('webdav', 'renaming part file to final file failed', \OCP\Util::ERROR);
throw new Exception('Could not rename part file to final file');
}
+ } catch (ForbiddenException $ex) {
+ throw new DAVForbiddenException($ex->getMessage(), $ex->getRetry());
} catch (\Exception $e) {
$partStorage->unlink($internalPartPath);
$this->convertToSabreException($e);
@@ -273,6 +277,8 @@ class File extends Node implements IFile {
throw new ServiceUnavailable("Encryption not ready: " . $e->getMessage());
} catch (StorageNotAvailableException $e) {
throw new ServiceUnavailable("Failed to open file: " . $e->getMessage());
+ } catch (ForbiddenException $ex) {
+ throw new DAVForbiddenException($ex->getMessage(), $ex->getRetry());
} catch (LockedException $e) {
throw new FileLocked($e->getMessage(), $e->getCode(), $e);
}
@@ -296,6 +302,8 @@ class File extends Node implements IFile {
}
} catch (StorageNotAvailableException $e) {
throw new ServiceUnavailable("Failed to unlink: " . $e->getMessage());
+ } catch (ForbiddenException $ex) {
+ throw new DAVForbiddenException($ex->getMessage(), $ex->getRetry());
} catch (LockedException $e) {
throw new FileLocked($e->getMessage(), $e->getCode(), $e);
}
@@ -474,6 +482,10 @@ class File extends Node implements IFile {
// a more general case - due to whatever reason the content could not be written
throw new Forbidden($e->getMessage(), 0, $e);
}
+ if ($e instanceof ForbiddenException) {
+ // the path for the file was forbidden
+ throw new DAVForbiddenException($e->getMessage(), $e->getRetry(), $e);
+ }
if ($e instanceof EntityTooLargeException) {
// the file is too big to be stored
throw new EntityTooLarge($e->getMessage(), 0, $e);
diff --git a/apps/dav/lib/connector/sabre/filesplugin.php b/apps/dav/lib/connector/sabre/filesplugin.php
index 61b5360cac1..d68397dcaa3 100644
--- a/apps/dav/lib/connector/sabre/filesplugin.php
+++ b/apps/dav/lib/connector/sabre/filesplugin.php
@@ -37,11 +37,14 @@ class FilesPlugin extends \Sabre\DAV\ServerPlugin {
// namespace
const NS_OWNCLOUD = 'http://owncloud.org/ns';
const FILEID_PROPERTYNAME = '{http://owncloud.org/ns}id';
+ const INTERNAL_FILEID_PROPERTYNAME = '{http://owncloud.org/ns}fileid';
const PERMISSIONS_PROPERTYNAME = '{http://owncloud.org/ns}permissions';
const DOWNLOADURL_PROPERTYNAME = '{http://owncloud.org/ns}downloadURL';
const SIZE_PROPERTYNAME = '{http://owncloud.org/ns}size';
const GETETAG_PROPERTYNAME = '{DAV:}getetag';
const LASTMODIFIED_PROPERTYNAME = '{DAV:}lastmodified';
+ const OWNER_ID_PROPERTYNAME = '{http://owncloud.org/ns}owner-id';
+ const OWNER_DISPLAY_NAME_PROPERTYNAME = '{http://owncloud.org/ns}owner-display-name';
/**
* Reference to main server object
@@ -96,9 +99,12 @@ class FilesPlugin extends \Sabre\DAV\ServerPlugin {
$server->xmlNamespaces[self::NS_OWNCLOUD] = 'oc';
$server->protectedProperties[] = self::FILEID_PROPERTYNAME;
+ $server->protectedProperties[] = self::INTERNAL_FILEID_PROPERTYNAME;
$server->protectedProperties[] = self::PERMISSIONS_PROPERTYNAME;
$server->protectedProperties[] = self::SIZE_PROPERTYNAME;
$server->protectedProperties[] = self::DOWNLOADURL_PROPERTYNAME;
+ $server->protectedProperties[] = self::OWNER_ID_PROPERTYNAME;
+ $server->protectedProperties[] = self::OWNER_DISPLAY_NAME_PROPERTYNAME;
// normally these cannot be changed (RFC4918), but we want them modifiable through PROPPATCH
$allowedProperties = ['{DAV:}getetag'];
@@ -171,6 +177,10 @@ class FilesPlugin extends \Sabre\DAV\ServerPlugin {
return $node->getFileId();
});
+ $propFind->handle(self::INTERNAL_FILEID_PROPERTYNAME, function() use ($node) {
+ return $node->getInternalFileId();
+ });
+
$propFind->handle(self::PERMISSIONS_PROPERTYNAME, function() use ($node) {
$perms = $node->getDavPermissions();
if ($this->isPublic) {
@@ -201,6 +211,16 @@ class FilesPlugin extends \Sabre\DAV\ServerPlugin {
return $node->getSize();
});
}
+
+ $propFind->handle(self::OWNER_ID_PROPERTYNAME, function() use ($node) {
+ $owner = $node->getOwner();
+ return $owner->getUID();
+ });
+ $propFind->handle(self::OWNER_DISPLAY_NAME_PROPERTYNAME, function() use ($node) {
+ $owner = $node->getOwner();
+ $displayName = $owner->getDisplayName();
+ return $displayName;
+ });
}
/**
diff --git a/apps/dav/lib/connector/sabre/lockplugin.php b/apps/dav/lib/connector/sabre/lockplugin.php
index 5840e59854c..8032d2b3fbf 100644
--- a/apps/dav/lib/connector/sabre/lockplugin.php
+++ b/apps/dav/lib/connector/sabre/lockplugin.php
@@ -39,18 +39,6 @@ class LockPlugin extends ServerPlugin {
private $server;
/**
- * @var \Sabre\DAV\Tree
- */
- private $tree;
-
- /**
- * @param \Sabre\DAV\Tree $tree tree
- */
- public function __construct(Tree $tree) {
- $this->tree = $tree;
- }
-
- /**
* {@inheritdoc}
*/
public function initialize(\Sabre\DAV\Server $server) {
@@ -66,7 +54,7 @@ class LockPlugin extends ServerPlugin {
return;
}
try {
- $node = $this->tree->getNodeForPath($request->getPath());
+ $node = $this->server->tree->getNodeForPath($request->getPath());
} catch (NotFound $e) {
return;
}
@@ -84,7 +72,7 @@ class LockPlugin extends ServerPlugin {
return;
}
try {
- $node = $this->tree->getNodeForPath($request->getPath());
+ $node = $this->server->tree->getNodeForPath($request->getPath());
} catch (NotFound $e) {
return;
}
diff --git a/apps/dav/lib/connector/sabre/node.php b/apps/dav/lib/connector/sabre/node.php
index 814aaceb077..daf82ba6f0d 100644
--- a/apps/dav/lib/connector/sabre/node.php
+++ b/apps/dav/lib/connector/sabre/node.php
@@ -207,6 +207,13 @@ abstract class Node implements \Sabre\DAV\INode {
}
/**
+ * @return string
+ */
+ public function getInternalFileId() {
+ return $this->info->getId();
+ }
+
+ /**
* @return string|null
*/
public function getDavPermissions() {
@@ -238,6 +245,10 @@ abstract class Node implements \Sabre\DAV\INode {
return $p;
}
+ public function getOwner() {
+ return $this->info->getOwner();
+ }
+
protected function verifyPath() {
try {
$fileName = basename($this->info->getPath());
diff --git a/apps/dav/lib/connector/sabre/objecttree.php b/apps/dav/lib/connector/sabre/objecttree.php
index 80c0ef74610..2e9c1b9916c 100644
--- a/apps/dav/lib/connector/sabre/objecttree.php
+++ b/apps/dav/lib/connector/sabre/objecttree.php
@@ -25,10 +25,12 @@
namespace OCA\DAV\Connector\Sabre;
+use OCA\DAV\Connector\Sabre\Exception\Forbidden;
use OCA\DAV\Connector\Sabre\Exception\InvalidPath;
use OCA\DAV\Connector\Sabre\Exception\FileLocked;
use OC\Files\FileInfo;
use OC\Files\Mount\MoveableMount;
+use OCP\Files\ForbiddenException;
use OCP\Files\StorageInvalidException;
use OCP\Files\StorageNotAvailableException;
use OCP\Lock\LockedException;
@@ -235,6 +237,8 @@ class ObjectTree extends \Sabre\DAV\Tree {
}
} catch (StorageNotAvailableException $e) {
throw new \Sabre\DAV\Exception\ServiceUnavailable($e->getMessage());
+ } catch (ForbiddenException $ex) {
+ throw new Forbidden($ex->getMessage(), $ex->getRetry());
} catch (LockedException $e) {
throw new FileLocked($e->getMessage(), $e->getCode(), $e);
}
@@ -274,6 +278,8 @@ class ObjectTree extends \Sabre\DAV\Tree {
$this->fileView->copy($source, $destination);
} catch (StorageNotAvailableException $e) {
throw new \Sabre\DAV\Exception\ServiceUnavailable($e->getMessage());
+ } catch (ForbiddenException $ex) {
+ throw new Forbidden($ex->getMessage(), $ex->getRetry());
} catch (LockedException $e) {
throw new FileLocked($e->getMessage(), $e->getCode(), $e);
}
diff --git a/apps/dav/lib/connector/sabre/serverfactory.php b/apps/dav/lib/connector/sabre/serverfactory.php
index a33acc9f00b..0f0377e96bd 100644
--- a/apps/dav/lib/connector/sabre/serverfactory.php
+++ b/apps/dav/lib/connector/sabre/serverfactory.php
@@ -107,7 +107,7 @@ class ServerFactory {
// FIXME: The following line is a workaround for legacy components relying on being able to send a GET to /
$server->addPlugin(new \OCA\DAV\Connector\Sabre\DummyGetResponsePlugin());
$server->addPlugin(new \OCA\DAV\Connector\Sabre\ExceptionLoggerPlugin('webdav', $this->logger));
- $server->addPlugin(new \OCA\DAV\Connector\Sabre\LockPlugin($objectTree));
+ $server->addPlugin(new \OCA\DAV\Connector\Sabre\LockPlugin());
$server->addPlugin(new \OCA\DAV\Connector\Sabre\ListenerPlugin($this->dispatcher));
// Finder on OS X requires Class 2 WebDAV support (locking), since we do
// not provide locking we emulate it using a fake locking plugin.
diff --git a/apps/dav/lib/rootcollection.php b/apps/dav/lib/rootcollection.php
index 850180d8481..10baff072cc 100644
--- a/apps/dav/lib/rootcollection.php
+++ b/apps/dav/lib/rootcollection.php
@@ -2,8 +2,10 @@
namespace OCA\DAV;
+use OCA\DAV\CalDAV\CalDavBackend;
use OCA\DAV\CardDAV\CardDavBackend;
use OCA\DAV\Connector\Sabre\Principal;
+use Sabre\CalDAV\CalendarRoot;
use Sabre\CalDAV\Principal\Collection;
use Sabre\CardDAV\AddressBookRoot;
use Sabre\DAV\SimpleCollection;
@@ -12,9 +14,10 @@ class RootCollection extends SimpleCollection {
public function __construct() {
$config = \OC::$server->getConfig();
+ $db = \OC::$server->getDatabaseConnection();
$principalBackend = new Principal(
- $config,
- \OC::$server->getUserManager()
+ $config,
+ \OC::$server->getUserManager()
);
// as soon as debug mode is enabled we allow listing of principals
$disableListing = !$config->getSystemValue('debug', false);
@@ -24,14 +27,18 @@ class RootCollection extends SimpleCollection {
$principalCollection->disableListing = $disableListing;
$filesCollection = new Files\RootCollection($principalBackend);
$filesCollection->disableListing = $disableListing;
- $cardDavBackend = new CardDavBackend(\OC::$server->getDatabaseConnection());
+ $caldavBackend = new CalDavBackend($db);
+ $calendarRoot = new CalendarRoot($principalBackend, $caldavBackend);
+ $calendarRoot->disableListing = $disableListing;
+ $cardDavBackend = new CardDavBackend($db);
$addressBookRoot = new AddressBookRoot($principalBackend, $cardDavBackend);
$addressBookRoot->disableListing = $disableListing;
$children = [
- $principalCollection,
- $filesCollection,
- $addressBookRoot,
+ $principalCollection,
+ $filesCollection,
+ $calendarRoot,
+ $addressBookRoot,
];
parent::__construct('root', $children);
diff --git a/apps/dav/lib/server.php b/apps/dav/lib/server.php
index 395544761ab..229f33858d9 100644
--- a/apps/dav/lib/server.php
+++ b/apps/dav/lib/server.php
@@ -17,6 +17,9 @@ class Server {
public function __construct(IRequest $request, $baseUri) {
$this->request = $request;
$this->baseUri = $baseUri;
+ $logger = \OC::$server->getLogger();
+ $dispatcher = \OC::$server->getEventDispatcher();
+
$root = new RootCollection();
$this->server = new \OCA\DAV\Connector\Sabre\Server($root);
@@ -32,9 +35,23 @@ class Server {
$this->server->addPlugin(new BlockLegacyClientPlugin(\OC::$server->getConfig()));
$this->server->addPlugin(new Plugin($authBackend, 'ownCloud'));
+ $this->server->addPlugin(new \OCA\DAV\Connector\Sabre\DummyGetResponsePlugin());
+ $this->server->addPlugin(new \OCA\DAV\Connector\Sabre\ExceptionLoggerPlugin('webdav', $logger));
+ $this->server->addPlugin(new \OCA\DAV\Connector\Sabre\LockPlugin());
+ $this->server->addPlugin(new \OCA\DAV\Connector\Sabre\ListenerPlugin($dispatcher));
+ // calendar plugins
+ $this->server->addPlugin(new \Sabre\CalDAV\Plugin());
$this->server->addPlugin(new \Sabre\DAVACL\Plugin());
+ $this->server->addPlugin(new \Sabre\CalDAV\ICSExportPlugin());
+ $senderEmail = \OCP\Util::getDefaultEmailAddress('no-reply');
+ $this->server->addPlugin(new \Sabre\CalDAV\Schedule\Plugin());
+ $this->server->addPlugin(new \Sabre\CalDAV\Schedule\IMipPlugin($senderEmail));
+ $this->server->addPlugin(new \Sabre\CalDAV\SharingPlugin());
+ $this->server->addPlugin(new \Sabre\CalDAV\Subscriptions\Plugin());
+ $this->server->addPlugin(new \Sabre\CalDAV\Notifications\Plugin());
+ // addressbook plugins
$this->server->addPlugin(new \Sabre\CardDAV\Plugin());
// Finder on OS X requires Class 2 WebDAV support (locking), since we do
diff --git a/apps/dav/tests/unit/caldav/caldavbackendtest.php b/apps/dav/tests/unit/caldav/caldavbackendtest.php
new file mode 100644
index 00000000000..258c5627ad9
--- /dev/null
+++ b/apps/dav/tests/unit/caldav/caldavbackendtest.php
@@ -0,0 +1,348 @@
+<?php
+/**
+ * @author Lukas Reschke <lukas@owncloud.com>
+ *
+ * @copyright Copyright (c) 2015, ownCloud, Inc.
+ * @license AGPL-3.0
+ *
+ * This code is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License, version 3,
+ * along with this program. If not, see <http://www.gnu.org/licenses/>
+ *
+ */
+namespace Tests\Connector\Sabre;
+
+use DateTime;
+use DateTimeZone;
+use OCA\DAV\CalDAV\CalDavBackend;
+use Sabre\CalDAV\Property\SupportedCalendarComponentSet;
+use Sabre\DAV\Property\Href;
+use Sabre\DAV\PropPatch;
+use Test\TestCase;
+
+/**
+ * Class CalDavBackendTest
+ *
+ * @group DB
+ *
+ * @package Tests\Connector\Sabre
+ */
+class CalDavBackendTest extends TestCase {
+
+ /** @var CalDavBackend */
+ private $backend;
+
+ const UNIT_TEST_USER = 'caldav-unit-test';
+
+
+ public function setUp() {
+ parent::setUp();
+
+ $db = \OC::$server->getDatabaseConnection();
+ $this->backend = new CalDavBackend($db);
+
+ $this->tearDown();
+ }
+
+ public function tearDown() {
+ parent::tearDown();
+
+ if (is_null($this->backend)) {
+ return;
+ }
+ $books = $this->backend->getCalendarsForUser(self::UNIT_TEST_USER);
+ foreach ($books as $book) {
+ $this->backend->deleteCalendar($book['id']);
+ }
+ $subscriptions = $this->backend->getSubscriptionsForUser(self::UNIT_TEST_USER);
+ foreach ($subscriptions as $subscription) {
+ $this->backend->deleteSubscription($subscription['id']);
+ }
+ }
+
+ public function testCalendarOperations() {
+
+ $calendarId = $this->createTestCalendar();
+
+ // update it's display name
+ $patch = new PropPatch([
+ '{DAV:}displayname' => 'Unit test',
+ '{urn:ietf:params:xml:ns:caldav}calendar-description' => 'Calendar used for unit testing'
+ ]);
+ $this->backend->updateCalendar($calendarId, $patch);
+ $patch->commit();
+ $books = $this->backend->getCalendarsForUser(self::UNIT_TEST_USER);
+ $this->assertEquals(1, count($books));
+ $this->assertEquals('Unit test', $books[0]['{DAV:}displayname']);
+ $this->assertEquals('Calendar used for unit testing', $books[0]['{urn:ietf:params:xml:ns:caldav}calendar-description']);
+
+ // delete the address book
+ $this->backend->deleteCalendar($books[0]['id']);
+ $books = $this->backend->getCalendarsForUser(self::UNIT_TEST_USER);
+ $this->assertEquals(0, count($books));
+ }
+
+ public function testCalendarObjectsOperations() {
+
+ $calendarId = $this->createTestCalendar();
+
+ // create a card
+ $uri = $this->getUniqueID('calobj');
+ $calData = <<<'EOD'
+BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:ownCloud Calendar
+BEGIN:VEVENT
+CREATED;VALUE=DATE-TIME:20130910T125139Z
+UID:47d15e3ec8
+LAST-MODIFIED;VALUE=DATE-TIME:20130910T125139Z
+DTSTAMP;VALUE=DATE-TIME:20130910T125139Z
+SUMMARY:Test Event
+DTSTART;VALUE=DATE-TIME:20130912T130000Z
+DTEND;VALUE=DATE-TIME:20130912T140000Z
+CLASS:PUBLIC
+END:VEVENT
+END:VCALENDAR
+EOD;
+
+ $this->backend->createCalendarObject($calendarId, $uri, $calData);
+
+ // get all the cards
+ $calendarObjects = $this->backend->getCalendarObjects($calendarId);
+ $this->assertEquals(1, count($calendarObjects));
+ $this->assertEquals($calendarId, $calendarObjects[0]['calendarid']);
+
+ // get the cards
+ $calendarObject = $this->backend->getCalendarObject($calendarId, $uri);
+ $this->assertNotNull($calendarObject);
+ $this->assertArrayHasKey('id', $calendarObject);
+ $this->assertArrayHasKey('uri', $calendarObject);
+ $this->assertArrayHasKey('lastmodified', $calendarObject);
+ $this->assertArrayHasKey('etag', $calendarObject);
+ $this->assertArrayHasKey('size', $calendarObject);
+ $this->assertEquals($calData, $calendarObject['calendardata']);
+
+ // update the card
+ $calData = <<<'EOD'
+BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:ownCloud Calendar
+BEGIN:VEVENT
+CREATED;VALUE=DATE-TIME:20130910T125139Z
+UID:47d15e3ec8
+LAST-MODIFIED;VALUE=DATE-TIME:20130910T125139Z
+DTSTAMP;VALUE=DATE-TIME:20130910T125139Z
+SUMMARY:Test Event
+DTSTART;VALUE=DATE-TIME:20130912T130000Z
+DTEND;VALUE=DATE-TIME:20130912T140000Z
+END:VEVENT
+END:VCALENDAR
+EOD;
+ $this->backend->updateCalendarObject($calendarId, $uri, $calData);
+ $calendarObject = $this->backend->getCalendarObject($calendarId, $uri);
+ $this->assertEquals($calData, $calendarObject['calendardata']);
+
+ // delete the card
+ $this->backend->deleteCalendarObject($calendarId, $uri);
+ $calendarObjects = $this->backend->getCalendarObjects($calendarId);
+ $this->assertEquals(0, count($calendarObjects));
+ }
+
+ public function testMultiCalendarObjects() {
+
+ $calendarId = $this->createTestCalendar();
+
+ // create an event
+ $calData = <<<'EOD'
+BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:ownCloud Calendar
+BEGIN:VEVENT
+CREATED;VALUE=DATE-TIME:20130910T125139Z
+UID:47d15e3ec8
+LAST-MODIFIED;VALUE=DATE-TIME:20130910T125139Z
+DTSTAMP;VALUE=DATE-TIME:20130910T125139Z
+SUMMARY:Test Event
+DTSTART;VALUE=DATE-TIME:20130912T130000Z
+DTEND;VALUE=DATE-TIME:20130912T140000Z
+CLASS:PUBLIC
+END:VEVENT
+END:VCALENDAR
+EOD;
+ $uri0 = $this->getUniqueID('card');
+ $this->backend->createCalendarObject($calendarId, $uri0, $calData);
+ $uri1 = $this->getUniqueID('card');
+ $this->backend->createCalendarObject($calendarId, $uri1, $calData);
+ $uri2 = $this->getUniqueID('card');
+ $this->backend->createCalendarObject($calendarId, $uri2, $calData);
+
+ // get all the cards
+ $calendarObjects = $this->backend->getCalendarObjects($calendarId);
+ $this->assertEquals(3, count($calendarObjects));
+
+ // get the cards
+ $calendarObjects = $this->backend->getMultipleCalendarObjects($calendarId, [$uri1, $uri2]);
+ $this->assertEquals(2, count($calendarObjects));
+ foreach($calendarObjects as $card) {
+ $this->assertArrayHasKey('id', $card);
+ $this->assertArrayHasKey('uri', $card);
+ $this->assertArrayHasKey('lastmodified', $card);
+ $this->assertArrayHasKey('etag', $card);
+ $this->assertArrayHasKey('size', $card);
+ $this->assertEquals($calData, $card['calendardata']);
+ }
+
+ // delete the card
+ $this->backend->deleteCalendarObject($calendarId, $uri0);
+ $this->backend->deleteCalendarObject($calendarId, $uri1);
+ $this->backend->deleteCalendarObject($calendarId, $uri2);
+ $calendarObjects = $this->backend->getCalendarObjects($calendarId);
+ $this->assertEquals(0, count($calendarObjects));
+ }
+
+ /**
+ * @dataProvider providesCalendarQueryParameters
+ */
+ public function testCalendarQuery($expectedEventsInResult, $propFilters, $compFilter) {
+ $calendarId = $this->createTestCalendar();
+ $events = [];
+ $events[0] = $this->createEvent($calendarId, '20130912T130000Z', '20130912T140000Z');
+ $events[1] = $this->createEvent($calendarId, '20130912T150000Z', '20130912T170000Z');
+ $events[2] = $this->createEvent($calendarId, '20130912T173000Z', '20130912T220000Z');
+
+ $result = $this->backend->calendarQuery($calendarId, [
+ 'name' => '',
+ 'prop-filters' => $propFilters,
+ 'comp-filters' => $compFilter
+ ]);
+
+ $expectedEventsInResult = array_map(function($index) use($events) {
+ return $events[$index];
+ }, $expectedEventsInResult);
+ $this->assertEquals($expectedEventsInResult, $result, '', 0.0, 10, true);
+ }
+
+ public function testGetCalendarObjectByUID() {
+ $calendarId = $this->createTestCalendar();
+ $this->createEvent($calendarId, '20130912T130000Z', '20130912T140000Z');
+
+ $co = $this->backend->getCalendarObjectByUID(self::UNIT_TEST_USER, '47d15e3ec8');
+ $this->assertNotNull($co);
+ }
+
+ public function providesCalendarQueryParameters() {
+ return [
+ 'all' => [[0, 1, 2], [], []],
+ 'only-todos' => [[], ['name' => 'VTODO'], []],
+ 'only-events' => [[0, 1, 2], [], [['name' => 'VEVENT', 'is-not-defined' => false, 'comp-filters' => [], 'time-range' => ['start' => null, 'end' => null], 'prop-filters' => []]],],
+ 'start' => [[1, 2], [], [['name' => 'VEVENT', 'is-not-defined' => false, 'comp-filters' => [], 'time-range' => ['start' => new DateTime('2013-09-12 14:00:00', new DateTimeZone('UTC')), 'end' => null], 'prop-filters' => []]],],
+ 'end' => [[0], [], [['name' => 'VEVENT', 'is-not-defined' => false, 'comp-filters' => [], 'time-range' => ['start' => null, 'end' => new DateTime('2013-09-12 14:00:00', new DateTimeZone('UTC'))], 'prop-filters' => []]],],
+ ];
+ }
+
+ private function createTestCalendar() {
+ $this->backend->createCalendar(self::UNIT_TEST_USER, 'Example', [
+ '{http://apple.com/ns/ical/}calendar-color' => '#1C4587FF'
+ ]);
+ $calendars = $this->backend->getCalendarsForUser(self::UNIT_TEST_USER);
+ $this->assertEquals(1, count($calendars));
+ $this->assertEquals(self::UNIT_TEST_USER, $calendars[0]['principaluri']);
+ /** @var SupportedCalendarComponentSet $components */
+ $components = $calendars[0]['{urn:ietf:params:xml:ns:caldav}supported-calendar-component-set'];
+ $this->assertEquals(['VEVENT','VTODO'], $components->getValue());
+ $color = $calendars[0]['{http://apple.com/ns/ical/}calendar-color'];
+ $this->assertEquals('#1C4587FF', $color);
+ $this->assertEquals('Example', $calendars[0]['uri']);
+ $this->assertEquals('Example', $calendars[0]['{DAV:}displayname']);
+ $calendarId = $calendars[0]['id'];
+
+ return $calendarId;
+ }
+
+ private function createEvent($calendarId, $start = '20130912T130000Z', $end = '20130912T140000Z') {
+
+ $calData = <<<EOD
+BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:ownCloud Calendar
+BEGIN:VEVENT
+CREATED;VALUE=DATE-TIME:20130910T125139Z
+UID:47d15e3ec8
+LAST-MODIFIED;VALUE=DATE-TIME:20130910T125139Z
+DTSTAMP;VALUE=DATE-TIME:20130910T125139Z
+SUMMARY:Test Event
+DTSTART;VALUE=DATE-TIME:$start
+DTEND;VALUE=DATE-TIME:$end
+CLASS:PUBLIC
+END:VEVENT
+END:VCALENDAR
+EOD;
+ $uri0 = $this->getUniqueID('event');
+ $this->backend->createCalendarObject($calendarId, $uri0, $calData);
+
+ return $uri0;
+ }
+
+ public function testSyncSupport() {
+ $calendarId = $this->createTestCalendar();
+
+ // fist call without synctoken
+ $changes = $this->backend->getChangesForCalendar($calendarId, '', 1);
+ $syncToken = $changes['syncToken'];
+
+ // add a change
+ $event = $this->createEvent($calendarId, '20130912T130000Z', '20130912T140000Z');
+
+ // look for changes
+ $changes = $this->backend->getChangesForCalendar($calendarId, $syncToken, 1);
+ $this->assertEquals($event, $changes['added'][0]);
+ }
+
+ public function testSubscriptions() {
+ $id = $this->backend->createSubscription(self::UNIT_TEST_USER, 'Subscription', [
+ '{http://calendarserver.org/ns/}source' => new Href('test-source')
+ ]);
+
+ $subscriptions = $this->backend->getSubscriptionsForUser(self::UNIT_TEST_USER);
+ $this->assertEquals(1, count($subscriptions));
+ $this->assertEquals($id, $subscriptions[0]['id']);
+
+ $patch = new PropPatch([
+ '{DAV:}displayname' => 'Unit test',
+ ]);
+ $this->backend->updateSubscription($id, $patch);
+ $patch->commit();
+
+ $subscriptions = $this->backend->getSubscriptionsForUser(self::UNIT_TEST_USER);
+ $this->assertEquals(1, count($subscriptions));
+ $this->assertEquals($id, $subscriptions[0]['id']);
+ $this->assertEquals('Unit test', $subscriptions[0]['{DAV:}displayname']);
+
+ $this->backend->deleteSubscription($id);
+ $subscriptions = $this->backend->getSubscriptionsForUser(self::UNIT_TEST_USER);
+ $this->assertEquals(0, count($subscriptions));
+ }
+
+ public function testScheduling() {
+ $this->backend->createSchedulingObject(self::UNIT_TEST_USER, 'Sample Schedule', '');
+
+ $sos = $this->backend->getSchedulingObjects(self::UNIT_TEST_USER);
+ $this->assertEquals(1, count($sos));
+
+ $so = $this->backend->getSchedulingObject(self::UNIT_TEST_USER, 'Sample Schedule');
+ $this->assertNotNull($so);
+
+ $this->backend->deleteSchedulingObject(self::UNIT_TEST_USER, 'Sample Schedule');
+
+ $sos = $this->backend->getSchedulingObjects(self::UNIT_TEST_USER);
+ $this->assertEquals(0, count($sos));
+ }
+}
diff --git a/apps/dav/tests/unit/connector/sabre/directory.php b/apps/dav/tests/unit/connector/sabre/directory.php
index 148a91d26db..75c4828641b 100644
--- a/apps/dav/tests/unit/connector/sabre/directory.php
+++ b/apps/dav/tests/unit/connector/sabre/directory.php
@@ -9,6 +9,8 @@
namespace OCA\DAV\Tests\Unit\Connector\Sabre;
+use OCP\Files\ForbiddenException;
+
class Directory extends \Test\TestCase {
/** @var \OC\Files\View | \PHPUnit_Framework_MockObject_MockObject */
@@ -49,6 +51,25 @@ class Directory extends \Test\TestCase {
}
/**
+ * @expectedException \OCA\DAV\Connector\Sabre\Exception\Forbidden
+ */
+ public function testDeleteForbidden() {
+ // deletion allowed
+ $this->info->expects($this->once())
+ ->method('isDeletable')
+ ->will($this->returnValue(true));
+
+ // but fails
+ $this->view->expects($this->once())
+ ->method('rmdir')
+ ->with('sub')
+ ->willThrowException(new ForbiddenException('', true));
+
+ $dir = $this->getDir('sub');
+ $dir->delete();
+ }
+
+ /**
*
*/
public function testDeleteFolderWhenAllowed() {
diff --git a/apps/dav/tests/unit/connector/sabre/exception/forbiddentest.php b/apps/dav/tests/unit/connector/sabre/exception/forbiddentest.php
new file mode 100644
index 00000000000..19799c71b9e
--- /dev/null
+++ b/apps/dav/tests/unit/connector/sabre/exception/forbiddentest.php
@@ -0,0 +1,44 @@
+<?php
+/**
+ * Copyright (c) 2015 Thomas Müller <deepdiver@owncloud.com>
+ * This file is licensed under the Affero General Public License version 3 or
+ * later.
+ * See the COPYING-README file.
+ */
+
+namespace OCA\DAV\Tests\Unit\Connector\Sabre\Exception;
+
+use OCA\DAV\Connector\Sabre\Exception\Forbidden;
+
+class ForbiddenTest extends \Test\TestCase {
+
+ public function testSerialization() {
+
+ // create xml doc
+ $DOM = new \DOMDocument('1.0','utf-8');
+ $DOM->formatOutput = true;
+ $error = $DOM->createElementNS('DAV:','d:error');
+ $error->setAttribute('xmlns:s', \Sabre\DAV\Server::NS_SABREDAV);
+ $DOM->appendChild($error);
+
+ // serialize the exception
+ $message = "1234567890";
+ $retry = false;
+ $expectedXml = <<<EOD
+<?xml version="1.0" encoding="utf-8"?>
+<d:error xmlns:d="DAV:" xmlns:s="http://sabredav.org/ns" xmlns:o="http://owncloud.org/ns">
+ <o:retry xmlns:o="o:">false</o:retry>
+ <o:reason xmlns:o="o:">1234567890</o:reason>
+</d:error>
+
+EOD;
+
+ $ex = new Forbidden($message, $retry);
+ $server = $this->getMock('Sabre\DAV\Server');
+ $ex->serialize($server, $error);
+
+ // assert
+ $xml = $DOM->saveXML();
+ $this->assertEquals($expectedXml, $xml);
+ }
+}
diff --git a/apps/dav/tests/unit/connector/sabre/exception/invalidpathtest.php b/apps/dav/tests/unit/connector/sabre/exception/invalidpathtest.php
index 19e82320d55..4296a4d5618 100644
--- a/apps/dav/tests/unit/connector/sabre/exception/invalidpathtest.php
+++ b/apps/dav/tests/unit/connector/sabre/exception/invalidpathtest.php
@@ -1,15 +1,15 @@
<?php
-
-namespace OCA\DAV\Tests\Unit\Connector\Sabre\Exception;
-
-use OCA\DAV\Connector\Sabre\Exception\InvalidPath;
-
/**
* Copyright (c) 2015 Thomas Müller <deepdiver@owncloud.com>
* This file is licensed under the Affero General Public License version 3 or
* later.
* See the COPYING-README file.
*/
+
+namespace OCA\DAV\Tests\Unit\Connector\Sabre\Exception;
+
+use OCA\DAV\Connector\Sabre\Exception\InvalidPath;
+
class InvalidPathTest extends \Test\TestCase {
public function testSerialization() {
diff --git a/apps/dav/tests/unit/connector/sabre/file.php b/apps/dav/tests/unit/connector/sabre/file.php
index 94dadf88fe4..399634f8bee 100644
--- a/apps/dav/tests/unit/connector/sabre/file.php
+++ b/apps/dav/tests/unit/connector/sabre/file.php
@@ -9,6 +9,7 @@
namespace OCA\DAV\Tests\Unit\Connector\Sabre;
use OC\Files\Storage\Local;
+use OCP\Files\ForbiddenException;
use Test\HookHelper;
use OC\Files\Filesystem;
use OCP\Lock\ILockingProvider;
@@ -73,6 +74,10 @@ class File extends \Test\TestCase {
'Sabre\DAV\Exception\Forbidden'
],
[
+ new \OCP\Files\ForbiddenException('', true),
+ 'OCA\DAV\Connector\Sabre\Exception\Forbidden'
+ ],
+ [
new \OCP\Files\LockNotAcquiredException('/test.txt', 1),
'OCA\DAV\Connector\Sabre\Exception\FileLocked'
],
@@ -691,6 +696,29 @@ class File extends \Test\TestCase {
}
/**
+ * @expectedException \OCA\DAV\Connector\Sabre\Exception\Forbidden
+ */
+ public function testDeleteThrowsWhenDeletionThrows() {
+ // setup
+ $view = $this->getMock('\OC\Files\View',
+ array());
+
+ // but fails
+ $view->expects($this->once())
+ ->method('unlink')
+ ->willThrowException(new ForbiddenException('', true));
+
+ $info = new \OC\Files\FileInfo('/test.txt', null, null, array(
+ 'permissions' => \OCP\Constants::PERMISSION_ALL
+ ), null);
+
+ $file = new \OCA\DAV\Connector\Sabre\File($view, $info);
+
+ // action
+ $file->delete();
+ }
+
+ /**
* Asserts hook call
*
* @param array $callData hook call data to check
@@ -835,4 +863,22 @@ class File extends \Test\TestCase {
$file->get();
}
+
+ /**
+ * @expectedException \OCA\DAV\Connector\Sabre\Exception\Forbidden
+ */
+ public function testGetFopenThrows() {
+ $view = $this->getMock('\OC\Files\View', ['fopen'], array());
+ $view->expects($this->atLeastOnce())
+ ->method('fopen')
+ ->willThrowException(new ForbiddenException('', true));
+
+ $info = new \OC\Files\FileInfo('/test.txt', null, null, array(
+ 'permissions' => \OCP\Constants::PERMISSION_ALL
+ ), null);
+
+ $file = new \OCA\DAV\Connector\Sabre\File($view, $info);
+
+ $file->get();
+ }
}
diff --git a/apps/dav/tests/unit/connector/sabre/filesplugin.php b/apps/dav/tests/unit/connector/sabre/filesplugin.php
index f3c862941c0..2e3338fefa1 100644
--- a/apps/dav/tests/unit/connector/sabre/filesplugin.php
+++ b/apps/dav/tests/unit/connector/sabre/filesplugin.php
@@ -11,10 +11,13 @@ namespace OCA\DAV\Tests\Unit\Connector\Sabre;
class FilesPlugin extends \Test\TestCase {
const GETETAG_PROPERTYNAME = \OCA\DAV\Connector\Sabre\FilesPlugin::GETETAG_PROPERTYNAME;
const FILEID_PROPERTYNAME = \OCA\DAV\Connector\Sabre\FilesPlugin::FILEID_PROPERTYNAME;
+ const INTERNAL_FILEID_PROPERTYNAME = \OCA\DAV\Connector\Sabre\FilesPlugin::INTERNAL_FILEID_PROPERTYNAME;
const SIZE_PROPERTYNAME = \OCA\DAV\Connector\Sabre\FilesPlugin::SIZE_PROPERTYNAME;
const PERMISSIONS_PROPERTYNAME = \OCA\DAV\Connector\Sabre\FilesPlugin::PERMISSIONS_PROPERTYNAME;
const LASTMODIFIED_PROPERTYNAME = \OCA\DAV\Connector\Sabre\FilesPlugin::LASTMODIFIED_PROPERTYNAME;
const DOWNLOADURL_PROPERTYNAME = \OCA\DAV\Connector\Sabre\FilesPlugin::DOWNLOADURL_PROPERTYNAME;
+ const OWNER_ID_PROPERTYNAME = \OCA\DAV\Connector\Sabre\FilesPlugin::OWNER_ID_PROPERTYNAME;
+ const OWNER_DISPLAY_NAME_PROPERTYNAME = \OCA\DAV\Connector\Sabre\FilesPlugin::OWNER_DISPLAY_NAME_PROPERTYNAME;
/**
* @var \Sabre\DAV\Server
@@ -67,7 +70,10 @@ class FilesPlugin extends \Test\TestCase {
$node->expects($this->any())
->method('getFileId')
- ->will($this->returnValue(123));
+ ->will($this->returnValue('00000123instanceid'));
+ $node->expects($this->any())
+ ->method('getInternalFileId')
+ ->will($this->returnValue('123'));
$node->expects($this->any())
->method('getEtag')
->will($this->returnValue('"abc"'));
@@ -88,16 +94,33 @@ class FilesPlugin extends \Test\TestCase {
array(
self::GETETAG_PROPERTYNAME,
self::FILEID_PROPERTYNAME,
+ self::INTERNAL_FILEID_PROPERTYNAME,
self::SIZE_PROPERTYNAME,
self::PERMISSIONS_PROPERTYNAME,
self::DOWNLOADURL_PROPERTYNAME,
+ self::OWNER_ID_PROPERTYNAME,
+ self::OWNER_DISPLAY_NAME_PROPERTYNAME
),
0
);
+ $user = $this->getMockBuilder('\OC\User\User')
+ ->disableOriginalConstructor()->getMock();
+ $user
+ ->expects($this->once())
+ ->method('getUID')
+ ->will($this->returnValue('foo'));
+ $user
+ ->expects($this->once())
+ ->method('getDisplayName')
+ ->will($this->returnValue('M. Foo'));
+
$node->expects($this->once())
->method('getDirectDownload')
->will($this->returnValue(array('url' => 'http://example.com/')));
+ $node->expects($this->exactly(2))
+ ->method('getOwner')
+ ->will($this->returnValue($user));
$node->expects($this->never())
->method('getSize');
@@ -107,10 +130,13 @@ class FilesPlugin extends \Test\TestCase {
);
$this->assertEquals('"abc"', $propFind->get(self::GETETAG_PROPERTYNAME));
- $this->assertEquals(123, $propFind->get(self::FILEID_PROPERTYNAME));
+ $this->assertEquals('00000123instanceid', $propFind->get(self::FILEID_PROPERTYNAME));
+ $this->assertEquals('123', $propFind->get(self::INTERNAL_FILEID_PROPERTYNAME));
$this->assertEquals(null, $propFind->get(self::SIZE_PROPERTYNAME));
$this->assertEquals('DWCKMSR', $propFind->get(self::PERMISSIONS_PROPERTYNAME));
$this->assertEquals('http://example.com/', $propFind->get(self::DOWNLOADURL_PROPERTYNAME));
+ $this->assertEquals('foo', $propFind->get(self::OWNER_ID_PROPERTYNAME));
+ $this->assertEquals('M. Foo', $propFind->get(self::OWNER_DISPLAY_NAME_PROPERTYNAME));
$this->assertEquals(array(self::SIZE_PROPERTYNAME), $propFind->get404Properties());
}
@@ -166,7 +192,7 @@ class FilesPlugin extends \Test\TestCase {
);
$this->assertEquals('"abc"', $propFind->get(self::GETETAG_PROPERTYNAME));
- $this->assertEquals(123, $propFind->get(self::FILEID_PROPERTYNAME));
+ $this->assertEquals('00000123instanceid', $propFind->get(self::FILEID_PROPERTYNAME));
$this->assertEquals(1025, $propFind->get(self::SIZE_PROPERTYNAME));
$this->assertEquals('DWCKMSR', $propFind->get(self::PERMISSIONS_PROPERTYNAME));
$this->assertEquals(null, $propFind->get(self::DOWNLOADURL_PROPERTYNAME));
@@ -207,6 +233,36 @@ class FilesPlugin extends \Test\TestCase {
$this->assertEquals(200, $result[self::GETETAG_PROPERTYNAME]);
}
+ public function testUpdatePropsForbidden() {
+ $node = $this->createTestNode('\OCA\DAV\Connector\Sabre\File');
+
+ $propPatch = new \Sabre\DAV\PropPatch(array(
+ self::OWNER_ID_PROPERTYNAME => 'user2',
+ self::OWNER_DISPLAY_NAME_PROPERTYNAME => 'User Two',
+ self::FILEID_PROPERTYNAME => 12345,
+ self::PERMISSIONS_PROPERTYNAME => 'C',
+ self::SIZE_PROPERTYNAME => 123,
+ self::DOWNLOADURL_PROPERTYNAME => 'http://example.com/',
+ ));
+
+ $this->plugin->handleUpdateProperties(
+ '/dummypath',
+ $propPatch
+ );
+
+ $propPatch->commit();
+
+ $this->assertEmpty($propPatch->getRemainingMutations());
+
+ $result = $propPatch->getResult();
+ $this->assertEquals(403, $result[self::OWNER_ID_PROPERTYNAME]);
+ $this->assertEquals(403, $result[self::OWNER_DISPLAY_NAME_PROPERTYNAME]);
+ $this->assertEquals(403, $result[self::FILEID_PROPERTYNAME]);
+ $this->assertEquals(403, $result[self::PERMISSIONS_PROPERTYNAME]);
+ $this->assertEquals(403, $result[self::SIZE_PROPERTYNAME]);
+ $this->assertEquals(403, $result[self::DOWNLOADURL_PROPERTYNAME]);
+ }
+
/**
* Testcase from https://github.com/owncloud/core/issues/5251
*
diff --git a/apps/files/appinfo/app.php b/apps/files/appinfo/app.php
index 40b194ab882..c752b5e7d72 100644
--- a/apps/files/appinfo/app.php
+++ b/apps/files/appinfo/app.php
@@ -25,12 +25,14 @@
*/
\OCP\App::registerAdmin('files', 'admin');
+
\OC::$server->getNavigationManager()->add(function () {
+ $urlGenerator = \OC::$server->getURLGenerator();
$l = \OC::$server->getL10N('files');
return [
'id' => 'files_index',
'order' => 0,
- 'href' => \OCP\Util::linkTo('files', 'index.php'),
+ 'href' => $urlGenerator->linkToRoute('files.view.index'),
'icon' => \OCP\Util::imagePath('core', 'places/files.svg'),
'name' => $l->t('Files'),
];
diff --git a/apps/files/appinfo/routes.php b/apps/files/appinfo/routes.php
index d52dfaab21c..2bb913c30a6 100644
--- a/apps/files/appinfo/routes.php
+++ b/apps/files/appinfo/routes.php
@@ -48,14 +48,17 @@ $application->registerRoutes(
'verb' => 'GET',
'requirements' => array('tagName' => '.+'),
),
+ [
+ 'name' => 'view#index',
+ 'url' => '/',
+ 'verb' => 'GET',
+ ],
)
)
);
/** @var $this \OC\Route\Router */
-$this->create('files_index', '/')
- ->actionInclude('files/index.php');
$this->create('files_ajax_delete', 'ajax/delete.php')
->actionInclude('files/ajax/delete.php');
$this->create('files_ajax_download', 'ajax/download.php')
diff --git a/apps/files/controller/viewcontroller.php b/apps/files/controller/viewcontroller.php
new file mode 100644
index 00000000000..c274680e525
--- /dev/null
+++ b/apps/files/controller/viewcontroller.php
@@ -0,0 +1,224 @@
+<?php
+/**
+ * @author Lukas Reschke <lukas@owncloud.com>
+ *
+ * @copyright Copyright (c) 2015, ownCloud, Inc.
+ * @license AGPL-3.0
+ *
+ * This code is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License, version 3,
+ * along with this program. If not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+namespace OCA\Files\Controller;
+
+use OC\AppFramework\Http\Request;
+use OCP\AppFramework\Controller;
+use OCP\AppFramework\Http\RedirectResponse;
+use OCP\AppFramework\Http\TemplateResponse;
+use OCP\IL10N;
+use OCP\INavigationManager;
+use OCP\IRequest;
+use OCP\IURLGenerator;
+use OCP\IConfig;
+use Symfony\Component\EventDispatcher\EventDispatcherInterface;
+
+/**
+ * Class ViewController
+ *
+ * @package OCA\Files\Controller
+ */
+class ViewController extends Controller {
+ /** @var string */
+ protected $appName;
+ /** @var IRequest */
+ protected $request;
+ /** @var IURLGenerator */
+ protected $urlGenerator;
+ /** @var INavigationManager */
+ protected $navigationManager;
+ /** @var IL10N */
+ protected $l10n;
+ /** @var IConfig */
+ protected $config;
+ /** @var EventDispatcherInterface */
+ protected $eventDispatcher;
+
+ /**
+ * @param string $appName
+ * @param IRequest $request
+ * @param IURLGenerator $urlGenerator
+ * @param INavigationManager $navigationManager
+ * @param IL10N $l10n
+ * @param IConfig $config
+ * @param EventDispatcherInterface $eventDispatcherInterface
+ */
+ public function __construct($appName,
+ IRequest $request,
+ IURLGenerator $urlGenerator,
+ INavigationManager $navigationManager,
+ IL10N $l10n,
+ IConfig $config,
+ EventDispatcherInterface $eventDispatcherInterface) {
+ parent::__construct($appName, $request);
+ $this->appName = $appName;
+ $this->request = $request;
+ $this->urlGenerator = $urlGenerator;
+ $this->navigationManager = $navigationManager;
+ $this->l10n = $l10n;
+ $this->config = $config;
+ $this->eventDispatcher = $eventDispatcherInterface;
+ }
+
+ /**
+ * @param string $appName
+ * @param string $scriptName
+ * @return string
+ */
+ protected function renderScript($appName, $scriptName) {
+ $content = '';
+ $appPath = \OC_App::getAppPath($appName);
+ $scriptPath = $appPath . '/' . $scriptName;
+ if (file_exists($scriptPath)) {
+ // TODO: sanitize path / script name ?
+ ob_start();
+ include $scriptPath;
+ $content = ob_get_contents();
+ @ob_end_clean();
+ }
+ return $content;
+ }
+
+ /**
+ * FIXME: Replace with non static code
+ *
+ * @return array
+ * @throws \OCP\Files\NotFoundException
+ */
+ protected function getStorageInfo() {
+ $dirInfo = \OC\Files\Filesystem::getFileInfo('/', false);
+ return \OC_Helper::getStorageInfo('/', $dirInfo);
+ }
+
+ /**
+ * @NoCSRFRequired
+ * @NoAdminRequired
+ *
+ * @param string $dir
+ * @param string $view
+ * @return TemplateResponse
+ * @throws \OCP\Files\NotFoundException
+ */
+ public function index($dir = '', $view = '') {
+ // Load the files we need
+ \OCP\Util::addStyle('files', 'files');
+ \OCP\Util::addStyle('files', 'upload');
+ \OCP\Util::addStyle('files', 'mobile');
+ \OCP\Util::addscript('files', 'app');
+ \OCP\Util::addscript('files', 'file-upload');
+ \OCP\Util::addscript('files', 'newfilemenu');
+ \OCP\Util::addscript('files', 'jquery.iframe-transport');
+ \OCP\Util::addscript('files', 'jquery.fileupload');
+ \OCP\Util::addscript('files', 'jquery-visibility');
+ \OCP\Util::addscript('files', 'fileinfomodel');
+ \OCP\Util::addscript('files', 'filesummary');
+ \OCP\Util::addscript('files', 'breadcrumb');
+ \OCP\Util::addscript('files', 'filelist');
+ \OCP\Util::addscript('files', 'search');
+
+ \OCP\Util::addScript('files', 'favoritesfilelist');
+ \OCP\Util::addScript('files', 'tagsplugin');
+ \OCP\Util::addScript('files', 'favoritesplugin');
+
+ \OCP\Util::addScript('files', 'detailfileinfoview');
+ \OCP\Util::addScript('files', 'detailtabview');
+ \OCP\Util::addScript('files', 'mainfileinfodetailview');
+ \OCP\Util::addScript('files', 'detailsview');
+ \OCP\Util::addStyle('files', 'detailsView');
+
+ \OC_Util::addVendorScript('core', 'handlebars/handlebars');
+
+ \OCP\Util::addscript('files', 'fileactions');
+ \OCP\Util::addscript('files', 'fileactionsmenu');
+ \OCP\Util::addscript('files', 'files');
+ \OCP\Util::addscript('files', 'keyboardshortcuts');
+ \OCP\Util::addscript('files', 'navigation');
+
+ // if IE8 and "?dir=path&view=someview" was specified, reformat the URL to use a hash like "#?dir=path&view=someview"
+ $isIE8 = $this->request->isUserAgent([Request::USER_AGENT_IE_8]);
+ if ($isIE8 && ($dir !== '' || $view !== '')) {
+ $dir = !empty($dir) ? $dir : '/';
+ $view = !empty($view) ? $view : 'files';
+ $hash = '#?dir=' . \OCP\Util::encodePath($dir);
+ if ($view !== 'files') {
+ $hash .= '&view=' . urlencode($view);
+ }
+ return new RedirectResponse($this->urlGenerator->linkToRoute('files.view.index') . $hash);
+ }
+
+ // mostly for the home storage's free space
+ // FIXME: Make non static
+ $storageInfo = $this->getStorageInfo();
+
+ $nav = new \OCP\Template('files', 'appnavigation', '');
+
+ \OCA\Files\App::getNavigationManager()->add(
+ [
+ 'id' => 'favorites',
+ 'appname' => 'files',
+ 'script' => 'simplelist.php',
+ 'order' => 5,
+ 'name' => $this->l10n->t('Favorites')
+ ]
+ );
+
+ $navItems = \OCA\Files\App::getNavigationManager()->getAll();
+ usort($navItems, function($item1, $item2) {
+ return $item1['order'] - $item2['order'];
+ });
+ $nav->assign('navigationItems', $navItems);
+
+ $contentItems = [];
+
+ // render the container content for every navigation item
+ foreach ($navItems as $item) {
+ $content = '';
+ if (isset($item['script'])) {
+ $content = $this->renderScript($item['appname'], $item['script']);
+ }
+ $contentItem = [];
+ $contentItem['id'] = $item['id'];
+ $contentItem['content'] = $content;
+ $contentItems[] = $contentItem;
+ }
+
+ $this->eventDispatcher->dispatch('OCA\Files::loadAdditionalScripts');
+
+ $params = [];
+ $params['usedSpacePercent'] = (int)$storageInfo['relative'];
+ $params['owner'] = $storageInfo['owner'];
+ $params['ownerDisplayName'] = $storageInfo['ownerDisplayName'];
+ $params['isPublic'] = false;
+ $params['mailNotificationEnabled'] = $this->config->getAppValue('core', 'shareapi_allow_mail_notification', 'no');
+ $params['mailPublicNotificationEnabled'] = $this->config->getAppValue('core', 'shareapi_allow_public_notification', 'no');
+ $params['allowShareWithLink'] = $this->config->getAppValue('core', 'shareapi_allow_links', 'yes');
+ $params['appNavigation'] = $nav;
+ $params['appContents'] = $contentItems;
+ $this->navigationManager->setActiveEntry('files_index');
+
+ return new TemplateResponse(
+ $this->appName,
+ 'index',
+ $params
+ );
+ }
+}
diff --git a/apps/files/index.php b/apps/files/index.php
deleted file mode 100644
index cc007ebdb07..00000000000
--- a/apps/files/index.php
+++ /dev/null
@@ -1,161 +0,0 @@
-<?php
-/**
- * @author Björn Schießle <schiessle@owncloud.com>
- * @author Frank Karlitschek <frank@owncloud.org>
- * @author Jakob Sack <mail@jakobsack.de>
- * @author Jan-Christoph Borchardt <hey@jancborchardt.net>
- * @author Joas Schilling <nickvergessen@owncloud.com>
- * @author Jörn Friedrich Dreyer <jfd@butonic.de>
- * @author Morris Jobke <hey@morrisjobke.de>
- * @author Robin Appelman <icewind@owncloud.com>
- * @author Roman Geber <rgeber@owncloudapps.com>
- * @author Thomas Müller <thomas.mueller@tmit.eu>
- * @author Vincent Petry <pvince81@owncloud.com>
- *
- * @copyright Copyright (c) 2015, ownCloud, Inc.
- * @license AGPL-3.0
- *
- * This code is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License, version 3,
- * as published by the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License, version 3,
- * along with this program. If not, see <http://www.gnu.org/licenses/>
- *
- */
-
-// Check if we are a user
-OCP\User::checkLoggedIn();
-
-// Load the files we need
-OCP\Util::addStyle('files', 'files');
-OCP\Util::addStyle('files', 'upload');
-OCP\Util::addStyle('files', 'mobile');
-OCP\Util::addscript('files', 'app');
-OCP\Util::addscript('files', 'file-upload');
-OCP\Util::addscript('files', 'newfilemenu');
-OCP\Util::addscript('files', 'jquery.iframe-transport');
-OCP\Util::addscript('files', 'jquery.fileupload');
-OCP\Util::addscript('files', 'jquery-visibility');
-OCP\Util::addscript('files', 'fileinfomodel');
-OCP\Util::addscript('files', 'filesummary');
-OCP\Util::addscript('files', 'breadcrumb');
-OCP\Util::addscript('files', 'filelist');
-OCP\Util::addscript('files', 'search');
-
-\OCP\Util::addScript('files', 'favoritesfilelist');
-\OCP\Util::addScript('files', 'tagsplugin');
-\OCP\Util::addScript('files', 'favoritesplugin');
-
-\OCP\Util::addScript('files', 'detailfileinfoview');
-\OCP\Util::addScript('files', 'detailtabview');
-\OCP\Util::addScript('files', 'mainfileinfodetailview');
-\OCP\Util::addScript('files', 'detailsview');
-\OCP\Util::addStyle('files', 'detailsView');
-
-\OC_Util::addVendorScript('core', 'handlebars/handlebars');
-
-OCP\App::setActiveNavigationEntry('files_index');
-
-$l = \OC::$server->getL10N('files');
-
-$isIE8 = false;
-preg_match('/MSIE (.*?);/', $_SERVER['HTTP_USER_AGENT'], $matches);
-if (count($matches) > 0 && $matches[1] <= 9) {
- $isIE8 = true;
-}
-
-// if IE8 and "?dir=path&view=someview" was specified, reformat the URL to use a hash like "#?dir=path&view=someview"
-if ($isIE8 && (isset($_GET['dir']) || isset($_GET['view']))) {
- $hash = '#?';
- $dir = isset($_GET['dir']) ? $_GET['dir'] : '/';
- $view = isset($_GET['view']) ? $_GET['view'] : 'files';
- $hash = '#?dir=' . \OCP\Util::encodePath($dir);
- if ($view !== 'files') {
- $hash .= '&view=' . urlencode($view);
- }
- header('Location: ' . OCP\Util::linkTo('files', 'index.php') . $hash);
- exit();
-}
-
-$user = OC_User::getUser();
-
-$config = \OC::$server->getConfig();
-
-// mostly for the home storage's free space
-$dirInfo = \OC\Files\Filesystem::getFileInfo('/', false);
-$storageInfo=OC_Helper::getStorageInfo('/', $dirInfo);
-
-$nav = new OCP\Template('files', 'appnavigation', '');
-
-function sortNavigationItems($item1, $item2) {
- return $item1['order'] - $item2['order'];
-}
-
-\OCA\Files\App::getNavigationManager()->add(
- array(
- 'id' => 'favorites',
- 'appname' => 'files',
- 'script' => 'simplelist.php',
- 'order' => 5,
- 'name' => $l->t('Favorites')
- )
-);
-
-$navItems = \OCA\Files\App::getNavigationManager()->getAll();
-usort($navItems, 'sortNavigationItems');
-$nav->assign('navigationItems', $navItems);
-
-$contentItems = array();
-
-function renderScript($appName, $scriptName) {
- $content = '';
- $appPath = OC_App::getAppPath($appName);
- $scriptPath = $appPath . '/' . $scriptName;
- if (file_exists($scriptPath)) {
- // TODO: sanitize path / script name ?
- ob_start();
- include $scriptPath;
- $content = ob_get_contents();
- @ob_end_clean();
- }
- return $content;
-}
-
-// render the container content for every navigation item
-foreach ($navItems as $item) {
- $content = '';
- if (isset($item['script'])) {
- $content = renderScript($item['appname'], $item['script']);
- }
- $contentItem = array();
- $contentItem['id'] = $item['id'];
- $contentItem['content'] = $content;
- $contentItems[] = $contentItem;
-}
-
-OCP\Util::addscript('files', 'fileactions');
-OCP\Util::addscript('files', 'fileactionsmenu');
-OCP\Util::addscript('files', 'files');
-OCP\Util::addscript('files', 'navigation');
-OCP\Util::addscript('files', 'keyboardshortcuts');
-
-\OC::$server->getEventDispatcher()->dispatch('OCA\Files::loadAdditionalScripts');
-
-$tmpl = new OCP\Template('files', 'index', 'user');
-$tmpl->assign('usedSpacePercent', (int)$storageInfo['relative']);
-$tmpl->assign('owner', $storageInfo['owner']);
-$tmpl->assign('ownerDisplayName', $storageInfo['ownerDisplayName']);
-$tmpl->assign('isPublic', false);
-$tmpl->assign("mailNotificationEnabled", $config->getAppValue('core', 'shareapi_allow_mail_notification', 'no'));
-$tmpl->assign("mailPublicNotificationEnabled", $config->getAppValue('core', 'shareapi_allow_public_notification', 'no'));
-$tmpl->assign("allowShareWithLink", $config->getAppValue('core', 'shareapi_allow_links', 'yes'));
-$tmpl->assign('appNavigation', $nav);
-$tmpl->assign('appContents', $contentItems);
-
-$tmpl->printPage();
diff --git a/apps/files/l10n/hu_HU.js b/apps/files/l10n/hu_HU.js
index 48e9bfb168b..3c12e4bd486 100644
--- a/apps/files/l10n/hu_HU.js
+++ b/apps/files/l10n/hu_HU.js
@@ -106,6 +106,8 @@ OC.L10N.register(
"Maximum upload size" : "Maximális feltölthető fájlméret",
"max. possible: " : "max. lehetséges: ",
"Save" : "Mentés",
+ "With PHP-FPM it might take 5 minutes for changes to be applied." : "PHP-FPM-mel akár 5 percbe is telhet, míg ez a beállítás érvénybe lép.",
+ "Missing permissions to edit from here." : "Innen nem lehet szerkeszteni hiányzó jogosultság miatt.",
"Settings" : "Beállítások",
"WebDAV" : "WebDAV",
"Use this address to <a href=\"%s\" target=\"_blank\">access your Files via WebDAV</a>" : "Ezt a címet használja, ha <a href=\"%s\" target=\"_blank\">WebDAV-on keresztül szeretné elérni a fájljait</a>",
diff --git a/apps/files/l10n/hu_HU.json b/apps/files/l10n/hu_HU.json
index c72dfa0d48b..b1bde8b7d51 100644
--- a/apps/files/l10n/hu_HU.json
+++ b/apps/files/l10n/hu_HU.json
@@ -104,6 +104,8 @@
"Maximum upload size" : "Maximális feltölthető fájlméret",
"max. possible: " : "max. lehetséges: ",
"Save" : "Mentés",
+ "With PHP-FPM it might take 5 minutes for changes to be applied." : "PHP-FPM-mel akár 5 percbe is telhet, míg ez a beállítás érvénybe lép.",
+ "Missing permissions to edit from here." : "Innen nem lehet szerkeszteni hiányzó jogosultság miatt.",
"Settings" : "Beállítások",
"WebDAV" : "WebDAV",
"Use this address to <a href=\"%s\" target=\"_blank\">access your Files via WebDAV</a>" : "Ezt a címet használja, ha <a href=\"%s\" target=\"_blank\">WebDAV-on keresztül szeretné elérni a fájljait</a>",
diff --git a/apps/files/l10n/nl.js b/apps/files/l10n/nl.js
index d60598ca147..88eb77c02a5 100644
--- a/apps/files/l10n/nl.js
+++ b/apps/files/l10n/nl.js
@@ -105,6 +105,8 @@ OC.L10N.register(
"Maximum upload size" : "Maximale bestandsgrootte voor uploads",
"max. possible: " : "max. mogelijk: ",
"Save" : "Bewaren",
+ "With PHP-FPM it might take 5 minutes for changes to be applied." : "Met PHP-FPM kan het 5 minuten duren voordat wijzigingen zijn doorgevoerd.",
+ "Missing permissions to edit from here." : "Ontbrekende rechten om vanaf hier te bewerken.",
"Settings" : "Instellingen",
"WebDAV" : "WebDAV",
"Use this address to <a href=\"%s\" target=\"_blank\">access your Files via WebDAV</a>" : "Gebruik deze link <a href=\"%s\" target=\"_blank\">om uw bestanden via WebDAV te benaderen</a>",
diff --git a/apps/files/l10n/nl.json b/apps/files/l10n/nl.json
index 28f80c09af7..0eca44aaa0d 100644
--- a/apps/files/l10n/nl.json
+++ b/apps/files/l10n/nl.json
@@ -103,6 +103,8 @@
"Maximum upload size" : "Maximale bestandsgrootte voor uploads",
"max. possible: " : "max. mogelijk: ",
"Save" : "Bewaren",
+ "With PHP-FPM it might take 5 minutes for changes to be applied." : "Met PHP-FPM kan het 5 minuten duren voordat wijzigingen zijn doorgevoerd.",
+ "Missing permissions to edit from here." : "Ontbrekende rechten om vanaf hier te bewerken.",
"Settings" : "Instellingen",
"WebDAV" : "WebDAV",
"Use this address to <a href=\"%s\" target=\"_blank\">access your Files via WebDAV</a>" : "Gebruik deze link <a href=\"%s\" target=\"_blank\">om uw bestanden via WebDAV te benaderen</a>",
diff --git a/apps/files/tests/controller/ViewControllerTest.php b/apps/files/tests/controller/ViewControllerTest.php
new file mode 100644
index 00000000000..028dfce8c58
--- /dev/null
+++ b/apps/files/tests/controller/ViewControllerTest.php
@@ -0,0 +1,250 @@
+<?php
+/**
+ * @author Lukas Reschke <lukas@owncloud.com>
+ *
+ * @copyright Copyright (c) 2015, ownCloud, Inc.
+ * @license AGPL-3.0
+ *
+ * This code is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License, version 3,
+ * along with this program. If not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+namespace OCA\Files\Tests\Controller;
+
+use OCA\Files\Controller\ViewController;
+use OCP\AppFramework\Http;
+use OCP\Template;
+use Test\TestCase;
+use OCP\IRequest;
+use OCP\IURLGenerator;
+use OCP\AppFramework\Http\RedirectResponse;
+use OCP\INavigationManager;
+use OCP\IL10N;
+use OCP\IConfig;
+use Symfony\Component\EventDispatcher\EventDispatcherInterface;
+
+/**
+ * Class ViewControllerTest
+ *
+ * @package OCA\Files\Tests\Controller
+ */
+class ViewControllerTest extends TestCase {
+ /** @var IRequest */
+ private $request;
+ /** @var IURLGenerator */
+ private $urlGenerator;
+ /** @var INavigationManager */
+ private $navigationManager;
+ /** @var IL10N */
+ private $l10n;
+ /** @var IConfig */
+ private $config;
+ /** @var EventDispatcherInterface */
+ private $eventDispatcher;
+ /** @var ViewController */
+ private $viewController;
+
+ public function setUp() {
+ parent::setUp();
+ $this->request = $this->getMock('\OCP\IRequest');
+ $this->urlGenerator = $this->getMock('\OCP\IURLGenerator');
+ $this->navigationManager = $this->getMock('\OCP\INavigationManager');
+ $this->l10n = $this->getMock('\OCP\IL10N');
+ $this->config = $this->getMock('\OCP\IConfig');
+ $this->eventDispatcher = $this->getMock('\Symfony\Component\EventDispatcher\EventDispatcherInterface');
+ $this->viewController = $this->getMockBuilder('\OCA\Files\Controller\ViewController')
+ ->setConstructorArgs([
+ 'files',
+ $this->request,
+ $this->urlGenerator,
+ $this->navigationManager,
+ $this->l10n,
+ $this->config,
+ $this->eventDispatcher
+ ])
+ ->setMethods([
+ 'getStorageInfo',
+ 'renderScript'
+ ])
+ ->getMock();
+ }
+
+ public function testIndexWithIE8RedirectAndDirDefined() {
+ $this->request
+ ->expects($this->once())
+ ->method('isUserAgent')
+ ->with(['/MSIE 8.0/'])
+ ->will($this->returnValue(true));
+ $this->urlGenerator
+ ->expects($this->once())
+ ->method('linkToRoute')
+ ->with('files.view.index')
+ ->will($this->returnValue('/apps/files/'));
+
+ $expected = new Http\RedirectResponse('/apps/files/#?dir=MyDir');
+ $this->assertEquals($expected, $this->viewController->index('MyDir'));
+ }
+
+ public function testIndexWithIE8RedirectAndViewDefined() {
+ $this->request
+ ->expects($this->once())
+ ->method('isUserAgent')
+ ->with(['/MSIE 8.0/'])
+ ->will($this->returnValue(true));
+ $this->urlGenerator
+ ->expects($this->once())
+ ->method('linkToRoute')
+ ->with('files.view.index')
+ ->will($this->returnValue('/apps/files/'));
+
+ $expected = new Http\RedirectResponse('/apps/files/#?dir=/&view=MyView');
+ $this->assertEquals($expected, $this->viewController->index('', 'MyView'));
+ }
+
+ public function testIndexWithIE8RedirectAndViewAndDirDefined() {
+ $this->request
+ ->expects($this->once())
+ ->method('isUserAgent')
+ ->with(['/MSIE 8.0/'])
+ ->will($this->returnValue(true));
+ $this->urlGenerator
+ ->expects($this->once())
+ ->method('linkToRoute')
+ ->with('files.view.index')
+ ->will($this->returnValue('/apps/files/'));
+
+ $expected = new RedirectResponse('/apps/files/#?dir=MyDir&view=MyView');
+ $this->assertEquals($expected, $this->viewController->index('MyDir', 'MyView'));
+ }
+
+ public function testIndexWithRegularBrowser() {
+ $this->request
+ ->expects($this->once())
+ ->method('isUserAgent')
+ ->with(['/MSIE 8.0/'])
+ ->will($this->returnValue(false));
+ $this->viewController
+ ->expects($this->once())
+ ->method('getStorageInfo')
+ ->will($this->returnValue([
+ 'relative' => 123,
+ 'owner' => 'MyName',
+ 'ownerDisplayName' => 'MyDisplayName',
+ ]));
+
+ $this->config
+ ->expects($this->any())
+ ->method('getAppValue')
+ ->will($this->returnArgument(2));
+
+ $nav = new Template('files', 'appnavigation');
+ $nav->assign('navigationItems', [
+ 0 => [
+ 'id' => 'files',
+ 'appname' => 'files',
+ 'script' => 'list.php',
+ 'order' => 0,
+ 'name' => new \OC_L10N_String(new \OC_L10N('files'), 'All files', []),
+ 'active' => false,
+ 'icon' => '',
+ ],
+ 1 => [
+ 'id' => 'favorites',
+ 'appname' => 'files',
+ 'script' => 'simplelist.php',
+ 'order' => 5,
+ 'name' => null,
+ 'active' => false,
+ 'icon' => '',
+ ],
+ 2 => [
+ 'id' => 'sharingin',
+ 'appname' => 'files_sharing',
+ 'script' => 'list.php',
+ 'order' => 10,
+ 'name' => new \OC_L10N_String(new \OC_L10N('files_sharing'), 'Shared with you', []),
+ 'active' => false,
+ 'icon' => '',
+ ],
+ 3 => [
+ 'id' => 'sharingout',
+ 'appname' => 'files_sharing',
+ 'script' => 'list.php',
+ 'order' => 15,
+ 'name' => new \OC_L10N_String(new \OC_L10N('files_sharing'), 'Shared with others', []),
+ 'active' => false,
+ 'icon' => '',
+ ],
+ 4 => [
+ 'id' => 'sharinglinks',
+ 'appname' => 'files_sharing',
+ 'script' => 'list.php',
+ 'order' => 20,
+ 'name' => new \OC_L10N_String(new \OC_L10N('files_sharing'), 'Shared by link', []),
+ 'active' => false,
+ 'icon' => '',
+ ],
+ 5 => [
+ 'id' => 'trashbin',
+ 'appname' => 'files_trashbin',
+ 'script' => 'list.php',
+ 'order' => 50,
+ 'name' => new \OC_L10N_String(new \OC_L10N('files_trashbin'), 'Deleted files', []),
+ 'active' => false,
+ 'icon' => '',
+ ],
+ ]);
+
+ $expected = new Http\TemplateResponse(
+ 'files',
+ 'index',
+ [
+ 'usedSpacePercent' => 123,
+ 'owner' => 'MyName',
+ 'ownerDisplayName' => 'MyDisplayName',
+ 'isPublic' => false,
+ 'mailNotificationEnabled' => 'no',
+ 'mailPublicNotificationEnabled' => 'no',
+ 'allowShareWithLink' => 'yes',
+ 'appNavigation' => $nav,
+ 'appContents' => [
+ 0 => [
+ 'id' => 'files',
+ 'content' => null,
+ ],
+ 1 => [
+ 'id' => 'favorites',
+ 'content' => null,
+ ],
+ 2 => [
+ 'id' => 'sharingin',
+ 'content' => null,
+ ],
+ 3 => [
+ 'id' => 'sharingout',
+ 'content' => null,
+ ],
+ 4 => [
+ 'id' => 'sharinglinks',
+ 'content' => null,
+ ],
+ 5 => [
+ 'id' => 'trashbin',
+ 'content' => null,
+ ],
+ ],
+ ]
+ );
+ $this->assertEquals($expected, $this->viewController->index('MyDir', 'MyView'));
+ }
+}
diff --git a/apps/files_sharing/api/remote.php b/apps/files_sharing/api/remote.php
index 41ebb6e2eab..fb692f8a9a6 100644
--- a/apps/files_sharing/api/remote.php
+++ b/apps/files_sharing/api/remote.php
@@ -98,7 +98,7 @@ class Remote {
*/
private static function extendShareInfo($share) {
$view = new \OC\Files\View('/' . \OC_User::getUser() . '/files/');
- $info = $view->getFileInfo($shares['mountpoint']);
+ $info = $view->getFileInfo($share['mountpoint']);
$share['mimetype'] = $info->getMimetype();
$share['mtime'] = $info->getMtime();
diff --git a/apps/files_sharing/lib/external/storage.php b/apps/files_sharing/lib/external/storage.php
index 270d8b6d1b8..2a0d827e064 100644
--- a/apps/files_sharing/lib/external/storage.php
+++ b/apps/files_sharing/lib/external/storage.php
@@ -250,7 +250,7 @@ class Storage extends DAV implements ISharedStorage {
$response = $client->post($url, ['body' => ['password' => $password]]);
} catch (\GuzzleHttp\Exception\RequestException $e) {
if ($e->getCode() === 401 || $e->getCode() === 403) {
- throw new ForbiddenException();
+ throw new ForbiddenException();
}
// throw this to be on the safe side: the share will still be visible
// in the UI in case the failure is intermittent, and the user will
@@ -260,4 +260,9 @@ class Storage extends DAV implements ISharedStorage {
return json_decode($response->getBody(), true);
}
+
+ public function getOwner($path) {
+ list(, $remote) = explode('://', $this->remote, 2);
+ return $this->remoteUser . '@' . $remote;
+ }
}
diff --git a/apps/provisioning_api/appinfo/routes.php b/apps/provisioning_api/appinfo/routes.php
index d2cba549846..22a923f5c20 100644
--- a/apps/provisioning_api/appinfo/routes.php
+++ b/apps/provisioning_api/appinfo/routes.php
@@ -51,7 +51,8 @@ API::register('get', '/cloud/users/{userid}/subadmins', [$users, 'getUserSubAdmi
// Groups
$groups = new \OCA\Provisioning_API\Groups(
\OC::$server->getGroupManager(),
- \OC::$server->getUserSession()
+ \OC::$server->getUserSession(),
+ \OC::$server->getRequest()
);
API::register('get', '/cloud/groups', [$groups, 'getGroups'], 'provisioning_api', API::SUBADMIN_AUTH);
API::register('post', '/cloud/groups', [$groups, 'addGroup'], 'provisioning_api', API::SUBADMIN_AUTH);
diff --git a/apps/provisioning_api/lib/groups.php b/apps/provisioning_api/lib/groups.php
index c28db35972f..7a6e6150782 100644
--- a/apps/provisioning_api/lib/groups.php
+++ b/apps/provisioning_api/lib/groups.php
@@ -37,14 +37,20 @@ class Groups{
/** @var \OCP\IUserSession */
private $userSession;
+ /** @var \OCP\IRequest */
+ private $request;
+
/**
* @param \OCP\IGroupManager $groupManager
* @param \OCP\IUserSession $userSession
+ * @param \OCP\IRequest $request
*/
public function __construct(\OCP\IGroupManager $groupManager,
- \OCP\IUserSession $userSession) {
+ \OCP\IUserSession $userSession,
+ \OCP\IRequest $request) {
$this->groupManager = $groupManager;
$this->userSession = $userSession;
+ $this->request = $request;
}
/**
@@ -54,9 +60,16 @@ class Groups{
* @return OC_OCS_Result
*/
public function getGroups($parameters) {
- $search = !empty($_GET['search']) ? $_GET['search'] : '';
- $limit = !empty($_GET['limit']) ? $_GET['limit'] : null;
- $offset = !empty($_GET['offset']) ? $_GET['offset'] : null;
+ $search = $this->request->getParam('search', '');
+ $limit = $this->request->getParam('limit');
+ $offset = $this->request->getParam('offset');
+
+ if ($limit !== null) {
+ $limit = (int)$limit;
+ }
+ if ($offset !== null) {
+ $offset = (int)$offset;
+ }
$groups = $this->groupManager->search($search, $limit, $offset);
$groups = array_map(function($group) {
@@ -80,21 +93,23 @@ class Groups{
return new OC_OCS_Result(null, \OCP\API::RESPOND_UNAUTHORISED);
}
+ $groupId = $parameters['groupid'];
+
// Check the group exists
- if(!$this->groupManager->groupExists($parameters['groupid'])) {
+ if(!$this->groupManager->groupExists($groupId)) {
return new OC_OCS_Result(null, \OCP\API::RESPOND_NOT_FOUND, 'The requested group could not be found');
}
$isSubadminOfGroup = false;
- $targetGroupObject =$this->groupManager->get($parameters['groupid']);
- if($targetGroupObject !== null) {
- $isSubadminOfGroup =$this->groupManager->getSubAdmin()->isSubAdminofGroup($user, $targetGroupObject);
+ $group = $this->groupManager->get($groupId);
+ if ($group !== null) {
+ $isSubadminOfGroup =$this->groupManager->getSubAdmin()->isSubAdminofGroup($user, $group);
}
// Check subadmin has access to this group
if($this->groupManager->isAdmin($user->getUID())
|| $isSubadminOfGroup) {
- $users = $this->groupManager->get($parameters['groupid'])->getUsers();
+ $users = $this->groupManager->get($groupId)->getUsers();
$users = array_map(function($user) {
/** @var IUser $user */
return $user->getUID();
@@ -114,7 +129,7 @@ class Groups{
*/
public function addGroup($parameters) {
// Validate name
- $groupId = isset($_POST['groupid']) ? $_POST['groupid'] : '';
+ $groupId = $this->request->getParam('groupid', '');
if( preg_match( '/[^a-zA-Z0-9 _\.@\-]/', $groupId ) || empty($groupId)){
\OCP\Util::writeLog('provisioning_api', 'Attempt made to create group using invalid characters.', \OCP\Util::ERROR);
return new OC_OCS_Result(null, 101, 'Invalid group name');
@@ -161,14 +176,8 @@ class Groups{
foreach ($subadmins as $user) {
$uids[] = $user->getUID();
}
- $subadmins = $uids;
- // Go
- if(!$subadmins) {
- return new OC_OCS_Result(null, 102, 'Unknown error occured');
- } else {
- return new OC_OCS_Result($subadmins);
- }
+ return new OC_OCS_Result($uids);
}
}
diff --git a/apps/provisioning_api/tests/groupstest.php b/apps/provisioning_api/tests/groupstest.php
index f67ed1c36ae..d37f4412e20 100644
--- a/apps/provisioning_api/tests/groupstest.php
+++ b/apps/provisioning_api/tests/groupstest.php
@@ -25,79 +25,131 @@
namespace OCA\Provisioning_API\Tests;
-use OCP\IUserManager;
use OCP\IGroupManager;
use OCP\IUserSession;
+use OCP\IRequest;
-class GroupsTest extends TestCase {
- /** @var IUserManager */
- protected $userManager;
+class GroupsTest extends \Test\TestCase {
/** @var IGroupManager */
protected $groupManager;
/** @var IUserSession */
protected $userSession;
+ /** @var IRequest */
+ protected $request;
+ /** @var \OC\SubAdmin */
+ protected $subAdminManager;
/** @var \OCA\Provisioning_API\Groups */
protected $api;
protected function setup() {
- parent::setup();
+ $this->subAdminManager = $this->getMockBuilder('OC\SubAdmin')->disableOriginalConstructor()->getMock();
- $this->userManager = \OC::$server->getUserManager();
- $this->groupManager = \OC::$server->getGroupManager();
- $this->userSession = \OC::$server->getUserSession();
+ $this->groupManager = $this->getMockBuilder('OC\Group\Manager')->disableOriginalConstructor()->getMock();
+ $this->groupManager
+ ->method('getSubAdmin')
+ ->willReturn($this->subAdminManager);
+
+ $this->userSession = $this->getMock('OCP\IUserSession');
+ $this->request = $this->getMock('OCP\IRequest');
$this->api = new \OCA\Provisioning_API\Groups(
$this->groupManager,
- $this->userSession
+ $this->userSession,
+ $this->request
);
}
- public function testGetGroups() {
- $groups = [];
- $id = $this->getUniqueID();
+ private function createGroup($gid) {
+ $group = $this->getMock('OCP\IGroup');
+ $group
+ ->method('getGID')
+ ->willReturn($gid);
+ return $group;
+ }
- for ($i=0; $i < 10; $i++) {
- $groups[] = $this->groupManager->createGroup($id . '_' . $i);
- }
+ private function createUser($uid) {
+ $user = $this->getMock('OCP\IUser');
+ $user
+ ->method('getUID')
+ ->willReturn($uid);
+ return $user;
+ }
- $_GET = [];
- $result = $this->api->getGroups([]);
- $this->assertInstanceOf('OC_OCS_Result', $result);
- $this->assertTrue($result->succeeded());
- $this->assertCount(count($this->groupManager->search('')), $result->getData()['groups']);
- $this->assertContains('admin', $result->getData()['groups']);
- foreach ($groups as $group) {
- $this->assertContains($group->getGID(), $result->getData()['groups']);
- }
-
- $_GET = [
- 'search' => $id,
- 'limit' => 5,
- 'offset' => 2
+ private function asUser() {
+ $user = $this->createUser('user');
+ $this->userSession
+ ->method('getUser')
+ ->willReturn($user);
+ }
+
+ private function asAdmin() {
+ $user = $this->createUser('admin');
+ $this->userSession
+ ->method('getUser')
+ ->willReturn($user);
+
+ $this->groupManager
+ ->method('isAdmin')
+ ->with('admin')
+ ->willReturn(true);
+ }
+
+ private function asSubAdminOfGroup($group) {
+ $user = $this->createUser('subAdmin');
+ $this->userSession
+ ->method('getUser')
+ ->willReturn($user);
+
+ $this->subAdminManager
+ ->method('isSubAdminOfGroup')
+ ->will($this->returnCallback(function($_user, $_group) use ($user, $group) {
+ if ($_user === $user && $_group === $group) {
+ return true;
+ }
+ return false;
+ }));
+ }
+
+ public function dataGetGroups() {
+ return [
+ [null, null, null],
+ ['foo', null, null],
+ [null, 1, null],
+ [null, null, 2],
+ ['foo', 1, 2],
];
+ }
+
+ /**
+ * @dataProvider dataGetGroups
+ */
+ public function testGetGroups($search, $limit, $offset) {
+ $this->request
+ ->expects($this->exactly(3))
+ ->method('getParam')
+ ->will($this->returnValueMap([
+ ['search', '', $search],
+ ['limit', null, $limit],
+ ['offset', null, $offset],
+ ]));
+
+ $groups = [$this->createGroup('group1'), $this->createGroup('group2')];
+
+ $search = $search === null ? '' : $search;
+
+ $this->groupManager
+ ->expects($this->once())
+ ->method('search')
+ ->with($search, $limit, $offset)
+ ->willReturn($groups);
+
$result = $this->api->getGroups([]);
$this->assertInstanceOf('OC_OCS_Result', $result);
$this->assertTrue($result->succeeded());
- $this->assertCount(5, $result->getData()['groups']);
- foreach (array_splice($groups, 2, 5) as $group) {
- $this->assertContains($group->getGID(), $result->getData()['groups']);
- }
-
- foreach ($groups as $group) {
- $group->delete();
- }
+ $this->assertEquals(['group1', 'group2'], $result->getData()['groups']);
}
public function testGetGroupAsUser() {
-
- $users = $this->generateUsers(2);
- $this->userSession->setUser($users[0]);
-
- $group = $this->groupManager->createGroup($this->getUniqueID());
- $group->addUser($users[1]);
-
- $result = $this->api->getGroup(array(
- 'groupid' => $group->getGID(),
- ));
+ $result = $this->api->getGroup([]);
$this->assertInstanceOf('OC_OCS_Result', $result);
$this->assertFalse($result->succeeded());
@@ -106,80 +158,91 @@ class GroupsTest extends TestCase {
}
public function testGetGroupAsSubadmin() {
-
- $users = $this->generateUsers(2);
- $this->userSession->setUser($users[0]);
-
- $group = $this->groupManager->createGroup($this->getUniqueID());
- $group->addUser($users[0]);
- $group->addUser($users[1]);
-
- $this->groupManager->getSubAdmin()->createSubAdmin($users[0], $group);
+ $group = $this->createGroup('group');
+ $this->asSubAdminOfGroup($group);
+
+ $this->groupManager
+ ->method('get')
+ ->with('group')
+ ->willReturn($group);
+ $this->groupManager
+ ->method('groupExists')
+ ->with('group')
+ ->willReturn(true);
+ $group
+ ->method('getUsers')
+ ->willReturn([
+ $this->createUser('user1'),
+ $this->createUser('user2')
+ ]);
$result = $this->api->getGroup([
- 'groupid' => $group->getGID(),
+ 'groupid' => 'group',
]);
$this->assertInstanceOf('OC_OCS_Result', $result);
$this->assertTrue($result->succeeded());
$this->assertEquals(1, sizeof($result->getData()), 'Asserting the result data array only has the "users" key');
$this->assertArrayHasKey('users', $result->getData());
- $resultData = $result->getData();
- $resultData = $resultData['users'];
-
- $users = array_map(function($user) {
- return $user->getUID();
- }, $users);
-
- sort($users);
- sort($resultData);
- $this->assertEquals($users, $resultData);
-
+ $this->assertEquals(['user1', 'user2'], $result->getData()['users']);
}
public function testGetGroupAsIrrelevantSubadmin() {
-
- $users = $this->generateUsers(2);
- $this->userSession->setUser($users[0]);
-
- $group1 = $this->groupManager->createGroup($this->getUniqueID());
- $group2 = $this->groupManager->createGroup($this->getUniqueID());
- $group1->addUser($users[1]);
- $group2->addUser($users[0]);
-
- $this->groupManager->getSubAdmin()->createSubAdmin($users[0], $group2);
+ $group = $this->createGroup('group');
+ $otherGroup = $this->createGroup('otherGroup');
+ $this->asSubAdminOfGroup($otherGroup);
+
+ $this->groupManager
+ ->method('get')
+ ->with('group')
+ ->willReturn($group);
+ $this->groupManager
+ ->method('groupExists')
+ ->with('group')
+ ->willReturn(true);
$result = $this->api->getGroup([
- 'groupid' => $group1->getGID(),
+ 'groupid' => 'group',
]);
$this->assertInstanceOf('OC_OCS_Result', $result);
$this->assertFalse($result->succeeded());
$this->assertEquals(\OCP\API::RESPOND_UNAUTHORISED, $result->getStatusCode());
-
}
public function testGetGroupAsAdmin() {
-
- $users = $this->generateUsers(2);
- $this->userSession->setUser($users[0]);
-
- $group = $this->groupManager->createGroup($this->getUniqueID());
-
- $group->addUser($users[1]);
- $this->groupManager->get('admin')->addUser($users[0]);
+ $group = $this->createGroup('group');
+ $this->asAdmin();
+
+ $this->groupManager
+ ->method('get')
+ ->with('group')
+ ->willReturn($group);
+ $this->groupManager
+ ->method('groupExists')
+ ->with('group')
+ ->willReturn(true);
+ $group
+ ->method('getUsers')
+ ->willReturn([
+ $this->createUser('user1'),
+ $this->createUser('user2')
+ ]);
$result = $this->api->getGroup([
- 'groupid' => $group->getGID(),
+ 'groupid' => 'group',
]);
$this->assertInstanceOf('OC_OCS_Result', $result);
$this->assertTrue($result->succeeded());
- $this->assertEquals(['users' => [$users[1]->getUID()]], $result->getData());
-
+ $this->assertEquals(1, sizeof($result->getData()), 'Asserting the result data array only has the "users" key');
+ $this->assertArrayHasKey('users', $result->getData());
+ $this->assertEquals(['user1', 'user2'], $result->getData()['users']);
}
public function testGetGroupNonExisting() {
+ $this->asUser();
+
$result = $this->api->getGroup([
'groupid' => $this->getUniqueId()
]);
@@ -190,35 +253,71 @@ class GroupsTest extends TestCase {
$this->assertEquals('The requested group could not be found', $result->getMeta()['message']);
}
+ public function testGetSubAdminsOfGroupsNotExists() {
+ $result = $this->api->getSubAdminsOfGroup([
+ 'groupid' => 'NonExistingGroup',
+ ]);
+
+ $this->assertInstanceOf('OC_OCS_Result', $result);
+ $this->assertFalse($result->succeeded());
+ $this->assertEquals(101, $result->getStatusCode());
+ $this->assertEquals('Group does not exist', $result->getMeta()['message']);
+ }
+
public function testGetSubAdminsOfGroup() {
- $user1 = $this->generateUsers();
- $user2 = $this->generateUsers();
- $this->userSession->setUser($user1);
- $this->groupManager->get('admin')->addUser($user1);
- $group1 = $this->groupManager->createGroup($this->getUniqueID());
- $this->groupManager->getSubAdmin()->createSubAdmin($user2, $group1);
+ $group = $this->createGroup('GroupWithSubAdmins');
+ $this->groupManager
+ ->method('get')
+ ->with('GroupWithSubAdmins')
+ ->willReturn($group);
+
+ $this->subAdminManager
+ ->expects($this->once())
+ ->method('getGroupsSubAdmins')
+ ->with($group)
+ ->willReturn([
+ $this->createUser('SubAdmin1'),
+ $this->createUser('SubAdmin2'),
+ ]);
+
$result = $this->api->getSubAdminsOfGroup([
- 'groupid' => $group1->getGID(),
+ 'groupid' => 'GroupWithSubAdmins',
]);
+
$this->assertInstanceOf('OC_OCS_Result', $result);
$this->assertTrue($result->succeeded());
- $data = $result->getData();
- $this->assertEquals($user2->getUID(), reset($data));
- $group1->delete();
+ $this->assertEquals(['SubAdmin1', 'SubAdmin2'], $result->getData());
+ }
+
+ public function testGetSubAdminsOfGroupEmptyList() {
+ $group = $this->createGroup('GroupWithOutSubAdmins');
+ $this->groupManager
+ ->method('get')
+ ->with('GroupWithOutSubAdmins')
+ ->willReturn($group);
+
+ $this->subAdminManager
+ ->expects($this->once())
+ ->method('getGroupsSubAdmins')
+ ->with($group)
+ ->willReturn([
+ ]);
- $user1 = $this->generateUsers();
- $this->userSession->setUser($user1);
- $this->groupManager->get('admin')->addUser($user1);
$result = $this->api->getSubAdminsOfGroup([
- 'groupid' => $this->getUniqueID(),
+ 'groupid' => 'GroupWithOutSubAdmins',
]);
+
$this->assertInstanceOf('OC_OCS_Result', $result);
- $this->assertFalse($result->succeeded());
- $this->assertEquals(101, $result->getStatusCode());
+ $this->assertTrue($result->succeeded());
+ $this->assertEquals([], $result->getData());
}
public function testAddGroupEmptyGroup() {
- $_POST = [];
+ $this->request
+ ->method('getParam')
+ ->with('groupid')
+ ->willReturn('');
+
$result = $this->api->addGroup([]);
$this->assertInstanceOf('OC_OCS_Result', $result);
@@ -228,40 +327,47 @@ class GroupsTest extends TestCase {
}
public function testAddGroupExistingGroup() {
- $group = $this->groupManager->createGroup($this->getUniqueID());
+ $this->request
+ ->method('getParam')
+ ->with('groupid')
+ ->willReturn('ExistingGroup');
+
+ $this->groupManager
+ ->method('groupExists')
+ ->with('ExistingGroup')
+ ->willReturn(true);
- $_POST = [
- 'groupid' => $group->getGID()
- ];
$result = $this->api->addGroup([]);
$this->assertInstanceOf('OC_OCS_Result', $result);
$this->assertFalse($result->succeeded());
$this->assertEquals(102, $result->getStatusCode());
-
- $group->delete();
}
public function testAddGroup() {
- $group = $this->getUniqueId();
+ $this->request
+ ->method('getParam')
+ ->with('groupid')
+ ->willReturn('NewGroup');
- $_POST = [
- 'groupid' => $group
- ];
+ $this->groupManager
+ ->method('groupExists')
+ ->with('NewGroup')
+ ->willReturn(false);
+
+ $this->groupManager
+ ->expects($this->once())
+ ->method('createGroup')
+ ->with('NewGroup');
$result = $this->api->addGroup([]);
$this->assertInstanceOf('OC_OCS_Result', $result);
$this->assertTrue($result->succeeded());
- $this->assertTrue($this->groupManager->groupExists($group));
-
- $this->groupManager->get($group)->delete();
}
public function testDeleteGroupNonExisting() {
- $group = $this->getUniqueId();
-
$result = $this->api->deleteGroup([
- 'groupid' => $group
+ 'groupid' => 'NonExistingGroup'
]);
$this->assertInstanceOf('OC_OCS_Result', $result);
$this->assertFalse($result->succeeded());
@@ -269,6 +375,11 @@ class GroupsTest extends TestCase {
}
public function testDeleteAdminGroup() {
+ $this->groupManager
+ ->method('groupExists')
+ ->with('admin')
+ ->willReturn('true');
+
$result = $this->api->deleteGroup([
'groupid' => 'admin'
]);
@@ -278,13 +389,25 @@ class GroupsTest extends TestCase {
}
public function testDeleteGroup() {
- $group = $this->groupManager->createGroup($this->getUniqueId());
+ $this->groupManager
+ ->method('groupExists')
+ ->with('ExistingGroup')
+ ->willReturn('true');
+
+ $group = $this->createGroup('ExistingGroup');
+ $this->groupManager
+ ->method('get')
+ ->with('ExistingGroup')
+ ->willReturn($group);
+ $group
+ ->expects($this->once())
+ ->method('delete')
+ ->willReturn(true);
$result = $this->api->deleteGroup([
- 'groupid' => $group->getGID()
+ 'groupid' => 'ExistingGroup',
]);
$this->assertInstanceOf('OC_OCS_Result', $result);
$this->assertTrue($result->succeeded());
- $this->assertFalse($this->groupManager->groupExists($group->getGID()));
}
}
diff --git a/apps/user_ldap/l10n/tr.js b/apps/user_ldap/l10n/tr.js
index cbe95a194c5..c9798c5382d 100644
--- a/apps/user_ldap/l10n/tr.js
+++ b/apps/user_ldap/l10n/tr.js
@@ -24,6 +24,7 @@ OC.L10N.register(
"Could not detect Base DN, please enter it manually." : "Base DN tespit edilemedi, lütfen elle girin.",
"{nthServer}. Server" : "{nthServer}. Sunucu",
"No object found in the given Base DN. Please revise." : "Girilen Base DN içerisinde nesne bulunamadı. Lütfen gözden geçirin.",
+ "More than 1,000 directory entries available." : "1000'den fazla dizin şuan müsait durumdadır.",
" entries available within the provided Base DN" : " girdi sağlanan Base DN içerisinde mevcut",
"An error occurred. Please check the Base DN, as well as connection settings and credentials." : "Bir hata oluştu. Lütfen Base DN ile birlikte bağlantı ayarlarını ve kimlik bilgilerini denetleyin.",
"Do you really want to delete the current Server Configuration?" : "Şu anki sunucu yapılandırmasını silmek istediğinizden emin misiniz?",
diff --git a/apps/user_ldap/l10n/tr.json b/apps/user_ldap/l10n/tr.json
index 2bd0bd59907..c60842078a7 100644
--- a/apps/user_ldap/l10n/tr.json
+++ b/apps/user_ldap/l10n/tr.json
@@ -22,6 +22,7 @@
"Could not detect Base DN, please enter it manually." : "Base DN tespit edilemedi, lütfen elle girin.",
"{nthServer}. Server" : "{nthServer}. Sunucu",
"No object found in the given Base DN. Please revise." : "Girilen Base DN içerisinde nesne bulunamadı. Lütfen gözden geçirin.",
+ "More than 1,000 directory entries available." : "1000'den fazla dizin şuan müsait durumdadır.",
" entries available within the provided Base DN" : " girdi sağlanan Base DN içerisinde mevcut",
"An error occurred. Please check the Base DN, as well as connection settings and credentials." : "Bir hata oluştu. Lütfen Base DN ile birlikte bağlantı ayarlarını ve kimlik bilgilerini denetleyin.",
"Do you really want to delete the current Server Configuration?" : "Şu anki sunucu yapılandırmasını silmek istediğinizden emin misiniz?",
diff --git a/build/integration/features/bootstrap/FeatureContext.php b/build/integration/features/bootstrap/FeatureContext.php
index 46c86f1300b..3d579f52810 100644
--- a/build/integration/features/bootstrap/FeatureContext.php
+++ b/build/integration/features/bootstrap/FeatureContext.php
@@ -60,6 +60,8 @@ class FeatureContext implements Context, SnippetAcceptingContext {
/**
* Parses the xml answer to get ocs response which doesn't match with
* http one in v1 of the api.
+ * @param ResponseInterface $response
+ * @return string
*/
public function getOCSResponse($response) {
return $response->xml()->meta[0]->statuscode;
@@ -67,6 +69,8 @@ class FeatureContext implements Context, SnippetAcceptingContext {
/**
* Parses the xml answer to get the array of users returned.
+ * @param ResponseInterface $resp
+ * @return array
*/
public function getArrayOfUsersResponded($resp) {
$listCheckedElements = $resp->xml()->data[0]->users[0]->element;
@@ -76,6 +80,8 @@ class FeatureContext implements Context, SnippetAcceptingContext {
/**
* Parses the xml answer to get the array of groups returned.
+ * @param ResponseInterface $resp
+ * @return array
*/
public function getArrayOfGroupsResponded($resp) {
$listCheckedElements = $resp->xml()->data[0]->groups[0]->element;
@@ -85,6 +91,8 @@ class FeatureContext implements Context, SnippetAcceptingContext {
/**
* Parses the xml answer to get the array of subadmins returned.
+ * @param ResponseInterface $resp
+ * @return array
*/
public function getArrayOfSubadminsResponded($resp) {
$listCheckedElements = $resp->xml()->data[0]->element;
@@ -94,6 +102,8 @@ class FeatureContext implements Context, SnippetAcceptingContext {
/**
* Parses the xml answer to get the array of apps returned.
+ * @param ResponseInterface $resp
+ * @return array
*/
public function getArrayOfAppsResponded($resp) {
$listCheckedElements = $resp->xml()->data[0]->apps[0]->element;
@@ -108,10 +118,10 @@ class FeatureContext implements Context, SnippetAcceptingContext {
$a = array_map(function($subArray) { return $subArray[0]; }, $arrayOfArrays);
return $a;
}
-
+
/**
* @Then /^users returned are$/
- * @param \Behat\Gherkin\Node\TableNode|null $formData
+ * @param \Behat\Gherkin\Node\TableNode|null $usersList
*/
public function theUsersShouldBe($usersList) {
if ($usersList instanceof \Behat\Gherkin\Node\TableNode) {
@@ -125,7 +135,7 @@ class FeatureContext implements Context, SnippetAcceptingContext {
/**
* @Then /^groups returned are$/
- * @param \Behat\Gherkin\Node\TableNode|null $formData
+ * @param \Behat\Gherkin\Node\TableNode|null $groupsList
*/
public function theGroupsShouldBe($groupsList) {
if ($groupsList instanceof \Behat\Gherkin\Node\TableNode) {
@@ -139,7 +149,7 @@ class FeatureContext implements Context, SnippetAcceptingContext {
/**
* @Then /^subadmin groups returned are$/
- * @param \Behat\Gherkin\Node\TableNode|null $formData
+ * @param \Behat\Gherkin\Node\TableNode|null $groupsList
*/
public function theSubadminGroupsShouldBe($groupsList) {
if ($groupsList instanceof \Behat\Gherkin\Node\TableNode) {
@@ -153,7 +163,7 @@ class FeatureContext implements Context, SnippetAcceptingContext {
/**
* @Then /^subadmin users returned are$/
- * @param \Behat\Gherkin\Node\TableNode|null $formData
+ * @param \Behat\Gherkin\Node\TableNode|null $groupsList
*/
public function theSubadminUsersShouldBe($groupsList) {
$this->theSubadminGroupsShouldBe($groupsList);
@@ -161,7 +171,7 @@ class FeatureContext implements Context, SnippetAcceptingContext {
/**
* @Then /^apps returned are$/
- * @param \Behat\Gherkin\Node\TableNode|null $formData
+ * @param \Behat\Gherkin\Node\TableNode|null $appList
*/
public function theAppsShouldBe($appList) {
if ($appList instanceof \Behat\Gherkin\Node\TableNode) {
@@ -206,7 +216,7 @@ class FeatureContext implements Context, SnippetAcceptingContext {
*/
public function assureUserExists($user) {
try {
- $this->userExists($user);
+ $this->userExists($user);
} catch (\GuzzleHttp\Exception\ClientException $ex) {
$previous_user = $this->currentUser;
$this->currentUser = "admin";
@@ -276,7 +286,6 @@ class FeatureContext implements Context, SnippetAcceptingContext {
}
$this->response = $client->get($fullUrl, $options);
- $subadmins = array($user);
$respondedArray = $this->getArrayOfSubadminsResponded($this->response);
sort($respondedArray);
PHPUnit_Framework_Assert::assertContains($user, $respondedArray);
@@ -295,7 +304,6 @@ class FeatureContext implements Context, SnippetAcceptingContext {
}
$this->response = $client->get($fullUrl, $options);
- $subadmins = array($user);
$respondedArray = $this->getArrayOfSubadminsResponded($this->response);
sort($respondedArray);
PHPUnit_Framework_Assert::assertNotContains($user, $respondedArray);
@@ -498,7 +506,7 @@ class FeatureContext implements Context, SnippetAcceptingContext {
*/
public function assureGroupExists($group) {
try {
- $this->groupExists($group);
+ $this->groupExists($group);
} catch (\GuzzleHttp\Exception\ClientException $ex) {
$previous_user = $this->currentUser;
$this->currentUser = "admin";
@@ -608,8 +616,8 @@ class FeatureContext implements Context, SnippetAcceptingContext {
$fileinfo = $finfo->file("./$filename", FILEINFO_MIME_TYPE);
PHPUnit_Framework_Assert::assertEquals($fileinfo, "text/plain");
if (file_exists("./$filename")) {
- unlink("./$filename");
- }
+ unlink("./$filename");
+ }
}
/**
@@ -624,7 +632,7 @@ class FeatureContext implements Context, SnippetAcceptingContext {
else{
$token = $this->lastShareData->data->token;
}
-
+
$fullUrl = substr($this->baseUrl, 0, -4) . "public.php/webdav";
$options['auth'] = [$token, $password];
$options['save_to'] = "./$filename";
@@ -633,8 +641,8 @@ class FeatureContext implements Context, SnippetAcceptingContext {
$fileinfo = $finfo->file("./$filename", FILEINFO_MIME_TYPE);
PHPUnit_Framework_Assert::assertEquals($fileinfo, "text/plain");
if (file_exists("./$filename")) {
- unlink("./$filename");
- }
+ unlink("./$filename");
+ }
}
/**
@@ -658,7 +666,7 @@ class FeatureContext implements Context, SnippetAcceptingContext {
/**
* @When /^Updating last share with$/
- * @param \Behat\Gherkin\Node\TableNode|null $formData
+ * @param \Behat\Gherkin\Node\TableNode|null $body
*/
public function updatingLastShare($body) {
$share_id = $this->lastShareData->data[0]->id;
@@ -691,11 +699,11 @@ class FeatureContext implements Context, SnippetAcceptingContext {
public function createShare($user,
- $path = null,
- $shareType = null,
- $shareWith = null,
- $publicUpload = null,
- $password = null,
+ $path = null,
+ $shareType = null,
+ $shareWith = null,
+ $publicUpload = null,
+ $password = null,
$permissions = null){
$fullUrl = $this->baseUrl . "v{$this->apiVersion}.php/apps/files_sharing/api/v{$this->sharingApiVersion}/shares";
$client = new Client();
@@ -708,26 +716,26 @@ class FeatureContext implements Context, SnippetAcceptingContext {
}
$fd = [];
if (!is_null($path)){
- $fd['path'] = $path;
+ $fd['path'] = $path;
}
if (!is_null($shareType)){
- $fd['shareType'] = $shareType;
+ $fd['shareType'] = $shareType;
}
if (!is_null($shareWith)){
- $fd['shareWith'] = $shareWith;
+ $fd['shareWith'] = $shareWith;
}
if (!is_null($publicUpload)){
- $fd['publicUpload'] = $publicUpload;
+ $fd['publicUpload'] = $publicUpload;
}
if (!is_null($password)){
- $fd['password'] = $password;
+ $fd['password'] = $password;
}
if (!is_null($permissions)){
- $fd['permissions'] = $permissions;
+ $fd['permissions'] = $permissions;
}
$options['body'] = $fd;
-
+
try {
$this->response = $client->send($client->createRequest("POST", $fullUrl, $options));
$this->lastShareData = $this->response->xml();
@@ -740,7 +748,7 @@ class FeatureContext implements Context, SnippetAcceptingContext {
public function isExpectedUrl($possibleUrl, $finalPart){
$baseUrlChopped = substr($this->baseUrl, 0, -4);
$endCharacter = strlen($baseUrlChopped) + strlen($finalPart);
- return (substr($possibleUrl,0,$endCharacter) == "$baseUrlChopped" . "$finalPart");
+ return (substr($possibleUrl,0,$endCharacter) == "$baseUrlChopped" . "$finalPart");
}
public function isFieldInResponse($field, $contentExpected){
@@ -877,7 +885,7 @@ class FeatureContext implements Context, SnippetAcceptingContext {
public static function removeFile($path, $filename){
if (file_exists("$path" . "$filename")) {
unlink("$path" . "$filename");
- }
+ }
}
/**
@@ -888,7 +896,7 @@ class FeatureContext implements Context, SnippetAcceptingContext {
file_put_contents("../../core/skeleton/" . "textfile" . "$i" . ".txt", "ownCloud test text file\n");
}
if (!file_exists("../../core/skeleton/FOLDER")) {
- mkdir("../../core/skeleton/FOLDER", 0777, true);
+ mkdir("../../core/skeleton/FOLDER", 0777, true);
}
}
@@ -899,9 +907,9 @@ class FeatureContext implements Context, SnippetAcceptingContext {
public static function removeFilesFromSkeleton(){
for ($i=0; $i<5; $i++){
self::removeFile("../../core/skeleton/", "textfile" . "$i" . ".txt");
- }
- if (!is_dir("../../core/skeleton/FOLDER")) {
- rmdir("../../core/skeleton/FOLDER");
+ }
+ if (is_dir("../../core/skeleton/FOLDER")) {
+ rmdir("../../core/skeleton/FOLDER");
}
}
diff --git a/core/l10n/hu_HU.js b/core/l10n/hu_HU.js
index 0feea423eb6..3d0276c1700 100644
--- a/core/l10n/hu_HU.js
+++ b/core/l10n/hu_HU.js
@@ -269,6 +269,7 @@ OC.L10N.register(
"Contact your system administrator if this message persists or appeared unexpectedly." : "Ha ez az üzenet ismételten vagy indokolatlanul megjelenik, akkor keresse a rendszergazda segítségét!",
"Thank you for your patience." : "Köszönjük a türelmét.",
"You are accessing the server from an untrusted domain." : "A kiszolgálót nem megbízható tartományból éri el.",
+ "Please contact your administrator. If you are an administrator of this instance, configure the \"trusted_domains\" setting in config/config.php. An example configuration is provided in config/config.sample.php." : "Kérjük keresse fel a rendszergazdát! Ha ennek a telepítésnek Ön a rendszergazdája, akkor állítsa be a config/config.php állományban a \"trusted_domain\" paramétert! A config/config.sample.php állományban talál példát a beállításra.",
"Depending on your configuration, as an administrator you might also be able to use the button below to trust this domain." : "A beállításoktól függően, rendszergazdaként lehetséges, hogy az alábbi gombot is használhatja a tartomány megbízhatóvá tételéhez.",
"Add \"%s\" as trusted domain" : "Adjuk hozzá \"%s\"-t a megbízható tartományokhoz!",
"App update required" : "Alkalmazás frissítés szükséges",
diff --git a/core/l10n/hu_HU.json b/core/l10n/hu_HU.json
index 313be232295..b1aa5f4c8ad 100644
--- a/core/l10n/hu_HU.json
+++ b/core/l10n/hu_HU.json
@@ -267,6 +267,7 @@
"Contact your system administrator if this message persists or appeared unexpectedly." : "Ha ez az üzenet ismételten vagy indokolatlanul megjelenik, akkor keresse a rendszergazda segítségét!",
"Thank you for your patience." : "Köszönjük a türelmét.",
"You are accessing the server from an untrusted domain." : "A kiszolgálót nem megbízható tartományból éri el.",
+ "Please contact your administrator. If you are an administrator of this instance, configure the \"trusted_domains\" setting in config/config.php. An example configuration is provided in config/config.sample.php." : "Kérjük keresse fel a rendszergazdát! Ha ennek a telepítésnek Ön a rendszergazdája, akkor állítsa be a config/config.php állományban a \"trusted_domain\" paramétert! A config/config.sample.php állományban talál példát a beállításra.",
"Depending on your configuration, as an administrator you might also be able to use the button below to trust this domain." : "A beállításoktól függően, rendszergazdaként lehetséges, hogy az alábbi gombot is használhatja a tartomány megbízhatóvá tételéhez.",
"Add \"%s\" as trusted domain" : "Adjuk hozzá \"%s\"-t a megbízható tartományokhoz!",
"App update required" : "Alkalmazás frissítés szükséges",
diff --git a/core/l10n/nl.js b/core/l10n/nl.js
index f4702077004..fe5f45c95ea 100644
--- a/core/l10n/nl.js
+++ b/core/l10n/nl.js
@@ -269,6 +269,7 @@ OC.L10N.register(
"Contact your system administrator if this message persists or appeared unexpectedly." : "Neem contact op met uw systeembeheerder als deze melding aanhoudt of onverwacht verscheen.",
"Thank you for your patience." : "Bedankt voor uw geduld.",
"You are accessing the server from an untrusted domain." : "U benadert de server vanaf een niet vertrouwd domein.",
+ "Please contact your administrator. If you are an administrator of this instance, configure the \"trusted_domains\" setting in config/config.php. An example configuration is provided in config/config.sample.php." : "Neem contact op met uw beheerder. Als u de beheerder van deze service bent, configureer dan de \"trusted_domains\" instelling in config/config.php. Een voorbeeldconfiguratie is gegeven in config/config.sample.php.",
"Depending on your configuration, as an administrator you might also be able to use the button below to trust this domain." : "Afhankelijk van uw configuratie zou u als beheerder ook de onderstaande knop kunnen gebruiken om dit domein te vertrouwen.",
"Add \"%s\" as trusted domain" : "\"%s\" toevoegen als vertrouwd domein",
"App update required" : "Bijwerken App vereist",
diff --git a/core/l10n/nl.json b/core/l10n/nl.json
index a86bbe3d4af..117070e8399 100644
--- a/core/l10n/nl.json
+++ b/core/l10n/nl.json
@@ -267,6 +267,7 @@
"Contact your system administrator if this message persists or appeared unexpectedly." : "Neem contact op met uw systeembeheerder als deze melding aanhoudt of onverwacht verscheen.",
"Thank you for your patience." : "Bedankt voor uw geduld.",
"You are accessing the server from an untrusted domain." : "U benadert de server vanaf een niet vertrouwd domein.",
+ "Please contact your administrator. If you are an administrator of this instance, configure the \"trusted_domains\" setting in config/config.php. An example configuration is provided in config/config.sample.php." : "Neem contact op met uw beheerder. Als u de beheerder van deze service bent, configureer dan de \"trusted_domains\" instelling in config/config.php. Een voorbeeldconfiguratie is gegeven in config/config.sample.php.",
"Depending on your configuration, as an administrator you might also be able to use the button below to trust this domain." : "Afhankelijk van uw configuratie zou u als beheerder ook de onderstaande knop kunnen gebruiken om dit domein te vertrouwen.",
"Add \"%s\" as trusted domain" : "\"%s\" toevoegen als vertrouwd domein",
"App update required" : "Bijwerken App vereist",
diff --git a/core/l10n/ru.js b/core/l10n/ru.js
index 2df910a6aa4..39302e9a18e 100644
--- a/core/l10n/ru.js
+++ b/core/l10n/ru.js
@@ -177,6 +177,7 @@ OC.L10N.register(
"Hello {name}" : "Здравствуйте {name}",
"_download %n file_::_download %n files_" : ["скачать %n файл","скачать %n файла","скачать %n файлов","скачать %n файлов"],
"{version} is available. Get more information on how to update." : "Доступна версия {version}. Получить дополнительную информацию о порядке обновления.",
+ "The upgrade is in progress, leaving this page might interrupt the process in some environments." : "Идет обновление. Покидая эту страницу, вы можете прервать процесс обновления.",
"Updating {productName} to version {version}, this may take a while." : "Идет обновление {productName} до версии {version}, пожалуйста, подождите.",
"An error occurred." : "Произошла ошибка.",
"Please reload the page." : "Обновите страницу.",
@@ -268,6 +269,7 @@ OC.L10N.register(
"Contact your system administrator if this message persists or appeared unexpectedly." : "Обратитесь к вашему системному администратору если это сообщение не исчезает или появляется неожиданно.",
"Thank you for your patience." : "Спасибо за терпение.",
"You are accessing the server from an untrusted domain." : "Вы пытаетесь получить доступ к серверу с неподтверждённого домена.",
+ "Please contact your administrator. If you are an administrator of this instance, configure the \"trusted_domains\" setting in config/config.php. An example configuration is provided in config/config.sample.php." : "Пожалуйста, обратитесь к администратору. Если вы являетесь администратором этого экземпляра, настроить параметры \"trusted_domains\" можно в config/config.php. Пример настройки можно найти в config/config.sample.php.",
"Depending on your configuration, as an administrator you might also be able to use the button below to trust this domain." : "В зависимости от конфигурации, как администратор вы можете также внести домен в доверенные с помощью кнопки ниже.",
"Add \"%s\" as trusted domain" : "Добавить \"%s\" как доверенный домен",
"App update required" : "Требуется обновление приложения",
diff --git a/core/l10n/ru.json b/core/l10n/ru.json
index 66d3b463899..a24ee0831a0 100644
--- a/core/l10n/ru.json
+++ b/core/l10n/ru.json
@@ -175,6 +175,7 @@
"Hello {name}" : "Здравствуйте {name}",
"_download %n file_::_download %n files_" : ["скачать %n файл","скачать %n файла","скачать %n файлов","скачать %n файлов"],
"{version} is available. Get more information on how to update." : "Доступна версия {version}. Получить дополнительную информацию о порядке обновления.",
+ "The upgrade is in progress, leaving this page might interrupt the process in some environments." : "Идет обновление. Покидая эту страницу, вы можете прервать процесс обновления.",
"Updating {productName} to version {version}, this may take a while." : "Идет обновление {productName} до версии {version}, пожалуйста, подождите.",
"An error occurred." : "Произошла ошибка.",
"Please reload the page." : "Обновите страницу.",
@@ -266,6 +267,7 @@
"Contact your system administrator if this message persists or appeared unexpectedly." : "Обратитесь к вашему системному администратору если это сообщение не исчезает или появляется неожиданно.",
"Thank you for your patience." : "Спасибо за терпение.",
"You are accessing the server from an untrusted domain." : "Вы пытаетесь получить доступ к серверу с неподтверждённого домена.",
+ "Please contact your administrator. If you are an administrator of this instance, configure the \"trusted_domains\" setting in config/config.php. An example configuration is provided in config/config.sample.php." : "Пожалуйста, обратитесь к администратору. Если вы являетесь администратором этого экземпляра, настроить параметры \"trusted_domains\" можно в config/config.php. Пример настройки можно найти в config/config.sample.php.",
"Depending on your configuration, as an administrator you might also be able to use the button below to trust this domain." : "В зависимости от конфигурации, как администратор вы можете также внести домен в доверенные с помощью кнопки ниже.",
"Add \"%s\" as trusted domain" : "Добавить \"%s\" как доверенный домен",
"App update required" : "Требуется обновление приложения",
diff --git a/lib/l10n/fr.js b/lib/l10n/fr.js
index e0ac89cbec8..305dea06792 100644
--- a/lib/l10n/fr.js
+++ b/lib/l10n/fr.js
@@ -141,7 +141,7 @@ OC.L10N.register(
"Please upgrade your database version" : "Veuillez mettre à jour votre gestionnaire de base de données",
"Error occurred while checking PostgreSQL version" : "Une erreur s’est produite pendant la récupération du numéro de version de PostgreSQL",
"Please make sure you have PostgreSQL >= 9 or check the logs for more information about the error" : "Veuillez vérifier que vous utilisez PostgreSQL >= 9 , ou regardez dans le journal d’erreur pour plus d’informations sur ce problème",
- "Please change the permissions to 0770 so that the directory cannot be listed by other users." : "Veuillez changer les permissions du répertoire en mode 0770 afin que son contenu puisse être listé par les autres utilisateurs.",
+ "Please change the permissions to 0770 so that the directory cannot be listed by other users." : "Veuillez changer les permissions du répertoire en mode 0770 afin que son contenu ne puisse pas être listé par les autres utilisateurs.",
"Data directory (%s) is readable by other users" : "Le répertoire de données (%s) est lisible par les autres utilisateurs",
"Data directory (%s) must be an absolute path" : "Le chemin du dossier de données (%s) doit être absolu",
"Check the value of \"datadirectory\" in your configuration" : "Verifiez la valeur de \"datadirectory\" dans votre configuration",
diff --git a/lib/l10n/fr.json b/lib/l10n/fr.json
index 2d74c986a50..9ca7bce8d10 100644
--- a/lib/l10n/fr.json
+++ b/lib/l10n/fr.json
@@ -139,7 +139,7 @@
"Please upgrade your database version" : "Veuillez mettre à jour votre gestionnaire de base de données",
"Error occurred while checking PostgreSQL version" : "Une erreur s’est produite pendant la récupération du numéro de version de PostgreSQL",
"Please make sure you have PostgreSQL >= 9 or check the logs for more information about the error" : "Veuillez vérifier que vous utilisez PostgreSQL >= 9 , ou regardez dans le journal d’erreur pour plus d’informations sur ce problème",
- "Please change the permissions to 0770 so that the directory cannot be listed by other users." : "Veuillez changer les permissions du répertoire en mode 0770 afin que son contenu puisse être listé par les autres utilisateurs.",
+ "Please change the permissions to 0770 so that the directory cannot be listed by other users." : "Veuillez changer les permissions du répertoire en mode 0770 afin que son contenu ne puisse pas être listé par les autres utilisateurs.",
"Data directory (%s) is readable by other users" : "Le répertoire de données (%s) est lisible par les autres utilisateurs",
"Data directory (%s) must be an absolute path" : "Le chemin du dossier de données (%s) doit être absolu",
"Check the value of \"datadirectory\" in your configuration" : "Verifiez la valeur de \"datadirectory\" dans votre configuration",
diff --git a/lib/l10n/hu_HU.js b/lib/l10n/hu_HU.js
index d065c509460..df162844485 100644
--- a/lib/l10n/hu_HU.js
+++ b/lib/l10n/hu_HU.js
@@ -11,6 +11,7 @@ OC.L10N.register(
"PHP with a version lower than %s is required." : "Ennél régebbi PHP szükséges: %s.",
"Following databases are supported: %s" : "A következő adatbázis nem támogatott: %s",
"The library %s is not available." : "A könyvtár %s nem áll rendelkezésre.",
+ "Following platforms are supported: %s" : "Ezek a platformok támogatottak: %s",
"ownCloud %s or higher is required." : "ownCoud %s vagy ennél újabb szükséges.",
"ownCloud %s or lower is required." : "ownCoud %s vagy ennél régebbi szükséges.",
"Help" : "Súgó",
@@ -36,7 +37,9 @@ OC.L10N.register(
"Dot files are not allowed" : "Pontozott fájlok nem engedétlyezettek",
"4-byte characters are not supported in file names" : "4-byte karakterek nem támogatottak a fájl nevekben.",
"File name is a reserved word" : "A fajl neve egy rezervált szó",
+ "File name contains at least one invalid character" : "A fájlnév legalább egy érvénytelen karaktert tartalmaz!",
"File name is too long" : "A fájlnév túl hosszú!",
+ "File is currently busy, please try again later" : "A fájl jelenleg elfoglalt, kérjük próbáld újra később!",
"Can't read file" : "Nem olvasható a fájl",
"App directory already exists" : "Az alkalmazás mappája már létezik",
"Can't create app folder. Please fix permissions. %s" : "Nem lehetett létrehozni az alkalmazás mappáját. Kérem ellenőrizze a jogosultságokat. %s",
@@ -70,25 +73,32 @@ OC.L10N.register(
"Set an admin username." : "Állítson be egy felhasználói nevet az adminisztrációhoz.",
"Set an admin password." : "Állítson be egy jelszót az adminisztrációhoz.",
"Can't create or write into the data directory %s" : "Nem sikerült létrehozni vagy irni a \"data\" könyvtárba %s",
+ "Invalid Federated Cloud ID" : "Érvénytelen Egyesített Felhő Azonosító",
"%s shared »%s« with you" : "%s megosztotta Önnel ezt: »%s«",
"%s via %s" : "%s über %s",
+ "Sharing %s failed, because the backend does not allow shares from type %i" : "%s megosztása sikertelen, mert a megosztási alrendszer nem engedi a %l típus megosztását",
"Sharing %s failed, because the file does not exist" : "%s megosztása sikertelen, mert a fájl nem létezik",
"You are not allowed to share %s" : "Önnek nincs jogosultsága %s megosztására",
+ "Sharing %s failed, because you can not share with yourself" : "%s megosztása sikertelen, mert magaddal nem oszthatod meg",
"Sharing %s failed, because the user %s does not exist" : "%s megosztása nem sikerült, mert %s felhasználó nem létezik",
"Sharing %s failed, because the user %s is not a member of any groups that %s is a member of" : "%s megosztása nem sikerült, mert %s felhasználó nem tagja egyik olyan csoportnak sem, aminek %s tagja",
"Sharing %s failed, because this item is already shared with %s" : "%s megosztása nem sikerült, mert ez már meg van osztva %s-vel",
+ "Sharing %s failed, because this item is already shared with user %s" : "%s megosztása sikertelen, mert már meg van osztva %s felhasználóval",
"Sharing %s failed, because the group %s does not exist" : "%s megosztása nem sikerült, mert %s csoport nem létezik",
"Sharing %s failed, because %s is not a member of the group %s" : "%s megosztása nem sikerült, mert %s felhasználó nem tagja a %s csoportnak",
"You need to provide a password to create a public link, only protected links are allowed" : "Meg kell adnia egy jelszót is, mert a nyilvános linkek csak jelszóval védetten használhatók",
"Sharing %s failed, because sharing with links is not allowed" : "%s megosztása nem sikerült, mert a linkekkel történő megosztás nincs engedélyezve",
+ "Sharing %s failed, could not find %s, maybe the server is currently unreachable." : "%s megosztása sikertelen, mert %s nem található, talán a szerver jelenleg nem elérhető.",
"Share type %s is not valid for %s" : "A %s megosztási típus nem érvényes %s-re",
"Setting permissions for %s failed, because the permissions exceed permissions granted to %s" : "Nem sikerült %s-re beállítani az elérési jogosultságokat, mert a megadottak túllépik a %s-re érvényes jogosultságokat",
"Setting permissions for %s failed, because the item was not found" : "Nem sikerült %s-re beállítani az elérési jogosultságokat, mert a kérdéses állomány nem található",
"Cannot set expiration date. Shares cannot expire later than %s after they have been shared" : "Nem lehet beállítani a lejárati időt. A megosztások legfeljebb ennyi idővel járhatnak le a létrehozásukat követően: %s",
"Cannot set expiration date. Expiration date is in the past" : "Nem lehet beállítani a lejárati időt, mivel a megadott lejárati időpont már elmúlt.",
+ "Cannot clear expiration date. Shares are required to have an expiration date." : "Nem lehet beállítani a lejárati időt. A megosztásoknak kötelező megadni lejárati időt!",
"Sharing backend %s must implement the interface OCP\\Share_Backend" : "Az %s megosztási alrendszernek támogatnia kell az OCP\\Share_Backend interface-t",
"Sharing backend %s not found" : "A %s megosztási alrendszer nem található",
"Sharing backend for %s not found" : "%s megosztási alrendszere nem található",
+ "Sharing failed, because the user %s is the original sharer" : "Megosztás sikertelen, mert %s felhasználó az eredeti megosztó",
"Sharing %s failed, because the permissions exceed permissions granted to %s" : "%s megosztása nem sikerült, mert a jogosultságok túllépik azt, ami %s rendelkezésére áll",
"Sharing %s failed, because resharing is not allowed" : "%s megosztása nem sikerült, mert a megosztás továbbadása nincs engedélyezve",
"Sharing %s failed, because the sharing backend for %s could not find its source" : "%s megosztása nem sikerült, mert %s megosztási alrendszere nem találja",
@@ -111,6 +121,7 @@ OC.L10N.register(
"Please install one of these locales on your system and restart your webserver." : "Kérjük állítsa be a következő lokalizációk valamelyikét a rendszeren és indítsa újra a webszervert!",
"Please ask your server administrator to install the module." : "Kérje meg a rendszergazdát, hogy telepítse a modult!",
"PHP module %s not installed." : "A %s PHP modul nincs telepítve.",
+ "PHP setting \"%s\" is not set to \"%s\"." : "%s PHP beállítás nincs \"%s\"-re állítva.",
"This is probably caused by a cache/accelerator such as Zend OPcache or eAccelerator." : "Ezt valószínűleg egy gyorsítótár ill. kódgyorsító, mint pl, a Zend, OPcache vagy eAccelererator okozza.",
"PHP modules have been installed, but they are still listed as missing?" : "A PHP modulok telepítve vannak, de a listában mégsincsenek felsorolva?",
"Please ask your server administrator to restart the web server." : "Kérje meg a rendszergazdát, hogy indítsa újra a webszervert!",
@@ -120,6 +131,7 @@ OC.L10N.register(
"Please make sure you have PostgreSQL >= 9 or check the logs for more information about the error" : "Kérjük gondoskodjon róla, hogy a PostgreSQL legalább 9-es verziójú legyen, vagy ellenőrizze a naplófájlokat, hogy mi okozta a hibát!",
"Please change the permissions to 0770 so that the directory cannot be listed by other users." : "Kérjük módosítsa a könyvtár elérhetőségi engedélybeállítását 0770-re, hogy a tartalmát más felhasználó ne listázhassa!",
"Data directory (%s) is readable by other users" : "Az adatkönyvtár (%s) más felhasználók számára is olvasható ",
+ "Data directory (%s) must be an absolute path" : "Az adatkönyvtárnak (%s) abszolút elérési útnak kell lennie",
"Data directory (%s) is invalid" : "Érvénytelen a megadott adatkönyvtár (%s) ",
"Please check that the data directory contains a file \".ocdata\" in its root." : "Kérjük ellenőrizze, hogy az adatkönyvtár tartalmaz a gyökerében egy \".ocdata\" nevű állományt!",
"Could not obtain lock type %d on \"%s\"." : "Nem sikerült %d típusú zárolást elérni itt: \"%s\".",
diff --git a/lib/l10n/hu_HU.json b/lib/l10n/hu_HU.json
index 4f864c1d75d..a94321be0b6 100644
--- a/lib/l10n/hu_HU.json
+++ b/lib/l10n/hu_HU.json
@@ -9,6 +9,7 @@
"PHP with a version lower than %s is required." : "Ennél régebbi PHP szükséges: %s.",
"Following databases are supported: %s" : "A következő adatbázis nem támogatott: %s",
"The library %s is not available." : "A könyvtár %s nem áll rendelkezésre.",
+ "Following platforms are supported: %s" : "Ezek a platformok támogatottak: %s",
"ownCloud %s or higher is required." : "ownCoud %s vagy ennél újabb szükséges.",
"ownCloud %s or lower is required." : "ownCoud %s vagy ennél régebbi szükséges.",
"Help" : "Súgó",
@@ -34,7 +35,9 @@
"Dot files are not allowed" : "Pontozott fájlok nem engedétlyezettek",
"4-byte characters are not supported in file names" : "4-byte karakterek nem támogatottak a fájl nevekben.",
"File name is a reserved word" : "A fajl neve egy rezervált szó",
+ "File name contains at least one invalid character" : "A fájlnév legalább egy érvénytelen karaktert tartalmaz!",
"File name is too long" : "A fájlnév túl hosszú!",
+ "File is currently busy, please try again later" : "A fájl jelenleg elfoglalt, kérjük próbáld újra később!",
"Can't read file" : "Nem olvasható a fájl",
"App directory already exists" : "Az alkalmazás mappája már létezik",
"Can't create app folder. Please fix permissions. %s" : "Nem lehetett létrehozni az alkalmazás mappáját. Kérem ellenőrizze a jogosultságokat. %s",
@@ -68,25 +71,32 @@
"Set an admin username." : "Állítson be egy felhasználói nevet az adminisztrációhoz.",
"Set an admin password." : "Állítson be egy jelszót az adminisztrációhoz.",
"Can't create or write into the data directory %s" : "Nem sikerült létrehozni vagy irni a \"data\" könyvtárba %s",
+ "Invalid Federated Cloud ID" : "Érvénytelen Egyesített Felhő Azonosító",
"%s shared »%s« with you" : "%s megosztotta Önnel ezt: »%s«",
"%s via %s" : "%s über %s",
+ "Sharing %s failed, because the backend does not allow shares from type %i" : "%s megosztása sikertelen, mert a megosztási alrendszer nem engedi a %l típus megosztását",
"Sharing %s failed, because the file does not exist" : "%s megosztása sikertelen, mert a fájl nem létezik",
"You are not allowed to share %s" : "Önnek nincs jogosultsága %s megosztására",
+ "Sharing %s failed, because you can not share with yourself" : "%s megosztása sikertelen, mert magaddal nem oszthatod meg",
"Sharing %s failed, because the user %s does not exist" : "%s megosztása nem sikerült, mert %s felhasználó nem létezik",
"Sharing %s failed, because the user %s is not a member of any groups that %s is a member of" : "%s megosztása nem sikerült, mert %s felhasználó nem tagja egyik olyan csoportnak sem, aminek %s tagja",
"Sharing %s failed, because this item is already shared with %s" : "%s megosztása nem sikerült, mert ez már meg van osztva %s-vel",
+ "Sharing %s failed, because this item is already shared with user %s" : "%s megosztása sikertelen, mert már meg van osztva %s felhasználóval",
"Sharing %s failed, because the group %s does not exist" : "%s megosztása nem sikerült, mert %s csoport nem létezik",
"Sharing %s failed, because %s is not a member of the group %s" : "%s megosztása nem sikerült, mert %s felhasználó nem tagja a %s csoportnak",
"You need to provide a password to create a public link, only protected links are allowed" : "Meg kell adnia egy jelszót is, mert a nyilvános linkek csak jelszóval védetten használhatók",
"Sharing %s failed, because sharing with links is not allowed" : "%s megosztása nem sikerült, mert a linkekkel történő megosztás nincs engedélyezve",
+ "Sharing %s failed, could not find %s, maybe the server is currently unreachable." : "%s megosztása sikertelen, mert %s nem található, talán a szerver jelenleg nem elérhető.",
"Share type %s is not valid for %s" : "A %s megosztási típus nem érvényes %s-re",
"Setting permissions for %s failed, because the permissions exceed permissions granted to %s" : "Nem sikerült %s-re beállítani az elérési jogosultságokat, mert a megadottak túllépik a %s-re érvényes jogosultságokat",
"Setting permissions for %s failed, because the item was not found" : "Nem sikerült %s-re beállítani az elérési jogosultságokat, mert a kérdéses állomány nem található",
"Cannot set expiration date. Shares cannot expire later than %s after they have been shared" : "Nem lehet beállítani a lejárati időt. A megosztások legfeljebb ennyi idővel járhatnak le a létrehozásukat követően: %s",
"Cannot set expiration date. Expiration date is in the past" : "Nem lehet beállítani a lejárati időt, mivel a megadott lejárati időpont már elmúlt.",
+ "Cannot clear expiration date. Shares are required to have an expiration date." : "Nem lehet beállítani a lejárati időt. A megosztásoknak kötelező megadni lejárati időt!",
"Sharing backend %s must implement the interface OCP\\Share_Backend" : "Az %s megosztási alrendszernek támogatnia kell az OCP\\Share_Backend interface-t",
"Sharing backend %s not found" : "A %s megosztási alrendszer nem található",
"Sharing backend for %s not found" : "%s megosztási alrendszere nem található",
+ "Sharing failed, because the user %s is the original sharer" : "Megosztás sikertelen, mert %s felhasználó az eredeti megosztó",
"Sharing %s failed, because the permissions exceed permissions granted to %s" : "%s megosztása nem sikerült, mert a jogosultságok túllépik azt, ami %s rendelkezésére áll",
"Sharing %s failed, because resharing is not allowed" : "%s megosztása nem sikerült, mert a megosztás továbbadása nincs engedélyezve",
"Sharing %s failed, because the sharing backend for %s could not find its source" : "%s megosztása nem sikerült, mert %s megosztási alrendszere nem találja",
@@ -109,6 +119,7 @@
"Please install one of these locales on your system and restart your webserver." : "Kérjük állítsa be a következő lokalizációk valamelyikét a rendszeren és indítsa újra a webszervert!",
"Please ask your server administrator to install the module." : "Kérje meg a rendszergazdát, hogy telepítse a modult!",
"PHP module %s not installed." : "A %s PHP modul nincs telepítve.",
+ "PHP setting \"%s\" is not set to \"%s\"." : "%s PHP beállítás nincs \"%s\"-re állítva.",
"This is probably caused by a cache/accelerator such as Zend OPcache or eAccelerator." : "Ezt valószínűleg egy gyorsítótár ill. kódgyorsító, mint pl, a Zend, OPcache vagy eAccelererator okozza.",
"PHP modules have been installed, but they are still listed as missing?" : "A PHP modulok telepítve vannak, de a listában mégsincsenek felsorolva?",
"Please ask your server administrator to restart the web server." : "Kérje meg a rendszergazdát, hogy indítsa újra a webszervert!",
@@ -118,6 +129,7 @@
"Please make sure you have PostgreSQL >= 9 or check the logs for more information about the error" : "Kérjük gondoskodjon róla, hogy a PostgreSQL legalább 9-es verziójú legyen, vagy ellenőrizze a naplófájlokat, hogy mi okozta a hibát!",
"Please change the permissions to 0770 so that the directory cannot be listed by other users." : "Kérjük módosítsa a könyvtár elérhetőségi engedélybeállítását 0770-re, hogy a tartalmát más felhasználó ne listázhassa!",
"Data directory (%s) is readable by other users" : "Az adatkönyvtár (%s) más felhasználók számára is olvasható ",
+ "Data directory (%s) must be an absolute path" : "Az adatkönyvtárnak (%s) abszolút elérési útnak kell lennie",
"Data directory (%s) is invalid" : "Érvénytelen a megadott adatkönyvtár (%s) ",
"Please check that the data directory contains a file \".ocdata\" in its root." : "Kérjük ellenőrizze, hogy az adatkönyvtár tartalmaz a gyökerében egy \".ocdata\" nevű állományt!",
"Could not obtain lock type %d on \"%s\"." : "Nem sikerült %d típusú zárolást elérni itt: \"%s\".",
diff --git a/lib/private/appframework/dependencyinjection/dicontainer.php b/lib/private/appframework/dependencyinjection/dicontainer.php
index b0be4045541..a32d32fefc5 100644
--- a/lib/private/appframework/dependencyinjection/dicontainer.php
+++ b/lib/private/appframework/dependencyinjection/dicontainer.php
@@ -226,6 +226,10 @@ class DIContainer extends SimpleContainer implements IAppContainer {
return $this->getServer();
});
+ $this->registerService('Symfony\Component\EventDispatcher\EventDispatcherInterface', function ($c) {
+ return $this->getServer()->getEventDispatcher();
+ });
+
$this->registerService('OCP\\AppFramework\\IAppContainer', function ($c) {
return $c;
});
diff --git a/lib/private/cache/file.php b/lib/private/cache/file.php
index 1cda05f28e5..31d4718d18a 100644
--- a/lib/private/cache/file.php
+++ b/lib/private/cache/file.php
@@ -185,6 +185,8 @@ class File implements ICache {
} catch (\OCP\Lock\LockedException $e) {
// ignore locked chunks
\OC::$server->getLogger()->debug('Could not cleanup locked chunk "' . $file . '"', array('app' => 'core'));
+ } catch (\OCP\Files\ForbiddenException $e) {
+ \OC::$server->getLogger()->debug('Could not cleanup forbidden chunk "' . $file . '"', array('app' => 'core'));
} catch (\OCP\Files\LockNotAcquiredException $e) {
\OC::$server->getLogger()->debug('Could not cleanup locked chunk "' . $file . '"', array('app' => 'core'));
}
diff --git a/lib/private/files.php b/lib/private/files.php
index 9be5fc9a12f..af10f3e1e32 100644
--- a/lib/private/files.php
+++ b/lib/private/files.php
@@ -142,6 +142,11 @@ class OC_Files {
$l = \OC::$server->getL10N('core');
$hint = method_exists($ex, 'getHint') ? $ex->getHint() : '';
\OC_Template::printErrorPage($l->t('File is currently busy, please try again later'), $hint);
+ } catch (\OCP\Files\ForbiddenException $ex) {
+ self::unlockAllTheFiles($dir, $files, $getType, $view, $filename);
+ OC::$server->getLogger()->logException($ex);
+ $l = \OC::$server->getL10N('core');
+ \OC_Template::printErrorPage($l->t('Can\'t read file'), $ex->getMessage());
} catch (\Exception $ex) {
self::unlockAllTheFiles($dir, $files, $getType, $view, $filename);
OC::$server->getLogger()->logException($ex);
diff --git a/lib/private/files/cache/scanner.php b/lib/private/files/cache/scanner.php
index 35043029dc3..983e12d7639 100644
--- a/lib/private/files/cache/scanner.php
+++ b/lib/private/files/cache/scanner.php
@@ -432,6 +432,8 @@ class Scanner extends BasicEmitter {
// skip unavailable storages
} catch (\OCP\Files\StorageNotAvailableException $e) {
// skip unavailable storages
+ } catch (\OCP\Files\ForbiddenException $e) {
+ // skip forbidden storages
} catch (\OCP\Lock\LockedException $e) {
// skip unavailable storages
}
diff --git a/lib/private/files/view.php b/lib/private/files/view.php
index 7dd83588ec6..cee4b182425 100644
--- a/lib/private/files/view.php
+++ b/lib/private/files/view.php
@@ -46,11 +46,13 @@ namespace OC\Files;
use Icewind\Streams\CallbackWrapper;
use OC\Files\Cache\Updater;
use OC\Files\Mount\MoveableMount;
+use OC\User\User;
use OCP\Files\FileNameTooLongException;
use OCP\Files\InvalidCharacterInPathException;
use OCP\Files\InvalidPathException;
use OCP\Files\NotFoundException;
use OCP\Files\ReservedWordException;
+use OCP\IUser;
use OCP\Lock\ILockingProvider;
use OCP\Lock\LockedException;
@@ -687,14 +689,14 @@ class View {
} else {
$result = false;
}
- // moving a file/folder within the same mount point
+ // moving a file/folder within the same mount point
} elseif ($storage1 == $storage2) {
if ($storage1) {
$result = $storage1->rename($internalPath1, $internalPath2);
} else {
$result = false;
}
- // moving a file/folder between storages (from $storage1 to $storage2)
+ // moving a file/folder between storages (from $storage1 to $storage2)
} else {
$result = $storage2->moveFromStorage($storage1, $internalPath1, $internalPath2);
}
@@ -1164,6 +1166,19 @@ class View {
}
/**
+ * @param string $ownerId
+ * @return \OC\User\User
+ */
+ private function getUserObjectForOwner($ownerId) {
+ $owner = \OC::$server->getUserManager()->get($ownerId);
+ if ($owner instanceof IUser) {
+ return $owner;
+ } else {
+ return new User($ownerId, null);
+ }
+ }
+
+ /**
* get the filesystem info
*
* @param string $path
@@ -1250,7 +1265,7 @@ class View {
$data['permissions'] |= \OCP\Constants::PERMISSION_DELETE;
}
- $owner = \OC::$server->getUserManager()->get($storage->getOwner($internalPath));
+ $owner = $this->getUserObjectForOwner($storage->getOwner($internalPath));
return new FileInfo($path, $storage, $internalPath, $data, $mount, $owner);
}
@@ -1317,7 +1332,7 @@ class View {
if (\OCP\Util::isSharingDisabledForUser()) {
$content['permissions'] = $content['permissions'] & ~\OCP\Constants::PERMISSION_SHARE;
}
- $owner = \OC::$server->getUserManager()->get($storage->getOwner($content['path']));
+ $owner = $this->getUserObjectForOwner($storage->getOwner($content['path']));
$files[] = new FileInfo($path . '/' . $content['name'], $storage, $content['path'], $content, $mount, $owner);
}
@@ -1387,7 +1402,7 @@ class View {
$rootEntry['permissions'] = $rootEntry['permissions'] & ~\OCP\Constants::PERMISSION_SHARE;
}
- $owner = \OC::$server->getUserManager()->get($subStorage->getOwner(''));
+ $owner = $this->getUserObjectForOwner($subStorage->getOwner(''));
$files[] = new FileInfo($path . '/' . $rootEntry['name'], $subStorage, '', $rootEntry, $mount, $owner);
}
}
diff --git a/lib/private/log/owncloud.php b/lib/private/log/owncloud.php
index 1af989588d1..721bc6002fa 100644
--- a/lib/private/log/owncloud.php
+++ b/lib/private/log/owncloud.php
@@ -66,13 +66,16 @@ class OC_Log_Owncloud {
// default to ISO8601
$format = $config->getValue('logdateformat', 'c');
- $logtimezone = $config->getValue( "logtimezone", 'UTC' );
+ $logTimeZone = $config->getValue( "logtimezone", 'UTC' );
try {
- $timezone = new DateTimeZone($logtimezone);
+ $timezone = new DateTimeZone($logTimeZone);
} catch (Exception $e) {
$timezone = new DateTimeZone('UTC');
}
$time = DateTime::createFromFormat("U.u", microtime(true), $timezone);
+ if ($time === false) {
+ $time = new DateTime(null, $timezone);
+ }
$request = \OC::$server->getRequest();
$reqId = $request->getId();
$remoteAddr = $request->getRemoteAddress();
diff --git a/lib/private/memcache/memcached.php b/lib/private/memcache/memcached.php
index ce7c6fa9577..22b54f7bc95 100644
--- a/lib/private/memcache/memcached.php
+++ b/lib/private/memcache/memcached.php
@@ -41,9 +41,9 @@ class Memcached extends Cache implements IMemcache {
parent::__construct($prefix);
if (is_null(self::$cache)) {
self::$cache = new \Memcached();
- $servers = \OC_Config::getValue('memcached_servers');
+ $servers = \OC::$server->getSystemConfig()->getValue('memcached_servers');
if (!$servers) {
- $server = \OC_Config::getValue('memcached_server');
+ $server = \OC::$server->getSystemConfig()->getValue('memcached_server');
if ($server) {
$servers = array($server);
} else {
@@ -72,10 +72,12 @@ class Memcached extends Cache implements IMemcache {
public function set($key, $value, $ttl = 0) {
if ($ttl > 0) {
- return self::$cache->set($this->getNamespace() . $key, $value, $ttl);
+ $result = self::$cache->set($this->getNamespace() . $key, $value, $ttl);
} else {
- return self::$cache->set($this->getNamespace() . $key, $value);
+ $result = self::$cache->set($this->getNamespace() . $key, $value);
}
+ $this->verifyReturnCode();
+ return $result;
}
public function hasKey($key) {
@@ -84,7 +86,9 @@ class Memcached extends Cache implements IMemcache {
}
public function remove($key) {
- return self::$cache->delete($this->getNamespace() . $key);
+ $result= self::$cache->delete($this->getNamespace() . $key);
+ $this->verifyReturnCode();
+ return $result;
}
public function clear($prefix = '') {
@@ -121,7 +125,9 @@ class Memcached extends Cache implements IMemcache {
* @return bool
*/
public function add($key, $value, $ttl = 0) {
- return self::$cache->add($this->getPrefix() . $key, $value, $ttl);
+ $result = self::$cache->add($this->getPrefix() . $key, $value, $ttl);
+ $this->verifyReturnCode();
+ return $result;
}
/**
@@ -133,7 +139,9 @@ class Memcached extends Cache implements IMemcache {
*/
public function inc($key, $step = 1) {
$this->add($key, 0);
- return self::$cache->increment($this->getPrefix() . $key, $step);
+ $result = self::$cache->increment($this->getPrefix() . $key, $step);
+ $this->verifyReturnCode();
+ return $result;
}
/**
@@ -144,10 +152,24 @@ class Memcached extends Cache implements IMemcache {
* @return int | bool
*/
public function dec($key, $step = 1) {
- return self::$cache->decrement($this->getPrefix() . $key, $step);
+ $result = self::$cache->decrement($this->getPrefix() . $key, $step);
+ $this->verifyReturnCode();
+ return $result;
}
static public function isAvailable() {
return extension_loaded('memcached');
}
+
+ /**
+ * @throws \Exception
+ */
+ private function verifyReturnCode() {
+ $code = self::$cache->getResultCode();
+ if ($code === \Memcached::RES_SUCCESS) {
+ return;
+ }
+ $message = self::$cache->getResultMessage();
+ throw new \Exception("Error $code interacting with memcached : $message");
+ }
}
diff --git a/lib/private/notification/action.php b/lib/private/notification/action.php
index 6de8a1a4bbc..8eade697025 100644
--- a/lib/private/notification/action.php
+++ b/lib/private/notification/action.php
@@ -39,6 +39,9 @@ class Action implements IAction {
/** @var string */
protected $icon;
+ /** @var bool */
+ protected $primary;
+
/**
* Constructor
*/
@@ -47,7 +50,7 @@ class Action implements IAction {
$this->labelParsed = '';
$this->link = '';
$this->requestType = '';
- $this->icon = '';
+ $this->primary = false;
}
/**
@@ -95,6 +98,29 @@ class Action implements IAction {
}
/**
+ * @param $primary bool
+ * @return $this
+ * @throws \InvalidArgumentException if $primary is invalid
+ * @since 9.0.0
+ */
+ public function setPrimary($primary) {
+ if (!is_bool($primary)) {
+ throw new \InvalidArgumentException('The given primary option is invalid');
+ }
+
+ $this->primary = $primary;
+ return $this;
+ }
+
+ /**
+ * @return bool
+ * @since 9.0.0
+ */
+ public function isPrimary() {
+ return $this->primary;
+ }
+
+ /**
* @param string $link
* @param string $requestType
* @return $this
@@ -130,28 +156,6 @@ class Action implements IAction {
}
/**
- * @param string $icon
- * @return $this
- * @throws \InvalidArgumentException if the icon is invalid
- * @since 8.2.0
- */
- public function setIcon($icon) {
- if (!is_string($icon) || $icon === '' || isset($icon[64])) {
- throw new \InvalidArgumentException('The given icon is invalid');
- }
- $this->icon = $icon;
- return $this;
- }
-
- /**
- * @return string
- * @since 8.2.0
- */
- public function getIcon() {
- return $this->icon;
- }
-
- /**
* @return bool
*/
public function isValid() {
diff --git a/lib/private/notification/iaction.php b/lib/private/notification/iaction.php
index da6728f5c52..f7366dd4388 100644
--- a/lib/private/notification/iaction.php
+++ b/lib/private/notification/iaction.php
@@ -61,6 +61,20 @@ interface IAction {
public function getParsedLabel();
/**
+ * @param $primary bool
+ * @return $this
+ * @throws \InvalidArgumentException if $primary is invalid
+ * @since 9.0.0
+ */
+ public function setPrimary($primary);
+
+ /**
+ * @return bool
+ * @since 9.0.0
+ */
+ public function isPrimary();
+
+ /**
* @param string $link
* @param string $requestType
* @return $this
@@ -82,20 +96,6 @@ interface IAction {
public function getRequestType();
/**
- * @param string $icon
- * @return $this
- * @throws \InvalidArgumentException if the icon is invalid
- * @since 8.2.0
- */
- public function setIcon($icon);
-
- /**
- * @return string
- * @since 8.2.0
- */
- public function getIcon();
-
- /**
* @return bool
* @since 8.2.0
*/
diff --git a/lib/private/notification/inotification.php b/lib/private/notification/inotification.php
index faf5db1d24c..a8bf5b110ab 100644
--- a/lib/private/notification/inotification.php
+++ b/lib/private/notification/inotification.php
@@ -180,20 +180,6 @@ interface INotification {
public function getLink();
/**
- * @param string $icon
- * @return $this
- * @throws \InvalidArgumentException if the icon are invalid
- * @since 8.2.0
- */
- public function setIcon($icon);
-
- /**
- * @return string
- * @since 8.2.0
- */
- public function getIcon();
-
- /**
* @return IAction
* @since 8.2.0
*/
diff --git a/lib/private/notification/notification.php b/lib/private/notification/notification.php
index 40fe39a956e..01df659d4a1 100644
--- a/lib/private/notification/notification.php
+++ b/lib/private/notification/notification.php
@@ -68,6 +68,12 @@ class Notification implements INotification {
/** @var array */
protected $actionsParsed;
+ /** @var bool */
+ protected $hasPrimaryAction;
+
+ /** @var bool */
+ protected $hasPrimaryParsedAction;
+
/**
* Constructor
*/
@@ -330,28 +336,6 @@ class Notification implements INotification {
}
/**
- * @param string $icon
- * @return $this
- * @throws \InvalidArgumentException if the icon are invalid
- * @since 8.2.0
- */
- public function setIcon($icon) {
- if (!is_string($icon) || $icon === '' || isset($icon[64])) {
- throw new \InvalidArgumentException('The given icon is invalid');
- }
- $this->icon = $icon;
- return $this;
- }
-
- /**
- * @return string
- * @since 8.2.0
- */
- public function getIcon() {
- return $this->icon;
- }
-
- /**
* @return IAction
* @since 8.2.0
*/
@@ -369,6 +353,15 @@ class Notification implements INotification {
if (!$action->isValid()) {
throw new \InvalidArgumentException('The given action is invalid');
}
+
+ if ($action->isPrimary()) {
+ if ($this->hasPrimaryAction) {
+ throw new \InvalidArgumentException('The notification already has a primary action');
+ }
+
+ $this->hasPrimaryAction = true;
+ }
+
$this->actions[] = $action;
return $this;
}
@@ -391,6 +384,15 @@ class Notification implements INotification {
if (!$action->isValidParsed()) {
throw new \InvalidArgumentException('The given parsed action is invalid');
}
+
+ if ($action->isPrimary()) {
+ if ($this->hasPrimaryParsedAction) {
+ throw new \InvalidArgumentException('The notification already has a primary action');
+ }
+
+ $this->hasPrimaryParsedAction = true;
+ }
+
$this->actionsParsed[] = $action;
return $this;
}
diff --git a/lib/public/files/forbiddenexception.php b/lib/public/files/forbiddenexception.php
new file mode 100644
index 00000000000..13490c6eae3
--- /dev/null
+++ b/lib/public/files/forbiddenexception.php
@@ -0,0 +1,55 @@
+<?php
+/**
+ * @author Joas Schilling <nickvergessen@owncloud.com>
+ *
+ * @copyright Copyright (c) 2015, ownCloud, Inc.
+ * @license AGPL-3.0
+ *
+ * This code is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License, version 3,
+ * along with this program. If not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+// use OCP namespace for all classes that are considered public.
+// This means that they should be used by apps instead of the internal ownCloud classes
+namespace OCP\Files;
+
+/**
+ * Class ForbiddenException
+ *
+ * @package OCP\Files
+ * @since 9.0.0
+ */
+class ForbiddenException extends \Exception {
+
+ /** @var bool */
+ private $retry;
+
+ /**
+ * @param string $message
+ * @param bool $retry
+ * @param \Exception $previous previous exception for cascading
+ * @since 9.0.0
+ */
+ public function __construct($message, $retry, \Exception $previous = null) {
+ parent::__construct($message, 0, $previous);
+ $this->retry = $retry;
+ }
+
+ /**
+ * @return bool
+ * @since 9.0.0
+ */
+ public function getRetry() {
+ return (bool) $this->retry;
+ }
+}
diff --git a/lib/public/files/node.php b/lib/public/files/node.php
index aa1115f8c28..866b9b6f61f 100644
--- a/lib/public/files/node.php
+++ b/lib/public/files/node.php
@@ -225,4 +225,12 @@ interface Node extends FileInfo {
* @since 6.0.0
*/
public function getName();
+
+ /**
+ * Get the file owner
+ *
+ * @since 9.0.0
+ * @return string
+ */
+ public function getOwner();
}
diff --git a/lib/public/files/storage.php b/lib/public/files/storage.php
index 1c520026777..c42a2d05bc3 100644
--- a/lib/public/files/storage.php
+++ b/lib/public/files/storage.php
@@ -462,4 +462,10 @@ interface Storage {
* @param bool $isAvailable
*/
public function setAvailability($isAvailable);
+
+ /**
+ * @param $path path for which to retrieve the owner
+ * @since 9.0.0
+ */
+ public function getOwner($path);
}
diff --git a/settings/l10n/zh_TW.js b/settings/l10n/zh_TW.js
index caa85e2b443..b59605da676 100644
--- a/settings/l10n/zh_TW.js
+++ b/settings/l10n/zh_TW.js
@@ -229,7 +229,7 @@ OC.L10N.register(
"iOS app" : "iOS 應用程式",
"If you want to support the project\n\t\t<a href=\"https://owncloud.org/contribute\"\n\t\t\ttarget=\"_blank\" rel=\"noreferrer\">join development</a>\n\t\tor\n\t\t<a href=\"https://owncloud.org/promote\"\n\t\t\ttarget=\"_blank\" rel=\"noreferrer\">spread the word</a>!" : "若您想支援這個計畫\n\t\t<a href=\"https://owncloud.org/contribute\"\n\t\t\ttarget=\"_blank\" rel=\"noreferrer\">加入開發者</a>\n\t\t或\n\t\t<a href=\"https://owncloud.org/promote\"\n\t\t\ttarget=\"_blank\" rel=\"noreferrer\">替我們宣傳</a>!",
"Show First Run Wizard again" : "再次顯示首次使用精靈",
- "You have used <strong>%s</strong> of the available <strong>%s</strong>" : "您已經使用了 <strong>%s</strong> ,目前可用空間為 <strong>%s</strong>",
+ "You have used <strong>%s</strong> of the available <strong>%s</strong>" : "您已經使用了 <strong>%s</strong> ,總共可用空間為 <strong>%s</strong>",
"Password" : "密碼",
"Unable to change your password" : "無法變更您的密碼",
"Current password" : "目前密碼",
diff --git a/settings/l10n/zh_TW.json b/settings/l10n/zh_TW.json
index 2d7951b8930..9e1e42455df 100644
--- a/settings/l10n/zh_TW.json
+++ b/settings/l10n/zh_TW.json
@@ -227,7 +227,7 @@
"iOS app" : "iOS 應用程式",
"If you want to support the project\n\t\t<a href=\"https://owncloud.org/contribute\"\n\t\t\ttarget=\"_blank\" rel=\"noreferrer\">join development</a>\n\t\tor\n\t\t<a href=\"https://owncloud.org/promote\"\n\t\t\ttarget=\"_blank\" rel=\"noreferrer\">spread the word</a>!" : "若您想支援這個計畫\n\t\t<a href=\"https://owncloud.org/contribute\"\n\t\t\ttarget=\"_blank\" rel=\"noreferrer\">加入開發者</a>\n\t\t或\n\t\t<a href=\"https://owncloud.org/promote\"\n\t\t\ttarget=\"_blank\" rel=\"noreferrer\">替我們宣傳</a>!",
"Show First Run Wizard again" : "再次顯示首次使用精靈",
- "You have used <strong>%s</strong> of the available <strong>%s</strong>" : "您已經使用了 <strong>%s</strong> ,目前可用空間為 <strong>%s</strong>",
+ "You have used <strong>%s</strong> of the available <strong>%s</strong>" : "您已經使用了 <strong>%s</strong> ,總共可用空間為 <strong>%s</strong>",
"Password" : "密碼",
"Unable to change your password" : "無法變更您的密碼",
"Current password" : "目前密碼",
diff --git a/settings/templates/admin.php b/settings/templates/admin.php
index 27785c26dfe..24af4964248 100644
--- a/settings/templates/admin.php
+++ b/settings/templates/admin.php
@@ -491,15 +491,6 @@ if ($_['cronErrors']) {
<div class="section" id="log-section">
<h2><?php p($l->t('Log'));?></h2>
- <?php p($l->t('Log level'));?> <select name='loglevel' id='loglevel'>
-<?php for ($i = 0; $i < 5; $i++):
- $selected = '';
- if ($i == $_['loglevel']):
- $selected = 'selected="selected"';
- endif; ?>
- <option value='<?php p($i)?>' <?php p($selected) ?>><?php p($levelLabels[$i])?></option>
-<?php endfor;?>
-</select>
<?php if ($_['showLog'] && $_['doesLogFileExist']): ?>
<table id="log" class="grid">
<?php foreach ($_['entries'] as $entry): ?>
@@ -537,6 +528,16 @@ if ($_['cronErrors']) {
</em>
<?php endif; ?>
<?php endif; ?>
+
+ <p><?php p($l->t('What to log'));?> <select name='loglevel' id='loglevel'>
+ <?php for ($i = 0; $i < 5; $i++):
+ $selected = '';
+ if ($i == $_['loglevel']):
+ $selected = 'selected="selected"';
+ endif; ?>
+ <option value='<?php p($i)?>' <?php p($selected) ?>><?php p($levelLabels[$i])?></option>
+ <?php endfor;?>
+ </select></p>
</div>
<div class="section" id="admin-tips">
diff --git a/tests/lib/helper.php b/tests/lib/helper.php
index bd527de160d..ca3e0e46d82 100644
--- a/tests/lib/helper.php
+++ b/tests/lib/helper.php
@@ -353,18 +353,24 @@ class Test_Helper extends \Test\TestCase {
$this->assertEquals($expectedResult, $result);
}
+ /**
+ * @return array
+ */
public function provideDocRootAppUrlParts() {
return array(
- array('files', 'index.php', array(), '/index.php/apps/files'),
- array('files', 'index.php', array('trut' => 'trat', 'dut' => 'dat'), '/index.php/apps/files?trut=trat&dut=dat'),
+ array('files', 'ajax/list.php', array(), '/index.php/apps/files/ajax/list.php'),
+ array('files', 'ajax/list.php', array('trut' => 'trat', 'dut' => 'dat'), '/index.php/apps/files/ajax/list.php?trut=trat&dut=dat'),
array('', 'index.php', array('trut' => 'trat', 'dut' => 'dat'), '/index.php?trut=trat&dut=dat'),
);
}
+ /**
+ * @return array
+ */
public function provideSubDirAppUrlParts() {
return array(
- array('files', 'index.php', array(), '/owncloud/index.php/apps/files'),
- array('files', 'index.php', array('trut' => 'trat', 'dut' => 'dat'), '/owncloud/index.php/apps/files?trut=trat&dut=dat'),
+ array('files', 'ajax/list.php', array(), '/owncloud/index.php/apps/files/ajax/list.php'),
+ array('files', 'ajax/list.php', array('trut' => 'trat', 'dut' => 'dat'), '/owncloud/index.php/apps/files/ajax/list.php?trut=trat&dut=dat'),
array('', 'index.php', array('trut' => 'trat', 'dut' => 'dat'), '/owncloud/index.php?trut=trat&dut=dat'),
);
}
@@ -393,18 +399,24 @@ class Test_Helper extends \Test\TestCase {
$this->assertEquals($expectedResult, $result);
}
+ /**
+ * @return array
+ */
public function provideDocRootAppAbsoluteUrlParts() {
return array(
- array('files', 'index.php', array(), 'http://localhost/index.php/apps/files'),
- array('files', 'index.php', array('trut' => 'trat', 'dut' => 'dat'), 'http://localhost/index.php/apps/files?trut=trat&dut=dat'),
+ array('files', 'ajax/list.php', array(), 'http://localhost/index.php/apps/files/ajax/list.php'),
+ array('files', 'ajax/list.php', array('trut' => 'trat', 'dut' => 'dat'), 'http://localhost/index.php/apps/files/ajax/list.php?trut=trat&dut=dat'),
array('', 'index.php', array('trut' => 'trat', 'dut' => 'dat'), 'http://localhost/index.php?trut=trat&dut=dat'),
);
}
+ /**
+ * @return array
+ */
public function provideSubDirAppAbsoluteUrlParts() {
return array(
- array('files', 'index.php', array(), 'http://localhost/owncloud/index.php/apps/files'),
- array('files', 'index.php', array('trut' => 'trat', 'dut' => 'dat'), 'http://localhost/owncloud/index.php/apps/files?trut=trat&dut=dat'),
+ array('files', 'ajax/list.php', array(), 'http://localhost/owncloud/index.php/apps/files/ajax/list.php'),
+ array('files', 'ajax/list.php', array('trut' => 'trat', 'dut' => 'dat'), 'http://localhost/owncloud/index.php/apps/files/ajax/list.php?trut=trat&dut=dat'),
array('', 'index.php', array('trut' => 'trat', 'dut' => 'dat'), 'http://localhost/owncloud/index.php?trut=trat&dut=dat'),
);
}
diff --git a/tests/lib/notification/actiontest.php b/tests/lib/notification/actiontest.php
index e319c250cc7..0044df82cd1 100644
--- a/tests/lib/notification/actiontest.php
+++ b/tests/lib/notification/actiontest.php
@@ -49,7 +49,7 @@ class ActionTest extends TestCase {
*/
public function testSetLabel($label) {
$this->assertSame('', $this->action->getLabel());
- $this->action->setLabel($label);
+ $this->assertSame($this->action, $this->action->setLabel($label));
$this->assertSame($label, $this->action->getLabel());
}
@@ -68,7 +68,7 @@ class ActionTest extends TestCase {
/**
* @dataProvider dataSetLabelInvalid
- * @param string $label
+ * @param mixed $label
*
* @expectedException \InvalidArgumentException
*/
@@ -90,7 +90,7 @@ class ActionTest extends TestCase {
*/
public function testSetParsedLabel($label) {
$this->assertSame('', $this->action->getParsedLabel());
- $this->action->setParsedLabel($label);
+ $this->assertSame($this->action, $this->action->setParsedLabel($label));
$this->assertSame($label, $this->action->getParsedLabel());
}
@@ -108,7 +108,7 @@ class ActionTest extends TestCase {
/**
* @dataProvider dataSetParsedLabelInvalid
- * @param string $label
+ * @param mixed $label
*
* @expectedException \InvalidArgumentException
*/
@@ -132,7 +132,7 @@ class ActionTest extends TestCase {
*/
public function testSetLink($link, $type) {
$this->assertSame('', $this->action->getLink());
- $this->action->setLink($link, $type);
+ $this->assertSame($this->action, $this->action->setLink($link, $type));
$this->assertSame($link, $this->action->getLink());
$this->assertSame($type, $this->action->getRequestType());
}
@@ -162,8 +162,8 @@ class ActionTest extends TestCase {
/**
* @dataProvider dataSetLinkInvalid
- * @param string $link
- * @param string $type
+ * @param mixed $link
+ * @param mixed $type
*
* @expectedException \InvalidArgumentException
*/
@@ -171,45 +171,42 @@ class ActionTest extends TestCase {
$this->action->setLink($link, $type);
}
- public function dataSetIcon() {
+ public function dataSetPrimary() {
return [
- ['test1'],
- [str_repeat('a', 1)],
- [str_repeat('a', 64)],
+ [true],
+ [false],
];
}
/**
- * @dataProvider dataSetIcon
- * @param string $icon
+ * @dataProvider dataSetPrimary
+ * @param bool $primary
*/
- public function testSetIcon($icon) {
- $this->assertSame('', $this->action->getIcon());
- $this->action->setIcon($icon);
- $this->assertSame($icon, $this->action->getIcon());
+ public function testSetPrimary($primary) {
+ $this->assertSame(false, $this->action->isPrimary());
+ $this->assertSame($this->action, $this->action->setPrimary($primary));
+ $this->assertSame($primary, $this->action->isPrimary());
}
- public function dataSetIconInvalid() {
+ public function dataSetPrimaryInvalid() {
return [
- [true],
- [false],
[0],
[1],
[''],
- [str_repeat('a', 65)],
+ [str_repeat('a', 257)],
[[]],
- [[str_repeat('a', 65)]],
+ [[str_repeat('a', 257)]],
];
}
/**
- * @dataProvider dataSetIconInvalid
- * @param string $icon
+ * @dataProvider dataSetPrimaryInvalid
+ * @param mixed $primary
*
* @expectedException \InvalidArgumentException
*/
- public function testSetIconInvalid($icon) {
- $this->action->setIcon($icon);
+ public function testSetPrimaryInvalid($primary) {
+ $this->action->setPrimary($primary);
}
public function testIsValid() {
diff --git a/tests/lib/notification/notificationtest.php b/tests/lib/notification/notificationtest.php
index a790a53eaa7..662dc5a6176 100644
--- a/tests/lib/notification/notificationtest.php
+++ b/tests/lib/notification/notificationtest.php
@@ -93,7 +93,7 @@ class NotificationTest extends TestCase {
*/
public function testSetApp($app) {
$this->assertSame('', $this->notification->getApp());
- $this->notification->setApp($app);
+ $this->assertSame($this->notification, $this->notification->setApp($app));
$this->assertSame($app, $this->notification->getApp());
}
@@ -121,7 +121,7 @@ class NotificationTest extends TestCase {
*/
public function testSetUser($user) {
$this->assertSame('', $this->notification->getUser());
- $this->notification->setUser($user);
+ $this->assertSame($this->notification, $this->notification->setUser($user));
$this->assertSame($user, $this->notification->getUser());
}
@@ -149,7 +149,7 @@ class NotificationTest extends TestCase {
*/
public function testSetTimestamp($timestamp) {
$this->assertSame(0, $this->notification->getTimestamp());
- $this->notification->setTimestamp($timestamp);
+ $this->assertSame($this->notification, $this->notification->setTimestamp($timestamp));
$this->assertSame($timestamp, $this->notification->getTimestamp());
}
@@ -182,7 +182,7 @@ class NotificationTest extends TestCase {
public function testSetObject($type, $id) {
$this->assertSame('', $this->notification->getObjectType());
$this->assertSame(0, $this->notification->getObjectId());
- $this->notification->setObject($type, $id);
+ $this->assertSame($this->notification, $this->notification->setObject($type, $id));
$this->assertSame($type, $this->notification->getObjectType());
$this->assertSame($id, $this->notification->getObjectId());
}
@@ -233,7 +233,7 @@ class NotificationTest extends TestCase {
public function testSetSubject($subject, $parameters) {
$this->assertSame('', $this->notification->getSubject());
$this->assertSame([], $this->notification->getSubjectParameters());
- $this->notification->setSubject($subject, $parameters);
+ $this->assertSame($this->notification, $this->notification->setSubject($subject, $parameters));
$this->assertSame($subject, $this->notification->getSubject());
$this->assertSame($parameters, $this->notification->getSubjectParameters());
}
@@ -262,7 +262,7 @@ class NotificationTest extends TestCase {
*/
public function testSetParsedSubject($subject) {
$this->assertSame('', $this->notification->getParsedSubject());
- $this->notification->setParsedSubject($subject);
+ $this->assertSame($this->notification, $this->notification->setParsedSubject($subject));
$this->assertSame($subject, $this->notification->getParsedSubject());
}
@@ -296,7 +296,7 @@ class NotificationTest extends TestCase {
public function testSetMessage($message, $parameters) {
$this->assertSame('', $this->notification->getMessage());
$this->assertSame([], $this->notification->getMessageParameters());
- $this->notification->setMessage($message, $parameters);
+ $this->assertSame($this->notification, $this->notification->setMessage($message, $parameters));
$this->assertSame($message, $this->notification->getMessage());
$this->assertSame($parameters, $this->notification->getMessageParameters());
}
@@ -325,7 +325,7 @@ class NotificationTest extends TestCase {
*/
public function testSetParsedMessage($message) {
$this->assertSame('', $this->notification->getParsedMessage());
- $this->notification->setParsedMessage($message);
+ $this->assertSame($this->notification, $this->notification->setParsedMessage($message));
$this->assertSame($message, $this->notification->getParsedMessage());
}
@@ -353,7 +353,7 @@ class NotificationTest extends TestCase {
*/
public function testSetLink($link) {
$this->assertSame('', $this->notification->getLink());
- $this->notification->setLink($link);
+ $this->assertSame($this->notification, $this->notification->setLink($link));
$this->assertSame($link, $this->notification->getLink());
}
@@ -371,34 +371,6 @@ class NotificationTest extends TestCase {
$this->notification->setLink($link);
}
- public function dataSetIcon() {
- return $this->dataValidString(64);
- }
-
- /**
- * @dataProvider dataSetIcon
- * @param string $icon
- */
- public function testSetIcon($icon) {
- $this->assertSame('', $this->notification->getIcon());
- $this->notification->setIcon($icon);
- $this->assertSame($icon, $this->notification->getIcon());
- }
-
- public function dataSetIconInvalid() {
- return $this->dataInvalidString(64);
- }
-
- /**
- * @dataProvider dataSetIconInvalid
- * @param mixed $icon
- *
- * @expectedException \InvalidArgumentException
- */
- public function testSetIconInvalid($icon) {
- $this->notification->setIcon($icon);
- }
-
public function testCreateAction() {
$action = $this->notification->createAction();
$this->assertInstanceOf('OC\Notification\IAction', $action);
@@ -415,7 +387,7 @@ class NotificationTest extends TestCase {
$action->expects($this->never())
->method('isValidParsed');
- $this->notification->addAction($action);
+ $this->assertSame($this->notification, $this->notification->addAction($action));
$this->assertEquals([$action], $this->notification->getActions());
$this->assertEquals([], $this->notification->getParsedActions());
@@ -438,6 +410,24 @@ class NotificationTest extends TestCase {
$this->notification->addAction($action);
}
+ public function testAddActionSecondPrimary() {
+ /** @var \OC\Notification\IAction|\PHPUnit_Framework_MockObject_MockObject $action */
+ $action = $this->getMockBuilder('OC\Notification\IAction')
+ ->disableOriginalConstructor()
+ ->getMock();
+ $action->expects($this->exactly(2))
+ ->method('isValid')
+ ->willReturn(true);
+ $action->expects($this->exactly(2))
+ ->method('isPrimary')
+ ->willReturn(true);
+
+ $this->assertSame($this->notification, $this->notification->addAction($action));
+
+ $this->setExpectedException('\InvalidArgumentException');
+ $this->notification->addAction($action);
+ }
+
public function testAddParsedAction() {
/** @var \OC\Notification\IAction|\PHPUnit_Framework_MockObject_MockObject $action */
$action = $this->getMockBuilder('OC\Notification\IAction')
@@ -449,7 +439,7 @@ class NotificationTest extends TestCase {
$action->expects($this->never())
->method('isValid');
- $this->notification->addParsedAction($action);
+ $this->assertSame($this->notification, $this->notification->addParsedAction($action));
$this->assertEquals([$action], $this->notification->getParsedActions());
$this->assertEquals([], $this->notification->getActions());
@@ -472,6 +462,24 @@ class NotificationTest extends TestCase {
$this->notification->addParsedAction($action);
}
+ public function testAddActionSecondParsedPrimary() {
+ /** @var \OC\Notification\IAction|\PHPUnit_Framework_MockObject_MockObject $action */
+ $action = $this->getMockBuilder('OC\Notification\IAction')
+ ->disableOriginalConstructor()
+ ->getMock();
+ $action->expects($this->exactly(2))
+ ->method('isValidParsed')
+ ->willReturn(true);
+ $action->expects($this->exactly(2))
+ ->method('isPrimary')
+ ->willReturn(true);
+
+ $this->assertSame($this->notification, $this->notification->addParsedAction($action));
+
+ $this->setExpectedException('\InvalidArgumentException');
+ $this->notification->addParsedAction($action);
+ }
+
public function dataIsValid() {
return [
[false, '', false],
diff --git a/tests/lib/urlgenerator.php b/tests/lib/urlgenerator.php
index 60e1a86f16a..a5ab483109f 100644
--- a/tests/lib/urlgenerator.php
+++ b/tests/lib/urlgenerator.php
@@ -53,23 +53,23 @@ class Test_Urlgenerator extends \Test\TestCase {
public function provideRoutes() {
return array(
- array('files_index', 'http://localhost/owncloud/index.php/apps/files/'),
+ array('files_ajax_list', 'http://localhost/owncloud/index.php/apps/files/ajax/list.php'),
array('core_ajax_preview', 'http://localhost/owncloud/index.php/core/preview.png'),
);
}
public function provideDocRootAppUrlParts() {
return array(
- array('files', 'index.php', array(), '/index.php/apps/files'),
- array('files', 'index.php', array('trut' => 'trat', 'dut' => 'dat'), '/index.php/apps/files?trut=trat&dut=dat'),
+ array('files', 'ajax/list.php', array(), '/index.php/apps/files/ajax/list.php'),
+ array('files', 'ajax/list.php', array('trut' => 'trat', 'dut' => 'dat'), '/index.php/apps/files/ajax/list.php?trut=trat&dut=dat'),
array('', 'index.php', array('trut' => 'trat', 'dut' => 'dat'), '/index.php?trut=trat&dut=dat'),
);
}
public function provideSubDirAppUrlParts() {
return array(
- array('files', 'index.php', array(), '/owncloud/index.php/apps/files'),
- array('files', 'index.php', array('trut' => 'trat', 'dut' => 'dat'), '/owncloud/index.php/apps/files?trut=trat&dut=dat'),
+ array('files', 'ajax/list.php', array(), '/owncloud/index.php/apps/files/ajax/list.php'),
+ array('files', 'ajax/list.php', array('trut' => 'trat', 'dut' => 'dat'), '/owncloud/index.php/apps/files/ajax/list.php?trut=trat&dut=dat'),
array('', 'index.php', array('trut' => 'trat', 'dut' => 'dat'), '/owncloud/index.php?trut=trat&dut=dat'),
);
}