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 changeTagSuggestions = moderator, administrator
banTags = moderator, administrator banTags = moderator, administrator
deleteTags = moderator, administrator deleteTags = moderator, administrator
mergeTags = moderator, administrator
listComments = regularUser, powerUser, moderator, administrator listComments = regularUser, powerUser, moderator, administrator
addComments = 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', deleteOwnComments: 'deleteOwnComments',
deleteAllComments: 'deleteAllComments', deleteAllComments: 'deleteAllComments',
deleteTags: 'deleteTags', deleteTags: 'deleteTags',
mergeTags: 'mergeTags',
listTags: 'listTags', listTags: 'listTags',
massTag: 'massTag', massTag: 'massTag',

View file

@ -37,6 +37,7 @@ App.Presenters.TagPresenter = function(
privileges.canBan = auth.hasPrivilege(auth.privileges.banTags); privileges.canBan = auth.hasPrivilege(auth.privileges.banTags);
privileges.canViewHistory = auth.hasPrivilege(auth.privileges.viewHistory); privileges.canViewHistory = auth.hasPrivilege(auth.privileges.viewHistory);
privileges.canDelete = auth.hasPrivilege(auth.privileges.deleteTags); privileges.canDelete = auth.hasPrivilege(auth.privileges.deleteTags);
privileges.canMerge = auth.hasPrivilege(auth.privileges.mergeTags);
promise.wait( promise.wait(
util.promiseTemplate('tag'), util.promiseTemplate('tag'),
@ -92,6 +93,7 @@ App.Presenters.TagPresenter = function(
$el.find('form').submit(function(e) { e.preventDefault(); }); $el.find('form').submit(function(e) { e.preventDefault(); });
$el.find('form button[name=update]').click(updateButtonClicked); $el.find('form button[name=update]').click(updateButtonClicked);
$el.find('form button[name=delete]').click(deleteButtonClicked); $el.find('form button[name=delete]').click(deleteButtonClicked);
$el.find('form button[name=merge]').click(mergeButtonClicked);
implicationsTagInput = App.Controls.TagInput($el.find('[name=implications]')); implicationsTagInput = App.Controls.TagInput($el.find('[name=implications]'));
suggestionsTagInput = App.Controls.TagInput($el.find('[name=suggestions]')); 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) { function renderPosts(posts) {
var $target = $el.find('.post-list ul'); var $target = $el.find('.post-list ul');
_.each(posts, function(post) { _.each(posts, function(post) {

View file

@ -74,6 +74,9 @@
<% if (privileges.canDelete) { %> <% if (privileges.canDelete) { %>
<button type="submit" name="delete">Delete</button> <button type="submit" name="delete">Delete</button>
<% } %> <% } %>
<% if (privileges.canMerge) { %>
<button type="submit" name="merge">Merge with&hellip;</button>
<% } %>
</div> </div>
</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', [$this, 'getTag']);
$router->get('/api/tags/:tagName/siblings', [$this, 'getTagSiblings']); $router->get('/api/tags/:tagName/siblings', [$this, 'getTagSiblings']);
$router->put('/api/tags/:tagName', [$this, 'updateTag']); $router->put('/api/tags/:tagName', [$this, 'updateTag']);
$router->put('/api/tags/:tagName/merge', [$this, 'mergeTag']);
$router->delete('/api/tags/:tagName', [$this, 'deleteTag']); $router->delete('/api/tags/:tagName', [$this, 'deleteTag']);
} }
@ -105,6 +106,15 @@ final class TagController extends AbstractController
return $this->tagService->deleteTag($tag); 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() private function getFullFetchConfig()
{ {
return return

View file

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

View file

@ -186,6 +186,29 @@ class TagService
$this->transactionManager->commit($transactionFunc); $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) private function updateTagName(Tag $tag, $newName)
{ {
$otherTag = $this->tagDao->findByName($newName); $otherTag = $this->tagDao->findByName($newName);

View file

@ -4,6 +4,7 @@ use Szurubooru\Config;
use Szurubooru\Dao\PublicFileDao; use Szurubooru\Dao\PublicFileDao;
use Szurubooru\DatabaseConnection; use Szurubooru\DatabaseConnection;
use Szurubooru\Entities\Post; use Szurubooru\Entities\Post;
use Szurubooru\Entities\Tag;
use Szurubooru\Entities\User; use Szurubooru\Entities\User;
use Szurubooru\Injector; use Szurubooru\Injector;
use Szurubooru\Tests\AbstractTestCase; use Szurubooru\Tests\AbstractTestCase;
@ -35,6 +36,14 @@ abstract class AbstractDatabaseTestCase extends AbstractTestCase
$this->databaseConnection->close(); $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() protected static function getTestPost()
{ {
$post = new Post(); $post = new Post();

View file

@ -13,9 +13,9 @@ final class TagDaoFilterTest extends AbstractDatabaseTestCase
{ {
public function testCategories() public function testCategories()
{ {
$tag1 = $this->getTestTag('test 1'); $tag1 = self::getTestTag('test 1');
$tag2 = $this->getTestTag('test 2'); $tag2 = self::getTestTag('test 2');
$tag3 = $this->getTestTag('test 3'); $tag3 = self::getTestTag('test 3');
$tag2->setCategory('misc'); $tag2->setCategory('misc');
$tag3->setCategory('other'); $tag3->setCategory('other');
$tagDao = $this->getTagDao(); $tagDao = $this->getTagDao();
@ -36,9 +36,9 @@ final class TagDaoFilterTest extends AbstractDatabaseTestCase
public function testCompositeCategories() public function testCompositeCategories()
{ {
$tag1 = $this->getTestTag('test 1'); $tag1 = self::getTestTag('test 1');
$tag2 = $this->getTestTag('test 2'); $tag2 = self::getTestTag('test 2');
$tag3 = $this->getTestTag('test 3'); $tag3 = self::getTestTag('test 3');
$tag2->setCategory('misc'); $tag2->setCategory('misc');
$tag3->setCategory('other'); $tag3->setCategory('other');
$tagDao = $this->getTagDao(); $tagDao = $this->getTagDao();
@ -61,12 +61,4 @@ final class TagDaoFilterTest extends AbstractDatabaseTestCase
{ {
return new TagDao($this->databaseConnection); 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() public function testSaving()
{ {
$tag = $this->getTestTag('test'); $tag = self::getTestTag('test');
$tag->setCreationTime(date('c', mktime(0, 0, 0, 10, 1, 2014))); $tag->setCreationTime(date('c', mktime(0, 0, 0, 10, 1, 2014)));
$this->assertFalse($tag->isBanned()); $this->assertFalse($tag->isBanned());
$tag->setBanned(true); $tag->setBanned(true);
@ -26,17 +26,17 @@ final class TagDaoTest extends AbstractDatabaseTestCase
public function testSavingRelations() public function testSavingRelations()
{ {
$tag1 = $this->getTestTag('test 1'); $tag1 = self::getTestTag('test 1');
$tag2 = $this->getTestTag('test 2'); $tag2 = self::getTestTag('test 2');
$tag3 = $this->getTestTag('test 3'); $tag3 = self::getTestTag('test 3');
$tag4 = $this->getTestTag('test 4'); $tag4 = self::getTestTag('test 4');
$tagDao = $this->getTagDao(); $tagDao = $this->getTagDao();
$tagDao->save($tag1); $tagDao->save($tag1);
$tagDao->save($tag2); $tagDao->save($tag2);
$tagDao->save($tag3); $tagDao->save($tag3);
$tagDao->save($tag4); $tagDao->save($tag4);
$tag = $this->getTestTag('test'); $tag = self::getTestTag('test');
$tag->setImpliedTags([$tag1, $tag3]); $tag->setImpliedTags([$tag1, $tag3]);
$tag->setSuggestedTags([$tag2, $tag4]); $tag->setSuggestedTags([$tag2, $tag4]);
@ -84,13 +84,4 @@ final class TagDaoTest extends AbstractDatabaseTestCase
{ {
return new TagDao($this->databaseConnection); 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 <?php
namespace Szurubooru\Tests\Services; namespace Szurubooru\Tests\Services;
use Szurubooru\Dao\PostDao;
use Szurubooru\Dao\PublicFileDao; use Szurubooru\Dao\PublicFileDao;
use Szurubooru\Dao\TagDao;
use Szurubooru\Entities\Post;
use Szurubooru\Entities\Tag; use Szurubooru\Entities\Tag;
use Szurubooru\Injector; use Szurubooru\Injector;
use Szurubooru\Services\TagService; 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')); $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() public function testExportMultiple()
{ {
$tag1 = new Tag(); $tag1 = new Tag();