parent
f0e6876f2e
commit
698ecbf308
@ -0,0 +1,224 @@ |
|||||||
|
<?php |
||||||
|
/** |
||||||
|
* ownCloud - OCS API for server-to-server shares |
||||||
|
* |
||||||
|
* @copyright (C) 2014 ownCloud, Inc. |
||||||
|
* |
||||||
|
* @author Bjoern Schiessle <schiessle@owncloud.com> |
||||||
|
* |
||||||
|
* This library is free software; you can redistribute it and/or |
||||||
|
* modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE |
||||||
|
* License as published by the Free Software Foundation; either |
||||||
|
* version 3 of the License, or any later version. |
||||||
|
* |
||||||
|
* This library is distributed in the hope that it will be useful, |
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||||
|
* GNU AFFERO GENERAL PUBLIC LICENSE for more details. |
||||||
|
* |
||||||
|
* You should have received a copy of the GNU Affero General Public |
||||||
|
* License along with this library. If not, see <http://www.gnu.org/licenses/>. |
||||||
|
* |
||||||
|
*/ |
||||||
|
|
||||||
|
namespace OCA\Files_Sharing\API; |
||||||
|
|
||||||
|
class Server2Server { |
||||||
|
|
||||||
|
/** |
||||||
|
* create a new share |
||||||
|
* |
||||||
|
* @param array $params |
||||||
|
* @return \OC_OCS_Result |
||||||
|
*/ |
||||||
|
public function createShare($params) { |
||||||
|
|
||||||
|
if (!$this->isS2SEnabled(true)) { |
||||||
|
return \OC_OCS_Result(null, 503, 'Server does not support server-to-server sharing'); |
||||||
|
} |
||||||
|
|
||||||
|
$remote = isset($_POST['remote']) ? $_POST['remote'] : null; |
||||||
|
$token = isset($_POST['token']) ? $_POST['token'] : null; |
||||||
|
$name = isset($_POST['name']) ? $_POST['name'] : null; |
||||||
|
$owner = isset($_POST['owner']) ? $_POST['owner'] : null; |
||||||
|
$shareWith = isset($_POST['shareWith']) ? $_POST['shareWith'] : null; |
||||||
|
$remoteId = isset($_POST['remote_id']) ? (int)$_POST['remote_id'] : null; |
||||||
|
|
||||||
|
if ($remote && $token && $name && $owner && $remoteId && $shareWith) { |
||||||
|
|
||||||
|
if(!\OCP\Util::isValidFileName($name)) { |
||||||
|
return new \OC_OCS_Result(null, 400, 'The mountpoint name contains invalid characters.'); |
||||||
|
} |
||||||
|
|
||||||
|
if (!\OCP\User::userExists($shareWith)) { |
||||||
|
return new \OC_OCS_Result(null, 400, 'User does not exists'); |
||||||
|
} |
||||||
|
|
||||||
|
\OC_Util::setupFS($shareWith); |
||||||
|
|
||||||
|
$mountPoint = \OC\Files\Filesystem::normalizePath('/' . $name); |
||||||
|
$name = \OCP\Files::buildNotExistingFileName('/', $name); |
||||||
|
|
||||||
|
try { |
||||||
|
\OCA\Files_Sharing\Helper::addServer2ServerShare($remote, $token, $name, $mountPoint, $owner, $shareWith, '', $remoteId); |
||||||
|
|
||||||
|
\OC::$server->getActivityManager()->publishActivity( |
||||||
|
'files_sharing', \OCA\Files_Sharing\Activity::SUBJECT_REMOTE_SHARE_RECEIVED, array($owner), '', array(), |
||||||
|
'', '', $shareWith, \OCA\Files_Sharing\Activity::TYPE_REMOTE_SHARE, \OCA\Files_Sharing\Activity::PRIORITY_LOW); |
||||||
|
|
||||||
|
return new \OC_OCS_Result(); |
||||||
|
} catch (\Exception $e) { |
||||||
|
return new \OC_OCS_Result(null, 500, 'server can not add remote share, ' . $e->getMessage()); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return new \OC_OCS_Result(null, 400, 'server can not add remote share, missing parameter'); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* accept server-to-server share |
||||||
|
* |
||||||
|
* @param array $params |
||||||
|
* @return \OC_OCS_Result |
||||||
|
*/ |
||||||
|
public function acceptShare($params) { |
||||||
|
|
||||||
|
if (!$this->isS2SEnabled()) { |
||||||
|
return \OC_OCS_Result(null, 503, 'Server does not support server-to-server sharing'); |
||||||
|
} |
||||||
|
|
||||||
|
$id = $params['id']; |
||||||
|
$token = isset($_POST['token']) ? $_POST['token'] : null; |
||||||
|
$share = self::getShare($id, $token); |
||||||
|
|
||||||
|
if ($share) { |
||||||
|
list($file, $link) = self::getFile($share['uid_owner'], $share['file_source']); |
||||||
|
|
||||||
|
\OC::$server->getActivityManager()->publishActivity( |
||||||
|
'files_sharing', \OCA\Files_Sharing\Activity::SUBJECT_REMOTE_SHARE_ACCEPTED, array($share['share_with'], basename($file)), '', array(), |
||||||
|
$file, $link, $share['uid_owner'], \OCA\Files_Sharing\Activity::TYPE_REMOTE_SHARE, \OCA\Files_Sharing\Activity::PRIORITY_LOW); |
||||||
|
} |
||||||
|
|
||||||
|
return new \OC_OCS_Result(); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* decline server-to-server share |
||||||
|
* |
||||||
|
* @param array $params |
||||||
|
* @return \OC_OCS_Result |
||||||
|
*/ |
||||||
|
public function declineShare($params) { |
||||||
|
|
||||||
|
if (!$this->isS2SEnabled()) { |
||||||
|
return \OC_OCS_Result(null, 503, 'Server does not support server-to-server sharing'); |
||||||
|
} |
||||||
|
|
||||||
|
$id = $params['id']; |
||||||
|
$token = isset($_POST['token']) ? $_POST['token'] : null; |
||||||
|
|
||||||
|
$share = $this->getShare($id, $token); |
||||||
|
|
||||||
|
if ($share) { |
||||||
|
// userId must be set to the user who unshares |
||||||
|
\OCP\Share::unshare($share['item_type'], $share['item_source'], $share['share_type'], null, $share['uid_owner']); |
||||||
|
|
||||||
|
list($file, $link) = $this->getFile($share['uid_owner'], $share['file_source']); |
||||||
|
|
||||||
|
\OC::$server->getActivityManager()->publishActivity( |
||||||
|
'files_sharing', \OCA\Files_Sharing\Activity::SUBJECT_REMOTE_SHARE_DECLINED, array($share['share_with'], basename($file)), '', array(), |
||||||
|
$file, $link, $share['uid_owner'], \OCA\Files_Sharing\Activity::TYPE_REMOTE_SHARE, \OCA\Files_Sharing\Activity::PRIORITY_LOW); |
||||||
|
} |
||||||
|
|
||||||
|
return new \OC_OCS_Result(); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* remove server-to-server share if it was unshared by the owner |
||||||
|
* |
||||||
|
* @param array $params |
||||||
|
* @return \OC_OCS_Result |
||||||
|
*/ |
||||||
|
public function unshare($params) { |
||||||
|
|
||||||
|
if (!$this->isS2SEnabled()) { |
||||||
|
return \OC_OCS_Result(null, 503, 'Server does not support server-to-server sharing'); |
||||||
|
} |
||||||
|
|
||||||
|
$id = $params['id']; |
||||||
|
$token = isset($_POST['token']) ? $_POST['token'] : null; |
||||||
|
|
||||||
|
$query = \OCP\DB::prepare('SELECT * FROM `*PREFIX*share_external` WHERE `remote_id` = ? AND `share_token` = ?'); |
||||||
|
$query->execute(array($id, $token)); |
||||||
|
$share = $query->fetchRow(); |
||||||
|
|
||||||
|
if ($token && $id && !empty($share)) { |
||||||
|
|
||||||
|
$owner = $share['owner'] . '@' . $share['remote']; |
||||||
|
$mountpoint = $share['mountpoint']; |
||||||
|
$user = $share['user']; |
||||||
|
|
||||||
|
$query = \OCP\DB::prepare('DELETE FROM `*PREFIX*share_external` WHERE `remote_id` = ? AND `share_token` = ?'); |
||||||
|
$query->execute(array($id, $token)); |
||||||
|
|
||||||
|
\OC::$server->getActivityManager()->publishActivity( |
||||||
|
'files_sharing', \OCA\Files_Sharing\Activity::SUBJECT_REMOTE_SHARE_DECLINED, array($owner, $mountpoint), '', array(), |
||||||
|
'', '', $user, \OCA\Files_Sharing\Activity::TYPE_REMOTE_SHARE, \OCA\Files_Sharing\Activity::PRIORITY_MEDIUM); |
||||||
|
} |
||||||
|
|
||||||
|
return new \OC_OCS_Result(); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* get share |
||||||
|
* |
||||||
|
* @param int $id |
||||||
|
* @param string $token |
||||||
|
* @return array |
||||||
|
*/ |
||||||
|
private function getShare($id, $token) { |
||||||
|
$query = \OCP\DB::prepare('SELECT * FROM `*PREFIX*share` WHERE `id` = ? AND `token` = ? AND `share_type` = ?'); |
||||||
|
$query->execute(array($id, $token, \OCP\Share::SHARE_TYPE_REMOTE)); |
||||||
|
$share = $query->fetchRow(); |
||||||
|
|
||||||
|
return $share; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* get file |
||||||
|
* |
||||||
|
* @param string $user |
||||||
|
* @param int $fileSource |
||||||
|
* @return array with internal path of the file and a absolute link to it |
||||||
|
*/ |
||||||
|
private function getFile($user, $fileSource) { |
||||||
|
\OC_Util::setupFS($user); |
||||||
|
|
||||||
|
$file = \OC\Files\Filesystem::getPath($fileSource); |
||||||
|
$args = \OC\Files\Filesystem::is_dir($file) ? array('dir' => $file) : array('dir' => dirname($file), 'scrollto' => $file); |
||||||
|
$link = \OCP\Util::linkToAbsolute('files', 'index.php', $args); |
||||||
|
|
||||||
|
return array($file, $link); |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* check if server-to-server sharing is enabled |
||||||
|
* |
||||||
|
* @param bool $incoming |
||||||
|
* @return bool |
||||||
|
*/ |
||||||
|
private function isS2SEnabled($incoming = false) { |
||||||
|
|
||||||
|
$result = \OCP\App::isEnabled('files_sharing'); |
||||||
|
|
||||||
|
if ($incoming) { |
||||||
|
$result = $result && \OCA\Files_Sharing\Helper::isIncomingServer2serverShareEnabled(); |
||||||
|
} else { |
||||||
|
$result = $result && \OCA\Files_Sharing\Helper::isOutgoingServer2serverShareEnabled(); |
||||||
|
} |
||||||
|
|
||||||
|
return $result; |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -1 +1 @@ |
|||||||
0.5.3 |
0.5.4 |
||||||
|
@ -0,0 +1,165 @@ |
|||||||
|
<?php |
||||||
|
/** |
||||||
|
* ownCloud - publish activities |
||||||
|
* |
||||||
|
* @copyright (c) 2014, ownCloud Inc. |
||||||
|
* |
||||||
|
* @author Bjoern Schiessle <schiessle@owncloud.com> |
||||||
|
* |
||||||
|
* This library is free software; you can redistribute it and/or |
||||||
|
* modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE |
||||||
|
* License as published by the Free Software Foundation; either |
||||||
|
* version 3 of the License, or any later version. |
||||||
|
* |
||||||
|
* This library is distributed in the hope that it will be useful, |
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||||
|
* GNU AFFERO GENERAL PUBLIC LICENSE for more details. |
||||||
|
* |
||||||
|
* You should have received a copy of the GNU Affero General Public |
||||||
|
* License along with this library. If not, see <http://www.gnu.org/licenses/>. |
||||||
|
*/ |
||||||
|
|
||||||
|
namespace OCA\Files_Sharing; |
||||||
|
|
||||||
|
class Activity implements \OCP\Activity\IExtension { |
||||||
|
|
||||||
|
const TYPE_REMOTE_SHARE = 'remote_share'; |
||||||
|
const SUBJECT_REMOTE_SHARE_RECEIVED = 'remote_share_received'; |
||||||
|
const SUBJECT_REMOTE_SHARE_ACCEPTED = 'remote_share_accepted'; |
||||||
|
const SUBJECT_REMOTE_SHARE_DECLINED = 'remote_share_declined'; |
||||||
|
const SUBJECT_REMOTE_SHARE_UNSHARED = 'remote_share_unshared'; |
||||||
|
|
||||||
|
/** |
||||||
|
* The extension can return an array of additional notification types. |
||||||
|
* If no additional types are to be added false is to be returned |
||||||
|
* |
||||||
|
* @param string $languageCode |
||||||
|
* @return array|false |
||||||
|
*/ |
||||||
|
public function getNotificationTypes($languageCode) { |
||||||
|
$l = \OC::$server->getL10N('files_sharing', $languageCode); |
||||||
|
return array(self::TYPE_REMOTE_SHARE => $l->t('A file or folder was shared from <strong>another server</strong>')); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* The extension can filter the types based on the filter if required. |
||||||
|
* In case no filter is to be applied false is to be returned unchanged. |
||||||
|
* |
||||||
|
* @param array $types |
||||||
|
* @param string $filter |
||||||
|
* @return array|false |
||||||
|
*/ |
||||||
|
public function filterNotificationTypes($types, $filter) { |
||||||
|
return $types; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* For a given method additional types to be displayed in the settings can be returned. |
||||||
|
* In case no additional types are to be added false is to be returned. |
||||||
|
* |
||||||
|
* @param string $method |
||||||
|
* @return array|false |
||||||
|
*/ |
||||||
|
public function getDefaultTypes($method) { |
||||||
|
if ($method === 'stream') { |
||||||
|
return array(self::TYPE_REMOTE_SHARE); |
||||||
|
} |
||||||
|
|
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* The extension can translate a given message to the requested languages. |
||||||
|
* If no translation is available false is to be returned. |
||||||
|
* |
||||||
|
* @param string $app |
||||||
|
* @param string $text |
||||||
|
* @param array $params |
||||||
|
* @param boolean $stripPath |
||||||
|
* @param boolean $highlightParams |
||||||
|
* @param string $languageCode |
||||||
|
* @return string|false |
||||||
|
*/ |
||||||
|
public function translate($app, $text, $params, $stripPath, $highlightParams, $languageCode) { |
||||||
|
|
||||||
|
$l = \OC::$server->getL10N('files_sharing', $languageCode); |
||||||
|
|
||||||
|
if (!$text) { |
||||||
|
return ''; |
||||||
|
} |
||||||
|
|
||||||
|
if ($app === 'files_sharing') { |
||||||
|
switch ($text) { |
||||||
|
case self::SUBJECT_REMOTE_SHARE_RECEIVED: |
||||||
|
return $l->t('You received a new remote share from %s', $params)->__toString(); |
||||||
|
case self::SUBJECT_REMOTE_SHARE_ACCEPTED: |
||||||
|
return $l->t('%1$s accepted remote share <strong>%2$s</strong>', $params)->__toString(); |
||||||
|
case self::SUBJECT_REMOTE_SHARE_DECLINED: |
||||||
|
return $l->t('%1$s declined remote share <strong>%2$s</strong>', $params)->__toString(); |
||||||
|
case self::SUBJECT_REMOTE_SHARE_UNSHARED: |
||||||
|
return $l->t('%1$s unshared <strong>%2$s</strong>', $params)->__toString(); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* A string naming the css class for the icon to be used can be returned. |
||||||
|
* If no icon is known for the given type false is to be returned. |
||||||
|
* |
||||||
|
* @param string $type |
||||||
|
* @return string|false |
||||||
|
*/ |
||||||
|
public function getTypeIcon($type) { |
||||||
|
return 'icon-share'; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* The extension can define the parameter grouping by returning the index as integer. |
||||||
|
* In case no grouping is required false is to be returned. |
||||||
|
* |
||||||
|
* @param array $activity |
||||||
|
* @return integer|false |
||||||
|
*/ |
||||||
|
public function getGroupParameter($activity) { |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* The extension can define additional navigation entries. The array returned has to contain two keys 'top' |
||||||
|
* and 'apps' which hold arrays with the relevant entries. |
||||||
|
* If no further entries are to be added false is no be returned. |
||||||
|
* |
||||||
|
* @return array|false |
||||||
|
*/ |
||||||
|
public function getNavigation() { |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* The extension can check if a customer filter (given by a query string like filter=abc) is valid or not. |
||||||
|
* |
||||||
|
* @param string $filterValue |
||||||
|
* @return boolean |
||||||
|
*/ |
||||||
|
public function isFilterValid($filterValue) { |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* For a given filter the extension can specify the sql query conditions including parameters for that query. |
||||||
|
* In case the extension does not know the filter false is to be returned. |
||||||
|
* The query condition and the parameters are to be returned as array with two elements. |
||||||
|
* E.g. return array('`app` = ? and `message` like ?', array('mail', 'ownCloud%')); |
||||||
|
* |
||||||
|
* @param string $filter |
||||||
|
* @return array|false |
||||||
|
*/ |
||||||
|
public function getQueryForFilter($filter) { |
||||||
|
if ($filter === 'shares') { |
||||||
|
return array('`app` = ? and `type` = ?', array('files_sharing', self::TYPE_REMOTE_SHARE)); |
||||||
|
} |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -0,0 +1,102 @@ |
|||||||
|
<?php |
||||||
|
/** |
||||||
|
* ownCloud - test server-to-server OCS API |
||||||
|
* |
||||||
|
* @copyright (c) ownCloud, Inc. |
||||||
|
* |
||||||
|
* @author Bjoern Schiessle <schiessle@owncloud.com> |
||||||
|
* |
||||||
|
* This library is free software; you can redistribute it and/or |
||||||
|
* modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE |
||||||
|
* License as published by the Free Software Foundation; either |
||||||
|
* version 3 of the License, or any later version. |
||||||
|
* |
||||||
|
* This library is distributed in the hope that it will be useful, |
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||||
|
* GNU AFFERO GENERAL PUBLIC LICENSE for more details. |
||||||
|
* |
||||||
|
* You should have received a copy of the GNU Affero General Public |
||||||
|
* License along with this library. If not, see <http://www.gnu.org/licenses/>. |
||||||
|
* |
||||||
|
*/ |
||||||
|
|
||||||
|
use OCA\Files_Sharing\Tests\TestCase; |
||||||
|
|
||||||
|
/** |
||||||
|
* Class Test_Files_Sharing_Api |
||||||
|
*/ |
||||||
|
class Test_Files_Sharing_S2S_OCS_API extends TestCase { |
||||||
|
|
||||||
|
const TEST_FOLDER_NAME = '/folder_share_api_test'; |
||||||
|
|
||||||
|
private $s2s; |
||||||
|
|
||||||
|
protected function setUp() { |
||||||
|
parent::setUp(); |
||||||
|
|
||||||
|
self::loginHelper(self::TEST_FILES_SHARING_API_USER1); |
||||||
|
\OCP\Share::registerBackend('test', 'Test_Share_Backend'); |
||||||
|
|
||||||
|
$this->s2s = new \OCA\Files_Sharing\API\Server2Server(); |
||||||
|
} |
||||||
|
|
||||||
|
protected function tearDown() { |
||||||
|
$query = \OCP\DB::prepare('DELETE FROM `*PREFIX*share_external`'); |
||||||
|
$query->execute(); |
||||||
|
|
||||||
|
parent::tearDown(); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @medium |
||||||
|
*/ |
||||||
|
function testCreateShare() { |
||||||
|
// simulate a post request |
||||||
|
$_POST['remote'] = 'localhost'; |
||||||
|
$_POST['token'] = 'token'; |
||||||
|
$_POST['name'] = 'name'; |
||||||
|
$_POST['owner'] = 'owner'; |
||||||
|
$_POST['shareWith'] = self::TEST_FILES_SHARING_API_USER2; |
||||||
|
$_POST['remote_id'] = 1; |
||||||
|
|
||||||
|
$result = $this->s2s->createShare(null); |
||||||
|
|
||||||
|
$this->assertTrue($result->succeeded()); |
||||||
|
|
||||||
|
$query = \OCP\DB::prepare('SELECT * FROM `*PREFIX*share_external` WHERE `remote_id` = ?'); |
||||||
|
$result = $query->execute(array('1')); |
||||||
|
$data = $result->fetchRow(); |
||||||
|
|
||||||
|
$this->assertSame('localhost', $data['remote']); |
||||||
|
$this->assertSame('token', $data['share_token']); |
||||||
|
$this->assertSame('/name', $data['name']); |
||||||
|
$this->assertSame('owner', $data['owner']); |
||||||
|
$this->assertSame(self::TEST_FILES_SHARING_API_USER2, $data['user']); |
||||||
|
$this->assertSame(1, (int)$data['remote_id']); |
||||||
|
$this->assertSame(0, (int)$data['accepted']); |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
function testDeclineShare() { |
||||||
|
$dummy = \OCP\DB::prepare(' |
||||||
|
INSERT INTO `*PREFIX*share` |
||||||
|
(`share_type`, `uid_owner`, `item_type`, `item_source`, `item_target`, `file_source`, `file_target`, `permissions`, `stime`, `token`) |
||||||
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) |
||||||
|
'); |
||||||
|
$dummy->execute(array(\OCP\Share::SHARE_TYPE_REMOTE, self::TEST_FILES_SHARING_API_USER1, 'test', '1', '/1', '1', '/test.txt', '1', time(), 'token')); |
||||||
|
|
||||||
|
$verify = \OCP\DB::prepare('SELECT * FROM `*PREFIX*share`'); |
||||||
|
$result = $verify->execute(); |
||||||
|
$data = $result->fetchAll(); |
||||||
|
$this->assertSame(1, count($data)); |
||||||
|
|
||||||
|
$_POST['token'] = 'token'; |
||||||
|
$this->s2s->declineShare(array('id' => $data[0]['id'])); |
||||||
|
|
||||||
|
$verify = \OCP\DB::prepare('SELECT * FROM `*PREFIX*share`'); |
||||||
|
$result = $verify->execute(); |
||||||
|
$data = $result->fetchAll(); |
||||||
|
$this->assertEmpty($data); |
||||||
|
} |
||||||
|
} |
Loading…
Reference in new issue