diff --git a/TODO b/TODO index 9a36ba57..2590c2db 100644 --- a/TODO +++ b/TODO @@ -26,7 +26,6 @@ everything related to tags: - basic tags - merging - tag editing - - name - category (from config.ini) - description - related tags diff --git a/data/config.ini b/data/config.ini index be888d38..62016ba5 100644 --- a/data/config.ini +++ b/data/config.ini @@ -57,6 +57,7 @@ changePostFlags = regularUser, powerUser, moderator, administrator listTags = anonymous, regularUser, powerUser, moderator, administrator massTag = powerUser, moderator, administrator +changeTagName = moderator, administrator listComments = anonymous, regularUser, powerUser, moderator, administrator addComments = regularUser, powerUser, moderator, administrator diff --git a/public_html/css/tags.css b/public_html/css/tags.css index 3906c0b1..f7fdd5a0 100644 --- a/public_html/css/tags.css +++ b/public_html/css/tags.css @@ -17,3 +17,9 @@ #tag-view h3 { margin-bottom: 0.5em; } + +#tag-view form { + text-align: left; + max-width: 20em; + margin: 0 auto; +} diff --git a/public_html/js/Auth.js b/public_html/js/Auth.js index 152de6b8..04535268 100644 --- a/public_html/js/Auth.js +++ b/public_html/js/Auth.js @@ -43,6 +43,7 @@ App.Auth = function(_, jQuery, util, api, appState, promise) { listTags: 'listTags', massTag: 'massTag', + changeTagName: 'changeTagName', viewHistory: 'viewHistory', }; diff --git a/public_html/js/Presenters/TagPresenter.js b/public_html/js/Presenters/TagPresenter.js index 6c6a2eaa..a5a19cef 100644 --- a/public_html/js/Presenters/TagPresenter.js +++ b/public_html/js/Presenters/TagPresenter.js @@ -6,19 +6,28 @@ App.Presenters.TagPresenter = function( jQuery, util, promise, + auth, api, + router, keyboard, - topNavigationPresenter) { + topNavigationPresenter, + messagePresenter) { var $el = jQuery('#content'); + var $messages = $el; var templates = {}; + var tag; var tagName; + var privileges = {}; + function init(params, loaded) { topNavigationPresenter.select('tags'); topNavigationPresenter.changeTitle('Tags'); + privileges.canChangeName = auth.hasPrivilege(auth.privileges.changeTagName); + promise.wait( util.promiseTemplate('tag'), util.promiseTemplate('post-list-item')) @@ -33,22 +42,50 @@ App.Presenters.TagPresenter = function( function reinit(params, loaded) { tagName = params.tagName; - render(); - loaded(); + messagePresenter.hideMessages($messages); - promise.wait(api.get('posts', {query: tagName})) - .then(function(response) { - var posts = response.json.data; + promise.wait( + api.get('tags/' + tagName), + api.get('posts', {query: tagName})) + .then(function(tagResponse, postsResponse) { + tag = tagResponse.json; + var posts = postsResponse.json.data; posts = posts.slice(0, 8); + + render(); + loaded(); + renderPosts(posts); - }).fail(function(response) { - console.log(new Error(response)); + }).fail(function(tagResponse, postsResponse) { + messagePresenter.showError($messages, tagResponse.json.error || postsResponse.json.error); + loaded(); }); } function render() { - $el.html(templates.tag({tagName: tagName})); + $el.html(templates.tag({privileges: privileges, tag: tag, tagName: tagName})); $el.find('.post-list').hide(); + $el.find('form').submit(editFormSubmitted); + } + + function editFormSubmitted(e) { + e.preventDefault(); + var $form = $el.find('form'); + var formData = {}; + + if (privileges.canChangeName) { + formData.name = $form.find('[name=name]').val(); + } + + promise.wait(api.put('/tags/' + tag.name, formData)) + .then(function(response) { + tag = response.json; + render(); + router.navigate('#/tag/' + tag.name); + }).fail(function(response) { + console.log(response); + window.alert('An error occurred'); + }); } function renderPosts(posts) { @@ -76,4 +113,4 @@ App.Presenters.TagPresenter = function( }; -App.DI.register('tagPresenter', ['_', 'jQuery', 'util', 'promise', 'api', 'keyboard', 'topNavigationPresenter'], App.Presenters.TagPresenter); +App.DI.register('tagPresenter', ['_', 'jQuery', 'util', 'promise', 'auth', 'api', 'router', 'keyboard', 'topNavigationPresenter', 'messagePresenter'], App.Presenters.TagPresenter); diff --git a/public_html/templates/tag.tpl b/public_html/templates/tag.tpl index 2440825b..48ccc569 100644 --- a/public_html/templates/tag.tpl +++ b/public_html/templates/tag.tpl @@ -3,6 +3,26 @@

<%= tagName %>

+ <% if (_.any(privileges)) { %> +
+ <% if (privileges.canChangeName) { %> +
+ +
+ +
+
+ <% } %> + +
+ +
+ +
+
+
+ <% } %> +

Example usages

