Skip to content

[6.0] Added possibility to batch remove a tag #3344

@jgerman-bot

Description

@jgerman-bot

New language relevant PR in upstream repo: joomla/joomla-cms#40613 Here are the upstream changes:

Click to expand the diff!
diff --git a/administrator/language/en-GB/lib_joomla.ini b/administrator/language/en-GB/lib_joomla.ini
index b940984bcd512..154b38a64382f 100644
--- a/administrator/language/en-GB/lib_joomla.ini
+++ b/administrator/language/en-GB/lib_joomla.ini
@@ -379,8 +379,11 @@ JLIB_HTML_BATCH_MOVE="Move"
 JLIB_HTML_BATCH_MOVE_QUESTION="Action to Perform"
 JLIB_HTML_BATCH_NO_CATEGORY="- Don't copy or move -"
 JLIB_HTML_BATCH_NOCHANGE="- Keep original Access Levels -"
-JLIB_HTML_BATCH_TAG_LABEL="Add Tag"
+JLIB_HTML_BATCH_TAG_ADD="Add"
+JLIB_HTML_BATCH_TAG_ADDREMOVE_QUESTION="Action to Perform"
+JLIB_HTML_BATCH_TAG_LABEL="Add or Remove Tag"
 JLIB_HTML_BATCH_TAG_NOCHANGE="- Keep original Tags -"
+JLIB_HTML_BATCH_TAG_REMOVE="Remove"
 JLIB_HTML_BATCH_USER_LABEL="Set User."
 JLIB_HTML_BATCH_USER_NOCHANGE="- Keep original User -"
 JLIB_HTML_BATCH_USER_NOUSER="No User."
diff --git a/build/media_source/layouts/js/joomla/html/batch/batch-tag-addremove.es6.js b/build/media_source/layouts/js/joomla/html/batch/batch-tag-addremove.es6.js
new file mode 100644
index 0000000000000..54fa047e18ac2
--- /dev/null
+++ b/build/media_source/layouts/js/joomla/html/batch/batch-tag-addremove.es6.js
@@ -0,0 +1,42 @@
+/**
+ * @copyright  (C) 2023 Open Source Matters, Inc. <https://www.joomla.org>
+ * @license    GNU General Public License version 2 or later; see LICENSE.txt
+ */
+
+(() => {
+  const onSelect = () => {
+    const batchTag = document.getElementById('batch-tag-id');
+    const batchTagAddRemove = document.getElementById('batch-tag-addremove');
+    let batchSelector;
+
+    const onChange = () => {
+      if (!batchSelector.value
+          || (batchSelector.value && parseInt(batchSelector.value, 10) === 0)) {
+        batchTagAddRemove.classList.add('hidden');
+      } else {
+        batchTagAddRemove.classList.remove('hidden');
+      }
+    };
+
+    if (batchTag) {
+      batchSelector = batchTag;
+    }
+
+    if (batchTagAddRemove) {
+      batchTagAddRemove.classList.add('hidden');
+    }
+
+    if (batchTagAddRemove) {
+      batchSelector.addEventListener('change', onChange);
+    }
+
+    // Cleanup
+    document.removeEventListener('DOMContentLoaded', onSelect, true);
+  };
+
+  // Document loaded
+  document.addEventListener('DOMContentLoaded', onSelect, true);
+
+  // Joomla updated
+  document.addEventListener('joomla:updated', onSelect, true);
+})();
diff --git a/build/media_source/system/joomla.asset.json b/build/media_source/system/joomla.asset.json
index 1eae307baedf8..71dd180f0384d 100644
--- a/build/media_source/system/joomla.asset.json
+++ b/build/media_source/system/joomla.asset.json
@@ -304,6 +304,14 @@
         "defer": true
       }
     },
