diff --git a/administrator/components/com_admin/sql/updates/mysql/4.0.0-2019-07-02.sql b/administrator/components/com_admin/sql/updates/mysql/4.0.0-2019-07-02.sql new file mode 100644 index 0000000000000..41ab9cb4807aa --- /dev/null +++ b/administrator/components/com_admin/sql/updates/mysql/4.0.0-2019-07-02.sql @@ -0,0 +1,2 @@ +ALTER TABLE `#__associations` ADD COLUMN `parent_id` int(11) NOT NULL DEFAULT -1 COMMENT 'The parent of an association.'; +ALTER TABLE `#__associations` ADD COLUMN `parent_date` datetime COMMENT 'The save or modified date of the parent.'; diff --git a/administrator/components/com_admin/sql/updates/postgresql/4.0.0-2019-07-02.sql b/administrator/components/com_admin/sql/updates/postgresql/4.0.0-2019-07-02.sql new file mode 100644 index 0000000000000..25fc0fd66f80a --- /dev/null +++ b/administrator/components/com_admin/sql/updates/postgresql/4.0.0-2019-07-02.sql @@ -0,0 +1,4 @@ +ALTER TABLE "#__associations" ADD COLUMN "parent_id" integer DEFAULT -1 NOT NULL; +ALTER TABLE "#__associations" ADD COLUMN "parent_date" timestamp without time zone; +COMMENT ON COLUMN "#__associations"."parent_id" IS 'The parent of an association.'; +COMMENT ON COLUMN "#__associations"."parent_date" IS 'The save or modified date of the parent.'; diff --git a/administrator/components/com_associations/Controller/AssociationsController.php b/administrator/components/com_associations/Controller/AssociationsController.php index f5c362d5a3f01..f4e98420b8abd 100644 --- a/administrator/components/com_associations/Controller/AssociationsController.php +++ b/administrator/components/com_associations/Controller/AssociationsController.php @@ -49,7 +49,7 @@ public function getModel($name = 'Associations', $prefix = 'Administrator', $con } /** - * Method to purge the associations table. + * Method to purge the associations table by context. * * @return void * @@ -57,7 +57,8 @@ public function getModel($name = 'Associations', $prefix = 'Administrator', $con */ public function purge() { - $this->getModel('associations')->purge(); + $context = $this->input->getString('itemtype'); + $this->getModel('associations')->purge($context); $this->setRedirect(Route::_('index.php?option=' . $this->option . '&view=' . $this->view_list, false)); } diff --git a/administrator/components/com_associations/Controller/DefaultAssocLangController.php b/administrator/components/com_associations/Controller/DefaultAssocLangController.php new file mode 100644 index 0000000000000..843ef07ed4a37 --- /dev/null +++ b/administrator/components/com_associations/Controller/DefaultAssocLangController.php @@ -0,0 +1,41 @@ +input->get('targetId', '', 'int'); + $parentId = $this->input->get('id', '', 'int'); + $itemtype = $this->input->get('itemtype', '', 'string'); + + $this->getModel('defaultAssocLang')->update($targetId, $parentId, $itemtype); + + $this->setRedirect(Route::_('index.php?option=com_associations&view=associations', false)); + } +} diff --git a/administrator/components/com_associations/Field/ContentdefaultassoclangField.php b/administrator/components/com_associations/Field/ContentdefaultassoclangField.php new file mode 100644 index 0000000000000..ebd884b414126 --- /dev/null +++ b/administrator/components/com_associations/Field/ContentdefaultassoclangField.php @@ -0,0 +1,68 @@ +lang_code == $defaultAssocLang) + { + $options[] = HTMLHelper::_('select.option', $langCode->lang_code, $langCode->title . ' - ' . Text::_('JGLOBAL_ASSOCIATIONS_DEFAULT_ASSOC_LANG')); + } + else + { + $options[] = HTMLHelper::_('select.option', $langCode->lang_code, $langCode->title); + } + } + + return $options; + } +} diff --git a/administrator/components/com_associations/Field/ItemlanguageField.php b/administrator/components/com_associations/Field/ItemlanguageField.php index 3f23450b9c8b9..903e2fc6f6c06 100644 --- a/administrator/components/com_associations/Field/ItemlanguageField.php +++ b/administrator/components/com_associations/Field/ItemlanguageField.php @@ -13,7 +13,9 @@ use Joomla\CMS\Factory; use Joomla\CMS\Form\Field\ListField; +use Joomla\CMS\Language\Associations; use Joomla\CMS\Language\LanguageHelper; +use Joomla\CMS\Language\Text; use Joomla\Component\Associations\Administrator\Helper\AssociationsHelper; use Joomla\Utilities\ArrayHelper; @@ -62,6 +64,10 @@ protected function getOptions() // Gets existing languages. $existingLanguages = LanguageHelper::getContentLanguages(array(0, 1)); + // Get default association language and check if reference is a parent. + $defaultAssocLang = Associations::getDefaultAssocLang(); + $refIsParent = $referenceLang === $defaultAssocLang; + $options = array(); // Each option has the format "|", example: "en-GB|1" @@ -73,9 +79,23 @@ protected function getOptions() continue; } + if ($defaultAssocLang && !$refIsParent) + { + // If reference is a child then display just the default association language + if ($language->lang_code !== $defaultAssocLang) + { + continue; + } + } + $options[$langCode] = new \stdClass; $options[$langCode]->text = $language->title; + if ($defaultAssocLang && ($language->lang_code === $defaultAssocLang)) + { + $options[$langCode]->text .= ' - ' . Text::_('JGLOBAL_ASSOCIATIONS_DEFAULT_ASSOC_LANG'); + } + // If association exists in this language. if (isset($associations[$language->lang_code])) { diff --git a/administrator/components/com_associations/Helper/AssociationsHelper.php b/administrator/components/com_associations/Helper/AssociationsHelper.php index ecdcc480311d4..424d6579efea8 100644 --- a/administrator/components/com_associations/Helper/AssociationsHelper.php +++ b/administrator/components/com_associations/Helper/AssociationsHelper.php @@ -13,8 +13,10 @@ use Joomla\CMS\Association\AssociationExtensionInterface; use Joomla\CMS\Association\AssociationServiceInterface; +use Joomla\CMS\Component\ComponentHelper; use Joomla\CMS\Factory; use Joomla\CMS\Helper\ContentHelper; +use Joomla\CMS\Language\Associations; use Joomla\CMS\Language\LanguageHelper; use Joomla\CMS\Language\Text; use Joomla\CMS\Layout\LayoutHelper; @@ -209,15 +211,18 @@ private static function getExtensionRealName($extensionName) * @param string $itemLanguage Item language code. * @param boolean $addLink True for adding edit links. False for just text. * @param boolean $assocLanguages True for showing non associated content languages. False only languages with associations. + * @param string $assocState The filter association state, enabled when a default association language is used. * * @return string The language HTML * * @since 3.7.0 */ - public static function getAssociationHtmlList($extensionName, $typeName, $itemId, $itemLanguage, $addLink = true, $assocLanguages = true) + public static function getAssociationHtmlList($extensionName, $typeName, $itemId, $itemLanguage, + $addLink = true, $assocLanguages = true, $assocState = 'all' + ) { // Get the associations list for this item. - $items = self::getAssociationList($extensionName, $typeName, $itemId); + $items = self::getAssociationList($extensionName, $typeName, $itemId); $titleFieldName = self::getTypeFieldName($extensionName, $typeName, 'title'); @@ -227,33 +232,64 @@ public static function getAssociationHtmlList($extensionName, $typeName, $itemId $canEditReference = self::allowEdit($extensionName, $typeName, $itemId); $canCreate = self::allowAdd($extensionName, $typeName); + // Get the default association language, empty if not used + $defaultAssocLang = Associations::getDefaultAssocLang(); + + // Check if versions are enabled + $saveHistory = ComponentHelper::getParams($extensionName)->get('save_history', 0); + $context = ($typeName === 'category') ? 'com_categories.item' : $extensionName . '.item'; + + if ($items) + { + // Get parent dates of each item of an association and the parent id. + $assocParentDates = DefaultAssocLangHelper::getParentDates($items, $context); + $parentId = $items[$defaultAssocLang]['id'] ?? null; + } + // Create associated items list. foreach ($languages as $langCode => $language) { - // Don't do for the reference language. - if ($langCode == $itemLanguage) - { - continue; - } + // Defaults + $update = false; + $parentChildInfo = ''; - // Don't show languages with associations, if we don't want to show them. - if ($assocLanguages && isset($items[$langCode])) + if (!$defaultAssocLang) { - unset($items[$langCode]); - continue; - } + // Don't do for the reference language. + if ($langCode == $itemLanguage) + { + continue; + } - // Don't show languages without associations, if we don't want to show them. - if (!$assocLanguages && !isset($items[$langCode])) - { - continue; + // Don't show languages with associations, if we don't want to show them. + if ($assocLanguages && isset($items[$langCode])) + { + unset($items[$langCode]); + continue; + } + + // Don't show languages without associations, if we don't want to show them. + if (!$assocLanguages && !isset($items[$langCode])) + { + continue; + } } - // Get html parameters. + // Get html parameters for associated items. if (isset($items[$langCode])) { - $title = $items[$langCode][$titleFieldName]; - $additional = ''; + if ($defaultAssocLang) + { + // Don't display any other children to the child item. + if (($itemLanguage !== $defaultAssocLang) && ($langCode !== $defaultAssocLang) && ($langCode !== $itemLanguage)) + { + unset($items[$langCode]); + continue; + } + } + + $title = $items[$langCode][$titleFieldName]; + $additional = ''; if (isset($items[$langCode]['catid'])) { @@ -286,33 +322,150 @@ public static function getAssociationHtmlList($extensionName, $typeName, $itemId $additional = '' . Text::sprintf('COM_MENUS_MENU_SPRINTF', $menutype_title) . '
'; } - $labelClass = 'badge-secondary'; + $labelClass = 'badge-success'; $target = $langCode . ':' . $items[$langCode]['id'] . ':edit'; $allow = $canEditReference && self::allowEdit($extensionName, $typeName, $items[$langCode]['id']) && self::canCheckinItem($extensionName, $typeName, $items[$langCode]['id']); - $additional .= $addLink && $allow ? Text::_('COM_ASSOCIATIONS_EDIT_ASSOCIATION') : ''; + $parentChildInfoSpace = $additional && !$addLink ? '
' : '

'; + + if ($defaultAssocLang) + { + // Settings for the default association language. + if ($defaultAssocLang === $langCode) + { + $labelClass .= ' parent-item'; + $additional .= $addLink && $allow ? Text::_('COM_ASSOCIATIONS_EDIT_ASSOCIATION') : ''; + $parentChildInfo = $parentChildInfoSpace . Text::_('JGLOBAL_ASSOCIATIONS_DEFAULT_ASSOC_LANG_ITEM'); + + if ($defaultAssocLang === $itemLanguage) + { + // Do not define any child target as there can be more than one + $target = ''; + } + else + { + $target = $itemLanguage . ':' . $itemId . ':edit'; + } + } + // Setting for children + else + { + // When there is no associated parent, set it to target + if (!$parentId) + { + $target = $defaultAssocLang . ':0:add'; + } + + if (array_key_exists($items[$langCode]['id'], $assocParentDates) && array_key_exists($parentId, $assocParentDates)) + { + $associatedModifiedParent = $assocParentDates[$items[$langCode]['id']][0]; + $lastModifiedParent = $assocParentDates[$parentId][0]; + + if ($associatedModifiedParent < $lastModifiedParent) + { + // Don't display not corresponding item + if ($assocState !== 'all' && $assocState !== 'out_of_date') + { + unset($items[$langCode]); + continue; + } + + $additional .= $addLink && $allow ? Text::_('COM_ASSOCIATIONS_UPDATE_ASSOCIATION') : ''; + $labelClass = 'badge-warning'; + $target = $langCode . ':' . $items[$langCode]['id'] . ':edit'; + $update = true; + + /* + When versions are disabled then the modified date is used for the parent. + That means that when no changes were made and the parent has been saved the modified date has been changed. + So the out-of-date state means in that case there might have been made changes and it is necessary to check manually and update the target. + */ + $parentChildInfo = $saveHistory + ? $parentChildInfoSpace . Text::_('JGLOBAL_ASSOCIATIONS_STATE_OUT_OF_DATE_DESC') + : $parentChildInfoSpace . Text::_('JGLOBAL_ASSOCIATIONS_STATE_MIGHT_BE_OUT_OF_DATE_DESC'); + } + else + { + // Don't display not corresponding item + if ($assocState !== 'all' && $assocState !== 'up_to_date') + { + unset($items[$langCode]); + continue; + } + + $additional .= $addLink && $allow ? Text::_('COM_ASSOCIATIONS_EDIT_ASSOCIATION') : ''; + $parentChildInfo = $parentChildInfoSpace . Text::_('JGLOBAL_ASSOCIATIONS_STATE_UP_TO_DATE_DESC'); + + // For item types that do not use modified date or versions like menu items + if (!$associatedModifiedParent) + { + $parentChildInfo = ''; + } + } + } + } + } + else + { + $additional .= $addLink && $allow ? Text::_('COM_ASSOCIATIONS_EDIT_ASSOCIATION') : ''; + } } + // Get html parameters for not associated items else { + // Don't display any other children to the child item + if ($defaultAssocLang && ($itemLanguage != $defaultAssocLang) + && ($langCode != $itemLanguage) + && ($langCode != $defaultAssocLang)) + { + continue; + } + $items[$langCode] = array(); $title = Text::_('COM_ASSOCIATIONS_NO_ASSOCIATION'); $additional = $addLink ? Text::_('COM_ASSOCIATIONS_ADD_NEW_ASSOCIATION') : ''; - $labelClass = 'badge-warning'; + $labelClass = 'badge-secondary'; $target = $langCode . ':0:add'; $allow = $canCreate; + + if ($defaultAssocLang) + { + if ($defaultAssocLang === $langCode) + { + $labelClass .= ' parent-item'; + $parentChildInfoSpace = $addLink ? '

' : ''; + $parentChildInfo = $parentChildInfoSpace . Text::_('JGLOBAL_ASSOCIATIONS_DEFAULT_ASSOC_LANG_ITEM'); + $target = ''; + } + else + { + // Don't display not corresponding item + if ($assocState !== 'all' && $assocState !== 'not_associated') + { + unset($items[$langCode]); + continue; + } + } + + // Change target, when there is no association with the default association language for the child item + if ($defaultAssocLang !== $itemLanguage) + { + $target = $defaultAssocLang . ':0:add'; + } + } } // Generate item Html. $options = array( 'option' => 'com_associations', 'view' => 'association', - 'layout' => 'edit', + 'layout' => $update ? 'update' : 'edit', 'itemtype' => $extensionName . '.' . $typeName, 'task' => 'association.edit', - 'id' => $itemId, + 'id' => $parentId ?? $itemId, 'target' => $target, ); @@ -321,11 +474,18 @@ public static function getAssociationHtmlList($extensionName, $typeName, $itemId $text = strtoupper($language->sef); $tooltip = '' . htmlspecialchars($language->title, ENT_QUOTES, 'UTF-8') . '
' - . htmlspecialchars($title, ENT_QUOTES, 'UTF-8') . '

' . $additional; + . htmlspecialchars($title, ENT_QUOTES, 'UTF-8') . '

