' . $additional;
$classes = 'badge ' . $labelClass;
+ if (!empty($items[$langCode]['association_outdated'])) {
+ $text .= ' '
+ . '' . Text::_('JGLOBAL_ASSOCIATIONS_OUTDATED') . ' ';
+ $tooltip .= ' ' . Text::_('JGLOBAL_ASSOCIATIONS_OUTDATED');
+ }
+
$items[$langCode]['link'] = '' . $text . ' '
. '' . $tooltip . '
';
}
diff --git a/administrator/components/com_associations/src/Model/AssociationsModel.php b/administrator/components/com_associations/src/Model/AssociationsModel.php
index 03adf17c33da7..fc38efa418417 100644
--- a/administrator/components/com_associations/src/Model/AssociationsModel.php
+++ b/administrator/components/com_associations/src/Model/AssociationsModel.php
@@ -50,6 +50,7 @@ public function __construct($config = [], ?MVCFactoryInterface $factory = null)
'itemtype',
'language',
'association',
+ 'translation',
'menutype',
'menutype_title',
'level',
@@ -139,6 +140,7 @@ protected function getStoreId($id = '')
$id .= ':' . $this->getState('filter.menutype');
$id .= ':' . $this->getState('filter.access');
$id .= ':' . $this->getState('filter.level');
+ $id .= ':' . $this->getState('filter.translation');
return parent::getStoreId($id);
}
@@ -224,6 +226,7 @@ protected function getListQuery()
// Join over the associations.
$query->select('COUNT(' . $db->quoteName('asso2.id') . ') > 1 AS ' . $db->quoteName('association'))
+ ->select($db->quoteName('asso.outdated', 'association_outdated'))
->join(
'LEFT',
$db->quoteName('#__associations', 'asso'),
@@ -245,8 +248,17 @@ protected function getListQuery()
$fields['language'],
'l.title',
'l.image',
+ 'asso.outdated',
];
+ $translation = $this->getState('filter.translation');
+
+ if ($translation === 'outdated') {
+ $query->where($db->quoteName('asso.outdated') . ' = 1');
+ } elseif ($translation === 'uptodate') {
+ $query->where($db->quoteName('asso.outdated') . ' = 0');
+ }
+
// Select author for ACL checks.
if (!empty($fields['created_user_id'])) {
$query->select($db->quoteName($fields['created_user_id'], 'created_user_id'));
diff --git a/administrator/components/com_associations/tmpl/associations/default.php b/administrator/components/com_associations/tmpl/associations/default.php
index 290d9c5da1239..3e6fe5858d207 100644
--- a/administrator/components/com_associations/tmpl/associations/default.php
+++ b/administrator/components/com_associations/tmpl/associations/default.php
@@ -135,6 +135,12 @@
+ association_outdated)) : ?>
+
+
+
+
+
extensionName, $this->typeName, (int) $item->id, $item->language, !$isCheckout, false); ?>
diff --git a/administrator/components/com_categories/forms/filter_categories.xml b/administrator/components/com_categories/forms/filter_categories.xml
index 1bac0a161c996..70602395a6068 100644
--- a/administrator/components/com_categories/forms/filter_categories.xml
+++ b/administrator/components/com_categories/forms/filter_categories.xml
@@ -40,6 +40,19 @@
JALL
+
+ JGLOBAL_ASSOCIATIONS_FILTER_TRANSLATION_SELECT
+ JGLOBAL_ASSOCIATIONS_OUTDATED
+ JGLOBAL_ASSOCIATIONS_UPTODATE
+
+
getState('filter.published');
$id .= ':' . $this->getState('filter.access');
$id .= ':' . $this->getState('filter.language');
+ $id .= ':' . $this->getState('filter.translation');
$id .= ':' . $this->getState('filter.level');
$id .= ':' . serialize($this->getState('filter.tag'));
@@ -221,6 +223,7 @@ protected function getListQuery()
if ($assoc) {
$query->select('COUNT(asso2.id)>1 as association')
+ ->select($db->quoteName('asso.outdated', 'association_outdated'))
->join(
'LEFT',
$db->quoteName('#__associations', 'asso'),
@@ -232,7 +235,15 @@ protected function getListQuery()
$db->quoteName('#__associations', 'asso2'),
$db->quoteName('asso2.key') . ' = ' . $db->quoteName('asso.key')
)
- ->group('a.id, l.title, uc.name, ag.title, ua.name');
+ ->group('a.id, l.title, uc.name, ag.title, ua.name, asso.outdated');
+
+ $translation = $this->getState('filter.translation');
+
+ if ($translation === 'outdated') {
+ $query->where($db->quoteName('asso.outdated') . ' = 1');
+ } elseif ($translation === 'uptodate') {
+ $query->where($db->quoteName('asso.outdated') . ' = 0');
+ }
}
// Filter by extension
diff --git a/administrator/components/com_categories/src/Model/CategoryModel.php b/administrator/components/com_categories/src/Model/CategoryModel.php
index b60f7c73dc15b..00807609d8765 100644
--- a/administrator/components/com_categories/src/Model/CategoryModel.php
+++ b/administrator/components/com_categories/src/Model/CategoryModel.php
@@ -19,7 +19,6 @@
use Joomla\CMS\Form\Form;
use Joomla\CMS\Helper\TagsHelper;
use Joomla\CMS\Language\Associations;
-use Joomla\CMS\Language\LanguageHelper;
use Joomla\CMS\Language\Text;
use Joomla\CMS\MVC\Factory\MVCFactoryInterface;
use Joomla\CMS\MVC\Model\AdminModel;
@@ -458,32 +457,7 @@ protected function preprocessForm(Form $form, $data, $group = 'content')
// Association category items
if ($this->getAssoc()) {
- $languages = LanguageHelper::getContentLanguages(false, false, null, 'ordering', 'asc');
-
- if (\count($languages) > 1) {
- $addform = new \SimpleXMLElement('');
- $fields = $addform->addChild('fields');
- $fields->addAttribute('name', 'associations');
- $fieldset = $fields->addChild('fieldset');
- $fieldset->addAttribute('name', 'item_associations');
-
- foreach ($languages as $language) {
- $field = $fieldset->addChild('field');
- $field->addAttribute('name', $language->lang_code);
- $field->addAttribute('type', 'modal_category');
- $field->addAttribute('language', $language->lang_code);
- $field->addAttribute('label', $language->title);
- $field->addAttribute('translate_label', 'false');
- $field->addAttribute('extension', $extension);
- $field->addAttribute('select', 'true');
- $field->addAttribute('new', 'true');
- $field->addAttribute('edit', 'true');
- $field->addAttribute('clear', 'true');
- $field->addAttribute('propagate', 'true');
- }
-
- $form->load($addform, false);
- }
+ $this->associationPreprocessForm($form, $data, ['type' => 'modal_category', 'extension' => $extension]);
}
// Trigger the default form events.
diff --git a/administrator/components/com_categories/src/Service/HTML/AdministratorService.php b/administrator/components/com_categories/src/Service/HTML/AdministratorService.php
index cddb935cf5517..a8739383be3b9 100644
--- a/administrator/components/com_categories/src/Service/HTML/AdministratorService.php
+++ b/administrator/components/com_categories/src/Service/HTML/AdministratorService.php
@@ -61,9 +61,16 @@ public function association($catid, $extension = 'com_content')
$db->quoteName('l.lang_code'),
$db->quoteName('l.image'),
$db->quoteName('l.title', 'language_title'),
+ $db->quoteName('assoc.outdated'),
]
)
->from($db->quoteName('#__categories', 'c'))
+ ->join(
+ 'LEFT',
+ $db->quoteName('#__associations', 'assoc'),
+ $db->quoteName('assoc.id') . ' = ' . $db->quoteName('c.id')
+ . ' AND ' . $db->quoteName('assoc.context') . ' = ' . $db->quote('com_categories.item')
+ )
->whereIn($db->quoteName('c.id'), array_values($associations))
->where($db->quoteName('c.id') . ' != :catid')
->bind(':catid', $catid, ParameterType::INTEGER)
@@ -92,6 +99,12 @@ public function association($catid, $extension = 'com_content')
. htmlspecialchars($item->title, ENT_QUOTES, 'UTF-8');
$classes = 'badge bg-secondary';
+ if (!empty($item->outdated)) {
+ $text .= ' '
+ . '' . Text::_('JGLOBAL_ASSOCIATIONS_OUTDATED') . ' ';
+ $tooltip .= ' ' . Text::_('JGLOBAL_ASSOCIATIONS_OUTDATED');
+ }
+
$item->link = '' . $text . ' '
. '' . $tooltip . '
';
} else {
diff --git a/administrator/components/com_categories/src/View/Categories/HtmlView.php b/administrator/components/com_categories/src/View/Categories/HtmlView.php
index 0c6da623efd26..f0d05abb462df 100644
--- a/administrator/components/com_categories/src/View/Categories/HtmlView.php
+++ b/administrator/components/com_categories/src/View/Categories/HtmlView.php
@@ -13,6 +13,7 @@
use Joomla\CMS\Component\ComponentHelper;
use Joomla\CMS\Factory;
use Joomla\CMS\Helper\ContentHelper;
+use Joomla\CMS\Language\Associations;
use Joomla\CMS\Language\Multilanguage;
use Joomla\CMS\Language\Text;
use Joomla\CMS\MVC\View\GenericDataException;
@@ -124,6 +125,11 @@ public function display($tpl = null)
$this->ordering[$item->parent_id][] = $item->id;
}
+ if (!Associations::isEnabled()) {
+ unset($this->activeFilters['translation']);
+ $this->filterForm->removeField('translation', 'filter');
+ }
+
// We don't need toolbar in the modal window.
if ($this->getLayout() !== 'modal') {
$this->addToolbar();
diff --git a/administrator/components/com_categories/tmpl/categories/default.php b/administrator/components/com_categories/tmpl/categories/default.php
index 7f243f78f577d..93afdc86f8366 100644
--- a/administrator/components/com_categories/tmpl/categories/default.php
+++ b/administrator/components/com_categories/tmpl/categories/default.php
@@ -248,6 +248,17 @@
assoc) : ?>
+ association_outdated)) : ?>
+ >
+
+
+
+
association) : ?>
id, $extension); ?>
diff --git a/administrator/components/com_contact/forms/filter_contacts.xml b/administrator/components/com_contact/forms/filter_contacts.xml
index ca12bf545ee09..8ef3ddaa052bc 100644
--- a/administrator/components/com_contact/forms/filter_contacts.xml
+++ b/administrator/components/com_contact/forms/filter_contacts.xml
@@ -65,6 +65,19 @@
JALL
+
+ JGLOBAL_ASSOCIATIONS_FILTER_TRANSLATION_SELECT
+ JGLOBAL_ASSOCIATIONS_OUTDATED
+ JGLOBAL_ASSOCIATIONS_UPTODATE
+
+
1) {
- $addform = new \SimpleXMLElement('');
- $fields = $addform->addChild('fields');
- $fields->addAttribute('name', 'associations');
- $fieldset = $fields->addChild('fieldset');
- $fieldset->addAttribute('name', 'item_associations');
-
- foreach ($languages as $language) {
- $field = $fieldset->addChild('field');
- $field->addAttribute('name', $language->lang_code);
- $field->addAttribute('type', 'modal_contact');
- $field->addAttribute('language', $language->lang_code);
- $field->addAttribute('label', $language->title);
- $field->addAttribute('translate_label', 'false');
- $field->addAttribute('select', 'true');
- $field->addAttribute('new', 'true');
- $field->addAttribute('edit', 'true');
- $field->addAttribute('clear', 'true');
- $field->addAttribute('propagate', 'true');
- }
-
- $form->load($addform, false);
- }
+ $this->associationPreprocessForm($form, $data, ['type' => 'modal_contact']);
}
parent::preprocessForm($form, $data, $group);
diff --git a/administrator/components/com_contact/src/Model/ContactsModel.php b/administrator/components/com_contact/src/Model/ContactsModel.php
index a781a3ca4e6f4..1dbafb94fd1dd 100644
--- a/administrator/components/com_contact/src/Model/ContactsModel.php
+++ b/administrator/components/com_contact/src/Model/ContactsModel.php
@@ -65,6 +65,7 @@ public function __construct($config = [], ?MVCFactoryInterface $factory = null)
if (Associations::isEnabled()) {
$config['filter_fields'][] = 'association';
+ $config['filter_fields'][] = 'translation';
}
}
@@ -129,6 +130,7 @@ protected function getStoreId($id = '')
$id .= ':' . serialize($this->getState('filter.category_id'));
$id .= ':' . $this->getState('filter.access');
$id .= ':' . $this->getState('filter.language');
+ $id .= ':' . $this->getState('filter.translation');
$id .= ':' . serialize($this->getState('filter.tag'));
$id .= ':' . $this->getState('filter.level');
@@ -220,6 +222,25 @@ protected function getListQuery()
);
$query->select('(' . $subQuery . ') AS ' . $db->quoteName('association'));
+
+ $query->select($db->quoteName('assoc.outdated', 'association_outdated'))
+ ->join(
+ 'LEFT',
+ $db->quoteName('#__associations', 'assoc'),
+ $db->quoteName('assoc.id') . ' = ' . $db->quoteName('a.id')
+ . ' AND ' . $db->quoteName('assoc.context') . ' = ' . $db->quote('com_contact.item')
+ );
+
+ $translation = $this->getState('filter.translation');
+
+ switch ($translation) {
+ case 'outdated':
+ $query->where($db->quoteName('assoc.outdated') . ' = 1');
+ break;
+ case 'uptodate':
+ $query->where($db->quoteName('assoc.outdated') . ' = 0');
+ break;
+ }
}
// Filter by featured.
diff --git a/administrator/components/com_contact/src/Service/HTML/AdministratorService.php b/administrator/components/com_contact/src/Service/HTML/AdministratorService.php
index 4c6a829bec937..9d49b9021ad27 100644
--- a/administrator/components/com_contact/src/Service/HTML/AdministratorService.php
+++ b/administrator/components/com_contact/src/Service/HTML/AdministratorService.php
@@ -46,8 +46,11 @@ public function association($contactid)
// Get the associations
if ($associations = Associations::getAssociations('com_contact', '#__contact_details', 'com_contact.item', $contactid)) {
+ $outdated = [];
+
foreach ($associations as $tag => $associated) {
- $associations[$tag] = (int) $associated->id;
+ $outdated[(int) $associated->id] = (int) $associated->outdated;
+ $associations[$tag] = (int) $associated->id;
}
// Get the associated contact items
@@ -90,6 +93,12 @@ public function association($contactid)
. htmlspecialchars($item->title, ENT_QUOTES, 'UTF-8') . ' ' . Text::sprintf('JCATEGORY_SPRINTF', $item->category_title);
$classes = 'badge bg-secondary';
+ if (!empty($outdated[(int) $item->id])) {
+ $text .= ' '
+ . '' . Text::_('JGLOBAL_ASSOCIATIONS_OUTDATED') . ' ';
+ $tooltip .= ' ' . Text::_('JGLOBAL_ASSOCIATIONS_OUTDATED');
+ }
+
$item->link = '' . $text . ' '
. '' . $tooltip . '
';
} else {
diff --git a/administrator/components/com_contact/src/View/Contacts/HtmlView.php b/administrator/components/com_contact/src/View/Contacts/HtmlView.php
index 31759d510880a..0efd4caf8da0b 100644
--- a/administrator/components/com_contact/src/View/Contacts/HtmlView.php
+++ b/administrator/components/com_contact/src/View/Contacts/HtmlView.php
@@ -12,6 +12,7 @@
use Joomla\CMS\Factory;
use Joomla\CMS\Helper\ContentHelper;
+use Joomla\CMS\Language\Associations;
use Joomla\CMS\Language\Multilanguage;
use Joomla\CMS\MVC\View\ListView;
use Joomla\CMS\Session\Session;
@@ -69,6 +70,11 @@ protected function initializeView()
$this->canDo->set('core.create', true);
}
+ if (!Associations::isEnabled()) {
+ unset($this->activeFilters['translation']);
+ $this->filterForm->removeField('translation', 'filter');
+ }
+
// We don't need toolbar in the modal window.
if ($this->getLayout() !== 'modal') {
// We do not need to filter by language when multilingual is disabled
diff --git a/administrator/components/com_contact/tmpl/contacts/default.php b/administrator/components/com_contact/tmpl/contacts/default.php
index 32147cf2658ef..300a75a5be259 100644
--- a/administrator/components/com_contact/tmpl/contacts/default.php
+++ b/administrator/components/com_contact/tmpl/contacts/default.php
@@ -168,6 +168,17 @@
+ association_outdated)) : ?>
+ >
+
+
+
+
association) : ?>
id); ?>
diff --git a/administrator/components/com_content/forms/filter_articles.xml b/administrator/components/com_content/forms/filter_articles.xml
index 6b9be58d6a399..cf8353906518c 100644
--- a/administrator/components/com_content/forms/filter_articles.xml
+++ b/administrator/components/com_content/forms/filter_articles.xml
@@ -89,6 +89,19 @@
JALL
+
+ JGLOBAL_ASSOCIATIONS_FILTER_TRANSLATION_SELECT
+ JGLOBAL_ASSOCIATIONS_OUTDATED
+ JGLOBAL_ASSOCIATIONS_UPTODATE
+
+
1) {
- $addform = new \SimpleXMLElement('');
- $fields = $addform->addChild('fields');
- $fields->addAttribute('name', 'associations');
- $fieldset = $fields->addChild('fieldset');
- $fieldset->addAttribute('name', 'item_associations');
-
- foreach ($languages as $language) {
- $field = $fieldset->addChild('field');
- $field->addAttribute('name', $language->lang_code);
- $field->addAttribute('type', 'modal_article');
- $field->addAttribute('language', $language->lang_code);
- $field->addAttribute('label', $language->title);
- $field->addAttribute('translate_label', 'false');
- $field->addAttribute('select', 'true');
- $field->addAttribute('new', 'true');
- $field->addAttribute('edit', 'true');
- $field->addAttribute('clear', 'true');
- $field->addAttribute('propagate', 'true');
- }
-
- $form->load($addform, false);
- }
+ $this->associationPreprocessForm($form, $data, ['type' => 'modal_article']);
}
$this->workflowPreprocessForm($form, $data);
diff --git a/administrator/components/com_content/src/Model/ArticlesModel.php b/administrator/components/com_content/src/Model/ArticlesModel.php
index 97b379cc68f48..d182b7cc9a9dd 100644
--- a/administrator/components/com_content/src/Model/ArticlesModel.php
+++ b/administrator/components/com_content/src/Model/ArticlesModel.php
@@ -81,6 +81,7 @@ public function __construct($config = [], ?MVCFactoryInterface $factory = null)
if (Associations::isEnabled()) {
$config['filter_fields'][] = 'association';
+ $config['filter_fields'][] = 'translation';
}
}
@@ -182,6 +183,7 @@ protected function getStoreId($id = '')
$id .= ':' . serialize($this->getState('filter.category_id'));
$id .= ':' . serialize($this->getState('filter.author_id'));
$id .= ':' . $this->getState('filter.language');
+ $id .= ':' . $this->getState('filter.translation');
$id .= ':' . serialize($this->getState('filter.tag'));
$id .= ':' . $this->getState('filter.checked_out');
$id .= ':' . $this->getState('filter.date_filtering');
@@ -305,6 +307,22 @@ protected function getListQuery()
);
$query->select('(' . $subQuery . ') AS ' . $db->quoteName('association'));
+
+ $query->select($db->quoteName('assoc.outdated', 'association_outdated'))
+ ->join(
+ 'LEFT',
+ $db->quoteName('#__associations', 'assoc'),
+ $db->quoteName('assoc.id') . ' = ' . $db->quoteName('a.id')
+ . ' AND ' . $db->quoteName('assoc.context') . ' = ' . $db->quote('com_content.item')
+ );
+
+ $translation = $this->getState('filter.translation');
+
+ if ($translation === 'outdated') {
+ $query->where($db->quoteName('assoc.outdated') . ' = 1');
+ } elseif ($translation === 'uptodate') {
+ $query->where($db->quoteName('assoc.outdated') . ' = 0');
+ }
}
// Filter by access level.
diff --git a/administrator/components/com_content/src/Service/HTML/AdministratorService.php b/administrator/components/com_content/src/Service/HTML/AdministratorService.php
index bfa4f52fedb4f..f6669c0a0c830 100644
--- a/administrator/components/com_content/src/Service/HTML/AdministratorService.php
+++ b/administrator/components/com_content/src/Service/HTML/AdministratorService.php
@@ -50,8 +50,11 @@ public function association($articleid)
// Get the associations
if ($associations = Associations::getAssociations('com_content', '#__content', 'com_content.item', $articleid)) {
+ $outdated = [];
+
foreach ($associations as $tag => $associated) {
- $associations[$tag] = (int) $associated->id;
+ $outdated[(int) $associated->id] = (int) $associated->outdated;
+ $associations[$tag] = (int) $associated->id;
}
// Get the associated menu items
@@ -100,6 +103,12 @@ public function association($articleid)
. htmlspecialchars($item->title, ENT_QUOTES, 'UTF-8') . ' ' . Text::sprintf('JCATEGORY_SPRINTF', $item->category_title);
$classes = 'badge bg-secondary';
+ if (!empty($outdated[(int) $item->id])) {
+ $text .= ' '
+ . '' . Text::_('JGLOBAL_ASSOCIATIONS_OUTDATED') . ' ';
+ $tooltip .= ' ' . Text::_('JGLOBAL_ASSOCIATIONS_OUTDATED');
+ }
+
$item->link = '' . $text . ' '
. '' . $tooltip . '
';
} else {
diff --git a/administrator/components/com_content/src/View/Articles/HtmlView.php b/administrator/components/com_content/src/View/Articles/HtmlView.php
index 272d89a947dff..3d55b4e38e11f 100644
--- a/administrator/components/com_content/src/View/Articles/HtmlView.php
+++ b/administrator/components/com_content/src/View/Articles/HtmlView.php
@@ -12,6 +12,7 @@
use Joomla\CMS\Component\ComponentHelper;
use Joomla\CMS\Factory;
+use Joomla\CMS\Language\Associations;
use Joomla\CMS\Language\Multilanguage;
use Joomla\CMS\Language\Text;
use Joomla\CMS\MVC\View\HtmlView as BaseHtmlView;
@@ -169,6 +170,11 @@ public function display($tpl = null)
}
}
+ if (!Associations::isEnabled()) {
+ unset($this->activeFilters['translation']);
+ $this->filterForm->removeField('translation', 'filter');
+ }
+
// Add form control fields
$this->filterForm
->addControlField('task')
diff --git a/administrator/components/com_content/tmpl/articles/default.php b/administrator/components/com_content/tmpl/articles/default.php
index 0c56baffad293..5678fcae06f71 100644
--- a/administrator/components/com_content/tmpl/articles/default.php
+++ b/administrator/components/com_content/tmpl/articles/default.php
@@ -347,6 +347,17 @@
+ association_outdated)) : ?>
+ >
+
+
+
+
association) : ?>
id); ?>
diff --git a/administrator/components/com_menus/forms/filter_items.xml b/administrator/components/com_menus/forms/filter_items.xml
index 7b832866aec56..57a92176d2652 100644
--- a/administrator/components/com_menus/forms/filter_items.xml
+++ b/administrator/components/com_menus/forms/filter_items.xml
@@ -59,6 +59,19 @@
JOPTION_SELECT_LANGUAGE
JALL
+
+
+ JGLOBAL_ASSOCIATIONS_FILTER_TRANSLATION_SELECT
+ JGLOBAL_ASSOCIATIONS_OUTDATED
+ JGLOBAL_ASSOCIATIONS_UPTODATE
+
1) {
- $addform = new \SimpleXMLElement('');
- $fields = $addform->addChild('fields');
- $fields->addAttribute('name', 'associations');
- $fieldset = $fields->addChild('fieldset');
- $fieldset->addAttribute('name', 'item_associations');
- $fieldset->addAttribute('addfieldprefix', 'Joomla\Component\Menus\Administrator\Field');
-
- foreach ($languages as $language) {
- $field = $fieldset->addChild('field');
- $field->addAttribute('name', $language->lang_code);
- $field->addAttribute('type', 'modal_menu');
- $field->addAttribute('language', $language->lang_code);
- $field->addAttribute('label', $language->title);
- $field->addAttribute('translate_label', 'false');
- $field->addAttribute('select', 'true');
- $field->addAttribute('new', 'true');
- $field->addAttribute('edit', 'true');
- $field->addAttribute('clear', 'true');
- $field->addAttribute('propagate', 'true');
- $option = $field->addChild('option', 'COM_MENUS_ITEM_FIELD_ASSOCIATION_NO_VALUE');
- $option->addAttribute('value', '');
- }
-
- $form->load($addform, false);
- }
+ $this->associationPreprocessForm(
+ $form,
+ $data,
+ ['type' => 'modal_menu'],
+ 'Joomla\Component\Menus\Administrator\Field'
+ );
}
// Trigger the default form events.
@@ -1519,109 +1496,13 @@ public function save($data)
$this->setState('item.menutype', $table->menutype);
// Load associated menu items, for now not supported for admin menu… may be later
- if ($table->client_id == 0 && Associations::isEnabled()) {
- // Adding self to the association
- $associations = $data['associations'] ?? [];
-
- // Unset any invalid associations
- $associations = ArrayHelper::toInteger($associations);
-
- foreach ($associations as $tag => $id) {
- if (!$id) {
- unset($associations[$tag]);
- }
- }
-
- // Detecting all item menus
- $all_language = $table->language == '*';
-
- if ($all_language && !empty($associations)) {
- Factory::getApplication()->enqueueMessage(Text::_('COM_MENUS_ERROR_ALL_LANGUAGE_ASSOCIATED'), 'notice');
- }
-
- // Get associationskey for edited item
- $db = $this->getDatabase();
- $query = $db->createQuery()
- ->select($db->quoteName('key'))
- ->from($db->quoteName('#__associations'))
- ->where(
- [
- $db->quoteName('context') . ' = :context',
- $db->quoteName('id') . ' = :id',
- ]
- )
- ->bind(':context', $this->associationsContext)
- ->bind(':id', $table->id, ParameterType::INTEGER);
- $db->setQuery($query);
- $oldKey = $db->loadResult();
-
- if ($associations || $oldKey !== null) {
- // Deleting old associations for the associated items
- $where = [];
- $query = $db->createQuery()
- ->delete($db->quoteName('#__associations'))
- ->where($db->quoteName('context') . ' = :context')
- ->bind(':context', $this->associationsContext);
-
- if ($associations) {
- $where[] = $db->quoteName('id') . ' IN (' . implode(',', $query->bindArray(array_values($associations))) . ')';
- }
-
- if ($oldKey !== null) {
- $where[] = $db->quoteName('key') . ' = :oldKey';
- $query->bind(':oldKey', $oldKey);
- }
-
- $query->extendWhere('AND', $where, 'OR');
-
- try {
- $db->setQuery($query);
- $db->execute();
- } catch (\RuntimeException $e) {
- $this->setError($e->getMessage());
-
- return false;
- }
- }
-
- // Adding self to the association
- if (!$all_language) {
- $associations[$table->language] = (int) $table->id;
- }
-
- if (\count($associations) > 1) {
- // Adding new association for these items
- $key = md5(json_encode($associations));
- $query = $db->createQuery()
- ->insert($db->quoteName('#__associations'))
- ->columns(
- [
- $db->quoteName('id'),
- $db->quoteName('context'),
- $db->quoteName('key'),
- ]
- );
-
- foreach ($associations as $id) {
- $query->values(
- implode(
- ',',
- $query->bindArray(
- [$id, $this->associationsContext, $key],
- [ParameterType::INTEGER, ParameterType::STRING, ParameterType::STRING]
- )
- )
- );
- }
-
- try {
- $db->setQuery($query);
- $db->execute();
- } catch (\RuntimeException $e) {
- $this->setError($e->getMessage());
+ if ($table->client_id == 0) {
+ try {
+ $this->storeAssociations($table, $data);
+ } catch (\RuntimeException $e) {
+ $this->setError($e->getMessage());
- return false;
- }
+ return false;
}
}
diff --git a/administrator/components/com_menus/src/Model/ItemsModel.php b/administrator/components/com_menus/src/Model/ItemsModel.php
index ef802abfbc58f..164ecbc0916d3 100644
--- a/administrator/components/com_menus/src/Model/ItemsModel.php
+++ b/administrator/components/com_menus/src/Model/ItemsModel.php
@@ -69,6 +69,7 @@ public function __construct($config = [], ?MVCFactoryInterface $factory = null)
if (Associations::isEnabled()) {
$config['filter_fields'][] = 'association';
+ $config['filter_fields'][] = 'translation';
}
}
@@ -206,6 +207,7 @@ protected function getStoreId($id = '')
$id .= ':' . $this->getState('filter.access');
$id .= ':' . $this->getState('filter.published');
$id .= ':' . $this->getState('filter.language');
+ $id .= ':' . $this->getState('filter.translation');
$id .= ':' . $this->getState('filter.search');
$id .= ':' . $this->getState('filter.parent_id');
$id .= ':' . $this->getState('filter.menutype');
@@ -314,6 +316,22 @@ protected function getListQuery()
);
$query->select('(' . $subQuery . ') AS ' . $db->quoteName('association'));
+
+ $query->select($db->quoteName('assoc.outdated', 'association_outdated'))
+ ->join(
+ 'LEFT',
+ $db->quoteName('#__associations', 'assoc'),
+ $db->quoteName('assoc.id') . ' = ' . $db->quoteName('a.id')
+ . ' AND ' . $db->quoteName('assoc.context') . ' = ' . $db->quote('com_menus.item')
+ );
+
+ $translation = $this->getState('filter.translation');
+
+ if ($translation === 'outdated') {
+ $query->where($db->quoteName('assoc.outdated') . ' = 1');
+ } elseif ($translation === 'uptodate') {
+ $query->where($db->quoteName('assoc.outdated') . ' = 0');
+ }
}
// Exclude the root category.
diff --git a/administrator/components/com_menus/src/Service/HTML/Menus.php b/administrator/components/com_menus/src/Service/HTML/Menus.php
index 4aa3fe7fe9308..6d0cfead2a81c 100644
--- a/administrator/components/com_menus/src/Service/HTML/Menus.php
+++ b/administrator/components/com_menus/src/Service/HTML/Menus.php
@@ -62,9 +62,16 @@ public function association($itemid)
$db->quoteName('mt.title', 'menu_title'),
$db->quoteName('l.image'),
$db->quoteName('l.title', 'language_title'),
+ $db->quoteName('assoc.outdated'),
]
)
->from($db->quoteName('#__menu', 'm'))
+ ->join(
+ 'LEFT',
+ $db->quoteName('#__associations', 'assoc'),
+ $db->quoteName('assoc.id') . ' = ' . $db->quoteName('m.id')
+ . ' AND ' . $db->quoteName('assoc.context') . ' = ' . $db->quote('com_menus.item')
+ )
->join('LEFT', $db->quoteName('#__menu_types', 'mt'), $db->quoteName('mt.menutype') . ' = ' . $db->quoteName('m.menutype'))
->join('LEFT', $db->quoteName('#__languages', 'l'), $db->quoteName('m.language') . ' = ' . $db->quoteName('l.lang_code'))
->whereIn($db->quoteName('m.id'), array_values($associations))
@@ -91,6 +98,12 @@ public function association($itemid)
. htmlspecialchars($item->title, ENT_QUOTES, 'UTF-8') . ' ' . Text::sprintf('COM_MENUS_MENU_SPRINTF', $item->menu_title);
$classes = 'badge bg-secondary';
+ if (!empty($item->outdated)) {
+ $text .= ' '
+ . '' . Text::_('JGLOBAL_ASSOCIATIONS_OUTDATED') . ' ';
+ $tooltip .= ' ' . Text::_('JGLOBAL_ASSOCIATIONS_OUTDATED');
+ }
+
$item->link = '' . $text . ' '
. '' . $tooltip . '
';
} else {
diff --git a/administrator/components/com_menus/src/View/Items/HtmlView.php b/administrator/components/com_menus/src/View/Items/HtmlView.php
index 0b64478528587..40fa607e996ed 100644
--- a/administrator/components/com_menus/src/View/Items/HtmlView.php
+++ b/administrator/components/com_menus/src/View/Items/HtmlView.php
@@ -14,6 +14,7 @@
use Joomla\CMS\Factory;
use Joomla\CMS\Helper\ContentHelper;
use Joomla\CMS\HTML\HTMLHelper;
+use Joomla\CMS\Language\Associations;
use Joomla\CMS\Language\Multilanguage;
use Joomla\CMS\Language\Text;
use Joomla\CMS\MVC\View\HtmlView as BaseHtmlView;
@@ -254,6 +255,11 @@ public function display($tpl = null)
$this->f_levels = $options;
+ if (!Associations::isEnabled()) {
+ unset($this->activeFilters['translation']);
+ $this->filterForm->removeField('translation', 'filter');
+ }
+
// We don't need toolbar in the modal window.
if ($this->getLayout() !== 'modal') {
$this->addToolbar();
diff --git a/administrator/components/com_menus/tmpl/items/default.php b/administrator/components/com_menus/tmpl/items/default.php
index 95ee028f79264..50e91bcb3e711 100644
--- a/administrator/components/com_menus/tmpl/items/default.php
+++ b/administrator/components/com_menus/tmpl/items/default.php
@@ -248,6 +248,17 @@
+ association_outdated)) : ?>
+ >
+
+
+
+
association) : ?>
id); ?>
diff --git a/administrator/components/com_modules/forms/filter_modules.xml b/administrator/components/com_modules/forms/filter_modules.xml
index 7fc4293515184..2262dd6ff3000 100644
--- a/administrator/components/com_modules/forms/filter_modules.xml
+++ b/administrator/components/com_modules/forms/filter_modules.xml
@@ -75,6 +75,19 @@
JOPTION_SELECT_LANGUAGE
JALL
+
+
+ JGLOBAL_ASSOCIATIONS_FILTER_TRANSLATION_SELECT
+ JGLOBAL_ASSOCIATIONS_OUTDATED
+ JGLOBAL_ASSOCIATIONS_UPTODATE
+
1) {
- $addform = new \SimpleXMLElement('');
- $fields = $addform->addChild('fields');
- $fields->addAttribute('name', 'associations');
- $fieldset = $fields->addChild('fieldset');
- $fieldset->addAttribute('name', 'item_associations');
- $fieldset->addAttribute('addfieldprefix', 'Joomla\Component\Modules\Administrator\Field');
-
- foreach ($languages as $language) {
- $field = $fieldset->addChild('field');
- $field->addAttribute('name', $language->lang_code);
- $field->addAttribute('type', 'modal_module');
- $field->addAttribute('language', $language->lang_code);
- $field->addAttribute('label', $language->title);
- $field->addAttribute('translate_label', 'false');
- $field->addAttribute('select', 'true');
- $field->addAttribute('new', 'true');
- $field->addAttribute('edit', 'true');
- $field->addAttribute('clear', 'true');
- $field->addAttribute('propagate', 'true');
- }
-
- $form->load($addform, false);
- }
+ $this->associationPreprocessForm(
+ $form,
+ $data,
+ ['type' => 'modal_module'],
+ 'Joomla\Component\Modules\Administrator\Field'
+ );
}
// Trigger the default form events.
parent::preprocessForm($form, $data, $group);
diff --git a/administrator/components/com_modules/src/Model/ModulesModel.php b/administrator/components/com_modules/src/Model/ModulesModel.php
index d790472edc643..16194b179b8b5 100644
--- a/administrator/components/com_modules/src/Model/ModulesModel.php
+++ b/administrator/components/com_modules/src/Model/ModulesModel.php
@@ -67,6 +67,7 @@ public function __construct($config = [], ?MVCFactoryInterface $factory = null)
if (Associations::isEnabled()) {
$config['filter_fields'][] = 'association';
+ $config['filter_fields'][] = 'translation';
}
}
@@ -161,6 +162,7 @@ protected function getStoreId($id = '')
$id .= ':' . $this->getState('filter.menuitem');
$id .= ':' . $this->getState('filter.access');
$id .= ':' . $this->getState('filter.language');
+ $id .= ':' . $this->getState('filter.translation');
return parent::getStoreId($id);
}
@@ -440,6 +442,23 @@ protected function getListQuery()
);
$query->select('(' . $subQuery . ') AS ' . $db->quoteName('association'));
+
+ $query->select($db->quoteName('assoc.outdated', 'association_outdated'))
+ ->join(
+ 'LEFT',
+ $db->quoteName('#__associations', 'assoc'),
+ $db->quoteName('assoc.id') . ' = ' . $db->quoteName('a.id')
+ . ' AND ' . $db->quoteName('assoc.context') . ' = ' . $db->quote('com_modules.item')
+ )
+ ->group($db->quoteName('assoc.outdated'));
+
+ $translation = $this->getState('filter.translation');
+
+ if ($translation === 'outdated') {
+ $query->where($db->quoteName('assoc.outdated') . ' = 1');
+ } elseif ($translation === 'uptodate') {
+ $query->where($db->quoteName('assoc.outdated') . ' = 0');
+ }
}
return $query;
diff --git a/administrator/components/com_modules/src/Service/HTML/Modules.php b/administrator/components/com_modules/src/Service/HTML/Modules.php
index a7f9736c95080..87915ff381848 100644
--- a/administrator/components/com_modules/src/Service/HTML/Modules.php
+++ b/administrator/components/com_modules/src/Service/HTML/Modules.php
@@ -66,9 +66,16 @@ public function association($itemid)
$db->quoteName('l.lang_code'),
$db->quoteName('l.image'),
$db->quoteName('l.title', 'language_title'),
+ $db->quoteName('assoc.outdated'),
]
)
->from($db->quoteName('#__modules', 'm'))
+ ->join(
+ 'LEFT',
+ $db->quoteName('#__associations', 'assoc'),
+ $db->quoteName('assoc.id') . ' = ' . $db->quoteName('m.id')
+ . ' AND ' . $db->quoteName('assoc.context') . ' = ' . $db->quote('com_modules.item')
+ )
->join('LEFT', $db->quoteName('#__languages', 'l'), $db->quoteName('m.language') . ' = ' . $db->quoteName('l.lang_code'))
->whereIn($db->quoteName('m.id'), array_values($associations))
->where($db->quoteName('m.id') . ' != :itemid')
@@ -94,6 +101,12 @@ public function association($itemid)
. Text::sprintf('COM_MODULES_MODULE_SPRINTF', $item->title);
$classes = 'badge bg-secondary';
+ if (!empty($item->outdated)) {
+ $text .= ' '
+ . '' . Text::_('JGLOBAL_ASSOCIATIONS_OUTDATED') . ' ';
+ $tooltip .= ' ' . Text::_('JGLOBAL_ASSOCIATIONS_OUTDATED');
+ }
+
$item->link = '' . $text . ' '
. '' . $tooltip . '
';
} else {
diff --git a/administrator/components/com_modules/src/View/Modules/HtmlView.php b/administrator/components/com_modules/src/View/Modules/HtmlView.php
index b610a5a593426..0aecfd496494f 100644
--- a/administrator/components/com_modules/src/View/Modules/HtmlView.php
+++ b/administrator/components/com_modules/src/View/Modules/HtmlView.php
@@ -13,6 +13,7 @@
use Joomla\CMS\Factory;
use Joomla\CMS\Helper\ContentHelper;
use Joomla\CMS\Helper\ModuleHelper;
+use Joomla\CMS\Language\Associations;
use Joomla\CMS\Language\Multilanguage;
use Joomla\CMS\MVC\Model\ListModel;
use Joomla\CMS\MVC\View\ListView;
@@ -114,6 +115,11 @@ protected function initializeView()
$this->filterForm->removeField('language', 'filter');
}
+ if (!Associations::isEnabled()) {
+ unset($this->activeFilters['translation']);
+ $this->filterForm->removeField('translation', 'filter');
+ }
+
// We don't need the toolbar in the modal window.
if ($this->getLayout() !== 'modal') {
// We do not need to filter by language when multilingual is disabled
diff --git a/administrator/components/com_modules/tmpl/modules/default.php b/administrator/components/com_modules/tmpl/modules/default.php
index 679f51614f2f1..f99ef6b5f507d 100644
--- a/administrator/components/com_modules/tmpl/modules/default.php
+++ b/administrator/components/com_modules/tmpl/modules/default.php
@@ -181,6 +181,17 @@
+ association_outdated)) : ?>
+ >
+
+
+
+
association) : ?>
id); ?>
diff --git a/administrator/components/com_newsfeeds/forms/filter_newsfeeds.xml b/administrator/components/com_newsfeeds/forms/filter_newsfeeds.xml
index cb3edd952e136..837f55909c53e 100644
--- a/administrator/components/com_newsfeeds/forms/filter_newsfeeds.xml
+++ b/administrator/components/com_newsfeeds/forms/filter_newsfeeds.xml
@@ -51,6 +51,19 @@
JALL
+
+ JGLOBAL_ASSOCIATIONS_FILTER_TRANSLATION_SELECT
+ JGLOBAL_ASSOCIATIONS_OUTDATED
+ JGLOBAL_ASSOCIATIONS_UPTODATE
+
+
1) {
- $addform = new \SimpleXMLElement('');
- $fields = $addform->addChild('fields');
- $fields->addAttribute('name', 'associations');
- $fieldset = $fields->addChild('fieldset');
- $fieldset->addAttribute('name', 'item_associations');
-
- foreach ($languages as $language) {
- $field = $fieldset->addChild('field');
- $field->addAttribute('name', $language->lang_code);
- $field->addAttribute('type', 'modal_newsfeed');
- $field->addAttribute('language', $language->lang_code);
- $field->addAttribute('label', $language->title);
- $field->addAttribute('translate_label', 'false');
- $field->addAttribute('select', 'true');
- $field->addAttribute('new', 'true');
- $field->addAttribute('edit', 'true');
- $field->addAttribute('clear', 'true');
- $field->addAttribute('propagate', 'true');
- }
-
- $form->load($addform, false);
- }
+ $this->associationPreprocessForm($form, $data, ['type' => 'modal_newsfeed']);
}
parent::preprocessForm($form, $data, $group);
diff --git a/administrator/components/com_newsfeeds/src/Model/NewsfeedsModel.php b/administrator/components/com_newsfeeds/src/Model/NewsfeedsModel.php
index e7a62b1f7c61e..174d6eeb6a30e 100644
--- a/administrator/components/com_newsfeeds/src/Model/NewsfeedsModel.php
+++ b/administrator/components/com_newsfeeds/src/Model/NewsfeedsModel.php
@@ -66,6 +66,7 @@ public function __construct($config = [], ?MVCFactoryInterface $factory = null)
if (Associations::isEnabled()) {
$config['filter_fields'][] = 'association';
+ $config['filter_fields'][] = 'translation';
}
}
@@ -132,6 +133,7 @@ protected function getStoreId($id = '')
$id .= ':' . $this->getState('filter.category_id');
$id .= ':' . $this->getState('filter.access');
$id .= ':' . $this->getState('filter.language');
+ $id .= ':' . $this->getState('filter.translation');
$id .= ':' . $this->getState('filter.level');
$id .= ':' . serialize($this->getState('filter.tag'));
@@ -203,6 +205,22 @@ protected function getListQuery()
);
$query->select('(' . $subQuery . ') AS ' . $db->quoteName('association'));
+
+ $query->select($db->quoteName('assoc.outdated', 'association_outdated'))
+ ->join(
+ 'LEFT',
+ $db->quoteName('#__associations', 'assoc'),
+ $db->quoteName('assoc.id') . ' = ' . $db->quoteName('a.id')
+ . ' AND ' . $db->quoteName('assoc.context') . ' = ' . $db->quote('com_newsfeeds.item')
+ );
+
+ $translation = $this->getState('filter.translation');
+
+ if ($translation === 'outdated') {
+ $query->where($db->quoteName('assoc.outdated') . ' = 1');
+ } elseif ($translation === 'uptodate') {
+ $query->where($db->quoteName('assoc.outdated') . ' = 0');
+ }
}
// Filter by access level.
diff --git a/administrator/components/com_newsfeeds/src/Service/HTML/AdministratorService.php b/administrator/components/com_newsfeeds/src/Service/HTML/AdministratorService.php
index 97d94dd5d12ce..57373c1cacce1 100644
--- a/administrator/components/com_newsfeeds/src/Service/HTML/AdministratorService.php
+++ b/administrator/components/com_newsfeeds/src/Service/HTML/AdministratorService.php
@@ -45,8 +45,11 @@ public function association($newsfeedid)
// Get the associations
if ($associations = Associations::getAssociations('com_newsfeeds', '#__newsfeeds', 'com_newsfeeds.item', $newsfeedid)) {
+ $outdated = [];
+
foreach ($associations as $tag => $associated) {
- $associations[$tag] = (int) $associated->id;
+ $outdated[(int) $associated->id] = (int) $associated->outdated;
+ $associations[$tag] = (int) $associated->id;
}
// Get the associated newsfeed items
@@ -94,6 +97,12 @@ public function association($newsfeedid)
. htmlspecialchars($item->title, ENT_QUOTES, 'UTF-8') . ' ' . Text::sprintf('JCATEGORY_SPRINTF', $item->category_title);
$classes = 'badge bg-secondary';
+ if (!empty($outdated[(int) $item->id])) {
+ $text .= ' '
+ . '' . Text::_('JGLOBAL_ASSOCIATIONS_OUTDATED') . ' ';
+ $tooltip .= ' ' . Text::_('JGLOBAL_ASSOCIATIONS_OUTDATED');
+ }
+
$item->link = '' . $text . ' '
. '' . $tooltip . '
';
} else {
diff --git a/administrator/components/com_newsfeeds/src/View/Newsfeeds/HtmlView.php b/administrator/components/com_newsfeeds/src/View/Newsfeeds/HtmlView.php
index 76d42e26fdf14..92ceb39037946 100644
--- a/administrator/components/com_newsfeeds/src/View/Newsfeeds/HtmlView.php
+++ b/administrator/components/com_newsfeeds/src/View/Newsfeeds/HtmlView.php
@@ -12,6 +12,7 @@
use Joomla\CMS\Factory;
use Joomla\CMS\Helper\ContentHelper;
+use Joomla\CMS\Language\Associations;
use Joomla\CMS\Language\Multilanguage;
use Joomla\CMS\Language\Text;
use Joomla\CMS\MVC\View\HtmlView as BaseHtmlView;
@@ -110,6 +111,11 @@ public function display($tpl = null)
->addControlField('task')
->addControlField('boxchecked', '0');
+ if (!Associations::isEnabled()) {
+ unset($this->activeFilters['translation']);
+ $this->filterForm->removeField('translation', 'filter');
+ }
+
// We don't need toolbar in the modal layout.
if ($this->getLayout() !== 'modal') {
$this->addToolbar();
diff --git a/administrator/components/com_newsfeeds/tmpl/newsfeeds/default.php b/administrator/components/com_newsfeeds/tmpl/newsfeeds/default.php
index 1d2a205d34067..b2e1c513f46c8 100644
--- a/administrator/components/com_newsfeeds/tmpl/newsfeeds/default.php
+++ b/administrator/components/com_newsfeeds/tmpl/newsfeeds/default.php
@@ -155,6 +155,17 @@
+ association_outdated)) : ?>
+ >
+
+
+
+
association) : ?>
id); ?>
diff --git a/administrator/language/en-GB/joomla.ini b/administrator/language/en-GB/joomla.ini
index 7ccabcf0c069a..3e36bf53390cf 100644
--- a/administrator/language/en-GB/joomla.ini
+++ b/administrator/language/en-GB/joomla.ini
@@ -334,14 +334,27 @@ JGLOBAL_ARTICLE_ORDER_LABEL="Article Order"
JGLOBAL_ARTICLES="Articles"
JGLOBAL_ASSOC_NOT_POSSIBLE="To define associations, please make sure the item language is not set to 'All'."
JGLOBAL_ASSOCIATIONS_CONTENTLANGUAGE_WARNING="Some associated items are assigned to the %s Content Language but that Content Language is trashed or deleted."
+JGLOBAL_ASSOCIATIONS_FILTER_TRANSLATION="Translation Status"
+JGLOBAL_ASSOCIATIONS_FILTER_TRANSLATION_SELECT="- Select Translation Status -"
+JGLOBAL_ASSOCIATIONS_MARK_UPTODATE="Mark this translation as up to date"
JGLOBAL_ASSOCIATIONS_NEW_ITEM_WARNING="To create associations, first save the item."
+JGLOBAL_ASSOCIATIONS_N_ITEMS_UPTODATE="%d translations marked as up to date."
+JGLOBAL_ASSOCIATIONS_N_ITEMS_UPTODATE_1="Translation marked as up to date."
+JGLOBAL_ASSOCIATIONS_OUTDATED="Outdated"
JGLOBAL_ASSOCIATIONS_PROPAGATE_BUTTON="Propagate"
JGLOBAL_ASSOCIATIONS_PROPAGATE_FAILED="Failed propagating associations. You may have to select or create them manually."
JGLOBAL_ASSOCIATIONS_PROPAGATE_MESSAGE_ALL="All existing associations have been set."
JGLOBAL_ASSOCIATIONS_PROPAGATE_MESSAGE_NONE="No associations exist to propagate."
JGLOBAL_ASSOCIATIONS_PROPAGATE_MESSAGE_SOME="Associations have been set for: %s"
JGLOBAL_ASSOCIATIONS_PROPAGATE_TIP="Propagates this item's existing associations."
+JGLOBAL_ASSOCIATIONS_RELEVANCE_DESC="When selected \"Relevant\" assigned translations will be marked as outdated."
+JGLOBAL_ASSOCIATIONS_RELEVANCE_LABEL="Translation Relevance"
+JGLOBAL_ASSOCIATIONS_RELEVANCE_NONE="Not Relevant"
+JGLOBAL_ASSOCIATIONS_RELEVANCE_NONE_OUTDATED="Not Relevant - Stays Outdated"
+JGLOBAL_ASSOCIATIONS_RELEVANCE_OUTDATE="Relevant - Mark Translations Outdated"
+JGLOBAL_ASSOCIATIONS_RELEVANCE_SYNC="Translation Updated"
JGLOBAL_ASSOCIATIONS_RESET_WARNING="The language has been changed. If you save this item again it will reset the available associations. If this was not intended, close the item."
+JGLOBAL_ASSOCIATIONS_UPTODATE="Up to date"
JGLOBAL_AUTH_ACCESS_DENIED="Access Denied"
JGLOBAL_AUTH_ACCESS_GRANTED="Access Granted"
JGLOBAL_AUTH_BIND_FAILED="Failed binding to LDAP server"
diff --git a/installation/sql/mysql/supports.sql b/installation/sql/mysql/supports.sql
index f5f28b276cb1f..fbc224cab64e1 100644
--- a/installation/sql/mysql/supports.sql
+++ b/installation/sql/mysql/supports.sql
@@ -11,6 +11,7 @@ CREATE TABLE IF NOT EXISTS `#__associations` (
`id` int NOT NULL COMMENT 'A reference to the associated item.',
`context` varchar(50) NOT NULL COMMENT 'The context of the associated item.',
`key` char(32) NOT NULL COMMENT 'The key for the association computed from an md5 on associated ids.',
+ `outdated` tinyint NOT NULL DEFAULT 0 COMMENT 'Whether this item is outdated compared to the item the last translation relevant change was made in.',
PRIMARY KEY (`context`,`id`),
KEY `idx_key` (`key`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 DEFAULT COLLATE=utf8mb4_unicode_ci;
diff --git a/installation/sql/postgresql/supports.sql b/installation/sql/postgresql/supports.sql
index c0688fa70a7be..c069bbbd99f2d 100644
--- a/installation/sql/postgresql/supports.sql
+++ b/installation/sql/postgresql/supports.sql
@@ -6,6 +6,7 @@ CREATE TABLE IF NOT EXISTS "#__associations" (
"id" int NOT NULL,
"context" varchar(50) NOT NULL,
"key" char(32) NOT NULL,
+ "outdated" smallint DEFAULT 0 NOT NULL,
CONSTRAINT "#__associations_idx_context_id" PRIMARY KEY ("context", "id")
);
CREATE INDEX "#__associations_idx_key" ON "#__associations" ("key");
@@ -13,6 +14,7 @@ CREATE INDEX "#__associations_idx_key" ON "#__associations" ("key");
COMMENT ON COLUMN "#__associations"."id" IS 'A reference to the associated item.';
COMMENT ON COLUMN "#__associations"."context" IS 'The context of the associated item.';
COMMENT ON COLUMN "#__associations"."key" IS 'The key for the association computed from an md5 on associated ids.';
+COMMENT ON COLUMN "#__associations"."outdated" IS 'Whether this item is outdated compared to the item the last translation relevant change was made in.';
--
-- Table structure for table `#__categories`
diff --git a/libraries/src/Association/AssociationExtensionHelper.php b/libraries/src/Association/AssociationExtensionHelper.php
index 9e1aca7eca32e..95ccd199d594a 100644
--- a/libraries/src/Association/AssociationExtensionHelper.php
+++ b/libraries/src/Association/AssociationExtensionHelper.php
@@ -90,7 +90,8 @@ public function getAssociationList($typeName, $itemId)
$associations = $this->getAssociations($typeName, $itemId);
foreach ($associations as $key => $association) {
- $items[$key] = ArrayHelper::fromObject($this->getItem($typeName, (int) $association->id), false);
+ $items[$key] = ArrayHelper::fromObject($this->getItem($typeName, (int) $association->id), false);
+ $items[$key]['association_outdated'] = (int) ($association->outdated ?? 0);
}
return $items;
diff --git a/libraries/src/Language/Associations.php b/libraries/src/Language/Associations.php
index b1d3032efa4b0..c7d44cf8dbed6 100644
--- a/libraries/src/Language/Associations.php
+++ b/libraries/src/Language/Associations.php
@@ -71,7 +71,13 @@ public static function getAssociations(
$query->bind(':extension1', $extension);
}
- $query->select([$db->quoteName('c2.language'), $db->quoteName('c.' . $pk) . ' AS ' . $db->quoteName('pk')])
+ $query->select(
+ [
+ $db->quoteName('c2.language'),
+ $db->quoteName('c.' . $pk) . ' AS ' . $db->quoteName('pk'),
+ $db->quoteName('a2.outdated'),
+ ]
+ )
->from($db->quoteName($tablename, 'c'))
->join(
'INNER',
@@ -80,7 +86,13 @@ public static function getAssociations(
. ' AND ' . $db->quoteName('a.context') . ' = :context'
)
->bind(':context', $context)
- ->join('INNER', $db->quoteName('#__associations', 'a2'), $db->quoteName('a.key') . ' = ' . $db->quoteName('a2.key'))
+ ->join(
+ 'INNER',
+ $db->quoteName('#__associations', 'a2'),
+ $db->quoteName('a.key') . ' = ' . $db->quoteName('a2.key')
+ . ' AND ' . $db->quoteName('a2.context') . ' = :context2'
+ )
+ ->bind(':context2', $context)
->join(
'INNER',
$db->quoteName($tablename, 'c2'),
diff --git a/libraries/src/MVC/Controller/AdminController.php b/libraries/src/MVC/Controller/AdminController.php
index a4ff729a77032..8e5f689a1740b 100644
--- a/libraries/src/MVC/Controller/AdminController.php
+++ b/libraries/src/MVC/Controller/AdminController.php
@@ -238,6 +238,42 @@ public function publish()
$this->setRedirect($this->getRedirectUrlToList());
}
+ /**
+ * Method to clear the outdated flag of a list of translations.
+ *
+ * @return void
+ *
+ * @since __DEPLOY_VERSION__
+ */
+ public function translationUpToDate()
+ {
+ // Check for request forgeries
+ $this->checkToken();
+
+ // Remove zero values resulting from input filter
+ $cid = array_filter((array) $this->input->post->get('cid', [], 'int'));
+
+ if (!$cid) {
+ $this->getLogger()->warning(Text::_($this->text_prefix . '_NO_ITEM_SELECTED'), ['category' => 'jerror']);
+ } else {
+ $model = $this->getModel();
+
+ if (!method_exists($model, 'markAssociationsUpToDate')) {
+ throw new \RuntimeException(Text::_('JLIB_APPLICATION_ERROR_TASK_NOT_FOUND'), 404);
+ }
+
+ try {
+ if ($model->markAssociationsUpToDate($cid)) {
+ $this->setMessage(Text::plural('JGLOBAL_ASSOCIATIONS_N_ITEMS_UPTODATE', \count($cid)));
+ }
+ } catch (\Exception $e) {
+ $this->setMessage($e->getMessage(), 'error');
+ }
+ }
+
+ $this->setRedirect($this->getRedirectUrlToList());
+ }
+
/**
* Changes the order of one or more records.
*
diff --git a/libraries/src/MVC/Model/AdminModel.php b/libraries/src/MVC/Model/AdminModel.php
index ba95f39aaeef7..fbf6acfe37c85 100644
--- a/libraries/src/MVC/Model/AdminModel.php
+++ b/libraries/src/MVC/Model/AdminModel.php
@@ -42,6 +42,8 @@
*/
abstract class AdminModel extends FormModel
{
+ use AssociationBehaviorTrait;
+
/**
* The type alias for this content type (for example, 'com_content.article').
*
@@ -1385,97 +1387,7 @@ public function save($data)
$this->setState($this->getName() . '.new', $isNew);
- if ($this->associationsContext && Associations::isEnabled() && !empty($data['associations'])) {
- $associations = $data['associations'];
-
- // Unset any invalid associations
- $associations = ArrayHelper::toInteger($associations);
-
- // Unset any invalid associations
- foreach ($associations as $tag => $id) {
- if (!$id) {
- unset($associations[$tag]);
- }
- }
-
- // Show a warning if the item isn't assigned to a language but we have associations.
- if ($associations && $table->language === '*') {
- $app->enqueueMessage(
- Text::_(strtoupper($this->option) . '_ERROR_ALL_LANGUAGE_ASSOCIATED'),
- 'warning'
- );
- }
-
- // Get associationskey for edited item
- $db = $this->getDatabase();
- $id = (int) $table->$key;
- $query = $db->createQuery()
- ->select($db->quoteName('key'))
- ->from($db->quoteName('#__associations'))
- ->where($db->quoteName('context') . ' = :context')
- ->where($db->quoteName('id') . ' = :id')
- ->bind(':context', $this->associationsContext)
- ->bind(':id', $id, ParameterType::INTEGER);
- $db->setQuery($query);
- $oldKey = $db->loadResult();
-
- if ($associations || $oldKey !== null) {
- // Deleting old associations for the associated items
- $query = $db->createQuery()
- ->delete($db->quoteName('#__associations'))
- ->where($db->quoteName('context') . ' = :context')
- ->bind(':context', $this->associationsContext);
-
- $where = [];
-
- if ($associations) {
- $where[] = $db->quoteName('id') . ' IN (' . implode(',', $query->bindArray(array_values($associations))) . ')';
- }
-
- if ($oldKey !== null) {
- $where[] = $db->quoteName('key') . ' = :oldKey';
- $query->bind(':oldKey', $oldKey);
- }
-
- $query->extendWhere('AND', $where, 'OR');
- $db->setQuery($query);
- $db->execute();
- }
-
- // Adding self to the association
- if ($table->language !== '*') {
- $associations[$table->language] = (int) $table->$key;
- }
-
- if (\count($associations) > 1) {
- // Adding new association for these items
- $key = md5(json_encode($associations));
- $query = $db->createQuery()
- ->insert($db->quoteName('#__associations'))
- ->columns(
- [
- $db->quoteName('id'),
- $db->quoteName('context'),
- $db->quoteName('key'),
- ]
- );
-
- foreach ($associations as $id) {
- $query->values(
- implode(
- ',',
- $query->bindArray(
- [$id, $this->associationsContext, $key],
- [ParameterType::INTEGER, ParameterType::STRING, ParameterType::STRING]
- )
- )
- );
- }
-
- $db->setQuery($query);
- $db->execute();
- }
- }
+ $this->storeAssociations($table, $data);
if ($this instanceof VersionableModelInterface) {
$this->saveHistory($data, $this->typeAlias);
diff --git a/libraries/src/MVC/Model/AssociationBehaviorTrait.php b/libraries/src/MVC/Model/AssociationBehaviorTrait.php
new file mode 100644
index 0000000000000..bdd80438bbdcd
--- /dev/null
+++ b/libraries/src/MVC/Model/AssociationBehaviorTrait.php
@@ -0,0 +1,412 @@
+
+ * @license GNU General Public License version 2 or later; see LICENSE.txt
+ */
+
+namespace Joomla\CMS\MVC\Model;
+
+use Joomla\CMS\Factory;
+use Joomla\CMS\Form\Form;
+use Joomla\CMS\Language\Associations;
+use Joomla\CMS\Language\LanguageHelper;
+use Joomla\CMS\Language\Text;
+use Joomla\CMS\Table\TableInterface;
+use Joomla\Database\ParameterType;
+use Joomla\Utilities\ArrayHelper;
+
+// phpcs:disable PSR1.Files.SideEffects
+\defined('_JEXEC') or die;
+// phpcs:enable PSR1.Files.SideEffects
+
+/**
+ * Trait which supports multilingual associations for an administrator model.
+ *
+ * @since __DEPLOY_VERSION__
+ */
+trait AssociationBehaviorTrait
+{
+ /**
+ * Adds the association fields to the form.
+ *
+ * @param Form $form The form to change.
+ * @param mixed $data The data of the item being edited.
+ * @param array $fieldAttributes Attributes for the per language fields, at least a "type".
+ * @param string $fieldPrefix Namespace to resolve the field type in, when it is not a core field.
+ *
+ * @return void
+ *
+ * @since __DEPLOY_VERSION__
+ */
+ public function associationPreprocessForm(Form $form, $data, array $fieldAttributes, string $fieldPrefix = '')
+ {
+ $languages = LanguageHelper::getContentLanguages(false, false, null, 'ordering', 'asc');
+
+ if (\count($languages) < 2) {
+ return;
+ }
+
+ $item = \is_object($data) ? $data : (object) (array) $data;
+ $id = (int) ($item->id ?? 0);
+
+ if (!$id) {
+ $id = (int) $this->getState($this->getName() . '.id');
+ }
+
+ if (!$id) {
+ $id = (int) Factory::getApplication()->getInput()->getInt($this->getTable()->getKeyName());
+ }
+
+ $addform = new \SimpleXMLElement('');
+
+ $outdated = $this->getAssociationOutdated($id);
+
+ if ($outdated !== null) {
+ $field = $addform->addChild('field');
+ $field->addAttribute('name', 'translation_relevance');
+ $field->addAttribute('type', 'list');
+ $field->addAttribute('fieldset', 'item_associations');
+ $field->addAttribute('label', 'JGLOBAL_ASSOCIATIONS_RELEVANCE_LABEL');
+ $field->addAttribute('description', 'JGLOBAL_ASSOCIATIONS_RELEVANCE_DESC');
+ $field->addAttribute('default', $outdated ? 'sync' : 'outdate');
+ $field->addAttribute('validate', 'options');
+
+ $option = $field->addChild('option', 'JGLOBAL_ASSOCIATIONS_RELEVANCE_OUTDATE');
+ $option->addAttribute('value', 'outdate');
+
+ $option = $field->addChild(
+ 'option',
+ $outdated ? 'JGLOBAL_ASSOCIATIONS_RELEVANCE_NONE_OUTDATED' : 'JGLOBAL_ASSOCIATIONS_RELEVANCE_NONE'
+ );
+ $option->addAttribute('value', 'none');
+
+ if ($outdated) {
+ $option = $field->addChild('option', 'JGLOBAL_ASSOCIATIONS_RELEVANCE_SYNC');
+ $option->addAttribute('value', 'sync');
+ }
+ }
+
+ $fields = $addform->addChild('fields');
+ $fields->addAttribute('name', 'associations');
+ $fieldset = $fields->addChild('fieldset');
+ $fieldset->addAttribute('name', 'item_associations');
+
+ if ($fieldPrefix !== '') {
+ $fieldset->addAttribute('addfieldprefix', $fieldPrefix);
+ }
+
+ foreach ($languages as $language) {
+ $field = $fieldset->addChild('field');
+ $field->addAttribute('name', $language->lang_code);
+ $field->addAttribute('language', $language->lang_code);
+ $field->addAttribute('label', $language->title);
+ $field->addAttribute('translate_label', 'false');
+ $field->addAttribute('select', 'true');
+ $field->addAttribute('new', 'true');
+ $field->addAttribute('edit', 'true');
+ $field->addAttribute('clear', 'true');
+ $field->addAttribute('propagate', 'true');
+
+ foreach ($fieldAttributes as $name => $value) {
+ $field->addAttribute($name, $value);
+ }
+ }
+
+ $form->load($addform, false);
+ }
+
+ /**
+ * Clears the outdated flag of the given items.
+ *
+ * @param array $pks The ids of the items to mark as up to date.
+ *
+ * @return boolean True on success.
+ *
+ * @since __DEPLOY_VERSION__
+ */
+ public function markAssociationsUpToDate($pks)
+ {
+ if (!$this->associationsContext || !Associations::isEnabled()) {
+ return true;
+ }
+
+ $pks = array_filter(ArrayHelper::toInteger((array) $pks));
+ $table = $this->getTable();
+
+ foreach ($pks as $i => $pk) {
+ $table->reset();
+
+ if (!$table->load($pk) || !$this->canEditState($table)) {
+ unset($pks[$i]);
+ Factory::getApplication()->enqueueMessage(Text::_('JLIB_APPLICATION_ERROR_EDITSTATE_NOT_PERMITTED'), 'error');
+ }
+ }
+
+ if (!$pks) {
+ return false;
+ }
+
+ $db = $this->getDatabase();
+ $query = $db->createQuery()
+ ->update($db->quoteName('#__associations'))
+ ->set($db->quoteName('outdated') . ' = 0')
+ ->where($db->quoteName('context') . ' = :context')
+ ->whereIn($db->quoteName('id'), $pks)
+ ->bind(':context', $this->associationsContext);
+
+ $db->setQuery($query);
+ $db->execute();
+
+ $this->cleanCache();
+
+ return true;
+ }
+
+ /**
+ * Writes the association group of an item and applies the translation relevance of this save.
+ *
+ * @param TableInterface $table The table of the item which has been stored.
+ * @param array $data The validated form data.
+ *
+ * @return void
+ *
+ * @since __DEPLOY_VERSION__
+ *
+ * @throws \RuntimeException
+ */
+ protected function storeAssociations(TableInterface $table, array $data)
+ {
+ if (!$this->associationsContext || !Associations::isEnabled()) {
+ return;
+ }
+
+ $db = $this->getDatabase();
+ $key = $table->getKeyName();
+ $id = (int) $table->$key;
+
+ if (!empty($data['associations'])) {
+ $associations = ArrayHelper::toInteger($data['associations']);
+
+ foreach ($associations as $tag => $assocId) {
+ if (!$assocId) {
+ unset($associations[$tag]);
+ }
+ }
+
+ // Show a warning if the item isn't assigned to a language but we have associations.
+ if ($associations && $table->language === '*') {
+ Factory::getApplication()->enqueueMessage(
+ Text::_(strtoupper($this->option) . '_ERROR_ALL_LANGUAGE_ASSOCIATED'),
+ 'warning'
+ );
+ }
+
+ $query = $db->createQuery()
+ ->select($db->quoteName('key'))
+ ->from($db->quoteName('#__associations'))
+ ->where(
+ [
+ $db->quoteName('context') . ' = :context',
+ $db->quoteName('id') . ' = :id',
+ ]
+ )
+ ->bind(':context', $this->associationsContext)
+ ->bind(':id', $id, ParameterType::INTEGER);
+ $db->setQuery($query);
+ $oldKey = $db->loadResult();
+
+ // Keep the outdated flags of the current group
+ $outdated = [];
+
+ if ($oldKey !== null) {
+ $query = $db->createQuery()
+ ->select(
+ [
+ $db->quoteName('id'),
+ $db->quoteName('outdated'),
+ ]
+ )
+ ->from($db->quoteName('#__associations'))
+ ->where($db->quoteName('context') . ' = :context')
+ ->where($db->quoteName('key') . ' = :oldKey')
+ ->bind(':context', $this->associationsContext)
+ ->bind(':oldKey', $oldKey);
+ $db->setQuery($query);
+ $outdated = $db->loadAssocList('id', 'outdated');
+ }
+
+ if ($associations || $oldKey !== null) {
+ // Deleting old associations for the associated items
+ $query = $db->createQuery()
+ ->delete($db->quoteName('#__associations'))
+ ->where($db->quoteName('context') . ' = :context')
+ ->bind(':context', $this->associationsContext);
+
+ $where = [];
+
+ if ($associations) {
+ $where[] = $db->quoteName('id') . ' IN (' . implode(',', $query->bindArray(array_values($associations))) . ')';
+ }
+
+ if ($oldKey !== null) {
+ $where[] = $db->quoteName('key') . ' = :oldKey';
+ $query->bind(':oldKey', $oldKey);
+ }
+
+ $query->extendWhere('AND', $where, 'OR');
+ $db->setQuery($query);
+ $db->execute();
+ }
+
+ // Adding self to the association
+ if ($table->language !== '*') {
+ $associations[$table->language] = $id;
+ }
+
+ if (\count($associations) > 1) {
+ $groupKey = md5(json_encode($associations));
+ $query = $db->createQuery()
+ ->insert($db->quoteName('#__associations'))
+ ->columns(
+ [
+ $db->quoteName('id'),
+ $db->quoteName('context'),
+ $db->quoteName('key'),
+ $db->quoteName('outdated'),
+ ]
+ );
+
+ foreach ($associations as $assocId) {
+ $query->values(
+ implode(
+ ',',
+ $query->bindArray(
+ [$assocId, $this->associationsContext, $groupKey, (int) ($outdated[$assocId] ?? 0)],
+ [ParameterType::INTEGER, ParameterType::STRING, ParameterType::STRING, ParameterType::INTEGER]
+ )
+ )
+ );
+ }
+
+ $db->setQuery($query);
+ $db->execute();
+ }
+ }
+
+ $this->applyTranslationRelevance($table, $data, $id);
+ }
+
+ /**
+ * Applies the translation relevance chosen on save to the association group of an item.
+ *
+ * @param TableInterface $table The table of the item which has been stored.
+ * @param array $data The validated form data.
+ * @param integer $id The id of the item which has been stored.
+ *
+ * @return void
+ *
+ * @since __DEPLOY_VERSION__
+ *
+ * @throws \RuntimeException
+ */
+ private function applyTranslationRelevance(TableInterface $table, array $data, int $id)
+ {
+ $relevance = $data['translation_relevance'] ?? '';
+
+ if (!\in_array($relevance, ['outdate', 'sync'], true) || $table->language === '*') {
+ return;
+ }
+
+ $db = $this->getDatabase();
+ $query = $db->createQuery()
+ ->select($db->quoteName('key'))
+ ->from($db->quoteName('#__associations'))
+ ->where(
+ [
+ $db->quoteName('context') . ' = :context',
+ $db->quoteName('id') . ' = :id',
+ ]
+ )
+ ->bind(':context', $this->associationsContext)
+ ->bind(':id', $id, ParameterType::INTEGER);
+ $db->setQuery($query);
+ $groupKey = $db->loadResult();
+
+ if ($groupKey === null) {
+ return;
+ }
+
+ $query = $db->createQuery()
+ ->update($db->quoteName('#__associations'))
+ ->set($db->quoteName('outdated') . ' = 0')
+ ->where(
+ [
+ $db->quoteName('context') . ' = :context',
+ $db->quoteName('key') . ' = :groupKey',
+ $db->quoteName('id') . ' = :id',
+ ]
+ )
+ ->bind(':context', $this->associationsContext)
+ ->bind(':groupKey', $groupKey)
+ ->bind(':id', $id, ParameterType::INTEGER);
+ $db->setQuery($query);
+ $db->execute();
+
+ if ($relevance !== 'outdate') {
+ return;
+ }
+
+ $query = $db->createQuery()
+ ->update($db->quoteName('#__associations'))
+ ->set($db->quoteName('outdated') . ' = 1')
+ ->where(
+ [
+ $db->quoteName('context') . ' = :context',
+ $db->quoteName('key') . ' = :groupKey',
+ $db->quoteName('id') . ' != :id',
+ ]
+ )
+ ->bind(':context', $this->associationsContext)
+ ->bind(':groupKey', $groupKey)
+ ->bind(':id', $id, ParameterType::INTEGER);
+ $db->setQuery($query);
+ $db->execute();
+ }
+
+ /**
+ * Returns the outdated state of an item, or null when the item has no associations.
+ *
+ * @param integer $id The id of the item.
+ *
+ * @return integer|null
+ *
+ * @since __DEPLOY_VERSION__
+ */
+ private function getAssociationOutdated(int $id): ?int
+ {
+ if (!$id || !$this->associationsContext) {
+ return null;
+ }
+
+ $db = $this->getDatabase();
+ $query = $db->createQuery()
+ ->select($db->quoteName('outdated'))
+ ->from($db->quoteName('#__associations'))
+ ->where(
+ [
+ $db->quoteName('context') . ' = :context',
+ $db->quoteName('id') . ' = :id',
+ ]
+ )
+ ->bind(':context', $this->associationsContext)
+ ->bind(':id', $id, ParameterType::INTEGER);
+ $db->setQuery($query);
+
+ $outdated = $db->loadResult();
+
+ return $outdated === null ? null : (int) $outdated;
+ }
+}
diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon
index 418dc86a677e4..a05116992e377 100644
--- a/phpstan-baseline.neon
+++ b/phpstan-baseline.neon
@@ -3427,7 +3427,7 @@ parameters:
Throw an Exception instead of using setError$#
'''
identifier: method.deprecated
- count: 35
+ count: 34
path: administrator/components/com_menus/src/Model/ItemModel.php
-
diff --git a/plugins/sampledata/multilang/src/Extension/MultiLanguage.php b/plugins/sampledata/multilang/src/Extension/MultiLanguage.php
index 5f42d3864e6bd..f34599a6b329d 100644
--- a/plugins/sampledata/multilang/src/Extension/MultiLanguage.php
+++ b/plugins/sampledata/multilang/src/Extension/MultiLanguage.php
@@ -937,11 +937,13 @@ private function addAssociations($groupedAssociations)
$id,
$context,
$key,
+ 0,
],
[
ParameterType::INTEGER,
ParameterType::STRING,
ParameterType::STRING,
+ ParameterType::INTEGER,
]
)
)