diff --git a/src/Controllers/TagController.php b/src/Controllers/TagController.php index bb3b30d2..6c8df736 100644 --- a/src/Controllers/TagController.php +++ b/src/Controllers/TagController.php @@ -1,6 +1,7 @@ get('/api/tags', [$this, 'getTags']); + $router->get('/api/tags/:tagName', [$this, 'getTag']); + $router->put('/api/tags/:tagName', [$this, 'updateTag']); + } + + public function getTag($tagName) + { + $this->privilegeService->assertPrivilege(Privilege::LIST_TAGS); + + $tag = $this->tagService->getByName($tagName); + return $this->tagViewProxy->fromEntity($tag); } public function getTags() @@ -49,4 +60,16 @@ final class TagController extends AbstractController 'pageSize' => $result->getPageSize(), 'totalRecords' => $result->getTotalRecords()]; } + + public function updateTag($tagName) + { + $tag = $this->tagService->getByName($tagName); + $formData = new TagEditFormData($this->inputReader); + + if ($formData->name !== null) + $this->privilegeService->assertPrivilege(Privilege::CHANGE_TAG_NAME); + + $tag = $this->tagService->updateTag($tag, $formData); + return $this->tagViewProxy->fromEntity($tag); + } } diff --git a/src/Dao/AbstractDao.php b/src/Dao/AbstractDao.php index be87b67b..3ea71b66 100644 --- a/src/Dao/AbstractDao.php +++ b/src/Dao/AbstractDao.php @@ -242,12 +242,15 @@ abstract class AbstractDao implements ICrudDao, IBatchDao $query->where($sql, ...$bindings); } - protected function arrayToEntities(array $arrayEntities) + protected function arrayToEntities(array $arrayEntities, $entityConverter = null) { + if ($entityConverter === null) + $entityConverter = $this->entityConverter; + $entities = []; foreach ($arrayEntities as $arrayEntity) { - $entity = $this->entityConverter->toEntity($arrayEntity); + $entity = $entityConverter->toEntity($arrayEntity); $entities[$entity->getId()] = $entity; } return $entities; diff --git a/src/Dao/PostDao.php b/src/Dao/PostDao.php index 42254a83..4d6dbfb5 100644 --- a/src/Dao/PostDao.php +++ b/src/Dao/PostDao.php @@ -52,6 +52,17 @@ class PostDao extends AbstractDao implements ICrudDao return $this->findOneBy('name', $name); } + public function findByTagName($tagName) + { + $query = $this->fpdo->from('posts') + ->disableSmartJoin() + ->innerJoin('postTags ON postTags.postId = posts.id') + ->innerJoin('tags ON postTags.tagId = tags.id') + ->where('tags.name', $tagName); + $arrayEntities = iterator_to_array($query); + return $this->arrayToEntities($arrayEntities); + } + public function findByContentChecksum($checksum) { return $this->findOneBy('contentChecksum', $checksum); diff --git a/src/Dao/TagDao.php b/src/Dao/TagDao.php index e69324d3..13bece48 100644 --- a/src/Dao/TagDao.php +++ b/src/Dao/TagDao.php @@ -1,5 +1,6 @@ findOneBy('name', $tagName); + } + public function findByNames($tagNames) { return $this->findBy('name', $tagNames); diff --git a/src/FormData/TagEditFormData.php b/src/FormData/TagEditFormData.php new file mode 100644 index 00000000..fb5cbd91 --- /dev/null +++ b/src/FormData/TagEditFormData.php @@ -0,0 +1,24 @@ +name = $inputReader->name; + } + } + + public function validate(Validator $validator) + { + if ($this->name !== null) + $validator->validatePostTags([$this->name]); + } +} + diff --git a/src/Privilege.php b/src/Privilege.php index 1d8308cd..4a76505b 100644 --- a/src/Privilege.php +++ b/src/Privilege.php @@ -36,6 +36,7 @@ class Privilege const LIST_TAGS = 'listTags'; const MASS_TAG = 'massTag'; + const CHANGE_TAG_NAME = 'changeTagName'; const LIST_COMMENTS = 'listComments'; const ADD_COMMENTS = 'addComments'; diff --git a/src/Services/TagService.php b/src/Services/TagService.php index 72009443..aa53e357 100644 --- a/src/Services/TagService.php +++ b/src/Services/TagService.php @@ -1,31 +1,55 @@ validator = $validator; $this->transactionManager = $transactionManager; + $this->postDao = $postDao; $this->tagDao = $tagDao; $this->fileDao = $fileDao; + $this->historyService = $historyService; $this->timeService = $timeService; } + public function getByName($tagName) + { + $transactionFunc = function() use ($tagName) + { + $tag = $this->tagDao->findByName($tagName); + if (!$tag) + throw new \InvalidArgumentException('Tag with name "' . $tagName . '" was not found.'); + return $tag; + }; + return $this->transactionManager->rollback($transactionFunc); + } + public function getFiltered(TagFilter $filter) { $transactionFunc = function() use ($filter) @@ -96,4 +120,34 @@ class TagService }; return $this->transactionManager->commit($transactionFunc); } + + public function updateTag(Tag $tag, TagEditFormData $formData) + { + $transactionFunc = function() use ($tag, $formData) + { + $this->validator->validate($formData); + + if ($formData->name !== null) + $this->updateTagName($tag, $formData->name); + + return $this->tagDao->save($tag); + }; + $ret = $this->transactionManager->commit($transactionFunc); + + $transactionFunc = function() use ($tag) + { + $posts = $this->postDao->findByTagName($tag->getName()); + foreach ($posts as $post) + $this->historyService->saveSnapshot($this->historyService->getPostChangeSnapshot($post)); + }; + $this->transactionManager->commit($transactionFunc); + + $this->exportJson(); + return $ret; + } + + private function updateTagName(Tag $tag, $newName) + { + $tag->setName($newName); + } }