' . $additional . $parentChildInfo; $classes = 'badge ' . $labelClass; $items[$langCode]['link'] = '' . $text . '' . '
' . $tooltip . '
'; + + // Reorder the array, so the parent gets to the first place + if ($langCode === $defaultAssocLang) + { + $items = array('parent' => $items[$langCode]) + $items; + unset($items[$langCode]); + } } return LayoutHelper::render('joomla.content.associations', $items); diff --git a/administrator/components/com_associations/Helper/DefaultAssocLangHelper.php b/administrator/components/com_associations/Helper/DefaultAssocLangHelper.php new file mode 100644 index 0000000000000..958ffcb217cb8 --- /dev/null +++ b/administrator/components/com_associations/Helper/DefaultAssocLangHelper.php @@ -0,0 +1,314 @@ +getQuery(true) + ->select($db->quoteName(['title', 'sef'])) + ->from($db->quoteName('#__languages')) + ->where($db->quoteName('lang_code') . ' = :lang_code') + ->bind(':lang_code', $defaultAssocLang); + $db->setQuery($query); + $defaultAssocLangInfos = $db->loadAssoc(); + + $classes = 'badge badge-secondary'; + $parentChildInfo = '

' . Text::_('JGLOBAL_ASSOCIATIONS_DEFAULT_ASSOC_LANG_ITEM'); + $text = $defaultAssocLangInfos['sef'] ? strtoupper($defaultAssocLangInfos['sef']) : 'XX'; + $title = Text::_('JGLOBAL_ASSOCIATIONS_STATE_NOT_ASSOCIATED_DESC'); + $url = Route::_(self::getAssociationUrl($itemId, $defaultAssocLang, $itemType)); + + $tooltip = '' . htmlspecialchars($defaultAssocLangInfos['title'], ENT_QUOTES, 'UTF-8') . '
' + . htmlspecialchars($title, ENT_QUOTES, 'UTF-8') . $parentChildInfo; + + $link = '' . $text . '' + . ''; + + return $link; + } + + /** + * Method to get parent dates of each item of an association + * + * @param array $associations the associations to be saved + * @param string $context the association context + * + * @return array association with parent dates + */ + public static function getParentDates($associations, $context) + { + $parentDates = []; + $db = Factory::getDbo(); + + foreach ($associations as $langCode => $id) + { + if (is_array($id)) + { + $id = $id['id']; + } + + $query = $db->getQuery(true) + ->select($db->quoteName(['parent_date', 'key'])) + ->from($db->quoteName('#__associations')) + ->where( + [ + $db->quoteName('id') . ' = :id', + $db->quoteName('context') . ' = :context' + ] + ) + ->bind(':id', $id, ParameterType::INTEGER) + ->bind(':context', $context); + $db->setQuery($query); + $parentDates[$id] = $db->loadRow(); + } + + return $parentDates; + } + + /** + * Method to get parent_id and parent_date for an association going to be saved. + * + * @param integer $id Item id + * @param integer $dataId Item id of an item that is going to be saved + * @param integer $parentId Id of the associated parent + * @param string $parentModified The latest modified date of the parent + * @param array $assocParentDates Parents modified date of an associated item + * @param string $old_key The old association key to check if it is a new association + * + * @return array parent id and parent dates for an associated item + */ + public static function getParentValues($id, $dataId, $parentId, $parentModified, $assocParentDates, $old_key) + { + if ($parentId) + { + // For the parent + if ($parentId === $id) + { + $parentIdValue = 0; + + // Set always the last modified date + $parentDateValue = $parentModified ?? 'NULL'; + } + + // For the children + else + { + $parentIdValue = $parentId; + + // If modified date isn't set to the child item, set current modified date from parent OR if child is added from another association + $parentDateValue = ( + empty($assocParentDates[$id][0]) + || ($assocParentDates[$id][1] !== $old_key) + || ($assocParentDates[$parentId][1] !== $assocParentDates[$id][1]) + ) + ? $parentModified + : $assocParentDates[$id][0]; + + if (!$old_key && ($dataId !== $id)) + { + // Add modified date from parent to new associated item + $parentDateValue = $parentModified ?? 'NULL'; + } + } + } + else + { + // Default values when there is no associated parent. + $parentIdValue = -1; + $parentDateValue = 'NULL'; + } + + return [(int) $parentIdValue, $parentDateValue]; + } + + /** + * Method to get the latest modified date of a parent + * + * @param integer $parentId Id of the associated parent + * @param string $tableName The name of the table + * @param string $typeAlias Alias for the content type + * + * @return string The modified date of the parent + */ + public static function getParentModifiedDate($parentId, $tableName, $typeAlias) + { + // Check if the content version is enabled + $aliasParts = explode('.', $typeAlias); + $saveHistory = ComponentHelper::getParams($aliasParts[0])->get('save_history', 0); + $contentTypeTable = Table::getInstance('ContentType'); + $contentTypeTblName = $contentTypeTable->getTableName(); + $typeId = $contentTypeTable->getTypeId($typeAlias); + + $db = Factory::getDbo(); + $fieldMapsQuery = $db->getQuery(true) + ->select($db->quoteName('field_mappings')) + ->from($db->quoteName($contentTypeTblName)) + ->where($db->quoteName('type_id') . ' = :type_id') + ->bind(':type_id', $typeId, ParameterType::INTEGER); + $db->setQuery($fieldMapsQuery); + $fieldMaps = $db->loadResult(); + + $modifiedColumn = json_decode($fieldMaps)->common->core_modified_time; + + if ($parentId) + { + // If versions are enabled get the save_date of the parent from history table + if ($saveHistory) + { + $parentHistory = ContentHistoryHelper::getHistory($typeId, $parentId); + + // Latest saved date of the parent + $parentModified = $parentHistory[0]->save_date; + } + else + { + $parentDateQuery = $db->getQuery(true) + ->select($db->quoteName($modifiedColumn)) + ->from($db->quoteName($tableName)) + ->where($db->quoteName('id') . ' = :id') + ->bind(':id', $parentId, ParameterType::INTEGER); + $db->setQuery($parentDateQuery); + $parentModified = $db->loadResult(); + } + } + + return $parentModified ?? ''; + } + + /** + * Method to set class name and information about the association state or the parent. + * + * @param integer $itemId Item id + * @param array $items The associated items for the item with the itemId + * @param integer $key The current key from $items that is currently going through the foreach loop + * @param array $item The current value from $items that is currently going through the foreach loop + * @param string $defaultAssocLang The default association language + * @param boolean $isParent If the item with $itemId is a parent + * @param integer $parentId Id of the associated parent + * @param array $assocParentDates Parent Dates of each associated item + * @param boolean $saveHistory If Versions are enabled or not + * + * @return array the className and parentInfo for the association state, the array $items back and boolean if item needs update. + */ + public static function setParentAndChildInfos($itemId, $items, $key, $item, $defaultAssocLang, $isParent, $parentId, $assocParentDates, $saveHistory) + { + + $addClass = 'badge-success'; + $parentInfo = ''; + $update = false; + + // Don't display other children if the current item is a child. + if (($key !== $itemId) && ($defaultAssocLang !== $item->lang_code) && !$isParent) + { + unset($items[$key]); + } + + if ($key === $parentId) + { + $addClass .= ' parent-item'; + $parentInfo = '

' . Text::_('JGLOBAL_ASSOCIATIONS_DEFAULT_ASSOC_LANG_ITEM'); + } + else + { + // Get association state of child when a parent item exists + if ($parentId && (array_key_exists($key, $assocParentDates)) && (array_key_exists($parentId, $assocParentDates))) + { + $associatedModifiedParent = $assocParentDates[$key][0]; + $lastModifiedParent = $assocParentDates[$parentId][0]; + + if ($associatedModifiedParent < $lastModifiedParent) + { + $update = true; + $addClass = 'badge-warning'; + $parentInfo = $saveHistory + ? '

' . Text::_('JGLOBAL_ASSOCIATIONS_STATE_OUT_OF_DATE_DESC') + : '

' . Text::_('JGLOBAL_ASSOCIATIONS_STATE_MIGHT_BE_OUT_OF_DATE_DESC'); + } + else + { + $addClass = 'badge-success'; + $parentInfo = '

