diff --git a/administrator/components/com_categories/Field/CategoryeditField.php b/administrator/components/com_categories/Field/CategoryeditField.php index a4d82a8c7f975..0040b0d964c4e 100644 --- a/administrator/components/com_categories/Field/CategoryeditField.php +++ b/administrator/components/com_categories/Field/CategoryeditField.php @@ -15,6 +15,7 @@ use Joomla\CMS\Form\Field\ListField; use Joomla\CMS\HTML\HTMLHelper; use Joomla\CMS\Language\Text; +use Joomla\Database\ParameterType; use Joomla\Utilities\ArrayHelper; /** @@ -176,17 +177,28 @@ protected function getOptions() $user = Factory::getUser(); $query = $db->getQuery(true) - ->select('a.id AS value, a.title AS text, a.level, a.published, a.lft, a.language') - ->from('#__categories AS a'); + ->select( + [ + $db->quoteName('a.id', 'value'), + $db->quoteName('a.title', 'text'), + $db->quoteName('a.level'), + $db->quoteName('a.published'), + $db->quoteName('a.lft'), + $db->quoteName('a.language'), + ] + ) + ->from($db->quoteName('#__categories', 'a')); // Filter by the extension type if ($this->element['parent'] == true || $jinput->get('option') == 'com_categories') { - $query->where('(a.extension = ' . $db->quote($extension) . ' OR a.parent_id = 0)'); + $query->where('(' . $db->quoteName('a.extension') . ' = :extension OR ' . $db->quoteName('a.parent_id') . ' = 0)') + ->bind(':extension', $extension); } else { - $query->where('(a.extension = ' . $db->quote($extension) . ')'); + $query->where($db->quoteName('a.extension') . ' = :extension') + ->bind(':extension', $extension); } // Filter language @@ -194,43 +206,44 @@ protected function getOptions() { if (strpos($this->element['language'], ',') !== false) { - $language = implode(',', $db->quote(explode(',', $this->element['language']))); + $language = explode(',', $this->element['language']); } else { - $language = $db->quote($this->element['language']); + $language = $this->element['language']; } - $query->where($db->quoteName('a.language') . ' IN (' . $language . ')'); + $query->whereIn($db->quoteName('a.language'), $language, ParameterType::STRING); } // Filter on the published state - $query->where('a.published IN (' . implode(',', ArrayHelper::toInteger($published)) . ')'); + $state = ArrayHelper::toInteger($published); + $query->whereIn($db->quoteName('a.published'), $state); // Filter categories on User Access Level // Filter by access level on categories. if (!$user->authorise('core.admin')) { - $groups = implode(',', $user->getAuthorisedViewLevels()); - $query->where('a.access IN (' . $groups . ')'); + $groups = $user->getAuthorisedViewLevels(); + $query->whereIn($db->quoteName('a.access'), $groups); } - $query->order('a.lft ASC'); + $query->order($db->quoteName('a.lft') . ' ASC'); // If parent isn't explicitly stated but we are in com_categories assume we want parents if ($oldCat != 0 && ($this->element['parent'] == true || $jinput->get('option') == 'com_categories')) { // Prevent parenting to children of this item. // To rearrange parents and children move the children up, not the parents down. - $query->join('LEFT', $db->quoteName('#__categories') . ' AS p ON p.id = ' . (int) $oldCat) - ->where('NOT(a.lft >= p.lft AND a.rgt <= p.rgt)'); - - $rowQuery = $db->getQuery(true); - $rowQuery->select('a.id AS value, a.title AS text, a.level, a.parent_id') - ->from('#__categories AS a') - ->where('a.id = ' . (int) $oldCat); - $db->setQuery($rowQuery); - $row = $db->loadObject(); + $query->join( + 'LEFT', + $db->quoteName('#__categories', 'p'), + $db->quoteName('p.id') . ' = :oldcat' + ) + ->bind(':oldcat', $oldCat, ParameterType::INTEGER) + ->where('NOT(' . $db->quoteName('a.lft') . ' >= ' . $db->quoteName('p.lft') + . ' AND ' . $db->quoteName('a.rgt') . ' <= ' . $db->quoteName('p.rgt') . ')' + ); } // Get the options. @@ -332,10 +345,25 @@ protected function getOptions() } } - if (($this->element['parent'] == true || $jinput->get('option') == 'com_categories') - && (isset($row) && !isset($options[0])) + if ($oldCat != 0 && ($this->element['parent'] == true || $jinput->get('option') == 'com_categories') + && !isset($options[0]) && isset($this->element['show_root'])) { + $rowQuery = $db->getQuery(true) + ->select( + [ + $db->quoteName('a.id', 'value'), + $db->quoteName('a.title', 'text'), + $db->quoteName('a.level'), + $db->quoteName('a.parent_id'), + ] + ) + ->from($db->quoteName('#__categories', 'a')) + ->where($db->quoteName('a.id') . ' = :aid') + ->bind(':aid', $oldCat, ParameterType::INTEGER); + $db->setQuery($rowQuery); + $row = $db->loadObject(); + if ($row->parent_id == '1') { $parent = new \stdClass; diff --git a/administrator/components/com_categories/Field/Modal/CategoryField.php b/administrator/components/com_categories/Field/Modal/CategoryField.php index 89283cc4e27b6..2063eb1d38afa 100644 --- a/administrator/components/com_categories/Field/Modal/CategoryField.php +++ b/administrator/components/com_categories/Field/Modal/CategoryField.php @@ -17,6 +17,7 @@ use Joomla\CMS\Language\LanguageHelper; use Joomla\CMS\Language\Text; use Joomla\CMS\Session\Session; +use Joomla\Database\ParameterType; /** * Supports a modal category picker. @@ -119,7 +120,8 @@ function jSelectCategory_" . $this->id . "(id, title, object) { $query = $db->getQuery(true) ->select($db->quoteName('title')) ->from($db->quoteName('#__categories')) - ->where($db->quoteName('id') . ' = ' . (int) $value); + ->where($db->quoteName('id') . ' = :value') + ->bind(':value', $value, ParameterType::INTEGER); $db->setQuery($query); try diff --git a/administrator/components/com_categories/Helper/CategoriesHelper.php b/administrator/components/com_categories/Helper/CategoriesHelper.php index 12ee9733afa55..bde0b970c0c23 100644 --- a/administrator/components/com_categories/Helper/CategoriesHelper.php +++ b/administrator/components/com_categories/Helper/CategoriesHelper.php @@ -14,6 +14,7 @@ use Joomla\CMS\Factory; use Joomla\CMS\Language\Associations; use Joomla\CMS\Table\Table; +use Joomla\Database\ParameterType; /** * Categories helper. @@ -35,20 +36,21 @@ public static function getAssociations($pk, $extension = 'com_content') $langAssociations = Associations::getAssociations($extension, '#__categories', 'com_categories.item', $pk, 'id', 'alias', ''); $associations = array(); $user = Factory::getUser(); - $groups = implode(',', $user->getAuthorisedViewLevels()); + $groups = $user->getAuthorisedViewLevels(); foreach ($langAssociations as $langAssociation) { // Include only published categories with user access - $arrId = explode(':', $langAssociation->id); - $assocId = $arrId[0]; - $db = Factory::getDbo(); + $arrId = explode(':', $langAssociation->id); + $assocId = (int) $arrId[0]; + $db = Factory::getDbo(); $query = $db->getQuery(true) ->select($db->quoteName('published')) ->from($db->quoteName('#__categories')) - ->where('access IN (' . $groups . ')') - ->where($db->quoteName('id') . ' = ' . (int) $assocId); + ->whereIn($db->quoteName('access'), $groups) + ->where($db->quoteName('id') . ' = :associd') + ->bind(':associd', $assocId, ParameterType::INTEGER); $result = (int) $db->setQuery($query)->loadResult(); diff --git a/administrator/components/com_categories/Model/CategoriesModel.php b/administrator/components/com_categories/Model/CategoriesModel.php index 1e551d5482d8f..3022cc966df97 100644 --- a/administrator/components/com_categories/Model/CategoriesModel.php +++ b/administrator/components/com_categories/Model/CategoriesModel.php @@ -17,6 +17,7 @@ use Joomla\CMS\Language\Associations; use Joomla\CMS\MVC\Factory\MVCFactoryInterface; use Joomla\CMS\MVC\Model\ListModel; +use Joomla\Database\ParameterType; /** * Categories Component Categories Model @@ -167,23 +168,44 @@ protected function getListQuery() ', a.language' ) ); - $query->from('#__categories AS a'); + $query->from($db->quoteName('#__categories', 'a')); // Join over the language - $query->select('l.title AS language_title, l.image AS language_image') - ->join('LEFT', $db->quoteName('#__languages') . ' AS l ON l.lang_code = a.language'); + $query->select( + [ + $db->quoteName('l.title', 'language_title'), + $db->quoteName('l.image', 'language_image'), + ] + ) + ->join( + 'LEFT', + $db->quoteName('#__languages', 'l'), + $db->quoteName('l.lang_code') . ' = ' . $db->quoteName('a.language') + ); // Join over the users for the checked out user. - $query->select('uc.name AS editor') - ->join('LEFT', '#__users AS uc ON uc.id=a.checked_out'); + $query->select($db->quoteName('uc.name', 'editor')) + ->join( + 'LEFT', + $db->quoteName('#__users', 'uc'), + $db->quoteName('uc.id') . ' = ' . $db->quoteName('a.checked_out') + ); // Join over the asset groups. - $query->select('ag.title AS access_level') - ->join('LEFT', '#__viewlevels AS ag ON ag.id = a.access'); + $query->select($db->quoteName('ag.title', 'access_level')) + ->join( + 'LEFT', + $db->quoteName('#__viewlevels', 'ag'), + $db->quoteName('ag.id') . ' = ' . $db->quoteName('a.access') + ); // Join over the users for the author. - $query->select('ua.name AS author_name') - ->join('LEFT', '#__users AS ua ON ua.id = a.created_user_id'); + $query->select($db->quoteName('ua.name', 'author_name')) + ->join( + 'LEFT', + $db->quoteName('#__users', 'ua'), + $db->quoteName('ua.id') . ' = ' . $db->quoteName('a.created_user_id') + ); // Join over the associations. $assoc = $this->getAssoc(); @@ -191,34 +213,46 @@ protected function getListQuery() if ($assoc) { $query->select('COUNT(asso2.id)>1 as association') - ->join('LEFT', '#__associations AS asso ON asso.id = a.id AND asso.context=' . $db->quote('com_categories.item')) - ->join('LEFT', '#__associations AS asso2 ON asso2.key = asso.key') + ->join( + 'LEFT', + $db->quoteName('#__associations', 'asso'), + $db->quoteName('asso.id') . ' = ' . $db->quoteName('a.id') + . ' AND ' . $db->quoteName('asso.context') . ' = ' . $db->quote('com_categories.item') + ) + ->join( + 'LEFT', + $db->quoteName('#__associations', 'asso2'), + $db->quoteName('asso2.key') . ' = ' . $db->quoteName('asso.key') + ) ->group('a.id, l.title, uc.name, ag.title, ua.name'); } // Filter by extension if ($extension = $this->getState('filter.extension')) { - $query->where('a.extension = ' . $db->quote($extension)); + $query->where($db->quoteName('a.extension') . ' = :extension') + ->bind(':extension', $extension); } // Filter on the level. - if ($level = $this->getState('filter.level')) + if ($level = (int) $this->getState('filter.level')) { - $query->where('a.level <= ' . (int) $level); + $query->where($db->quoteName('a.level') . ' <= :level') + ->bind(':level', $level, ParameterType::INTEGER); } // Filter by access level. - if ($access = $this->getState('filter.access')) + if ($access = (int) $this->getState('filter.access')) { - $query->where('a.access = ' . (int) $access); + $query->where($db->quoteName('a.access') . ' = :access') + ->bind(':access', $access, ParameterType::INTEGER); } // Implement View Level Access if (!$user->authorise('core.admin')) { - $groups = implode(',', $user->getAuthorisedViewLevels()); - $query->where('a.access IN (' . $groups . ')'); + $groups = $user->getAuthorisedViewLevels(); + $query->whereIn($db->quoteName('a.access'), $groups); } // Filter by published state @@ -226,11 +260,13 @@ protected function getListQuery() if (is_numeric($published)) { - $query->where('a.published = ' . (int) $published); + $published = (int) $published; + $query->where($db->quoteName('a.published') . ' = :published') + ->bind(':published', $published, ParameterType::INTEGER); } elseif ($published === '') { - $query->where('(a.published IN (0, 1))'); + $query->whereIn($db->quoteName('a.published'), [0, 1]); } // Filter by search in title @@ -240,19 +276,33 @@ protected function getListQuery() { if (stripos($search, 'id:') === 0) { - $query->where('a.id = ' . (int) substr($search, 3)); + $search = (int) substr($search, 3); + $query->where($db->quoteName('a.id') . ' = :search') + ->bind(':search', $search, ParameterType::INTEGER); } else { - $search = $db->quote('%' . str_replace(' ', '%', $db->escape(trim($search), true) . '%')); - $query->where('(a.title LIKE ' . $search . ' OR a.alias LIKE ' . $search . ' OR a.note LIKE ' . $search . ')'); + $search = '%' . str_replace(' ', '%', trim($search)) . '%'; + $query->extendWhere( + 'AND', + [ + $db->quoteName('a.title') . ' LIKE :title', + $db->quoteName('a.alias') . ' LIKE :alias', + $db->quoteName('a.note') . ' LIKE :note', + ], + 'OR' + ) + ->bind(':title', $search) + ->bind(':alias', $search) + ->bind(':note', $search); } } // Filter on the language. if ($language = $this->getState('filter.language')) { - $query->where('a.language = ' . $db->quote($language)); + $query->where($db->quoteName('a.language') . ' = :language') + ->bind(':language', $language); } // Filter by a single tag. @@ -260,12 +310,16 @@ protected function getListQuery() if (is_numeric($tagId)) { - $query->where($db->quoteName('tagmap.tag_id') . ' = ' . (int) $tagId) + $tagId = (int) $tagId; + $typeAlias = $extension . '.category'; + $query->where($db->quoteName('tagmap.tag_id') . ' = :tagid') + ->bind(':tagid', $tagId, ParameterType::INTEGER) ->join( 'LEFT', $db->quoteName('#__contentitem_tag_map', 'tagmap') . ' ON ' . $db->quoteName('tagmap.content_item_id') . ' = ' . $db->quoteName('a.id') - . ' AND ' . $db->quoteName('tagmap.type_alias') . ' = ' . $db->quote($extension . '.category') - ); + . ' AND ' . $db->quoteName('tagmap.type_alias') . ' = :typealias' + ) + ->bind(':typealias', $typeAlias); } // Add the list ordering clause diff --git a/administrator/components/com_categories/Model/CategoryModel.php b/administrator/components/com_categories/Model/CategoryModel.php index 54cd153054b25..f8f23cab9ca0a 100644 --- a/administrator/components/com_categories/Model/CategoryModel.php +++ b/administrator/components/com_categories/Model/CategoryModel.php @@ -28,6 +28,7 @@ use Joomla\CMS\Plugin\PluginHelper; use Joomla\CMS\UCM\UCMType; use Joomla\Component\Categories\Administrator\Helper\CategoriesHelper; +use Joomla\Database\ParameterType; use Joomla\Registry\Registry; use Joomla\String\StringHelper; use Joomla\Utilities\ArrayHelper; @@ -632,28 +633,39 @@ public function save($data) // Get associationskey for edited item $db = $this->getDbo(); + $id = (int) $table->id; $query = $db->getQuery(true) ->select($db->quoteName('key')) ->from($db->quoteName('#__associations')) - ->where($db->quoteName('context') . ' = ' . $db->quote($this->associationsContext)) - ->where($db->quoteName('id') . ' = ' . (int) $table->id); + ->where($db->quoteName('context') . ' = :associationscontext') + ->where($db->quoteName('id') . ' = :id') + ->bind(':associationscontext', $this->associationsContext) + ->bind(':id', $id, ParameterType::INTEGER); $db->setQuery($query); $oldKey = $db->loadResult(); - // Deleting old associations for the associated items - $query = $db->getQuery(true) - ->delete($db->quoteName('#__associations')) - ->where($db->quoteName('context') . ' = ' . $db->quote($this->associationsContext)); - - if ($associations) - { - $query->where('(' . $db->quoteName('id') . ' IN (' . implode(',', $associations) . ') OR ' - . $db->quoteName('key') . ' = ' . $db->quote($oldKey) . ')' - ); - } - else + if ($associations || $oldKey !== null) { - $query->where($db->quoteName('key') . ' = ' . $db->quote($oldKey)); + $where = []; + + // Deleting old associations for the associated items + $query = $db->getQuery(true) + ->delete($db->quoteName('#__associations')) + ->where($db->quoteName('context') . ' = :associationscontext') + ->bind(':associationscontext', $this->associationsContext); + + if ($associations) + { + $where[] = $db->quoteName('id') . ' IN (' . implode(',', $query->bindArray(array_values($associations))) . ')'; + } + + if ($oldKey !== null) + { + $where[] = $db->quoteName('key') . ' = :oldKey'; + $query->bind(':oldKey', $oldKey); + } + + $query->extendWhere('AND', $where, 'OR'); } $db->setQuery($query); @@ -680,11 +692,28 @@ public function save($data) // Adding new association for these items $key = md5(json_encode($associations)); $query->clear() - ->insert('#__associations'); + ->insert($db->quoteName('#__associations')) + ->columns( + [ + $db->quoteName('id'), + $db->quoteName('context'), + $db->quoteName('key'), + ] + ); foreach ($associations as $id) { - $query->values(((int) $id) . ',' . $db->quote($this->associationsContext) . ',' . $db->quote($key)); + $id = (int) $id; + + $query->values( + implode( + ',', + $query->bindArray( + [$id, $this->associationsContext, $key], + [ParameterType::INTEGER, ParameterType::STRING, ParameterType::STRING] + ) + ) + ); } $db->setQuery($query); @@ -834,9 +863,10 @@ protected function batchFlipordering($value, $pks, $contexts) */ foreach ($pks as $id) { - $query->select('MAX(ordering)') - ->from('#__content') - ->where($db->quoteName('catid') . ' = ' . $db->quote($id)); + $query->select('MAX(' . $db->quoteName('ordering') . ')') + ->from($db->quoteName('#__content')) + ->where($db->quoteName('catid') . ' = :catid') + ->bind(':catid', $id, ParameterType::INTEGER); $db->setQuery($query); @@ -845,9 +875,11 @@ protected function batchFlipordering($value, $pks, $contexts) $query->clear(); - $query->update('#__content') - ->set($db->quoteName('ordering') . ' = ' . $max . ' - ' . $db->quoteName('ordering')) - ->where($db->quoteName('catid') . ' = ' . $db->quote($id)); + $query->update($db->quoteName('#__content')) + ->set($db->quoteName('ordering') . ' = :max - ' . $db->quoteName('ordering')) + ->where($db->quoteName('catid') . ' = :catid') + ->bind(':max', $max, ParameterType::INTEGER) + ->bind(':catid', $id, ParameterType::INTEGER); $db->setQuery($query); @@ -946,7 +978,7 @@ protected function batchCopy($value, $pks, $contexts) // Calculate the emergency stop count as a precaution against a runaway loop bug $query = $db->getQuery(true) - ->select('COUNT(id)') + ->select('COUNT(' . $db->quoteName('id') . ')') ->from($db->quoteName('#__categories')); $db->setQuery($query); @@ -988,11 +1020,15 @@ protected function batchCopy($value, $pks, $contexts) } // Copy is a bit tricky, because we also need to copy the children + $lft = (int) $this->table->lft; + $rgt = (int) $this->table->rgt; $query->clear() - ->select('id') + ->select($db->quoteName('id')) ->from($db->quoteName('#__categories')) - ->where('lft > ' . (int) $this->table->lft) - ->where('rgt < ' . (int) $this->table->rgt); + ->where($db->quoteName('lft') . ' > :lft') + ->where($db->quoteName('rgt') . ' < :rgt') + ->bind(':lft', $lft, ParameterType::INTEGER) + ->bind(':rgt', $rgt, ParameterType::INTEGER); $db->setQuery($query); $childIds = $db->loadColumn(); @@ -1052,11 +1088,14 @@ protected function batchCopy($value, $pks, $contexts) // Copy rules $query->clear() ->update($db->quoteName('#__assets', 't')) - ->join('INNER', $db->quoteName('#__assets', 's') . - ' ON ' . $db->quoteName('s.id') . ' = ' . $oldAssetId + ->join('INNER', + $db->quoteName('#__assets', 's'), + $db->quoteName('s.id') . ' = :oldid' ) + ->bind(':oldid', $oldAssetId, ParameterType::INTEGER) ->set($db->quoteName('t.rules') . ' = ' . $db->quoteName('s.rules')) - ->where($db->quoteName('t.id') . ' = ' . $this->table->asset_id); + ->where($db->quoteName('t.id') . ' = :assetid') + ->bind(':assetid', $this->table->asset_id, ParameterType::INTEGER); $db->setQuery($query)->execute(); // Now we log the old 'parent' to the new 'parent' @@ -1186,11 +1225,16 @@ protected function batchMove($value, $pks, $contexts) // Check if we are moving to a different parent if ($parentId != $this->table->parent_id) { + $lft = (int) $this->table->lft; + $rgt = (int) $this->table->rgt; + // Add the child node ids to the children array. $query->clear() - ->select('id') + ->select($db->quoteName('id')) ->from($db->quoteName('#__categories')) - ->where($db->quoteName('lft') . ' BETWEEN ' . (int) $this->table->lft . ' AND ' . (int) $this->table->rgt); + ->where($db->quoteName('lft') . ' BETWEEN :lft AND :rgt') + ->bind(':lft', $lft, ParameterType::INTEGER) + ->bind(':rgt', $rgt, ParameterType::INTEGER); $db->setQuery($query); try diff --git a/administrator/components/com_categories/Service/HTML/AdministratorService.php b/administrator/components/com_categories/Service/HTML/AdministratorService.php index ee5932ffb26dd..1da609857f992 100644 --- a/administrator/components/com_categories/Service/HTML/AdministratorService.php +++ b/administrator/components/com_categories/Service/HTML/AdministratorService.php @@ -15,6 +15,7 @@ use Joomla\CMS\Layout\LayoutHelper; use Joomla\CMS\Router\Route; use Joomla\Component\Categories\Administrator\Helper\CategoriesHelper; +use Joomla\Database\ParameterType; use Joomla\Utilities\ArrayHelper; /** @@ -48,15 +49,25 @@ 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'), + $db->quoteName('c.title'), + $db->quoteName('l.sef', 'lang_sef'), + $db->quoteName('l.lang_code'), + $db->quoteName('l.image'), + $db->quoteName('l.title', 'language_title'), + ] + ) + ->from($db->quoteName('#__categories', 'c')) + ->whereIn($db->quoteName('c.id'), array_values($associations)) + ->where($db->quoteName('c.id') . ' != :catid') + ->bind(':catid', $catid, ParameterType::INTEGER) + ->join( + 'LEFT', + $db->quoteName('#__languages', 'l'), + $db->quoteName('c.language') . ' = ' . $db->quoteName('l.lang_code') + ); $db->setQuery($query); try