From 72ae13706bc2f039b231ef49aa4c58bfee74b78d Mon Sep 17 00:00:00 2001 From: Robbie Mackay Date: Mon, 22 May 2017 21:43:14 +1200 Subject: [PATCH] Turn posts_tags into a full post value table, and avoid duplicating info - Add extra field to posts_tags - Remove tags from post_varchar - Add Post/Tags validator - Add Post/Tags repo --- application/classes/Ushahidi/Core.php | 7 ++ .../classes/Ushahidi/Repository/Post/Tags.php | 60 ++++++++++ .../Validator/Form/Attribute/Update.php | 2 +- .../classes/Ushahidi/Validator/Post/Tags.php | 33 ++++++ ...54_convert_form_tags_to_form_attribute.php | 2 +- ...1_move_post_tag_values_to_post_varchar.php | 111 +++++++----------- ...22004400_remove_tags_from_post_varchar.php | 21 ++++ ...409_join_posts_tags_table_to_attribute.php | 86 ++++++++++++++ tests/datasets/ushahidi/Base.yml | 15 +++ 9 files changed, 268 insertions(+), 69 deletions(-) create mode 100644 application/classes/Ushahidi/Repository/Post/Tags.php create mode 100644 application/classes/Ushahidi/Validator/Post/Tags.php create mode 100644 migrations/20170522004400_remove_tags_from_post_varchar.php create mode 100644 migrations/20170522004409_join_posts_tags_table_to_attribute.php diff --git a/application/classes/Ushahidi/Core.php b/application/classes/Ushahidi/Core.php index 9d609248c6..0c0b2c2b3b 100644 --- a/application/classes/Ushahidi/Core.php +++ b/application/classes/Ushahidi/Core.php @@ -465,6 +465,7 @@ public static function init() $di->set('repository.post.markdown', $di->lazyNew('Ushahidi_Repository_Post_Markdown')); $di->set('repository.post.title', $di->lazyNew('Ushahidi_Repository_Post_Title')); $di->set('repository.post.media', $di->lazyNew('Ushahidi_Repository_Post_Media')); + $di->set('repository.post.tags', $di->lazyNew('Ushahidi_Repository_Post_Tags')); // The post value repo factory $di->set('repository.post_value_factory', $di->lazyNew('Ushahidi_Repository_Post_ValueFactory')); @@ -483,6 +484,7 @@ public static function init() 'markdown' => $di->lazyGet('repository.post.markdown'), 'title' => $di->lazyGet('repository.post.title'), 'media' => $di->lazyGet('repository.post.media'), + 'tags' => $di->lazyGet('repository.post.tags'), ], ]; @@ -621,6 +623,10 @@ public static function init() $di->params['Ushahidi_Validator_Post_Media'] = [ 'media_repo' => $di->lazyGet('repository.media') ]; + $di->set('validator.post.tags', $di->lazyNew('Ushahidi_Validator_Post_Tags')); + $di->params['Ushahidi_Validator_Post_Tags'] = [ + 'media_repo' => $di->lazyGet('repository.tags') + ]; $di->set('validator.post.value_factory', $di->lazyNew('Ushahidi_Validator_Post_ValueFactory')); @@ -639,6 +645,7 @@ public static function init() 'title' => $di->lazyGet('validator.post.title'), 'media' => $di->lazyGet('validator.post.media'), 'video' => $di->lazyGet('validator.post.video'), + 'tags' => $di->lazyGet('validator.post.tags'), ], ]; diff --git a/application/classes/Ushahidi/Repository/Post/Tags.php b/application/classes/Ushahidi/Repository/Post/Tags.php new file mode 100644 index 0000000000..00738cc60a --- /dev/null +++ b/application/classes/Ushahidi/Repository/Post/Tags.php @@ -0,0 +1,60 @@ + + * @package Ushahidi\Application + * @copyright 2014 Ushahidi + * @license https://www.gnu.org/licenses/agpl-3.0.html GNU Affero General Public License Version 3 (AGPL3) + */ + +class Ushahidi_Repository_Post_Tags extends Ushahidi_Repository_Post_Value +{ + // Ushahidi_Repository + protected function getTable() + { + return 'posts_tags'; + } + + // Override selectQuery to fetch attribute 'key' too + protected function selectQuery(Array $where = []) + { + $query = parent::selectQuery($where); + + // Select 'tag_id' as value too + $query->select( + ['posts_tags.tag_id', 'value'] + ); + + return $query; + } + + // PostValueRepository + public function getValueQuery($form_attribute_id, $match) + { + return $this->selectQuery(compact('form_attribute_id')) + ->where('tag_id', 'LIKE', "%$match%"); + } + + // UpdatePostValueRepository + public function createValue($value, $form_attribute_id, $post_id) + { + $tag_id = $value; + $input = compact('tag_id', 'form_attribute_id', 'post_id'); + $input['created'] = time(); + + return $this->executeInsert($input); + } + + // UpdatePostValueRepository + public function updateValue($id, $value) + { + $update = ['tag_id' => $value]; + if ($id && $update) + { + $this->executeUpdate(compact('id'), $update); + } + } + +} diff --git a/application/classes/Ushahidi/Validator/Form/Attribute/Update.php b/application/classes/Ushahidi/Validator/Form/Attribute/Update.php index f1352fa412..5664b68913 100644 --- a/application/classes/Ushahidi/Validator/Form/Attribute/Update.php +++ b/application/classes/Ushahidi/Validator/Form/Attribute/Update.php @@ -114,7 +114,7 @@ public function formStageBelongsToForm($value) public function canMakePrivate($value, $input) { // If input type is tags, then attribute cannot be private - if ($input === 'tags' && $value !== false) { + if ($type === 'tags' && $value !== false) { return false; } diff --git a/application/classes/Ushahidi/Validator/Post/Tags.php b/application/classes/Ushahidi/Validator/Post/Tags.php new file mode 100644 index 0000000000..45e3371d16 --- /dev/null +++ b/application/classes/Ushahidi/Validator/Post/Tags.php @@ -0,0 +1,33 @@ + + * @package Ushahidi\Application + * @copyright 2014 Ushahidi + * @license https://www.gnu.org/licenses/agpl-3.0.html GNU Affero General Public License Version 3 (AGPL3) + */ + +use Ushahidi\Core\Entity\TagsRepository; + +class Ushahidi_Validator_Post_Tags extends Ushahidi_Validator_Post_ValueValidator +{ + protected $media_repo; + + public function __construct(TagsRepository $tags_repo) + { + $this->repo = $tags_repo; + } + + protected function validate($value) + { + if (!Valid::digit($value)) { + return 'digit'; + } + + if (! $this->repo->exists($value)) { + return 'exists'; + } + } +} diff --git a/migrations/20170412191954_convert_form_tags_to_form_attribute.php b/migrations/20170412191954_convert_form_tags_to_form_attribute.php index af0c62fa33..76e0436091 100644 --- a/migrations/20170412191954_convert_form_tags_to_form_attribute.php +++ b/migrations/20170412191954_convert_form_tags_to_form_attribute.php @@ -54,6 +54,6 @@ public function up() */ public function down() { - $this->execute("DELETE from form_attributes where input = 'tags'"); + $this->execute("DELETE from form_attributes where type = 'varchar' AND input = 'tags'"); } } diff --git a/migrations/20170417090621_move_post_tag_values_to_post_varchar.php b/migrations/20170417090621_move_post_tag_values_to_post_varchar.php index fed076d255..1c807b1474 100644 --- a/migrations/20170417090621_move_post_tag_values_to_post_varchar.php +++ b/migrations/20170417090621_move_post_tag_values_to_post_varchar.php @@ -9,42 +9,47 @@ class MovePostTagValuesToPostVarchar extends AbstractMigration */ public function up() { - $pdo = $this->getAdapter()->getConnection(); - // fetching posts with form_attribute_id - $posts = $this->fetchAll( - "SELECT posts.id, posts.form_id, form_attributes.id as form_attribute_id - FROM posts - INNER JOIN form_stages - ON form_stages.form_id = posts.form_id - INNER JOIN form_attributes - ON form_attributes.form_stage_id = form_stages.id" - ); - $insert = $pdo->prepare( - "INSERT into - post_varchar - (`post_id`, `form_attribute_id`, `value`, `created`) - VALUES(:post_id, :form_attribute_id, :value, :created)" - ); - foreach ($posts as $post) { - $post_tags = $pdo->prepare( - "SELECT tag_id - FROM posts_tags - WHERE post_id = :post_id" - ); - $post_tags->execute([':post_id' => $post['id']]); - // inserting post_ids and tag_ids(value) in post_varchar - $tags = $post_tags->fetchAll(); - foreach ($tags as $tag) { - $insert->execute( - [ - ':post_id' => $post['id'], - ':form_attribute_id' => $post['form_attribute_id'], - ':value' => $tag['tag_id'], - ':created' => time() - ] - ); - } - } + // Don't do this if we haven't already! + // We have a better way. + + //$pdo = $this->getAdapter()->getConnection(); + // fetching posts with form_attribute_id + // $posts = $this->fetchAll( + // "SELECT posts.id, posts.form_id, form_attributes.id as form_attribute_id + // FROM posts + // INNER JOIN form_stages + // ON form_stages.form_id = posts.form_id + // INNER JOIN form_attributes + // ON form_attributes.form_stage_id = form_stages.id + // AND form_attributes.input = 'tags' + // AND form_attributes.type = 'varchar'" + // ); + // $insert = $pdo->prepare( + // "INSERT into + // post_varchar + // (`post_id`, `form_attribute_id`, `value`, `created`) + // VALUES(:post_id, :form_attribute_id, :value, :created)" + // ); + // foreach ($posts as $post) { + // $post_tags = $pdo->prepare( + // "SELECT tag_id + // FROM posts_tags + // WHERE post_id = :post_id" + // ); + // $post_tags->execute([':post_id' => $post['id']]); + // // inserting post_ids and tag_ids(value) in post_varchar + // $tags = $post_tags->fetchAll(); + // foreach ($tags as $tag) { + // $insert->execute( + // [ + // ':post_id' => $post['id'], + // ':form_attribute_id' => $post['form_attribute_id'], + // ':value' => $tag['tag_id'], + // ':created' => time() + // ] + // ); + // } + // } } /** @@ -52,38 +57,10 @@ public function up() */ public function down() { - $pdo = $this->getAdapter()->getConnection(); - $posts = $this->fetchAll( - "SELECT posts.id, posts.form_id, form_attributes.id as form_attribute_id - FROM posts - INNER JOIN form_stages - ON form_stages.form_id = posts.form_id - INNER JOIN form_attributes - ON form_attributes.form_stage_id = form_stages.id" - ); - $delete = $pdo->prepare( + $this->execute( "DELETE from post_varchar - WHERE post_id = :post_id AND form_attribute_id = :form_attribute_id AND value = :value" + WHERE form_attribute_id IN + (SELECT form_attributes.id FROM form_attributes WHERE input = 'tags' AND type = 'varchar')" ); - - foreach ($posts as $post) { - $post_tags = $pdo->prepare( - "SELECT tag_id - FROM posts_tags - WHERE post_id = :post_id" - ); - $post_tags->execute([':post_id' => $post['id']]); - // inserting post_ids and tag_ids(value) in post_varchar - $tags = $post_tags->fetchAll(); - foreach ($tags as $tag) { - $delete->execute( - [ - ':post_id' => $post['id'], - ':form_attribute_id' => $post['form_attribute_id'], - ':value' => $tag['tag_id'] - ] - ); - } - } } } diff --git a/migrations/20170522004400_remove_tags_from_post_varchar.php b/migrations/20170522004400_remove_tags_from_post_varchar.php new file mode 100644 index 0000000000..1c2d8b1719 --- /dev/null +++ b/migrations/20170522004400_remove_tags_from_post_varchar.php @@ -0,0 +1,21 @@ +execute( + "DELETE from post_varchar + WHERE form_attribute_id IN + (SELECT form_attributes.id FROM form_attributes WHERE input = 'tags' AND type = 'varchar')" + ); + } + + public function down() + { + // No op - not reversible + } +} diff --git a/migrations/20170522004409_join_posts_tags_table_to_attribute.php b/migrations/20170522004409_join_posts_tags_table_to_attribute.php new file mode 100644 index 0000000000..6cf847aaa3 --- /dev/null +++ b/migrations/20170522004409_join_posts_tags_table_to_attribute.php @@ -0,0 +1,86 @@ +getAdapter()->getConnection(); + + $this->table('posts_tags') + ->addColumn('id', 'integer', ['null' => false]) + ->addColumn('form_attribute_id', 'integer') + ->addColumn('created', 'integer', ['default' => 0]) + ->update(); + + // Manually fix up keys + $this->execute('ALTER TABLE posts_tags + DROP PRIMARY KEY, + ADD PRIMARY KEY (id), + ADD INDEX (post_id), + MODIFY COLUMN id INT AUTO_INCREMENT, + ADD UNIQUE INDEX unique_post_tag_attribute_ids (post_id, tag_id, form_attribute_id)'); + + // Make varchar/tags attributes into tags/tags attributes + $this->execute(" + UPDATE form_attributes + SET type = 'tags' + WHERE type = 'varchar' AND input = 'tags' + "); + + $attributes = $this->fetchAll(" + SELECT form_attributes.id, form_stages.form_id + FROM form_attributes + JOIN form_stages ON (form_attributes.form_stage_id = form_stages.id) + WHERE + form_attributes.type = 'tags' AND + form_stages.type = 'post' + "); + + // Set form_attribute_id for posts_tags entries + $insert = $pdo->prepare(' + UPDATE posts_tags JOIN posts ON (posts_tags.post_id = posts.id) + SET form_attribute_id = :attr_id WHERE posts.form_id = :form_id + '); + foreach ($attributes as $attribute) { + $insert->execute([ + ':attr_id' => $attribute['id'], + ':form_id' => $attribute['form_id'] + ]); + } + + // Add foreign key for form_attribute_id + $this->table('posts_tags') + ->addForeignKey('form_attribute_id', 'form_attributes', 'id', [ + 'delete' => 'CASCADE', + 'update' => 'CASCADE', + ]) + ->update(); + } + + public function down() + { + // Make tags/tags attributes into varchar/tags attributes + $this->execute(" + UPDATE form_attributes + SET type = 'varchar' + WHERE type = 'tags' AND input = 'tags' + "); + + // Restore keys/indexs + $this->execute('ALTER TABLE posts_tags + DROP PRIMARY KEY, + ADD PRIMARY KEY (post_id, tag_id), + MODIFY COLUMN id INT, + DROP INDEX unique_post_tag_attribute_ids'); + + // Remove columns + $this->table('posts_tags') + ->dropForeignKey('form_attribute_id') + ->removeColumn('id') + ->removeColumn('form_attribute_id') + ->removeColumn('created') + ->update(); + } +} diff --git a/tests/datasets/ushahidi/Base.yml b/tests/datasets/ushahidi/Base.yml index e72e69a85f..0d2f5a4d3a 100644 --- a/tests/datasets/ushahidi/Base.yml +++ b/tests/datasets/ushahidi/Base.yml @@ -441,6 +441,18 @@ form_attributes: options: "" cardinality: 1 form_stage_id: 1 + - + id: 26 + label: "Categories" + key: "tags" + type: "tags" + input: "tags" + response_private: 0 + required: 0 + priority: 3 + options: "" + cardinality: 0 + form_stage_id: 1 posts: - id: 1 @@ -950,12 +962,15 @@ posts_tags: - post_id: 1 tag_id: 4 + form_attribute_id: 26 - post_id: 1 tag_id: 3 + form_attribute_id: 26 - post_id: 99 tag_id: 3 + form_attribute_id: 26 form_stages_posts: - form_stage_id: 1