' . Text::_('JGLOBAL_ASSOCIATIONS_STATE_UP_TO_DATE_DESC'); + } + } + } + + return [$addClass, $parentInfo, $items, $update]; + } + + /** + * Method to get the association url for an item + * + * @param integer $itemId The item id + * @param string $defaultAssocLang The default association language + * @param string $itemType The item type + * @param string $itemLang The current value from $items that is currently going through the foreach loop + * @param integer $key The current key from $items that is currently going through the foreach loop + * @param integer $parentId Id of the associated parent + * @param boolean $needsUpdate If the item needs an update or not + * + * @return string + */ + public static function getAssociationUrl($itemId, $defaultAssocLang, $itemType, $itemLang = '', $key = '', $parentId = '', $needsUpdate = false) + { + $target = ''; + + if (empty($parentId)) + { + $target = $defaultAssocLang . ':0:add'; + } + elseif ($key !== $parentId) + { + $target = $itemLang . ':' . $itemId . ':edit'; + } + + // Generate item Html. + $options = array( + 'option' => 'com_associations', + 'view' => 'association', + 'layout' => $needsUpdate ? 'update' : 'edit', + 'itemtype' => $itemType, + 'task' => 'association.edit', + 'id' => empty($parentId) ? $itemId : $parentId, + 'target' => $target, + ); + + $url = 'index.php?' . http_build_query($options); + + return $url; + } +} diff --git a/administrator/components/com_associations/Model/AssociationModel.php b/administrator/components/com_associations/Model/AssociationModel.php index 8ed1685a28658..881ed5b9cffe1 100644 --- a/administrator/components/com_associations/Model/AssociationModel.php +++ b/administrator/components/com_associations/Model/AssociationModel.php @@ -11,7 +11,9 @@ defined('_JEXEC') or die; +use Joomla\CMS\Factory; use Joomla\CMS\MVC\Model\ListModel; +use Joomla\Database\ParameterType; /** * Methods supporting a list of article records. @@ -37,4 +39,84 @@ public function getForm($data = array(), $loadData = true) return !empty($form) ? $form : false; } + + /** + * Method to get the history version ids of a parent. + * + * @param integer $parentId Id of the parent + * @param integer $targetId Id of a child + * @param string $extensionName The extension name with com_ + * @param string $typeName The item type + * @param integer $typeId the content type id + * + * @return array Array containing two version history ids + */ + public function getParentCompareValues($parentId, $targetId, $extensionName, $typeName, $typeId) + { + + $context = ($typeName === 'category') + ? 'com_categories.item' + : $extensionName . '.item'; + + $db = Factory::getDbo(); + $parentQuery = $db->getQuery(true) + ->select($db->quoteName('parent_date')) + ->from($db->quoteName('#__associations')) + ->where( + [ + $db->quoteName('id') . ' = :id', + $db->quoteName('context') . ' = :context' + ] + ) + ->bind(':id', $parentId, ParameterType::INTEGER) + ->bind(':context', $context); + $latestParentDate = $db->setQuery($parentQuery)->loadResult(); + + $latestVersionQuery = $db->getQuery(true) + ->select($db->quoteName('version_id')) + ->from($db->quoteName('#__ucm_history')) + ->where( + [ + $db->quoteName('ucm_item_id') . ' = :ucm_item_id', + $db->quoteName('ucm_type_id') . ' = :ucm_type_id', + $db->quoteName('save_date') . ' = :save_date' + ] + ) + ->bind(':ucm_item_id', $parentId, ParameterType::INTEGER) + ->bind(':ucm_type_id', $typeId, ParameterType::INTEGER) + ->bind(':save_date', $latestParentDate); + $latestVersionId = $db->setQuery($latestVersionQuery)->loadResult(); + + $childQuery = $db->getQuery(true) + ->select($db->quoteName('parent_date')) + ->from($db->quoteName('#__associations')) + ->where( + [ + $db->quoteName('id') . ' = :id', + $db->quoteName('parent_id') . ' = :parent_id', + $db->quoteName('context') . ' = :context' + ] + ) + ->bind(':id', $targetId, ParameterType::INTEGER) + ->bind(':parent_id', $parentId, ParameterType::INTEGER) + ->bind(':context', $context); + $childParentDate = $db->setQuery($childQuery)->loadResult(); + + $olderVersionQuery = $db->getQuery(true) + ->select($db->quoteName('version_id')) + ->from($db->quoteName('#__ucm_history')) + ->where( + [ + $db->quoteName('ucm_item_id') . ' = :ucm_item_id', + $db->quoteName('ucm_type_id') . ' = :ucm_type_id', + $db->quoteName('save_date') . ' = :save_date' + ] + ) + ->bind(':ucm_item_id', $parentId, ParameterType::INTEGER) + ->bind(':ucm_type_id', $typeId, ParameterType::INTEGER) + ->bind(':save_date', $childParentDate); + $olderVersionId = $db->setQuery($olderVersionQuery)->loadResult(); + + return [$latestVersionId, $olderVersionId]; + } } diff --git a/administrator/components/com_associations/Model/AssociationsModel.php b/administrator/components/com_associations/Model/AssociationsModel.php index d4e0d2379fb5d..31a3c3a8cdbb5 100644 --- a/administrator/components/com_associations/Model/AssociationsModel.php +++ b/administrator/components/com_associations/Model/AssociationsModel.php @@ -11,13 +11,17 @@ defined('_JEXEC') or die; +use Joomla\CMS\Component\ComponentHelper; use Joomla\CMS\Factory; +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\ListModel; use Joomla\CMS\Table\Table; use Joomla\Component\Associations\Administrator\Helper\AssociationsHelper; use Joomla\Database\Exception\ExecutionFailureException; +use Joomla\Database\ParameterType; /** * Methods supporting a list of article records. @@ -45,6 +49,7 @@ public function __construct($config = array(), MVCFactoryInterface $factory = nu 'ordering', 'itemtype', 'language', + 'assocstate', 'association', 'menutype', 'menutype_title', @@ -79,6 +84,13 @@ protected function populateState($ordering = 'ordering', $direction = 'asc') $forcedLanguage = $app->input->get('forcedLanguage', '', 'cmd'); $forcedItemType = $app->input->get('forcedItemType', '', 'string'); + // Set language select box to default site language or if set to, to the default association language as default. + $defaultAssocLang = Associations::getDefaultAssocLang(); + $langParam = ComponentHelper::getParams('com_languages'); + + $defaultLanguage = empty($defaultAssocLang) ? $langParam->get('site') : $defaultAssocLang; + $defaultItemType = 'com_content.article'; + // Adjust the context to support modal layouts. if ($layout = $app->input->get('layout')) { @@ -97,9 +109,9 @@ protected function populateState($ordering = 'ordering', $direction = 'asc') $this->context .= '.' . $forcedItemType; } - $this->setState('itemtype', $this->getUserStateFromRequest($this->context . '.itemtype', 'itemtype', '', 'string')); - $this->setState('language', $this->getUserStateFromRequest($this->context . '.language', 'language', '', 'string')); - + $this->setState('itemtype', $this->getUserStateFromRequest($this->context . '.itemtype', 'itemtype', $defaultItemType, 'string')); + $this->setState('language', $this->getUserStateFromRequest($this->context . '.language', 'language', $defaultLanguage, 'string')); + $this->setState('assocstate', $this->getUserStateFromRequest($this->context . '.assocstate', 'assocstate', 'all', 'string')); $this->setState('filter.search', $this->getUserStateFromRequest($this->context . '.filter.search', 'filter_search', '', 'string')); $this->setState('filter.state', $this->getUserStateFromRequest($this->context . '.filter.state', 'filter_state', '', 'cmd')); $this->setState('filter.category_id', $this->getUserStateFromRequest($this->context . '.filter.category_id', 'filter_category_id', '', 'cmd')); @@ -141,6 +153,7 @@ protected function getStoreId($id = '') // Compile the store id. $id .= ':' . $this->getState('itemtype'); $id .= ':' . $this->getState('language'); + $id .= ':' . $this->getState('assocstate'); $id .= ':' . $this->getState('filter.search'); $id .= ':' . $this->getState('filter.state'); $id .= ':' . $this->getState('filter.category_id'); @@ -166,6 +179,7 @@ protected function getListQuery() $extension = AssociationsHelper::getSupportedExtension($extensionName); $types = $extension->get('types'); + $assocContextName = ($typeName === 'category') ? 'com_categories.item' : $extensionName . '.item'; if (array_key_exists($typeName, $types)) { @@ -235,12 +249,12 @@ protected function getListQuery() // Join over the associations. $query->select('COUNT(' . $db->quoteName('asso2.id') . ') > 1 AS ' . $db->quoteName('association')) - ->join( - 'LEFT', + ->leftJoin( $db->quoteName('#__associations', 'asso') . ' ON ' . $db->quoteName('asso.id') . ' = ' . $db->quoteName($fields['id']) - . ' AND ' . $db->quoteName('asso.context') . ' = ' . $db->quote($extensionName . '.item') + . ' AND ' . $db->quoteName('asso.context') . ' = :context' ) - ->join('LEFT', $db->quoteName('#__associations', 'asso2') . ' ON ' . $db->quoteName('asso2.key') . ' = ' . $db->quoteName('asso.key')); + ->leftJoin($db->quoteName('#__associations', 'asso2') . ' ON ' . $db->quoteName('asso2.key') . ' = ' . $db->quoteName('asso.key')) + ->bind(':context', $assocContextName); // Prepare the group by clause. $groupby = array( @@ -432,6 +446,60 @@ protected function getListQuery() } } + // Filter by association state + $assocStateField = $this->state->get('assocstate'); + + if ($assocStateField !== 'all') + { + // Not associated + if ($assocStateField === 'not_associated') + { + $countContentLanguages = count(LanguageHelper::getContentLanguages(array(0, 1))); + + // Get all keys where not all languages are associated. + $assocQuery = $db->getQuery(true) + ->select($db->quoteName('key')) + ->from($db->quoteName('#__associations')) + ->group($db->quoteName('key')) + ->having('COUNT(*) < :count'); + + // Join over associations where id does not exist + $query->where('((' . $db->quoteName('asso.id') . ' IS NULL )' + // Or if we are on the child language and there is no parent + . ' OR ( ' . $db->quoteName('asso2.parent_id') . ' = -1)' + // Or a child of the parent does not exist. + . ' OR ( ' . $db->quoteName('asso.key') . ' IN (' . $assocQuery . ') + AND ' . $db->quoteName('asso.parent_id') . ' = 0)' + . ')' + ) + ->bind(':count', $countContentLanguages, ParameterType::INTEGER); + } + + // Out-of-date + if ($assocStateField === 'out_of_date') + { + // If we are on the parent language and we check the state of the children + $query->where('((' . $db->quoteName('asso2.parent_id') . ' = ' . $db->quoteName('asso.id') + . ' AND ' . $db->quoteName('asso2.parent_date') . ' < ' . $db->quoteName('asso.parent_date') . ')' + // Or we are on the child language and we check its state comparing to its parent. + . ' OR (' . $db->quoteName('asso.parent_date') . ' < ' . $db->quoteName('asso2.parent_date') + . ' AND ' . $db->quoteName('asso2.id') . ' = ' . $db->quoteName('asso.parent_id') . '))' + ); + } + + // Up-to-date + if ($assocStateField === 'up_to_date') + { + // If we are on the parent language and we check the state of the children + $query->where('((' . $db->quoteName('asso2.parent_id') . ' = ' . $db->quoteName('asso.id') + . ' AND ' . $db->quoteName('asso2.parent_date') . ' = ' . $db->quoteName('asso.parent_date') . ')' + // Or we are on the child language and we check its state comparing to its parent. + . ' OR (' . $db->quoteName('asso.parent_date') . ' = ' . $db->quoteName('asso2.parent_date') + . ' AND ' . $db->quoteName('asso2.id') . ' = ' . $db->quoteName('asso.parent_id') . '))' + ); + } + } + // Add the group by clause $query->group($db->quoteName($groupby)); @@ -463,7 +531,29 @@ public function purge($context = '', $key = '') // Filter by associations context. if ($context) { - $query->where($db->quoteName('context') . ' = ' . $db->quote($context)); + list($extensionName, $typeName) = explode('.', $context, 2); + + // The associations of the type category may only be deleted with the appropriate context, + // therefore with the appropriate extension. Not all categories. + if ($typeName === 'category') + { + // Subquery: Search for category-items with the given context + $subQuery = $db->getQuery(true) + ->select($db->quoteName('id')) + ->from($db->quoteName('#__categories')) + ->where($db->quoteName('extension') . ' = :extension') + ->bind(':extension', $extensionName); + $idResults = $db->setQuery($subQuery)->loadColumn(); + + // Delete associations of categories with the given context by comparing id of both tables + $query->whereIn($db->quoteName('id'), $idResults, ParameterType::INTEGER) + ->where($db->quoteName('context') . ' = :context') + ->bind(':context', $context = 'com_categories.item'); + } + else + { + $query->where($db->quoteName('context') . ' = ' . $db->quote($extensionName . '.item')); + } } // Filter by key. diff --git a/administrator/components/com_associations/Model/DefaultAssocLangModel.php b/administrator/components/com_associations/Model/DefaultAssocLangModel.php new file mode 100644 index 0000000000000..5fe957cf57e45 --- /dev/null +++ b/administrator/components/com_associations/Model/DefaultAssocLangModel.php @@ -0,0 +1,88 @@ +getQuery(true) + ->select($db->quoteName('parent_date')) + ->from($db->quoteName('#__associations')) + ->where( + [ + $db->quoteName('id') . ' = :id', + $db->quoteName('parent_id') . ' = ' . $db->quote(0), + $db->quoteName('context') . ' = :context' + ] + ) + ->bind(':id', $parentId, ParameterType::INTEGER) + ->bind(':context', $context); + $parentModified = $db->setQuery($subQuery)->loadResult(); + $parentModifiedSql = Factory::getDate($parentModified)->toSql(); + + $query = $db->getQuery(true) + ->update($db->quoteName('#__associations')) + ->set($db->quoteName('parent_date') . ' = :parent_date') + ->where( + [ + $db->quoteName('id') . ' = :id', + $db->quoteName('parent_id') . ' = :parent_id', + $db->quoteName('context') . ' = :context' + ] + ) + ->bind(':parent_date', $parentModifiedSql) + ->bind(':id', $childId, ParameterType::INTEGER) + ->bind(':parent_id', $parentId, ParameterType::INTEGER) + ->bind(':context', $context); + $db->setQuery($query); + + try + { + $db->execute(); + } + catch (ExecutionFailureException $e) + { + return false; + } + + return true; + } +} diff --git a/administrator/components/com_associations/View/Association/HtmlView.php b/administrator/components/com_associations/View/Association/HtmlView.php index f1a412e22553d..29ba8360c1be8 100644 --- a/administrator/components/com_associations/View/Association/HtmlView.php +++ b/administrator/components/com_associations/View/Association/HtmlView.php @@ -11,10 +11,12 @@ defined('_JEXEC') or die; +use Joomla\CMS\Component\ComponentHelper; use Joomla\CMS\Factory; use Joomla\CMS\Language\Text; use Joomla\CMS\MVC\View\HtmlView as BaseHtmlView; use Joomla\CMS\Router\Route; +use Joomla\CMS\Table\Table; use Joomla\CMS\Toolbar\Toolbar; use Joomla\CMS\Toolbar\ToolbarHelper; use Joomla\Component\Associations\Administrator\Helper\AssociationsHelper; @@ -169,6 +171,27 @@ public function display($tpl = null) $this->targetTitle = AssociationsHelper::getTypeFieldName($extensionName, $typeName, 'title'); $task = $typeName . '.' . $this->targetAction; + // The update layout can only be set when a default association language is used. + // Get version ids of the parent, when versions are enabled. + if ($this->getLayout() === 'update') + { + $saveHistory = ComponentHelper::getParams($extensionName)->get('save_history', 0); + + if ($saveHistory) + { + $typeAlias = $typeName === 'category' ? $extensionName . '.' . $typeName : $reference['typeAlias']; + $model = $this->getModel(); + $typeId = Table::getInstance('ContentType')->getTypeId($typeAlias); + $parentVersionIds = $model->getParentCompareValues($referenceId, $this->targetId, $extensionName, $typeName, $typeId); + + if ($parentVersionIds[0] !== null && $parentVersionIds[1] !== null) + { + $this->referenceVersionIdNew = $parentVersionIds[0]; + $this->referenceVersionIdOld = $parentVersionIds[1]; + } + } + } + /* * Let's put the target src into a variable to use in the javascript code * to avoid race conditions when the reference iframe loads. @@ -207,23 +230,43 @@ protected function addToolbar() ToolbarHelper::title(Text::sprintf('COM_ASSOCIATIONS_TITLE_EDIT', Text::_($this->extensionName), Text::_($languageKey)), 'language assoc'); - $bar = Toolbar::getInstance('toolbar'); - - $bar->appendButton( - 'Custom', '', 'reference' - ); + $toolbar = Toolbar::getInstance('toolbar'); - $bar->appendButton( - 'Custom', '', 'target' - ); + // The update layout can only be set when a default association language is used. + if ($this->getLayout() === 'update') + { + // In the update view we can just save the target + $toolbar->appendButton( + 'Custom', '', 'target' + ); - if ($this->typeName === 'category' || $this->extensionName === 'com_menus' || $this->save2copy === true) + // And when saving the target this button gets activated via js to update the parent's date for the child + $toolbar->appendButton( + 'Custom', '', 'target' + ); + } + else { - ToolbarHelper::custom('copy', 'copy.png', '', 'COM_ASSOCIATIONS_COPY_REFERENCE', false); + $toolbar->appendButton( + 'Custom', '', + 'reference' + ); + + $toolbar->appendButton( + 'Custom', '', 'target' + ); + + if ($this->typeName === 'category' || $this->extensionName === 'com_menus' || $this->save2copy === true) + { + ToolbarHelper::custom('copy', 'copy.png', '', 'COM_ASSOCIATIONS_COPY_REFERENCE', false); + } } ToolbarHelper::cancel('association.cancel', 'JTOOLBAR_CLOSE'); diff --git a/administrator/components/com_associations/View/Associations/HtmlView.php b/administrator/components/com_associations/View/Associations/HtmlView.php index b82befbf90488..dd56bb0635d07 100644 --- a/administrator/components/com_associations/View/Associations/HtmlView.php +++ b/administrator/components/com_associations/View/Associations/HtmlView.php @@ -16,6 +16,7 @@ use Joomla\CMS\Language\Text; use Joomla\CMS\MVC\View\HtmlView as BaseHtmlView; use Joomla\CMS\Router\Route; +use Joomla\CMS\Toolbar\Toolbar; use Joomla\CMS\Toolbar\ToolbarHelper; use Joomla\Component\Associations\Administrator\Helper\AssociationsHelper; @@ -77,6 +78,22 @@ public function display($tpl = null) $this->filterForm = $this->get('FilterForm'); $this->activeFilters = $this->get('ActiveFilters'); + // Get default values and set these to selected to the select boxes + if ($this->state->get('itemtype')) + { + $this->filterForm->setValue('itemtype', null, $this->state->get('itemtype')); + } + + if ($this->state->get('language')) + { + $this->filterForm->setValue('language', null, $this->state->get('language')); + } + + if ($this->state->get('assocstate')) + { + $this->filterForm->setValue('assocstate', null, $this->state->get('assocstate')); + } + if (!Associations::isEnabled()) { $link = Route::_('index.php?option=com_plugins&task=plugin.edit&extension_id=' . AssociationsHelper::getLanguagefilterPluginId()); @@ -132,6 +149,15 @@ public function display($tpl = null) // This selectors doesn't have to activate the filter bar. unset($this->activeFilters['itemtype']); unset($this->activeFilters['language']); + unset($this->activeFilters['assocstate']); + + // Remove association state filter depending on default association language + $defaultAssocLang = Associations::getDefaultAssocLang(); + + if (!$defaultAssocLang) + { + $this->filterForm->removeField('assocstate', 'filter'); + } // Remove filters options depending on selected type. if (empty($support['state'])) @@ -213,6 +239,7 @@ public function display($tpl = null) protected function addToolbar() { $user = Factory::getUser(); + $toolbar = Toolbar::getInstance('toolbar'); if (isset($this->typeName) && isset($this->extensionName)) { @@ -239,12 +266,15 @@ protected function addToolbar() if ($user->authorise('core.admin', 'com_associations') || $user->authorise('core.options', 'com_associations')) { - if (!isset($this->typeName)) - { - ToolbarHelper::custom('associations.purge', 'purge', 'purge', 'COM_ASSOCIATIONS_PURGE', false, false); - ToolbarHelper::custom('associations.clean', 'refresh', 'refresh', 'COM_ASSOCIATIONS_DELETE_ORPHANS', false, false); - } - + $toolbar->confirmButton('purge') + ->text('COM_ASSOCIATIONS_PURGE') + ->message( + (isset($this->extensionName) && isset($languageKey)) + ? Text::plural('COM_ASSOCIATIONS_PURGE_CONFIRM_PROMPT', (Text::_($this->extensionName) . ' > ' . Text::_($languageKey))) + : Text::_('COM_ASSOCIATIONS_PURGE_CONFIRM_PROMPT') + ) + ->task('associations.purge'); + ToolbarHelper::custom('associations.clean', 'refresh', 'refresh', 'COM_ASSOCIATIONS_DELETE_ORPHANS', false, false); ToolbarHelper::preferences('com_associations'); } diff --git a/administrator/components/com_associations/forms/filter_associations.xml b/administrator/components/com_associations/forms/filter_associations.xml index 6177a48df1c41..f094d10073c21 100644 --- a/administrator/components/com_associations/forms/filter_associations.xml +++ b/administrator/components/com_associations/forms/filter_associations.xml @@ -5,14 +5,14 @@ type="itemtype" label="COM_ASSOCIATIONS_FILTER_SELECT_ITEM" filtermode="selector" - onchange="Joomla.resetFilters(this)" + onchange="this.form.submit();" > JOPTION_SELECT_LANGUAGE + + + + + + + input->get('forcedLanguage', '', 'cmd') == '') : ?> filterForm->getField('language'); ?> -
+ +
+ +
+
label; ?>
input; ?>
+ + filterForm->getField('assocstate'); ?> +
+
+ + input; ?> +
+
+
diff --git a/administrator/components/com_associations/tmpl/association/update.php b/administrator/components/com_associations/tmpl/association/update.php new file mode 100644 index 0000000000000..4141d64874913 --- /dev/null +++ b/administrator/components/com_associations/tmpl/association/update.php @@ -0,0 +1,74 @@ + 'auto', 'relative' => true]); +HTMLHelper::_('stylesheet', 'com_associations/sidebyside.css', ['version' => 'auto', 'relative' => true]); + +$options = array( + 'layout' => $this->app->input->get('layout', '', 'string'), + 'itemtype' => $this->itemtype, + 'id' => $this->referenceId, + 'targetId' => $this->targetId +); +?> +
+
+
+
+ referenceVersionIdNew)) : ?> +

+ + +

+ + +
+
+
+
+

