first commit

This commit is contained in:
2025-06-17 11:53:18 +02:00
commit 9f0f7ba12b
8804 changed files with 1369176 additions and 0 deletions

View File

@ -0,0 +1,90 @@
<?php
/**
* @package Joomla.Administrator
* @subpackage com_associations
*
* @copyright (C) 2017 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
namespace Joomla\Component\Associations\Administrator\Controller;
use Joomla\CMS\Language\Text;
use Joomla\CMS\MVC\Controller\FormController;
use Joomla\CMS\Router\Route;
use Joomla\Component\Associations\Administrator\Helper\AssociationsHelper;
// phpcs:disable PSR1.Files.SideEffects
\defined('_JEXEC') or die;
// phpcs:enable PSR1.Files.SideEffects
/**
* Association edit controller class.
*
* @since 3.7.0
*/
class AssociationController extends FormController
{
/**
* Method to edit an existing record.
*
* @param string $key The name of the primary key of the URL variable.
* @param string $urlVar The name of the URL variable if different from the primary key
* (sometimes required to avoid router collisions).
*
* @return FormController|boolean True if access level check and checkout passes, false otherwise.
*
* @since 3.7.0
*/
public function edit($key = null, $urlVar = null)
{
list($extensionName, $typeName) = explode('.', $this->input->get('itemtype', '', 'string'), 2);
$id = $this->input->get('id', 0, 'int');
// Check if reference item can be edited.
if (!AssociationsHelper::allowEdit($extensionName, $typeName, $id)) {
$this->setMessage(Text::_('JLIB_APPLICATION_ERROR_EDIT_NOT_PERMITTED'), 'error');
$this->setRedirect(Route::_('index.php?option=com_associations&view=associations', false));
return false;
}
return parent::display();
}
/**
* Method for canceling the edit action
*
* @param string $key The name of the primary key of the URL variable.
*
* @return void
*
* @since 3.7.0
*/
public function cancel($key = null)
{
$this->checkToken();
list($extensionName, $typeName) = explode('.', $this->input->get('itemtype', '', 'string'), 2);
// Only check in, if component item type allows to check out.
if (AssociationsHelper::typeSupportsCheckout($extensionName, $typeName)) {
$ids = [];
$targetId = $this->input->get('target-id', '', 'string');
if ($targetId !== '') {
$ids = array_unique(explode(',', $targetId));
}
$ids[] = $this->input->get('id', 0, 'int');
foreach ($ids as $key => $id) {
AssociationsHelper::getItem($extensionName, $typeName, $id)->checkIn();
}
}
$this->setRedirect(Route::_('index.php?option=com_associations&view=associations', false));
}
}

View File

@ -0,0 +1,139 @@
<?php
/**
* @package Joomla.Administrator
* @subpackage com_associations
*
* @copyright (C) 2017 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
namespace Joomla\Component\Associations\Administrator\Controller;
use Joomla\CMS\Language\Text;
use Joomla\CMS\MVC\Controller\AdminController;
use Joomla\CMS\Router\Route;
use Joomla\Component\Associations\Administrator\Helper\AssociationsHelper;
// phpcs:disable PSR1.Files.SideEffects
\defined('_JEXEC') or die;
// phpcs:enable PSR1.Files.SideEffects
/**
* Associations controller class.
*
* @since 3.7.0
*/
class AssociationsController extends AdminController
{
/**
* The URL view list variable.
*
* @var string
*
* @since 3.7.0
*/
protected $view_list = 'associations';
/**
* Proxy for getModel.
*
* @param string $name The model name. Optional.
* @param string $prefix The class prefix. Optional.
* @param array $config The array of possible config values. Optional.
*
* @return \Joomla\CMS\MVC\Model\BaseDatabaseModel|boolean
*
* @since 3.7.0
*/
public function getModel($name = 'Associations', $prefix = 'Administrator', $config = ['ignore_request' => true])
{
return parent::getModel($name, $prefix, $config);
}
/**
* Method to purge the associations table.
*
* @return void
*
* @since 3.7.0
*/
public function purge()
{
$this->checkToken();
$this->getModel('associations')->purge();
$this->setRedirect(Route::_('index.php?option=' . $this->option . '&view=' . $this->view_list, false));
}
/**
* Method to delete the orphans from the associations table.
*
* @return void
*
* @since 3.7.0
*/
public function clean()
{
$this->checkToken();
$this->getModel('associations')->clean();
$this->setRedirect(Route::_('index.php?option=' . $this->option . '&view=' . $this->view_list, false));
}
/**
* Method to check in an item from the association item overview.
*
* @return void
*
* @since 3.7.1
*/
public function checkin()
{
// Set the redirect so we can just stop processing when we find a condition we can't process
$this->setRedirect(Route::_('index.php?option=' . $this->option . '&view=' . $this->view_list, false));
// Figure out if the item supports checking and check it in
list($extensionName, $typeName) = explode('.', $this->input->get('itemtype'));
$extension = AssociationsHelper::getSupportedExtension($extensionName);
$types = $extension->get('types');
if (!\array_key_exists($typeName, $types)) {
return;
}
if (AssociationsHelper::typeSupportsCheckout($extensionName, $typeName) === false) {
// How on earth we came to that point, eject internet
return;
}
$cid = (array) $this->input->get('cid', [], 'int');
if (empty($cid)) {
// Seems we don't have an id to work with.
return;
}
// We know the first element is the one we need because we don't allow multi selection of rows
$id = $cid[0];
if ($id === 0) {
// Seems we don't have an id to work with.
return;
}
if (AssociationsHelper::canCheckinItem($extensionName, $typeName, $id) === true) {
$item = AssociationsHelper::getItem($extensionName, $typeName, $id);
$item->checkIn($id);
return;
}
$this->setRedirect(
Route::_('index.php?option=' . $this->option . '&view=' . $this->view_list),
Text::_('COM_ASSOCIATIONS_YOU_ARE_NOT_ALLOWED_TO_CHECKIN_THIS_ITEM')
);
}
}

View File

@ -0,0 +1,34 @@
<?php
/**
* @package Joomla.Administrator
* @subpackage com_associations
*
* @copyright (C) 2017 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
namespace Joomla\Component\Associations\Administrator\Controller;
use Joomla\CMS\MVC\Controller\BaseController;
// phpcs:disable PSR1.Files.SideEffects
\defined('_JEXEC') or die;
// phpcs:enable PSR1.Files.SideEffects
/**
* Component Controller
*
* @since 3.7.0
*/
class DisplayController extends BaseController
{
/**
* The default view.
*
* @var string
*
* @since 3.7.0
*/
protected $default_view = 'associations';
}

View File

@ -0,0 +1,60 @@
<?php
/**
* @package Joomla.Administrator
* @subpackage com_associations
*
* @copyright (C) 2017 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
namespace Joomla\Component\Associations\Administrator\Dispatcher;
use Joomla\CMS\Access\Exception\NotAllowed;
use Joomla\CMS\Dispatcher\ComponentDispatcher;
use Joomla\CMS\Language\Text;
use Joomla\Component\Associations\Administrator\Helper\AssociationsHelper;
// phpcs:disable PSR1.Files.SideEffects
\defined('_JEXEC') or die;
// phpcs:enable PSR1.Files.SideEffects
/**
* ComponentDispatcher class for com_associations
*
* @since 4.0.0
*/
class Dispatcher extends ComponentDispatcher
{
/**
* Method to check component access permission
*
* @since 4.0.0
*
* @return void
*
* @throws \Exception|NotAllowed
*/
protected function checkAccess()
{
parent::checkAccess();
// Check if user has permission to access the component item type.
$itemType = $this->input->get('itemtype', '', 'string');
if ($itemType !== '') {
list($extensionName, $typeName) = explode('.', $itemType);
if (!AssociationsHelper::hasSupport($extensionName)) {
throw new \Exception(
Text::sprintf('COM_ASSOCIATIONS_COMPONENT_NOT_SUPPORTED', $this->app->getLanguage()->_($extensionName)),
404
);
}
if (!$this->app->getIdentity()->authorise('core.manage', $extensionName)) {
throw new NotAllowed($this->app->getLanguage()->_('JERROR_ALERTNOAUTHOR'), 403);
}
}
}
}