+    {
+      "name": "joomla.batch-tag-addremove",
+      "type": "script",
+      "uri": "layouts/joomla/html/batch/batch-tag-addremove.min.js",
+      "attributes": {
+        "defer": true
+      }
+    },
     {
       "name": "webcomponent.field-fancy-select-legacy",
       "type": "script",
diff --git a/language/en-GB/lib_joomla.ini b/language/en-GB/lib_joomla.ini
index 867b99ac71159..0254984502a2f 100644
--- a/language/en-GB/lib_joomla.ini
+++ b/language/en-GB/lib_joomla.ini
@@ -375,8 +375,11 @@ JLIB_HTML_BATCH_MOVE="Move"
 JLIB_HTML_BATCH_MOVE_QUESTION="Action to Perform"
 JLIB_HTML_BATCH_NO_CATEGORY="- Don't copy or move -"
 JLIB_HTML_BATCH_NOCHANGE="- Keep original Access Levels -"
-JLIB_HTML_BATCH_TAG_LABEL="Add Tag"
+JLIB_HTML_BATCH_TAG_ADD="Add"
+JLIB_HTML_BATCH_TAG_ADDREMOVE_QUESTION="Action to Perform"
+JLIB_HTML_BATCH_TAG_LABEL="Add or Remove Tag"
 JLIB_HTML_BATCH_TAG_NOCHANGE="- Keep original Tags -"
+JLIB_HTML_BATCH_TAG_REMOVE="Remove"
 JLIB_HTML_BATCH_USER_LABEL="Set User."
 JLIB_HTML_BATCH_USER_NOCHANGE="- Keep original User -"
 JLIB_HTML_BATCH_USER_NOUSER="No User."
diff --git a/layouts/joomla/html/batch/tag.php b/layouts/joomla/html/batch/tag.php
index 4112bb222b7fd..ea122fdf50e00 100644
--- a/layouts/joomla/html/batch/tag.php
+++ b/layouts/joomla/html/batch/tag.php
@@ -10,14 +10,35 @@
 
 defined('_JEXEC') or die;
 
+use Joomla\CMS\Factory;
 use Joomla\CMS\HTML\HTMLHelper;
 use Joomla\CMS\Language\Text;
 
+// Create the add/remove tag options.
+$options = [
+    HTMLHelper::_('select.option', 'a', Text::_('JLIB_HTML_BATCH_TAG_ADD')),
+    HTMLHelper::_('select.option', 'r', Text::_('JLIB_HTML_BATCH_TAG_REMOVE'))
+];
+
+/** @var Joomla\CMS\WebAsset\WebAssetManager $wa */
+$wa = Factory::getApplication()->getDocument()->getWebAssetManager();
+$wa->useScript('joomla.batch-tag-addremove');
+
 ?>
-<label id="batch-tag-lbl" for="batch-tag-id">
+<label id="batch-tag-choose-action-lbl" for="batch-tag-id">
     <?php echo Text::_('JLIB_HTML_BATCH_TAG_LABEL'); ?>
 </label>
-<select name="batch[tag]" class="form-select" id="batch-tag-id">
-    <option value=""><?php echo Text::_('JLIB_HTML_BATCH_TAG_NOCHANGE'); ?></option>
-    <?php echo HTMLHelper::_('select.options', HTMLHelper::_('tag.tags', ['filter.published' => [1]]), 'value', 'text'); ?>
-</select>
+<div id="batch-tag-choose-action" class="control-group">
+    <select name="batch[tag]" class="form-select" id="batch-tag-id">
+        <option value=""><?php echo Text::_('JLIB_HTML_BATCH_TAG_NOCHANGE'); ?></option>
+        <?php echo HTMLHelper::_('select.options', HTMLHelper::_('tag.tags', ['filter.published' => [1]]), 'value', 'text'); ?>
+    </select>
+</div>
+<div id="batch-tag-addremove" class="control-group radio">
+    <fieldset id="batch-tag-addremove-id">
+        <legend>
+            <?php echo Text::_('JLIB_HTML_BATCH_TAG_ADDREMOVE_QUESTION'); ?>
+        </legend>
+        <?php echo HTMLHelper::_('select.radiolist', $options, 'batch[tag_addremove]', '', 'value', 'text', 'a'); ?>
+    </fieldset>
+</div>
diff --git a/libraries/src/Helper/TagsHelper.php b/libraries/src/Helper/TagsHelper.php
index d91f504701035..313c2e22fc3a2 100644
--- a/libraries/src/Helper/TagsHelper.php
+++ b/libraries/src/Helper/TagsHelper.php
@@ -821,8 +821,29 @@ public static function getTypes($arrayType = 'objectList', $selectTypes = null,
      * @return  boolean
      *
      * @since   3.1
+     *
+     * @deprecated  6.0 will be removed in 7.0
+     *              Please use postStore
      */
     public function postStoreProcess(TableInterface $table, $newTags = [], $replace = true)
+    {
+        $this->postStore($table, $newTags, $replace);
+    }
+
+    /**
+     * Function that handles saving tags used in a table class after a store().
+     *
+     * @param   TableInterface  $table    Table being processed.
+     * @param   array           $newTags  Array of new tags.
+     * @param   boolean         $replace  Flag indicating if all existing tags should be replaced.
+     *                                    This flag takes precedence before $remove.
+     * @param   boolean         $remove   Flag indicating if the tags in $newTags should be removed.
+     *
+     * @return  boolean
+     *
+     * @since   __DEPLOY_VERSION__
+     */
+    public function postStore(TableInterface $table, $newTags = [], $replace = true, $remove = false)
     {
         if (!empty($table->newTags) && empty($newTags)) {
             $newTags = $table->newTags;
@@ -856,7 +877,11 @@ public function postStoreProcess(TableInterface $table, $newTags = [], $replace
                 $ucmId     = $ucmContentTable->core_content_id;
 
                 // Store the tag data if the article data was saved and run related methods.
-                $result = $result && $this->tagItem($ucmId, $table, $newTags, $replace);
+                if ($remove) {
+                    $result = $result && $this->unTagItem($ucmId, $table, $newTags);
+                } else {
+                    $result = $result && $this->tagItem($ucmId, $table, $newTags, $replace);
+                }
             }
         }
 
diff --git a/libraries/src/MVC/Model/AdminModel.php b/libraries/src/MVC/Model/AdminModel.php
index 7a53808244b3e..9a1ae67ce7dad 100644
--- a/libraries/src/MVC/Model/AdminModel.php
+++ b/libraries/src/MVC/Model/AdminModel.php
@@ -325,7 +325,13 @@ public function batch($commands, $pks, $contexts)
 
         foreach ($this->batch_commands as $identifier => $command) {
             if (!empty($commands[$identifier])) {
-                if (!$this->$command($commands[$identifier], $pks, $contexts)) {
+                if ($command === 'batchTag') {
+                    $removeTags = ArrayHelper::getValue($commands, 'tag_addremove', 'a') === 'r';
+
+                    if (!$this->batchTags($commands[$identifier], $pks, $contexts, $removeTags)) {
+                        return false;
+                    }
+                } elseif (!$this->$command($commands[$identifier], $pks, $contexts)) {
                     return false;
                 }
 
@@ -693,8 +699,28 @@ protected function batchMove($value, $pks, $contexts)
      * @return  boolean  True if successful, false otherwise and internal error is set.
      *
      * @since   3.1
+     *
+     * @deprecated  6.0 will be removed in 7.0
+     *              Please use batchTags
      */
     protected function batchTag($value, $pks, $contexts)
+    {
+        return $this->batchTags($value, $pks, $contexts);
+    }
+
+    /**
+     * Batch tag a list of item.
+     *
+     * @param   integer  $value       The value of the new tag.
+     * @param   array    $pks         An array of row IDs.
+     * @param   array    $contexts    An array of item contexts.
+     * @param   boolean  $removeTags  Flag indicating whether the tags in $value have to be removed.
+     *
+     * @return  boolean  True if successful, false otherwise and internal error is set.
+     *
+     * @since   __DEPLOY_VERSION__
+     */
+    protected function batchTags($value, $pks, $contexts, $removeTags = false)
     {
         // Initialize re-usable member properties, and re-usable local variables
         $this->initBatch();
@@ -711,6 +737,7 @@ protected function batchTag($value, $pks, $contexts)
                         'subject'     => $this->table,
                         'newTags'     => $tags,
                         'replaceTags' => false,
+                        'removeTags'  => $removeTags,
                     ]
                 );
 
diff --git a/plugins/behaviour/taggable/src/Extension/Taggable.php b/plugins/behaviour/taggable/src/Extension/Taggable.php
index b721d8bd15dbe..b2f054dded700 100644
--- a/plugins/behaviour/taggable/src/Extension/Taggable.php
+++ b/plugins/behaviour/taggable/src/Extension/Taggable.php
@@ -171,7 +171,7 @@ public function onTableAfterStore(AfterStoreEvent $event)
         $newTags = $table->newTags ?? [];
 
         if (empty($newTags)) {
-            $result = $tagsHelper->postStoreProcess($table);
+            $result = $tagsHelper->postStore($table);
         } else {
             if (\is_string($newTags) && (strpos($newTags, ',') !== false)) {
                 $newTags = explode(',', $newTags);
@@ -179,7 +179,7 @@ public function onTableAfterStore(AfterStoreEvent $event)
                 $newTags = (array) $newTags;
             }
 
-            $result = $tagsHelper->postStoreProcess($table, $newTags);
+            $result = $tagsHelper->postStore($table, $newTags);
         }
     }
 
@@ -231,6 +231,7 @@ public function onTableSetNewTags(SetNewTagsEvent $event)
         $table       = $event['subject'];
         $newTags     = $event['newTags'];
         $replaceTags = $event['replaceTags'];
+        $removeTags  = $event['removeTags'];
 
         // If the tags table doesn't implement the interface bail
         if (!($table instanceof TaggableTableInterface)) {
@@ -247,7 +248,7 @@ public function onTableSetNewTags(SetNewTagsEvent $event)
         $tagsHelper            = $table->getTagsHelper();
         $tagsHelper->typeAlias = $table->getTypeAlias();
 
-        if (!$tagsHelper->postStoreProcess($table, $newTags, $replaceTags)) {
+        if (!$tagsHelper->postStore($table, $newTags, $replaceTags, $removeTags)) {
             throw new \RuntimeException($table->getError());
         }
     }

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions