Added system tags data structure and PHP side managers

Added SystemTagManager and SystemTagObjectMapper
This commit is contained in:
Vincent Petry 2015-11-26 15:49:14 +01:00
parent 129ec4fb40
commit b666367a79
11 changed files with 1478 additions and 5 deletions

View File

@ -1063,6 +1063,130 @@
</table> </table>
<table>
<!--
List of system-wide tags
-->
<name>*dbprefix*systemtag</name>
<declaration>
<field>
<name>id</name>
<type>integer</type>
<default>0</default>
<notnull>true</notnull>
<autoincrement>1</autoincrement>
<unsigned>true</unsigned>
<length>4</length>
</field>
<!-- Tag name -->
<field>
<name>name</name>
<type>text</type>
<default></default>
<notnull>true</notnull>
<length>64</length>
</field>
<!-- Visibility: 0 user-not-visible, 1 user-visible -->
<field>
<name>visibility</name>
<type>integer</type>
<default>1</default>
<notnull>true</notnull>
<length>1</length>
</field>
<!-- Editable: 0 user-not-editable, 1 user-editable -->
<field>
<name>editable</name>
<type>integer</type>
<default>1</default>
<notnull>true</notnull>
<length>1</length>
</field>
<index>
<name>tag_ident</name>
<unique>true</unique>
<field>
<name>name</name>
<sorting>ascending</sorting>
</field>
<field>
<name>visibility</name>
<sorting>ascending</sorting>
</field>
<field>
<name>editable</name>
<sorting>ascending</sorting>
</field>
</index>
</declaration>
</table>
<table>
<!--
System tag to object associations per object type.
-->
<name>*dbprefix*systemtag_object_mapping</name>
<declaration>
<!-- object id (ex: file id for files)-->
<field>
<name>objectid</name>
<type>integer</type>
<default>0</default>
<notnull>true</notnull>
<unsigned>true</unsigned>
<length>4</length>
</field>
<!-- object type (ex: "files")-->
<field>
<name>objecttype</name>
<type>text</type>
<default></default>
<notnull>true</notnull>
<length>64</length>
</field>
<!-- Foreign Key systemtag::id -->
<field>
<name>systemtagid</name>
<type>integer</type>
<default>0</default>
<notnull>true</notnull>
<unsigned>true</unsigned>
<length>4</length>
</field>
<index>
<unique>true</unique>
<name>mapping</name>
<field>
<name>objecttype</name>
<sorting>ascending</sorting>
</field>
<field>
<name>objectid</name>
<sorting>ascending</sorting>
</field>
<field>
<name>systemtagid</name>
<sorting>ascending</sorting>
</field>
</index>
</declaration>
</table>
<table> <table>
<!-- <!--

View File