View File

@ -0,0 +1,104 @@
<?php
/**
* @package Joomla.Administrator
* @subpackage com_associations
*
* @copyright (C) 2017 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
namespace Joomla\Component\Associations\Administrator\Field;
use Joomla\CMS\Factory;
use Joomla\CMS\Form\Field\ListField;
use Joomla\CMS\Language\LanguageHelper;
use Joomla\Component\Associations\Administrator\Helper\AssociationsHelper;
use Joomla\Utilities\ArrayHelper;
// phpcs:disable PSR1.Files.SideEffects
\defined('_JEXEC') or die;
// phpcs:enable PSR1.Files.SideEffects
/**
* Field listing item languages
*
* @since 3.7.0
*/
class ItemlanguageField extends ListField
{
/**
* The form field type.
*
* @var string
* @since 3.7.0
*/
protected $type = 'Itemlanguage';
/**
* Method to get the field options.
*
* @return array The field option objects.
*
* @since 3.7.0
*/
protected function getOptions()
{
$input = Factory::getApplication()->getInput();
list($extensionName, $typeName) = explode('.', $input->get('itemtype', '', 'string'), 2);
// Get the extension specific helper method
$helper = AssociationsHelper::getExtensionHelper($extensionName);
$languageField = $helper->getTypeFieldName($typeName, 'language');
$referenceId = $input->get('id', 0, 'int');
$reference = ArrayHelper::fromObject(AssociationsHelper::getItem($extensionName, $typeName, $referenceId));
$referenceLang = $reference[$languageField];
// Get item associations given ID and item type
$associations = AssociationsHelper::getAssociationList($extensionName, $typeName, $referenceId);
// Check if user can create items in this component item type.
$canCreate = AssociationsHelper::allowAdd($extensionName, $typeName);
// Gets existing languages.
$existingLanguages = LanguageHelper::getContentLanguages([0, 1], false);
$options = [];
// Each option has the format "<lang>|<id>", example: "en-GB|1"
foreach ($existingLanguages as $langCode => $language) {
// If language code is equal to reference language we don't need it.
if ($language->lang_code == $referenceLang) {
continue;
}
$options[$langCode] = new \stdClass();
$options[$langCode]->text = $language->title;
// If association exists in this language.
if (isset($associations[$language->lang_code])) {
$itemId = (int) $associations[$language->lang_code]['id'];
$options[$langCode]->value = $language->lang_code . ':' . $itemId . ':edit';
// Check if user does have permission to edit the associated item.
$canEdit = AssociationsHelper::allowEdit($extensionName, $typeName, $itemId);
// Check if item can be checked out
$canCheckout = AssociationsHelper::canCheckinItem($extensionName, $typeName, $itemId);
// Disable language if user is not allowed to edit the item associated to it.
$options[$langCode]->disable = !($canEdit && $canCheckout);
} else {
// New item, id = 0 and disabled if user is not allowed to create new items.
$options[$langCode]->value = $language->lang_code . ':0:add';
// Disable language if user is not allowed to create items.
$options[$langCode]->disable = !$canCreate;
}
}
return array_merge(parent::getOptions(), $options);
}
}

View File

@ -0,0 +1,66 @@
<?php
/**
* @package Joomla.Administrator
* @subpackage com_associations
*
* @copyright (C) 2017 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
namespace Joomla\Component\Associations\Administrator\Field;
use Joomla\CMS\Form\Field\GroupedlistField;
use Joomla\CMS\HTML\HTMLHelper;
use Joomla\Component\Associations\Administrator\Helper\AssociationsHelper;
// phpcs:disable PSR1.Files.SideEffects
\defined('_JEXEC') or die;
// phpcs:enable PSR1.Files.SideEffects
/**
* A drop down containing all component item types that implement associations.
*
* @since 3.7.0
*/
class ItemtypeField extends GroupedlistField
{
/**
* The form field type.
*
* @var string
*
* @since 3.7.0
*/
protected $type = 'Itemtype';
/**
* Method to get the field input markup.
*
* @return array The field option objects as a nested array in groups.
*
* @since 3.7.0
*
* @throws \UnexpectedValueException
*/
protected function getGroups()
{
$options = [];
$extensions = AssociationsHelper::getSupportedExtensions();
foreach ($extensions as $extension) {
if ($extension->get('associationssupport') === true) {
foreach ($extension->get('types') as $type) {
$context = $extension->get('component') . '.' . $type->get('name');
$options[$extension->get('title')][] = HTMLHelper::_('select.option', $context, $type->get('title'));
}
}
}
// Sort by alpha order.
uksort($options, 'strnatcmp');
// Add options to parent array.
return array_merge(parent::getGroups(), $options);
}
}

View File

@ -0,0 +1,110 @@
<?php
/**
* @package Joomla.Administrator
* @subpackage com_associations
*
* @copyright (C) 2017 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
namespace Joomla\Component\Associations\Administrator\Field\Modal;
use Joomla\CMS\Factory;
use Joomla\CMS\Form\FormField;
use Joomla\CMS\HTML\HTMLHelper;
use Joomla\CMS\Language\Text;
use Joomla\CMS\Session\Session;
// phpcs:disable PSR1.Files.SideEffects
\defined('_JEXEC') or die;
// phpcs:enable PSR1.Files.SideEffects
/**
* Supports a modal item picker.
*
* @since 3.7.0
*/
class AssociationField extends FormField
{
/**
* The form field type.
*
* @var string
* @since 3.7.0
*/
protected $type = 'Modal_Association';
/**
* Method to get the field input markup.
*
* @return string The field input markup.
*
* @since 3.7.0
*/
protected function getInput()
{
// @todo USE Layouts here!!!
// The active item id field.
$value = (int) $this->value ?: '';
$doc = Factory::getApplication()->getDocument();
$wa = $doc->getWebAssetManager();
$doc->addScriptOptions('admin_associations_modal', ['itemId' => $value]);
$wa->useScript('com_associations.admin-associations-modal');
// Setup variables for display.
$html = [];
$linkAssociations = 'index.php?option=com_associations&amp;view=associations&amp;layout=modal&amp;tmpl=component'
. '&amp;forcedItemType=' . Factory::getApplication()->getInput()->get('itemtype', '', 'string') . '&amp;function=jSelectAssociation_' . $this->id;
$linkAssociations .= "&amp;forcedLanguage=' + document.getElementById('target-association').getAttribute('data-language') + '";
$urlSelect = $linkAssociations . '&amp;' . Session::getFormToken() . '=1';
// Select custom association button
$html[] = '<button'
. ' type="button"'
. ' id="select-change"'
. ' class="btn btn-secondary' . ($value ? '' : ' hidden') . '"'
. ' data-bs-toggle="modal"'
. ' data-select="' . Text::_('COM_ASSOCIATIONS_SELECT_TARGET') . '"'
. ' data-change="' . Text::_('COM_ASSOCIATIONS_CHANGE_TARGET') . '"'
. ' data-bs-target="#associationSelect' . $this->id . 'Modal">'
. '<span class="icon-file" aria-hidden="true"></span> '
. '<span id="select-change-text"></span>'
. '</button>';
// Clear association button
$html[] = '<button'
. ' type="button"'
. ' class="btn btn-secondary' . ($value ? '' : ' hidden') . '"'
. ' onclick="return Joomla.submitbutton(\'undo-association\');"'
. ' id="remove-assoc">'
. '<span class="icon-times" aria-hidden="true"></span> ' . Text::_('JCLEAR')
. '</button>';
$html[] = '<input type="hidden" id="' . $this->id . '_id" name="' . $this->name . '" value="' . $value . '">';
// Select custom association modal
$html[] = HTMLHelper::_(
'bootstrap.renderModal',
'associationSelect' . $this->id . 'Modal',
[
'title' => Text::_('COM_ASSOCIATIONS_SELECT_TARGET'),
'backdrop' => 'static',
'url' => $urlSelect,
'height' => '400px',
'width' => '800px',
'bodyHeight' => 70,
'modalWidth' => 80,
'footer' => '<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">'
. Text::_('JLIB_HTML_BEHAVIOR_CLOSE') . '</button>',
]
);
return implode("\n", $html);
}
}