+ +
+
+
+ + +
diff --git a/administrator/components/com_associations/tmpl/associations/default.php b/administrator/components/com_associations/tmpl/associations/default.php index 12696b9bbcc55..b97bd91f6bbc4 100644 --- a/administrator/components/com_associations/tmpl/associations/default.php +++ b/administrator/components/com_associations/tmpl/associations/default.php @@ -11,6 +11,7 @@ use Joomla\CMS\Factory; use Joomla\CMS\HTML\HTMLHelper; +use Joomla\CMS\Language\Associations; use Joomla\CMS\Language\Text; use Joomla\CMS\Layout\LayoutHelper; use Joomla\CMS\Router\Route; @@ -21,6 +22,8 @@ $listOrder = $this->escape($this->state->get('list.ordering')); $listDirn = $this->escape($this->state->get('list.direction')); $canManageCheckin = Factory::getUser()->authorise('core.manage', 'com_checkin'); +$defaultAssocLang = Associations::getDefaultAssocLang(); +$assocState = $this->escape($this->state->get('assocstate')); $iconStates = array( -2 => 'icon-trash', @@ -29,8 +32,6 @@ 2 => 'icon-archive', ); -Text::script('COM_ASSOCIATIONS_PURGE_CONFIRM_PROMPT', true); -HTMLHelper::_('script', 'com_associations/admin-associations-default.min.js', ['version' => 'auto', 'relative' => true]); ?>
@@ -60,12 +61,18 @@ - - - - - - + + + + + + + + + + + + typeFields['menutype'])) : ?> @@ -123,12 +130,18 @@ - - extensionName, $this->typeName, (int) $item->id, $item->language, !$isCheckout, false); ?> - - - extensionName, $this->typeName, (int) $item->id, $item->language, !$isCheckout, true); ?> - + + + extensionName, $this->typeName, (int) $item->id, $item->language, !$isCheckout, true, $assocState); ?> + + + + extensionName, $this->typeName, (int) $item->id, $item->language, !$isCheckout, false); ?> + + + extensionName, $this->typeName, (int) $item->id, $item->language, !$isCheckout, true); ?> + + typeFields['menutype'])) : ?> escape($item->menutype_title); ?> @@ -147,7 +160,7 @@ - + pagination->getListFooter(); ?> diff --git a/administrator/components/com_associations/tmpl/associations/modal.php b/administrator/components/com_associations/tmpl/associations/modal.php index ed7c320393274..185949b729692 100644 --- a/administrator/components/com_associations/tmpl/associations/modal.php +++ b/administrator/components/com_associations/tmpl/associations/modal.php @@ -29,6 +29,7 @@ $function = $app->input->getCmd('function', 'jSelectAssociation'); $listOrder = $this->escape($this->state->get('list.ordering')); $listDirn = $this->escape($this->state->get('list.direction')); +$assocState = $this->escape($this->state->get('assocstate')); $canManageCheckin = Factory::getUser()->authorise('core.manage', 'com_checkin'); $iconStates = array( @@ -131,7 +132,7 @@ association) : ?> - extensionName, $this->typeName, (int) $item->id, $item->language, false, false); ?> + extensionName, $this->typeName, (int) $item->id, $item->language, false, false, $assocState); ?> typeFields['menutype'])) : ?> diff --git a/administrator/components/com_categories/Model/CategoryModel.php b/administrator/components/com_categories/Model/CategoryModel.php index 8584d2abc2d2d..41c92ee075983 100644 --- a/administrator/components/com_categories/Model/CategoryModel.php +++ b/administrator/components/com_categories/Model/CategoryModel.php @@ -27,7 +27,9 @@ use Joomla\CMS\MVC\Model\AdminModel; use Joomla\CMS\Plugin\PluginHelper; use Joomla\CMS\UCM\UCMType; +use Joomla\Component\Associations\Administrator\Helper\DefaultAssocLangHelper; use Joomla\Component\Categories\Administrator\Helper\CategoriesHelper; +use Joomla\Database\ParameterType; use Joomla\Registry\Registry; use Joomla\String\StringHelper; use Joomla\Utilities\ArrayHelper; @@ -630,6 +632,9 @@ public function save($data) Factory::getApplication()->enqueueMessage(Text::_('COM_CATEGORIES_ERROR_ALL_LANGUAGE_ASSOCIATED'), 'notice'); } + // Get association params before they get deleted + $assocParentDates = DefaultAssocLangHelper::getParentDates($associations, $this->associationsContext); + // Get associationskey for edited item $db = $this->getDbo(); $query = $db->getQuery(true) @@ -677,27 +682,57 @@ public function save($data) if (count($associations) > 1) { + // If there is an association item with the default association language, then get its id + $defaultAssocLang = Associations::getDefaultAssocLang(); + $parentId = $associations[$defaultAssocLang] ?? ''; + + // Id of the saved item + $dataId = (int) $table->id; + + // Get the latest modified date of parent + $parentModified = DefaultAssocLangHelper::getParentModifiedDate($parentId, $table->getTableName(), $table->extension . '.category'); + // Adding new association for these items $key = md5(json_encode($associations)); - $query->clear() - ->insert('#__associations'); foreach ($associations as $id) { - $query->values(((int) $id) . ',' . $db->quote($this->associationsContext) . ',' . $db->quote($key)); - } + $parentIdAndDateValues = DefaultAssocLangHelper::getParentValues( + $id, $dataId, $parentId, $parentModified, $assocParentDates, $oldKey + ); + $parentIdValue = $parentIdAndDateValues[0]; + $parentDateValue = $parentIdAndDateValues[1] === 'NULL' ? 'NULL' : Factory::getDate($parentIdAndDateValues[1])->toSql(); - $db->setQuery($query); + $query->clear() + ->insert($db->quoteName('#__associations')); - try - { - $db->execute(); - } - catch (\RuntimeException $e) - { - $this->setError($e->getMessage()); + if ($parentDateValue === 'NULL') + { + $query->values((' :id, :context, :key, :parentId, NULL')); + } + else + { + $query->values((' :id, :context, :key, :parentId, :parentDate')) + ->bind(':parentDate', $parentDateValue); + } - return false; + $query->bind(':id', $id, ParameterType::INTEGER) + ->bind(':context', $this->associationsContext) + ->bind(':key', $key) + ->bind(':parentId', $parentIdValue, ParameterType::INTEGER); + + $db->setQuery($query); + + try + { + $db->execute(); + } + catch (\RuntimeException $e) + { + $this->setError($e->getMessage()); + + return false; + } } } } diff --git a/administrator/components/com_categories/Service/HTML/AdministratorService.php b/administrator/components/com_categories/Service/HTML/AdministratorService.php index ee5932ffb26dd..c3a2a2e60142e 100644 --- a/administrator/components/com_categories/Service/HTML/AdministratorService.php +++ b/administrator/components/com_categories/Service/HTML/AdministratorService.php @@ -11,10 +11,14 @@ defined('_JEXEC') or die; +use Joomla\CMS\Component\ComponentHelper; use Joomla\CMS\Factory; +use Joomla\CMS\Language\Associations; use Joomla\CMS\Layout\LayoutHelper; use Joomla\CMS\Router\Route; +use Joomla\Component\Associations\Administrator\Helper\DefaultAssocLangHelper; use Joomla\Component\Categories\Administrator\Helper\CategoriesHelper; +use Joomla\Database\ParameterType; use Joomla\Utilities\ArrayHelper; /** @@ -38,7 +42,11 @@ class AdministratorService public function association($catid, $extension = 'com_content') { // Defaults - $html = ''; + $html = ''; + $defaultAssocLang = Associations::getDefaultAssocLang(); + + // Check if versions are enabled. + $saveHistory = ComponentHelper::getParams($extension)->get('save_history', 0); // Get the associations if ($associations = CategoriesHelper::getAssociations($catid, $extension)) @@ -48,15 +56,19 @@ public function association($catid, $extension = 'com_content') // Get the associated categories $db = Factory::getDbo(); $query = $db->getQuery(true) - ->select('c.id, c.title') - ->select('l.sef as lang_sef') - ->select('l.lang_code') - ->from('#__categories as c') - ->where('c.id IN (' . implode(',', array_values($associations)) . ')') - ->where('c.id != ' . $catid) - ->join('LEFT', '#__languages as l ON c.language=l.lang_code') - ->select('l.image') - ->select('l.title as language_title'); + ->select($db->quoteName(['c.id', 'c.title', 'l.lang_code', 'l.image'])) + ->select($db->quoteName(['l.sef', 'l.title'], ['lang_sef', 'language_title'])) + ->from($db->quoteName('#__categories', 'c')) + ->whereIN($db->quoteName('c.id'), array_values($associations)); + + // Don't get the id of the item itself when there is no default association language used. + if (!$defaultAssocLang) + { + $query->where($db->quoteName('c.id') . ' != :id') + ->bind(':id', $catid, ParameterType::INTEGER); + } + + $query->leftJoin($db->quoteName('#__languages', 'l'), $db->quoteName('c.language') . ' = ' . $db->quoteName('l.lang_code')); $db->setQuery($query); try @@ -68,18 +80,76 @@ public function association($catid, $extension = 'com_content') throw new \Exception($e->getMessage(), 500, $e); } + if ($defaultAssocLang) + { + // Check if the current item is a parent. + $isParent = (array_key_exists($catid, $items) && ($items[$catid]->lang_code === $defaultAssocLang)) + ? true + : false; + + // Check if there is a parent in the association and get its id if so. + $parentId = array_key_exists($defaultAssocLang, $associations) + ? $associations[$defaultAssocLang] + : ''; + + // Get parent dates of each item of associations. + $assocParentDates = DefaultAssocLangHelper::getParentDates($associations, 'com_categories.item'); + } + if ($items) { - foreach ($items as &$item) + foreach ($items as $key => &$item) { - $text = $item->lang_sef ? strtoupper($item->lang_sef) : 'XX'; - $url = Route::_('index.php?option=com_categories&task=category.edit&id=' . (int) $item->id . '&extension=' . $extension); - $tooltip = '' . htmlspecialchars($item->language_title, ENT_QUOTES, 'UTF-8') . '
' - . htmlspecialchars($item->title, ENT_QUOTES, 'UTF-8'); - $classes = 'badge badge-secondary'; + $labelClass = 'badge-success'; + $parentChildInfo = ''; + $url = Route::_('index.php?option=com_categories&task=category.edit&id=' . (int) $item->id . '&extension=' . $extension); + + if ($defaultAssocLang) + { + // Don't continue for parent, because it has been set here before. + if ($key === 'parent') + { + continue; + } + + $classParentInfoItems = DefaultAssocLangHelper::setParentAndChildInfos( + $catid, $items, $key, $item, $defaultAssocLang, $isParent, $parentId, $assocParentDates, $saveHistory + ); + $labelClass = $classParentInfoItems[0]; + $parentChildInfo = $classParentInfoItems[1]; + $items = $classParentInfoItems[2]; + $needsUpdate = $classParentInfoItems[3]; + + $url = Route::_( + DefaultAssocLangHelper::getAssociationUrl( + $item->id, $defaultAssocLang, $extension . '.category', $item->lang_code, $key, $parentId, $needsUpdate + ) + ); + } + + $text = $item->lang_sef ? strtoupper($item->lang_sef) : 'XX'; + $tooltip = '' . htmlspecialchars($item->language_title, ENT_QUOTES, 'UTF-8') . '
' + . htmlspecialchars($item->title, ENT_QUOTES, 'UTF-8') . $parentChildInfo; + $classes = 'badge ' . $labelClass; $item->link = '' . $text . '' . ''; + + // Reorder the array, so the parent gets to the first place. + if ($item->lang_code === $defaultAssocLang) + { + $items = array('parent' => $items[$key]) + $items; + unset($items[$key]); + } + } + + // If a parent doesn't exist, display that there is no association with the default association language. + if ($defaultAssocLang && !$parentId) + { + $link = DefaultAssocLangHelper::addNotAssociatedParentLink($defaultAssocLang, $catid, $extension . '.category'); + + // Add this on the top of the array. + $items = array('parent' => array('link' => $link)) + $items; } } diff --git a/administrator/components/com_categories/tmpl/categories/default.php b/administrator/components/com_categories/tmpl/categories/default.php index 4548e2b72ec93..a181efe8ca69f 100644 --- a/administrator/components/com_categories/tmpl/categories/default.php +++ b/administrator/components/com_categories/tmpl/categories/default.php @@ -11,6 +11,7 @@ use Joomla\CMS\Factory; use Joomla\CMS\HTML\HTMLHelper; +use Joomla\CMS\Language\Associations; use Joomla\CMS\Language\Multilanguage; use Joomla\CMS\Language\Text; use Joomla\CMS\Layout\LayoutHelper; @@ -20,15 +21,16 @@ HTMLHelper::_('behavior.multiselect'); -$user = Factory::getUser(); -$userId = $user->get('id'); -$extension = $this->escape($this->state->get('filter.extension')); -$listOrder = $this->escape($this->state->get('list.ordering')); -$listDirn = $this->escape($this->state->get('list.direction')); -$saveOrder = ($listOrder == 'a.lft' && strtolower($listDirn) == 'asc'); -$parts = explode('.', $extension, 2); -$component = $parts[0]; -$section = null; +$user = Factory::getUser(); +$userId = $user->get('id'); +$extension = $this->escape($this->state->get('filter.extension')); +$listOrder = $this->escape($this->state->get('list.ordering')); +$listDirn = $this->escape($this->state->get('list.direction')); +$saveOrder = ($listOrder == 'a.lft' && strtolower($listDirn) == 'asc'); +$parts = explode('.', $extension, 2); +$component = $parts[0]; +$section = null; +$assocAlign = Associations::getDefaultAssocLang() ? 'text-center ' : ''; if (count($parts) > 1) { @@ -108,7 +110,7 @@ assoc) : ?> - + diff --git a/administrator/components/com_contact/Service/HTML/AdministratorService.php b/administrator/components/com_contact/Service/HTML/AdministratorService.php index 114313efd618e..b8cb94107b5f0 100644 --- a/administrator/components/com_contact/Service/HTML/AdministratorService.php +++ b/administrator/components/com_contact/Service/HTML/AdministratorService.php @@ -11,11 +11,14 @@ defined('_JEXEC') or die; +use Joomla\CMS\Component\ComponentHelper; use Joomla\CMS\Factory; use Joomla\CMS\Language\Associations; use Joomla\CMS\Language\Text; use Joomla\CMS\Layout\LayoutHelper; use Joomla\CMS\Router\Route; +use Joomla\Component\Associations\Administrator\Helper\DefaultAssocLangHelper; +use Joomla\Database\ParameterType; use Joomla\Utilities\ArrayHelper; /** @@ -37,7 +40,11 @@ class AdministratorService public function association($contactid) { // Defaults - $html = ''; + $html = ''; + $defaultAssocLang = Associations::getDefaultAssocLang(); + + // Check if versions are enabled + $saveHistory = ComponentHelper::getParams('com_contact')->get('save_history', 0); // Get the associations if ($associations = Associations::getAssociations('com_contact', '#__contact_details', 'com_contact.item', $contactid)) @@ -50,16 +57,20 @@ public function association($contactid) // Get the associated contact items $db = Factory::getDbo(); $query = $db->getQuery(true) - ->select('c.id, c.name as title') - ->select('l.sef as lang_sef, lang_code') - ->from('#__contact_details as c') - ->select('cat.title as category_title') - ->join('LEFT', '#__categories as cat ON cat.id=c.catid') - ->where('c.id IN (' . implode(',', array_values($associations)) . ')') - ->where('c.id != ' . $contactid) - ->join('LEFT', '#__languages as l ON c.language=l.lang_code') - ->select('l.image') - ->select('l.title as language_title'); + ->select($db->quoteName(['c.id', 'lang_code', 'l.image'])) + ->select($db->quoteName(['c.name', 'l.sef', 'cat.title', 'l.title'], ['title', 'lang_sef', 'category_title', 'language_title'])) + ->from($db->quoteName('#__contact_details', 'c')) + ->leftJoin($db->quoteName('#__categories', 'cat'), $db->quoteName('cat.id') . ' = ' . $db->quoteName('c.catid')) + ->whereIN($db->quoteName('c.id'), array_values($associations)); + + // Don't get the id of the item itself when there is no default association language used. + if (!$defaultAssocLang) + { + $query->where($db->quoteName('c.id') . ' != :id') + ->bind(':id', $contactid, ParameterType::INTEGER); + } + + $query->leftJoin($db->quoteName('#__languages', 'l'), $db->quoteName('c.language') . ' = ' . $db->quoteName('l.lang_code')); $db->setQuery($query); try @@ -71,18 +82,76 @@ public function association($contactid) throw new \Exception($e->getMessage(), 500, $e); } + if ($defaultAssocLang) + { + // Check if current item is the parent. + $isParent = (array_key_exists($contactid, $items) && ($items[$contactid]->lang_code === $defaultAssocLang)) + ? true + : false; + + // Check if there is a parent in the association and get its id if so. + $parentId = array_key_exists($defaultAssocLang, $associations) + ? $associations[$defaultAssocLang] + : ''; + + // Get parent dates of each item of associations. + $assocParentDates = DefaultAssocLangHelper::getParentDates($associations, 'com_contact.item'); + } + if ($items) { - foreach ($items as &$item) + foreach ($items as $key => &$item) { - $text = strtoupper($item->lang_sef); - $url = Route::_('index.php?option=com_contact&task=contact.edit&id=' . (int) $item->id); + $parentChildInfo = ''; + $labelClass = 'badge-success'; + $url = Route::_('index.php?option=com_contact&task=contact.edit&id=' . (int) $item->id); + + if ($defaultAssocLang) + { + // Don't continue for parent, because it has been set just before as new array item + if ($key === 'parent') + { + continue; + } + + $classParentInfoItems = DefaultAssocLangHelper::setParentAndChildInfos( + $contactid, $items, $key, $item, $defaultAssocLang, $isParent, $parentId, $assocParentDates, $saveHistory + ); + $labelClass = $classParentInfoItems[0]; + $parentChildInfo = $classParentInfoItems[1]; + $items = $classParentInfoItems[2]; + $needsUpdate = $classParentInfoItems[3]; + + $url = Route::_( + DefaultAssocLangHelper::getAssociationUrl( + $item->id, $defaultAssocLang, 'com_contact.contact', $item->lang_code, $key, $parentId, $needsUpdate + ) + ); + } + + $text = strtoupper($item->lang_sef); $tooltip = '' . htmlspecialchars($item->language_title, ENT_QUOTES, 'UTF-8') . '
' - . htmlspecialchars($item->title, ENT_QUOTES, 'UTF-8') . '
' . Text::sprintf('JCATEGORY_SPRINTF', $item->category_title); - $classes = 'badge badge-secondary'; + . htmlspecialchars($item->title, ENT_QUOTES, 'UTF-8') . '
' . Text::sprintf('JCATEGORY_SPRINTF', $item->category_title) . $parentChildInfo; + $classes = 'badge ' . $labelClass; $item->link = '' . $text . '' . ''; + + // Reorder the array, so the parent gets to the first place + if ($item->lang_code === $defaultAssocLang) + { + $items = array('parent' => $items[$key]) + $items; + unset($items[$key]); + } + } + + // If a parent doesn't exist, display that there is no association with the default association language. + if ($defaultAssocLang && !$parentId) + { + $link = DefaultAssocLangHelper::addNotAssociatedParentLink($defaultAssocLang, $contactid, 'com_contact.contact'); + + // Add this on the top of the array + $items = array('parent' => array('link' => $link)) + $items; } } diff --git a/administrator/components/com_content/Service/HTML/AdministratorService.php b/administrator/components/com_content/Service/HTML/AdministratorService.php index 6b4ee27979972..b0ed7a8328236 100644 --- a/administrator/components/com_content/Service/HTML/AdministratorService.php +++ b/administrator/components/com_content/Service/HTML/AdministratorService.php @@ -11,11 +11,15 @@ defined('_JEXEC') or die; +use Joomla\CMS\Component\ComponentHelper; use Joomla\CMS\Factory; use Joomla\CMS\Language\Associations; use Joomla\CMS\Language\Text; use Joomla\CMS\Layout\LayoutHelper; use Joomla\CMS\Router\Route; +use Joomla\Component\Associations\Administrator\Helper\DefaultAssocLangHelper; +use Joomla\Database\ParameterType; +use Joomla\Utilities\ArrayHelper; /** * Content HTML helper @@ -37,7 +41,11 @@ class AdministratorService public function association($articleid) { // Defaults - $html = ''; + $html = ''; + $defaultAssocLang = Associations::getDefaultAssocLang(); + + // Check if versions are enabled + $saveHistory = ComponentHelper::getParams('com_content')->get('save_history', 0); // Get the associations if ($associations = Associations::getAssociations('com_content', '#__content', 'com_content.item', $articleid)) @@ -51,16 +59,20 @@ public function association($articleid) $db = Factory::getDbo(); $query = $db->getQuery(true) ->select('c.*') - ->select('l.sef as lang_sef') - ->select('l.lang_code') - ->from('#__content as c') - ->select('cat.title as category_title') - ->join('LEFT', '#__categories as cat ON cat.id=c.catid') - ->where('c.id IN (' . implode(',', array_values($associations)) . ')') - ->where('c.id != ' . $articleid) - ->join('LEFT', '#__languages as l ON c.language=l.lang_code') - ->select('l.image') - ->select('l.title as language_title'); + ->select($db->quoteName(['l.lang_code', 'l.image'])) + ->select($db->quoteName(['l.sef', 'cat.title', 'l.title'], ['lang_sef', 'category_title', 'language_title'])) + ->from($db->quoteName('#__content', 'c')) + ->leftJoin($db->quoteName('#__categories', 'cat'), $db->quoteName('cat.id') . ' = ' . $db->quoteName('c.catid')) + ->whereIN($db->quoteName('c.id'), array_values($associations)); + + // Don't get the id of the item itself when there is no default association language used. + if (!$defaultAssocLang) + { + $query->where($db->quoteName('c.id') . ' != :id') + ->bind(':id', $articleid, ParameterType::INTEGER); + } + + $query->leftJoin($db->quoteName('#__languages', 'l'), $db->quoteName('c.language') . ' = ' . $db->quoteName('l.lang_code')); $db->setQuery($query); try @@ -72,18 +84,76 @@ public function association($articleid) throw new \Exception($e->getMessage(), 500, $e); } + if ($defaultAssocLang) + { + // Check if the current item is a parent. + $isParent = (array_key_exists($articleid, $items) && ($items[$articleid]->lang_code === $defaultAssocLang)) + ? true + : false; + + // Check if there is a parent in the association and get its id if so + $parentId = array_key_exists($defaultAssocLang, $associations) + ? $associations[$defaultAssocLang] + : ''; + + // Get parent dates of each item of associations. + $assocParentDates = DefaultAssocLangHelper::getParentDates($associations, 'com_content.item'); + } + if ($items) { - foreach ($items as &$item) + foreach ($items as $key => &$item) { + $parentChildInfo = ''; + $labelClass = 'badge-success'; + $url = Route::_('index.php?option=com_content&task=article.edit&id=' . (int) $item->id); + + if ($defaultAssocLang) + { + // Don't continue for parent, because it has been set here before + if ($key === 'parent') + { + continue; + } + + $classParentInfoItems = DefaultAssocLangHelper::setParentAndChildInfos( + $articleid, $items, $key, $item, $defaultAssocLang, $isParent, $parentId, $assocParentDates, $saveHistory + ); + $labelClass = $classParentInfoItems[0]; + $parentChildInfo = $classParentInfoItems[1]; + $items = $classParentInfoItems[2]; + $needsUpdate = $classParentInfoItems[3]; + + $url = Route::_( + DefaultAssocLangHelper::getAssociationUrl( + $item->id, $defaultAssocLang, 'com_content.article', $item->lang_code, $key, $parentId, $needsUpdate + ) + ); + } + $text = $item->lang_sef ? strtoupper($item->lang_sef) : 'XX'; - $url = Route::_('index.php?option=com_content&task=article.edit&id=' . (int) $item->id); $tooltip = '' . htmlspecialchars($item->language_title, ENT_QUOTES, 'UTF-8') . '
' - . htmlspecialchars($item->title, ENT_QUOTES, 'UTF-8') . '
' . Text::sprintf('JCATEGORY_SPRINTF', $item->category_title); - $classes = 'badge badge-secondary'; + . htmlspecialchars($item->title, ENT_QUOTES, 'UTF-8') . '
' . Text::sprintf('JCATEGORY_SPRINTF', $item->category_title) . $parentChildInfo; + $classes = 'badge ' . $labelClass; $item->link = '' . $text . '' . ''; + + // Reorder the array, so the parent gets to the first place + if ($item->lang_code === $defaultAssocLang) + { + $items = array('parent' => $items[$key]) + $items; + unset($items[$key]); + } + } + + // If a parent doesn't exist, display that there is no association with the default association language. + if ($defaultAssocLang && !$parentId) + { + $link = DefaultAssocLangHelper::addNotAssociatedParentLink($defaultAssocLang, $articleid, 'com_content.article'); + + // Add this on the top of the array + $items = array('parent' => array('link' => $link)) + $items; } } diff --git a/administrator/components/com_contenthistory/tmpl/compare/compareassocparent.php b/administrator/components/com_contenthistory/tmpl/compare/compareassocparent.php new file mode 100644 index 0000000000000..82317cf975fe9 --- /dev/null +++ b/administrator/components/com_contenthistory/tmpl/compare/compareassocparent.php @@ -0,0 +1,97 @@ +items[0]; +$version1 = $this->items[1]; +$object1 = $version1->data; +$object2 = $version2->data; + +$objLabel = ''; +if (array_key_exists('title', $object2)) +{ + $objLabel = $object2->title; +} +elseif (array_key_exists('name', $object2)) +{ + $objLabel = $object2->name; +} + +HTMLHelper::_('script', 'vendor/diff/diff.min.js', array('version' => 'auto', 'relative' => true)); +HTMLHelper::_('script', 'com_associations/admin-compare-assoc-parent.min.js', array('version' => 'auto', 'relative' => true)); +HTMLHelper::_('stylesheet', 'com_associations/sidebyside.css', ['version' => 'auto', 'relative' => true]); +?> + +
+
+
+ label ?> +
+
+ +
+
+
+
+ data->alias->label ?> +
+
+ +
+
+
+ + + + + + + + + $value) : ?> + value != $object2->$name->value) : ?> + + value)) : ?> + + value as $subName => $subValue) : ?> + $name->value->$subName->value ?? ''; ?> + value || $newSubValue) : ?> + value != $newSubValue) : ?> + + + + + + + + + + + + + $name->value = is_object($object2->$name->value) ? json_encode($object2->$name->value) : $object2->$name->value; ?> + + + + + + + +
+ label; ?> +
label; ?>
+ label; ?> +
diff --git a/administrator/components/com_menus/Model/ItemModel.php b/administrator/components/com_menus/Model/ItemModel.php index 528e2be1f63df..a3f654a023b0b 100644 --- a/administrator/components/com_menus/Model/ItemModel.php +++ b/administrator/components/com_menus/Model/ItemModel.php @@ -23,6 +23,7 @@ use Joomla\CMS\Plugin\PluginHelper; use Joomla\CMS\Uri\Uri; use Joomla\Component\Menus\Administrator\Helper\MenusHelper; +use Joomla\Database\ParameterType; use Joomla\Registry\Registry; use Joomla\String\StringHelper; use Joomla\Utilities\ArrayHelper; @@ -1580,27 +1581,38 @@ public function save($data) if (count($associations) > 1) { + // If there is an associated item with the default association language, get its id. + $defaultAssocLang = Associations::getDefaultAssocLang(); + $parentId = $associations[$defaultAssocLang] ?? ''; + // Adding new association for these items $key = md5(json_encode($associations)); - $query->clear() - ->insert('#__associations'); foreach ($associations as $id) { - $query->values(((int) $id) . ',' . $db->quote($this->associationsContext) . ',' . $db->quote($key)); - } - - $db->setQuery($query); - - try - { - $db->execute(); - } - catch (\RuntimeException $e) - { - $this->setError($e->getMessage()); + // If there is no parent in this association, then reset the parent id. + // Otherwise, if the associated item is a parent, set its parent id to 0, otherwise to the parent Id. + $parentIdValue = $parentId ? ($parentId === $id ? 0 : $parentId) : -1; + $query->clear() + ->insert($db->quoteName('#__associations')) + ->values((' :id, :context, :key, :parentId, NULL')) + ->bind(':id', $id, ParameterType::INTEGER) + ->bind(':context', $this->associationsContext) + ->bind(':key', $key) + ->bind(':parentId', $parentIdValue, ParameterType::INTEGER); + + $db->setQuery($query); + + try + { + $db->execute(); + } + catch (\RuntimeException $e) + { + $this->setError($e->getMessage()); - return false; + return false; + } } } } diff --git a/administrator/components/com_menus/Service/HTML/Menus.php b/administrator/components/com_menus/Service/HTML/Menus.php index 98581db575172..f119cffd3c11a 100644 --- a/administrator/components/com_menus/Service/HTML/Menus.php +++ b/administrator/components/com_menus/Service/HTML/Menus.php @@ -12,10 +12,13 @@ defined('_JEXEC') or die; use Joomla\CMS\Factory; +use Joomla\CMS\Language\Associations; use Joomla\CMS\Language\Text; use Joomla\CMS\Layout\LayoutHelper; use Joomla\CMS\Router\Route; +use Joomla\Component\Associations\Administrator\Helper\DefaultAssocLangHelper; use Joomla\Component\Menus\Administrator\Helper\MenusHelper; +use Joomla\Database\ParameterType; use Joomla\Registry\Registry; /** @@ -42,6 +45,7 @@ public function association($itemid) { // Defaults $html = ''; + $defaultAssocLang = Associations::getDefaultAssocLang(); // Get the associations if ($associations = MenusHelper::getAssociations($itemid)) @@ -49,16 +53,20 @@ public function association($itemid) // Get the associated menu items $db = Factory::getDbo(); $query = $db->getQuery(true) - ->select('m.id, m.title') - ->select('l.sef as lang_sef, l.lang_code') - ->select('mt.title as menu_title') - ->from('#__menu as m') - ->join('LEFT', '#__menu_types as mt ON mt.menutype=m.menutype') - ->where('m.id IN (' . implode(',', array_values($associations)) . ')') - ->where('m.id != ' . $itemid) - ->join('LEFT', '#__languages as l ON m.language=l.lang_code') - ->select('l.image') - ->select('l.title as language_title'); + ->select($db->quoteName(['m.id', 'm.title', 'l.lang_code', 'l.image'])) + ->select($db->quoteName(['l.sef', 'mt.title', 'l.title'], ['lang_sef', 'menu_title', 'language_title'])) + ->from($db->quoteName('#__menu', 'm')) + ->leftJoin($db->quoteName('#__menu_types', 'mt'), $db->quoteName('mt.menutype') . ' = ' . $db->quoteName('m.menutype')) + ->whereIN($db->quoteName('m.id'), array_values($associations)); + + // Don't get the id of the item itself when there is no default association language used + if (!$defaultAssocLang) + { + $query->where($db->quoteName('m.id') . ' != :id') + ->bind(':id', $itemid, ParameterType::INTEGER); + } + + $query->leftJoin($db->quoteName('#__languages', 'l'), $db->quoteName('m.language') . ' = ' . $db->quoteName('l.lang_code')); $db->setQuery($query); try @@ -70,19 +78,74 @@ public function association($itemid) throw new \Exception($e->getMessage(), 500); } + if ($defaultAssocLang) + { + // Check if current item is the parent. + $isParent = (array_key_exists($itemid, $items) && ($items[$itemid]->lang_code === $defaultAssocLang)) + ? true + : false; + + // Check if there is a parent in the association and get its id if so + $parentId = array_key_exists($defaultAssocLang, $associations) + ? $associations[$defaultAssocLang] + : ''; + } + // Construct html if ($items) { - foreach ($items as &$item) + foreach ($items as $key => &$item) { + $parentChildInfo = ''; + $classes = 'badge badge-success'; + $url = Route::_('index.php?option=com_menus&task=item.edit&id=' . (int) $item->id); + + if ($defaultAssocLang) + { + // Don't continue for parent, because it has been set here before + if ($key === 'parent') + { + continue; + } + + // Don't display other children if the current item is a child. + if ($key !== $itemid && $defaultAssocLang !== $item->lang_code && !$isParent) + { + unset($items[$key]); + } + + if ($key === $parentId) + { + $classes .= ' parent-item'; + $parentChildInfo = '

' . Text::_('JGLOBAL_ASSOCIATIONS_DEFAULT_ASSOC_LANG_ITEM'); + } + + $url = Route::_(DefaultAssocLangHelper::getAssociationUrl($item->id, $defaultAssocLang, 'com_menus.item', $item->lang_code, $key, $parentId)); + } + $text = strtoupper($item->lang_sef); - $url = Route::_('index.php?option=com_menus&task=item.edit&id=' . (int) $item->id); $tooltip = '' . htmlspecialchars($item->language_title, ENT_QUOTES, 'UTF-8') . '
' - . htmlspecialchars($item->title, ENT_QUOTES, 'UTF-8') . '
' . Text::sprintf('COM_MENUS_MENU_SPRINTF', $item->menu_title); - $classes = 'badge badge-secondary'; + . htmlspecialchars($item->title, ENT_QUOTES, 'UTF-8') . '
' . Text::sprintf('COM_MENUS_MENU_SPRINTF', $item->menu_title) + . $parentChildInfo; $item->link = '' . $text . '' . ''; + + // Reorder the array, so the parent gets to the first place + if ($item->lang_code === $defaultAssocLang) + { + $items = array('parent' => $items[$key]) + $items; + unset($items[$key]); + } + } + + // If a parent doesn't exist, display that there is no association with the default association language. + if ($defaultAssocLang && !$parentId) + { + $link = DefaultAssocLangHelper::addNotAssociatedParentLink($defaultAssocLang, $itemid, 'com_menus.item'); + + // Add this on the top of the array + $items = array('parent' => array('link' => $link)) + $items; } } diff --git a/administrator/components/com_newsfeeds/Service/HTML/AdministratorService.php b/administrator/components/com_newsfeeds/Service/HTML/AdministratorService.php index ad9473e5f27b2..ab3dd0df6cfa7 100644 --- a/administrator/components/com_newsfeeds/Service/HTML/AdministratorService.php +++ b/administrator/components/com_newsfeeds/Service/HTML/AdministratorService.php @@ -11,11 +11,14 @@ defined('_JEXEC') or die; +use Joomla\CMS\Component\ComponentHelper; use Joomla\CMS\Factory; use Joomla\CMS\Language\Associations; use Joomla\CMS\Language\Text; use Joomla\CMS\Layout\LayoutHelper; use Joomla\CMS\Router\Route; +use Joomla\Component\Associations\Administrator\Helper\DefaultAssocLangHelper; +use Joomla\Database\ParameterType; /** * Utility class for creating HTML Grids. @@ -36,7 +39,11 @@ class AdministratorService public function association($newsfeedid) { // Defaults - $html = ''; + $html = ''; + $defaultAssocLang = Associations::getDefaultAssocLang(); + + // Check if versions are enabled + $saveHistory = ComponentHelper::getParams('com_newsfeeds')->get('save_history', 0); // Get the associations if ($associations = Associations::getAssociations('com_newsfeeds', '#__newsfeeds', 'com_newsfeeds.item', $newsfeedid)) @@ -49,16 +56,20 @@ public function association($newsfeedid) // Get the associated newsfeed items $db = Factory::getDbo(); $query = $db->getQuery(true) - ->select('c.id, c.name as title') - ->select('l.sef as lang_sef, lang_code') - ->from('#__newsfeeds as c') - ->select('cat.title as category_title') - ->join('LEFT', '#__categories as cat ON cat.id=c.catid') - ->where('c.id IN (' . implode(',', array_values($associations)) . ')') - ->where('c.id != ' . $newsfeedid) - ->join('LEFT', '#__languages as l ON c.language=l.lang_code') - ->select('l.image') - ->select('l.title as language_title'); + ->select($db->quoteName(['c.id', 'lang_code', 'l.image'])) + ->select($db->quoteName(['c.name', 'l.sef', 'cat.title', 'l.title'], ['title', 'lang_sef', 'category_title', 'language_title'])) + ->from($db->quoteName('#__newsfeeds', 'c')) + ->leftJoin($db->quoteName('#__categories', 'cat'), $db->quoteName('cat.id') . ' = ' . $db->quoteName('c.catid')) + ->whereIN($db->quoteName('c.id'), array_values($associations)); + + // Don't get the id of the item itself when there is no default association language used + if (!$defaultAssocLang) + { + $query->where($db->quoteName('c.id') . ' != :id') + ->bind(':id', $newsfeedid, ParameterType::INTEGER); + } + + $query->leftJoin($db->quoteName('#__languages', 'l'), $db->quoteName('c.language') . ' = ' . $db->quoteName('l.lang_code')); $db->setQuery($query); try @@ -70,18 +81,76 @@ public function association($newsfeedid) throw new \Exception($e->getMessage(), 500); } + if ($defaultAssocLang) + { + // Check if current item is a parent. + $isParent = (array_key_exists($newsfeedid, $items) && ($items[$newsfeedid]->lang_code === $defaultAssocLang)) + ? true + : false; + + // Check if there is a parent in the association and get its id if so. + $parentId = array_key_exists($defaultAssocLang, $associations) + ? $associations[$defaultAssocLang] + : ''; + + // Get parent dates of each item of associations. + $assocParentDates = DefaultAssocLangHelper::getParentDates($associations, 'com_newsfeeds.item'); + } + if ($items) { - foreach ($items as &$item) + foreach ($items as $key => &$item) { + $parentChildInfo = ''; + $labelClass = 'badge-success'; + $url = Route::_('index.php?option=com_newsfeeds&task=newsfeed.edit&id=' . (int) $item->id); + + if ($defaultAssocLang) + { + // Don't continue for parent, because it has been set here before + if ($key === 'parent') + { + continue; + } + + $classParentInfoItems = DefaultAssocLangHelper::setParentAndChildInfos( + $newsfeedid, $items, $key, $item, $defaultAssocLang, $isParent, $parentId, $assocParentDates, $saveHistory + ); + $labelClass = $classParentInfoItems[0]; + $parentChildInfo = $classParentInfoItems[1]; + $items = $classParentInfoItems[2]; + $needsUpdate = $classParentInfoItems[3]; + + $url = Route::_( + DefaultAssocLangHelper::getAssociationUrl( + $item->id, $defaultAssocLang, 'com_newsfeeds.newsfeed', $item->lang_code, $key, $parentId, $needsUpdate + ) + ); + } + $text = strtoupper($item->lang_sef); - $url = Route::_('index.php?option=com_newsfeeds&task=newsfeed.edit&id=' . (int) $item->id); $tooltip = '' . htmlspecialchars($item->language_title, ENT_QUOTES, 'UTF-8') . '
' - . htmlspecialchars($item->title, ENT_QUOTES, 'UTF-8') . '
' . Text::sprintf('JCATEGORY_SPRINTF', $item->category_title); - $classes = 'badge badge-secondary'; + . htmlspecialchars($item->title, ENT_QUOTES, 'UTF-8') . '
' . Text::sprintf('JCATEGORY_SPRINTF', $item->category_title) . $parentChildInfo; + $classes = 'badge ' . $labelClass; $item->link = '' . $text . '' . ''; + + // Reorder the array, so the parent gets to the first place + if ($item->lang_code === $defaultAssocLang) + { + $items = array('parent' => $items[$key]) + $items; + unset($items[$key]); + } + } + + // If a parent doesn't exist, display that there is no association with the default association language. + if ($defaultAssocLang && !$parentId) + { + $link = DefaultAssocLangHelper::addNotAssociatedParentLink($defaultAssocLang, $newsfeedid, 'com_newsfeeds.newsfeed'); + + // Add this on the top of the array + $items = array('parent' => array('link' => $link)) + $items; } } diff --git a/administrator/language/en-GB/en-GB.com_associations.ini b/administrator/language/en-GB/en-GB.com_associations.ini index e7e2a25b6eb68..42746f785484e 100644 --- a/administrator/language/en-GB/en-GB.com_associations.ini +++ b/administrator/language/en-GB/en-GB.com_associations.ini @@ -19,8 +19,10 @@ COM_ASSOCIATIONS_EDIT_HIDE_REFERENCE="Hide Reference" COM_ASSOCIATIONS_EDIT_SHOW_REFERENCE="Show Reference" COM_ASSOCIATIONS_ERROR_NO_ASSOC="The Multilingual Associations component can't be used if the site is not set as multilingual and/or Associations is not enabled in the Language Filter plugin." COM_ASSOCIATIONS_ERROR_NO_TYPE="The item type selected does not exist for this component." +COM_ASSOCIATIONS_FILTER_ASSOCIATION_STATE="All Association States" COM_ASSOCIATIONS_FILTER_SEARCH_DESC="Search an item by its title" COM_ASSOCIATIONS_FILTER_SEARCH_LABEL="Search item" +COM_ASSOCIATIONS_FILTER_SELECT_ASSOCIATION_STATE="- Select Association State -" COM_ASSOCIATIONS_FILTER_SELECT_ITEM="Select Item Type" COM_ASSOCIATIONS_FILTER_SELECT_ITEM_TYPE="- Select Item Type -" COM_ASSOCIATIONS_FILTER_SELECT_LANGUAGE="Select Language" @@ -33,20 +35,26 @@ COM_ASSOCIATIONS_ITEMS="Items" COM_ASSOCIATIONS_NO_ASSOCIATION="There is no association for this language" COM_ASSOCIATIONS_NOTICE_NO_SELECTORS="Please select an Item Type and a reference language to view the associations." COM_ASSOCIATIONS_PURGE="Delete All Associations" -COM_ASSOCIATIONS_PURGE_CONFIRM_PROMPT="Are you sure you want to delete all associations? Confirming will permanently delete them!" +COM_ASSOCIATIONS_PURGE_CONFIRM_PROMPT="Are you sure you want to delete all associations concerning %s? Confirming will permanently delete them!" COM_ASSOCIATIONS_PURGE_FAILED="Failed to delete all associations." COM_ASSOCIATIONS_PURGE_NONE="There were no associations to delete." COM_ASSOCIATIONS_PURGE_SUCCESS="All associations have been deleted." COM_ASSOCIATIONS_REFERENCE_ITEM="Reference" +COM_ASSOCIATIONS_REFERENCE_ITEM_COMPARE_VIEW="Reference - Compare View" COM_ASSOCIATIONS_SAVE_REFERENCE="Save Reference" COM_ASSOCIATIONS_SAVE_TARGET="Save Target" +COM_ASSOCIATIONS_SAVE_AND_UPDATE_TARGET="Update Target and Close" COM_ASSOCIATIONS_SELECT_MENU="- Select Menu -" COM_ASSOCIATIONS_SELECT_TARGET="Select Target" COM_ASSOCIATIONS_SELECT_TARGET_LANGUAGE="- Select Target Language -" +COM_ASSOCIATIONS_STATE_NOT_ASSOCIATED="Not Associated" +COM_ASSOCIATIONS_STATE_OUT_OF_DATE="Out-of-date" +COM_ASSOCIATIONS_STATE_UP_TO_DATE="Up-to-date" COM_ASSOCIATIONS_TABLE_CAPTION="Table of Associations" COM_ASSOCIATIONS_TITLE="Associations" COM_ASSOCIATIONS_TITLE_EDIT="Multilingual Associations: Edit Associations (%1s > %2s)" COM_ASSOCIATIONS_TITLE_LIST="Multilingual Associations (%1s > %2s)" COM_ASSOCIATIONS_TITLE_LIST_SELECT="Multilingual Associations: Select Item Type and Language" +COM_ASSOCIATIONS_UPDATE_ASSOCIATION="Update Association" COM_ASSOCIATIONS_XML_DESCRIPTION="Improved multilingual content management component" COM_ASSOCIATIONS_YOU_ARE_NOT_ALLOWED_TO_CHECKIN_THIS_ITEM="You can't check in this item" diff --git a/administrator/language/en-GB/en-GB.ini b/administrator/language/en-GB/en-GB.ini index a97bba951cf6d..1314c08b7e4e6 100644 --- a/administrator/language/en-GB/en-GB.ini +++ b/administrator/language/en-GB/en-GB.ini @@ -281,6 +281,8 @@ JGLOBAL_ARTICLE_ORDER_DESC="The order that articles will show in." 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_DEFAULT_ASSOC_LANG="Default Association Language" +JGLOBAL_ASSOCIATIONS_DEFAULT_ASSOC_LANG_ITEM="The Default Association Language" JGLOBAL_ASSOCIATIONS_NEW_ITEM_WARNING="To create associations, first save the item." JGLOBAL_ASSOCIATIONS_PROPAGATE_BUTTON="Propagate" JGLOBAL_ASSOCIATIONS_PROPAGATE_FAILED="Failed propagating associations. You may have to select or create them manually." @@ -289,6 +291,10 @@ 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_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_STATE_MIGHT_BE_OUT_OF_DATE_DESC="This item might be out-of-date with the item in the default association language." +JGLOBAL_ASSOCIATIONS_STATE_NOT_ASSOCIATED_DESC="There is no association for the default association language." +JGLOBAL_ASSOCIATIONS_STATE_OUT_OF_DATE_DESC="This item is out-of-date with the item in the default association language." +JGLOBAL_ASSOCIATIONS_STATE_UP_TO_DATE_DESC="This item is up-to-date with the item in the default association language." JGLOBAL_AUTH_ACCESS_DENIED="Access Denied" JGLOBAL_AUTH_ACCESS_GRANTED="Access Granted" JGLOBAL_AUTH_BIND_FAILED="Failed binding to LDAP server" diff --git a/administrator/language/en-GB/en-GB.plg_system_languagefilter.ini b/administrator/language/en-GB/en-GB.plg_system_languagefilter.ini index 5b577e3384b53..358963750ab50 100644 --- a/administrator/language/en-GB/en-GB.plg_system_languagefilter.ini +++ b/administrator/language/en-GB/en-GB.plg_system_languagefilter.ini @@ -9,7 +9,11 @@ PLG_SYSTEM_LANGUAGEFILTER_FIELD_ALTERNATE_META_LABEL="Add Alternate Meta Tags" PLG_SYSTEM_LANGUAGEFILTER_FIELD_AUTOMATIC_CHANGE_LABEL="Automatic Language Change" PLG_SYSTEM_LANGUAGEFILTER_FIELD_COOKIE_LABEL="Cookie Lifetime" PLG_SYSTEM_LANGUAGEFILTER_FIELD_DETECT_BROWSER_LABEL="Language Selection for new Visitors" +PLG_SYSTEM_LANGUAGEFILTER_FIELD_DEFAULT_ASSOC_LANG_LABEL="Default Association Language" +PLG_SYSTEM_LANGUAGEFILTER_FIELD_DEFAULT_ASSOC_LANG_DESC="Note that changing this value will overwrite all existing parent-child relationships with their association states." PLG_SYSTEM_LANGUAGEFILTER_FIELD_ITEM_ASSOCIATIONS_LABEL="Item Associations" +PLG_SYSTEM_LANGUAGEFILTER_FIELD_USE_DEFAULT_ASSOC_LANG_LABEL="Set a Default Association Language?" +PLG_SYSTEM_LANGUAGEFILTER_FIELD_USE_DEFAULT_ASSOC_LANG_DESC="When enabled, a parent-child relationship is added between the languages. This adds additional 'out-of-date' and 'up-to-date' association state to a child depending on its parent. Enable versions to get a comparison view for out-of-date items. If this field is disabled, functions dependent on this data are not available." PLG_SYSTEM_LANGUAGEFILTER_FIELD_XDEFAULT_LABEL="Add x-default Meta Tag" PLG_SYSTEM_LANGUAGEFILTER_FIELD_XDEFAULT_LANGUAGE_LABEL="x-default Language" PLG_SYSTEM_LANGUAGEFILTER_OPTION_DEFAULT_LANGUAGE="Default frontend language" diff --git a/administrator/templates/atum/scss/blocks/_global.scss b/administrator/templates/atum/scss/blocks/_global.scss index 0ffefe53ba109..f8677f0dfb44a 100644 --- a/administrator/templates/atum/scss/blocks/_global.scss +++ b/administrator/templates/atum/scss/blocks/_global.scss @@ -132,11 +132,34 @@ body .container-main { // Multilingual associations specific .item-associations { padding: 0; -} + margin-bottom: 0; + + li, ul { + display: inline-block; + list-style: none; + padding: 0; + } + + &.parent { + text-align: center; + + div[role="tooltip"] { + text-align: left; + } + } + + hr { + margin-top: 0.2rem; + margin-bottom: 0.2rem; + } -.item-associations li { - display: inline-block; - list-style: none; + .parent-language { + + & > a { + border-radius: 0; + padding-top: 0.2rem; + } + } } // Quickicon specific diff --git a/administrator/templates/atum/scss/blocks/_searchtools.scss b/administrator/templates/atum/scss/blocks/_searchtools.scss index 9da8a3644f2fc..373b63591c63b 100644 --- a/administrator/templates/atum/scss/blocks/_searchtools.scss +++ b/administrator/templates/atum/scss/blocks/_searchtools.scss @@ -82,6 +82,14 @@ } } + .js-stools-container-selector-second { + margin-right: 8px; + + html[dir=rtl] & { + margin-left: 8px; + } + } + .js-stools-container-bar { .btn-toolbar { diff --git a/build/media_source/com_associations/css/sidebyside.css b/build/media_source/com_associations/css/sidebyside.css index c2ddde2af6da2..27df7eb024842 100644 --- a/build/media_source/com_associations/css/sidebyside.css +++ b/build/media_source/com_associations/css/sidebyside.css @@ -58,6 +58,21 @@ html[dir=rtl] .target-text { float: right; } +.diff .added { + background-color: #a6f3a6; + border-radius: .2rem; +} + +.diff .removed { + background-color: #f8cbcb; + border-radius: .2rem; +} + +.diff .same { + background-color: unset; + border-radius: 0; +} + /* Responsive layout */ @media (max-width: 767px) { .sidebyside .outer-panel { diff --git a/build/media_source/com_associations/js/admin-associations-default.es6.js b/build/media_source/com_associations/js/admin-associations-default.es6.js deleted file mode 100644 index cf078ebdd53f1..0000000000000 --- a/build/media_source/com_associations/js/admin-associations-default.es6.js +++ /dev/null @@ -1,18 +0,0 @@ -/** - * @copyright Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved. - * @license GNU General Public License version 2 or later; see LICENSE.txt - */ -Joomla.submitbutton = (pressbutton) => { - if (pressbutton === 'associations.purge') { - // eslint-disable-next-line no-restricted-globals - if (confirm(Joomla.JText._('COM_ASSOCIATIONS_PURGE_CONFIRM_PROMPT'))) { - Joomla.submitform(pressbutton); - } else { - return false; - } - } else { - Joomla.submitform(pressbutton); - } - - return true; -}; diff --git a/build/media_source/com_associations/js/admin-compare-assoc-parent.es6.js b/build/media_source/com_associations/js/admin-compare-assoc-parent.es6.js new file mode 100644 index 0000000000000..0a9fcc98fc6b9 --- /dev/null +++ b/build/media_source/com_associations/js/admin-compare-assoc-parent.es6.js @@ -0,0 +1,46 @@ +/** + * @copyright Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved. + * @license GNU General Public License version 2 or later; see LICENSE.txt + */ +(() => { + 'use strict'; + + const compare = (original, changed) => { + const display = changed.nextElementSibling; + const tagName = 'mark'; + let className = 'same'; + let tagElement = null; + + const diff = window.JsDiff.diffWords(original.textContent, changed.textContent); + const fragment = document.createDocumentFragment(); + + diff.forEach((part) => { + if (part.added) { + className = 'added'; + } + + if (part.removed) { + className = 'removed'; + } + + tagElement = document.createElement(tagName); + tagElement.setAttribute('class', className); + tagElement.appendChild(document.createTextNode(part.value)); + fragment.appendChild(tagElement); + }); + + display.appendChild(fragment); + }; + + const onBoot = () => { + const diffs = [].slice.call(document.querySelectorAll('.original')); + diffs.forEach((fragment) => { + compare(fragment, fragment.nextElementSibling); + }); + + // Cleanup + document.removeEventListener('DOMContentLoaded', onBoot); + }; + + document.addEventListener('DOMContentLoaded', onBoot); +})(); diff --git a/build/media_source/com_associations/js/sidebysideupdate.es6.js b/build/media_source/com_associations/js/sidebysideupdate.es6.js new file mode 100644 index 0000000000000..65834eeaf8404 --- /dev/null +++ b/build/media_source/com_associations/js/sidebysideupdate.es6.js @@ -0,0 +1,60 @@ +/** + * @copyright Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved. + * @license GNU General Public License version 2 or later; see LICENSE.txt + */ +Joomla = window.Joomla || {}; + +((Joomla, document) => { + 'use strict'; + + document.addEventListener('DOMContentLoaded', () => { + const referenceIframe = document.getElementById('reference-association'); + const targetIframe = document.getElementById('target-association'); + + // Saving target, send the save action to the target iframe. + Joomla.submitbutton = (task) => { + // Using close button, normal joomla submit. + if (task === 'association.cancel') { + Joomla.submitform(task); + } else if (task === 'defaultAssocLang.update') { + Joomla.submitform(task); + } else { + window.frames['target-association'].Joomla.submitbutton(`${document.getElementById('adminForm').getAttribute('data-associatedview')}, .apply`); + document.getElementById('updateChild').click(); + } + }; + + // Attach behaviour to reference frame load event. + referenceIframe.addEventListener('load', () => { + const reference = referenceIframe.contentDocument; + const referenceDiff = reference.querySelector('#diff'); + // Waiting until the reference has loaded before loading the target to avoid race conditions + + if (!referenceDiff) { + // Disable language field. + reference.querySelector('#jform_language').setAttribute('disabled', ''); + + // Remove modal buttons on the reference + reference.querySelector('#associations').querySelectorAll('.btn').forEach(e => e.parentNode.removeChild(e)); + } + + // Iframe load finished, hide Joomla loading layer. + Joomla.loadingLayer('hide'); + }); + + targetIframe.addEventListener('load', () => { + if (targetIframe.getAttribute('src') !== '') { + const target = targetIframe.contentDocument; + + // Update language field with the selected language and then disable it. + target.querySelector('#jform_language').setAttribute('disabled', ''); + + // Remove modal buttons on the reference + target.querySelector('#associations').querySelectorAll('.btn').forEach(e => e.parentNode.removeChild(e)); + + // Iframe load finished, hide Joomla loading layer. + Joomla.loadingLayer('hide'); + } + }); + }); +})(Joomla, document); diff --git a/installation/sql/mysql/joomla.sql b/installation/sql/mysql/joomla.sql index 56f5b81922026..bd958e68441e2 100644 --- a/installation/sql/mysql/joomla.sql +++ b/installation/sql/mysql/joomla.sql @@ -120,6 +120,8 @@ CREATE TABLE IF NOT EXISTS `#__associations` ( `id` int(11) 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.', + `parent_id` int(11) NOT NULL DEFAULT -1 COMMENT 'The parent of an association.', + `parent_date` datetime COMMENT 'The save or modified date of the parent.', PRIMARY KEY (`context`,`id`), KEY `idx_key` (`key`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 DEFAULT COLLATE=utf8mb4_unicode_ci; diff --git a/installation/sql/postgresql/joomla.sql b/installation/sql/postgresql/joomla.sql index b35c6cbb5cea4..0cea9919a2899 100644 --- a/installation/sql/postgresql/joomla.sql +++ b/installation/sql/postgresql/joomla.sql @@ -124,6 +124,8 @@ CREATE TABLE IF NOT EXISTS "#__associations" ( "id" int NOT NULL, "context" varchar(50) NOT NULL, "key" char(32) NOT NULL, + "parent_id" integer DEFAULT -1 NOT NULL, + "parent_date" timestamp without time zone, CONSTRAINT "#__associations_idx_context_id" PRIMARY KEY ("context", "id") ); CREATE INDEX "#__associations_idx_key" ON "#__associations" ("key"); @@ -131,6 +133,8 @@ 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"."parent_id" IS 'The parent of an association.'; +COMMENT ON COLUMN "#__associations"."parent_date" IS 'The save or modified date of the parent.'; -- -- Table structure for table `#__banners` diff --git a/installation/src/Model/LanguagesModel.php b/installation/src/Model/LanguagesModel.php index 4726583540d4b..01572f5cfe383 100644 --- a/installation/src/Model/LanguagesModel.php +++ b/installation/src/Model/LanguagesModel.php @@ -17,6 +17,7 @@ use Joomla\CMS\Form\Form; use Joomla\CMS\Installer\Installer; use Joomla\CMS\Installer\InstallerHelper; +use Joomla\CMS\Language\Associations; use Joomla\CMS\Language\Language; use Joomla\CMS\Language\LanguageHelper; use Joomla\CMS\Language\Text; @@ -24,6 +25,7 @@ use Joomla\CMS\Updater\Update; use Joomla\CMS\Updater\Updater; use Joomla\Database\Exception\ExecutionFailureException; +use Joomla\Database\ParameterType; /** * Language Installer model for the Joomla Core Installer. @@ -1405,27 +1407,38 @@ public function addBlogMenuItem($itemLanguage, $categoryId) public function addAssociations($groupedAssociations) { $db = Factory::getDbo(); + $defaultAssocLang = Associations::getDefaultAssocLang(); foreach ($groupedAssociations as $context => $associations) { - $key = md5(json_encode($associations)); - $query = $db->getQuery(true) - ->insert('#__associations'); + // If there is an association item with the default association language, get its id. + $parentId = $associations[$defaultAssocLang] ?? ''; + $key = md5(json_encode($associations)); foreach ($associations as $language => $id) { - $query->values(((int) $id) . ',' . $db->quote($context) . ',' . $db->quote($key)); - } - - $db->setQuery($query); - - try - { - $db->execute(); - } - catch (\RuntimeException $e) - { - return false; + // If there is no parent within this association, then reset the parent_id to -1 + // Otherwise, if the associated item is a parent set the parent_id to 0, otherwise to the parentId. + $parentIdValue = $parentId ? ($parentId === $id ? 0 : $parentId) : -1; + + $query = $db->getQuery(true) + ->insert($db->quoteName('#__associations')) + ->values((' :id, :context, :key, :parentId, NULL')) + ->bind(':id', $id, ParameterType::INTEGER) + ->bind(':context', $context) + ->bind(':key', $key) + ->bind(':parentId', $parentIdValue, ParameterType::INTEGER); + + $db->setQuery($query); + + try + { + $db->execute(); + } + catch (\RuntimeException $e) + { + return false; + } } } diff --git a/layouts/joomla/content/associations.php b/layouts/joomla/content/associations.php index 9bd8cd3fa1138..f61a0ce4653f6 100644 --- a/layouts/joomla/content/associations.php +++ b/layouts/joomla/content/associations.php @@ -12,11 +12,28 @@ $items = $displayData; if (!empty($items)) : ?> -
    - $item) : ?> -
  • - link; ?> -
  • - + +
      +
    • + link; ?> +
      +
        + $item) : ?> + +
      • + link; ?> +
      • + + +
      +
    • + +
        + $item) : ?> +
      • + link; ?> +
      • + +
      diff --git a/libraries/src/Language/Associations.php b/libraries/src/Language/Associations.php index fe753a8a700bc..3a57e2aba19b4 100644 --- a/libraries/src/Language/Associations.php +++ b/libraries/src/Language/Associations.php @@ -43,6 +43,8 @@ public static function getAssociations($extension, $tablename, $context, $id, $p $advClause = array() ) { + $defaultAssocLang = self::getDefaultAssocLang(); + // To avoid doing duplicate database queries. static $multilanguageAssociations = array(); @@ -126,11 +128,19 @@ public static function getAssociations($extension, $tablename, $context, $id, $p { foreach ($items as $tag => $item) { - // Do not return itself as result - if ((int) $item->{$pk} !== $id) + if ($defaultAssocLang) { + // If a default association language is set, we need all items of an associations $multilanguageAssociations[$queryKey][$tag] = $item; } + else + { + // Do not return itself as result + if ((int) $item->{$pk} !== $id) + { + $multilanguageAssociations[$queryKey][$tag] = $item; + } + } } } } @@ -173,4 +183,39 @@ public static function isEnabled() return $enabled; } + + /** + * Method to get the default association language parameter for associations. + * + * @return string empty if not set, lang_code otherwise. + * + * @since 4.0 + */ + public static function getDefaultAssocLang() + { + // Flag to avoid doing multiple database queries. + static $tested = false; + + // Status of default association language parameter. + static $defaultAssocLang = ''; + + if (self::isEnabled()) + { + // If already tested, don't test again. + if (!$tested) + { + $plugin = PluginHelper::getPlugin('system', 'languagefilter'); + + if (!empty($plugin)) + { + $params = new Registry($plugin->params); + $defaultAssocLang = $params->get('default_assoc_lang'); + } + + $tested = true; + } + } + + return $defaultAssocLang ?? ''; + } } diff --git a/libraries/src/MVC/Model/AdminModel.php b/libraries/src/MVC/Model/AdminModel.php index 0cd2934107de2..c33bc1d362c85 100644 --- a/libraries/src/MVC/Model/AdminModel.php +++ b/libraries/src/MVC/Model/AdminModel.php @@ -23,6 +23,8 @@ use Joomla\CMS\Router\Route; use Joomla\CMS\Table\Table; use Joomla\CMS\UCM\UCMType; +use Joomla\Component\Associations\Administrator\Helper\DefaultAssocLangHelper; +use Joomla\Database\ParameterType; use Joomla\Registry\Registry; use Joomla\String\StringHelper; use Joomla\Utilities\ArrayHelper; @@ -1319,6 +1321,12 @@ public function save($data) ); } + // Get association params before they get deleted + if ($associations) + { + $assocParentDates = DefaultAssocLangHelper::getParentDates($associations, $this->associationsContext); + } + // Get associationskey for edited item $db = $this->getDbo(); $query = $db->getQuery(true) @@ -1356,18 +1364,49 @@ public function save($data) if (count($associations) > 1) { + // If there is an associated item with the default association language, then get its id + $defaultAssocLang = Associations::getDefaultAssocLang(); + $parentId = $associations[$defaultAssocLang] ?? ''; + + // Get id of the item that get saved + $dataId = (int) $table->id; + + // Get the latest modified date of the parent + $parentModified = DefaultAssocLangHelper::getParentModifiedDate($parentId, $table->getTableName(), $table->typeAlias); + // Adding new association for these items - $key = md5(json_encode($associations)); - $query = $db->getQuery(true) - ->insert('#__associations'); + $key = md5(json_encode($associations) . $context); foreach ($associations as $id) { - $query->values(((int) $id) . ',' . $db->quote($this->associationsContext) . ',' . $db->quote($key)); - } + $parentIdAndDateValues = DefaultAssocLangHelper::getParentValues( + $id, $dataId, $parentId, $parentModified, $assocParentDates, $old_key + ); + $parentIdValue = $parentIdAndDateValues[0]; + $parentDateValue = $parentIdAndDateValues[1] === 'NULL' ? 'NULL' : Factory::getDate($parentIdAndDateValues[1])->toSql(); + + $query = $db->getQuery(true) + ->insert($db->quoteName('#__associations')); - $db->setQuery($query); - $db->execute(); + // Save NULL for parent_date + if ($parentDateValue === 'NULL') + { + $query->values((' :id, :context, :key, :parentId, NULL')); + } + else + { + $query->values((' :id, :context, :key, :parentId, :parentDate')) + ->bind(':parentDate', $parentDateValue); + } + + $query->bind(':id', $id, ParameterType::INTEGER) + ->bind(':context', $this->associationsContext) + ->bind(':key', $key) + ->bind(':parentId', $parentIdValue, ParameterType::INTEGER); + + $db->setQuery($query); + $db->execute(); + } } } @@ -1647,6 +1686,20 @@ public function editAssociations($data) } } + $defaultAssocLang = Associations::getDefaultAssocLang(); + $isParent = $data['language'] === $defaultAssocLang; + + // If a default association language is set and the current item is a child item, then open his parent as reference and the child as target + if ($defaultAssocLang && !$isParent) + { + // If there is an associated parent, change reference id. + if ($data['associations'][$defaultAssocLang]) + { + $id = $data['associations'][$defaultAssocLang]; + $target = '&target=' . $data['language'] . '%3A' . $data['id'] . '%3Aedit'; + } + } + $app->redirect( Route::_( 'index.php?option=com_associations&view=association&layout=edit&itemtype=' . $this->typeAlias diff --git a/plugins/sampledata/multilang/multilang.php b/plugins/sampledata/multilang/multilang.php index 11416a25f2bbb..958520651ce40 100644 --- a/plugins/sampledata/multilang/multilang.php +++ b/plugins/sampledata/multilang/multilang.php @@ -20,6 +20,7 @@ use Joomla\CMS\Plugin\CMSPlugin; use Joomla\CMS\Table\Table; use Joomla\Database\Exception\ExecutionFailureException; +use Joomla\Database\ParameterType; /** * Sampledata - Multilang Plugin @@ -517,7 +518,9 @@ private function enablePlugin($pluginName) . '"item_associations":"1",' . '"remove_default_prefix":"0",' . '"lang_cookie":"0",' - . '"alternate_meta":"1"' + . '"alternate_meta":"1",' + . '"use_default_assoc_lang":0,' + . '"default_assoc_lang":""' . '}'; $query ->clear() @@ -901,24 +904,29 @@ private function addAssociations($groupedAssociations) foreach ($groupedAssociations as $context => $associations) { - $key = md5(json_encode($associations)); - $query = $db->getQuery(true) - ->insert('#__associations'); + $key = md5(json_encode($associations)); + $parentId = -1; foreach ($associations as $language => $id) { - $query->values(((int) $id) . ',' . $db->quote($context) . ',' . $db->quote($key)); - } - - $db->setQuery($query); - - try - { - $db->execute(); - } - catch (\RuntimeException $e) - { - return false; + $query = $db->getQuery(true) + ->insert($db->quoteName('#__associations')) + ->values((' :id, :context, :key, :parentId, NULL')) + ->bind(':id', $id, ParameterType::INTEGER) + ->bind(':context', $context) + ->bind(':key', $key) + ->bind(':parentId', $parentId, ParameterType::INTEGER); + + $db->setQuery($query); + + try + { + $db->execute(); + } + catch (\RuntimeException $e) + { + return false; + } } } diff --git a/plugins/system/languagefilter/Field/ContentsitelanguageField.php b/plugins/system/languagefilter/Field/ContentsitelanguageField.php new file mode 100644 index 0000000000000..0835815ee8a72 --- /dev/null +++ b/plugins/system/languagefilter/Field/ContentsitelanguageField.php @@ -0,0 +1,67 @@ +get('site'); + $contentLanguages = LanguageHelper::getContentLanguages(array(0, 1)); + $options = array(); + + foreach ($contentLanguages as $langCode) + { + // Add the information to the language if it is the default site language + if ($langCode->lang_code == $defaultSiteLanguage) + { + $options[] = HTMLHelper::_('select.option', $langCode->lang_code, $langCode->title + . ' - ' . Text::_('PLG_SYSTEM_LANGUAGEFILTER_OPTION_DEFAULT_LANGUAGE') + ); + } + else + { + $options[] = HTMLHelper::_('select.option', $langCode->lang_code, $langCode->title); + } + } + + return $options; + } +} diff --git a/plugins/system/languagefilter/languagefilter.php b/plugins/system/languagefilter/languagefilter.php index 7ef448fc93721..9612a92a13402 100644 --- a/plugins/system/languagefilter/languagefilter.php +++ b/plugins/system/languagefilter/languagefilter.php @@ -17,6 +17,7 @@ use Joomla\CMS\Event\BeforeExecuteEvent; use Joomla\CMS\Factory; use Joomla\CMS\Filesystem\Folder; +use Joomla\CMS\Helper\ContentHistoryHelper; use Joomla\CMS\Language\Associations; use Joomla\CMS\Language\Language; use Joomla\CMS\Language\LanguageHelper; @@ -25,10 +26,14 @@ use Joomla\CMS\Plugin\CMSPlugin; use Joomla\CMS\Router\Route; use Joomla\CMS\Router\Router; +use Joomla\CMS\Table\Table; use Joomla\CMS\Uri\Uri; +use Joomla\Component\Associations\Administrator\Helper\AssociationsHelper; use Joomla\Component\Menus\Administrator\Helper\MenusHelper; +use Joomla\Database\ParameterType; use Joomla\Registry\Registry; use Joomla\String\StringHelper; +use Joomla\Utilities\ArrayHelper; /** * Joomla! Language Filter Plugin. @@ -960,4 +965,309 @@ private function getLanguageCookie() return $languageCode; } + + // Events for Default Association Language: + + /** + * Before Saving extensions + * Method is called when an extension is going to be saved. + * Change parameters for default association language as they depends on other parameters. + * + * @param string $context The extension + * @param JTable $table DataBase Table object + * + * @return void + * + * @since 4.0.0 + */ + public function onExtensionBeforeSave($context, $table) + { + if ($context !== 'com_plugins.plugin') + { + return true; + } + + $tableElement = $table->element; + + if ($tableElement !== 'languagefilter') + { + return true; + } + + // Get the parameters which are to be saved. + $params = json_decode($table->params); + $pluginStatus = $table->enabled; + $itemAssocStatus = $params->item_associations ?? false; + + // If the plugin and the parameter item_associations are enabled then set the correct value for the default association language. + if ($pluginStatus && $itemAssocStatus) + { + $params->default_assoc_lang = ($params->use_default_assoc_lang === 1) + ? $params->default_assoc_lang + : ''; + } + // Reset parameters for default association language + else + { + $params->use_default_assoc_lang = ''; + $params->default_assoc_lang = ''; + } + + // Check if there were changes for the default association language. + $this->hasAssocLangChanged = ($params->default_assoc_lang === $this->params->get('default_assoc_lang')) ? false : true; + + return $table->params = json_encode($params); + } + + /** + * After save extensions + * Method is called when an extension has been saved. + * + * @param string $context The extension + * @param JTable $table DataBase Table object + * + * @return void + * + * @since 4.0.0 + */ + public function onExtensionAfterSave($context, $table) + { + if ($context !== 'com_plugins.plugin') + { + return true; + } + + $tableElement = $table->element; + + if ($tableElement !== 'languagefilter') + { + return true; + } + + // Get the parameters which have been saved + $params = json_decode($table->params); + + // Only set association parent items if the default association language has changed. + if ($this->hasAssocLangChanged) + { + $this->_setParentItem($params->default_assoc_lang); + } + + unset($this->hasAssocLangChanged); + } + + /** + * Method to set the parent id and modified dates to all associated items. + * This resets all current parent ids and modified dates and set these new. + * Parent and children will be up-to-date, as they get the same modified date. + * + * @param string $language The default association language + * + * @return boolean Returns true on success, false on failure. + * + * @throws Exception + * + * @since 4.0.0 + */ + private function _setParentItem($language) + { + $db = Factory::getDbo(); + $defaultAssocLang = $language; + + // If there is no default association language set, set all parent ids to -1 and parent dates to null + if (!$defaultAssocLang) + { + $resetQuery = $db->getQuery(true) + ->update($db->quoteName('#__associations')) + ->set($db->quoteName('parent_id') . ' = -1') + ->set($db->quoteName('parent_date') . ' = NULL'); + $db->setQuery($resetQuery); + + try + { + $db->execute(); + } + catch (ExecutionFailureException $e) + { + return false; + } + } + else + { + // Get every different key + $keyQuery = $db->getQuery(true) + ->select($db->quoteName('key')) + ->from($db->quoteName('#__associations')) + ->group($db->quoteName('key')) + ->having('COUNT(*) > 1'); + $assocKeys = $db->setQuery($keyQuery)->loadColumn(); + + foreach ($assocKeys as $value) + { + // Get the context of this association with the current key + $contextQuery = $db->getQuery(true) + ->select($db->quoteName('context')) + ->from($db->quoteName('#__associations')) + ->where($db->quoteName('key') . ' = :key') + ->bind(':key', $value); + $assocContext = $db->setQuery($contextQuery)->loadResult(); + + // Get the right table to search for modified date or save_date from history + + $checkCategoryComponent = ''; + $component = explode('.', $assocContext)[0]; + + if ($component === 'com_categories') + { + $fromTable = $db->quoteName('#__categories', 'e'); + $modified = $db->quoteName('e.modified_time'); + $checkCategoryComponent = $db->quoteName('e.extension'); + } + else + { + $extension = AssociationsHelper::getSupportedExtension($component); + $extensionType = $extension['helper']->getItemTypes()[0]; + $extensionTable = $extension['types'][$extensionType]->get('details')['tables']; + $tableName = $extensionTable[array_keys($extensionTable)[0]]; + $fromTable = $db->quoteName($tableName, 'e'); + $typeAlias = $component . '.' . $extensionType; + $modified = ($component === 'com_menus') ? '' : $db->quoteName('e.modified'); + } + + // Get ids of items with the default association language + $subQuery = $db->getQuery(true) + ->select($db->quoteName('e.id')) + ->from($fromTable) + ->where($db->quoteName('e.language') . ' = :lang') + ->bind(':lang', $defaultAssocLang); + $idResults = $db->setQuery($subQuery)->loadColumn(); + + // Get parent id of an item that has the default association language + $parentQuery = $db->getQuery(true) + ->select($db->quoteName('id')) + ->from($db->quoteName('#__associations')) + ->whereIn($db->quoteName('id'), $idResults, ParameterType::INTEGER) + ->where($db->quoteName('key') . ' = :key') + ->bind(':key', $value); + $parentId = $db->setQuery($parentQuery)->loadResult(); + + // Get modified date of parent + if ($modified) + { + // Get the context of this category + if ($checkCategoryComponent) + { + $categoryQuery = $db->getQuery(true) + ->select($checkCategoryComponent) + ->from($fromTable) + ->where($db->quoteName('id') . ' = :id') + ->bind(':id', $parentId, ParameterType::INTEGER); + $categoryParentExtension = $db->setQuery($categoryQuery)->loadResult(); + $typeAlias = $categoryParentExtension . '.category'; + } + + $component = $categoryParentExtension ?? $component; + $saveHistory = ComponentHelper::getParams($component)->get('save_history', 0); + + // If versions are enabled get the save_date of the parent item from history table otherwise use the modified date + if ($saveHistory) + { + $typeId = Table::getInstance('ContentType')->getTypeId($typeAlias); + $parentHistory = ContentHistoryHelper::getHistory($typeId, $parentId); + + // Latest saved date of the parent item + $parentModified = $parentHistory[0]->save_date; + } + else + { + $parentModQuery = $db->getQuery(true) + ->select($modified) + ->from($fromTable) + ->where($db->quoteName('id') . ' = :id') + ->bind(':id', $parentId, ParameterType::INTEGER); + $parentModified = $db->setQuery($parentModQuery)->loadResult(); + } + + $parentModifiedSql = Factory::getDate($parentModified)->toSql(); + } + + $parentId = $parentId ?? -1; + + // Set the id and the modified date of the parent item. + $query = $db->getQuery(true) + ->update($db->quoteName('#__associations')) + ->set($db->quoteName('parent_id') . ' = ' . $db->quote(0)); + + if ($modified) + { + $query->set($db->quoteName('parent_date') . ' = :parentDate') + ->bind(':parentDate', $parentModifiedSql); + } + else + { + $query->set($db->quoteName('parent_date') . ' = NULL'); + } + + $query->where( + [ + $db->quoteName('id') . ' = :id', + $db->quoteName('key') . ' = :key', + $db->quoteName('context') . ' = :context' + ] + ) + ->bind(':id', $parentId, ParameterType::INTEGER) + ->bind(':key', $value) + ->bind(':context', $assocContext); + $db->setQuery($query); + + try + { + $db->execute(); + } + catch (ExecutionFailureException $e) + { + return false; + } + + // Set the id and modified date of the parent item to the children + $query = $db->getQuery(true) + ->update($db->quoteName('#__associations')) + ->set($db->quoteName('parent_id') . ' = :parentId'); + + if ($modified) + { + $query->set($db->quoteName('parent_date') . ' = :parentDate') + ->bind(':parentDate', $parentModifiedSql); + } + else + { + $query->set($db->quoteName('parent_date') . ' = NULL'); + } + + $query->where( + [ + $db->quoteName('id') . ' <> :id', + $db->quoteName('key') . ' = :key', + $db->quoteName('context') . ' = :context' + ] + ) + ->bind(':parentId', $parentId, ParameterType::INTEGER) + ->bind(':id', $parentId, ParameterType::INTEGER) + ->bind(':key', $value) + ->bind(':context', $assocContext); + $db->setQuery($query); + + try + { + $db->execute(); + } + catch (ExecutionFailureException $e) + { + return false; + } + } + } + + return true; + } } diff --git a/plugins/system/languagefilter/languagefilter.xml b/plugins/system/languagefilter/languagefilter.xml index fea021cbea836..c7aff4fbac573 100644 --- a/plugins/system/languagefilter/languagefilter.xml +++ b/plugins/system/languagefilter/languagefilter.xml @@ -9,8 +9,10 @@ www.joomla.org 3.0.0 PLG_SYSTEM_LANGUAGEFILTER_XML_DESCRIPTION + Joomla\Plugin\System\Languagefilter languagefilter.php + Field en-GB.plg_system_languagefilter.ini @@ -42,6 +44,11 @@ + + JYES + + + + + + + + + - \ No newline at end of file +