diff --git a/administrator/components/com_banners/src/Model/TracksModel.php b/administrator/components/com_banners/src/Model/TracksModel.php index c2f60ce7e468c..ac3cc6b079d5b 100644 --- a/administrator/components/com_banners/src/Model/TracksModel.php +++ b/administrator/components/com_banners/src/Model/TracksModel.php @@ -441,9 +441,9 @@ public function getContent() . str_replace('"', '""', Text::_('JDATE')) . '"' . "\n"; foreach ($this->getItems() as $item) { - $this->content .= '"' . str_replace('"', '""', $item->banner_name) . '","' - . str_replace('"', '""', $item->client_name) . '","' - . str_replace('"', '""', $item->category_title) . '","' + $this->content .= '"' . str_replace('"', '""', $this->escapeCsvFormula($item->banner_name)) . '","' + . str_replace('"', '""', $this->escapeCsvFormula($item->client_name ?? '')) . '","' + . str_replace('"', '""', $this->escapeCsvFormula($item->category_title)) . '","' . str_replace('"', '""', ($item->track_type == 1 ? Text::_('COM_BANNERS_IMPRESSION') : Text::_('COM_BANNERS_CLICK'))) . '","' . str_replace('"', '""', $item->count) . '","' . str_replace('"', '""', $item->track_date) . '"' . "\n"; @@ -496,4 +496,26 @@ public function getContent() return $this->content; } + + /** + * Escapes potential characters that start a formula in a CSV value to prevent injection attacks + * + * @param string $value csv field value + * + * @return string + * + * @since __DEPLOY_VERSION__ + */ + private function escapeCsvFormula(string $value): string + { + if ($value === '') { + return $value; + } + + if (\in_array($value[0], ['=', '+', '-', '@'], true)) { + return ' ' . $value; + } + + return $value; + } } diff --git a/administrator/components/com_media/resources/scripts/store/mutations.es6.js b/administrator/components/com_media/resources/scripts/store/mutations.es6.js index e81deedf1d753..7b97a1eecfb24 100644 --- a/administrator/components/com_media/resources/scripts/store/mutations.es6.js +++ b/administrator/components/com_media/resources/scripts/store/mutations.es6.js @@ -279,7 +279,10 @@ export default { * @param payload the item */ [types.SELECT_BROWSER_ITEM]: (state, payload) => { - state.selectedItems.push(payload); + const isSelected = state.selectedItems.some((item) => item.path === payload.path); + if (!isSelected) { + state.selectedItems.push(payload); + } }, /** diff --git a/administrator/components/com_templates/src/Controller/TemplateController.php b/administrator/components/com_templates/src/Controller/TemplateController.php index 619aaa6b65f2f..2171fe090d8a4 100644 --- a/administrator/components/com_templates/src/Controller/TemplateController.php +++ b/administrator/components/com_templates/src/Controller/TemplateController.php @@ -848,11 +848,13 @@ public function extractArchive() if ($model->extractArchive($file)) { $this->setMessage(Text::_('COM_TEMPLATES_FILE_ARCHIVE_EXTRACT_SUCCESS')); - $url = 'index.php?option=com_templates&view=template&id=' . $id . '&file=' . $file; + $url = 'index.php?option=com_templates&view=template&id=' . $id . '&file=' . $file + . '&isMedia=' . $this->input->getInt('isMedia', 0); $this->setRedirect(Route::_($url, false)); } else { $this->setMessage(Text::_('COM_TEMPLATES_FILE_ARCHIVE_EXTRACT_FAIL'), 'error'); - $url = 'index.php?option=com_templates&view=template&id=' . $id . '&file=' . $file; + $url = 'index.php?option=com_templates&view=template&id=' . $id . '&file=' . $file + . '&isMedia=' . $this->input->getInt('isMedia', 0); $this->setRedirect(Route::_($url, false)); } } diff --git a/administrator/components/com_templates/src/Model/TemplateModel.php b/administrator/components/com_templates/src/Model/TemplateModel.php index 026bbbcec161e..caa7920f9d27f 100644 --- a/administrator/components/com_templates/src/Model/TemplateModel.php +++ b/administrator/components/com_templates/src/Model/TemplateModel.php @@ -1842,27 +1842,26 @@ public function getArchive() public function extractArchive($file) { if ($this->getTemplate()) { - $app = Factory::getApplication(); - $relPath = base64_decode($file); - $explodeArray = explode('/', $relPath); - $fileName = end($explodeArray); - $path = $this->getBasePath() . base64_decode($file); + $app = Factory::getApplication(); + $basePath = $this->getBasePath(); + $archiveFilePath = Path::check($basePath . base64_decode($file), $basePath); + $extractPath = \dirname($archiveFilePath); - if (file_exists(Path::check($path . '/' . $fileName))) { + if (file_exists($archiveFilePath)) { $zip = new \ZipArchive(); - if ($zip->open(Path::clean($path . '/' . $fileName)) === true) { + if ($zip->open($archiveFilePath) === true) { for ($i = 0; $i < $zip->numFiles; $i++) { $entry = $zip->getNameIndex($i); - if (file_exists(Path::clean($path . '/' . $entry))) { + if (file_exists(Path::check($extractPath . '/' . $entry, $extractPath))) { $app->enqueueMessage(Text::_('COM_TEMPLATES_FILE_ARCHIVE_EXISTS'), 'error'); return false; } } - $zip->extractTo($path); + $zip->extractTo($extractPath); return true; } diff --git a/composer.lock b/composer.lock index 28f2cc4a04c5d..83ba4d16524d0 100644 --- a/composer.lock +++ b/composer.lock @@ -1992,16 +1992,16 @@ }, { "name": "joomla/registry", - "version": "4.0.0", + "version": "4.0.1", "source": { "type": "git", "url": "https://github.com/joomla-framework/registry.git", - "reference": "5692d8423c9b568627df701e9fd505eae5ea8f72" + "reference": "788009a10cb39caad29c39a7c718eb576692ebd8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/joomla-framework/registry/zipball/5692d8423c9b568627df701e9fd505eae5ea8f72", - "reference": "5692d8423c9b568627df701e9fd505eae5ea8f72", + "url": "https://api.github.com/repos/joomla-framework/registry/zipball/788009a10cb39caad29c39a7c718eb576692ebd8", + "reference": "788009a10cb39caad29c39a7c718eb576692ebd8", "shasum": "" }, "require": { @@ -2011,8 +2011,8 @@ "require-dev": { "phpstan/phpstan": "^2.1.17", "phpstan/phpstan-deprecation-rules": "^2.0.3", - "phpunit/phpunit": "^12.2.6", - "squizlabs/php_codesniffer": "^3.7.2", + "phpunit/phpunit": "^12.5 || ^13.0", + "squizlabs/php_codesniffer": "^4.0", "symfony/yaml": "^7.3" }, "suggest": { @@ -2039,9 +2039,9 @@ ], "support": { "issues": "https://github.com/joomla-framework/registry/issues", - "source": "https://github.com/joomla-framework/registry/tree/4.0.0" + "source": "https://github.com/joomla-framework/registry/tree/4.0.1" }, - "time": "2025-07-23T19:26:22+00:00" + "time": "2026-08-24T19:58:10+00:00" }, { "name": "joomla/router", diff --git a/layouts/plugins/editors/tinymce/field/tinymcebuilder.php b/layouts/plugins/editors/tinymce/field/tinymcebuilder.php index eaedcdbac0c43..8a2aec52bacf9 100644 --- a/layouts/plugins/editors/tinymce/field/tinymcebuilder.php +++ b/layouts/plugins/editors/tinymce/field/tinymcebuilder.php @@ -68,9 +68,10 @@ ->useStyle('webcomponent.joomla-tab') ->useScript('webcomponent.joomla-tab'); -// Add TinyMCE language file to translate the buttons +// Add TinyMCE language file to translate the buttons. +// It has to run after the builder script, which replaces the global tinymce object with its own stub. if ($languageFile) { - $wa->registerAndUseScript('tinymce.language', $languageFile, [], ['defer' => true], []); + $wa->registerAndUseScript('tinymce.language', $languageFile, [], ['defer' => true], ['plg_editors_tinymce.builder']); } // Add the builder options diff --git a/libraries/src/Helper/MediaHelper.php b/libraries/src/Helper/MediaHelper.php index 623b8e5976851..defd4d9cc62af 100644 --- a/libraries/src/Helper/MediaHelper.php +++ b/libraries/src/Helper/MediaHelper.php @@ -56,7 +56,7 @@ public static function isImage($fileName) { static $imageTypes = 'xcf|odg|gif|jpg|jpeg|png|bmp|webp|avif'; - return preg_match("/\.(?:$imageTypes)$/i", $fileName); + return (bool) preg_match("/\.(?:$imageTypes)$/i", $fileName); } /** diff --git a/libraries/src/Helper/TagsHelper.php b/libraries/src/Helper/TagsHelper.php index d461b60ddf266..651f3af4b558d 100644 --- a/libraries/src/Helper/TagsHelper.php +++ b/libraries/src/Helper/TagsHelper.php @@ -910,16 +910,17 @@ public function postStore(TableInterface $table, array $newTags = [], bool $repl } $query = $db->createQuery() - ->select($db->quoteName('ucm_id')) - ->from($db->quoteName('#__ucm_base')) + ->select($db->quoteName('core_content_id')) + ->from($db->quoteName('#__ucm_content')) + ->where( [ - $db->quoteName('ucm_item_id') . ' = :itemId', - $db->quoteName('ucm_type_id') . ' = :typeId', + $db->quoteName('core_content_item_id') . ' = :itemId', + $db->quoteName('core_type_alias') . ' = :typeAlias', ] ) ->bind(':itemId', $ucmData['common']['core_content_item_id'], ParameterType::INTEGER) - ->bind(':typeId', $ucmData['common']['core_type_id'], ParameterType::INTEGER); + ->bind(':typeAlias', $ucmData['common']['core_type_alias']); $db->setQuery($query); $primaryId = $db->loadResult(); diff --git a/modules/mod_articles_news/src/Helper/ArticlesNewsHelper.php b/modules/mod_articles_news/src/Helper/ArticlesNewsHelper.php index 6a3e61ad6993d..dc0650d8ccfad 100644 --- a/modules/mod_articles_news/src/Helper/ArticlesNewsHelper.php +++ b/modules/mod_articles_news/src/Helper/ArticlesNewsHelper.php @@ -160,7 +160,7 @@ public function getArticles(Registry $params, SiteApplication $app) $item->imageSrc = htmlspecialchars($images->image_fulltext, ENT_COMPAT, 'UTF-8'); $item->imageAlt = htmlspecialchars($images->image_fulltext_alt, ENT_COMPAT, 'UTF-8'); - if ($images->image_intro_caption) { + if ($images->image_fulltext_caption) { $item->imageCaption = htmlspecialchars($images->image_fulltext_caption, ENT_COMPAT, 'UTF-8'); } } diff --git a/tests/System/integration/site/components/com_content/Category.cy.js b/tests/System/integration/site/components/com_content/Category.cy.js index 5ae782476b634..021f90bbad1d8 100644 --- a/tests/System/integration/site/components/com_content/Category.cy.js +++ b/tests/System/integration/site/components/com_content/Category.cy.js @@ -1,4 +1,10 @@ describe('Test in frontend that the content category view', () => { + afterEach(() => { + cy.db_updateExtensionParameter('filter_field', 'hide', 'com_content'); + cy.db_updateExtensionParameter('list_show_author', '1', 'com_content'); + cy.db_updateExtensionParameter('list_show_hits', '1', 'com_content'); + }); + ['default', 'blog'].forEach((layout) => { it(`can display a list of articles in the ${layout} layout in a menu item`, () => { cy.db_createArticle({ title: 'article 1' }) @@ -38,6 +44,321 @@ describe('Test in frontend that the content category view', () => { }); }); + it('can use the title filter to narrow the article list', () => { + cy.db_createArticle({ title: 'article abc 1' }) + .then((article) => cy.db_createArticle({ title: 'article def 2', catid: article.catid })) + .then((article) => cy.db_createArticle({ title: 'article abc 3', catid: article.catid })) + .then((article) => cy.db_createArticle({ title: 'article xyz 4', catid: article.catid })) + .then((article) => cy.db_createArticle({ title: 'article def 5', catid: article.catid })) + .then((article) => cy.db_createMenuItem({ + title: 'automated test', + alias: 'automated-test', + path: 'automated-test', + link: `index.php?option=com_content&view=category&id=${article.catid}`, + params: { + filter_field: 'title', + list_show_author: '0', + }, + })) + .then(() => { + cy.visit('/'); + cy.get('a:contains(automated test)').click(); + cy.get('main div.com-content-category.category-list').within(() => { + cy.get('table.com-content-category__table thead th').should('have.length', 2); + cy.get('table.com-content-category__table thead th').eq(0).should('contain.text', 'Title'); + cy.get('table.com-content-category__table thead th').eq(1).should('contain.text', 'Hits'); + cy.get('table.com-content-category__table tbody th a') + .should('contain.text', 'article abc 1') + .should('contain.text', 'article def 2') + .should('contain.text', 'article abc 3') + .should('contain.text', 'article xyz 4') + .should('contain.text', 'article def 5'); + + cy.get('#filter-search').should('have.attr', 'placeholder', 'Title Filter'); + cy.get('#filter-search').clear().type('abc'); + cy.get('.com-content__filter button[type="submit"]').click(); + + cy.get('table.com-content-category__table tbody th a') + .should('contain.text', 'article abc 1') + .should('not.contain.text', 'article def 2') + .should('contain.text', 'article abc 3') + .should('not.contain.text', 'article xyz 4') + .should('not.contain.text', 'article def 5'); + + cy.get('#filter-search').clear().type('xyz'); + cy.get('.com-content__filter button[type="submit"]').click(); + + cy.get('table.com-content-category__table tbody th a') + .should('not.contain.text', 'article abc 1') + .should('not.contain.text', 'article def 2') + .should('not.contain.text', 'article abc 3') + .should('contain.text', 'article xyz 4') + .should('not.contain.text', 'article def 5'); + + cy.get('#filter-search').clear().type('def'); + cy.get('.com-content__filter button[type="submit"]').click(); + + cy.get('table.com-content-category__table tbody th a') + .should('not.contain.text', 'article abc 1') + .should('contain.text', 'article def 2') + .should('not.contain.text', 'article abc 3') + .should('not.contain.text', 'article xyz 4') + .should('contain.text', 'article def 5'); + + cy.get('.com-content__filter button[type="reset"]').click(); + cy.get('#filter-search').should('have.value', ''); + + cy.get('table.com-content-category__table tbody th a') + .should('contain.text', 'article abc 1') + .should('contain.text', 'article def 2') + .should('contain.text', 'article abc 3') + .should('contain.text', 'article xyz 4') + .should('contain.text', 'article def 5'); + }); + }); + }); + + it('can use the author filter to narrow the article list', () => { + cy.db_createArticle({ title: 'article 1', created_by_alias: 'James' }) + .then((article) => cy.db_createArticle({ title: 'article 2', catid: article.catid, created_by_alias: 'William' })) + .then((article) => cy.db_createArticle({ title: 'article 3', catid: article.catid, created_by_alias: 'Emma' })) + .then((article) => { + cy.db_createUser({ name: 'Emma' }).then((id) => { + cy.db_createArticle({ title: 'article 4', catid: article.catid, created_by: id }); + cy.db_createArticle({ title: 'article 5', catid: article.catid, created_by: id, created_by_alias: 'Charlotte' }); + }); + }) + .then((article) => cy.db_createMenuItem({ + title: 'automated test', + alias: 'automated-test', + path: 'automated-test', + link: `index.php?option=com_content&view=category&id=${article.catid}`, + params: { + filter_field: '', + list_show_date: 'published', + }, + })) + .then(() => { + cy.db_updateExtensionParameter('filter_field', 'author', 'com_content'); + cy.db_updateExtensionParameter('list_show_hits', '0', 'com_content'); + cy.visit('/'); + cy.get('a:contains(automated test)').click(); + cy.get('main div.com-content-category.category-list').within(() => { + cy.get('table.com-content-category__table thead th').should('have.length', 3); + cy.get('table.com-content-category__table thead th').eq(0).should('contain.text', 'Title'); + cy.get('table.com-content-category__table thead th').eq(1).should('contain.text', 'Published Date'); + cy.get('table.com-content-category__table thead th').eq(2).should('contain.text', 'Author'); + cy.get('table.com-content-category__table tbody th a') + .should('contain.text', 'article 1') + .should('contain.text', 'article 2') + .should('contain.text', 'article 3') + .should('contain.text', 'article 4') + .should('contain.text', 'article 5'); + + cy.get('#filter-search').should('have.attr', 'placeholder', 'Author Filter'); + cy.get('#filter-search').clear().type('william'); + cy.get('.com-content__filter button[type="submit"]').click(); + + cy.get('table.com-content-category__table tbody th a') + .should('not.contain.text', 'article 1') + .should('contain.text', 'article 2') + .should('not.contain.text', 'article 3') + .should('not.contain.text', 'article 4') + .should('not.contain.text', 'article 5'); + + cy.get('#filter-search').clear().type('James'); + cy.get('.com-content__filter button[type="submit"]').click(); + + cy.get('table.com-content-category__table tbody th a') + .should('contain.text', 'article 1') + .should('not.contain.text', 'article 2') + .should('not.contain.text', 'article 3') + .should('not.contain.text', 'article 4') + .should('not.contain.text', 'article 5'); + + cy.get('#filter-search').clear().type('Charlotte'); + cy.get('.com-content__filter button[type="submit"]').click(); + + cy.get('table.com-content-category__table tbody th a') + .should('not.contain.text', 'article 1') + .should('not.contain.text', 'article 2') + .should('not.contain.text', 'article 3') + .should('not.contain.text', 'article 4') + .should('contain.text', 'article 5'); + + cy.get('#filter-search').clear().type('emma'); + cy.get('.com-content__filter button[type="submit"]').click(); + + cy.get('table.com-content-category__table tbody th a') + .should('not.contain.text', 'article 1') + .should('not.contain.text', 'article 2') + .should('contain.text', 'article 3') + .should('contain.text', 'article 4') + .should('not.contain.text', 'article 5'); + + cy.get('.com-content__filter button[type="reset"]').click(); + cy.get('#filter-search').should('have.value', ''); + + cy.get('table.com-content-category__table tbody th a') + .should('contain.text', 'article 1') + .should('contain.text', 'article 2') + .should('contain.text', 'article 3') + .should('contain.text', 'article 4') + .should('contain.text', 'article 5'); + }); + }); + }); + + it('can use the tag filter to narrow the article list', () => { + cy.db_createArticle({ title: 'article 1' }) + .then((article) => { + cy.db_createTag({ title: 'tag1' }).then((id) => { + cy.db_createArticle({ title: 'article 2', catid: article.catid, tags: [id] }); + cy.db_createArticle({ title: 'article 3', catid: article.catid, tags: [id] }); + }); + }) + .then((article) => { + cy.db_createTag({ title: 'tag2' }).then((id) => { + cy.db_createArticle({ title: 'article 4', catid: article.catid, tags: [id] }); + cy.db_createArticle({ title: 'article 5', catid: article.catid, tags: [id] }); + }); + }) + .then((article) => cy.db_createMenuItem({ + title: 'automated test', + alias: 'automated-test', + path: 'automated-test', + link: `index.php?option=com_content&view=category&id=${article.catid}`, + params: { + filter_field: 'tag', + }, + })) + .then(() => { + cy.visit('/'); + cy.get('a:contains(automated test)').click(); + cy.get('main div.com-content-category.category-list').within(() => { + cy.get('table.com-content-category__table thead th').should('have.length', 3); + cy.get('table.com-content-category__table thead th').eq(0).should('contain.text', 'Title'); + cy.get('table.com-content-category__table thead th').eq(1).should('contain.text', 'Author'); + cy.get('table.com-content-category__table thead th').eq(2).should('contain.text', 'Hits'); + cy.get('table.com-content-category__table tbody th a') + .should('contain.text', 'article 1') + .should('contain.text', 'article 2') + .should('contain.text', 'article 3') + .should('contain.text', 'article 4') + .should('contain.text', 'article 5'); + + cy.get('#filter-search').select('tag1'); + cy.get('.com-content__filter button[type="submit"]').click(); + + cy.get('table.com-content-category__table tbody th a') + .should('not.contain.text', 'article 1') + .should('contain.text', 'article 2') + .should('contain.text', 'article 3') + .should('not.contain.text', 'article 4') + .should('not.contain.text', 'article 5'); + + cy.get('#filter-search').select('tag2'); + cy.get('.com-content__filter button[type="submit"]').click(); + + cy.get('table.com-content-category__table tbody th a') + .should('not.contain.text', 'article 1') + .should('not.contain.text', 'article 2') + .should('not.contain.text', 'article 3') + .should('contain.text', 'article 4') + .should('contain.text', 'article 5'); + + cy.get('.com-content__filter button[type="reset"]').click(); + cy.get('#filter-search').should('have.value', ''); + + cy.get('table.com-content-category__table tbody th a') + .should('contain.text', 'article 1') + .should('contain.text', 'article 2') + .should('contain.text', 'article 3') + .should('contain.text', 'article 4') + .should('contain.text', 'article 5'); + }); + }); + }); + + it('can use the month filter to narrow the article list', () => { + cy.db_createArticle({ title: 'article 1', publish_up: '2026-08-17 20:00:00' }) + .then((article) => cy.db_createArticle({ title: 'article 2', catid: article.catid, publish_up: '2026-08-28 20:00:00' })) + .then((article) => cy.db_createArticle({ title: 'article 3', catid: article.catid, publish_up: '2026-05-20 20:00:00' })) + .then((article) => cy.db_createArticle({ title: 'article 4', catid: article.catid, publish_up: '2026-05-25 20:00:00' })) + .then((article) => cy.db_createArticle({ title: 'article 5', catid: article.catid, publish_up: '2025-08-17 20:00:00' })) + .then((article) => cy.db_createMenuItem({ + title: 'automated test', + alias: 'automated-test', + path: 'automated-test', + link: `index.php?option=com_content&view=category&id=${article.catid}`, + params: { + filter_field: 'month', + list_show_date: 'modified', + list_show_hits: '0', + }, + })) + .then(() => { + cy.db_updateExtensionParameter('filter_field', 'hits', 'com_content'); + cy.db_updateExtensionParameter('list_show_author', '0', 'com_content'); + cy.visit('/'); + cy.get('a:contains(automated test)').click(); + cy.get('main div.com-content-category.category-list').within(() => { + cy.get('table.com-content-category__table thead th').should('have.length', 2); + cy.get('table.com-content-category__table thead th').eq(0).should('contain.text', 'Title'); + cy.get('table.com-content-category__table thead th').eq(1).should('contain.text', 'Modified Date'); + cy.get('table.com-content-category__table tbody th a') + .should('contain.text', 'article 1') + .should('contain.text', 'article 2') + .should('contain.text', 'article 3') + .should('contain.text', 'article 4') + .should('contain.text', 'article 5'); + + cy.get('#filter-search').select('2026-08-01'); + cy.get('#filter-search option:selected').should('have.text', 'August 2026 [2]'); + cy.get('.com-content__filter button[type="submit"]').click(); + + cy.get('table.com-content-category__table tbody th a') + .should('contain.text', 'article 1') + .should('contain.text', 'article 2') + .should('not.contain.text', 'article 3') + .should('not.contain.text', 'article 4') + .should('not.contain.text', 'article 5'); + + cy.get('#filter-search').select('2025-08-01'); + cy.get('#filter-search option:selected').should('have.text', 'August 2025 [1]'); + cy.get('.com-content__filter button[type="submit"]').click(); + + cy.get('table.com-content-category__table tbody th a') + .should('not.contain.text', 'article 1') + .should('not.contain.text', 'article 2') + .should('not.contain.text', 'article 3') + .should('not.contain.text', 'article 4') + .should('contain.text', 'article 5'); + + cy.get('#filter-search').select('2026-05-01'); + cy.get('#filter-search option:selected').should('have.text', 'May 2026 [2]'); + cy.get('.com-content__filter button[type="submit"]').click(); + + cy.get('table.com-content-category__table tbody th a') + .should('not.contain.text', 'article 1') + .should('not.contain.text', 'article 2') + .should('contain.text', 'article 3') + .should('contain.text', 'article 4') + .should('not.contain.text', 'article 5'); + + cy.get('.com-content__filter button[type="reset"]').click(); + cy.get('#filter-search').should('have.value', ''); + + cy.get('table.com-content-category__table tbody th a') + .should('contain.text', 'article 1') + .should('contain.text', 'article 2') + .should('contain.text', 'article 3') + .should('contain.text', 'article 4') + .should('contain.text', 'article 5'); + }); + }); + }); + it('can open the article form in the default layout', () => { cy.db_createArticle({ title: 'article 1' }) .then((article) => cy.db_createMenuItem({ diff --git a/tests/System/plugins/db.mjs b/tests/System/plugins/db.mjs index e57200820ebf1..d77a16553a20a 100644 --- a/tests/System/plugins/db.mjs +++ b/tests/System/plugins/db.mjs @@ -170,6 +170,7 @@ function deleteInsertedItems(config) { if (item.table === `${config.expose.db_prefix}content`) { promises.push(queryTestDB(`DELETE FROM #__content_frontpage WHERE content_id IN (${item.rows.join(',')})`, config)); + promises.push(queryTestDB(`DELETE FROM #__contentitem_tag_map WHERE content_item_id IN (${item.rows.join(',')}) AND type_alias = 'com_content.article'`, config)); promises.push(queryTestDB(`DELETE FROM #__workflow_associations WHERE item_id IN (${item.rows.join(',')}) AND extension = 'com_content.article'`, config)); } diff --git a/tests/System/support/commands/db.mjs b/tests/System/support/commands/db.mjs index 0cac699a380f3..f05be25bc2539 100644 --- a/tests/System/support/commands/db.mjs +++ b/tests/System/support/commands/db.mjs @@ -113,6 +113,9 @@ Cypress.Commands.add('db_createArticle', (articleData) => { metadata: '', }; + const tags = articleData.tags ?? []; + delete articleData.tags; + const article = { ...defaultArticleOptions, ...articleData }; return getDefaultCategoryId('com_content') @@ -128,6 +131,9 @@ Cypress.Commands.add('db_createArticle', (articleData) => { if (article.featured === 1) { await cy.task('queryDB', `INSERT INTO #__content_frontpage (content_id, ordering) VALUES ('${article.id}', '1')`); } + tags.forEach(async (tag) => { + await cy.task('queryDB', `INSERT INTO #__contentitem_tag_map (type_alias, core_content_id, content_item_id, tag_id, tag_date, type_id) VALUES ('com_content.article', '0', '${article.id}', '${tag}', '2026-08-29 11:00:00', '1')`); + }); await cy.task('queryDB', `INSERT INTO #__workflow_associations (item_id, stage_id, extension) VALUES (${article.id}, 1, 'com_content.article')`); return article; @@ -504,6 +510,11 @@ Cypress.Commands.add('db_createMenuItem', (menuItemData) => { defaultMenuItemOptions.rgt = myrgt[0].rgt + 1; const menuItem = { ...defaultMenuItemOptions, ...menuItemData }; + ['params'].forEach((key) => { + if (typeof menuItem[key] === 'object') { + menuItem[key] = JSON.stringify(menuItem[key]); + } + }); // Extract the component from the link const component = (new URLSearchParams(menuItem.link.replace('index.php', ''))).get('option'); return cy.task('queryDB', `SELECT extension_id FROM #__extensions WHERE name = '${component}'`).then((id) => {