View File

@ -0,0 +1,671 @@
<?php
/**
* @package Joomla.Administrator
* @subpackage com_associations
*
* @copyright (C) 2017 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
namespace Joomla\Component\Associations\Administrator\Helper;
use Joomla\CMS\Association\AssociationExtensionInterface;
use Joomla\CMS\Association\AssociationServiceInterface;
use Joomla\CMS\Factory;
use Joomla\CMS\Helper\ContentHelper;
use Joomla\CMS\Language\LanguageHelper;
use Joomla\CMS\Language\Text;
use Joomla\CMS\Layout\LayoutHelper;
use Joomla\CMS\Router\Route;
use Joomla\Database\ParameterType;
use Joomla\Registry\Registry;
// phpcs:disable PSR1.Files.SideEffects
\defined('_JEXEC') or die;
// phpcs:enable PSR1.Files.SideEffects
/**
* Associations component helper.
*
* @since 3.7.0
*/
class AssociationsHelper extends ContentHelper
{
/**
* Array of Registry objects of extensions
*
* @var array
* @since 3.7.0
*/
public static $extensionsSupport = null;
/**
* List of extensions name with support
*
* @var array
* @since 3.7.0
*/
public static $supportedExtensionsList = [];
/**
* Get the associated items for an item
*
* @param string $extensionName The extension name with com_
* @param string $typeName The item type
* @param int $itemId The id of item for which we need the associated items
*
* @return array
*
* @since 3.7.0
*/
public static function getAssociationList($extensionName, $typeName, $itemId)
{
if (!self::hasSupport($extensionName)) {
return [];
}
// Get the extension specific helper method
$helper = self::getExtensionHelper($extensionName);
return $helper->getAssociationList($typeName, $itemId);
}
/**
* Get the the instance of the extension helper class
*
* @param string $extensionName The extension name with com_
*
* @return \Joomla\CMS\Association\AssociationExtensionHelper|null
*
* @since 3.7.0
*/
public static function getExtensionHelper($extensionName)
{
if (!self::hasSupport($extensionName)) {
return null;
}
$support = self::$extensionsSupport[$extensionName];
return $support->get('helper');
}
/**
* Get item information
*
* @param string $extensionName The extension name with com_
* @param string $typeName The item type
* @param int $itemId The id of item for which we need the associated items
*
* @return \Joomla\CMS\Table\Table|null
*
* @since 3.7.0
*/
public static function getItem($extensionName, $typeName, $itemId)
{
if (!self::hasSupport($extensionName)) {
return null;
}
// Get the extension specific helper method
$helper = self::getExtensionHelper($extensionName);
return $helper->getItem($typeName, $itemId);
}
/**
* Check if extension supports associations
*
* @param string $extensionName The extension name with com_
*
* @return boolean
*
* @since 3.7.0
*/
public static function hasSupport($extensionName)
{
if (\is_null(self::$extensionsSupport)) {
self::getSupportedExtensions();
}
return \in_array($extensionName, self::$supportedExtensionsList);
}
/**
* Loads the helper for the given class.
*
* @param string $extensionName The extension name with com_
*
* @return AssociationExtensionInterface|null
*
* @since 4.0.0
*/
private static function loadHelper($extensionName)
{
$component = Factory::getApplication()->bootComponent($extensionName);
if ($component instanceof AssociationServiceInterface) {
return $component->getAssociationsExtension();
}
// Check if associations helper exists
if (!file_exists(JPATH_ADMINISTRATOR . '/components/' . $extensionName . '/helpers/associations.php')) {
return null;
}
require_once JPATH_ADMINISTRATOR . '/components/' . $extensionName . '/helpers/associations.php';
$componentAssociationsHelperClassName = self::getExtensionHelperClassName($extensionName);
if (!class_exists($componentAssociationsHelperClassName, false)) {
return null;
}
// Create an instance of the helper class
return new $componentAssociationsHelperClassName();
}
/**
* Get the extension specific helper class name
*
* @param string $extensionName The extension name with com_
*
* @return string
*
* @since 3.7.0
*/
private static function getExtensionHelperClassName($extensionName)
{
$realName = self::getExtensionRealName($extensionName);
return ucfirst($realName) . 'AssociationsHelper';
}
/**
* Get the real extension name. This means without com_
*
* @param string $extensionName The extension name with com_
*
* @return string
*
* @since 3.7.0
*/
private static function getExtensionRealName($extensionName)
{
return strpos($extensionName, 'com_') === false ? $extensionName : substr($extensionName, 4);
}
/**
* Get the associated language edit links Html.
*
* @param string $extensionName Extension Name
* @param string $typeName ItemType
* @param integer $itemId Item id.
* @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.
*
* @return string The language HTML
*
* @since 3.7.0
*/
public static function getAssociationHtmlList($extensionName, $typeName, $itemId, $itemLanguage, $addLink = true, $assocLanguages = true)
{
// Get the associations list for this item.
$items = self::getAssociationList($extensionName, $typeName, $itemId);
$titleFieldName = self::getTypeFieldName($extensionName, $typeName, 'title');
// Get all content languages.
$languages = LanguageHelper::getContentLanguages([0, 1], false);
$content_languages = array_column($languages, 'lang_code');
// Display warning if Content Language is trashed or deleted
foreach ($items as $item) {
if (!\in_array($item['language'], $content_languages)) {
Factory::getApplication()->enqueueMessage(Text::sprintf('JGLOBAL_ASSOCIATIONS_CONTENTLANGUAGE_WARNING', $item['language']), 'warning');
}
}
$canEditReference = self::allowEdit($extensionName, $typeName, $itemId);
$canCreate = self::allowAdd($extensionName, $typeName);
// Create associated items list.
foreach ($languages as $langCode => $language) {
// Don't do for the reference language.
if ($langCode == $itemLanguage) {
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.
if (isset($items[$langCode])) {
$title = $items[$langCode][$titleFieldName];
$additional = '';
if (isset($items[$langCode]['catid'])) {
$db = Factory::getDbo();
// Get the category name
$query = $db->getQuery(true)
->select($db->quoteName('title'))
->from($db->quoteName('#__categories'))
->where($db->quoteName('id') . ' = :id')
->bind(':id', $items[$langCode]['catid'], ParameterType::INTEGER);
$db->setQuery($query);
$categoryTitle = $db->loadResult();
$additional = '<strong>' . Text::sprintf('JCATEGORY_SPRINTF', $categoryTitle) . '</strong> <br>';
} elseif (isset($items[$langCode]['menutype'])) {
$db = Factory::getDbo();
// Get the menutype name
$query = $db->getQuery(true)
->select($db->quoteName('title'))
->from($db->quoteName('#__menu_types'))
->where($db->quoteName('menutype') . ' = :menutype')
->bind(':menutype', $items[$langCode]['menutype']);
$db->setQuery($query);
$menutypeTitle = $db->loadResult();
$additional = '<strong>' . Text::sprintf('COM_MENUS_MENU_SPRINTF', $menutypeTitle) . '</strong><br>';
}
$labelClass = 'bg-secondary';
$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') : '';
} else {
$items[$langCode] = [];
$title = Text::_('COM_ASSOCIATIONS_NO_ASSOCIATION');
$additional = $addLink ? Text::_('COM_ASSOCIATIONS_ADD_NEW_ASSOCIATION') : '';
$labelClass = 'bg-warning text-dark';
$target = $langCode . ':0:add';
$allow = $canCreate;
}
// Generate item Html.
$options = [
'option' => 'com_associations',
'view' => 'association',
'layout' => 'edit',
'itemtype' => $extensionName . '.' . $typeName,
'task' => 'association.edit',
'id' => $itemId,
'target' => $target,
];
$url = Route::_('index.php?' . http_build_query($options));
$url = $allow && $addLink ? $url : '';
$text = $language->lang_code;
$tooltip = '<strong>' . htmlspecialchars($language->title, ENT_QUOTES, 'UTF-8') . '</strong><br>'
. htmlspecialchars($title, ENT_QUOTES, 'UTF-8') . '<br><br>' . $additional;
$classes = 'badge ' . $labelClass;
$items[$langCode]['link'] = '<a href="' . $url . '" class="' . $classes . '">' . $text . '</a>'
. '<div role="tooltip">' . $tooltip . '</div>';
}
return LayoutHelper::render('joomla.content.associations', $items);
}
/**
* Get all extensions with associations support.
*
* @return array The extensions.
*
* @since 3.7.0
*/
public static function getSupportedExtensions()
{
if (!\is_null(self::$extensionsSupport)) {
return self::$extensionsSupport;
}
self::$extensionsSupport = [];
$extensions = self::getEnabledExtensions();
foreach ($extensions as $extension) {
$support = self::getSupportedExtension($extension->element);
if ($support->get('associationssupport') === true) {
self::$supportedExtensionsList[] = $extension->element;
}
self::$extensionsSupport[$extension->element] = $support;
}
return self::$extensionsSupport;
}
/**
* Get item context based on the item key.
*
* @param string $extensionName The extension identifier.
*
* @return \Joomla\Registry\Registry The item properties.
*
* @since 3.7.0
*/
public static function getSupportedExtension($extensionName)
{
$result = new Registry();
$result->def('component', $extensionName);
$result->def('associationssupport', false);
$result->def('helper', null);
$helper = self::loadHelper($extensionName);
if (!$helper) {
return $result;
}
$result->set('helper', $helper);
if ($helper->hasAssociationsSupport() === false) {
return $result;
}
$result->set('associationssupport', true);
// Get the translated titles.
$languagePath = JPATH_ADMINISTRATOR . '/components/' . $extensionName;
$lang = Factory::getLanguage();
$lang->load($extensionName . '.sys', JPATH_ADMINISTRATOR);
$lang->load($extensionName . '.sys', $languagePath);
$lang->load($extensionName, JPATH_ADMINISTRATOR);
$lang->load($extensionName, $languagePath);
$result->def('title', Text::_(strtoupper($extensionName)));
// Get the supported types
$types = $helper->getItemTypes();
$rTypes = [];
foreach ($types as $typeName) {
$details = $helper->getType($typeName);
$context = 'component';
$title = $helper->getTypeTitle($typeName);
$languageKey = $typeName;
$typeNameExploded = explode('.', $typeName);
if (array_pop($typeNameExploded) === 'category') {
$languageKey = strtoupper($extensionName) . '_CATEGORIES';
$context = 'category';
}
if ($lang->hasKey(strtoupper($extensionName . '_' . $title . 'S'))) {
$languageKey = strtoupper($extensionName . '_' . $title . 'S');
}
$title = $lang->hasKey($languageKey) ? Text::_($languageKey) : Text::_('COM_ASSOCIATIONS_ITEMS');
$rType = new Registry();
$rType->def('name', $typeName);
$rType->def('details', $details);
$rType->def('title', $title);
$rType->def('context', $context);
$rTypes[$typeName] = $rType;
}
$result->def('types', $rTypes);
return $result;
}
/**
* Get all installed and enabled extensions
*
* @return mixed
*
* @since 3.7.0
*/
private static function getEnabledExtensions()
{
$db = Factory::getDbo();
$query = $db->getQuery(true)
->select('*')
->from($db->quoteName('#__extensions'))
->where($db->quoteName('type') . ' = ' . $db->quote('component'))
->where($db->quoteName('enabled') . ' = 1');
$db->setQuery($query);
return $db->loadObjectList();
}
/**
* Get all the content languages.
*
* @return array Array of objects all content languages by language code.
*
* @since 3.7.0
*/
public static function getContentLanguages()
{
return LanguageHelper::getContentLanguages([0, 1]);
}
/**
* Get the associated items for an item
*
* @param string $extensionName The extension name with com_
* @param string $typeName The item type
* @param int $itemId The id of item for which we need the associated items
*
* @return boolean
*
* @since 3.7.0
*/
public static function allowEdit($extensionName, $typeName, $itemId)
{
if (!self::hasSupport($extensionName)) {
return false;
}
// Get the extension specific helper method
$helper = self::getExtensionHelper($extensionName);
if (method_exists($helper, 'allowEdit')) {
return $helper->allowEdit($typeName, $itemId);
}
return Factory::getUser()->authorise('core.edit', $extensionName);
}
/**
* Check if user is allowed to create items.
*
* @param string $extensionName The extension name with com_
* @param string $typeName The item type
*
* @return boolean True on allowed.
*
* @since 3.7.0
*/
public static function allowAdd($extensionName, $typeName)
{
if (!self::hasSupport($extensionName)) {
return false;
}
// Get the extension specific helper method
$helper = self::getExtensionHelper($extensionName);
if (method_exists($helper, 'allowAdd')) {
return $helper->allowAdd($typeName);
}
return Factory::getUser()->authorise('core.create', $extensionName);
}
/**
* Check if an item is checked out
*
* @param string $extensionName The extension name with com_
* @param string $typeName The item type
* @param int $itemId The id of item for which we need the associated items
*
* @return boolean True if item is checked out.
*
* @since 3.7.0
*/
public static function isCheckoutItem($extensionName, $typeName, $itemId)
{
if (!self::hasSupport($extensionName)) {
return false;
}
if (!self::typeSupportsCheckout($extensionName, $typeName)) {
return false;
}
// Get the extension specific helper method
$helper = self::getExtensionHelper($extensionName);
if (method_exists($helper, 'isCheckoutItem')) {
return $helper->isCheckoutItem($typeName, $itemId);
}
$item = self::getItem($extensionName, $typeName, $itemId);
$checkedOutFieldName = $helper->getTypeFieldName($typeName, 'checked_out');
return $item->{$checkedOutFieldName} != 0;
}
/**
* Check if user can checkin an item.
*
* @param string $extensionName The extension name with com_
* @param string $typeName The item type
* @param int $itemId The id of item for which we need the associated items
*
* @return boolean True on allowed.
*
* @since 3.7.0
*/
public static function canCheckinItem($extensionName, $typeName, $itemId)
{
if (!self::hasSupport($extensionName)) {
return false;
}
if (!self::typeSupportsCheckout($extensionName, $typeName)) {
return true;
}
// Get the extension specific helper method
$helper = self::getExtensionHelper($extensionName);
if (method_exists($helper, 'canCheckinItem')) {
return $helper->canCheckinItem($typeName, $itemId);
}
$item = self::getItem($extensionName, $typeName, $itemId);
$checkedOutFieldName = $helper->getTypeFieldName($typeName, 'checked_out');
$userId = Factory::getUser()->id;
return ($item->{$checkedOutFieldName} == $userId || $item->{$checkedOutFieldName} == 0);
}
/**
* Check if the type supports checkout
*
* @param string $extensionName The extension name with com_
* @param string $typeName The item type
*
* @return boolean True on allowed.
*
* @since 3.7.0
*/
public static function typeSupportsCheckout($extensionName, $typeName)
{
if (!self::hasSupport($extensionName)) {
return false;
}
// Get the extension specific helper method
$helper = self::getExtensionHelper($extensionName);
$support = $helper->getTypeSupport($typeName);
return !empty($support['checkout']);
}
/**
* Get a table field name for a type
*
* @param string $extensionName The extension name with com_
* @param string $typeName The item type
* @param string $fieldName The item type
*
* @return boolean True on allowed.
*
* @since 3.7.0
*/
public static function getTypeFieldName($extensionName, $typeName, $fieldName)
{
if (!self::hasSupport($extensionName)) {
return false;
}
// Get the extension specific helper method
$helper = self::getExtensionHelper($extensionName);
return $helper->getTypeFieldName($typeName, $fieldName);
}
/**
* Gets the language filter system plugin extension id.
*
* @return integer The language filter system plugin extension id.
*
* @since 3.7.2
*/
public static function getLanguagefilterPluginId()
{
$db = Factory::getDbo();
$query = $db->getQuery(true)
->select($db->quoteName('extension_id'))
->from($db->quoteName('#__extensions'))
->where($db->quoteName('folder') . ' = ' . $db->quote('system'))
->where($db->quoteName('element') . ' = ' . $db->quote('languagefilter'));
$db->setQuery($query);
try {
$result = (int) $db->loadResult();
} catch (\RuntimeException $e) {
Factory::getApplication()->enqueueMessage($e->getMessage(), 'error');
}
return $result;
}
}

View File

@ -0,0 +1,43 @@
<?php
/**
* @package Joomla.Administrator
* @subpackage com_associations
*
* @copyright (C) 2017 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
namespace Joomla\Component\Associations\Administrator\Model;
use Joomla\CMS\MVC\Model\ListModel;
// phpcs:disable PSR1.Files.SideEffects
\defined('_JEXEC') or die;
// phpcs:enable PSR1.Files.SideEffects
/**
* Methods supporting a list of article records.
*
* @since 3.7.0
*/
class AssociationModel extends ListModel
{
/**
* Method to get the record form.
*
* @param array $data Data for the form.
* @param boolean $loadData True if the form is to load its own data (default case), false if not.
*
* @return \Joomla\CMS\Form\Form|boolean A Form object on success, false on failure
*
* @since 3.7.0
*/
public function getForm($data = [], $loadData = true)
{
// Get the form.
$form = $this->loadForm('com_associations.association', 'association', ['control' => 'jform', 'load_data' => $loadData]);
return !empty($form) ? $form : false;
}
}

View File

@ -0,0 +1,568 @@
<?php
/**
* @package Joomla.Administrator
* @subpackage com_associations
*
* @copyright (C) 2017 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
namespace Joomla\Component\Associations\Administrator\Model;
use Joomla\CMS\Factory;
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;
// phpcs:disable PSR1.Files.SideEffects
\defined('_JEXEC') or die;
// phpcs:enable PSR1.Files.SideEffects
/**
* Methods supporting a list of article records.
*
* @since 3.7.0
*/
class AssociationsModel extends ListModel
{
/**
* Override parent constructor.
*
* @param array $config An optional associative array of configuration settings.
* @param MVCFactoryInterface $factory The factory.
*
* @see \Joomla\CMS\MVC\Model\BaseDatabaseModel
* @since 3.7
*/
public function __construct($config = [], MVCFactoryInterface $factory = null)
{
if (empty($config['filter_fields'])) {
$config['filter_fields'] = [
'id',
'title',
'ordering',
'itemtype',
'language',
'association',
'menutype',
'menutype_title',
'level',
'state',
'category_id',
'category_title',
'access',
'access_level',
];
}
parent::__construct($config, $factory);
}
/**
* Method to auto-populate the model state.
*
* Note. Calling getState in this method will result in recursion.
*
* @param string $ordering An optional ordering field.
* @param string $direction An optional direction (asc|desc).
*
* @return void
*
* @since 3.7.0
*/
protected function populateState($ordering = 'ordering', $direction = 'asc')
{
$app = Factory::getApplication();
$forcedLanguage = $app->getInput()->get('forcedLanguage', '', 'cmd');
$forcedItemType = $app->getInput()->get('forcedItemType', '', 'string');
// Adjust the context to support modal layouts.
if ($layout = $app->getInput()->get('layout')) {
$this->context .= '.' . $layout;
}
// Adjust the context to support forced languages.
if ($forcedLanguage) {
$this->context .= '.' . $forcedLanguage;
}
// Adjust the context to support forced component item types.
if ($forcedItemType) {
$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('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'));
$this->setState('filter.menutype', $this->getUserStateFromRequest($this->context . '.filter.menutype', 'filter_menutype', '', 'string'));
$this->setState('filter.access', $this->getUserStateFromRequest($this->context . '.filter.access', 'filter_access', '', 'string'));
$this->setState('filter.level', $this->getUserStateFromRequest($this->context . '.filter.level', 'filter_level', '', 'cmd'));
// List state information.
parent::populateState($ordering, $direction);
// Force a language.
if (!empty($forcedLanguage)) {
$this->setState('language', $forcedLanguage);
}
// Force a component item type.
if (!empty($forcedItemType)) {
$this->setState('itemtype', $forcedItemType);
}
}
/**
* Method to get a store id based on model configuration state.
*
* This is necessary because the model is used by the component and
* different modules that might need different sets of data or different
* ordering requirements.
*
* @param string $id A prefix for the store id.
*
* @return string A store id.
*
* @since 3.7.0
*/
protected function getStoreId($id = '')
{
// Compile the store id.
$id .= ':' . $this->getState('itemtype');
$id .= ':' . $this->getState('language');
$id .= ':' . $this->getState('filter.search');
$id .= ':' . $this->getState('filter.state');
$id .= ':' . $this->getState('filter.category_id');
$id .= ':' . $this->getState('filter.menutype');
$id .= ':' . $this->getState('filter.access');
$id .= ':' . $this->getState('filter.level');
return parent::getStoreId($id);
}
/**
* Build an SQL query to load the list data.
*
* @return \Joomla\Database\DatabaseQuery|boolean
*
* @since 3.7.0
*/
protected function getListQuery()
{
$type = null;
list($extensionName, $typeName) = explode('.', $this->state->get('itemtype'), 2);
$extension = AssociationsHelper::getSupportedExtension($extensionName);
$types = $extension->get('types');
if (\array_key_exists($typeName, $types)) {
$type = $types[$typeName];
}
if (\is_null($type)) {
return false;
}
// Create a new query object.
$user = $this->getCurrentUser();
$db = $this->getDatabase();
$query = $db->getQuery(true);
$details = $type->get('details');
if (!\array_key_exists('support', $details)) {
return false;
}
$support = $details['support'];
if (!\array_key_exists('fields', $details)) {
return false;
}
$fields = $details['fields'];
// Main query.
$query->select($db->quoteName($fields['id'], 'id'))
->select($db->quoteName($fields['title'], 'title'))
->select($db->quoteName($fields['alias'], 'alias'));
if (!\array_key_exists('tables', $details)) {
return false;
}
$tables = $details['tables'];
foreach ($tables as $key => $table) {
$query->from($db->quoteName($table, $key));
}
if (!\array_key_exists('joins', $details)) {
return false;
}
$joins = $details['joins'];
foreach ($joins as $join) {
$query->join($join['type'], $db->quoteName($join['condition']));
}
// Join over the language.
$query->select($db->quoteName($fields['language'], 'language'))
->select($db->quoteName('l.title', 'language_title'))
->select($db->quoteName('l.image', 'language_image'))
->join(
'LEFT',
$db->quoteName('#__languages', 'l'),
$db->quoteName('l.lang_code') . ' = ' . $db->quoteName($fields['language'])
);
$extensionNameItem = $extensionName . '.item';
// Join over the associations.
$query->select('COUNT(' . $db->quoteName('asso2.id') . ') > 1 AS ' . $db->quoteName('association'))
->join(
'LEFT',
$db->quoteName('#__associations', 'asso'),
$db->quoteName('asso.id') . ' = ' . $db->quoteName($fields['id'])
. ' AND ' . $db->quoteName('asso.context') . ' = :context'
)
->join(
'LEFT',
$db->quoteName('#__associations', 'asso2'),
$db->quoteName('asso2.key') . ' = ' . $db->quoteName('asso.key')
)
->bind(':context', $extensionNameItem);
// Prepare the group by clause.
$groupby = [
$fields['id'],
$fields['title'],
$fields['alias'],
$fields['language'],
'l.title',
'l.image',
];
// Select author for ACL checks.
if (!empty($fields['created_user_id'])) {
$query->select($db->quoteName($fields['created_user_id'], 'created_user_id'));
$groupby[] = $fields['created_user_id'];
}
// Select checked out data for check in checkins.
if (!empty($fields['checked_out']) && !empty($fields['checked_out_time'])) {
$query->select($db->quoteName($fields['checked_out'], 'checked_out'))
->select($db->quoteName($fields['checked_out_time'], 'checked_out_time'));
// Join over the users.
$query->select($db->quoteName('u.name', 'editor'))
->join(
'LEFT',
$db->quoteName('#__users', 'u'),
$db->quoteName('u.id') . ' = ' . $db->quoteName($fields['checked_out'])
);
$groupby[] = 'u.name';
$groupby[] = $fields['checked_out'];
$groupby[] = $fields['checked_out_time'];
}
// If component item type supports ordering, select the ordering also.
if (!empty($fields['ordering'])) {
$query->select($db->quoteName($fields['ordering'], 'ordering'));
$groupby[] = $fields['ordering'];
}
// If component item type supports state, select the item state also.
if (!empty($fields['state'])) {
$query->select($db->quoteName($fields['state'], 'state'));
$groupby[] = $fields['state'];
}
// If component item type supports level, select the level also.
if (!empty($fields['level'])) {
$query->select($db->quoteName($fields['level'], 'level'));
$groupby[] = $fields['level'];
}
// If component item type supports categories, select the category also.
if (!empty($fields['catid'])) {
$query->select($db->quoteName($fields['catid'], 'catid'));
// Join over the categories.
$query->select($db->quoteName('c.title', 'category_title'))
->join(
'LEFT',
$db->quoteName('#__categories', 'c'),
$db->quoteName('c.id') . ' = ' . $db->quoteName($fields['catid'])
);
$groupby[] = 'c.title';
$groupby[] = $fields['catid'];
}
// If component item type supports menu type, select the menu type also.
if (!empty($fields['menutype'])) {
$query->select($db->quoteName($fields['menutype'], 'menutype'));
// Join over the menu types.
$query->select($db->quoteName('mt.title', 'menutype_title'))
->select($db->quoteName('mt.id', 'menutypeid'))
->join(
'LEFT',
$db->quoteName('#__menu_types', 'mt'),
$db->quoteName('mt.menutype') . ' = ' . $db->quoteName($fields['menutype'])
);
$groupby[] = 'mt.title';
$groupby[] = 'mt.id';
$groupby[] = $fields['menutype'];
}
// If component item type supports access level, select the access level also.
if (\array_key_exists('acl', $support) && $support['acl'] == true && !empty($fields['access'])) {
$query->select($db->quoteName($fields['access'], 'access'));
// Join over the access levels.
$query->select($db->quoteName('ag.title', 'access_level'))
->join(
'LEFT',
$db->quoteName('#__viewlevels', 'ag'),
$db->quoteName('ag.id') . ' = ' . $db->quoteName($fields['access'])
);
$groupby[] = 'ag.title';
$groupby[] = $fields['access'];
// Implement View Level Access.
if (!$user->authorise('core.admin', $extensionName)) {
$groups = $user->getAuthorisedViewLevels();
$query->whereIn($db->quoteName($fields['access']), $groups);
}
}
// If component item type is menus we need to remove the root item and the administrator menu.
if ($extensionName === 'com_menus') {
$query->where($db->quoteName($fields['id']) . ' > 1')
->where($db->quoteName('a.client_id') . ' = 0');
}
// If component item type is category we need to remove all other component categories.
if ($typeName === 'category') {
$query->where($db->quoteName('a.extension') . ' = :extensionname')
->bind(':extensionname', $extensionName);
} elseif ($typeNameExploded = explode('.', $typeName)) {
if (\count($typeNameExploded) > 1 && array_pop($typeNameExploded) === 'category') {
$section = implode('.', $typeNameExploded);
$extensionNameSection = $extensionName . '.' . $section;
$query->where($db->quoteName('a.extension') . ' = :extensionsection')
->bind(':extensionsection', $extensionNameSection);
}
}
// Filter on the language.
if ($language = $this->getState('language')) {
$query->where($db->quoteName($fields['language']) . ' = :language')
->bind(':language', $language);
}
// Filter by item state.
$state = $this->getState('filter.state');
if (is_numeric($state)) {
$state = (int) $state;
$query->where($db->quoteName($fields['state']) . ' = :state')
->bind(':state', $state, ParameterType::INTEGER);
} elseif ($state === '') {
$query->whereIn($db->quoteName($fields['state']), [0, 1]);
}
// Filter on the category.
$baselevel = 1;
if ($categoryId = $this->getState('filter.category_id')) {
$categoryTable = Table::getInstance('Category', '\\Joomla\\CMS\\Table\\');
$categoryTable->load($categoryId);
$baselevel = (int) $categoryTable->level;
$lft = (int) $categoryTable->lft;
$rgt = (int) $categoryTable->rgt;
$query->where($db->quoteName('c.lft') . ' >= :lft')
->where($db->quoteName('c.rgt') . ' <= :rgt')
->bind(':lft', $lft, ParameterType::INTEGER)
->bind(':rgt', $rgt, ParameterType::INTEGER);
}
// Filter on the level.
if ($level = $this->getState('filter.level')) {
$queryLevel = ((int) $level + (int) $baselevel - 1);
$query->where($db->quoteName('a.level') . ' <= :alevel')
->bind(':alevel', $queryLevel, ParameterType::INTEGER);
}
// Filter by menu type.
if ($menutype = $this->getState('filter.menutype')) {
$query->where($db->quoteName($fields['menutype']) . ' = :menutype2')
->bind(':menutype2', $menutype);
}
// Filter by access level.
if ($access = $this->getState('filter.access')) {
$access = (int) $access;
$query->where($db->quoteName($fields['access']) . ' = :access')
->bind(':access', $access, ParameterType::INTEGER);
}
// Filter by search in name.
if ($search = $this->getState('filter.search')) {
if (stripos($search, 'id:') === 0) {
$search = (int) substr($search, 3);
$query->where($db->quoteName($fields['id']) . ' = :searchid')
->bind(':searchid', $search, ParameterType::INTEGER);
} else {
$search = '%' . str_replace(' ', '%', trim($search)) . '%';
$query->where('(' . $db->quoteName($fields['title']) . ' LIKE :title'
. ' OR ' . $db->quoteName($fields['alias']) . ' LIKE :alias)')
->bind(':title', $search)
->bind(':alias', $search);
}
}
// Add the group by clause
$query->group($db->quoteName($groupby));
// Add the list ordering clause
$listOrdering = $this->state->get('list.ordering', 'id');
$orderDirn = $this->state->get('list.direction', 'ASC');
$query->order($db->escape($listOrdering) . ' ' . $db->escape($orderDirn));
return $query;
}
/**
* Delete associations from #__associations table.
*
* @param string $context The associations context. Empty for all.
* @param string $key The associations key. Empty for all.
*
* @return boolean True on success.
*
* @since 3.7.0
*/
public function purge($context = '', $key = '')
{
$app = Factory::getApplication();
$db = $this->getDatabase();
$query = $db->getQuery(true)->delete($db->quoteName('#__associations'));
// Filter by associations context.
if ($context) {
$query->where($db->quoteName('context') . ' = :context')
->bind(':context', $context);
}
// Filter by key.
if ($key) {
$query->where($db->quoteName('key') . ' = :key')
->bind(':key', $key);
}
$db->setQuery($query);
try {
$db->execute();
} catch (ExecutionFailureException $e) {
$app->enqueueMessage(Text::_('COM_ASSOCIATIONS_PURGE_FAILED'), 'error');
return false;
}
$app->enqueueMessage(
Text::_((int) $db->getAffectedRows() > 0 ? 'COM_ASSOCIATIONS_PURGE_SUCCESS' : 'COM_ASSOCIATIONS_PURGE_NONE'),
'message'
);
return true;
}
/**
* Delete orphans from the #__associations table.
*
* @param string $context The associations context. Empty for all.
* @param string $key The associations key. Empty for all.
*
* @return boolean True on success
*
* @since 3.7.0
*/
public function clean($context = '', $key = '')
{
$app = Factory::getApplication();
$db = $this->getDatabase();
$query = $db->getQuery(true)
->select($db->quoteName('key') . ', COUNT(*)')
->from($db->quoteName('#__associations'))
->group($db->quoteName('key'))
->having('COUNT(*) = 1');
// Filter by associations context.
if ($context) {
$query->where($db->quoteName('context') . ' = :context')
->bind(':context', $context);
}
// Filter by key.
if ($key) {
$query->where($db->quoteName('key') . ' = :key')
->bind(':key', $key);
}
$db->setQuery($query);
$assocKeys = $db->loadObjectList();
$count = 0;
// We have orphans. Let's delete them.
foreach ($assocKeys as $value) {
$query->clear()
->delete($db->quoteName('#__associations'))
->where($db->quoteName('key') . ' = :valuekey')
->bind(':valuekey', $value->key);
$db->setQuery($query);
try {
$db->execute();
} catch (ExecutionFailureException $e) {
$app->enqueueMessage(Text::_('COM_ASSOCIATIONS_DELETE_ORPHANS_FAILED'), 'error');
return false;
}
$count += (int) $db->getAffectedRows();
}
$app->enqueueMessage(
Text::_($count > 0 ? 'COM_ASSOCIATIONS_DELETE_ORPHANS_SUCCESS' : 'COM_ASSOCIATIONS_DELETE_ORPHANS_NONE'),
'message'
);
return true;
}
}

View File

@ -0,0 +1,381 @@
<?php
/**
* @package Joomla.Administrator
* @subpackage com_associations
*
* @copyright (C) 2017 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
namespace Joomla\Component\Associations\Administrator\View\Association;
use Joomla\CMS\Application\AdministratorApplication;
use Joomla\CMS\Factory;
use Joomla\CMS\Form\Form;
use Joomla\CMS\Language\Text;
use Joomla\CMS\MVC\View\GenericDataException;
use Joomla\CMS\MVC\View\HtmlView as BaseHtmlView;
use Joomla\CMS\Pagination\Pagination;
use Joomla\CMS\Router\Route;
use Joomla\CMS\Toolbar\Toolbar;
use Joomla\CMS\Toolbar\ToolbarHelper;
use Joomla\Component\Associations\Administrator\Helper\AssociationsHelper;
use Joomla\Component\Associations\Administrator\Model\AssociationModel;
use Joomla\Input\Input;
use Joomla\Registry\Registry;
use Joomla\Utilities\ArrayHelper;
// phpcs:disable PSR1.Files.SideEffects
\defined('_JEXEC') or die;
// phpcs:enable PSR1.Files.SideEffects
/**
* View class for a list of articles.
*
* @since 3.7.0
*/
class HtmlView extends BaseHtmlView
{
/**
* An array of items
*
* @var array
*
* @since 3.7.0
*/
protected $items = [];
/**
* The pagination object
*
* @var Pagination
*
* @since 3.7.0
*/
protected $pagination;
/**
* The model state
*
* @var \Joomla\Registry\Registry
*
* @since 3.7.0
*/
protected $state;
/**
* Selected item type properties.
*
* @var Registry
*
* @since 3.7.0
*/
protected $itemType;
/**
* The application
*
* @var AdministratorApplication
* @since 3.7.0
*/
protected $app;
/**
* The ID of the reference language
*
* @var integer
* @since 3.7.0
*/
protected $referenceId = 0;
/**
* The type name
*
* @var string
* @since 3.7.0
*/
protected $typeName = '';
/**
* The reference language
*
* @var string
* @since 3.7.0
*/
protected $referenceLanguage = '';
/**
* The title of the reference language
*
* @var string
* @since 3.7.0
*/
protected $referenceTitle = '';
/**
* The value of the reference title
*
* @var string
* @since 3.7.0
*/
protected $referenceTitleValue = '';
/**
* The URL to the edit screen
*
* @var string
* @since 3.7.0
*/
protected $editUri = '';
/**
* The ID of the target field
*
* @var string
* @since 3.7.0
*/
protected $targetId = '';
/**
* The target language
*
* @var string
* @since 3.7.0
*/
protected $targetLanguage = '';
/**
* The source of the target field
*
* @var string
* @since 3.7.0
*/
protected $defaultTargetSrc = '';
/**
* The action to perform for the target field
*
* @var string
* @since 3.7.0
*/
protected $targetAction = '';
/**
* The title of the target field
*
* @var string
* @since 3.7.0
*/
protected $targetTitle = '';
/**
* The edit form
*
* @var Form
* @since 3.7.0
*/
protected $form;
/**
* Set if the option is set to save as copy
*
* @var boolean
* @since 3.7.0
*/
private $save2copy = false;
/**
* The type of language
*
* @var Registry
* @since 3.7.0
*/
private $type;
/**
* The supported types
*
* @var array
* @since 3.7.0
*/
private $typeSupports = [];
/**
* The extension name
*
* @var string
* @since 3.7.0
*/
private $extensionName = '';
/**
* Display the view
*
* @param string $tpl The name of the template file to parse; automatically searches through the template paths.
*
* @return void
*
* @since 3.7.0
*
* @throws \Exception
*/
public function display($tpl = null): void
{
/** @var AssociationModel $model */
$model = $this->getModel();
// Check for errors.
if (\count($errors = $model->getErrors())) {
throw new GenericDataException(implode("\n", $errors), 500);
}
$this->app = Factory::getApplication();
$this->form = $model->getForm();
/** @var Input $input */
$input = $this->app->getInput();
$this->referenceId = $input->get('id', 0, 'int');
[$extensionName, $typeName] = explode('.', $input->get('itemtype', '', 'string'), 2);
/** @var Registry $extension */
$extension = AssociationsHelper::getSupportedExtension($extensionName);
$types = $extension->get('types');
if (\array_key_exists($typeName, $types)) {
$this->type = $types[$typeName];
$this->typeSupports = [];
$details = $this->type->get('details');
$this->save2copy = false;
if (\array_key_exists('support', $details)) {
$support = $details['support'];
$this->typeSupports = $support;
}
if (!empty($this->typeSupports['save2copy'])) {
$this->save2copy = true;
}
}
$this->extensionName = $extensionName;
$this->typeName = $typeName;
$this->itemType = $extensionName . '.' . $typeName;
$languageField = AssociationsHelper::getTypeFieldName($extensionName, $typeName, 'language');
$referenceId = $input->get('id', 0, 'int');
$reference = ArrayHelper::fromObject(AssociationsHelper::getItem($extensionName, $typeName, $referenceId));
$this->referenceLanguage = $reference[$languageField];
$this->referenceTitle = AssociationsHelper::getTypeFieldName($extensionName, $typeName, 'title');
$this->referenceTitleValue = $reference[$this->referenceTitle];
// Check for special case category
$typeNameExploded = explode('.', $typeName);
if (array_pop($typeNameExploded) === 'category') {
$this->typeName = 'category';
if ($typeNameExploded) {
$extensionName .= '.' . implode('.', $typeNameExploded);
}
$options = [
'option' => 'com_categories',
'view' => 'category',
'extension' => $extensionName,
'tmpl' => 'component',
];
} else {
$options = [
'option' => $extensionName,
'view' => $typeName,
'extension' => $extensionName,
'tmpl' => 'component',
];
}
// Reference and target edit links.
$this->editUri = 'index.php?' . http_build_query($options);
// Get target language.
$this->targetId = '0';
$this->targetLanguage = '';
$this->defaultTargetSrc = '';
$this->targetAction = '';
$this->targetTitle = '';
if ($target = $input->get('target', '', 'string')) {
$matches = preg_split("#[\:]+#", $target);
$this->targetAction = $matches[2];
$this->targetId = $matches[1];
$this->targetLanguage = $matches[0];
$this->targetTitle = AssociationsHelper::getTypeFieldName($extensionName, $typeName, 'title');
$task = $typeName . '.' . $this->targetAction;
/**
* Let's put the target src into a variable to use in the javascript code
* to avoid race conditions when the reference iframe loads.
*/
$this->getDocument()->addScriptOptions('targetSrc', Route::_($this->editUri . '&task=' . $task . '&id=' . (int) $this->targetId));
$this->form->setValue('itemlanguage', '', $this->targetLanguage . ':' . $this->targetId . ':' . $this->targetAction);
}
$this->addToolbar();
parent::display($tpl);
}
/**
* Add the page title and toolbar.
*
* @return void
*
* @since 3.7.0
*
* @throws \Exception
*/
protected function addToolbar(): void
{
// Hide main menu.
$this->app->getInput()->set('hidemainmenu', 1);
$helper = AssociationsHelper::getExtensionHelper($this->extensionName);
$title = $helper->getTypeTitle($this->typeName);
$languageKey = strtoupper($this->extensionName . '_' . $title . 'S');
if ($this->typeName === 'category') {
$languageKey = strtoupper($this->extensionName) . '_CATEGORIES';
}
ToolbarHelper::title(
Text::sprintf(
'COM_ASSOCIATIONS_TITLE_EDIT',
Text::_($this->extensionName),
Text::_($languageKey)
),
'language assoc'
);
$toolbar = Toolbar::getInstance();
$toolbar->customButton('reference')
->html('<joomla-toolbar-button><button onclick="Joomla.submitbutton(\'reference\')" '
. 'class="btn btn-success"><span class="icon-save" aria-hidden="true"></span>'
. Text::_('COM_ASSOCIATIONS_SAVE_REFERENCE') . '</button></joomla-toolbar-button>');
$toolbar->customButton('target')
->html('<joomla-toolbar-button><button onclick="Joomla.submitbutton(\'target\')" '
. 'class="btn btn-success"><span class="icon-save" aria-hidden="true"></span>'
. Text::_('COM_ASSOCIATIONS_SAVE_TARGET') . '</button></joomla-toolbar-button>');
if ($this->typeName === 'category' || $this->extensionName === 'com_menus' || $this->save2copy === true) {
$toolbar->standardButton('', 'COM_ASSOCIATIONS_COPY_REFERENCE', 'copy')
->icon('icon-copy')
->listCheck(false);
}
$toolbar->cancel('association.cancel');
$toolbar->help('Multilingual_Associations:_Edit');
}
}

View File

@ -0,0 +1,238 @@
<?php
/**
* @package Joomla.Administrator
* @subpackage com_associations
*
* @copyright (C) 2017 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
namespace Joomla\Component\Associations\Administrator\View\Associations;
use Joomla\CMS\Factory;
use Joomla\CMS\Language\Associations;
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;
// phpcs:disable PSR1.Files.SideEffects
\defined('_JEXEC') or die;
// phpcs:enable PSR1.Files.SideEffects
/**
* View class for a list of articles.
*
* @since 3.7.0
*/
class HtmlView extends BaseHtmlView
{
/**
* An array of items
*
* @var array
*
* @since 3.7.0
*/
protected $items;
/**
* The pagination object
*
* @var \Joomla\CMS\Pagination\Pagination
*
* @since 3.7.0
*/
protected $pagination;
/**
* The model state
*
* @var object
*
* @since 3.7.0
*/
protected $state;
/**
* Selected item type properties.
*
* @var \Joomla\Registry\Registry
*
* @since 3.7.0
*/
public $itemType = null;
/**
* Display the view
*
* @param string $tpl The name of the template file to parse; automatically searches through the template paths.
*
* @return void
*
* @since 3.7.0
*/
public function display($tpl = null)
{
$this->state = $this->get('State');
$this->filterForm = $this->get('FilterForm');
$this->activeFilters = $this->get('ActiveFilters');
if (!Associations::isEnabled()) {
$link = Route::_('index.php?option=com_plugins&task=plugin.edit&extension_id=' . AssociationsHelper::getLanguagefilterPluginId());
Factory::getApplication()->enqueueMessage(Text::sprintf('COM_ASSOCIATIONS_ERROR_NO_ASSOC', $link), 'warning');
} elseif ($this->state->get('itemtype') != '' && $this->state->get('language') != '') {
$type = null;
list($extensionName, $typeName) = explode('.', $this->state->get('itemtype'), 2);
$extension = AssociationsHelper::getSupportedExtension($extensionName);
$types = $extension->get('types');
if (\array_key_exists($typeName, $types)) {
$type = $types[$typeName];
}
$this->itemType = $type;
if (\is_null($type)) {
Factory::getApplication()->enqueueMessage(Text::_('COM_ASSOCIATIONS_ERROR_NO_TYPE'), 'warning');
} else {
$this->extensionName = $extensionName;
$this->typeName = $typeName;
$this->typeSupports = [];
$this->typeFields = [];
$details = $type->get('details');
if (\array_key_exists('support', $details)) {
$support = $details['support'];
$this->typeSupports = $support;
}
if (\array_key_exists('fields', $details)) {
$fields = $details['fields'];
$this->typeFields = $fields;
}
// Dynamic filter form.
// This selectors doesn't have to activate the filter bar.
unset($this->activeFilters['itemtype']);
unset($this->activeFilters['language']);
// Remove filters options depending on selected type.
if (empty($support['state'])) {
unset($this->activeFilters['state']);
$this->filterForm->removeField('state', 'filter');
}
if (empty($support['category'])) {
unset($this->activeFilters['category_id']);
$this->filterForm->removeField('category_id', 'filter');
}
if ($extensionName !== 'com_menus') {
unset($this->activeFilters['menutype']);
$this->filterForm->removeField('menutype', 'filter');
}
if (empty($support['level'])) {
unset($this->activeFilters['level']);
$this->filterForm->removeField('level', 'filter');
}
if (empty($support['acl'])) {
unset($this->activeFilters['access']);
$this->filterForm->removeField('access', 'filter');
}
// Add extension attribute to category filter.
if (empty($support['catid'])) {
$this->filterForm->setFieldAttribute('category_id', 'extension', $extensionName, 'filter');
if ($this->getLayout() == 'modal') {
// We need to change the category filter to only show categories tagged to All or to the forced language.
if ($forcedLanguage = Factory::getApplication()->getInput()->get('forcedLanguage', '', 'CMD')) {
$this->filterForm->setFieldAttribute('category_id', 'language', '*,' . $forcedLanguage, 'filter');
}
}
}
$this->items = $this->get('Items');
$this->pagination = $this->get('Pagination');
$linkParameters = [
'layout' => 'edit',
'itemtype' => $extensionName . '.' . $typeName,
'task' => 'association.edit',
];
$this->editUri = 'index.php?option=com_associations&view=association&' . http_build_query($linkParameters);
}
}
// Check for errors.
if (\count($errors = $this->get('Errors'))) {
throw new \Exception(implode("\n", $errors), 500);
}
$this->addToolbar();
parent::display($tpl);
}
/**
* Add the page title and toolbar.
*
* @return void
*
* @since 3.7.0
*/
protected function addToolbar()
{
$user = $this->getCurrentUser();
if (isset($this->typeName) && isset($this->extensionName)) {
$helper = AssociationsHelper::getExtensionHelper($this->extensionName);
$title = $helper->getTypeTitle($this->typeName);
$languageKey = strtoupper($this->extensionName . '_' . $title . 'S');
if ($this->typeName === 'category') {
$languageKey = strtoupper($this->extensionName) . '_CATEGORIES';
}
ToolbarHelper::title(
Text::sprintf(
'COM_ASSOCIATIONS_TITLE_LIST',
Text::_($this->extensionName),
Text::_($languageKey)
),
'language assoc'
);
} else {
ToolbarHelper::title(Text::_('COM_ASSOCIATIONS_TITLE_LIST_SELECT'), 'language assoc');
}
$toolbar = Toolbar::getInstance();
if ($user->authorise('core.admin', 'com_associations') || $user->authorise('core.options', 'com_associations')) {
if (!isset($this->typeName)) {
$toolbar->standardButton('', 'COM_ASSOCIATIONS_PURGE', 'associations.purge')
->icon('icon-purge')
->listCheck(false);
$toolbar->standardButton('', 'COM_ASSOCIATIONS_DELETE_ORPHANS', 'associations.clean')
->icon('icon-refresh')
->listCheck(false);
}
$toolbar->preferences('com_associations');
}
$toolbar->help('Multilingual_Associations');
}
}