@ -138,6 +138,12 @@ class Server extends SimpleContainer implements IServerContainer {
$tagMapper = $c->query('TagMapper'); $tagMapper = $c->query('TagMapper');
return new TagManager($tagMapper, $c->getUserSession()); return new TagManager($tagMapper, $c->getUserSession());
}); });
$this->registerService('SystemTagManager', function (Server $c) {
return new SystemTag\SystemTagManager($c->getDatabaseConnection());
});
$this->registerService('SystemTagObjectMapper', function (Server $c) {
return new SystemTag\SystemTagObjectMapper($c->getDatabaseConnection(), $c->getSystemTagManager());
});
$this->registerService('RootFolder', function (Server $c) { $this->registerService('RootFolder', function (Server $c) {
// TODO: get user and user manager from container as well // TODO: get user and user manager from container as well
$user = \OC_User::getUser(); $user = \OC_User::getUser();
@ -581,6 +587,29 @@ class Server extends SimpleContainer implements IServerContainer {
return $this->query('TagManager'); return $this->query('TagManager');
} }
/**
* Returns the system-tag manager
*
* @return \OCP\SystemTag\ISystemTagManager
*
* @since 9.0.0
*/
public function getSystemTagManager() {
return $this->query('SystemTagManager');
}
/**
* Returns the system-tag object mapper
*
* @return \OCP\SystemTag\ISystemTagObjectMapper
*
* @since 9.0.0
*/
public function getSystemTagObjectMapper() {
return $this->query('SystemTagObjectMapper');
}
/** /**
* Returns the avatar manager, used for avatar functionality * Returns the avatar manager, used for avatar functionality
* *

View File

@ -0,0 +1,90 @@
<?php
/**
* @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/>
*
*/
namespace OC\SystemTag;
use \OCP\IDBConnection;
class SystemTag implements \OCP\SystemTag\ISystemTag {
/**
* @var string
*/
private $id;
/**
* @var string
*/
private $name;
/**
* @var bool
*/
private $userVisible;
/**
* @var bool
*/
private $userAssignable;
/**
* Constructor.
*
* @param string $id tag id
* @param string $name tag name
* @param bool $userVisible whether the tag is user visible
* @param bool $userAssignable whether the tag is user assignable
*/
public function __construct($id, $name, $userVisible, $userAssignable) {
$this->id = $id;
$this->name = $name;
$this->userVisible = $userVisible;
$this->userAssignable = $userAssignable;
}
/**
* {@inheritdoc}
*/
public function getId() {
return $this->id;
}
/**
* {@inheritdoc}
*/
public function getName() {
return $this->name;
}
/**
* {@inheritdoc}
*/
public function isUserVisible() {
return $this->userVisible;
}
/**
* {@inheritdoc}
*/
public function isUserAssignable() {
return $this->userAssignable;
}
}

View File

@ -0,0 +1,264 @@
<?php
/**
* @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/>
*
*/
namespace OC\SystemTag;
use \OCP\IDBConnection;
use \OCP\SystemTag\TagNotFoundException;
use \OCP\SystemTag\TagAlreadyExistsException;
use \Doctrine\DBAL\Exception\UniqueConstraintViolationException;
class SystemTagManager implements \OCP\SystemTag\ISystemTagManager {
const TAG_TABLE = 'systemtag';
/**
* @var IDBConnection
*/
private $connection;
/**
* Prepared query for selecting tags directly
*
* @var \OCP\DB\QueryBuilder\IQueryBuilder
*/
private $selectTagQuery;
/**
* Constructor.
*
* @param IDBConnection $connection database connection
*/
public function __construct(IDBConnection $connection) {
$this->connection = $connection;
$query = $this->connection->getQueryBuilder();
$this->selectTagQuery = $query->select('*')
->from(self::TAG_TABLE)
->where($query->expr()->eq('name', $query->createParameter('name')))
->andWhere($query->expr()->eq('visibility', $query->createParameter('visibility')))
->andWhere($query->expr()->eq('editable', $query->createParameter('editable')));
}
/**
* {@inheritdoc}
*/
public function getTagsById($tagIds) {
if (!is_array($tagIds)) {
$tagIds = [$tagIds];
}
$tags = [];
// note: not all databases will fail if it's a string or starts with a number
foreach ($tagIds as $tagId) {
if (!is_numeric($tagId)) {
throw new \InvalidArgumentException('Tag id must be integer');
}
}
$query = $this->connection->getQueryBuilder();
$query->select('*')
->from(self::TAG_TABLE)
->where($query->expr()->in('id', $query->createParameter('tagids')))
->addOrderBy('name', 'ASC')
->addOrderBy('visibility', 'ASC')
->addOrderBy('editable', 'ASC')
->setParameter('tagids', $tagIds, \Doctrine\DBAL\Connection::PARAM_INT_ARRAY);
$result = $query->execute();
while ($row = $result->fetch()) {
$tags[$row['id']] = $this->createSystemTagFromRow($row);
}
$result->closeCursor();
if (count($tags) !== count($tagIds)) {
throw new TagNotFoundException(
'Tag(s) with id(s) ' . json_encode(array_diff($tagIds, array_keys($tags))) . ' not found'
);
}
return $tags;
}
/**
* {@inheritdoc}
*/
public function getAllTags($visibilityFilter = null, $nameSearchPattern = null) {
$tags = [];
$query = $this->connection->getQueryBuilder();
$query->select('*')
->from(self::TAG_TABLE);
if (!is_null($visibilityFilter)) {
$query->andWhere($query->expr()->eq('visibility', $query->createNamedParameter((int)$visibilityFilter)));
}
if (!empty($nameSearchPattern)) {
$query->andWhere(
$query->expr()->like(
'name',
$query->expr()->literal('%' . $this->connection->escapeLikeParameter($nameSearchPattern). '%')
)
);
}
$query
->addOrderBy('name', 'ASC')
->addOrderBy('visibility', 'ASC')
->addOrderBy('editable', 'ASC');
$result = $query->execute();
while ($row = $result->fetch()) {
$tags[$row['id']] = $this->createSystemTagFromRow($row);
}
$result->closeCursor();
return $tags;
}
/**
* {@inheritdoc}
*/
public function getTag($tagName, $userVisible, $userAssignable) {
$userVisible = (int)$userVisible;
$userAssignable = (int)$userAssignable;
$result = $this->selectTagQuery
->setParameter('name', $tagName)
->setParameter('visibility', $userVisible)
->setParameter('editable', $userAssignable)
->execute();
$row = $result->fetch();
$result->closeCursor();
if (!$row) {
throw new TagNotFoundException(
'Tag ("' . $tagName . '", '. $userVisible . ', ' . $userAssignable . ') does not exist'
);
}
return $this->createSystemTagFromRow($row);
}
/**
* {@inheritdoc}
*/
public function createTag($tagName, $userVisible, $userAssignable) {
$userVisible = (int)$userVisible;
$userAssignable = (int)$userAssignable;
$query = $this->connection->getQueryBuilder();
$query->insert(self::TAG_TABLE)
->values([
'name' => $query->createNamedParameter($tagName),
'visibility' => $query->createNamedParameter($userVisible),
'editable' => $query->createNamedParameter($userAssignable),
]);
try {
$query->execute();
} catch (UniqueConstraintViolationException $e) {
throw new TagAlreadyExistsException(
'Tag ("' . $tagName . '", '. $userVisible . ', ' . $userAssignable . ') already exists',
0,
$e
);
}
$tagId = $this->connection->lastInsertId('*PREFIX*' . self::TAG_TABLE);
return new SystemTag(
(int)$tagId,
$tagName,
(bool)$userVisible,
(bool)$userAssignable
);
}
/**
* {@inheritdoc}
*/
public function updateTag($tagId, $tagName, $userVisible, $userAssignable) {
$userVisible = (int)$userVisible;
$userAssignable = (int)$userAssignable;
$query = $this->connection->getQueryBuilder();
$query->update(self::TAG_TABLE)
->set('name', $query->createParameter('name'))
->set('visibility', $query->createParameter('visibility'))
->set('editable', $query->createParameter('editable'))
->where($query->expr()->eq('id', $query->createParameter('tagid')))
->setParameter('name', $tagName)
->setParameter('visibility', $userVisible)
->setParameter('editable', $userAssignable)
->setParameter('tagid', $tagId);
try {
if ($query->execute() === 0) {
throw new TagNotFoundException(
'Tag ("' . $tagName . '", '. $userVisible . ', ' . $userAssignable . ') does not exist'
);
}
} catch (UniqueConstraintViolationException $e) {
throw new TagAlreadyExistsException(
'Tag ("' . $tagName . '", '. $userVisible . ', ' . $userAssignable . ') already exists',
0,
$e
);
}
}
/**
* {@inheritdoc}
*/
public function deleteTags($tagIds) {
if (!is_array($tagIds)) {
$tagIds = [$tagIds];
}
// delete relationships first
$query = $this->connection->getQueryBuilder();
$query->delete(SystemTagObjectMapper::RELATION_TABLE)
->where($query->expr()->in('systemtagid', $query->createParameter('tagids')))
->setParameter('tagids', $tagIds, \Doctrine\DBAL\Connection::PARAM_INT_ARRAY)
->execute();
$query = $this->connection->getQueryBuilder();
$query->delete(self::TAG_TABLE)
->where($query->expr()->in('id', $query->createParameter('tagids')))
->setParameter('tagids', $tagIds, \Doctrine\DBAL\Connection::PARAM_INT_ARRAY);
if ($query->execute() === 0) {
throw new TagNotFoundException(
'Tag does not exist'
);
}
}
private function createSystemTagFromRow($row) {
return new SystemTag((int)$row['id'], $row['name'], (bool)$row['visibility'], (bool)$row['editable']);
}
}

View File

@ -0,0 +1,213 @@
<?php
/**
* @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/>
*
*/
namespace OC\SystemTag;
use \OCP\SystemTag\ISystemTagManager;
use \OCP\IDBConnection;
use \OCP\SystemTag\TagNotFoundException;
use \Doctrine\DBAL\Exception\UniqueConstraintViolationException;
class SystemTagObjectMapper implements \OCP\SystemTag\ISystemTagObjectMapper {
const RELATION_TABLE = 'systemtag_object_mapping';
/**
* @var ISystemTagManager
*/
private $tagManager;
/**
* @var IDBConnection
*/
private $connection;
/**
* Constructor.
*
* @param IDBConnection $connection database connection
* @param ISystemTagManager $tagManager system tag manager
*/
public function __construct(IDBConnection $connection, ISystemTagManager $tagManager) {
$this->connection = $connection;
$this->tagManager = $tagManager;
}
/**
* {@inheritdoc}
*/
public function getTagIdsForObjects($objIds, $objectType) {
if (!is_array($objIds)) {
$objIds = [$objIds];
}
$query = $this->connection->getQueryBuilder();
$query->select(['systemtagid', 'objectid'])
->from(self::RELATION_TABLE)
->where($query->expr()->in('objectid', $query->createParameter('objectids')))
->andWhere($query->expr()->eq('objecttype', $query->createParameter('objecttype')))
->setParameter('objectids', $objIds, \Doctrine\DBAL\Connection::PARAM_INT_ARRAY)
->setParameter('objecttype', $objectType)
->addOrderBy('objectid', 'ASC')
->addOrderBy('systemtagid', 'ASC');
$mapping = [];
foreach ($objIds as $objId) {
$mapping[$objId] = [];
}
$result = $query->execute();
while ($row = $result->fetch()) {
$objectId = $row['objectid'];
$mapping[$objectId][] = $row['systemtagid'];
}
$result->closeCursor();
return $mapping;
}
/**
* {@inheritdoc}
*/
public function getObjectIdsForTags($tagIds, $objectType) {
if (!is_array($tagIds)) {
$tagIds = [$tagIds];
}
$this->assertTagsExist($tagIds);
$query = $this->connection->getQueryBuilder();
$query->select($query->createFunction('DISTINCT(objectid)'))
->from(self::RELATION_TABLE)
->where($query->expr()->in('systemtagid', $query->createParameter('tagids')))
->andWhere($query->expr()->eq('objecttype', $query->createParameter('objecttype')))
->setParameter('tagids', $tagIds, \Doctrine\DBAL\Connection::PARAM_INT_ARRAY)
->setParameter('objecttype', $objectType);
$objectIds = [];
$result = $query->execute();
while ($row = $result->fetch()) {
$objectIds[] = $row['objectid'];
}
return $objectIds;
}
/**
* {@inheritdoc}
*/
public function assignTags($objId, $objectType, $tagIds) {
if (!is_array($tagIds)) {
$tagIds = [$tagIds];
}
$this->assertTagsExist($tagIds);
$query = $this->connection->getQueryBuilder();
$query->insert(self::RELATION_TABLE)
->values([
'objectid' => $query->createNamedParameter($objId),
'objecttype' => $query->createNamedParameter($objectType),
'systemtagid' => $query->createParameter('tagid'),
]);
foreach ($tagIds as $tagId) {
try {
$query->setParameter('tagid', $tagId);
$query->execute();
} catch (UniqueConstraintViolationException $e) {
// ignore existing relations
}
}
}
/**
* {@inheritdoc}
*/
public function unassignTags($objId, $objectType, $tagIds) {
if (!is_array($tagIds)) {
$tagIds = [$tagIds];
}
$this->assertTagsExist($tagIds);
$query = $this->connection->getQueryBuilder();
$query->delete(self::RELATION_TABLE)
->where($query->expr()->eq('objectid', $query->createParameter('objectid')))
->andWhere($query->expr()->eq('objecttype', $query->createParameter('objecttype')))
->andWhere($query->expr()->in('systemtagid', $query->createParameter('tagids')))
->setParameter('objectid', $objId)
->setParameter('objecttype', $objectType)
->setParameter('tagids', $tagIds, \Doctrine\DBAL\Connection::PARAM_INT_ARRAY)
->execute();
}
/**
* {@inheritdoc}
*/
public function haveTag($objIds, $objectType, $tagId, $all = true) {
$this->assertTagsExist([$tagId]);
$query = $this->connection->getQueryBuilder();
$query->select($query->createFunction('COUNT(1)'))
->from(self::RELATION_TABLE)
->where($query->expr()->in('objectid', $query->createParameter('objectids')))
->andWhere($query->expr()->eq('objecttype', $query->createParameter('objecttype')))
->andWhere($query->expr()->eq('systemtagid', $query->createParameter('tagid')))
->setParameter('objectids', $objIds, \Doctrine\DBAL\Connection::PARAM_INT_ARRAY)
->setParameter('tagid', $tagId)
->setParameter('objecttype', $objectType)
->setMaxResults(1);
$result = $query->execute();
$row = $result->fetch(\PDO::FETCH_NUM);
$result->closeCursor();
if ($all) {
return ((int)$row[0] === count($objIds));
} else {
return (int)$row[0] > 0;
}
}
/**
* Asserts that all the given tag ids exist.
*
* @param string[] $tagIds tag ids to check
*
* @throws \OCP\SystemTag\TagNotFoundException if at least one tag did not exist
*/
private function assertTagsExist($tagIds) {
$tags = $this->tagManager->getTagsById($tagIds);
if (count($tags) !== count($tagIds)) {
// at least one tag missing, bail out
$foundTagIds = array_map(
function($tag) {
return $tag->getId();
},
$tags
);
$missingTagIds = array_diff($tagIds, $foundTagIds);
throw new TagNotFoundException('Tags ' . json_encode($missingTagIds) . ' do not exist');
}
}
}

View File

@ -470,4 +470,22 @@ interface IServerContainer {
* @since 8.2.0 * @since 8.2.0
*/ */
public function getNotificationManager(); public function getNotificationManager();
/**
* Returns the system-tag manager
*
* @return \OCP\SystemTag\ISystemTagManager
*
* @since 9.0.0
*/
public function getSystemTagManager();
/**
* Returns the system-tag object mapper
*
* @return \OCP\SystemTag\ISystemTagObjectMapper
*
* @since 9.0.0
*/
public function getSystemTagObjectMapper();
} }

View File

@ -62,7 +62,7 @@ interface ISystemTag {
* *
* @since 9.0.0 * @since 9.0.0
*/ */
public function isUserAsssignable(); public function isUserAssignable();
} }

View File

@ -31,9 +31,11 @@ interface ISystemTagManager {
/** /**
* Returns the tag objects matching the given tag ids. * Returns the tag objects matching the given tag ids.
* *
* @param array|string $tagIds The ID or array of IDs of the tags to retrieve * @param array|string $tagIds id or array of unique ids of the tag to retrieve
* *
* @return \OCP\SystemTag\ISystemTag[] array of system tags or empty array if none found * @return \OCP\SystemTag\ISystemTag[] array of system tags with tag id as key
*
* @throws \OCP\SystemTag\TagNotFoundException if at least one given tag id did no exist
* *
* @since 9.0.0 * @since 9.0.0
*/ */
@ -72,14 +74,14 @@ interface ISystemTagManager {
/** /**
* Returns all known tags, optionally filtered by visibility. * Returns all known tags, optionally filtered by visibility.
* *
* @param bool $visibleOnly whether to only return user visible tags * @param bool|null $visibilityFilter filter by visibility if non-null
* @param string $nameSearchPattern optional search pattern for the tag name * @param string $nameSearchPattern optional search pattern for the tag name
* *
* @return \OCP\SystemTag\ISystemTag[] array of system tags or empty array if none found * @return \OCP\SystemTag\ISystemTag[] array of system tags or empty array if none found
* *
* @since 9.0.0 * @since 9.0.0
*/ */
public function getAllTags($visibleOnly = false, $nameSearchPattern = null); public function getAllTags($visibilityFilter = null, $nameSearchPattern = null);
/** /**
* Updates the given tag * Updates the given tag

View File

@ -69,6 +69,11 @@ interface ISystemTagObjectMapper {
/** /**
* Assign the given tags to the given object. * Assign the given tags to the given object.
* *
* If at least one of the given tag ids doesn't exist, none of the tags
* will be assigned.
*
* If the relationship already existed, fail silently.
*
* @param string $objId object id * @param string $objId object id
* @param string $objectType object type * @param string $objectType object type
* @param string|array $tagIds tag id or array of tag ids to assign * @param string|array $tagIds tag id or array of tag ids to assign
@ -83,6 +88,11 @@ interface ISystemTagObjectMapper {
/** /**
* Unassign the given tags from the given object. * Unassign the given tags from the given object.
* *
* If at least one of the given tag ids doesn't exist, none of the tags
* will be unassigned.
*
* If the relationship did not exist in the first place, fail silently.
*
* @param string $objId object id * @param string $objId object id
* @param string $objectType object type * @param string $objectType object type
* @param string|array $tagIds tag id or array of tag ids to unassign * @param string|array $tagIds tag id or array of tag ids to unassign

View File

@ -0,0 +1,396 @@
<?php
/**
* Copyright (c) 2015 Vincent Petry <pvince81@owncloud.com>
* This file is licensed under the Affero General Public License version 3 or
* later.
* See the COPYING-README file.
*
*/
namespace Test\SystemTag;
use \OCP\SystemTag\ISystemTagManager;
use \OCP\SystemTag\TagNotFoundException;
use \OCP\SystemTag\TagAlreadyExistsException;
use \OCP\IDBConnection;
class TestSystemTagManager extends \Test\TestCase {
/**
* @var ISystemTagManager
**/
private $tagManager;
/**
* @var IDBConnection
*/
private $connection;
public function setUp() {
parent::setUp();
$this->connection = \OC::$server->getDatabaseConnection();
$this->tagManager = new \OC\SystemTag\SystemTagManager($this->connection);
}
public function tearDown() {
$query = $this->connection->getQueryBuilder();
$query->delete(\OC\SystemTag\SystemTagObjectMapper::RELATION_TABLE)->execute();
$query->delete(\OC\SystemTag\SystemTagManager::TAG_TABLE)->execute();
}
public function getAllTagsDataProvider() {
return [
[
// no tags at all
[]
],
[
// simple
[
['one', false, false],
['two', false, false],
]
],
[
// duplicate names, different flags
[
['one', false, false],
['one', true, false],
['one', false, true],
['one', true, true],
['two', false, false],
['two', false, true],
]
]
];
}
/**
* @dataProvider getAllTagsDataProvider
*/
public function testGetAllTags($testTags) {
$testTagsById = [];
foreach ($testTags as $testTag) {
$tag = $this->tagManager->createTag($testTag[0], $testTag[1], $testTag[2]);
$testTagsById[$tag->getId()] = $tag;
}
$tagList = $this->tagManager->getAllTags();
$this->assertCount(count($testTags), $tagList);
foreach ($testTagsById as $testTagId => $testTag) {
$this->assertTrue(isset($tagList[$testTagId]));
$this->assertSameTag($tagList[$testTagId], $testTag);
}
}
public function getAllTagsFilteredDataProvider() {
return [
[
[
// no tags at all
],
null,
null,
[]
],
// filter by visibile only
[
// none visible
[
['one', false, false],
['two', false, false],
],
true,
null,
[]
],
[
// one visible
[
['one', true, false],
['two', false, false],
],
true,
null,
[
['one', true, false],
]
],
[
// one invisible
[
['one', true, false],
['two', false, false],
],
false,
null,
[
['two', false, false],
]
],
// filter by name pattern
[
[
['one', true, false],
['one', false, false],
['two', true, false],
],
null,
'on',
[
['one', true, false],
['one', false, false],
]
],
// filter by name pattern and visibility
[
// one visible
[
['one', true, false],
['two', true, false],
['one', false, false],
],
true,
'on',
[
['one', true, false],
]
],
// filter by name pattern in the middle
[
// one visible
[
['abcdefghi', true, false],
['two', true, false],
],
null,
'def',
[
['abcdefghi', true, false],
]
]
];
}
/**
* @dataProvider getAllTagsFilteredDataProvider
*/
public function testGetAllTagsFiltered($testTags, $visibilityFilter, $nameSearch, $expectedResults) {
foreach ($testTags as $testTag) {
$tag = $this->tagManager->createTag($testTag[0], $testTag[1], $testTag[2]);
}
$testTagsById = [];
foreach ($expectedResults as $expectedTag) {
$tag = $this->tagManager->getTag($expectedTag[0], $expectedTag[1], $expectedTag[2]);
$testTagsById[$tag->getId()] = $tag;
}
$tagList = $this->tagManager->getAllTags($visibilityFilter, $nameSearch);
$this->assertCount(count($testTagsById), $tagList);
foreach ($testTagsById as $testTagId => $testTag) {
$this->assertTrue(isset($tagList[$testTagId]));
$this->assertSameTag($tagList[$testTagId], $testTag);
}
}
public function oneTagMultipleFlagsProvider() {
return [
['one', false, false],
['one', true, false],
['one', false, true],
['one', true, true],
];
}
/**
* @dataProvider oneTagMultipleFlagsProvider
* @expectedException \OCP\SystemTag\TagAlreadyExistsException
*/
public function testCreateDuplicate($name, $userVisible, $userAssignable) {
try {
$this->tagManager->createTag($name, $userVisible, $userAssignable);
} catch (\Exception $e) {
$this->assertTrue(false, 'No exception thrown for the first create call');
}
$this->tagManager->createTag($name, $userVisible, $userAssignable);
}
/**
* @dataProvider oneTagMultipleFlagsProvider
*/
public function testGetExistingTag($name, $userVisible, $userAssignable) {
$tag1 = $this->tagManager->createTag($name, $userVisible, $userAssignable);
$tag2 = $this->tagManager->getTag($name, $userVisible, $userAssignable);
$this->assertSameTag($tag1, $tag2);
}
/**
* @dataProvider oneTagMultipleFlagsProvider
*/
public function testGetExistingTagById($name, $userVisible, $userAssignable) {
$tag1 = $this->tagManager->createTag('one', true, false);
$tag2 = $this->tagManager->createTag('two', false, true);
$tagList = $this->tagManager->getTagsById([$tag1->getId(), $tag2->getId()]);
$this->assertCount(2, $tagList);
$this->assertSameTag($tag1, $tagList[$tag1->getId()]);
$this->assertSameTag($tag2, $tagList[$tag2->getId()]);
}
/**
* @expectedException \OCP\SystemTag\TagNotFoundException
*/
public function testGetNonExistingTag() {
$this->tagManager->getTag('nonexist', false, false);
}
/**
* @expectedException \OCP\SystemTag\TagNotFoundException
*/
public function testGetNonExistingTagsById() {
$tag1 = $this->tagManager->createTag('one', true, false);
$this->tagManager->getTagsById([$tag1->getId(), 100, 101]);
}
/**
* @expectedException \InvalidArgumentException
*/
public function testGetInvalidTagIdFormat() {
$tag1 = $this->tagManager->createTag('one', true, false);
$this->tagManager->getTagsById([$tag1->getId() . 'suffix']);
}
public function updateTagProvider() {
return [
[
// update name
['one', true, true],
['two', true, true]
],
[
// update one flag
['one', false, true],
['one', true, true]
],
[
// update all flags
['one', false, false],
['one', true, true]
],
[
// update all
['one', false, false],
['two', true, true]
],
];
}
/**
* @dataProvider updateTagProvider
*/
public function testUpdateTag($tagCreate, $tagUpdated) {
$tag1 = $this->tagManager->createTag(
$tagCreate[0],
$tagCreate[1],
$tagCreate[2]
);
$this->tagManager->updateTag(
$tag1->getId(),
$tagUpdated[0],
$tagUpdated[1],
$tagUpdated[2]
);
$tag2 = $this->tagManager->getTag(
$tagUpdated[0],
$tagUpdated[1],
$tagUpdated[2]
);
$this->assertEquals($tag2->getId(), $tag1->getId());
$this->assertEquals($tag2->getName(), $tagUpdated[0]);
$this->assertEquals($tag2->isUserVisible(), $tagUpdated[1]);
$this->assertEquals($tag2->isUserAssignable(), $tagUpdated[2]);
}
/**
* @dataProvider updateTagProvider
* @expectedException \OCP\SystemTag\TagAlreadyExistsException
*/
public function testUpdateTagDuplicate($tagCreate, $tagUpdated) {
$this->tagManager->createTag(
$tagCreate[0],
$tagCreate[1],
$tagCreate[2]
);
$tag2 = $this->tagManager->createTag(
$tagUpdated[0],
$tagUpdated[1],
$tagUpdated[2]
);
// update to match the first tag
$this->tagManager->updateTag(
$tag2->getId(),
$tagCreate[0],
$tagCreate[1],
$tagCreate[2]
);
}
public function testDeleteTags() {
$tag1 = $this->tagManager->createTag('one', true, false);
$tag2 = $this->tagManager->createTag('two', false, true);
$this->tagManager->deleteTags([$tag1->getId(), $tag2->getId()]);
$this->assertEmpty($this->tagManager->getAllTags());
}
/**
* @expectedException \OCP\SystemTag\TagNotFoundException
*/
public function testDeleteNonExistingTag() {
$this->tagManager->deleteTags([100]);
}
public function testDeleteTagRemovesRelations() {
$tag1 = $this->tagManager->createTag('one', true, false);
$tag2 = $this->tagManager->createTag('two', true, true);
$tagMapper = new \OC\SystemTag\SystemTagObjectMapper($this->connection, $this->tagManager);
$tagMapper->assignTags(1, 'testtype', $tag1->getId());
$tagMapper->assignTags(1, 'testtype', $tag2->getId());
$tagMapper->assignTags(2, 'testtype', $tag1->getId());
$this->tagManager->deleteTags($tag1->getId());
$tagIdMapping = $tagMapper->getTagIdsForObjects(
[1, 2],
'testtype'
);
$this->assertEquals([
1 => [$tag2->getId()],
2 => [],
], $tagIdMapping);
}
private function assertSameTag($tag1, $tag2) {
$this->assertEquals($tag1->getId(), $tag2->getId());
$this->assertEquals($tag1->getName(), $tag2->getName());
$this->assertEquals($tag1->isUserVisible(), $tag2->isUserVisible());
$this->assertEquals($tag1->isUserAssignable(), $tag2->isUserAssignable());
}
}

View File

@ -0,0 +1,327 @@
<?php
/**
* Copyright (c) 2015 Vincent Petry <pvince81@owncloud.com>
* This file is licensed under the Affero General Public License version 3 or
* later.
* See the COPYING-README file.
*
*/
namespace Test\SystemTag;
use \OCP\SystemTag\ISystemTag;
use \OCP\SystemTag\ISystemTagManager;
use \OCP\SystemTag\ISystemTagObjectMapper;
use \OCP\SystemTag\TagNotFoundException;
use \OCP\SystemTag\TagAlreadyExistsException;
use \OCP\IDBConnection;
use \OC\SystemTag\SystemTag;
class TestSystemTagObjectMapper extends \Test\TestCase {
/**
* @var ISystemTagManager
**/
private $tagManager;
/**
* @var ISystemTagObjectMapper
**/
private $tagMapper;
/**
* @var IDBConnection
*/
private $connection;
/**
* @var ISystemTag
*/
private $tag1;
/**
* @var ISystemTag
*/
private $tag2;
/**
* @var ISystemTag
*/
private $tag3;
public function setUp() {
parent::setUp();
$this->connection = \OC::$server->getDatabaseConnection();
$this->tagManager = $this->getMockBuilder('OCP\SystemTag\ISystemTagManager')
->getMock();
$this->tagMapper = new \OC\SystemTag\SystemTagObjectMapper($this->connection, $this->tagManager);
$this->tag1 = new SystemTag(1, 'testtag1', false, false);
$this->tag2 = new SystemTag(2, 'testtag2', true, false);
$this->tag3 = new SystemTag(3, 'testtag3', false, false);
$this->tagManager->expects($this->any())
->method('getTagsById')
->will($this->returnCallback(function($tagIds) {
$result = [];
if (in_array(1, $tagIds)) {
$result[1] = $this->tag1;
}
if (in_array(2, $tagIds)) {
$result[2] = $this->tag2;
}
if (in_array(3, $tagIds)) {
$result[3] = $this->tag3;
}
return $result;
}));
$this->tagMapper->assignTags(1, 'testtype', $this->tag1->getId());
$this->tagMapper->assignTags(1, 'testtype', $this->tag2->getId());
$this->tagMapper->assignTags(2, 'testtype', $this->tag1->getId());
$this->tagMapper->assignTags(3, 'anothertype', $this->tag1->getId());
}
public function tearDown() {
$query = $this->connection->getQueryBuilder();
$query->delete(\OC\SystemTag\SystemTagObjectMapper::RELATION_TABLE)->execute();
$query->delete(\OC\SystemTag\SystemTagManager::TAG_TABLE)->execute();
}
public function testGetTagsForObjects() {
$tagIdMapping = $this->tagMapper->getTagIdsForObjects(
[1, 2, 3, 4],
'testtype'
);
$this->assertEquals([
1 => [$this->tag1->getId(), $this->tag2->getId()],
2 => [$this->tag1->getId()],
3 => [],
4 => [],
], $tagIdMapping);
}
public function testGetObjectsForTags() {
$objectIds = $this->tagMapper->getObjectIdsForTags(
[$this->tag1->getId(), $this->tag2->getId(), $this->tag3->getId()],
'testtype'
);
$this->assertEquals([
1,
2,
], $objectIds);
}
/**
* @expectedException \OCP\SystemTag\TagNotFoundException
*/
public function testGetObjectsForNonExistingTag() {
$this->tagMapper->getObjectIdsForTags(
[100],
'testtype'
);
}
public function testAssignUnassignTags() {
$this->tagMapper->unassignTags(1, 'testtype', [$this->tag1->getId()]);
$tagIdMapping = $this->tagMapper->getTagIdsForObjects(1, 'testtype');
$this->assertEquals([
1 => [$this->tag2->getId()],
], $tagIdMapping);
$this->tagMapper->assignTags(1, 'testtype', [$this->tag1->getId()]);
$this->tagMapper->assignTags(1, 'testtype', $this->tag3->getId());
$tagIdMapping = $this->tagMapper->getTagIdsForObjects(1, 'testtype');
$this->assertEquals([
1 => [$this->tag1->getId(), $this->tag2->getId(), $this->tag3->getId()],
], $tagIdMapping);
}
public function testReAssignUnassignTags() {
// reassign tag1
$this->tagMapper->assignTags(1, 'testtype', [$this->tag1->getId()]);
// tag 3 was never assigned
$this->tagMapper->unassignTags(1, 'testtype', [$this->tag3->getId()]);
$this->assertTrue(true, 'No error when reassigning/unassigning');
}
/**
* @expectedException \OCP\SystemTag\TagNotFoundException
*/
public function testAssignNonExistingTags() {
$this->tagMapper->assignTags(1, 'testtype', [100]);
}
public function testAssignNonExistingTagInArray() {
$caught = false;
try {
$this->tagMapper->assignTags(1, 'testtype', [100, $this->tag3->getId()]);
} catch (TagNotFoundException $e) {
$caught = true;
}
$this->assertTrue($caught, 'Exception thrown');
$tagIdMapping = $this->tagMapper->getTagIdsForObjects(
[1],
'testtype'
);
$this->assertEquals([
1 => [$this->tag1->getId(), $this->tag2->getId()],
], $tagIdMapping, 'None of the tags got assigned');
}
/**
* @expectedException \OCP\SystemTag\TagNotFoundException
*/
public function testUnassignNonExistingTags() {
$this->tagMapper->unassignTags(1, 'testtype', [100]);
}
public function testUnassignNonExistingTagsInArray() {
$caught = false;
try {
$this->tagMapper->unassignTags(1, 'testtype', [100, $this->tag1->getId()]);
} catch (TagNotFoundException $e) {
$caught = true;
}
$this->assertTrue($caught, 'Exception thrown');
$tagIdMapping = $this->tagMapper->getTagIdsForObjects(
[1],
'testtype'
);
$this->assertEquals([
1 => [$this->tag1->getId(), $this->tag2->getId()],
], $tagIdMapping, 'None of the tags got unassigned');
}
public function testHaveTagAllMatches() {
$this->assertTrue(
$this->tagMapper->haveTag(
[1],
'testtype',
$this->tag1->getId(),
true
),
'object 1 has the tag tag1'
);
$this->assertTrue(
$this->tagMapper->haveTag(
[1, 2],
'testtype',
$this->tag1->getId(),
true
),
'object 1 and object 2 ALL have the tag tag1'
);
$this->assertFalse(
$this->tagMapper->haveTag(
[1, 2],
'testtype',
$this->tag2->getId(),
true
),
'object 1 has tag2 but not object 2, so not ALL of them'
);
$this->assertFalse(
$this->tagMapper->haveTag(
[2],
'testtype',
$this->tag2->getId(),
true
),
'object 2 does not have tag2'
);
$this->assertFalse(
$this->tagMapper->haveTag(
[3],
'testtype',
$this->tag2->getId(),
true
),
'object 3 does not have tag1 due to different type'
);
}
public function testHaveTagAtLeastOneMatch() {
$this->assertTrue(
$this->tagMapper->haveTag(
[1],
'testtype',
$this->tag1->getId(),
false
),
'object1 has the tag tag1'
);
$this->assertTrue(
$this->tagMapper->haveTag(
[1, 2],
'testtype',
$this->tag1->getId(),
false
),
'object 1 and object 2 both the tag tag1'
);
$this->assertTrue(
$this->tagMapper->haveTag(
[1, 2],
'testtype',
$this->tag2->getId(),
false
),
'at least object 1 has the tag tag2'
);
$this->assertFalse(
$this->tagMapper->haveTag(
[2],
'testtype',
$this->tag2->getId(),
false
),
'object 2 does not have tag2'
);
$this->assertFalse(
$this->tagMapper->haveTag(
[3],
'testtype',
$this->tag2->getId(),
false
),
'object 3 does not have tag1 due to different type'
);
}
/**
* @expectedException \OCP\SystemTag\TagNotFoundException
*/
public function testHaveTagNonExisting() {
$this->tagMapper->haveTag(
[1],
'testtype',
100
);
}
}