Added tag merging

This commit is contained in:
Marcin Kurczewski 2014-11-10 11:56:30 +01:00
parent be4b742d21
commit 2d529107db
11 changed files with 113 additions and 29 deletions

View file

@ -74,6 +74,7 @@ changeTagImplications = moderator, administrator
changeTagSuggestions = moderator, administrator
banTags = moderator, administrator
deleteTags = moderator, administrator
mergeTags = moderator, administrator
listComments = regularUser, powerUser, moderator, administrator
addComments = regularUser, powerUser, moderator, administrator

View file

@ -46,6 +46,7 @@ App.Auth = function(_, jQuery, util, api, appState, promise) {
deleteOwnComments: 'deleteOwnComments',
deleteAllComments: 'deleteAllComments',
deleteTags: 'deleteTags',
mergeTags: 'mergeTags',
listTags: 'listTags',
massTag: 'massTag',

View file

@ -37,6 +37,7 @@ App.Presenters.TagPresenter = function(
privileges.canBan = auth.hasPrivilege(auth.privileges.banTags);
privileges.canViewHistory = auth.hasPrivilege(auth.privileges.viewHistory);
privileges.canDelete = auth.hasPrivilege(auth.privileges.deleteTags);
privileges.canMerge = auth.hasPrivilege(auth.privileges.mergeTags);
promise.wait(
util.promiseTemplate('tag'),
@ -92,6 +93,7 @@ App.Presenters.TagPresenter = function(
$el.find('form').submit(function(e) { e.preventDefault(); });
$el.find('form button[name=update]').click(updateButtonClicked);
$el.find('form button[name=delete]').click(deleteButtonClicked);
$el.find('form button[name=merge]').click(mergeButtonClicked);
implicationsTagInput = App.Controls.TagInput($el.find('[name=implications]'));
suggestionsTagInput = App.Controls.TagInput($el.find('[name=suggestions]'));
}
@ -141,6 +143,17 @@ App.Presenters.TagPresenter = function(
});
}
function mergeButtonClicked(e) {
if (targetTag = window.prompt('What tag should this be merged to?')) {
promise.wait(api.put('/tags/' + tag.name + '/merge', {targetTag: targetTag}))
.then(function(response) {
router.navigate('#/tags');
}).fail(function(response) {
window.alert(response.json && response.json.error || 'An error occured.');
});
}
}
function renderPosts(posts) {
var $target = $el.find('.post-list ul');
_.each(posts, function(post) {

View file

@ -74,6 +74,9 @@
<% if (privileges.canDelete) { %>
<button type="submit" name="delete">Delete</button>
<% } %>
<% if (privileges.canMerge) { %>
<button type="submit" name="merge">Merge with&hellip;</button>
<% } %>
</div>
</div>
<% } %>

View file

@ -37,6 +37,7 @@ final class TagController extends AbstractController
$router->get('/api/tags/:tagName', [$this, 'getTag']);
$router->get('/api/tags/:tagName/siblings', [$this, 'getTagSiblings']);
$router->put('/api/tags/:tagName', [$this, 'updateTag']);
$router->put('/api/tags/:tagName/merge', [$this, 'mergeTag']);
$router->delete('/api/tags/:tagName', [$this, 'deleteTag']);
}
@ -105,6 +106,15 @@ final class TagController extends AbstractController
return $this->tagService->deleteTag($tag);
}
public function mergeTag($tagName)
{
$targetTagName = $this->inputReader->targetTag;
$sourceTag = $this->tagService->getByName($tagName);
$targetTag = $this->tagService->getByName($targetTagName);
$this->privilegeService->assertPrivilege(Privilege::MERGE_TAGS);
return $this->tagService->mergeTag($sourceTag, $targetTag);
}
private function getFullFetchConfig()
{
return

View file

@ -47,6 +47,7 @@ class Privilege
const CHANGE_TAG_SUGGESTIONS = 'changeTagSuggestions';
const BAN_TAGS = 'banTags';
const DELETE_TAGS = 'deleteTags';
const MERGE_TAGS = 'mergeTags';
const LIST_COMMENTS = 'listComments';
const ADD_COMMENTS = 'addComments';

View file

@ -186,6 +186,29 @@ class TagService
$this->transactionManager->commit($transactionFunc);
}
public function mergeTag(Tag $sourceTag, Tag $targetTag)
{
$transactionFunc = function() use ($sourceTag, $targetTag)
{
$posts = $this->postDao->findByTagName($sourceTag->getName());
foreach ($posts as $post)
{
$newTags = $post->getTags();
$newTags = array_filter($newTags, function(Tag $tag) use ($sourceTag) {
return $tag->getId() !== $sourceTag->getId();
});
$newTags []= $targetTag;
$post->setTags($newTags);
$this->postDao->save($post);
$this->postHistoryService->savePostChange($post);
}
$this->tagHistoryService->saveTagDeletion($sourceTag);
$this->tagDao->deleteById($sourceTag->getId());
};
$this->transactionManager->commit($transactionFunc);
}
private function updateTagName(Tag $tag, $newName)
{
$otherTag = $this->tagDao->findByName($newName);

View file

@ -4,6 +4,7 @@ use Szurubooru\Config;
use Szurubooru\Dao\PublicFileDao;
use Szurubooru\DatabaseConnection;
use Szurubooru\Entities\Post;
use Szurubooru\Entities\Tag;
use Szurubooru\Entities\User;
use Szurubooru\Injector;
use Szurubooru\Tests\AbstractTestCase;
@ -35,6 +36,14 @@ abstract class AbstractDatabaseTestCase extends AbstractTestCase
$this->databaseConnection->close();
}
protected static function getTestTag($name = 'test')
{
$tag = new Tag();
$tag->setName($name);
$tag->setCreationTime(date('c'));
return $tag;
}
protected static function getTestPost()
{
$post = new Post();

View file

@ -13,9 +13,9 @@ final class TagDaoFilterTest extends AbstractDatabaseTestCase
{
public function testCategories()
{
$tag1 = $this->getTestTag('test 1');
$tag2 = $this->getTestTag('test 2');
$tag3 = $this->getTestTag('test 3');
$tag1 = self::getTestTag('test 1');
$tag2 = self::getTestTag('test 2');
$tag3 = self::getTestTag('test 3');
$tag2->setCategory('misc');
$tag3->setCategory('other');
$tagDao = $this->getTagDao();
@ -36,9 +36,9 @@ final class TagDaoFilterTest extends AbstractDatabaseTestCase
public function testCompositeCategories()
{
$tag1 = $this->getTestTag('test 1');
$tag2 = $this->getTestTag('test 2');
$tag3 = $this->getTestTag('test 3');
$tag1 = self::getTestTag('test 1');
$tag2 = self::getTestTag('test 2');
$tag3 = self::getTestTag('test 3');
$tag2->setCategory('misc');
$tag3->setCategory('other');
$tagDao = $this->getTagDao();
@ -61,12 +61,4 @@ final class TagDaoFilterTest extends AbstractDatabaseTestCase
{
return new TagDao($this->databaseConnection);
}
private function getTestTag($name)
{
$tag = new Tag();
$tag->setName($name);
$tag->setCreationTime(date('c'));
return $tag;
}
}

View file

@ -13,7 +13,7 @@ final class TagDaoTest extends AbstractDatabaseTestCase
public function testSaving()
{
$tag = $this->getTestTag('test');
$tag = self::getTestTag('test');
$tag->setCreationTime(date('c', mktime(0, 0, 0, 10, 1, 2014)));
$this->assertFalse($tag->isBanned());
$tag->setBanned(true);
@ -26,17 +26,17 @@ final class TagDaoTest extends AbstractDatabaseTestCase
public function testSavingRelations()
{
$tag1 = $this->getTestTag('test 1');
$tag2 = $this->getTestTag('test 2');
$tag3 = $this->getTestTag('test 3');
$tag4 = $this->getTestTag('test 4');
$tag1 = self::getTestTag('test 1');
$tag2 = self::getTestTag('test 2');
$tag3 = self::getTestTag('test 3');
$tag4 = self::getTestTag('test 4');
$tagDao = $this->getTagDao();
$tagDao->save($tag1);
$tagDao->save($tag2);
$tagDao->save($tag3);
$tagDao->save($tag4);
$tag = $this->getTestTag('test');
$tag = self::getTestTag('test');
$tag->setImpliedTags([$tag1, $tag3]);
$tag->setSuggestedTags([$tag2, $tag4]);
@ -84,13 +84,4 @@ final class TagDaoTest extends AbstractDatabaseTestCase
{
return new TagDao($this->databaseConnection);
}
private function getTestTag($name)
{
$tag = new Tag();
$tag->setName($name);
$tag->setCreationTime(date('c'));
$tag->setCategory('default');
return $tag;
}
}

View file

@ -1,6 +1,9 @@
<?php
namespace Szurubooru\Tests\Services;
use Szurubooru\Dao\PostDao;
use Szurubooru\Dao\PublicFileDao;
use Szurubooru\Dao\TagDao;
use Szurubooru\Entities\Post;
use Szurubooru\Entities\Tag;
use Szurubooru\Injector;
use Szurubooru\Services\TagService;
@ -116,6 +119,43 @@ final class TagServiceTest extends AbstractDatabaseTestCase
$this->assertEquals('[{"name":"test","usages":0,"banned":false}]', $fileDao->load('tags.json'));
}
public function testMerging()
{
$tag1 = self::getTestTag('test 1');
$tag2 = self::getTestTag('test 2');
$tag3 = self::getTestTag('test 3');
$tagDao = Injector::get(TagDao::class);
$tagDao->save($tag1);
$tagDao->save($tag2);
$tagDao->save($tag3);
$post1 = self::getTestPost();
$post2 = self::getTestPost();
$post3 = self::getTestPost();
$post1->setTags([$tag1]);
$post2->setTags([$tag1, $tag3]);
$post3->setTags([$tag2, $tag3]);
$postDao = Injector::get(PostDao::class);
$postDao->save($post1);
$postDao->save($post2);
$postDao->save($post3);
$tagService = $this->getTagService();
$tagService->mergeTag($tag1, $tag2);
$this->assertNull($tagDao->findByName($tag1->getName()));
$this->assertNotNull($tagDao->findByName($tag2->getName()));
$post1 = $postDao->findById($post1->getId());
$post2 = $postDao->findById($post2->getId());
$post3 = $postDao->findById($post3->getId());
$this->assertEntitiesEqual([$tag2], array_values($post1->getTags()));
$this->assertEntitiesEqual([$tag2, $tag3], array_values($post2->getTags()));
$this->assertEntitiesEqual([$tag2, $tag3], array_values($post3->getTags()));
}
public function testExportMultiple()
{
$tag1 = new Tag();