Está en la página 1de 163

Descripción del proyecto

En la actualidad el comercio electrónico ha crecido últimamente en los últimos


años simplemente por comodidad, rapidez, variedad entre otras cosas como
menciona Somalo “El comercio electrónico es el traslado de transacciones
normales, comerciales, gubernamentales o personales a medios computarizados,
incluyendo una gran variedad de actividades. Consiste principalmente en utilizar
las nuevas tecnologías para relacionarse y realizar transacciones de forma directa
entre agentes económicos (Somalo, 2018)”. Para el proyecto de una tienda en
línea para ofrecer servicios de animación de videos, introducciones, animación en
3D, animaciones de logotipos y otros servicios. Con este proyecto se podrán ver
catálogos como ejemplos de animaciones, y de trabajos de clientes. La tienda
contara con un carrito en línea y contacto directo para cualquier solicitud.
Portafolio de servicios
Catálogo de productos Duracion en segundos Precio en Pesos
EDICION 500
INTRO PARA VIDEO 150
ANIMACION 15-30 300
ANIMACION 7-15 200
ANIMACION 5-7 100
LOGTIPO 100
FLYERS 80
SESION DE FOTOS CON
CELULAR 200
TARJETAS DE
PRESENTACIóN 50
Diseño de menu para
restaurante 80
historia de redes sociales 80
5 historias para Instagram 300

Tabla comparativa de funcionalidades

Funcionalidades OPCOM WGM Animations


Videos de trabajos
realizados ✓ ✓
Correo de contactos ✓ ✓
Servicios de audio ✓ ✓
Contactos de redes
sociales ✓ ✓
Carrito de compras
inmediata ✓
Metodología elegida

El modelo elegido para la ejecución de cada una de las etapas un modelo


dinámico llamado “Modelo incremental” la cual es una propuesta que consiste en
aplicar secuencias lineales para desarrollar pequeños incrementos de la versión
final de desarrollo, agregando más funcionalidades conforme avanza el proyecto.
Como menciona Roger S. Pressman. (2010) El modelo incremental ejecuta una
serie de avances, llamados incrementos, que en forma progresiva dan más
funcionalidad al cliente conforme se le entrega cada incremento.

Roger S, Pressman. (2010) imagen descriptiva de un modelo incremental (pp.36)

La elección de este modelo es para poder realizar pequeñas presentaciones de


cada incremento hasta un producto final, poder llevar retroalimentaciones y
trabajar en algunos requerimientos nuevos que se deseen implementar, siempre y
cuando no afecten a la arquitectura del proyecto.

Requisitos funcionales y no funcionales


 La página será capaz de contar con un catálogo de ejemplos de
animaciones de productos en su página de inicio
 El software será capaz de contar con un carrito para compra directa de
algún servicio de interés
 El sitio Web contara con los contactos directos de redes sociales para
ponerse en contacto rápidamente con el cliente para cualquier duda
 Los pagos se podrán realizar en línea.

Diagrama de casos de uso general.


Diagrama de caso de uso de interacciones del usuario con la página web.

Diseño arquitectónico del sitio web.


El diagrama arquitectónico muestra la distribución de la arquitectura de los
proyectos

Diseño de la base de datos y descripción de cada una de las tablas que la


integran.

Diagrama de diseño de las tablas principales de la base de datos.


Tabla empleada donde se guardarán los registros básicos del todos los empleados

Tabla orden donde se llevará el registro de todas las órdenes de compra


Tabla de usuario donde se almacenará los servicios con los que cuenta en la
pagina

Tabla donde se guardarán los datos de usuario

Estas son las principales tablas con las cuales se partiría la base de la aplicación
de la página web, conforme vayan creciendo las necesidades del sistema se
añadirán tablas más complejas que ayuden a facilitar la información.

Prototipos de las interfaces gráficas de la tienda de productos.

Los prototipos fueron creados en Figma, una plataforma especializada para el


diseño UX y creación de prototipos como AdobeXd.
Diagramas de navegabilidad.

Diagrama de navegabilidad no lineal realizado en power point


Código fuente documentado

Página de inicio de sesión

use PrestaShop\PrestaShop\Core\Util\InternationalizedDomainNameConverter;
use Symfony\Component\Translation\TranslatorInterface;

class CustomerLoginFormCore extends AbstractForm


{
private $context;
private $urls;

protected $template = 'customer/_partials/login-form.tpl';

/**
* @var InternationalizedDomainNameConverter
*/
private $IDNConverter;

public function __construct(


Smarty $smarty,
Context $context,
TranslatorInterface $translator,
CustomerLoginFormatter $formatter,
array $urls
){
parent::__construct(
$smarty,
$translator,
$formatter
);

$this->context = $context;
$this->translator = $translator;
$this->formatter = $formatter;
$this->urls = $urls;
$this->constraintTranslator = new ValidateConstraintTranslator(
$this->translator
);
$this->IDNConverter = new InternationalizedDomainNameConverter();
}

public function submit()


{
if ($this->validate()) {
Hook::exec('actionAuthenticationBefore');

$customer = new Customer();


$authentication = $customer->getByEmail(
$this->getValue('email'),
$this->getValue('password')
);

if (isset($authentication->active) && !$authentication->active) {


$this->errors[''][] = $this->translator->trans('Your account isn\'t available at this time,
please contact us', [], 'Shop.Notifications.Error');
} elseif (!$authentication || !$customer->id || $customer->is_guest) {
$this->errors[''][] = $this->translator->trans('Authentication failed.', [],
'Shop.Notifications.Error');
} else {
$this->context->updateCustomer($customer);

Hook::exec('actionAuthentication', ['customer' => $this->context->customer]);

// Login information have changed, so we check if the cart rules still apply
CartRule::autoRemoveFromCart($this->context);
CartRule::autoAddToCart($this->context);
}
}

return !$this->hasErrors();
}

public function fillWith(array $params = [])


{
if (!empty($params['email'])) {
// In some cases, browsers convert non ASCII chars (from input type="email") to
"punycode",
// we need to convert it back
$params['email'] = $this->IDNConverter->emailToUtf8($params['email']);
}

return parent::fillWith($params);
}

public function getTemplateVariables()


{
if (!$this->formFields) {
$this->formFields = $this->formatter->getFormat();
}

return [
'action' => $this->action,
'urls' => $this->urls,
'formFields' => array_map(
function (FormField $field) {
return $field->toArray();
},
$this->formFields
),
'errors' => $this->getErrors(),
];
}
}

class CustomerLoginFormatterCore implements FormFormatterInterface


{
private $translator;

public function __construct(TranslatorInterface $translator)


{
$this->translator = $translator;
}

public function getFormat()


{
return [
'back' => (new FormField())
->setName('back')
->setType('hidden'),
'email' => (new FormField())
->setName('email')
->setType('email')
->setAutocompleteAttribute('email')
->setRequired(true)
->setLabel($this->translator->trans(
'Email',
[],
'Shop.Forms.Labels'
))
->addConstraint('isEmail'),
'password' => (new FormField())
->setName('password')
->setType('password')
->setAutocompleteAttribute('current-password')
->setRequired(true)
->setLabel($this->translator->trans(
'Password',
[],
'Shop.Forms.Labels'
))
->addConstraint('isPasswd'),
];
}
}

Página categoría de productos

class CategoryCore extends ObjectModel


{
public $id;

/** @var int category ID */


public $id_category;

/** @var mixed string or array of Name */


public $name;

/** @var bool Status for display */


public $active = 1;
/** @var int category position */
public $position;

/** @var mixed string or array of Description */


public $description;

/** @var int Parent category ID */


public $id_parent;

/** @var int default Category id */


public $id_category_default;

/** @var int Parents number */


public $level_depth;

/** @var int Nested tree model "left" value */


public $nleft;

/** @var int Nested tree model "right" value */


public $nright;

/** @var mixed string or array of string used in rewrited URL */


public $link_rewrite;

/** @var mixed string or array of Meta title */


public $meta_title;

/** @var mixed string or array of Meta keywords */


public $meta_keywords;

/** @var mixed string or array of Meta description */


public $meta_description;

/** @var string Object creation date */


public $date_add;

/** @var string Object last modification date */


public $date_upd;

/** @var bool is Category Root */


public $is_root_category;

/** @var int */


public $id_shop_default;

public $groupBox;

/** @var bool */


public $doNotRegenerateNTree = false;

protected static $_links = [];

/**
* @see ObjectModel::$definition
*/
public static $definition = [
'table' => 'category',
'primary' => 'id_category',
'multilang' => true,
'multilang_shop' => true,
'fields' => [
'nleft' => ['type' => self::TYPE_INT, 'validate' => 'isUnsignedInt'],
'nright' => ['type' => self::TYPE_INT, 'validate' => 'isUnsignedInt'],
'level_depth' => ['type' => self::TYPE_INT, 'validate' => 'isUnsignedInt'],
'active' => ['type' => self::TYPE_BOOL, 'validate' => 'isBool', 'required' => true],
'id_parent' => ['type' => self::TYPE_INT, 'validate' => 'isUnsignedInt'],
'id_shop_default' => ['type' => self::TYPE_INT, 'validate' => 'isUnsignedId'],
'is_root_category' => ['type' => self::TYPE_BOOL, 'validate' => 'isBool'],
'position' => ['type' => self::TYPE_INT],
'date_add' => ['type' => self::TYPE_DATE, 'validate' => 'isDate'],
'date_upd' => ['type' => self::TYPE_DATE, 'validate' => 'isDate'],
/* Lang fields */
'name' => ['type' => self::TYPE_STRING, 'lang' => true, 'validate' => 'isCatalogName',
'required' => true, 'size' => 128],
'link_rewrite' => [
'type' => self::TYPE_STRING,
'lang' => true,
'validate' => 'isLinkRewrite',
'required' => true,
'size' => 128,
'ws_modifier' => [
'http_method' => WebserviceRequest::HTTP_POST,
'modifier' => 'modifierWsLinkRewrite',
],
],
'description' => ['type' => self::TYPE_HTML, 'lang' => true, 'validate' => 'isCleanHtml'],
'meta_title' => ['type' => self::TYPE_STRING, 'lang' => true, 'validate' => 'isGenericName',
'size' => 255],
'meta_description' => ['type' => self::TYPE_STRING, 'lang' => true, 'validate' =>
'isGenericName', 'size' => 512],
'meta_keywords' => ['type' => self::TYPE_STRING, 'lang' => true, 'validate' =>
'isGenericName', 'size' => 255],
],
];

/** @var string id_image is the category ID when an image exists and 'default' otherwise */
public $id_image = 'default';

protected $webserviceParameters = [
'objectsNodeName' => 'categories',
'hidden_fields' => ['nleft', 'nright', 'groupBox'],
'fields' => [
'id_parent' => ['xlink_resource' => 'categories'],
'level_depth' => ['setter' => false],
'nb_products_recursive' => ['getter' => 'getWsNbProductsRecursive', 'setter' => false],
],
'associations' => [
'categories' => ['getter' => 'getChildrenWs', 'resource' => 'category'],
'products' => ['getter' => 'getProductsWs', 'resource' => 'product'],
],
];

/**
* CategoryCore constructor.
*
* @param int|null $idCategory
* @param int|null $idLang
* @param int|null $idShop
*/
public function __construct($idCategory = null, $idLang = null, $idShop = null)
{
parent::__construct($idCategory, $idLang, $idShop);
$this->image_dir = _PS_CAT_IMG_DIR_;
$this->id_image = ($this->id && file_exists($this->image_dir . (int) $this->id . '.jpg')) ? (int)
$this->id : false;
if (defined('PS_INSTALLATION_IN_PROGRESS')) {
$this->doNotRegenerateNTree = true;
}
}

/**
* Get the clean description without HTML tags and slashes.
*
* @param string $description Category description with HTML
*
* @return string Category description without HTML
*/
public static function getDescriptionClean($description)
{
return Tools::getDescriptionClean($description);
}

/**
* Adds current Category as a new Object to the database.
*
* @param bool $autoDate Automatically set `date_upd` and `date_add` columns
* @param bool $nullValues Whether we want to use NULL values instead of empty quotes
values
*
* @return bool Indicates whether the Category has been successfully added
*
* @throws PrestaShopDatabaseException
* @throws PrestaShopException
*/
public function add($autoDate = true, $nullValues = false)
{
if (!isset($this->level_depth)) {
$this->level_depth = $this->calcLevelDepth();
}

if ($this->is_root_category && ($idRootCategory = (int)


Configuration::get('PS_ROOT_CATEGORY'))) {
$this->id_parent = $idRootCategory;
}

$ret = parent::add($autoDate, $nullValues);


if (Tools::isSubmit('checkBoxShopAsso_category')) {
foreach (Tools::getValue('checkBoxShopAsso_category') as $idShop => $value) {
$position = (int) Category::getLastPosition((int) $this->id_parent, $idShop);
$this->addPosition($position, $idShop);
}
} else {
foreach (Shop::getShops(true) as $shop) {
$position = (int) Category::getLastPosition((int) $this->id_parent, $shop['id_shop']);
$this->addPosition($position, $shop['id_shop']);
}
}

if (!$this->doNotRegenerateNTree) {
Category::regenerateEntireNtree();
}
// if access group is not set, initialize it with 3 default groups
$this->updateGroup(($this->groupBox !== null) ? $this->groupBox : []);
Hook::exec('actionCategoryAdd', ['category' => $this]);

return $ret;
}

/**
* Updates the current object in the database.
*
* @param bool $nullValues Whether we want to use NULL values instead of empty quotes
values
*
* @return bool Indicates whether the CartRule has been successfully updated
*
* @throws PrestaShopDatabaseException
* @throws PrestaShopException
*/
public function update($nullValues = false)
{
if ($this->id_parent == $this->id) {
throw new PrestaShopException('a category cannot be its own parent');
}

if ($this->is_root_category && $this->id_parent != (int)


Configuration::get('PS_ROOT_CATEGORY')) {
$this->is_root_category = 0;
}

// Update group selection


$this->updateGroup($this->groupBox);

if ($this->level_depth != $this->calcLevelDepth()) {
$this->level_depth = $this->calcLevelDepth();
$changed = true;
}

// If the parent category was changed, we don't want to have 2 categories with the same
position
if (!isset($changed)) {
$changed = $this->getDuplicatePosition();
}

if ($changed) {
if (Tools::isSubmit('checkBoxShopAsso_category')) {
foreach (Tools::getValue('checkBoxShopAsso_category') as $idAssoObject => $idShop) {
$this->addPosition($this->position, (int) $idShop);
}
} else {
foreach (Shop::getShops(true) as $shop) {
$this->addPosition($this->position, $shop['id_shop']);
}
}
}

$ret = parent::update($nullValues);
if ($changed && !$this->doNotRegenerateNTree) {
$this->cleanPositions((int) $this->id_parent);
Category::regenerateEntireNtree();
$this->recalculateLevelDepth($this->id);
}
Hook::exec('actionCategoryUpdate', ['category' => $this]);

return $ret;
}

/**
* Toggles the `active` flag.
*
* @return bool Indicates whether the status was successfully toggled
*/
public function toggleStatus()
{
$result = parent::toggleStatus();
Hook::exec('actionCategoryUpdate', ['category' => $this]);

return $result;
}

/**
* Recursive scan of subcategories.
*
* @param int $maxDepth Maximum depth of the tree (i.e. 2 => 3 levels depth)
* @param int $currentDepth specify the current depth in the tree (don't use it, only for
recursive calls!)
* @param int $idLang Specify the id of the language used
* @param array $excludedIdsArray Specify a list of IDs to exclude of results
* @param string $format
*
* @return array Subcategories lite tree
*/
public function recurseLiteCategTree($maxDepth = 3, $currentDepth = 0, $idLang = null,
$excludedIdsArray = null, $format = 'default')
{
$idLang = null === $idLang ? Context::getContext()->language->id : (int) $idLang;

$children = [];
$subcats = $this->getSubCategories($idLang, true);
if (($maxDepth == 0 || $currentDepth < $maxDepth) && $subcats && count($subcats)) {
foreach ($subcats as $subcat) {
if (!$subcat['id_category']) {
break;
} elseif (!is_array($excludedIdsArray) || !in_array($subcat['id_category'],
$excludedIdsArray)) {
$categ = new Category($subcat['id_category'], $idLang);
$children[] = $categ->recurseLiteCategTree($maxDepth, $currentDepth + 1, $idLang,
$excludedIdsArray, $format);
}
}
}

if (is_array($this->description)) {
foreach ($this->description as $lang => $description) {
$this->description[$lang] = Category::getDescriptionClean($description);
}
} else {
$this->description = Category::getDescriptionClean($this->description);
}

if ($format === 'sitemap') {


return [
'id' => 'category-page-' . (int) $this->id,
'label' => $this->name,
'url' => Context::getContext()->link->getCategoryLink($this->id, $this->link_rewrite),
'children' => $children,
];
}

return [
'id' => (int) $this->id,
'link' => Context::getContext()->link->getCategoryLink($this->id, $this->link_rewrite),
'name' => $this->name,
'desc' => $this->description,
'children' => $children,
];
}

/**
* Recursively add specified category childs to $to_delete array.
*
* @param array &$toDelete Array reference where categories ID will be saved
* @param int $idCategory Parent category ID
*/
protected function recursiveDelete(&$toDelete, $idCategory)
{
if (!is_array($toDelete) || !$idCategory) {
die(Tools::displayError());
}

$sql = new DbQuery();


$sql->select('`id_category`');
$sql->from('category');
$sql->where('`id_parent` = ' . (int) $idCategory);
$result = Db::getInstance()->executeS($sql);
foreach ($result as $row) {
$toDelete[] = (int) $row['id_category'];
$this->recursiveDelete($toDelete, (int) $row['id_category']);
}
}

/**
* Delete this object
* Skips the deletion procedure of Category and directly calls
* the delete() method of ObjectModel instead.
*
* @return bool Indicates whether this Category was successfully deleted
*/
public function deleteLite()
{
return parent::delete();
}

/**
* Deletes current CartRule from the database.
*
* @return bool `true` if successfully deleted
*
* @throws PrestaShopException
*/
public function delete()
{
if ((int) $this->id === 0 || (int) $this->id === (int) Configuration::get('PS_ROOT_CATEGORY')) {
return false;
}

$this->clearCache();

$deletedChildren = $allCat = $this->getAllChildren();


$allCat[] = $this;
foreach ($allCat as $cat) {
/* @var Category $cat */
$cat->deleteLite();
if (!$cat->hasMultishopEntries()) {
$cat->deleteImage();
$cat->cleanGroups();
$cat->cleanAssoProducts();
// Delete associated restrictions on cart rules
CartRule::cleanProductRuleIntegrity('categories', [$cat->id]);
Category::cleanPositions($cat->id_parent);
/* Delete Categories in GroupReduction */
if (GroupReduction::getGroupsReductionByCategoryId((int) $cat->id)) {
GroupReduction::deleteCategory($cat->id);
}
}
}

/* Rebuild the nested tree */


if (!$this->hasMultishopEntries() && !$this->doNotRegenerateNTree) {
Category::regenerateEntireNtree();
}

Hook::exec('actionCategoryDelete', ['category' => $this, 'deleted_children' =>


$deletedChildren]);

return true;
}

/**
* Delete selected categories from database.
*
* @param array $idCategories Category IDs to delete
*
* @return bool Deletion result
*/
public function deleteSelection($idCategories)
{
$return = 1;
foreach ($idCategories as $idCategory) {
$category = new Category($idCategory);
if ($category->isRootCategoryForAShop()) {
return false;
} else {
$return &= $category->delete();
}
}

return $return;
}

/**
* Get the depth level for the category.
*
* @return int Depth level
*
* @throws PrestaShopException
*/
public function calcLevelDepth()
{
/* Root category */
if (!$this->id_parent) {
return 0;
}

$parentCategory = new Category((int) $this->id_parent);


if (!Validate::isLoadedObject($parentCategory)) {
if (is_array($this->name)) {
$name = $this->name[Context::getContext()->language->id];
} else {
$name = $this->name;
}

throw new PrestaShopException('Parent category ' . $this->id_parent . ' does not exist.
Current category: ' . $name);
}

return (int) $parentCategory->level_depth + 1;


}

/**
* Re-calculate the values of all branches of the nested tree.
*/
public static function regenerateEntireNtree()
{
$id = Context::getContext()->shop->id;
$idShop = $id ? $id : Configuration::get('PS_SHOP_DEFAULT');
$sql = new DbQuery();
$sql->select('c.`id_category`, c.`id_parent`');
$sql->from('category', 'c');
$sql->leftJoin('category_shop', 'cs', 'c.`id_category` = cs.`id_category` AND cs.`id_shop` = ' .
(int) $idShop);
$sql->orderBy('c.`id_parent`, cs.`position` ASC');
$categories = Db::getInstance()->executeS($sql);
$categoriesArray = [];
foreach ($categories as $category) {
$categoriesArray[$category['id_parent']]['subcategories'][] = $category['id_category'];
}
$n = 1;

if (isset($categoriesArray[0]) && $categoriesArray[0]['subcategories']) {


$queries = Category::computeNTreeInfos($categoriesArray, $categoriesArray[0]
['subcategories'][0], $n);

// update by batch of 5000 categories


$chunks = array_chunk($queries, 5000);
foreach ($chunks as $chunk) {
$sqlChunk = array_map(function ($value) { return '(' . rtrim(implode(',', $value)) . ')'; },
$chunk);
Db::getInstance()->execute('INSERT INTO `' . _DB_PREFIX_ . 'category` (id_category, nleft,
nright)
VALUES ' . rtrim(implode(',', $sqlChunk), ',') . '
ON DUPLICATE KEY UPDATE nleft=VALUES(nleft), nright=VALUES(nright)');
}
}
}

/**
* @param $categories
* @param $idCategory
* @param $n
*
* @deprecated 1.7.0
*/
protected static function _subTree(&$categories, $idCategory, &$n)
{
self::subTree($categories, $idCategory, $n);
}

/**
* @param array $categories
* @param int $idCategory
* @param int $n
*
* @return array ntree infos
*/
protected static function computeNTreeInfos(&$categories, $idCategory, &$n)
{
$queries = [];
$left = $n++;
if (isset($categories[(int) $idCategory]['subcategories'])) {
foreach ($categories[(int) $idCategory]['subcategories'] as $idSubcategory) {
$queries = array_merge($queries, Category::computeNTreeInfos($categories, (int)
$idSubcategory, $n));
}
}
$right = (int) $n++;

$queries[] = [$idCategory, $left, $right];

return $queries;
}

/**
* @param $categories
* @param $idCategory
* @param $n
*
* @return bool Indicates whether the sub tree of categories has been successfully updated
*
* @deprecated 1.7.6.0 use computeNTreeInfos + sql query instead
*/
protected static function subTree(&$categories, $idCategory, &$n)
{
$left = $n++;
if (isset($categories[(int) $idCategory]['subcategories'])) {
foreach ($categories[(int) $idCategory]['subcategories'] as $idSubcategory) {
Category::subTree($categories, (int) $idSubcategory, $n);
}
}
$right = (int) $n++;

return Db::getInstance()->update(
'category',
[
'nleft' => (int) $left,
'nright' => (int) $right,
],
'`id_category` = ' . (int) $idCategory,
1
);
}

/**
* Updates `level_depth` for all children of the given `id_category`.
*
* @param int $idParentCategory Parent Category ID
*
* @throws PrestaShopException
*/
public function recalculateLevelDepth($idParentCategory)
{
if (!is_numeric($idParentCategory)) {
throw new PrestaShopException('id category is not numeric');
}
/* Gets all children */
$sql = new DbQuery();
$sql->select('c.`id_category`, c.`id_parent`, c.`level_depth`');
$sql->from('category', 'c');
$sql->where('c.`id_parent` = ' . (int) $idParentCategory);
$categories = Db::getInstance(_PS_USE_SQL_SLAVE_)->executeS($sql);
/* Gets level_depth */
$sql = new DbQuery();
$sql->select('c.`level_depth`');
$sql->from('category', 'c');
$sql->where('c.`id_category` = ' . (int) $idParentCategory);
$level = Db::getInstance(_PS_USE_SQL_SLAVE_)->getRow($sql);
/* Updates level_depth for all children */
foreach ($categories as $subCategory) {
Db::getInstance()->update(
'category',
[
'level_depth' => (int) ($level['level_depth'] + 1),
],
'`id_category` = ' . (int) $subCategory['id_category']
);
/* Recursive call */
$this->recalculateLevelDepth($subCategory['id_category']);
}
}

/**
* Return available categories.
*
* @param bool|int $idLang Language ID
* @param bool $active Only return active categories
* @param bool $order Order the results
* @param string $sqlFilter Additional SQL clause(s) to filter results
* @param string $orderBy Change the default order by
* @param string $limit Set the limit
* Both the offset and limit can be given
*
* @return array Categories
*/
public static function getCategories($idLang = false, $active = true, $order = true, $sqlFilter = '',
$orderBy = '', $limit = '')
{
if (!Validate::isBool($active)) {
die(Tools::displayError());
}
$result = Db::getInstance(_PS_USE_SQL_SLAVE_)->executeS(
'
SELECT *
FROM `' . _DB_PREFIX_ . 'category` c
' . Shop::addSqlAssociation('category', 'c') . '
LEFT JOIN `' . _DB_PREFIX_ . 'category_lang` cl ON c.`id_category` =
cl.`id_category`' . Shop::addSqlRestrictionOnLang('cl') . '
WHERE 1 ' . $sqlFilter . ' ' . ($idLang ? 'AND `id_lang` = ' . (int) $idLang : '') . '
' . ($active ? 'AND `active` = 1' : '') . '
' . (!$idLang ? 'GROUP BY c.id_category' : '') . '
' . ($orderBy != '' ? $orderBy : 'ORDER BY c.`level_depth` ASC,
category_shop.`position` ASC') . '
' . ($limit != '' ? $limit : '')
);

if (!$order) {
return $result;
}

$categories = [];
foreach ($result as $row) {
$categories[$row['id_parent']][$row['id_category']]['infos'] = $row;
}

return $categories;
}

/**
* @param int $idRootCategory ID of root Category
* @param int|bool $idLang Language ID
* `false` if language filter should not be applied
* @param bool $active Only return active categories
* @param array|null $groups
* @param bool $useShopRestriction Restrict to current Shop
* @param string $sqlFilter Additional SQL clause(s) to filter results
* @param string $orderBy Change the default order by
* @param string $limit Set the limit
* Both the offset and limit can be given
*
* @return array|false|mysqli_result|PDOStatement|resource|null Array with `id_category`
and `name`
*/
public static function getAllCategoriesName(
$idRootCategory = null,
$idLang = false,
$active = true,
$groups = null,
$useShopRestriction = true,
$sqlFilter = '',
$orderBy = '',
$limit = ''
){
if (isset($idRootCategory) && !Validate::isInt($idRootCategory)) {
die(Tools::displayError());
}

if (!Validate::isBool($active)) {
die(Tools::displayError());
}

if (isset($groups) && Group::isFeatureActive() && !is_array($groups)) {


$groups = (array) $groups;
}

$cacheId = 'Category::getAllCategoriesName_' . md5(


(int) $idRootCategory .
(int) $idLang .
(int) $active .
(int) $useShopRestriction .
(isset($groups) && Group::isFeatureActive() ? implode('', $groups) : '') .
(isset($sqlFilter) ? $sqlFilter : '') .
(isset($orderBy) ? $orderBy : '') .
(isset($limit) ? $limit : '')
);

if (!Cache::isStored($cacheId)) {
$result = Db::getInstance()->executeS(
'
SELECT c.`id_category`, cl.`name`
FROM `' . _DB_PREFIX_ . 'category` c
' . ($useShopRestriction ? Shop::addSqlAssociation('category', 'c') :
'') . '
LEFT JOIN `' . _DB_PREFIX_ . 'category_lang` cl ON c.`id_category`
= cl.`id_category`' . Shop::addSqlRestrictionOnLang('cl') . '
' . (isset($groups) && Group::isFeatureActive() ? 'LEFT JOIN `' .
_DB_PREFIX_ . 'category_group` cg ON c.`id_category` = cg.`id_category`' : '') . '
' . (isset($idRootCategory) ? 'RIGHT JOIN `' . _DB_PREFIX_ .
'category` c2 ON c2.`id_category` = ' . (int) $idRootCategory . ' AND c.`nleft` >= c2.`nleft` AND
c.`nright` <= c2.`nright`' : '') . '
WHERE 1 ' . $sqlFilter . ' ' . ($idLang ? 'AND `id_lang` = ' . (int)
$idLang : '') . '
' . ($active ? ' AND c.`active` = 1' : '') . '
' . (isset($groups) && Group::isFeatureActive() ? ' AND
cg.`id_group` IN (' . implode(',', array_map('intval', $groups)) . ')' : '') . '
' . (!$idLang || (isset($groups) && Group::isFeatureActive()) ? '
GROUP BY c.`id_category`' : '') . '
' . ($orderBy != '' ? $orderBy : ' ORDER BY c.`level_depth` ASC') . '
' . ($orderBy == '' && $useShopRestriction ? ',
category_shop.`position` ASC' : '') . '
' . ($limit != '' ? $limit : '')
);

Cache::store($cacheId, $result);
} else {
$result = Cache::retrieve($cacheId);
}
return $result;
}

/**
* Get nested categories.
*
* @param int|null $idRootCategory Root Category ID
* @param int|bool $idLang Language ID
* `false` if language filter should not be used
* @param bool $active Whether the category must be active
* @param null $groups
* @param bool $useShopRestriction Restrict to current Shop
* @param string $sqlFilter Additional SQL clause(s) to filter results
* @param string $orderBy Change the default order by
* @param string $limit Set the limit
* Both the offset and limit can be given
*
* @return array|null
*/
public static function getNestedCategories(
$idRootCategory = null,
$idLang = false,
$active = true,
$groups = null,
$useShopRestriction = true,
$sqlFilter = '',
$orderBy = '',
$limit = ''
){
if (isset($idRootCategory) && !Validate::isInt($idRootCategory)) {
die(Tools::displayError());
}

if (!Validate::isBool($active)) {
die(Tools::displayError());
}

if (isset($groups) && Group::isFeatureActive() && !is_array($groups)) {


$groups = (array) $groups;
}

$cacheId = 'Category::getNestedCategories_' . md5(


(int) $idRootCategory .
(int) $idLang .
(int) $active .
(int) $useShopRestriction .
(isset($groups) && Group::isFeatureActive() ? implode('', $groups) : '') .
(isset($sqlFilter) ? $sqlFilter : '') .
(isset($orderBy) ? $orderBy : '') .
(isset($limit) ? $limit : '')
);

if (!Cache::isStored($cacheId)) {
$result = Db::getInstance()->executeS(
'
SELECT c.*, cl.*
FROM `' . _DB_PREFIX_ . 'category` c
' . ($useShopRestriction ? Shop::addSqlAssociation('category', 'c') :
'') . '
LEFT JOIN `' . _DB_PREFIX_ . 'category_lang` cl ON c.`id_category`
= cl.`id_category`' . Shop::addSqlRestrictionOnLang('cl') . '
' . (isset($groups) && Group::isFeatureActive() ? 'LEFT JOIN `' .
_DB_PREFIX_ . 'category_group` cg ON c.`id_category` = cg.`id_category`' : '') . '
' . (isset($idRootCategory) ? 'RIGHT JOIN `' . _DB_PREFIX_ .
'category` c2 ON c2.`id_category` = ' . (int) $idRootCategory . ' AND c.`nleft` >= c2.`nleft` AND
c.`nright` <= c2.`nright`' : '') . '
WHERE 1 ' . $sqlFilter . ' ' . ($idLang ? 'AND `id_lang` = ' . (int)
$idLang : '') . '
' . ($active ? ' AND c.`active` = 1' : '') . '
' . (isset($groups) && Group::isFeatureActive() ? ' AND
cg.`id_group` IN (' . implode(',', array_map('intval', $groups)) . ')' : '') . '
' . (!$idLang || (isset($groups) && Group::isFeatureActive()) ? '
GROUP BY c.`id_category`' : '') . '
' . ($orderBy != '' ? $orderBy : ' ORDER BY c.`level_depth` ASC') . '
' . ($orderBy == '' && $useShopRestriction ? ',
category_shop.`position` ASC' : '') . '
' . ($limit != '' ? $limit : '')
);

$categories = [];
$buff = [];

if (!isset($idRootCategory)) {
$idRootCategory = Category::getRootCategory()->id;
}

foreach ($result as $row) {


$current = &$buff[$row['id_category']];
$current = $row;

if ($row['id_category'] == $idRootCategory) {
$categories[$row['id_category']] = &$current;
} else {
$buff[$row['id_parent']]['children'][$row['id_category']] = &$current;
}
}

Cache::store($cacheId, $categories);
} else {
$categories = Cache::retrieve($cacheId);
}

return $categories;
}

/**
* Get a simple list of categories with id_category and name for each Category.
*
* @param int $idLang Language ID
*
* @return array|false|mysqli_result|PDOStatement|resource|null
*/
public static function getSimpleCategories($idLang)
{
return Db::getInstance(_PS_USE_SQL_SLAVE_)->executeS('
SELECT c.`id_category`, cl.`name`
FROM `' . _DB_PREFIX_ . 'category` c
LEFT JOIN `' . _DB_PREFIX_ . 'category_lang` cl ON (c.`id_category` =
cl.`id_category`' . Shop::addSqlRestrictionOnLang('cl') . ')
' . Shop::addSqlAssociation('category', 'c') . '
WHERE cl.`id_lang` = ' . (int) $idLang . '
AND c.`id_category` != ' . Configuration::get('PS_ROOT_CATEGORY') . '
GROUP BY c.id_category
ORDER BY c.`id_category`, category_shop.`position`', true, false);
}

/**
* Get a simple list of categories with id_category, name and id_parent infos
* It also takes into account the root category of the current shop.
*
* @param int $idLang Language ID
*
* @return array|false|mysqli_result|PDOStatement|resource|null
*/
public static function getSimpleCategoriesWithParentInfos($idLang)
{
$context = Context::getContext();
if (count(Category::getCategoriesWithoutParent()) > 1
&& \Configuration::get('PS_MULTISHOP_FEATURE_ACTIVE')
&& count(Shop::getShops(true, null, true)) !== 1) {
$idCategoryRoot = (int) \Configuration::get('PS_ROOT_CATEGORY');
} elseif (!$context->shop->id) {
$idCategoryRoot = (new Shop(\Configuration::get('PS_SHOP_DEFAULT')))->id_category;
} else {
$idCategoryRoot = $context->shop->id_category;
}

$rootTreeInfo = Db::getInstance(_PS_USE_SQL_SLAVE_)->getRow(
'SELECT c.`nleft`, c.`nright` FROM `' . _DB_PREFIX_ . 'category` c ' .
'WHERE c.`id_category` = ' . (int) $idCategoryRoot
);

return Db::getInstance(_PS_USE_SQL_SLAVE_)->executeS('
SELECT c.`id_category`, cl.`name`, c.id_parent
FROM `' . _DB_PREFIX_ . 'category` c
LEFT JOIN `' . _DB_PREFIX_ . 'category_lang` cl
ON (c.`id_category` = cl.`id_category`' . Shop::addSqlRestrictionOnLang('cl') . ')
' . Shop::addSqlAssociation('category', 'c') . '
WHERE cl.`id_lang` = ' . (int) $idLang . '
AND c.`nleft` >= ' . (int) $rootTreeInfo['nleft'] . '
AND c.`nright` <= ' . (int) $rootTreeInfo['nright'] . '
GROUP BY c.id_category
ORDER BY c.`id_category`, category_shop.`position`');
}

/**
* Get Shop ID.
*
* @return int
*
* @deprecated 1.7.0
*/
public function getShopID()
{
return $this->id_shop;
}

/**
* Return current category childs.
*
* @param int $idLang Language ID
* @param bool $active return only active categories
*
* @return array Categories
*/
public function getSubCategories($idLang, $active = true)
{
$sqlGroupsWhere = '';
$sqlGroupsJoin = '';
if (Group::isFeatureActive()) {
$sqlGroupsJoin = 'LEFT JOIN `' . _DB_PREFIX_ . 'category_group` cg ON (cg.`id_category` =
c.`id_category`)';
$groups = FrontController::getCurrentCustomerGroups();
$sqlGroupsWhere = 'AND cg.`id_group` ' . (count($groups) ? 'IN (' . implode(',', $groups) .
')' : '=' . (int) Configuration::get('PS_UNIDENTIFIED_GROUP'));
}

$result = Db::getInstance(_PS_USE_SQL_SLAVE_)->executeS('
SELECT c.*, cl.`id_lang`, cl.`name`, cl.`description`, cl.`link_rewrite`, cl.`meta_title`,
cl.`meta_keywords`, cl.`meta_description`
FROM `' . _DB_PREFIX_ . 'category` c
' . Shop::addSqlAssociation('category', 'c') . '
LEFT JOIN `' . _DB_PREFIX_ . 'category_lang` cl ON (c.`id_category` =
cl.`id_category` AND `id_lang` = ' . (int) $idLang . ' ' . Shop::addSqlRestrictionOnLang('cl') . ')
' . $sqlGroupsJoin . '
WHERE `id_parent` = ' . (int) $this->id . '
' . ($active ? 'AND `active` = 1' : '') . '
' . $sqlGroupsWhere . '
GROUP BY c.`id_category`
ORDER BY `level_depth` ASC, category_shop.`position` ASC');

foreach ($result as &$row) {


$row['id_image'] = Tools::file_exists_cache($this->image_dir . $row['id_category'] . '.jpg') ?
(int) $row['id_category'] : Language::getIsoById($idLang) . '-default';
$row['legend'] = 'no picture';
}

return $result;
}

/**
* Returns category products.
*
* @param int $idLang Language ID
* @param int $pageNumber Page number
* @param int $productPerPage Number of products per page
* @param string|null $orderBy ORDER BY column
* @param string|null $orderWay Order way
* @param bool $getTotal If set to true, returns the total number of results only
* @param bool $active If set to true, finds only active products
* @param bool $random If true, sets a random filter for returned products
* @param int $randomNumberProducts Number of products to return if random is activated
* @param bool $checkAccess If set to `true`, check if the current customer
* can see the products from this category
* @param Context|null $context Instance of Context
*
* @return array|int|false Products, number of products or false (no access)
*
* @throws PrestaShopDatabaseException
*/
public function getProducts(
$idLang,
$pageNumber,
$productPerPage,
$orderBy = null,
$orderWay = null,
$getTotal = false,
$active = true,
$random = false,
$randomNumberProducts = 1,
$checkAccess = true,
Context $context = null
){
if (!$context) {
$context = Context::getContext();
}

if ($checkAccess && !$this->checkAccess($context->customer->id)) {


return false;
}

$front = in_array($context->controller->controller_type, ['front', 'modulefront']);


$idSupplier = (int) Tools::getValue('id_supplier');

/* Return only the number of products */


if ($getTotal) {
$sql = 'SELECT COUNT(cp.`id_product`) AS total
FROM `' . _DB_PREFIX_ . 'product` p
' . Shop::addSqlAssociation('product', 'p') . '
LEFT JOIN `' . _DB_PREFIX_ . 'category_product` cp ON
p.`id_product` = cp.`id_product`
WHERE cp.`id_category` = ' . (int) $this->id .
($front ? ' AND product_shop.`visibility` IN ("both", "catalog")' : '') .
($active ? ' AND product_shop.`active` = 1' : '') .
($idSupplier ? ' AND p.id_supplier = ' . (int) $idSupplier : '');

return (int) Db::getInstance(_PS_USE_SQL_SLAVE_)->getValue($sql);


}

if ($pageNumber < 1) {
$pageNumber = 1;
}

/** Tools::strtolower is a fix for all modules which are now using lowercase values for
'orderBy' parameter */
$orderBy = Validate::isOrderBy($orderBy) ? Tools::strtolower($orderBy) : 'position';
$orderWay = Validate::isOrderWay($orderWay) ? Tools::strtoupper($orderWay) : 'ASC';

$orderByPrefix = false;
if ($orderBy === 'id_product' || $orderBy === 'date_add' || $orderBy === 'date_upd') {
$orderByPrefix = 'p';
} elseif ($orderBy === 'name') {
$orderByPrefix = 'pl';
} elseif ($orderBy === 'manufacturer' || $orderBy === 'manufacturer_name') {
$orderByPrefix = 'm';
$orderBy = 'name';
} elseif ($orderBy === 'position') {
$orderByPrefix = 'cp';
}

if ($orderBy === 'price') {


$orderBy = 'orderprice';
}

$nbDaysNewProduct = Configuration::get('PS_NB_DAYS_NEW_PRODUCT');
if (!Validate::isUnsignedInt($nbDaysNewProduct)) {
$nbDaysNewProduct = 20;
}

$sql = 'SELECT p.*, product_shop.*, stock.out_of_stock, IFNULL(stock.quantity, 0) AS


quantity' . (Combination::isFeatureActive() ? ',
IFNULL(product_attribute_shop.id_product_attribute, 0) AS id_product_attribute,
product_attribute_shop.minimal_quantity AS
product_attribute_minimal_quantity' : '') . ', pl.`description`, pl.`description_short`,
pl.`available_now`,
pl.`available_later`, pl.`link_rewrite`,
pl.`meta_description`, pl.`meta_keywords`, pl.`meta_title`, pl.`name`, image_shop.`id_image`
id_image,
il.`legend` as legend, m.`name` AS manufacturer_name,
cl.`name` AS category_default,
DATEDIFF(product_shop.`date_add`, DATE_SUB("' .
date('Y-m-d') . ' 00:00:00",
INTERVAL ' . (int) $nbDaysNewProduct . ' DAY)) > 0 AS
new, product_shop.price AS orderprice
FROM `' . _DB_PREFIX_ . 'category_product` cp
LEFT JOIN `' . _DB_PREFIX_ . 'product` p
ON p.`id_product` = cp.`id_product`
' . Shop::addSqlAssociation('product', 'p') .
(Combination::isFeatureActive() ? ' LEFT JOIN `' . _DB_PREFIX_ . 'product_attribute_shop`
product_attribute_shop
ON (p.`id_product` = product_attribute_shop.`id_product` AND
product_attribute_shop.`default_on` = 1 AND product_attribute_shop.id_shop=' . (int) $context-
>shop->id . ')' : '') . '
' . Product::sqlStock('p', 0) . '
LEFT JOIN `' . _DB_PREFIX_ . 'category_lang` cl
ON (product_shop.`id_category_default` = cl.`id_category`
AND cl.`id_lang` = ' . (int) $idLang .
Shop::addSqlRestrictionOnLang('cl') . ')
LEFT JOIN `' . _DB_PREFIX_ . 'product_lang` pl
ON (p.`id_product` = pl.`id_product`
AND pl.`id_lang` = ' . (int) $idLang .
Shop::addSqlRestrictionOnLang('pl') . ')
LEFT JOIN `' . _DB_PREFIX_ . 'image_shop` image_shop
ON (image_shop.`id_product` = p.`id_product` AND
image_shop.cover=1 AND image_shop.id_shop=' . (int) $context->shop->id . ')
LEFT JOIN `' . _DB_PREFIX_ . 'image_lang` il
ON (image_shop.`id_image` = il.`id_image`
AND il.`id_lang` = ' . (int) $idLang . ')
LEFT JOIN `' . _DB_PREFIX_ . 'manufacturer` m
ON m.`id_manufacturer` = p.`id_manufacturer`
WHERE product_shop.`id_shop` = ' . (int) $context->shop->id . '
AND cp.`id_category` = ' . (int) $this->id
. ($active ? ' AND product_shop.`active` = 1' : '')
. ($front ? ' AND product_shop.`visibility` IN ("both", "catalog")' : '')
. ($idSupplier ? ' AND p.id_supplier = ' . (int) $idSupplier : '');

if ($random === true) {


$sql .= ' ORDER BY RAND() LIMIT ' . (int) $randomNumberProducts;
} elseif ($orderBy !== 'orderprice') {
$sql .= ' ORDER BY ' . (!empty($orderByPrefix) ? $orderByPrefix . '.' : '') . '`' . bqSQL($orderBy)
. '` ' . pSQL($orderWay) . '
LIMIT ' . (((int) $pageNumber - 1) * (int) $productPerPage) . ',' . (int)
$productPerPage;
}

$result = Db::getInstance(_PS_USE_SQL_SLAVE_)->executeS($sql, true, false);

if (!$result) {
return [];
}

if ($orderBy === 'orderprice') {


Tools::orderbyPrice($result, $orderWay);
$result = array_slice($result, (int) (($pageNumber - 1) * $productPerPage), (int)
$productPerPage);
}

// Modify SQL result


return Product::getProductsProperties($idLang, $result);
}

/**
* Return main categories.
*
* @param int $idLang Language ID
* @param bool $active return only active categories
*
* @return array categories
*/
public static function getHomeCategories($idLang, $active = true, $idShop = false)
{
return self::getChildren(Configuration::get('PS_HOME_CATEGORY'), $idLang, $active,
$idShop);
}

/**
* Get root Category object
* Returns the top Category if there are multiple root Categories.
*
* @param int|null $idLang Language ID
* @param Shop|null $shop Shop object
*
* @return Category object
*/
public static function getRootCategory($idLang = null, Shop $shop = null)
{
$context = Context::getContext();
if (null === $idLang) {
$idLang = $context->language->id;
}
if (!$shop) {
if (Shop::isFeatureActive() && Shop::getContext() != Shop::CONTEXT_SHOP) {
$shop = new Shop(Configuration::get('PS_SHOP_DEFAULT'));
} else {
$shop = $context->shop;
}
} else {
return new Category($shop->getCategory(), $idLang);
}
$isMoreThanOneRootCategory = count(Category::getCategoriesWithoutParent()) > 1;
if (Shop::isFeatureActive() && $isMoreThanOneRootCategory) {
$category = Category::getTopCategory($idLang);
} else {
$category = new Category($shop->getCategory(), $idLang);
}

return $category;
}

/**
* Get children of the given Category.
*
* @param int $idParent Parent Category ID
* @param int $idLang Language ID
* @param bool $active Active children only
* @param bool $idShop Shop ID
*
* @return array Children of given Category
*/
public static function getChildren($idParent, $idLang, $active = true, $idShop = false)
{
if (!Validate::isBool($active)) {
die(Tools::displayError());
}

$cacheId = 'Category::getChildren_' . (int) $idParent . '-' . (int) $idLang . '-' . (bool) $active . '-' .
(int) $idShop;
if (!Cache::isStored($cacheId)) {
$query = 'SELECT c.`id_category`, cl.`name`, cl.`link_rewrite`, category_shop.`id_shop`
FROM `' . _DB_PREFIX_ . 'category` c
LEFT JOIN `' . _DB_PREFIX_ . 'category_lang` cl ON (c.`id_category` =
cl.`id_category`' . Shop::addSqlRestrictionOnLang('cl') . ')
' . Shop::addSqlAssociation('category', 'c') . '
WHERE `id_lang` = ' . (int) $idLang . '
AND c.`id_parent` = ' . (int) $idParent . '
' . ($active ? 'AND `active` = 1' : '') . '
GROUP BY c.`id_category`
ORDER BY category_shop.`position` ASC';
$result = Db::getInstance(_PS_USE_SQL_SLAVE_)->executeS($query);
Cache::store($cacheId, $result);

return $result;
}

return Cache::retrieve($cacheId);
}

/**
* Check if the given Category has child categories.
*
* @param int $idParent Parent Category ID
* @param int $idLang Language ID
* @param bool $active Active children only
* @param bool $idShop Shop ID
*
* @return bool Indicates whether the given Category has children
*/
public static function hasChildren($idParent, $idLang, $active = true, $idShop = false)
{
if (!Validate::isBool($active)) {
die(Tools::displayError());
}

$cacheId = 'Category::hasChildren_' . (int) $idParent . '-' . (int) $idLang . '-' . (bool) $active . '-' .
(int) $idShop;
if (!Cache::isStored($cacheId)) {
$query = 'SELECT c.id_category, "" as name
FROM `' . _DB_PREFIX_ . 'category` c
LEFT JOIN `' . _DB_PREFIX_ . 'category_lang` cl ON (c.`id_category` =
cl.`id_category`' . Shop::addSqlRestrictionOnLang('cl') . ')
' . Shop::addSqlAssociation('category', 'c') . '
WHERE `id_lang` = ' . (int) $idLang . '
AND c.`id_parent` = ' . (int) $idParent . '
' . ($active ? 'AND `active` = 1' : '') . ' LIMIT 1';
$result = (bool) Db::getInstance(_PS_USE_SQL_SLAVE_)->executeS($query);
Cache::store($cacheId, $result);

return $result;
}

return Cache::retrieve($cacheId);
}

/**
* Return an array of all children of the current category.
*
* @param int $idLang Language ID
*
* @return PrestaShopCollection Collection of Category
*/
public function getAllChildren($idLang = null)
{
if (null === $idLang) {
$idLang = Context::getContext()->language->id;
}

$categories = new PrestaShopCollection('Category', $idLang);


$categories->where('nleft', '>', $this->nleft);
$categories->where('nright', '<', $this->nright);

return $categories;
}

/**
* Return an ordered array of all parents of the current category.
*
* @param int $idLang
*
* @return PrestaShopCollection Collection of Category
*/
public function getAllParents($idLang = null)
{
if (null === $idLang) {
$idLang = Context::getContext()->language->id;
}

$categories = new PrestaShopCollection('Category', $idLang);


$categories->where('nleft', '<', $this->nleft);
$categories->where('nright', '>', $this->nright);
$categories->orderBy('nleft');

return $categories;
}

/**
* This method allow to return children categories with the number of sub children selected for
a product.
*
* @param int $idParent Parent Category ID
* @param int $selectedCategory Selected SubCategory ID
* @param int $idLang Language ID
* @param Shop $shop Shop ID
* @param bool $useShopContext Limit to current Shop
*
* @return array
*
* @internal param int $id_product Product ID
*/
public static function getChildrenWithNbSelectedSubCat($idParent, $selectedCategory, $idLang,
Shop $shop = null, $useShopContext = true)
{
if (!$shop) {
$shop = Context::getContext()->shop;
}

$idShop = $shop->id ? $shop->id : Configuration::get('PS_SHOP_DEFAULT');


$selectedCategory = explode(',', str_replace(' ', '', $selectedCategory));
$sql = '
SELECT c.`id_category`, c.`level_depth`, cl.`name`,
IF((
SELECT COUNT(*)
FROM `' . _DB_PREFIX_ . 'category` c2
WHERE c2.`id_parent` = c.`id_category`
) > 0, 1, 0) AS has_children,
' . ($selectedCategory ? '(
SELECT count(c3.`id_category`)
FROM `' . _DB_PREFIX_ . 'category` c3
WHERE c3.`nleft` > c.`nleft`
AND c3.`nright` < c.`nright`
AND c3.`id_category` IN (' . implode(',', array_map('intval',
$selectedCategory)) . ')
)' : '0') . ' AS nbSelectedSubCat
FROM `' . _DB_PREFIX_ . 'category` c
LEFT JOIN `' . _DB_PREFIX_ . 'category_lang` cl ON (c.`id_category` =
cl.`id_category` ' . Shop::addSqlRestrictionOnLang('cl', (int) $idShop) . ')
LEFT JOIN `' . _DB_PREFIX_ . 'category_shop` cs ON (c.`id_category` =
cs.`id_category` AND cs.`id_shop` = ' . (int) $idShop . ')
WHERE `id_lang` = ' . (int) $idLang . '
AND c.`id_parent` = ' . (int) $idParent;
if (Shop::getContext() === Shop::CONTEXT_SHOP && $useShopContext) {
$sql .= ' AND cs.`id_shop` = ' . (int) $shop->id;
}
if (!Shop::isFeatureActive() || Shop::getContext() === Shop::CONTEXT_SHOP &&
$useShopContext) {
$sql .= ' ORDER BY cs.`position` ASC';
}

return Db::getInstance(_PS_USE_SQL_SLAVE_)->executeS($sql);
}

/**
* Copy products from a category to another.
*
* @param int $idOld Source category ID
* @param bool $idNew Destination category ID
*
* @return bool Duplication result
*/
public static function duplicateProductCategories($idOld, $idNew)
{
$sql = 'SELECT `id_category`
FROM `' . _DB_PREFIX_ . 'category_product`
WHERE `id_product` = ' . (int) $idOld;
$result = Db::getInstance()->executeS($sql);

$row = [];
if ($result) {
foreach ($result as $i) {
$row[] = '(' . implode(', ', [(int) $idNew, $i['id_category'], '(SELECT tmp.max + 1 FROM (
SELECT MAX(cp.`position`) AS max
FROM `' . _DB_PREFIX_ . 'category_product` cp
WHERE cp.`id_category`=' . (int) $i['id_category'] . ') AS
tmp)',
]) . ')';
}
}

$flag = Db::getInstance()->execute(
'
INSERT IGNORE INTO `' . _DB_PREFIX_ . 'category_product` (`id_product`,
`id_category`, `position`)
VALUES ' . implode(',', $row)
);

return $flag;
}

/**
* Check if category can be moved in another one.
* The category cannot be moved in a child category.
*
* @param int $idCategory Current category
* @param int $idParent Parent candidate
*
* @return bool Parent validity
*/
public static function checkBeforeMove($idCategory, $idParent)
{
if ($idCategory == $idParent) {
return false;
}
if ($idParent == Configuration::get('PS_HOME_CATEGORY')) {
return true;
}
$i = (int) $idParent;

while (42) {
$result = Db::getInstance()->getRow('SELECT `id_parent` FROM `' . _DB_PREFIX_ . 'category`
WHERE `id_category` = ' . (int) $i);
if (!isset($result['id_parent'])) {
return false;
}
if ($result['id_parent'] == $idCategory) {
return false;
}
if ($result['id_parent'] == Configuration::get('PS_HOME_CATEGORY')) {
return true;
}
$i = $result['id_parent'];
}

return false;
}

/**
* Get the rewrite link of the given Category.
*
* @param int $idCategory Category ID
* @param int $idLang Language ID
*
* @return bool|mixed
*/
public static function getLinkRewrite($idCategory, $idLang)
{
if (!Validate::isUnsignedId($idCategory) || !Validate::isUnsignedId($idLang)) {
return false;
}

if (!isset(self::$_links[$idCategory . '-' . $idLang])) {


self::$_links[$idCategory . '-' . $idLang] = Db::getInstance()->getValue('
SELECT cl.`link_rewrite`
FROM `' . _DB_PREFIX_ . 'category_lang` cl
WHERE `id_lang` = ' . (int) $idLang . '
' . Shop::addSqlRestrictionOnLang('cl') . '
AND cl.`id_category` = ' . (int) $idCategory);
}

return self::$_links[$idCategory . '-' . $idLang];


}

/**
* Get link to this category.
*
* @param Link|null $link Link instance
* @param int|null $idLang Language ID
*
* @return string FO URL to this Category
*/
public function getLink(Link $link = null, $idLang = null)
{
if (!$link) {
$link = Context::getContext()->link;
}

if (!$idLang && is_array($this->link_rewrite)) {


$idLang = Context::getContext()->language->id;
}
return $link->getCategoryLink(
$this,
is_array($this->link_rewrite) ? $this->link_rewrite[$idLang] : $this->link_rewrite,
$idLang
);
}

/**
* Get category name in given Language.
*
* @param int|null $idLang Language ID
*
* @return string Category name
*/
public function getName($idLang = null)
{
if (!$idLang) {
if (isset($this->name[Context::getContext()->language->id])) {
$idLang = Context::getContext()->language->id;
} else {
$idLang = (int) Configuration::get('PS_LANG_DEFAULT');
}
}

return isset($this->name[$idLang]) ? $this->name[$idLang] : '';


}

/**
* Light back office search for categories.
*
* @param int $idLang Language ID
* @param string $query Searched string
* @param bool $unrestricted Allows search without lang and includes first category and exact
match
* @param bool $skipCache Skip the Cache
*
* @return array Corresponding categories
*
* @throws PrestaShopDatabaseException
*/
public static function searchByName($idLang, $query, $unrestricted = false, $skipCache = false)
{
if ($unrestricted === true) {
$key = 'Category::searchByName_' . $query;
if ($skipCache || !Cache::isStored($key)) {
$sql = new DbQuery();
$sql->select('c.*, cl.*');
$sql->from('category', 'c');
$sql->leftJoin('category_lang', 'cl', 'c.`id_category` = cl.`id_category` ' .
Shop::addSqlRestrictionOnLang('cl'));
$sql->where('`name` = \'' . pSQL($query) . '\'');
$categories = Db::getInstance(_PS_USE_SQL_SLAVE_)->getRow($sql);
if (!$skipCache) {
Cache::store($key, $categories);
}

return $categories;
}

return Cache::retrieve($key);
} else {
$sql = new DbQuery();
$sql->select('c.*, cl.*');
$sql->from('category', 'c');
$sql->leftJoin('category_lang', 'cl', 'c.`id_category` = cl.`id_category` AND `id_lang` = ' . (int)
$idLang . ' ' . Shop::addSqlRestrictionOnLang('cl'));
$sql->where('`name` LIKE \'%' . pSQL($query) . '%\'');
$sql->where('c.`id_category` != ' . (int) Configuration::get('PS_HOME_CATEGORY'));

return Db::getInstance(_PS_USE_SQL_SLAVE_)->executeS($sql);
}
}

/**
* Retrieve category by name and parent category id.
*
* @param int $idLang Language ID
* @param string $categoryName Searched category name
* @param int $idParentCategory parent category ID
*
* @return array Corresponding category
*/
public static function searchByNameAndParentCategoryId($idLang, $categoryName,
$idParentCategory)
{
return Db::getInstance(_PS_USE_SQL_SLAVE_)->getRow('
SELECT c.*, cl.*
FROM `' . _DB_PREFIX_ . 'category` c
LEFT JOIN `' . _DB_PREFIX_ . 'category_lang` cl
ON (c.`id_category` = cl.`id_category`
AND `id_lang` = ' . (int) $idLang . Shop::addSqlRestrictionOnLang('cl') . ')
WHERE `name` = \'' . pSQL($categoryName) . '\'
AND c.`id_category` != ' . (int) Configuration::get('PS_HOME_CATEGORY') .
'
AND c.`id_parent` = ' . (int) $idParentCategory);
}

/**
* Search with paths for Categories.
*
* @param int $idLang Language ID
* @param string $path Path of category
* @param bool $objectToCreate a category
* @param bool $methodToCreate a category
*
* @return array Corresponding categories
*/
public static function searchByPath($idLang, $path, $objectToCreate = false, $methodToCreate =
false)
{
$categories = explode('/', trim($path));
$category = $idParentCategory = false;

if (is_array($categories) && count($categories)) {


foreach ($categories as $categoryName) {
if ($idParentCategory) {
$category = Category::searchByNameAndParentCategoryId($idLang, $categoryName,
$idParentCategory);
} else {
$category = Category::searchByName($idLang, $categoryName, true, true);
}

if (!$category && $objectToCreate && $methodToCreate) {


call_user_func_array([$objectToCreate, $methodToCreate], [$idLang, $categoryName,
$idParentCategory]);
$category = Category::searchByPath($idLang, $categoryName);
}
if (isset($category['id_category']) && $category['id_category']) {
$idParentCategory = (int) $category['id_category'];
}
}
}

return $category;
}

/**
* Get Each parent category of this category until the root category.
*
* @param int $idLang Language ID
*
* @return array Corresponding categories
*/
public function getParentsCategories($idLang = null)
{
$context = Context::getContext()->cloneContext();
$context->shop = clone $context->shop;

if (null === $idLang) {


$idLang = $context->language->id;
}

$categories = null;
$idCurrent = $this->id;
if (!$context->shop->id) {
$context->shop = new Shop(Configuration::get('PS_SHOP_DEFAULT'));
}
if (count(Category::getCategoriesWithoutParent()) > 1) {
$context->shop->id_category = (int) Configuration::get('PS_ROOT_CATEGORY');
}
$idShop = $context->shop->id;

$sqlAppend = 'FROM `' . _DB_PREFIX_ . 'category` c


LEFT JOIN `' . _DB_PREFIX_ . 'category_lang` cl
ON (c.`id_category` = cl.`id_category`
AND `id_lang` = ' . (int) $idLang . Shop::addSqlRestrictionOnLang('cl') . ')';
if (Shop::isFeatureActive() && Shop::getContext() === Shop::CONTEXT_SHOP) {
$sqlAppend .= ' LEFT JOIN `' . _DB_PREFIX_ . 'category_shop` cs ' .
'ON (c.`id_category` = cs.`id_category` AND cs.`id_shop` = ' . (int) $idShop . ')';
}
if (Shop::isFeatureActive() && Shop::getContext() === Shop::CONTEXT_SHOP) {
$sqlAppend .= ' AND cs.`id_shop` = ' . (int) $context->shop->id;
}
$rootCategory = Category::getRootCategory();
if (Shop::isFeatureActive() && Shop::getContext() === Shop::CONTEXT_SHOP
&& (!Tools::isSubmit('id_category')
|| (int) Tools::getValue('id_category') == (int) $rootCategory->id
|| (int) $rootCategory->id == (int) $context->shop->id_category)) {
$sqlAppend .= ' AND c.`id_parent` != 0';
}

$categories = [];

$treeInfo = Db::getInstance(_PS_USE_SQL_SLAVE_)->getRow(
'SELECT c.`nleft`, c.`nright` ' . $sqlAppend . ' WHERE c.`id_category` = ' . (int) $idCurrent
);

if (!empty($treeInfo)) {
$rootTreeInfo = Db::getInstance(_PS_USE_SQL_SLAVE_)->getRow(
'SELECT c.`nleft`, c.`nright` FROM `' . _DB_PREFIX_ . 'category` c
WHERE c.`id_category` = ' . (int) $context->shop->id_category
);

$categories = Db::getInstance(_PS_USE_SQL_SLAVE_)->executeS(
'SELECT c.*, cl.* ' . $sqlAppend .
' WHERE c.`nleft` <= ' . (int) $treeInfo['nleft'] .
' AND c.`nright` >= ' . (int) $treeInfo['nright'] .
' AND c.`nleft` >= ' . (int) $rootTreeInfo['nleft'] .
' AND c.`nright` <= ' . (int) $rootTreeInfo['nright'] .
' ORDER BY `nleft` DESC'
);
}

return $categories;
}

/**
* Specify if a category already in base.
*
* @param int $idCategory Category id
*
* @return bool
*/
public static function categoryExists($idCategory)
{
$row = Db::getInstance()->getRow('
SELECT `id_category`
FROM ' . _DB_PREFIX_ . 'category c
WHERE c.`id_category` = ' . (int) $idCategory, false);

return isset($row['id_category']);
}

/**
* Check if all categories by provided ids are present in database.
* If at least one is missing return false
*
* @param int[] $categoryIds
*
* @return bool
*
* @throws PrestaShopDatabaseException
*/
public static function categoriesExists(array $categoryIds): bool
{
if (empty($categoryIds)) {
return false;
}
$categoryIds = array_map('intval', array_unique($categoryIds, SORT_REGULAR));
$categoryIdsFormatted = implode(',', $categoryIds);

$result = Db::getInstance()->query('
SELECT COUNT(c.id_category) as categories_found
FROM ' . _DB_PREFIX_ . 'category c
WHERE c.id_category IN (' . $categoryIdsFormatted . ')
')->fetch();

return count($categoryIds) === (int) $result['categories_found'];


}

/**
* Clean Category Groups.
*
* @return bool Indicated whether the cleanup was successful
*/
public function cleanGroups()
{
return Db::getInstance()->delete('category_group', 'id_category = ' . (int) $this->id);
}

/**
* Remove associated products.
*
* @return bool Indicates whether the cleanup was successful
*/
public function cleanAssoProducts()
{
return Db::getInstance()->delete('category_product', 'id_category = ' . (int) $this->id);
}

/**
* Add Category groups.
*
* @param $groups
*/
public function addGroups($groups)
{
foreach ($groups as $group) {
if ($group !== false) {
Db::getInstance()->insert('category_group', ['id_category' => (int) $this->id, 'id_group' =>
(int) $group]);
}
}
}

/**
* Get Category groups.
*
* @return array|null
*/
public function getGroups()
{
$cacheId = 'Category::getGroups_' . (int) $this->id;
if (!Cache::isStored($cacheId)) {
$sql = new DbQuery();
$sql->select('cg.`id_group`');
$sql->from('category_group', 'cg');
$sql->where('cg.`id_category` = ' . (int) $this->id);
$result = Db::getInstance()->executeS($sql);
$groups = [];
foreach ($result as $group) {
$groups[] = $group['id_group'];
}
Cache::store($cacheId, $groups);

return $groups;
}

return Cache::retrieve($cacheId);
}

/**
* Add group if it does not exist.
*
* @param int $idGroup Group ID
*
* @return bool|void
*/
public function addGroupsIfNoExist($idGroup)
{
$groups = $this->getGroups();
if (!in_array((int) $idGroup, $groups)) {
return $this->addGroups([(int) $idGroup]);
}

return false;
}

/**
* checkAccess return true if id_customer is in a group allowed to see this category.
*
* @param mixed $idCustomer
*
* @return bool true if access allowed for customer $id_customer
*/
public function checkAccess($idCustomer)
{
$cacheId = 'Category::checkAccess_' . (int) $this->id . '-' . $idCustomer . (!$idCustomer ? '-' .
(int) Group::getCurrent()->id : '');
if (!Cache::isStored($cacheId)) {
if (!$idCustomer) {
$result = (bool) Db::getInstance(_PS_USE_SQL_SLAVE_)->getValue('
SELECT ctg.`id_group`
FROM ' . _DB_PREFIX_ . 'category_group ctg
WHERE ctg.`id_category` = ' . (int) $this->id . ' AND ctg.`id_group`
= ' . (int) Group::getCurrent()->id);
} else {
$result = (bool) Db::getInstance(_PS_USE_SQL_SLAVE_)->getValue('
SELECT ctg.`id_group`
FROM ' . _DB_PREFIX_ . 'category_group ctg
INNER JOIN ' . _DB_PREFIX_ . 'customer_group cg on
(cg.`id_group` = ctg.`id_group` AND cg.`id_customer` = ' . (int) $idCustomer . ')
WHERE ctg.`id_category` = ' . (int) $this->id);
}
Cache::store($cacheId, $result);

return $result;
}

return Cache::retrieve($cacheId);
}

/**
* Update customer groups associated to the object. Don't update group access if list is null.
*
* @param array $list groups
*
* @return bool
*/
public function updateGroup($list)
{
// don't update group access if list is null
if ($list === null) {
return false;
}
$this->cleanGroups();
if (empty($list)) {
$list = [Configuration::get('PS_UNIDENTIFIED_GROUP'),
Configuration::get('PS_GUEST_GROUP'), Configuration::get('PS_CUSTOMER_GROUP')];
}
$this->addGroups($list);
return true;
}

/**
* @param $idGroup
*
* @return bool
*/
public static function setNewGroupForHome($idGroup)
{
if (!(int) $idGroup) {
return false;
}

return Db::getInstance()->execute('
INSERT INTO `' . _DB_PREFIX_ . 'category_group` (`id_category`, `id_group`)
VALUES (' . (int) Context::getContext()->shop->getCategory() . ', ' . (int) $idGroup .
')');
}

/**
* Update the position of the current Category.
*
* @param bool $way Indicates whether the Category should move up (`false`) or down (`true`)
* @param int $position Current Position
*
* @return bool
*/
public function updatePosition($way, $position)
{
if (!$res = Db::getInstance()->executeS('
SELECT cp.`id_category`, category_shop.`position`, cp.`id_parent`
FROM `' . _DB_PREFIX_ . 'category` cp
' . Shop::addSqlAssociation('category', 'cp') . '
WHERE cp.`id_parent` = ' . (int) $this->id_parent . '
ORDER BY category_shop.`position` ASC')
){
return false;
}

$movedCategory = false;
foreach ($res as $category) {
if ((int) $category['id_category'] == (int) $this->id) {
$movedCategory = $category;
}
}

if ($movedCategory === false) {


return false;
}
// < and > statements rather than BETWEEN operator
// since BETWEEN is treated differently according to databases
$increment = ($way ? '- 1' : '+ 1');
$result = (Db::getInstance()->execute(
'UPDATE `' . _DB_PREFIX_ . 'category` c ' . Shop::addSqlAssociation('category', 'c') . ' ' .
'SET c.`position`= ' .
'IF(cast(c.`position` as signed) ' . $increment . ' > 0, c.`position` ' . $increment . ', 0), ' .
'category_shop.`position` = ' .
'IF(cast(category_shop.`position` as signed) ' . $increment . ' > 0, category_shop.`position` ' .
$increment . ', 0), ' .
'c.`date_upd` = "' . date('Y-m-d H:i:s') . '" ' .
'WHERE category_shop.`position`' .
($way
? '> ' . (int) $movedCategory['position'] . ' AND category_shop.`position` <= ' . (int)
$position
: '< ' . (int) $movedCategory['position'] . ' AND category_shop.`position` >= ' . (int)
$position) . ' ' .
'AND c.`id_parent`=' . (int) $movedCategory['id_parent'])
&& Db::getInstance()->execute('
UPDATE `' . _DB_PREFIX_ . 'category` c ' . Shop::addSqlAssociation('category', 'c') . '
SET c.`position` = ' . (int) $position . ',
category_shop.`position` = ' . (int) $position . ',
c.`date_upd` = "' . date('Y-m-d H:i:s') . '"
WHERE c.`id_parent` = ' . (int) $movedCategory['id_parent'] . '
AND c.`id_category`=' . (int) $movedCategory['id_category']));
Hook::exec('actionCategoryUpdate', ['category' => new
Category($movedCategory['id_category'])]);

return $result;
}

/**
* cleanPositions keep order of category in $id_category_parent,
* but remove duplicate position. Should not be used if positions
* are clean at the beginning !
*
* @param mixed $idCategoryParent
*
* @return bool true if succeed
*/
public static function cleanPositions($idCategoryParent = null)
{
if ($idCategoryParent === null) {
return;
}
$return = true;
$result = Db::getInstance()->executeS('
SELECT c.`id_category`
FROM `' . _DB_PREFIX_ . 'category` c
' . Shop::addSqlAssociation('category', 'c') . '
WHERE c.`id_parent` = ' . (int) $idCategoryParent . '
ORDER BY category_shop.`position`');
$count = count($result);
for ($i = 0; $i < $count; ++$i) {
$return &= Db::getInstance()->execute('
UPDATE `' . _DB_PREFIX_ . 'category` c ' . Shop::addSqlAssociation('category', 'c') . '
SET c.`position` = ' . (int) ($i) . ',
category_shop.`position` = ' . (int) ($i) . ',
c.`date_upd` = "' . date('Y-m-d H:i:s') . '"
WHERE c.`id_parent` = ' . (int) $idCategoryParent . ' AND c.`id_category` = ' . (int) $result[$i]
['id_category']);
}

return $return;
}

/**
* Returns the number of categories + 1 having $idCategoryParent as parent.
*
* @param int $idCategoryParent The parent category
* @param int $idShop Shop ID
*
* @return int Number of categories + 1 having $idCategoryParent as parent
*
* @todo rename that function to make it understandable (getNextPosition for example)
*/
public static function getLastPosition($idCategoryParent, $idShop)
{
// @TODO, if we remove this query, the position will begin at 1 instead of 0, but is this really a
problem?
$results = Db::getInstance()->executeS('
SELECT 1
FROM `' . _DB_PREFIX_ . 'category` c
JOIN `' . _DB_PREFIX_ . 'category_shop` cs
ON (c.`id_category` = cs.`id_category` AND cs.`id_shop` = ' . (int)
$idShop . ')
WHERE c.`id_parent` = ' . (int) $idCategoryParent . ' LIMIT 2');

if (count($results) === 1) {
return 0;
} else {
$maxPosition = (int) Db::getInstance()->getValue('
SELECT MAX(cs.`position`)
FROM `' . _DB_PREFIX_ . 'category` c
LEFT JOIN `' . _DB_PREFIX_ . 'category_shop` cs
ON (c.`id_category` = cs.`id_category` AND cs.`id_shop` = ' . (int)
$idShop . ')
WHERE c.`id_parent` = ' . (int) $idCategoryParent);

return 1 + $maxPosition;
}
}

/**
* @see self::getUrlRewriteInformation()
* @deprecated 1.7.0
*/
public static function getUrlRewriteInformations($idCategory)
{
return self::getUrlRewriteInformation($idCategory);
}

/**
* Get URL Rewrite information.
*
* @param $idCategory
*
* @return array|false|mysqli_result|PDOStatement|resource|null
*
* @since 1.7.0
*/
public static function getUrlRewriteInformation($idCategory)
{
$sql = new DbQuery();
$sql->select('l.`id_lang`, cl.`link_rewrite`');
$sql->from('category_link', 'cl');
$sql->leftJoin('lang', 'l', 'cl.`id_lang` = l.`id_lang`');
$sql->where('cl.`id_category` = ' . (int) $idCategory);
$sql->where('l.`active` = 1');

return Db::getInstance(_PS_USE_SQL_SLAVE_)->executeS($sql);
}

/**
* Return `nleft` and `nright` fields for a given category.
*
* @param int $id
*
* @return array
*
* @since 1.5.0
*/
public static function getInterval($id)
{
$cacheId = 'Category::getInterval_' . (int) $id;
if (!Cache::isStored($cacheId)) {
$sql = new DbQuery();
$sql->select('c.`nleft`, c.`nright`, c.`level_depth`');
$sql->from('category', 'c');
$sql->where('c.`id_category` = ' . (int) $id);
$result = Db::getInstance()->getRow($sql);
Cache::store($cacheId, $result);

return $result;
}

return Cache::retrieve($cacheId);
}

/**
* Check if current category is a child of shop root category.
*
* @param Shop $shop
*
* @return bool
*
* @since 1.5.0
*/
public function inShop(Shop $shop = null)
{
if (!$shop) {
$shop = Context::getContext()->shop;
}

if (!$interval = Category::getInterval($shop->getCategory())) {
return false;
}

return $this->nleft >= $interval['nleft'] && $this->nright <= $interval['nright'];


}

/**
* Check if current category is a child of shop root category.
*
* @param int $idCategory Category ID
* @param Shop $shop Shop object
*
* @return bool Indicates whether the current category is a child of the Shop root category
*
* @since 1.5.0
*/
public static function inShopStatic($idCategory, Shop $shop = null)
{
if (!$shop || !is_object($shop)) {
$shop = Context::getContext()->shop;
}

if (!$interval = Category::getInterval($shop->getCategory())) {
return false;
}
$sql = new DbQuery();
$sql->select('c.`nleft`, c.`nright`');
$sql->from('category', 'c');
$sql->where('c.`id_category` = ' . (int) $idCategory);
$row = Db::getInstance(_PS_USE_SQL_SLAVE_)->getRow($sql);

return $row['nleft'] >= $interval['nleft'] && $row['nright'] <= $interval['nright'];


}

/**
* Get Children for the webservice.
*
* @return array|false|mysqli_result|PDOStatement|resource|null
*/
public function getChildrenWs()
{
return Db::getInstance(_PS_USE_SQL_SLAVE_)->executeS('
SELECT c.`id_category` as id
FROM `' . _DB_PREFIX_ . 'category` c
' . Shop::addSqlAssociation('category', 'c') . '
WHERE c.`id_parent` = ' . (int) $this->id . '
AND c.`active` = 1
ORDER BY category_shop.`position` ASC');
}

/**
* Get Products for webservice.
*
* @return array|false|mysqli_result|PDOStatement|resource|null
*/
public function getProductsWs()
{
return Db::getInstance(_PS_USE_SQL_SLAVE_)->executeS('
SELECT cp.`id_product` as id
FROM `' . _DB_PREFIX_ . 'category_product` cp
WHERE cp.`id_category` = ' . (int) $this->id . '
ORDER BY `position` ASC');
}

/*
Create the link rewrite if not exists or invalid on category creation
*/
public function modifierWsLinkRewrite()
{
foreach ($this->name as $id_lang => $name) {
if (empty($this->link_rewrite[$id_lang])) {
$this->link_rewrite[$id_lang] = Tools::link_rewrite($name);
} elseif (!Validate::isLinkRewrite($this->link_rewrite[$id_lang])) {
$this->link_rewrite[$id_lang] = Tools::link_rewrite($this->link_rewrite[$id_lang]);
}
}

return true;
}

/**
* Search for another Category with the same parent and the same position.
*
* @return array first Category found
*/
public function getDuplicatePosition()
{
return Db::getInstance()->getValue('
SELECT c.`id_category`
FROM `' . _DB_PREFIX_ . 'category` c
' . Shop::addSqlAssociation('category', 'c') . '
WHERE c.`id_parent` = ' . (int) $this->id_parent . '
AND category_shop.`position` = ' . (int) $this->position . '
AND c.`id_category` != ' . (int) $this->id);
}

/**
* Recursively get amount of Products for the webservice.
*
* @return false|int|string|null
*/
public function getWsNbProductsRecursive()
{
$nbProductRecursive = Db::getInstance(_PS_USE_SQL_SLAVE_)->getValue('
SELECT COUNT(distinct(id_product))
FROM `' . _DB_PREFIX_ . 'category_product`
WHERE id_category = ' . (int) $this->id . ' OR
EXISTS (
SELECT 1
FROM `' . _DB_PREFIX_ . 'category` c2
' . Shop::addSqlAssociation('category', 'c2') . '
WHERE `' . _DB_PREFIX_ . 'category_product`.id_category =
c2.id_category
AND c2.nleft > ' . (int) $this->nleft . '
AND c2.nright < ' . (int) $this->nright . '
AND c2.active = 1
)
');
if (!$nbProductRecursive) {
return -1;
}

return $nbProductRecursive;
}

/**
* @see self::getCategoryInformation()
* @deprecated 1.7.0
*/
public static function getCategoryInformations($idsCategory, $idLang = null)
{
return self::getCategoryInformation($idsCategory, $idLang);
}

/**
* Get Category information.
*
* @param array $idsCategory Category IDs
* @param int $idLang Language ID
*
* @return array|false Array with Category information
* `false` if no Category found
*
* @since 1.7.0
*/
public static function getCategoryInformation($idsCategory, $idLang = null)
{
if ($idLang === null) {
$idLang = Context::getContext()->language->id;
}

if (!is_array($idsCategory) || !count($idsCategory)) {
return false;
}

$categories = [];
$results = Db::getInstance(_PS_USE_SQL_SLAVE_)->executeS('
SELECT c.`id_category`, cl.`name`, cl.`link_rewrite`, cl.`id_lang`
FROM `' . _DB_PREFIX_ . 'category` c
LEFT JOIN `' . _DB_PREFIX_ . 'category_lang` cl ON (c.`id_category` =
cl.`id_category`' . Shop::addSqlRestrictionOnLang('cl') . ')
' . Shop::addSqlAssociation('category', 'c') . '
WHERE cl.`id_lang` = ' . (int) $idLang . '
AND c.`id_category` IN (' . implode(',', array_map('intval', $idsCategory)) . ')');

foreach ($results as $category) {


$categories[$category['id_category']] = $category;
}

return $categories;
}

/**
* Is parent Category available.
*
* @return bool Indicates whether the parent Category is available
*/
public function isParentCategoryAvailable()
{
$id = Context::getContext()->shop->id;
$idShop = $id ? $id : Configuration::get('PS_SHOP_DEFAULT');

return (bool) Db::getInstance()->getValue('


SELECT c.`id_category`
FROM `' . _DB_PREFIX_ . 'category` c
' . Shop::addSqlAssociation('category', 'c') . '
WHERE category_shop.`id_shop` = ' . (int) $idShop . '
AND c.`id_parent` = ' . (int) $this->id_parent);
}

/**
* Add association between shop and categories.
*
* @param int $idShop Shop ID
*
* @return bool Indicates whether the association was successfully made
*/
public function addShop($idShop)
{
$data = [];
if (!$idShop) {
foreach (Shop::getShops(false) as $shop) {
if (!$this->existsInShop($shop['id_shop'])) {
$data[] = [
'id_category' => (int) $this->id,
'id_shop' => (int) $shop['id_shop'],
];
}
}
} elseif (!$this->existsInShop($idShop)) {
$data[] = [
'id_category' => (int) $this->id,
'id_shop' => (int) $idShop,
];
}

return Db::getInstance()->insert('category_shop', $data);


}

/**
* Get root Categories.
*
* @param int|null $idLang Language ID
* @param bool $active Whether the root Category must be active
*
* @return array|false|mysqli_result|PDOStatement|resource|null Root Categories
*/
public static function getRootCategories($idLang = null, $active = true)
{
if (!$idLang) {
$idLang = Context::getContext()->language->id;
}

return Db::getInstance(_PS_USE_SQL_SLAVE_)->executeS('
SELECT DISTINCT(c.`id_category`), cl.`name`
FROM `' . _DB_PREFIX_ . 'category` c
LEFT JOIN `' . _DB_PREFIX_ . 'category_lang` cl ON (cl.`id_category` =
c.`id_category` AND cl.`id_lang`=' . (int) $idLang . ')
WHERE `is_root_category` = 1
' . ($active ? 'AND `active` = 1' : ''));
}

/**
* Get Categories without parent.
*
* @return array|false|mysqli_result|PDOStatement|resource|null Categories without parent
*/
public static function getCategoriesWithoutParent()
{
$cacheId = 'Category::getCategoriesWithoutParent_' . (int) Context::getContext()->language-
>id;
if (!Cache::isStored($cacheId)) {
$result = Db::getInstance(_PS_USE_SQL_SLAVE_)->executeS('
SELECT DISTINCT c.*
FROM `' . _DB_PREFIX_ . 'category` c
LEFT JOIN `' . _DB_PREFIX_ . 'category_lang` cl ON (c.`id_category` =
cl.`id_category` AND cl.`id_lang` = ' . (int) Context::getContext()->language->id . ')
WHERE `level_depth` = 1');
Cache::store($cacheId, $result);

return $result;
}

return Cache::retrieve($cacheId);
}

/**
* Is Root Category for a Shop.
*
* @return bool Indicates whether the current Category is a Root category for a Shop
*/
public function isRootCategoryForAShop()
{
return (bool) Db::getInstance(_PS_USE_SQL_SLAVE_)->getValue('
SELECT `id_shop`
FROM `' . _DB_PREFIX_ . 'shop`
WHERE `id_category` = ' . (int) $this->id);
}

/**
* Get Top Category.
*
* @param int|null $idLang Language ID
*
* @return Category Top Category
*/
public static function getTopCategory($idLang = null)
{
if (null === $idLang) {
$idLang = (int) Context::getContext()->language->id;
}
$cacheId = 'Category::getTopCategory_' . (int) $idLang;
if (!Cache::isStored($cacheId)) {
$idCategory = (int) Db::getInstance()->getValue('
SELECT `id_category`
FROM `' . _DB_PREFIX_ . 'category`
WHERE `id_parent` = 0');
$category = new Category($idCategory, $idLang);
Cache::store($cacheId, $category);

return $category;
}
return Cache::retrieve($cacheId);
}

/**
* Add position to current Category.
*
* @param int $position Position
* @param int|null $idShop Shop ID
*
* @return bool Indicates whether the position was successfully added
*/
public function addPosition($position, $idShop = null)
{
$position = (int) $position;
$return = true;

if (null !== $idShop) {


$shopIds = [(int) $idShop];
} else {
if (Shop::getContext() != Shop::CONTEXT_SHOP) {
$shopIds = Shop::getContextListShopID();
} else {
$id = Context::getContext()->shop->id;
$shopIds = [$id ? $id : Configuration::get('PS_SHOP_DEFAULT')];
}
}

foreach ($shopIds as $idShop) {


$return &= Db::getInstance()->execute(
sprintf(
'INSERT INTO `' . _DB_PREFIX_ . 'category_shop` ' .
'(`id_category`, `id_shop`, `position`) VALUES ' .
'(%d, %d, %d) ' .
'ON DUPLICATE KEY UPDATE `position` = %d',
(int) $this->id,
(int) $idShop,
$position,
$position
)
);
}

$return &= Db::getInstance()->execute(


sprintf(
'UPDATE `' . _DB_PREFIX_ . 'category` c ' .
'SET c.`position`= %d WHERE c.id_category = %d',
$position,
(int) $this->id
)
);

return $return;
}

/**
* Get Shops by Category ID.
*
* @param int $idCategory Category ID
*
* @return array|false|mysqli_result|PDOStatement|resource|null Array with Shop IDs
*/
public static function getShopsByCategory($idCategory)
{
return Db::getInstance()->executeS('
SELECT `id_shop`
FROM `' . _DB_PREFIX_ . 'category_shop`
WHERE `id_category` = ' . (int) $idCategory);
}

/**
* Update Categories for a shop.
*
* @param string $categories Categories list to associate a shop
* @param string $idShop Categories list to associate a shop
*
* @return array|false Update/insertion result
* `false` if not successfully inserted/updated
*/
public static function updateFromShop($categories, $idShop)
{
$shop = new Shop($idShop);
// if array is empty or if the default category is not selected, return false
if (!is_array($categories) || !count($categories) || !in_array($shop->id_category,
$categories)) {
return false;
}

// delete categories for this shop


Category::deleteCategoriesFromShop($idShop);

// and add $categories to this shop


return Category::addToShop($categories, $idShop);
}

/**
* Delete category from shop $id_shop.
*
* @param int $idShop Shop ID
*
* @return bool Indicates whether the current Category was successfully removed from the
Shop
*/
public function deleteFromShop($idShop)
{
return Db::getInstance()->execute('
DELETE FROM `' . _DB_PREFIX_ . 'category_shop`
WHERE `id_shop` = ' . (int) $idShop . '
AND id_category = ' . (int) $this->id);
}

/**
* Deletes all Categories from the Shop ID.
*
* @return bool Indicates whether the Categories have been successfully removed
*/
public static function deleteCategoriesFromShop($idShop)
{
return Db::getInstance()->delete('category_shop', 'id_shop = ' . (int) $idShop);
}

/**
* Add some categories to a shop.
*
* @param array $categories
*
* @return bool Indicates whether the Categories were successfully added to the given Shop
*/
public static function addToShop(array $categories, $idShop)
{
if (!is_array($categories)) {
return false;
}
$sql = 'INSERT INTO `' . _DB_PREFIX_ . 'category_shop` (`id_category`, `id_shop`) VALUES';
$tabCategories = [];
foreach ($categories as $idCategory) {
$tabCategories[] = new Category($idCategory);
$sql .= '("' . (int) $idCategory . '", "' . (int) $idShop . '"),';
}
// removing last comma to avoid SQL error
$sql = substr($sql, 0, strlen($sql) - 1);

$return = Db::getInstance()->execute($sql);
// we have to update position for every new entries
foreach ($tabCategories as $category) {
/* @var Category $category */
$category->addPosition(Category::getLastPosition($category->id_parent, $idShop),
$idShop);
}

return $return;
}

/**
* Does the current Category exists in the given Shop.
*
* @param int $idShop Shop ID
*
* @return bool Indicates whether the current Category exists in the given Shop
*/
public function existsInShop($idShop)
{
return (bool) Db::getInstance()->getValue('
SELECT `id_category`
FROM `' . _DB_PREFIX_ . 'category_shop`
WHERE `id_category` = ' . (int) $this->id . '
AND `id_shop` = ' . (int) $idShop, false);
}

/**
* Indicates whether a category is ROOT for the shop.
* The root category is the one with no parent. It's a virtual category.
*
* @return bool
*/
public function isRootCategory(): bool
{
return 0 === (int) $this->id_parent;
}
}
Página descripción de productos

<?php
session_start();
error_reporting(0);
include('includes/config.php');
if(isset($_GET['action']) && $_GET['action']=="add"){
$id=intval($_GET['id']);
if(isset($_SESSION['cart'][$id])){
$_SESSION['cart'][$id]['quantity']++;
}else{
$sql_p="SELECT * FROM products WHERE id={$id}";
$query_p=mysqli_query($con,$sql_p);
if(mysqli_num_rows($query_p)!=0){
$row_p=mysqli_fetch_array($query_p);
$_SESSION['cart'][$row_p['id']]=array("quantity" => 1, "price" =>
$row_p['productPrice']);
header('location:my-cart.php');
}else{
$message="Product ID is invalid";
}
}
}
$pid=intval($_GET['pid']);
if(isset($_GET['pid']) && $_GET['action']=="wishlist" ){
if(strlen($_SESSION['login'])==0)
{
header('location:login.php');
}
else
{
mysqli_query($con,"insert into wishlist(userId,productId) values('".$_SESSION['id']."','$pid')");
echo "<script>alert('Product aaded in wishlist');</script>";
header('location:my-wishlist.php');

}
}
if(isset($_POST['submit']))
{
$qty=$_POST['quality'];
$price=$_POST['price'];
$value=$_POST['value'];
$name=$_POST['name'];
$summary=$_POST['summary'];
$review=$_POST['review'];
mysqli_query($con,"insert into
productreviews(productId,quality,price,value,name,summary,review)
values('$pid','$qty','$price','$value','$name','$summary','$review')");
}

?>
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="utf-8">
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-
scalable=no">
<meta name="description" content="">
<meta name="author" content="">
<meta name="keywords" content="MediaCenter, Template, eCommerce">
<meta name="robots" content="all">
<title>Product Details</title>
<link rel="stylesheet" href="assets/css/bootstrap.min.css">
<link rel="stylesheet" href="assets/css/main.css">
<link rel="stylesheet" href="assets/css/green.css">
<link rel="stylesheet" href="assets/css/owl.carousel.css">
<link rel="stylesheet" href="assets/css/owl.transitions.css">
<link href="assets/css/lightbox.css" rel="stylesheet">
<link rel="stylesheet" href="assets/css/animate.min.css">
<link rel="stylesheet" href="assets/css/rateit.css">
<link rel="stylesheet" href="assets/css/bootstrap-select.min.css">
<link rel="stylesheet" href="assets/css/config.css">

<link href="assets/css/green.css" rel="alternate stylesheet" title="Green color">


<link href="assets/css/blue.css" rel="alternate stylesheet" title="Blue color">
<link href="assets/css/red.css" rel="alternate stylesheet" title="Red color">
<link href="assets/css/orange.css" rel="alternate stylesheet" title="Orange color">
<link href="assets/css/dark-green.css" rel="alternate stylesheet" title="Darkgreen
color">
<link rel="stylesheet" href="assets/css/font-awesome.min.css">

<!-- Fonts -->


<link href='http://fonts.googleapis.com/css?family=Roboto:300,400,500,700'
rel='stylesheet' type='text/css'>
<link rel="shortcut icon" href="assets/images/favicon.ico">
</head>
<body class="cnt-home">

<header class="header-style-1">

<!-- ============================================== TOP MENU


============================================== -->
<?php include('includes/top-header.php');?>
<!-- ============================================== TOP MENU : END
============================================== -->
<?php include('includes/main-header.php');?>
<!-- ============================================== NAVBAR
============================================== -->
<?php include('includes/menu-bar.php');?>
<!-- ============================================== NAVBAR : END
============================================== -->

</header>
<!-- ============================================== HEADER : END
============================================== -->
<div class="breadcrumb">
<div class="container">
<div class="breadcrumb-inner">
<?php
$ret=mysqli_query($con,"select category.categoryName as catname,subCategory.subcategory as
subcatname,products.productName as pname from products join category on
category.id=products.category join subcategory on subcategory.id=products.subCategory where
products.id='$pid'");
while ($rw=mysqli_fetch_array($ret)) {

?>

<ul class="list-inline list-unstyled">


<li><a href="index.php">Inicio</a></li>
<li><?php echo htmlentities($rw['catname']);?></a></li>
<li><?php echo htmlentities($rw['subcatname']);?></li>
<li class='active'><?php echo htmlentities($rw['pname']);?></li>
</ul>
<?php }?>
</div><!-- /.breadcrumb-inner -->
</div><!-- /.container -->
</div><!-- /.breadcrumb -->
<div class="body-content outer-top-xs">
<div class='container'>
<div class='row single-product outer-bottom-sm '>
<div class='col-md-3 sidebar'>
<div class="sidebar-module-container">

<!--
==============================================CATEGORY=========================
===================== -->
<div class="sidebar-widget outer-bottom-xs wow fadeInUp">
<h3 class="section-title">Categoria</h3>
<div class="sidebar-widget-body m-t-10">
<div class="accordion">

<?php $sql=mysqli_query($con,"select id,categoryName from category");


while($row=mysqli_fetch_array($sql))
{
?>
<div class="accordion-group">
<div class="accordion-heading">
<a href="category.php?cid=<?php echo $row['id'];?>" class="accordion-toggle
collapsed">
<?php echo $row['categoryName'];?>
</a>
</div>

</div>
<?php } ?>
</div>
</div>
</div>
<!-- ============================================== CATEGORY : END
============================================== -->
<!-- ============================================== HOT DEALS
============================================== -->
<div class="sidebar-widget hot-deals wow fadeInUp">
<h3 class="section-title">LAS MEJORES OFERTAS</h3>
<div class="owl-carousel sidebar-carousel custom-carousel owl-theme outer-top-xs">

<?php
$ret=mysqli_query($con,"select * from products order by rand() limit 4 ");
while ($rws=mysqli_fetch_array($ret)) {

?>

<div class="item">
<div class="products">
<div class="hot-deal-wrapper">
<div class="image">
<img
src="admin/productimages/<?php echo htmlentities($rws['productName']);?>/<?php echo
htmlentities($rws['productImage1']);?>" width="200" height="334" alt="">
</div>

</div><!-- /.hot-deal-wrapper -->

<div class="product-info text-left m-t-20">


<h3 class="name"><a href="product-
details.php?pid=<?php echo htmlentities($rws['id']);?>"><?php echo
htmlentities($rws['productName']);?></a></h3>
<div class="rating rateit-small"></div>

<div class="product-price">
<span class="price">
$. <?php echo
htmlentities($rws['productPrice']);?>.00
</span>
<span class="price-before-
discount">$.<?php echo htmlentities($row['productPriceBeforeDiscount']);?></span>

</div><!-- /.product-price -->

</div><!-- /.product-info -->

<div class="cart clearfix animate-effect">


<div class="action">

<div class="add-cart-button btn-


group">
<button class="btn btn-
primary icon" data-toggle="dropdown" type="button">
<i class="fa fa-shopping-cart"></i>

</button>
<a href="product-details.php?
page=product&action=add&id=<?php echo $rws['id']; ?>" class="lnk btn btn-primary"
style="height:34px;padding-top: 5px;">Agregar a carrito</a>

</div>

</div><!-- /.action -->


</div><!-- /.cart -->
</div>
</div>
<?php } ?>

</div><!-- /.sidebar-widget -->


</div>

<!-- ============================================== COLOR: END


============================================== -->
</div>
</div><!-- /.sidebar -->
<?php
$ret=mysqli_query($con,"select * from products where id='$pid'");
while($row=mysqli_fetch_array($ret))
{

?>
<div class='col-md-9'>
<div class="row wow fadeInUp">
<div class="col-xs-12 col-sm-6 col-md-5 gallery-
holder">
<div class="product-item-holder size-big single-product-gallery small-gallery">

<div id="owl-single-product">

<div class="single-product-gallery-item" id="slide1">


<a data-lightbox="image-1" data-title="<?php echo htmlentities($row['productName']);?
>" href="admin/productimages/<?php echo htmlentities($row['id']);?>/<?php echo
htmlentities($row['productImage1']);?>">
<img class="img-responsive" alt="" src="assets/images/blank.gif"
data-echo="admin/productimages/<?php echo htmlentities($row['id']);?>/<?php echo
htmlentities($row['productImage1']);?>" width="370" height="350" />
</a>
</div>

<div class="single-product-gallery-item" id="slide1">


<a data-lightbox="image-1" data-title="<?php echo htmlentities($row['productName']);?
>" href="admin/productimages/<?php echo htmlentities($row['id']);?>/<?php echo
htmlentities($row['productImage1']);?>">
<img class="img-responsive" alt="" src="assets/images/blank.gif"
data-echo="admin/productimages/<?php echo htmlentities($row['id']);?>/<?php echo
htmlentities($row['productImage1']);?>" width="370" height="350" />
</a>
</div><!-- /.single-product-gallery-item -->

<div class="single-product-gallery-item" id="slide2">


<a data-lightbox="image-1" data-title="Gallery" href="admin/productimages/<?php echo
htmlentities($row['id']);?>/<?php echo htmlentities($row['productImage2']);?>">
<img class="img-responsive" alt="" src="assets/images/blank.gif"
data-echo="admin/productimages/<?php echo htmlentities($row['id']);?>/<?php echo
htmlentities($row['productImage2']);?>" />
</a>
</div><!-- /.single-product-gallery-item -->

<div class="single-product-gallery-item" id="slide3">


<a data-lightbox="image-1" data-title="Gallery" href="admin/productimages/<?php echo
htmlentities($row['id']);?>/<?php echo htmlentities($row['productImage3']);?>">
<img class="img-responsive" alt="" src="assets/images/blank.gif"
data-echo="admin/productimages/<?php echo htmlentities($row['id']);?>/<?php echo
htmlentities($row['productImage3']);?>" />
</a>
</div>

</div><!-- /.single-product-slider -->

<div class="single-product-gallery-thumbs gallery-thumbs">

<div id="owl-single-product-thumbnails">
<div class="item">
<a class="horizontal-thumb active" data-target="#owl-single-product" data-slide="1"
href="#slide1">
<img class="img-responsive" alt="" src="assets/images/blank.gif" data-
echo="admin/productimages/<?php echo htmlentities($row['id']);?>/<?php echo
htmlentities($row['productImage1']);?>" />
</a>
</div>

<div class="item">
<a class="horizontal-thumb" data-target="#owl-single-product" data-slide="2"
href="#slide2">
<img class="img-responsive" width="85" alt="" src="assets/images/blank.gif" data-
echo="admin/productimages/<?php echo htmlentities($row['id']);?>/<?php echo
htmlentities($row['productImage2']);?>"/>
</a>
</div>
<div class="item">

<a class="horizontal-thumb" data-target="#owl-single-product" data-slide="3"


href="#slide3">
<img class="img-responsive" width="85" alt="" src="assets/images/blank.gif" data-
echo="admin/productimages/<?php echo htmlentities($row['id']);?>/<?php echo
htmlentities($row['productImage3']);?>" height="200" />
</a>
</div>

</div><!-- /#owl-single-product-thumbnails -->

</div>

</div>
</div>
<div class='col-sm-6 col-md-7 product-info-block'>
<div class="product-info">
<h1 class="name"><?php echo
htmlentities($row['productName']);?></h1>
<?php $rt=mysqli_query($con,"select * from productreviews where productId='$pid'");
$num=mysqli_num_rows($rt);
{
?>
<div class="rating-reviews m-t-20">
<div class="row">
<div class="col-sm-3">
<div class="rating
rateit-small"></div>
</div>
<div class="col-sm-8">
<div
class="reviews">
<a
href="#" class="lnk">(<?php echo htmlentities($num);?> Calificaciones)</a>
</div>
</div>
</div><!-- /.row -->
</div><!-- /.rating-reviews -->
<?php } ?>
<div class="stock-container info-container
m-t-10">
<div class="row">
<div class="col-sm-3">
<div class="stock-
box">
<span
class="label">DISPONIBLE :</span>
</div>
</div>
<div class="col-sm-9">
<div class="stock-
box">
<span
class="value"><?php echo htmlentities($row['productAvailability']);?></span>
</div>
</div>
</div><!-- /.row -->
</div>
<div class="stock-container info-container m-t-10">
<div class="row">
<div class="col-sm-3">
<div class="stock-
box">
<span
class="label">Marca :</span>
</div>
</div>
<div class="col-sm-9">
<div class="stock-
box">
<span
class="value"><?php echo htmlentities($row['productCompany']);?></span>
</div>
</div>
</div><!-- /.row -->
</div>

<div class="stock-container info-container m-t-10">


<div class="row">
<div class="col-sm-3">
<div class="stock-
box">
<span
class="label">Costo de envío:</span>
</div>
</div>
<div class="col-sm-9">
<div class="stock-
box">
<span
class="value"><?php if($row['shippingCharge']==0)
{

echo "Gratis";
}
else
{

echo htmlentities($row['shippingCharge']);
}

?></span>
</div>
</div>
</div><!-- /.row -->
</div>

<div class="price-container info-container


m-t-20">
<div class="row">

<div class="col-sm-6">
<div class="price-
box">
<span
class="price">$. <?php echo htmlentities($row['productPrice']);?></span>
<span
class="price-strike">$.<?php echo htmlentities($row['productPriceBeforeDiscount']);?></span>
</div>
</div>

<div class="col-sm-6">
<div
class="favorite-button m-t-10">
<a
class="btn btn-primary" data-toggle="tooltip" data-placement="right" title="Lista de deseos"
href="product-details.php?pid=<?php echo htmlentities($row['id'])?>&&action=wishlist">
<i
class="fa fa-heart"></i>
</a>

</a>
</div>
</div>

</div><!-- /.row -->


</div><!-- /.price-container -->

<div class="quantity-container info-


container">
<div class="row">

<div class="col-sm-2">
<span
class="label">Cantidad :</span>
</div>

<div class="col-sm-2">
<div class="cart-
quantity">
<div
class="quant-input">
<div class="arrows">
<div class="arrow plus
gradient"><span class="ir"><i class="icon fa fa-sort-asc"></i></span></div>
<div class="arrow minus
gradient"><span class="ir"><i class="icon fa fa-sort-desc"></i></span></div>
</div>
<input type="text"
value="1">
</div>
</div>
</div>

<div class="col-sm-7">
<a href="product-
details.php?page=product&action=add&id=<?php echo $row['id']; ?>" class="btn btn-primary"><i
class="fa fa-shopping-cart inner-right-vs"></i> Agregar a compras</a>
</div>

</div><!-- /.row -->


</div><!-- /.quantity-container -->

<div class="product-social-link m-t-20


text-right">
<span class="social-
label">Compartir:</span>
<div class="social-icons">
<ul class="list-inline">
<li><a class="fa fa-facebook"
href="https://facebook.com/platea21"></a></li>
<li><a class="fa fa-twitter"
href="https://facebook.com/platea21"></a></li>
<li><a class="fa fa-linkedin"
href="https://facebook.com/platea21"></a></li>
<li><a class="fa fa-rss"
href="https://facebook.com/platea21"></a></li>
<li><a class="fa fa-pinterest"
href="https://facebook.com/platea21"></a></li>
</ul><!-- /.social-icons -->
</div>
</div>

</div><!-- /.product-info -->


</div><!-- /.col-sm-7 -->
</div><!-- /.row -->

<div class="product-tabs inner-bottom-xs wow fadeInUp">


<div class="row">
<div class="col-sm-3">
<ul id="product-tabs" class="nav nav-tabs
nav-tab-cell">
<li class="active"><a data-
toggle="tab" href="#description">DESCRIPCIÓN</a></li>
<li><a data-toggle="tab"
href="#review">CALIFICACIONES</a></li>
</ul><!-- /.nav-tabs #product-tabs -->
</div>
<div class="col-sm-9">

<div class="tab-content">

<div id="description" class="tab-


pane in active">
<div class="product-tab">
<p
class="text"><?php echo $row['productDescription'];?></p>
</div>
</div><!-- /.tab-pane -->

<div id="review" class="tab-


pane">
<div class="product-tab">

<div
class="product-reviews">
<h4
class="title">Calificaciones de Clientes</h4>
<?php $qry=mysqli_query($con,"select * from productreviews where productId='$pid'");
while($rvw=mysqli_fetch_array($qry))
{
?>
<div
class="reviews" style="border: solid 1px #000; padding-left: 2% ">

<div class="review">

<div class="review-title"><span class="summary"><?php echo


htmlentities($rvw['summary']);?></span><span class="date"><i class="fa
fa-calendar"></i><span><?php echo htmlentities($rvw['reviewDate']);?></span></span></div>

<div class="text">"<?php echo htmlentities($rvw['review']);?>"</div>

<div class="text"><b>Calidad :</b> <?php echo htmlentities($rvw['quality']);?>


Estrella</div>

<div class="text"><b>Precio :</b> <?php echo htmlentities($rvw['price']);?> Estrella</div>

<div class="text"><b>Valor :</b> <?php echo htmlentities($rvw['value']);?> Estrella</div>


<div class="author m-t-15"><i class="fa fa-pencil-square-o"></i> <span
class="name"><?php echo htmlentities($rvw['name']);?></span></div>
</div>

</div>
<?php } ?
><!-- /.reviews -->

</div><!-- /.product-reviews -->


<form
role="form" class="cnt-form" name="review" method="post">

<div
class="product-add-review">
<h4
class="title">Dejanos tu calificación</h4>
<div
class="review-table">

<div class="table-responsive">

<table class="table table-bordered">

<thead>

<tr>

<th class="cell-label">&nbsp;</th>
<th>1 estrella</th>

<th>2 estrellas</th>

<th>3 estrellas</th>

<th>4 estrellas</th>

<th>5 estrellas</th>

</tr>

</thead>

<tbody>

<tr>

<td class="cell-label">Calidad</td>

<td><input type="radio" name="quality" class="radio"


value="1"></td>

<td><input type="radio" name="quality" class="radio"


value="2"></td>

<td><input type="radio" name="quality" class="radio"


value="3"></td>

<td><input type="radio" name="quality" class="radio"


value="4"></td>

<td><input type="radio" name="quality" class="radio"


value="5"></td>

</tr>

<tr>

<td class="cell-label">Precio</td>

<td><input type="radio" name="price" class="radio"


value="1"></td>

<td><input type="radio" name="price" class="radio"


value="2"></td>
<td><input type="radio" name="price" class="radio"
value="3"></td>

<td><input type="radio" name="price" class="radio"


value="4"></td>

<td><input type="radio" name="price" class="radio"


value="5"></td>

</tr>

<tr>

<td class="cell-label">Valor</td>

<td><input type="radio" name="value" class="radio"


value="1"></td>

<td><input type="radio" name="value" class="radio"


value="2"></td>

<td><input type="radio" name="value" class="radio"


value="3"></td>

<td><input type="radio" name="value" class="radio"


value="4"></td>

<td><input type="radio" name="value" class="radio"


value="5"></td>

</tr>

</tbody>

</table><!-- /.table .table-bordered -->

</div><!-- /.table-responsive -->


</div><!--
/.review-table -->

<div
class="review-form">

<div class="form-container">
<div class="row">

<div class="col-sm-6">

<div class="form-group">

<label for="exampleInputName">Nombre <span


class="astk">*</span></label>

<input type="text" class="form-control txt"


id="exampleInputName" placeholder="" name="name" required="required">

</div><!-- /.form-group -->

<div class="form-group">

<label for="exampleInputSummary">Resumen <span


class="astk">*</span></label>

<input type="text" class="form-control txt"


id="exampleInputSummary" placeholder="" name="summary" required="required">

</div><!-- /.form-group -->

</div>

<div class="col-md-6">

<div class="form-group">

<label for="exampleInputReview">Opinión <span


class="astk">*</span></label>

<textarea class="form-control txt txt-review" id="exampleInputReview" rows="4" placeholder=""


name="review" required="required"></textarea>

</div><!-- /.form-group -->

</div>

</div><!-- /.row -->

<div class="action text-right">


<button name="submit" class="btn btn-primary btn-upper">ENVIAR
CALIFICACIÓN</button>

</div><!-- /.action -->

</form><!-- /.cnt-form -->

</div><!-- /.form-container -->


</div><!--
/.review-form -->

</div><!-- /.product-add-review -->

</div><!-- /.product-tab -->


</div><!-- /.tab-pane -->

</div><!-- /.tab-content -->


</div><!-- /.col -->
</div><!-- /.row -->
</div><!-- /.product-tabs -->

<?php $cid=$row['category'];
$subcid=$row['subCategory']; } ?>
<!-- ==============================================
UPSELL PRODUCTS ============================================== -->
<section class="section featured-product wow fadeInUp">
<h3 class="section-title">Productos similares </h3>
<div class="owl-carousel home-owl-carousel upsell-product custom-carousel owl-theme
outer-top-xs">

<?php
$qry=mysqli_query($con,"select * from products where subCategory='$subcid' and
category='$cid'");
while($rw=mysqli_fetch_array($qry))
{

?>

<div class="item item-carousel">


<div class="products">
<div class="product">
<div class="product-image">
<div class="image">
<a href="product-details.php?pid=<?php echo
htmlentities($rw['id']);?>"><img src="assets/images/blank.gif"
data-echo="admin/productimages/<?php echo htmlentities($rw['id']);?>/<?php echo
htmlentities($rw['productImage1']);?>" width="150" height="240" alt=""></a>
</div><!-- /.image -->

</div><!-- /.product-image -->

<div class="product-info text-left">


<h3 class="name"><a href="product-details.php?pid=<?php echo
htmlentities($rw['id']);?>"><?php echo htmlentities($rw['productName']);?></a></h3>
<div class="rating rateit-small"></div>
<div class="description"></div>

<div class="product-price">
<span class="price">
$.<?php echo htmlentities($rw['productPrice']);?>
</span>
<span
class="price-before-discount">$.
<?php echo
htmlentities($rw['productPriceBeforeDiscount']);?></span>

</div><!-- /.product-price -->

</div><!-- /.product-info -->


<div class="cart clearfix animate-effect">
<div class="action">
<ul class="list-unstyled">
<li class="add-cart-button btn-group">
<button class="btn btn-primary icon"
data-toggle="dropdown" type="button">
<i class="fa fa-shopping-cart"></i>

</button>
<a href="product-details.php?
page=product&action=add&id=<?php echo $rw['id']; ?>" class="lnk btn btn-primary">Agregar a
carrito</a>

</li>

</ul>
</div><!-- /.action -->
</div><!-- /.cart -->
</div><!-- /.product -->

</div><!-- /.products -->


</div><!-- /.item -->
<?php } ?>

</div><!-- /.home-owl-carousel -->


</section><!-- /.section -->

<!-- ============================================== UPSELL PRODUCTS : END


============================================== -->

</div><!-- /.col -->


<div class="clearfix"></div>
</div>
<?php include('includes/brands-slider.php');?>
</div>
</div>
<?php include('includes/footer.php');?>

<script src="assets/js/jquery-1.11.1.min.js"></script>

<script src="assets/js/bootstrap.min.js"></script>

<script src="assets/js/bootstrap-hover-dropdown.min.js"></script>
<script src="assets/js/owl.carousel.min.js"></script>

<script src="assets/js/echo.min.js"></script>
<script src="assets/js/jquery.easing-1.3.min.js"></script>
<script src="assets/js/bootstrap-slider.min.js"></script>
<script src="assets/js/jquery.rateit.min.js"></script>
<script type="text/javascript" src="assets/js/lightbox.min.js"></script>
<script src="assets/js/bootstrap-select.min.js"></script>
<script src="assets/js/wow.min.js"></script>
<script src="assets/js/scripts.js"></script>

<!-- For demo purposes – can be removed on production -->

<script src="switchstylesheet/switchstylesheet.js"></script>

<script>
$(document).ready(function(){
$(".changecolor").switchstylesheet( { seperator:"color"} );
$('.show-theme-options').click(function(){
$(this).parent().toggleClass('open');
return false;
});
});

$(window).bind("load", function() {
$('.show-theme-options').delay(2000).trigger('click');
});
</script>
<!-- For demo purposes – can be removed on production : End -->

</body>
</html>

Proceso de búsquedas
class RequestSqlCore extends ObjectModel
{
public $name;
public $sql;

/**
* @see ObjectModel::$definition
*/
public static $definition = [
'table' => 'request_sql',
'primary' => 'id_request_sql',
'fields' => [
'name' => ['type' => self::TYPE_STRING, 'validate' => 'isString', 'required' => true, 'size' =>
200],
'sql' => ['type' => self::TYPE_SQL, 'validate' => 'isString', 'required' => true],
],
];

/** @var array : List of params to tested */


public $tested = [
'required' => ['SELECT', 'FROM'],
'option' => ['WHERE', 'ORDER', 'LIMIT', 'HAVING', 'GROUP', 'UNION'],
'operator' => [
'AND', '&&', 'BETWEEN', 'AND', 'BINARY', '&', '~', '|', '^', 'CASE', 'WHEN', 'END', 'DIV', '/',
'<=>', '=', '>=',
'>', 'IS', 'NOT', 'NULL', '<<', '<=', '<', 'LIKE', '-', '%', '!=', '<>', 'REGEXP', '!', '||', 'OR', '+', '>>',
'RLIKE', 'SOUNDS', '*',
'-', 'XOR', 'IN',
],
'function' => [
'AVG', 'SUM', 'COUNT', 'MIN', 'MAX', 'STDDEV', 'STDDEV_SAMP', 'STDDEV_POP',
'VARIANCE', 'VAR_SAMP', 'VAR_POP',
'GROUP_CONCAT', 'BIT_AND', 'BIT_OR', 'BIT_XOR',
],
'unauthorized' => [
'DELETE', 'ALTER', 'INSERT', 'REPLACE', 'CREATE', 'TRUNCATE', 'OPTIMIZE', 'GRANT',
'REVOKE', 'SHOW', 'HANDLER',
'LOAD', 'ROLLBACK', 'SAVEPOINT', 'UNLOCK', 'INSTALL', 'UNINSTALL', 'ANALZYE',
'BACKUP', 'CHECK', 'CHECKSUM', 'REPAIR', 'RESTORE', 'CACHE',
'DESCRIBE', 'EXPLAIN', 'USE', 'HELP', 'SET', 'DUPLICATE', 'VALUES', 'INTO', 'RENAME',
'CALL', 'PROCEDURE', 'FUNCTION', 'DATABASE', 'SERVER',
'LOGFILE', 'DEFINER', 'RETURNS', 'EVENT', 'TABLESPACE', 'VIEW', 'TRIGGER', 'DATA',
'DO', 'PASSWORD', 'USER', 'PLUGIN', 'FLUSH', 'KILL',
'RESET', 'START', 'STOP', 'PURGE', 'EXECUTE', 'PREPARE', 'DEALLOCATE', 'LOCK',
'USING', 'DROP', 'FOR', 'UPDATE', 'BEGIN', 'BY', 'ALL', 'SHARE',
'MODE', 'TO', 'KEY', 'DISTINCTROW', 'DISTINCT', 'HIGH_PRIORITY', 'LOW_PRIORITY',
'DELAYED', 'IGNORE', 'FORCE', 'STRAIGHT_JOIN',
'SQL_SMALL_RESULT', 'SQL_BIG_RESULT', 'QUICK', 'SQL_BUFFER_RESULT',
'SQL_CACHE', 'SQL_NO_CACHE', 'SQL_CALC_FOUND_ROWS', 'WITH',
],
];

public $attributes = [
'passwd' => '*******************',
'secure_key' => '*******************',
];

/** @var array : list of errors */


public $error_sql = [];

/**
* Get list of request SQL.
*
* @return array|bool
*/
public static function getRequestSql()
{
if (!$result = Db::getInstance(_PS_USE_SQL_SLAVE_)->executeS('SELECT * FROM `' .
_DB_PREFIX_ . 'request_sql` ORDER BY `id_request_sql`')) {
return false;
}

$requestSql = [];
foreach ($result as $row) {
$requestSql[] = $row['sql'];
}

return $requestSql;
}

/**
* Get list of request SQL by id request.
*
* @param int $id
*
* @return array
*/
public static function getRequestSqlById($id)
{
return Db::getInstance()->executeS('SELECT `sql` FROM `' . _DB_PREFIX_ . 'request_sql`
WHERE `id_request_sql` = ' . (int) $id);
}
/**
* Call the parserSQL() method in Tools class
* Cut the request in table for check it.
*
* @param string $sql
*
* @return array|bool
*/
public function parsingSql($sql)
{
return Tools::parserSQL($sql);
}

/**
* Check if the parsing of the SQL request is good or not.
*
* @param array $tab
* @param bool $in
* @param string $sql
*
* @return bool
*/
public function validateParser($tab, $in, $sql)
{
if (!$tab) {
return false;
} elseif (isset($tab['UNION'])) {
$union = $tab['UNION'];
foreach ($union as $tab) {
if (!$this->validateSql($tab, $in, $sql)) {
return false;
}
}

return true;
} else {
return $this->validateSql($tab, $in, $sql);
}
}

/**
* Cut the request for check each cutting.
*
* @param $tab
* @param $in
* @param $sql
*
* @return bool
*/
public function validateSql($tab, $in, $sql)
{
if (!$this->testedRequired($tab)) {
return false;
} elseif (!$this->testedUnauthorized($tab)) {
return false;
} elseif (!$this->checkedFrom($tab['FROM'])) {
return false;
} elseif (!$this->checkedSelect($tab['SELECT'], $tab['FROM'], $in)) {
return false;
} elseif (isset($tab['WHERE'])) {
if (!$this->checkedWhere($tab['WHERE'], $tab['FROM'], $sql)) {
return false;
}
} elseif (isset($tab['HAVING'])) {
if (!$this->checkedHaving($tab['HAVING'], $tab['FROM'])) {
return false;
}
} elseif (isset($tab['ORDER'])) {
if (!$this->checkedOrder($tab['ORDER'], $tab['FROM'])) {
return false;
}
} elseif (isset($tab['GROUP'])) {
if (!$this->checkedGroupBy($tab['GROUP'], $tab['FROM'])) {
return false;
}
} elseif (isset($tab['LIMIT'])) {
if (!$this->checkedLimit($tab['LIMIT'])) {
return false;
}
}

if (empty($this->_errors) && !Db::getInstance()->executeS($sql)) {


return false;
}

return true;
}

/**
* Get list of all tables.
*
* @return array
*/
public function getTables()
{
$results = Db::getInstance()->executeS('SHOW TABLES');
foreach ($results as $result) {
$key = array_keys($result);
$tables[] = $result[$key[0]];
}

return $tables;
}

/**
* Get list of all attributes by an table.
*
* @param $table
*
* @return array
*/
public function getAttributesByTable($table)
{
return Db::getInstance()->executeS('DESCRIBE ' . pSQL($table));
}

/**
* Cut an join sentence.
*
* @param array $attrs
* @param array $from
*
* @return array
*/
public function cutJoin($attrs, $from)
{
$tab = [];

foreach ($attrs as $attr) {


if (in_array($attr['expr_type'], ['operator', 'const'])) {
continue;
}

if (!empty($attr['sub_tree'])) {
foreach ($attr['sub_tree'] as $treeItem) {
if ($treeItem['expr_type'] !== 'colref') {
continue;
}
if ($attribut = $this->cutAttribute($treeItem['base_expr'], $from)) {
$tab[] = $attribut;
}
}
} else {
if ($attribut = $this->cutAttribute($attr['base_expr'], $from)) {
$tab[] = $attribut;
}
}
}

return $tab;
}

/**
* Cut an attribute with or without the alias.
*
* @param $attr
* @param $from
*
* @return array|bool
*/
public function cutAttribute($attr, $from)
{
$matches = [];
if (preg_match('/((`(\()?([a-z0-9_])+`(\))?)|((\()?([a-z0-9_])+(\))?))\.((`(\()?([a-z0-9_])+`(\))?)|((\()?
([a-z0-9_])+(\))?))$/i', $attr, $matches, PREG_OFFSET_CAPTURE)) {
$tab = explode('.', str_replace(['`', '(', ')'], '', $matches[0][0]));
if ($table = $this->returnNameTable($tab[0], $from)) {
return [
'table' => $table,
'alias' => $tab[0],
'attribut' => $tab[1],
'string' => $attr,
];
}
} elseif (preg_match('/((`(\()?([a-z0-9_])+`(\))?)|((\()?([a-z0-9_])+(\))?))$/i', $attr, $matches,
PREG_OFFSET_CAPTURE)) {
$attribut = str_replace(['`', '(', ')'], '', $matches[0][0]);
if ($table = $this->returnNameTable(false, $from, $attr)) {
return [
'table' => $table,
'attribut' => $attribut,
'string' => $attr,
];
}
}

return false;
}

/**
* Get name of table by alias.
*
* @param bool $alias
* @param $tables
*
* @return array|bool
*/
public function returnNameTable($alias, $tables, $attr = null)
{
if ($alias) {
foreach ($tables as $table) {
if (!isset($table['alias']) || !isset($table['table'])) {
continue;
}
if ($table['alias']['no_quotes'] == $alias || $table['alias']['no_quotes']['parts'][0] == $alias) {
return [$table['table']];
}
}
} elseif (count($tables) > 1) {
if ($attr !== null) {
$tab = [];
foreach ($tables as $table) {
if ($this->attributExistInTable($attr, $table['table'])) {
$tab = $table['table'];
}
}
if (count($tab) == 1) {
return $tab;
}
}

$this->error_sql['returnNameTable'] = false;

return false;
} else {
$tab = [];
foreach ($tables as $table) {
$tab[] = $table['table'];
}

return $tab;
}
}

/**
* Check if an attributes exists in a table.
*
* @param string $attr
* @param array $table
*
* @return bool
*/
public function attributExistInTable($attr, $table)
{
if (!$attr) {
return true;
}
if (is_array($table) && (count($table) == 1)) {
$table = $table[0];
}
$attributs = $this->getAttributesByTable($table);
foreach ($attributs as $attribut) {
if ($attribut['Field'] == trim($attr, ' `')) {
return true;
}
}

return false;
}

/**
* Check if all required sentence existing.
*
* @param $tab
*
* @return bool
*/
public function testedRequired($tab)
{
foreach ($this->tested['required'] as $key) {
if (!array_key_exists($key, $tab)) {
$this->error_sql['testedRequired'] = $key;

return false;
}
}

return true;
}
/**
* Check if an unauthorized existing in an array.
*
* @param string $tab
*
* @return bool
*/
public function testedUnauthorized($tab)
{
foreach ($this->tested['unauthorized'] as $key) {
if (array_key_exists($key, $tab)) {
$this->error_sql['testedUnauthorized'] = $key;

return false;
}
}

return true;
}

/**
* Check a "FROM" sentence.
*
* @param array $from
*
* @return bool
*/
public function checkedFrom($from)
{
$nb = count($from);
for ($i = 0; $i < $nb; ++$i) {
$table = $from[$i];

if (isset($table['table']) && !in_array(str_replace('`', '', $table['table']), $this->getTables())) {


$this->error_sql['checkedFrom']['table'] = $table['table'];

return false;
}
if ($table['ref_type'] == 'ON' && (trim($table['join_type']) == 'LEFT' || trim($table['join_type'])
== 'JOIN')) {
$attrs = $this->cutJoin($table['ref_clause'], $from);
if (is_array($attrs)) {
foreach ($attrs as $attr) {
if (!$this->attributExistInTable($attr['attribut'], $attr['table'])) {
$this->error_sql['checkedFrom']['attribut'] = [$attr['attribut'], implode(', ',
$attr['table'])];

return false;
}
}
} else {
if (isset($this->error_sql['returnNameTable'])) {
$this->error_sql['checkedFrom'] = $this->error_sql['returnNameTable'];

return false;
} else {
$this->error_sql['checkedFrom'] = false;

return false;
}
}
}
}

return true;
}

/**
* Check a "SELECT" sentence.
*
* @param string $select
* @param string $from
* @param bool $in
*
* @return bool
*/
public function checkedSelect($select, $from, $in = false)
{
$nb = count($select);
for ($i = 0; $i < $nb; ++$i) {
$attribut = $select[$i];
if ($attribut['base_expr'] != '*' && !preg_match('/\.*$/', $attribut['base_expr'])) {
if ($attribut['expr_type'] == 'colref') {
if ($attr = $this->cutAttribute(trim($attribut['base_expr']), $from)) {
if (!$this->attributExistInTable($attr['attribut'], $attr['table'])) {
$this->error_sql['checkedSelect']['attribut'] = [$attr['attribut'], implode(', ',
$attr['table'])];

return false;
}
} else {
if (isset($this->error_sql['returnNameTable'])) {
$this->error_sql['checkedSelect'] = $this->error_sql['returnNameTable'];

return false;
} else {
$this->error_sql['checkedSelect'] = false;

return false;
}
}
}
} elseif ($in) {
$this->error_sql['checkedSelect']['*'] = false;

return false;
}
}

return true;
}
/**
* Check a "WHERE" sentence.
*
* @param string $where
* @param string $from
* @param string $sql
*
* @return bool
*/
public function checkedWhere($where, $from, $sql)
{
$nb = count($where);
for ($i = 0; $i < $nb; ++$i) {
$attribut = $where[$i];
if ($attribut['expr_type'] == 'colref' || $attribut['expr_type'] == 'reserved') {
if ($attr = $this->cutAttribute(trim($attribut['base_expr']), $from)) {
if (!$this->attributExistInTable($attr['attribut'], $attr['table'])) {
$this->error_sql['checkedWhere']['attribut'] = [$attr['attribut'], implode(', ',
$attr['table'])];

return false;
}
} else {
if (isset($this->error_sql['returnNameTable'])) {
$this->error_sql['checkedWhere'] = $this->error_sql['returnNameTable'];

return false;
} else {
$this->error_sql['checkedWhere'] = false;

return false;
}
}
} elseif ($attribut['expr_type'] == 'operator') {
if (!in_array(strtoupper($attribut['base_expr']), $this->tested['operator'])) {
$this->error_sql['checkedWhere']['operator'] = [$attribut['base_expr']];

return false;
}
} elseif ($attribut['expr_type'] == 'subquery') {
$tab = $attribut['sub_tree'];

return $this->validateParser($tab, true, $sql);


}
}

return true;
}

/**
* Check a "HAVING" sentence.
*
* @param string $having
* @param string $from
*
* @return bool
*/
public function checkedHaving($having, $from)
{
$nb = count($having);
for ($i = 0; $i < $nb; ++$i) {
$attribut = $having[$i];
if ($attribut['expr_type'] == 'colref') {
if ($attr = $this->cutAttribute(trim($attribut['base_expr']), $from)) {
if (!$this->attributExistInTable($attr['attribut'], $attr['table'])) {
$this->error_sql['checkedHaving']['attribut'] = [$attr['attribut'], implode(', ',
$attr['table'])];

return false;
}
} else {
if (isset($this->error_sql['returnNameTable'])) {
$this->error_sql['checkedHaving'] = $this->error_sql['returnNameTable'];

return false;
} else {
$this->error_sql['checkedHaving'] = false;

return false;
}
}
}

if ($attribut['expr_type'] == 'operator') {
if (!in_array(strtoupper($attribut['base_expr']), $this->tested['operator'])) {
$this->error_sql['checkedHaving']['operator'] = [$attribut['base_expr']];

return false;
}
}
}

return true;
}

/**
* Check a "ORDER" sentence.
*
* @param string $order
* @param string $from
*
* @return bool
*/
public function checkedOrder($order, $from)
{
$order = $order[0];
if (array_key_exists('expression', $order) && $order['type'] == 'expression') {
if ($attr = $this->cutAttribute(trim($order['base_expr']), $from)) {
if (!$this->attributExistInTable($attr['attribut'], $attr['table'])) {
$this->error_sql['checkedOrder']['attribut'] = [$attr['attribut'], implode(', ', $attr['table'])];

return false;
}
} else {
if (isset($this->error_sql['returnNameTable'])) {
$this->error_sql['checkedOrder'] = $this->error_sql['returnNameTable'];

return false;
} else {
$this->error_sql['checkedOrder'] = false;

return false;
}
}
}

return true;
}

/**
* Check a "GROUP BY" sentence.
*
* @param array $group
* @param array $from
*
* @return bool
*/
public function checkedGroupBy($group, $from)
{
$group = $group[0];
if ($group['expr_type'] == 'colref') {
if ($attr = $this->cutAttribute(trim($group['base_expr']), $from)) {
if (!$this->attributExistInTable($attr['attribut'], $attr['table'])) {
$this->error_sql['checkedGroupBy']['attribut'] = [$attr['attribut'], implode(', ',
$attr['table'])];

return false;
}
} else {
if (isset($this->error_sql['returnNameTable'])) {
$this->error_sql['checkedGroupBy'] = $this->error_sql['returnNameTable'];

return false;
} else {
$this->error_sql['checkedGroupBy'] = false;

return false;
}
}
}

return true;
}

/**
* Check a "LIMIT" sentence.
*
* @param string $limit
*
* @return bool
*/
public function checkedLimit($limit)
{
if (!preg_match('#^[0-9]+$#', trim($limit['start'])) || !preg_match('#^[0-9]+$#', trim($limit['end']))) {
$this->error_sql['checkedLimit'] = false;

return false;
}

return true;
}
}

Proceso de compras
class ProductSaleCore
{
/**
* Fill the `product_sale` SQL table with data from `order_detail`.
*
* @return bool True on success
*/
public static function fillProductSales()
{
$sql = 'REPLACE INTO ' . _DB_PREFIX_ . 'product_sale
(`id_product`, `quantity`, `sale_nbr`, `date_upd`)
SELECT od.product_id, SUM(od.product_quantity),
COUNT(od.product_id), NOW()
FROM ' . _DB_PREFIX_ . 'order_detail od
GROUP BY od.product_id';

return Db::getInstance()->execute($sql);
}

/**
* Get number of actives products sold.
*
* @return int number of actives products listed in product_sales
*/
public static function getNbSales()
{
$sql = 'SELECT COUNT(ps.`id_product`) AS nb
FROM `' . _DB_PREFIX_ . 'product_sale` ps
LEFT JOIN `' . _DB_PREFIX_ . 'product` p ON p.`id_product` =
ps.`id_product`
' . Shop::addSqlAssociation('product', 'p', false) . '
WHERE product_shop.`active` = 1';

return (int) Db::getInstance(_PS_USE_SQL_SLAVE_)->getValue($sql);


}

/**
* Get required informations on best sales products.
*
* @param int $idLang Language id
* @param int $pageNumber Start from (optional)
* @param int $nbProducts Number of products to return (optional)
*
* @return array|bool from Product::getProductProperties
* `false` if failure
*/
public static function getBestSales($idLang, $pageNumber = 0, $nbProducts = 10, $orderBy =
null, $orderWay = null)
{
$context = Context::getContext();
if ($pageNumber < 1) {
$pageNumber = 1;
}
if ($nbProducts < 1) {
$nbProducts = 10;
}
$finalOrderBy = $orderBy;
$orderTable = '';

$invalidOrderBy = !Validate::isOrderBy($orderBy);
if ($invalidOrderBy || null === $orderBy) {
$orderBy = 'quantity';
$orderTable = 'ps';
}

if ($orderBy == 'date_add' || $orderBy == 'date_upd') {


$orderTable = 'product_shop';
}

$invalidOrderWay = !Validate::isOrderWay($orderWay);
if ($invalidOrderWay || null === $orderWay || $orderBy == 'sales') {
$orderWay = 'DESC';
}

$interval = Validate::isUnsignedInt(Configuration::get('PS_NB_DAYS_NEW_PRODUCT')) ?
Configuration::get('PS_NB_DAYS_NEW_PRODUCT') : 20;

// no group by needed : there's only one attribute with default_on=1 for a given id_product +
shop
// same for image with cover=1
$sql = 'SELECT p.*, product_shop.*, stock.out_of_stock, IFNULL(stock.quantity, 0) as quantity,
' . (Combination::isFeatureActive() ?
'product_attribute_shop.minimal_quantity AS
product_attribute_minimal_quantity,IFNULL(product_attribute_shop.id_product_attribute,0)
id_product_attribute,' : '') . '
pl.`description`, pl.`description_short`, pl.`link_rewrite`,
pl.`meta_description`,
pl.`meta_keywords`, pl.`meta_title`, pl.`name`,
pl.`available_now`, pl.`available_later`,
m.`name` AS manufacturer_name, p.`id_manufacturer` as
id_manufacturer,
image_shop.`id_image` id_image, il.`legend`,
ps.`quantity` AS sales, t.`rate`, pl.`meta_keywords`,
pl.`meta_title`, pl.`meta_description`,
DATEDIFF(p.`date_add`, DATE_SUB("' . date('Y-m-d') . '
00:00:00",
INTERVAL ' . (int) $interval . ' DAY)) > 0 AS new'
. ' FROM `' . _DB_PREFIX_ . 'product_sale` ps
LEFT JOIN `' . _DB_PREFIX_ . 'product` p ON ps.`id_product` =
p.`id_product`
' . Shop::addSqlAssociation('product', 'p', false);
if (Combination::isFeatureActive()) {
$sql .= ' LEFT JOIN `' . _DB_PREFIX_ . 'product_attribute_shop` product_attribute_shop
ON (p.`id_product` =
product_attribute_shop.`id_product` AND product_attribute_shop.`default_on` = 1 AND
product_attribute_shop.id_shop=' . (int) $context->shop->id . ')';
}

$sql .= ' LEFT JOIN `' . _DB_PREFIX_ . 'product_lang` pl


ON p.`id_product` = pl.`id_product`
AND pl.`id_lang` = ' . (int) $idLang .
Shop::addSqlRestrictionOnLang('pl') . '
LEFT JOIN `' . _DB_PREFIX_ . 'image_shop` image_shop
ON (image_shop.`id_product` = p.`id_product` AND
image_shop.cover=1 AND image_shop.id_shop=' . (int) $context->shop->id . ')
LEFT JOIN `' . _DB_PREFIX_ . 'image_lang` il ON
(image_shop.`id_image` = il.`id_image` AND il.`id_lang` = ' . (int) $idLang . ')
LEFT JOIN `' . _DB_PREFIX_ . 'manufacturer` m ON
(m.`id_manufacturer` = p.`id_manufacturer`)
LEFT JOIN `' . _DB_PREFIX_ . 'tax_rule` tr ON
(product_shop.`id_tax_rules_group` = tr.`id_tax_rules_group`)
AND tr.`id_country` = ' . (int) $context->country->id . '
AND tr.`id_state` = 0
LEFT JOIN `' . _DB_PREFIX_ . 'tax` t ON (t.`id_tax` = tr.`id_tax`)
' . Product::sqlStock('p', 0);

$sql .= '
WHERE product_shop.`active` = 1
AND product_shop.`visibility` != \'none\'';

if (Group::isFeatureActive()) {
$groups = FrontController::getCurrentCustomerGroups();
$sql .= ' AND EXISTS(SELECT 1 FROM `' . _DB_PREFIX_ . 'category_product` cp
JOIN `' . _DB_PREFIX_ . 'category_group` cg ON (cp.id_category = cg.id_category AND
cg.`id_group` ' . (count($groups) ? 'IN (' . implode(',', $groups) . ')' : '=' . (int) Group::getCurrent()->id)
. ')
WHERE cp.`id_product` = p.`id_product`)';
}

if ($finalOrderBy != 'price') {
$sql .= '
ORDER BY ' . (!empty($orderTable) ? '`' .
pSQL($orderTable) . '`.' : '') . '`' . pSQL($orderBy) . '` ' . pSQL($orderWay) . '
LIMIT ' . (int) (($pageNumber - 1) * $nbProducts) . ', ' . (int)
$nbProducts;
}

$result = Db::getInstance(_PS_USE_SQL_SLAVE_)->executeS($sql);

if ($finalOrderBy == 'price') {
Tools::orderbyPrice($result, $orderWay);
$result = array_slice($result, (int) (($pageNumber - 1) * $nbProducts), (int) $nbProducts);
}
if (!$result) {
return false;
}

return Product::getProductsProperties($idLang, $result);


}

/**
* Get required informations on best sales products.
*
* @param int $idLang Language id
* @param int $pageNumber Start from (optional)
* @param int $nbProducts Number of products to return (optional)
*
* @return array keys : id_product, link_rewrite, name, id_image, legend, sales, ean13, upc, link
*/
public static function getBestSalesLight($idLang, $pageNumber = 0, $nbProducts = 10, Context
$context = null)
{
if (!$context) {
$context = Context::getContext();
}
if ($pageNumber < 0) {
$pageNumber = 0;
}
if ($nbProducts < 1) {
$nbProducts = 10;
}

// no group by needed : there's only one attribute with default_on=1 for a given id_product +
shop
// same for image with cover=1
$sql = '
SELECT
p.id_product, IFNULL(product_attribute_shop.id_product_attribute,0)
id_product_attribute, pl.`link_rewrite`, pl.`name`, pl.`description_short`,
product_shop.`id_category_default`,
image_shop.`id_image` id_image, il.`legend`,
ps.`quantity` AS sales, p.`ean13`, p.`upc`, cl.`link_rewrite` AS category,
p.show_price, p.available_for_order, IFNULL(stock.quantity, 0) as quantity, p.customizable,
IFNULL(pa.minimal_quantity, p.minimal_quantity) as minimal_quantity,
stock.out_of_stock,
product_shop.`date_add` > "' . date('Y-m-d', strtotime('-' .
(Configuration::get('PS_NB_DAYS_NEW_PRODUCT') ? (int)
Configuration::get('PS_NB_DAYS_NEW_PRODUCT') : 20) . ' DAY')) . '" as new,
product_shop.`on_sale`, product_attribute_shop.minimal_quantity AS
product_attribute_minimal_quantity
FROM `' . _DB_PREFIX_ . 'product_sale` ps
LEFT JOIN `' . _DB_PREFIX_ . 'product` p ON ps.`id_product` = p.`id_product`
' . Shop::addSqlAssociation('product', 'p') . '
LEFT JOIN `' . _DB_PREFIX_ . 'product_attribute_shop` product_attribute_shop
ON (p.`id_product` = product_attribute_shop.`id_product` AND
product_attribute_shop.`default_on` = 1 AND product_attribute_shop.id_shop=' . (int) $context-
>shop->id . ')
LEFT JOIN `' . _DB_PREFIX_ . 'product_attribute` pa ON
(product_attribute_shop.id_product_attribute=pa.id_product_attribute)
LEFT JOIN `' . _DB_PREFIX_ . 'product_lang` pl
ON p.`id_product` = pl.`id_product`
AND pl.`id_lang` = ' . (int) $idLang . Shop::addSqlRestrictionOnLang('pl') . '
LEFT JOIN `' . _DB_PREFIX_ . 'image_shop` image_shop
ON (image_shop.`id_product` = p.`id_product` AND image_shop.cover=1
AND image_shop.id_shop=' . (int) $context->shop->id . ')
LEFT JOIN `' . _DB_PREFIX_ . 'image_lang` il ON (image_shop.`id_image` =
il.`id_image` AND il.`id_lang` = ' . (int) $idLang . ')
LEFT JOIN `' . _DB_PREFIX_ . 'category_lang` cl
ON cl.`id_category` = product_shop.`id_category_default`
AND cl.`id_lang` = ' . (int) $idLang . Shop::addSqlRestrictionOnLang('cl') .
Product::sqlStock('p', 0);

$sql .= '
WHERE product_shop.`active` = 1
AND p.`visibility` != \'none\'';

if (Group::isFeatureActive()) {
$groups = FrontController::getCurrentCustomerGroups();
$sql .= ' AND EXISTS(SELECT 1 FROM `' . _DB_PREFIX_ . 'category_product` cp
JOIN `' . _DB_PREFIX_ . 'category_group` cg ON (cp.id_category
= cg.id_category AND cg.`id_group` ' . (count($groups) ? 'IN (' . implode(',', $groups) . ')' : '=' . (int)
Group::getCurrent()->id) . ')
WHERE cp.`id_product` = p.`id_product`)';
}

$sql .= '
ORDER BY ps.quantity DESC
LIMIT ' . (int) ($pageNumber * $nbProducts) . ', ' . (int) $nbProducts;

if (!$result = Db::getInstance(_PS_USE_SQL_SLAVE_)->executeS($sql)) {
return false;
}

return Product::getProductsProperties($idLang, $result);


}

/**
* Add Product sale.
*
* @param int $productId Product ID
* @param int $qty Quantity
*
* @return bool Indicates whether the sale was successfully added
*/
public static function addProductSale($productId, $qty = 1)
{
return Db::getInstance()->execute('
INSERT INTO ' . _DB_PREFIX_ . 'product_sale
(`id_product`, `quantity`, `sale_nbr`, `date_upd`)
VALUES (' . (int) $productId . ', ' . (int) $qty . ', 1, NOW())
ON DUPLICATE KEY UPDATE `quantity` = `quantity` + ' . (int) $qty . ',
`sale_nbr` = `sale_nbr` + 1, `date_upd` = NOW()');
}

/**
* Get number of sales.
*
* @param int $idProduct Product ID
*
* @return int Number of sales for the given Product
*/
public static function getNbrSales($idProduct)
{
$result = Db::getInstance()->getRow('SELECT `sale_nbr` FROM ' . _DB_PREFIX_ .
'product_sale WHERE `id_product` = ' . (int) $idProduct);
if (!$result || empty($result) || !array_key_exists('sale_nbr', $result)) {
return -1;
}

return (int) $result['sale_nbr'];


}

/**
* Remove a Product sale.
*
* @param int $idProduct Product ID
* @param int $qty Quantity
*
* @return bool Indicates whether the product sale has been successfully removed
*/
public static function removeProductSale($idProduct, $qty = 1)
{
$totalSales = ProductSale::getNbrSales($idProduct);
if ($totalSales > 1) {
return Db::getInstance()->execute(
'
UPDATE ' . _DB_PREFIX_ . 'product_sale
SET `quantity` = CAST(`quantity` AS SIGNED) - ' . (int) $qty . ',
`sale_nbr` = CAST(`sale_nbr` AS SIGNED) - 1, `date_upd` = NOW()
WHERE `id_product` = ' . (int) $idProduct
);
} elseif ($totalSales == 1) {
return Db::getInstance()->delete('product_sale', 'id_product = ' . (int) $idProduct);
}

return true;
}
}
Proceso de facturación
class ManufacturerCore extends ObjectModel
{
public $id;

/** @var string Name */


public $name;

/** @var array<string> Description */


public $description;

/** @var array<string> Short description */


public $short_description;

/** @var int Address */


public $id_address;

/** @var string Object creation date */


public $date_add;

/** @var string Object last modification date */


public $date_upd;

/** @var string Friendly URL */


public $link_rewrite;

/** @var array<string> Meta title */


public $meta_title;

/** @var array<string> Meta keywords */


public $meta_keywords;

/** @var array<string> Meta description */


public $meta_description;

/** @var bool active */


public $active;

/**
* @see ObjectModel::$definition
*/
public static $definition = [
'table' => 'manufacturer',
'primary' => 'id_manufacturer',
'multilang' => true,
'fields' => [
'name' => ['type' => self::TYPE_STRING, 'validate' => 'isCatalogName', 'required' => true,
'size' => 64],
'active' => ['type' => self::TYPE_BOOL],
'date_add' => ['type' => self::TYPE_DATE],
'date_upd' => ['type' => self::TYPE_DATE],

/* Lang fields */
'description' => ['type' => self::TYPE_HTML, 'lang' => true, 'validate' => 'isCleanHtml'],
'short_description' => ['type' => self::TYPE_HTML, 'lang' => true, 'validate' => 'isCleanHtml'],
'meta_title' => ['type' => self::TYPE_STRING, 'lang' => true, 'validate' => 'isGenericName',
'size' => 255],
'meta_description' => ['type' => self::TYPE_STRING, 'lang' => true, 'validate' =>
'isGenericName', 'size' => 512],
'meta_keywords' => ['type' => self::TYPE_STRING, 'lang' => true, 'validate' =>
'isGenericName'],
],
];

protected $webserviceParameters = [
'fields' => [
'active' => [],
'link_rewrite' => ['getter' => 'getLink', 'setter' => false],
],
'associations' => [
'addresses' => [
'resource' => 'address',
'setter' => false,
'fields' => [
'id' => ['xlink_resource' => 'addresses'],
],
],
],
];

/**
* ManufacturerCore constructor.
*
* @param int|null $id
* @param int|null $idLang
*/
public function __construct($id = null, $idLang = null)
{
parent::__construct($id, $idLang);

$this->link_rewrite = $this->getLink();
$this->image_dir = _PS_MANU_IMG_DIR_;
}

/**
* Deletes current Manufacturer from the database.
*
* @return bool `true` if delete was successful
*
* @throws PrestaShopException
*/
public function delete()
{
$address = new Address($this->id_address);

if (Validate::isLoadedObject($address) && !$address->delete()) {


return false;
}

if (parent::delete()) {
CartRule::cleanProductRuleIntegrity('manufacturers', $this->id);

return $this->deleteImage();
}
}

/**
* Delete several objects from database.
*
* return boolean Deletion result
*/
public function deleteSelection($selection)
{
if (!is_array($selection)) {
die(Tools::displayError());
}

$result = true;
foreach ($selection as $id) {
$this->id = (int) $id;
$this->id_address = Manufacturer::getManufacturerAddress();
$result = $result && $this->delete();
}

return $result;
}

/**
* Get Manufacturer Address ID.
*
* @return bool|false|string|null
*/
protected function getManufacturerAddress()
{
if (!(int) $this->id) {
return false;
}

return Db::getInstance(_PS_USE_SQL_SLAVE_)->getValue('SELECT `id_address` FROM ' .


_DB_PREFIX_ . 'address WHERE `id_manufacturer` = ' . (int) $this->id);
}

/**
* Return manufacturers.
*
* @param bool $getNbProducts [optional] return products numbers for each
* @param int $idLang Language ID
* @param bool $active
* @param int $p
* @param int $n
* @param bool $allGroup
*
* @return array Manufacturers
*/
public static function getManufacturers($getNbProducts = false, $idLang = 0, $active = true, $p =
false, $n = false, $allGroup = false, $group_by = false, $withProduct = false)
{
if (!$idLang) {
$idLang = (int) Configuration::get('PS_LANG_DEFAULT');
}
if (!Group::isFeatureActive()) {
$allGroup = true;
}
$manufacturers = Db::getInstance(_PS_USE_SQL_SLAVE_)->executeS('
SELECT m.*, ml.`description`, ml.`short_description`
FROM `' . _DB_PREFIX_ . 'manufacturer` m'
. Shop::addSqlAssociation('manufacturer', 'm') .
'INNER JOIN `' . _DB_PREFIX_ . 'manufacturer_lang` ml ON (m.`id_manufacturer` =
ml.`id_manufacturer` AND ml.`id_lang` = ' . (int) $idLang . ')' .
'WHERE 1 ' .
($active ? 'AND m.`active` = 1 ' : '') .
($withProduct ? 'AND m.`id_manufacturer` IN (SELECT `id_manufacturer` FROM `' .
_DB_PREFIX_ . 'product`) ' : '') .
($group_by ? ' GROUP BY m.`id_manufacturer`' : '') .
'ORDER BY m.`name` ASC
' . ($p ? ' LIMIT ' . (((int) $p - 1) * (int) $n) . ',' . (int) $n : ''));
if ($manufacturers === false) {
return false;
}

if ($getNbProducts) {
$sqlGroups = '';
if (!$allGroup) {
$groups = FrontController::getCurrentCustomerGroups();
$sqlGroups = (count($groups) ? 'IN (' . implode(',', $groups) . ')' : '=' . (int)
Group::getCurrent()->id);
}

$results = Db::getInstance(_PS_USE_SQL_SLAVE_)->executeS(
'
SELECT p.`id_manufacturer`, COUNT(DISTINCT
p.`id_product`) as nb_products
FROM `' . _DB_PREFIX_ . 'product` p USE INDEX
(product_manufacturer)
' . Shop::addSqlAssociation('product', 'p') . '
LEFT JOIN `' . _DB_PREFIX_ . 'manufacturer` as m ON
(m.`id_manufacturer`= p.`id_manufacturer`)
WHERE p.`id_manufacturer` != 0 AND
product_shop.`visibility` NOT IN ("none")
' . ($active ? ' AND product_shop.`active` = 1 ' : '') . '
' . (Group::isFeatureActive() && $allGroup ? '' : ' AND
EXISTS (
SELECT 1
FROM `' . _DB_PREFIX_ . 'category_group` cg
LEFT JOIN `' . _DB_PREFIX_ . 'category_product`
cp ON (cp.`id_category` = cg.`id_category`)
WHERE p.`id_product` = cp.`id_product` AND
cg.`id_group` ' . $sqlGroups . '
)') . '
GROUP BY p.`id_manufacturer`'
);

$counts = [];
foreach ($results as $result) {
$counts[(int) $result['id_manufacturer']] = (int) $result['nb_products'];
}

foreach ($manufacturers as $key => $manufacturer) {


if (array_key_exists((int) $manufacturer['id_manufacturer'], $counts)) {
$manufacturers[$key]['nb_products'] = $counts[(int) $manufacturer['id_manufacturer']];
} else {
$manufacturers[$key]['nb_products'] = 0;
}
}
}

$totalManufacturers = count($manufacturers);
$rewriteSettings = (int) Configuration::get('PS_REWRITING_SETTINGS');
for ($i = 0; $i < $totalManufacturers; ++$i) {
$manufacturers[$i]['link_rewrite'] = ($rewriteSettings ? Tools::link_rewrite($manufacturers[$i]
['name']) : 0);
}

return $manufacturers;
}

/**
* List of manufacturers.
*
* @param int $idLang Specify the id of the language used
*
* @return array Manufacturers lite tree
*/
public static function getLiteManufacturersList($idLang = null, $format = 'default')
{
$idLang = null === $idLang ? Context::getContext()->language->id : (int) $idLang;

$manufacturersList = [];
$manufacturers = Manufacturer::getManufacturers(false, $idLang);
if ($manufacturers && count($manufacturers)) {
foreach ($manufacturers as $manufacturer) {
if ($format === 'sitemap') {
$manufacturersList[] = [
'id' => 'manufacturer-page-' . (int) $manufacturer['id_manufacturer'],
'label' => $manufacturer['name'],
'url' => Context::getContext()->link-
>getManufacturerLink($manufacturer['id_manufacturer'], $manufacturer['link_rewrite']),
'children' => [],
];
} else {
$manufacturersList[] = [
'id' => (int) $manufacturer['id_manufacturer'],
'link' => Context::getContext()->link-
>getManufacturerLink($manufacturer['id_manufacturer'], $manufacturer['link_rewrite']),
'name' => $manufacturer['name'],
'desc' => $manufacturer['description'],
'children' => [],
];
}
}
}

return $manufacturersList;
}
/**
* Return name from id.
*
* @param int $id_manufacturer Manufacturer ID
*
* @return string name
*/
protected static $cacheName = [];

public static function getNameById($idManufacturer)


{
if (!isset(self::$cacheName[$idManufacturer])) {
self::$cacheName[$idManufacturer] = Db::getInstance(_PS_USE_SQL_SLAVE_)-
>getValue(
'
SELECT `name`
FROM `' . _DB_PREFIX_ . 'manufacturer`
WHERE `id_manufacturer` = ' . (int) $idManufacturer . '
AND `active` = 1'
);
}

return self::$cacheName[$idManufacturer];
}

/**
* Get Manufacturer ID by name.
*
* @param string $name
*
* @return bool|int
*/
public static function getIdByName($name)
{
$result = Db::getInstance()->getRow(
'
SELECT `id_manufacturer`
FROM `' . _DB_PREFIX_ . 'manufacturer`
WHERE `name` = \'' . pSQL($name) . '\''
);

if (isset($result['id_manufacturer'])) {
return (int) $result['id_manufacturer'];
}

return false;
}

/**
* Get link to Manufacturer page.
*
* @return string
*/
public function getLink()
{
return Tools::link_rewrite($this->name);
}

/**
* Get Products by Manufacturer ID.
*
* @param int $idManufacturer
* @param int $idLang
* @param int $p
* @param int $n
* @param null $orderBy
* @param null $orderWay
* @param bool $getTotal
* @param bool $active
* @param bool $activeCategory
* @param Context|null $context
*
* @return array|bool
*/
public static function getProducts(
$idManufacturer,
$idLang,
$p,
$n,
$orderBy = null,
$orderWay = null,
$getTotal = false,
$active = true,
$activeCategory = true,
Context $context = null
){
if (!$context) {
$context = Context::getContext();
}

$front = true;
if (!in_array($context->controller->controller_type, ['front', 'modulefront'])) {
$front = false;
}

if ($p < 1) {
$p = 1;
}

if (empty($orderBy) || $orderBy == 'position') {


$orderBy = 'name';
}

if (empty($orderWay)) {
$orderWay = 'ASC';
}

if (!Validate::isOrderBy($orderBy) || !Validate::isOrderWay($orderWay)) {
die(Tools::displayError());
}

$groups = FrontController::getCurrentCustomerGroups();
$sqlGroups = count($groups) ? 'IN (' . implode(',', $groups) . ')' : '=' . (int) Group::getCurrent()-
>id;

/* Return only the number of products */


if ($getTotal) {
$sql = '
SELECT p.`id_product`
FROM `' . _DB_PREFIX_ . 'product` p
' . Shop::addSqlAssociation('product', 'p') . '
WHERE p.id_manufacturer = ' . (int) $idManufacturer
. ($active ? ' AND product_shop.`active` = 1' : '') . '
' . ($front ? ' AND product_shop.`visibility` IN ("both", "catalog")' :
'') . '
AND EXISTS (
SELECT 1
FROM `' . _DB_PREFIX_ . 'category_group` cg
LEFT JOIN `' . _DB_PREFIX_ . 'category_product` cp ON
(cp.`id_category` = cg.`id_category`)' .
($activeCategory ? ' INNER JOIN `' . _DB_PREFIX_ . 'category` ca ON
cp.`id_category` = ca.`id_category` AND ca.`active` = 1' : '') . '
WHERE p.`id_product` = cp.`id_product` AND
cg.`id_group` ' . $sqlGroups . '
)';

$result = Db::getInstance(_PS_USE_SQL_SLAVE_)->executeS($sql);

return (int) count($result);


}
if (strpos($orderBy, '.') > 0) {
$orderBy = explode('.', $orderBy);
$orderBy = pSQL($orderBy[0]) . '.`' . pSQL($orderBy[1]) . '`';
}

if ($orderBy == 'price') {
$alias = 'product_shop.';
} elseif ($orderBy == 'name') {
$alias = 'pl.';
} elseif ($orderBy == 'manufacturer_name') {
$orderBy = 'name';
$alias = 'm.';
} elseif ($orderBy == 'quantity') {
$alias = 'stock.';
} else {
$alias = 'p.';
}

$sql = 'SELECT p.*, product_shop.*, stock.out_of_stock, IFNULL(stock.quantity, 0) as quantity'


. (Combination::isFeatureActive() ? ', product_attribute_shop.minimal_quantity AS
product_attribute_minimal_quantity, IFNULL(product_attribute_shop.`id_product_attribute`,0)
id_product_attribute' : '') . '
, pl.`description`, pl.`description_short`, pl.`link_rewrite`,
pl.`meta_description`, pl.`meta_keywords`,
pl.`meta_title`, pl.`name`, pl.`available_now`, pl.`available_later`,
image_shop.`id_image` id_image, il.`legend`, m.`name` AS manufacturer_name,
DATEDIFF(
product_shop.`date_add`,
DATE_SUB(
"' . date('Y-m-d') . ' 00:00:00",
INTERVAL ' .
(Validate::isUnsignedInt(Configuration::get('PS_NB_DAYS_NEW_PRODUCT')) ?
Configuration::get('PS_NB_DAYS_NEW_PRODUCT') : 20) . ' DAY
)
) > 0 AS new'
. ' FROM `' . _DB_PREFIX_ . 'product` p
' . Shop::addSqlAssociation('product', 'p') .
(Combination::isFeatureActive() ? 'LEFT JOIN `' . _DB_PREFIX_ . 'product_attribute_shop`
product_attribute_shop
ON (p.`id_product` =
product_attribute_shop.`id_product` AND product_attribute_shop.`default_on` = 1 AND
product_attribute_shop.id_shop=' . (int) $context->shop->id . ')' : '') . '
LEFT JOIN `' . _DB_PREFIX_ . 'product_lang` pl
ON (p.`id_product` = pl.`id_product` AND pl.`id_lang` = ' . (int)
$idLang . Shop::addSqlRestrictionOnLang('pl') . ')
LEFT JOIN `' . _DB_PREFIX_ . 'image_shop` image_shop
ON (image_shop.`id_product` = p.`id_product` AND
image_shop.cover=1 AND image_shop.id_shop=' . (int) $context->shop->id . ')
LEFT JOIN `' . _DB_PREFIX_ . 'image_lang` il
ON (image_shop.`id_image` = il.`id_image` AND il.`id_lang` = ' .
(int) $idLang . ')
LEFT JOIN `' . _DB_PREFIX_ . 'manufacturer` m
ON (m.`id_manufacturer` = p.`id_manufacturer`)
' . Product::sqlStock('p', 0);

if (Group::isFeatureActive() || $activeCategory) {
$sql .= 'JOIN `' . _DB_PREFIX_ . 'category_product` cp ON (p.id_product = cp.id_product)';
if (Group::isFeatureActive()) {
$sql .= 'JOIN `' . _DB_PREFIX_ . 'category_group` cg ON (cp.`id_category` =
cg.`id_category` AND cg.`id_group` ' . $sqlGroups . ')';
}
if ($activeCategory) {
$sql .= 'JOIN `' . _DB_PREFIX_ . 'category` ca ON cp.`id_category` = ca.`id_category`
AND ca.`active` = 1';
}
}

$sql .= '
WHERE p.`id_manufacturer` = ' . (int) $idManufacturer . '
' . ($active ? ' AND product_shop.`active` = 1' : '') . '
' . ($front ? ' AND product_shop.`visibility` IN ("both", "catalog")' :
'') . '
GROUP BY p.id_product';

if ($orderBy !== 'price') {


$sql .= '
ORDER BY ' . $alias . '`' . bqSQL($orderBy) . '` ' .
pSQL($orderWay) . '
LIMIT ' . (((int) $p - 1) * (int) $n) . ',' . (int) $n;
}

$result = Db::getInstance(_PS_USE_SQL_SLAVE_)->executeS($sql);

if (!$result) {
return false;
}

if ($orderBy === 'price') {


Tools::orderbyPrice($result, $orderWay);
$result = array_slice($result, (int) (($p - 1) * $n), (int) $n);
}

return Product::getProductsProperties($idLang, $result);


}

/**
* Get Products by Manufacturer
* (light edition).
*
* @param int $idLang
*
* @return array|false|mysqli_result|PDOStatement|resource|null
*/
public function getProductsLite($idLang)
{
$context = Context::getContext();
$front = true;
if (!in_array($context->controller->controller_type, ['front', 'modulefront'])) {
$front = false;
}

return Db::getInstance()->executeS('
SELECT p.`id_product`, pl.`name`
FROM `' . _DB_PREFIX_ . 'product` p
' . Shop::addSqlAssociation('product', 'p') . '
LEFT JOIN `' . _DB_PREFIX_ . 'product_lang` pl ON (
p.`id_product` = pl.`id_product`
AND pl.`id_lang` = ' . (int) $idLang . $context->shop-
>addSqlRestrictionOnLang('pl') . '
)
WHERE p.`id_manufacturer` = ' . (int) $this->id .
($front ? ' AND product_shop.`visibility` IN ("both", "catalog")' : ''));
}

/**
* Specify if a manufacturer already in base.
*
* @param int $idManufacturer Manufacturer id
*
* @return bool
*/
public static function manufacturerExists($idManufacturer)
{
$row = Db::getInstance()->getRow(
'
SELECT `id_manufacturer`
FROM ' . _DB_PREFIX_ . 'manufacturer m
WHERE m.`id_manufacturer` = ' . (int) $idManufacturer,
false
);
return isset($row['id_manufacturer']);
}

/**
* Get Manufacturer Addresses.
*
* @param int $idLang
*
* @return array|false|mysqli_result|PDOStatement|resource|null
*/
public function getAddresses($idLang)
{
return Db::getInstance()->executeS(
'
SELECT a.*, cl.name AS `country`, s.name AS `state`
FROM `' . _DB_PREFIX_ . 'address` AS a
LEFT JOIN `' . _DB_PREFIX_ . 'country_lang` AS cl ON (
cl.`id_country` = a.`id_country`
AND cl.`id_lang` = ' . (int) $idLang . '
)
LEFT JOIN `' . _DB_PREFIX_ . 'state` AS s ON (s.`id_state` = a.`id_state`)
WHERE `id_manufacturer` = ' . (int) $this->id . '
AND a.`deleted` = 0'
);
}

/**
* Get Manufacturer Addresses
* (for webservice).
*
* @return array|false|mysqli_result|PDOStatement|resource|null
*/
public function getWsAddresses()
{
return Db::getInstance()->executeS(
'
SELECT a.id_address as id
FROM `' . _DB_PREFIX_ . 'address` AS a
' . Shop::addSqlAssociation('manufacturer', 'a') . '
WHERE a.`id_manufacturer` = ' . (int) $this->id . '
AND a.`deleted` = 0'
);
}

/**
* Set Manufacturer Addresses
* (for webservice).
*
* @param array $idAddresses
*
* @return bool
*/
public function setWsAddresses($idAddresses)
{
$ids = [];
foreach ($idAddresses as $id) {
$ids[] = (int) $id['id'];
}

$result1 = (
Db::getInstance()->execute('
UPDATE `' . _DB_PREFIX_ . 'address`
SET id_manufacturer = 0
WHERE id_manufacturer = ' . (int) $this->id . '
AND deleted = 0') !== false
);

$result2 = true;
if (count($ids)) {
$result2 = (
Db::getInstance()->execute('
UPDATE `' . _DB_PREFIX_ . 'address`
SET id_customer = 0, id_supplier = 0, id_manufacturer = ' . (int)
$this->id . '
WHERE id_address IN(' . implode(',', $ids) . ')
AND deleted = 0') !== false
);
}

return $result1 && $result2;


}
}
Métodos de pago
use PrestaShop\PrestaShop\Adapter\MailTemplate\MailPartialTemplateRenderer;
use PrestaShop\PrestaShop\Adapter\StockManager;

abstract class PaymentModuleCore extends Module


{
/** @var int Current order's id */
public $currentOrder;
public $currentOrderReference;
public $currencies = true;
public $currencies_mode = 'checkbox';

const DEBUG_MODE = false;

/** @var MailPartialTemplateRenderer */


protected $partialRenderer;

public function install()


{
if (!parent::install()) {
return false;
}

// Insert currencies availability


if ($this->currencies_mode == 'checkbox') {
if (!$this->addCheckboxCurrencyRestrictionsForModule()) {
return false;
}
} elseif ($this->currencies_mode == 'radio') {
if (!$this->addRadioCurrencyRestrictionsForModule()) {
return false;
}
} else {
return $this->trans('No currency mode for payment module', [],
'Admin.Payment.Notification');
}

// Insert countries availability


$return = $this->addCheckboxCountryRestrictionsForModule();

// Insert carrier availability


$return &= $this->addCheckboxCarrierRestrictionsForModule();

if (!Configuration::get('CONF_' . strtoupper($this->name) . '_FIXED')) {


Configuration::updateValue('CONF_' . strtoupper($this->name) . '_FIXED', '0.2');
}
if (!Configuration::get('CONF_' . strtoupper($this->name) . '_VAR')) {
Configuration::updateValue('CONF_' . strtoupper($this->name) . '_VAR', '2');
}
if (!Configuration::get('CONF_' . strtoupper($this->name) . '_FIXED_FOREIGN')) {
Configuration::updateValue('CONF_' . strtoupper($this->name) . '_FIXED_FOREIGN', '0.2');
}
if (!Configuration::get('CONF_' . strtoupper($this->name) . '_VAR_FOREIGN')) {
Configuration::updateValue('CONF_' . strtoupper($this->name) . '_VAR_FOREIGN', '2');
}

return $return;
}

public function uninstall()


{
if (!Db::getInstance()->execute('DELETE FROM `' . _DB_PREFIX_ . 'module_country` WHERE
id_module = ' . (int) $this->id)
|| !Db::getInstance()->execute('DELETE FROM `' . _DB_PREFIX_ . 'module_currency`
WHERE id_module = ' . (int) $this->id)
|| !Db::getInstance()->execute('DELETE FROM `' . _DB_PREFIX_ . 'module_group` WHERE
id_module = ' . (int) $this->id)
|| !Db::getInstance()->execute('DELETE FROM `' . _DB_PREFIX_ . 'module_carrier`
WHERE id_module = ' . (int) $this->id)) {
return false;
}

return parent::uninstall();
}

/**
* Add checkbox currency restrictions for a new module.
*
* @param array $shops
*
* @return bool
*/
public function addCheckboxCurrencyRestrictionsForModule(array $shops = [])
{
if (!$shops) {
$shops = Shop::getShops(true, null, true);
}

foreach ($shops as $s) {


if (!Db::getInstance()->execute('
INSERT INTO `' . _DB_PREFIX_ . 'module_currency` (`id_module`, `id_shop`,
`id_currency`)
SELECT ' . (int) $this->id . ', "' . (int) $s . '", `id_currency` FROM `' . _DB_PREFIX_ .
'currency` WHERE deleted = 0')) {
return false;
}
}

return true;
}

/**
* Add radio currency restrictions for a new module.
*
* @param array $shops
*
* @return bool
*/
public function addRadioCurrencyRestrictionsForModule(array $shops = [])
{
if (!$shops) {
$shops = Shop::getShops(true, null, true);
}

foreach ($shops as $s) {


if (!Db::getInstance()->execute('INSERT INTO `' . _DB_PREFIX_ . 'module_currency`
(`id_module`, `id_shop`, `id_currency`)
VALUES (' . (int) $this->id . ', "' . (int) $s . '", -2)')) {
return false;
}
}

return true;
}

/**
* Add checkbox country restrictions for a new module.
*
* @param array $shops
*
* @return bool
*/
public function addCheckboxCountryRestrictionsForModule(array $shops = [])
{
$countries = Country::getCountries((int) Context::getContext()->language->id, true); //get only
active country
$country_ids = [];
foreach ($countries as $country) {
$country_ids[] = $country['id_country'];
}
return Country::addModuleRestrictions($shops, $countries, [['id_module' => (int) $this->id]]);
}

/**
* Add checkbox carrier restrictions for a new module.
*
* @param array $shops
*
* @return bool
*/
public function addCheckboxCarrierRestrictionsForModule(array $shops = [])
{
if (!$shops) {
$shops = Shop::getShops(true, null, true);
}

$carriers = Carrier::getCarriers((int) Context::getContext()->language->id, false, false, false,


null, Carrier::ALL_CARRIERS);
$carrier_ids = [];
foreach ($carriers as $carrier) {
$carrier_ids[] = $carrier['id_reference'];
}

foreach ($shops as $s) {


foreach ($carrier_ids as $id_carrier) {
if (!Db::getInstance()->execute('INSERT INTO `' . _DB_PREFIX_ . 'module_carrier`
(`id_module`, `id_shop`, `id_reference`)
VALUES (' . (int) $this->id . ', "' . (int) $s . '", ' . (int) $id_carrier . ')'))
{
return false;
}
}
}

return true;
}

/**
* Validate an order in database
* Function called from a payment module.
*
* @param int $id_cart
* @param int $id_order_state
* @param float $amount_paid Amount really paid by customer (in the default currency)
* @param string $payment_method Payment method (eg. 'Credit card')
* @param string|null $message Message to attach to order
* @param array $extra_vars
* @param int|null $currency_special
* @param bool $dont_touch_amount
* @param string|bool $secure_key
* @param Shop $shop
*
* @return bool
*
* @throws PrestaShopException
*/
public function validateOrder(
$id_cart,
$id_order_state,
$amount_paid,
$payment_method = 'Unknown',
$message = null,
$extra_vars = [],
$currency_special = null,
$dont_touch_amount = false,
$secure_key = false,
Shop $shop = null
){
if (self::DEBUG_MODE) {
PrestaShopLogger::addLog('PaymentModule::validateOrder - Function called', 1, null, 'Cart',
(int) $id_cart, true);
}

if (!isset($this->context)) {
$this->context = Context::getContext();
}
$this->context->cart = new Cart((int) $id_cart);
$this->context->customer = new Customer((int) $this->context->cart->id_customer);
// The tax cart is loaded before the customer so re-cache the tax calculation method
$this->context->cart->setTaxCalculationMethod();

$this->context->language = $this->context->cart->getAssociatedLanguage();
$this->context->shop = ($shop ? $shop : new Shop((int) $this->context->cart->id_shop));
ShopUrl::resetMainDomainCache();
$id_currency = $currency_special ? (int) $currency_special : (int) $this->context->cart-
>id_currency;
$this->context->currency = new Currency((int) $id_currency, null, (int) $this->context->shop-
>id);
if (Configuration::get('PS_TAX_ADDRESS_TYPE') == 'id_address_delivery') {
$context_country = $this->context->country;
}

$order_status = new OrderState((int) $id_order_state, (int) $this->context->language->id);


if (!Validate::isLoadedObject($order_status)) {
PrestaShopLogger::addLog('PaymentModule::validateOrder - Order Status cannot be
loaded', 3, null, 'Cart', (int) $id_cart, true);

throw new PrestaShopException('Can\'t load Order status');


}

if (!$this->active) {
PrestaShopLogger::addLog('PaymentModule::validateOrder - Module is not active', 3, null,
'Cart', (int) $id_cart, true);
die(Tools::displayError());
}

// Does order already exists ?


if (Validate::isLoadedObject($this->context->cart) && $this->context->cart->OrderExists() ==
false) {
if ($secure_key !== false && $secure_key != $this->context->cart->secure_key) {
PrestaShopLogger::addLog('PaymentModule::validateOrder - Secure key does not
match', 3, null, 'Cart', (int) $id_cart, true);
die(Tools::displayError());
}

// For each package, generate an order


$delivery_option_list = $this->context->cart->getDeliveryOptionList();
$package_list = $this->context->cart->getPackageList();
$cart_delivery_option = $this->context->cart->getDeliveryOption();

// If some delivery options are not defined, or not valid, use the first valid option
foreach ($delivery_option_list as $id_address => $package) {
if (!isset($cart_delivery_option[$id_address]) || !
array_key_exists($cart_delivery_option[$id_address], $package)) {
foreach ($package as $key => $val) {
$cart_delivery_option[$id_address] = $key;

break;
}
}
}

$order_list = [];
$order_detail_list = [];

do {
$reference = Order::generateReference();
} while (Order::getByReference($reference)->count());

$this->currentOrderReference = $reference;

$cart_total_paid = (float) Tools::ps_round(


(float) $this->context->cart->getOrderTotal(true, Cart::BOTH),
Context::getContext()->getComputingPrecision()
);

foreach ($cart_delivery_option as $id_address => $key_carriers) {


foreach ($delivery_option_list[$id_address][$key_carriers]['carrier_list'] as $id_carrier =>
$data) {
foreach ($data['package_list'] as $id_package) {
// Rewrite the id_warehouse
$package_list[$id_address][$id_package]['id_warehouse'] = (int) $this->context-
>cart->getPackageIdWarehouse($package_list[$id_address][$id_package], (int) $id_carrier);
$package_list[$id_address][$id_package]['id_carrier'] = $id_carrier;
}
}
}
// Make sure CartRule caches are empty
CartRule::cleanCache();
$cart_rules = $this->context->cart->getCartRules();
foreach ($cart_rules as $cart_rule) {
if (($rule = new CartRule((int) $cart_rule['obj']->id)) && Validate::isLoadedObject($rule)) {
if ($error = $rule->checkValidity($this->context, true, true)) {
$this->context->cart->removeCartRule((int) $rule->id);
if (isset($this->context->cookie, $this->context->cookie->id_customer) && $this-
>context->cookie->id_customer && !empty($rule->code)) {
Tools::redirect('index.php?
controller=order&submitAddDiscount=1&discount_name=' . urlencode($rule->code));
} else {
$rule_name = isset($rule->name[(int) $this->context->cart->id_lang]) ? $rule-
>name[(int) $this->context->cart->id_lang] : $rule->code;
$error = $this->trans('The cart rule named "%1s" (ID %2s) used in this cart is not
valid and has been withdrawn from cart', [$rule_name, (int) $rule->id],
'Admin.Payment.Notification');
PrestaShopLogger::addLog($error, 3, '0000002', 'Cart', (int) $this->context->cart-
>id);
}
}
}
}

// Amount paid by customer is not the right one -> Status = payment error
// We don't use the following condition to avoid the float precision issues :
http://www.php.net/manual/en/language.types.float.php
// if ($order->total_paid != $order->total_paid_real)
// We use number_format in order to compare two string
if ($order_status->logable && number_format($cart_total_paid, Context::getContext()-
>getComputingPrecision()) != number_format($amount_paid,
_PS_PRICE_COMPUTE_PRECISION_)) {
$id_order_state = Configuration::get('PS_OS_ERROR');
}

foreach ($package_list as $id_address => $packageByAddress) {


foreach ($packageByAddress as $id_package => $package) {
$orderData = $this->createOrderFromCart(
$this->context->cart,
$this->context->currency,
$package['product_list'],
$id_address,
$this->context,
$reference,
$secure_key,
$payment_method,
$this->name,
$dont_touch_amount,
$amount_paid,
$package_list[$id_address][$id_package]['id_warehouse'],
$cart_total_paid,
self::DEBUG_MODE,
$order_status,
$id_order_state,
isset($package['id_carrier']) ? $package['id_carrier'] : null
);
$order = $orderData['order'];
$order_list[] = $order;
$order_detail_list[] = $orderData['orderDetail'];
}
}

// The country can only change if the address used for the calculation is the delivery
address, and if multi-shipping is activated
if (Configuration::get('PS_TAX_ADDRESS_TYPE') == 'id_address_delivery') {
$this->context->country = $context_country;
}

if (!$this->context->country->active) {
PrestaShopLogger::addLog('PaymentModule::validateOrder - Country is not active', 3,
null, 'Cart', (int) $id_cart, true);

throw new PrestaShopException('The order address country is not active.');


}

if (self::DEBUG_MODE) {
PrestaShopLogger::addLog('PaymentModule::validateOrder - Payment is about to be
added', 1, null, 'Cart', (int) $id_cart, true);
}

// Register Payment only if the order status validate the order


if ($order_status->logable) {
// $order is the last order loop in the foreach
// The method addOrderPayment of the class Order make a create a paymentOrder
// linked to the order reference and not to the order id
if (isset($extra_vars['transaction_id'])) {
$transaction_id = $extra_vars['transaction_id'];
} else {
$transaction_id = null;
}

if (!isset($order) || !$order->addOrderPayment($amount_paid, null, $transaction_id)) {


PrestaShopLogger::addLog('PaymentModule::validateOrder - Cannot save Order
Payment', 3, null, 'Cart', (int) $id_cart, true);

throw new PrestaShopException('Can\'t save Order Payment');


}
}

// Next !
$only_one_gift = false;
$products = $this->context->cart->getProducts();

// Make sure CartRule caches are empty


CartRule::cleanCache();
foreach ($order_detail_list as $key => $order_detail) {
/** @var OrderDetail $order_detail */
$order = $order_list[$key];
if (isset($order->id)) {
if (!$secure_key) {
$message .= '<br />' . $this->trans('Warning: the secure key is empty, check your
payment account before validation', [], 'Admin.Payment.Notification');
}
// Optional message to attach to this order
if (!empty($message)) {
$msg = new Message();
$message = strip_tags($message, '<br>');
if (Validate::isCleanHtml($message)) {
if (self::DEBUG_MODE) {
PrestaShopLogger::addLog('PaymentModule::validateOrder - Message is about
to be added', 1, null, 'Cart', (int) $id_cart, true);
}
$msg->message = $message;
$msg->id_cart = (int) $id_cart;
$msg->id_customer = (int) ($order->id_customer);
$msg->id_order = (int) $order->id;
$msg->private = 1;
$msg->add();
}
}

// Insert new Order detail list using cart for the current order
//$orderDetail = new OrderDetail(null, null, $this->context);
//$orderDetail->createList($order, $this->context->cart, $id_order_state);

// Construct order detail table for the email


$products_list = '';
$virtual_product = true;

$product_var_tpl_list = [];
foreach ($order->product_list as $product) {
$price = Product::getPriceStatic((int) $product['id_product'], false,
($product['id_product_attribute'] ? (int) $product['id_product_attribute'] : null), 6, null, false, true,
$product['cart_quantity'], false, (int) $order->id_customer, (int) $order->id_cart, (int) $order-
>{Configuration::get('PS_TAX_ADDRESS_TYPE')}, $specific_price, true, true, null, true,
$product['id_customization']);
$price_wt = Product::getPriceStatic((int) $product['id_product'], true,
($product['id_product_attribute'] ? (int) $product['id_product_attribute'] : null), 2, null, false, true,
$product['cart_quantity'], false, (int) $order->id_customer, (int) $order->id_cart, (int) $order-
>{Configuration::get('PS_TAX_ADDRESS_TYPE')}, $specific_price, true, true, null, true,
$product['id_customization']);

$product_price = Product::getTaxCalculationMethod() == PS_TAX_EXC ?


Tools::ps_round($price, Context::getContext()->getComputingPrecision()) : $price_wt;

$product_var_tpl = [
'id_product' => $product['id_product'],
'id_product_attribute' => $product['id_product_attribute'],
'reference' => $product['reference'],
'name' => $product['name'] . (isset($product['attributes']) ? ' - ' .
$product['attributes'] : ''),
'price' => Tools::getContextLocale($this->context)->formatPrice($product_price *
$product['quantity'], $this->context->currency->iso_code),
'quantity' => $product['quantity'],
'customization' => [],
];

if (isset($product['price']) && $product['price']) {


$product_var_tpl['unit_price'] = Tools::getContextLocale($this->context)-
>formatPrice($product_price, $this->context->currency->iso_code);
$product_var_tpl['unit_price_full'] = Tools::getContextLocale($this->context)-
>formatPrice($product_price, $this->context->currency->iso_code)
. ' ' . $product['unity'];
} else {
$product_var_tpl['unit_price'] = $product_var_tpl['unit_price_full'] = '';
}
$customized_datas = Product::getAllCustomizedDatas((int) $order->id_cart, null,
true, null, (int) $product['id_customization']);
if (isset($customized_datas[$product['id_product']][$product['id_product_attribute']]))
{
$product_var_tpl['customization'] = [];
foreach ($customized_datas[$product['id_product']]
[$product['id_product_attribute']][$order->id_address_delivery] as $customization) {
$customization_text = '';
if (isset($customization['datas'][Product::CUSTOMIZE_TEXTFIELD])) {
foreach ($customization['datas'][Product::CUSTOMIZE_TEXTFIELD] as
$text) {
$customization_text .= '<strong>' . $text['name'] . '</strong>: ' .
$text['value'] . '<br />';
}
}

if (isset($customization['datas'][Product::CUSTOMIZE_FILE])) {
$customization_text .= $this->trans('%d image(s)',
[count($customization['datas'][Product::CUSTOMIZE_FILE])], 'Admin.Payment.Notification') . '<br
/>';
}

$customization_quantity = (int) $customization['quantity'];

$product_var_tpl['customization'][] = [
'customization_text' => $customization_text,
'customization_quantity' => $customization_quantity,
'quantity' => Tools::getContextLocale($this->context)-
>formatPrice($customization_quantity * $product_price, $this->context->currency->iso_code),
];
}
}

$product_var_tpl_list[] = $product_var_tpl;
// Check if is not a virtual product for the displaying of shipping
if (!$product['is_virtual']) {
$virtual_product &= false;
}
} // end foreach ($products)

$product_list_txt = '';
$product_list_html = '';
if (count($product_var_tpl_list) > 0) {
$product_list_txt = $this->getEmailTemplateContent('order_conf_product_list.txt',
Mail::TYPE_TEXT, $product_var_tpl_list);
$product_list_html = $this->getEmailTemplateContent('order_conf_product_list.tpl',
Mail::TYPE_HTML, $product_var_tpl_list);
}

$total_reduction_value_ti = 0;
$total_reduction_value_tex = 0;

$cart_rules_list = $this->createOrderCartRules(
$order,
$this->context->cart,
$order_list,
$total_reduction_value_ti,
$total_reduction_value_tex,
$id_order_state
);

$cart_rules_list_txt = '';
$cart_rules_list_html = '';
if (count($cart_rules_list) > 0) {
$cart_rules_list_txt = $this->getEmailTemplateContent('order_conf_cart_rules.txt',
Mail::TYPE_TEXT, $cart_rules_list);
$cart_rules_list_html = $this->getEmailTemplateContent('order_conf_cart_rules.tpl',
Mail::TYPE_HTML, $cart_rules_list);
}

// Specify order id for message


$old_message = Message::getMessageByCartId((int) $this->context->cart->id);
if ($old_message && !$old_message['private']) {
$update_message = new Message((int) $old_message['id_message']);
$update_message->id_order = (int) $order->id;
$update_message->update();

// Add this message in the customer thread


$customer_thread = new CustomerThread();
$customer_thread->id_contact = 0;
$customer_thread->id_customer = (int) $order->id_customer;
$customer_thread->id_shop = (int) $this->context->shop->id;
$customer_thread->id_order = (int) $order->id;
$customer_thread->id_lang = (int) $this->context->language->id;
$customer_thread->email = $this->context->customer->email;
$customer_thread->status = 'open';
$customer_thread->token = Tools::passwdGen(12);
$customer_thread->add();

$customer_message = new CustomerMessage();


$customer_message->id_customer_thread = $customer_thread->id;
$customer_message->id_employee = 0;
$customer_message->message = $update_message->message;
$customer_message->private = 0;

if (!$customer_message->add()) {
$this->errors[] = $this->trans('An error occurred while saving message', [],
'Admin.Payment.Notification');
}
}

if (self::DEBUG_MODE) {
PrestaShopLogger::addLog('PaymentModule::validateOrder - Hook validateOrder is
about to be called', 1, null, 'Cart', (int) $id_cart, true);
}

// Hook validate order


Hook::exec('actionValidateOrder', [
'cart' => $this->context->cart,
'order' => $order,
'customer' => $this->context->customer,
'currency' => $this->context->currency,
'orderStatus' => $order_status,
]);

foreach ($this->context->cart->getProducts() as $product) {


if ($order_status->logable) {
ProductSale::addProductSale((int) $product['id_product'], (int)
$product['cart_quantity']);
}
}

if (self::DEBUG_MODE) {
PrestaShopLogger::addLog('PaymentModule::validateOrder - Order Status is about
to be added', 1, null, 'Cart', (int) $id_cart, true);
}

// Set the order status


$new_history = new OrderHistory();
$new_history->id_order = (int) $order->id;
$new_history->changeIdOrderState((int) $id_order_state, $order, true);
$new_history->addWithemail(true, $extra_vars);

// Switch to back order if needed


if (Configuration::get('PS_STOCK_MANAGEMENT') &&
($order_detail->getStockState() ||
$order_detail->product_quantity_in_stock < 0)) {
$history = new OrderHistory();
$history->id_order = (int) $order->id;
$history->changeIdOrderState(Configuration::get($order->hasBeenPaid() ?
'PS_OS_OUTOFSTOCK_PAID' : 'PS_OS_OUTOFSTOCK_UNPAID'), $order, true);
$history->addWithemail();
}

unset($order_detail);

// Order is reloaded because the status just changed


$order = new Order((int) $order->id);

// Send an e-mail to customer (one order = one email)


if ($id_order_state != Configuration::get('PS_OS_ERROR') && $id_order_state !=
Configuration::get('PS_OS_CANCELED') && $this->context->customer->id) {
$invoice = new Address((int) $order->id_address_invoice);
$delivery = new Address((int) $order->id_address_delivery);
$delivery_state = $delivery->id_state ? new State((int) $delivery->id_state) : false;
$invoice_state = $invoice->id_state ? new State((int) $invoice->id_state) : false;
$carrier = $order->id_carrier ? new Carrier($order->id_carrier) : false;
$orderLanguage = new Language((int) $order->id_lang);

// Join PDF invoice


if ((int) Configuration::get('PS_INVOICE') && $order_status->invoice && $order-
>invoice_number) {
$currentLanguage = $this->context->language;
$this->context->language = $orderLanguage;
$this->context->getTranslator()->setLocale($orderLanguage->locale);
$order_invoice_list = $order->getInvoicesCollection();
Hook::exec('actionPDFInvoiceRender', ['order_invoice_list' =>
$order_invoice_list]);
$pdf = new PDF($order_invoice_list, PDF::TEMPLATE_INVOICE, $this->context-
>smarty);
$file_attachement['content'] = $pdf->render(false);
$file_attachement['name'] = Configuration::get('PS_INVOICE_PREFIX', (int)
$order->id_lang, null, $order->id_shop) . sprintf('%06d', $order->invoice_number) . '.pdf';
$file_attachement['mime'] = 'application/pdf';
$this->context->language = $currentLanguage;
$this->context->getTranslator()->setLocale($currentLanguage->locale);
} else {
$file_attachement = null;
}

if (self::DEBUG_MODE) {
PrestaShopLogger::addLog('PaymentModule::validateOrder - Mail is about to be
sent', 1, null, 'Cart', (int) $id_cart, true);
}

if (Validate::isEmail($this->context->customer->email)) {
$data = [
'{firstname}' => $this->context->customer->firstname,
'{lastname}' => $this->context->customer->lastname,
'{email}' => $this->context->customer->email,
'{delivery_block_txt}' => $this->_getFormatedAddress($delivery,
AddressFormat::FORMAT_NEW_LINE),
'{invoice_block_txt}' => $this->_getFormatedAddress($invoice,
AddressFormat::FORMAT_NEW_LINE),
'{delivery_block_html}' => $this->_getFormatedAddress($delivery, '<br />', [
'firstname' => '<span style="font-weight:bold;">%s</span>',
'lastname' => '<span style="font-weight:bold;">%s</span>',
]),
'{invoice_block_html}' => $this->_getFormatedAddress($invoice, '<br />', [
'firstname' => '<span style="font-weight:bold;">%s</span>',
'lastname' => '<span style="font-weight:bold;">%s</span>',
]),
'{delivery_company}' => $delivery->company,
'{delivery_firstname}' => $delivery->firstname,
'{delivery_lastname}' => $delivery->lastname,
'{delivery_address1}' => $delivery->address1,
'{delivery_address2}' => $delivery->address2,
'{delivery_city}' => $delivery->city,
'{delivery_postal_code}' => $delivery->postcode,
'{delivery_country}' => $delivery->country,
'{delivery_state}' => $delivery->id_state ? $delivery_state->name : '',
'{delivery_phone}' => ($delivery->phone) ? $delivery->phone : $delivery-
>phone_mobile,
'{delivery_other}' => $delivery->other,
'{invoice_company}' => $invoice->company,
'{invoice_vat_number}' => $invoice->vat_number,
'{invoice_firstname}' => $invoice->firstname,
'{invoice_lastname}' => $invoice->lastname,
'{invoice_address2}' => $invoice->address2,
'{invoice_address1}' => $invoice->address1,
'{invoice_city}' => $invoice->city,
'{invoice_postal_code}' => $invoice->postcode,
'{invoice_country}' => $invoice->country,
'{invoice_state}' => $invoice->id_state ? $invoice_state->name : '',
'{invoice_phone}' => ($invoice->phone) ? $invoice->phone : $invoice-
>phone_mobile,
'{invoice_other}' => $invoice->other,
'{order_name}' => $order->getUniqReference(),
'{id_order}' => $order->id,
'{date}' => Tools::displayDate(date('Y-m-d H:i:s'), null, 1),
'{carrier}' => ($virtual_product || !isset($carrier->name)) ? $this->trans('No
carrier', [], 'Admin.Payment.Notification') : $carrier->name,
'{payment}' => Tools::substr($order->payment, 0, 255) . ($order-
>hasBeenPaid() ? '' : '&nbsp;' . $this->trans('(waiting for validation)', [], 'Emails.Body')),
'{products}' => $product_list_html,
'{products_txt}' => $product_list_txt,
'{discounts}' => $cart_rules_list_html,
'{discounts_txt}' => $cart_rules_list_txt,
'{total_paid}' => Tools::getContextLocale($this->context)->formatPrice($order-
>total_paid, $this->context->currency->iso_code),
'{total_products}' => Tools::getContextLocale($this->context)-
>formatPrice(Product::getTaxCalculationMethod() == PS_TAX_EXC ? $order->total_products :
$order->total_products_wt, $this->context->currency->iso_code),
'{total_discounts}' => Tools::getContextLocale($this->context)-
>formatPrice($order->total_discounts, $this->context->currency->iso_code),
'{total_shipping}' => Tools::getContextLocale($this->context)-
>formatPrice($order->total_shipping, $this->context->currency->iso_code),
'{total_shipping_tax_excl}' => Tools::getContextLocale($this->context)-
>formatPrice($order->total_shipping_tax_excl, $this->context->currency->iso_code),
'{total_shipping_tax_incl}' => Tools::getContextLocale($this->context)-
>formatPrice($order->total_shipping_tax_incl, $this->context->currency->iso_code),
'{total_wrapping}' => Tools::getContextLocale($this->context)-
>formatPrice($order->total_wrapping, $this->context->currency->iso_code),
'{total_tax_paid}' => Tools::getContextLocale($this->context)-
>formatPrice(($order->total_paid_tax_incl - $order->total_paid_tax_excl), $this->context->currency-
>iso_code),
];

if (is_array($extra_vars)) {
$data = array_merge($data, $extra_vars);
}

Mail::Send(
(int) $order->id_lang,
'order_conf',
$this->context->getTranslator()->trans(
'Order confirmation',
[],
'Emails.Subject',
$orderLanguage->locale
),
$data,
$this->context->customer->email,
$this->context->customer->firstname . ' ' . $this->context->customer->lastname,
null,
null,
$file_attachement,
null,
_PS_MAIL_DIR_,
false,
(int) $order->id_shop
);
}
}

// updates stock in shops


if (Configuration::get('PS_ADVANCED_STOCK_MANAGEMENT')) {
$product_list = $order->getProducts();
foreach ($product_list as $product) {
// if the available quantities depends on the physical stock
if (StockAvailable::dependsOnStock($product['product_id'])) {
// synchronizes
StockAvailable::synchronize($product['product_id'], $order->id_shop);
}
}
}

$order->updateOrderDetailTax();

// sync all stock


(new StockManager())->updatePhysicalProductQuantity(
(int) $order->id_shop,
(int) Configuration::get('PS_OS_ERROR'),
(int) Configuration::get('PS_OS_CANCELED'),
null,
(int) $order->id
);
} else {
$error = $this->trans('Order creation failed', [], 'Admin.Payment.Notification');
PrestaShopLogger::addLog($error, 4, '0000002', 'Cart', (int) ($order->id_cart));
die(Tools::displayError($error));
}
} // End foreach $order_detail_list

// Use the last order as currentOrder


if (isset($order) && $order->id) {
$this->currentOrder = (int) $order->id;
}

if (self::DEBUG_MODE) {
PrestaShopLogger::addLog('PaymentModule::validateOrder - End of validateOrder', 1,
null, 'Cart', (int) $id_cart, true);
}

return true;
} else {
$error = $this->trans('Cart cannot be loaded or an order has already been placed using this
cart', [], 'Admin.Payment.Notification');
PrestaShopLogger::addLog($error, 4, '0000001', 'Cart', (int) ($this->context->cart->id));
die(Tools::displayError($error));
}
}

/**
* @deprecated 1.6.0.7
*
* @param mixed $content
*
* @return mixed
*/
public function formatProductAndVoucherForEmail($content)
{
Tools::displayAsDeprecated('Use $content instead');

return $content;
}

/**
* @param Address $the_address that needs to be txt formatted
*
* @return string the txt formatted address block
*/
protected function _getTxtFormatedAddress($the_address)
{
$adr_fields = AddressFormat::getOrderedAddressFields($the_address->id_country, false,
true);
$r_values = [];
foreach ($adr_fields as $fields_line) {
$tmp_values = [];
foreach (explode(' ', $fields_line) as $field_item) {
$field_item = trim($field_item);
$tmp_values[] = $the_address->{$field_item};
}
$r_values[] = implode(' ', $tmp_values);
}

$out = implode(AddressFormat::FORMAT_NEW_LINE, $r_values);

return $out;
}

/**
* @param Address Address $the_address that needs to be txt formatted
* @param string $line_sep
* @param array $fields_style
*
* @return string the txt formated address block
*/
protected function _getFormatedAddress(Address $the_address, $line_sep, $fields_style = [])
{
return AddressFormat::generateAddress($the_address, ['avoid' => []], $line_sep, ' ',
$fields_style);
}

/**
* @param int $current_id_currency
*
* @return Currency|array|false
*/
public function getCurrency($current_id_currency = null)
{
if (!(int) $current_id_currency) {
$current_id_currency = Context::getContext()->currency->id;
}

if (!$this->currencies) {
return false;
}
if ($this->currencies_mode == 'checkbox') {
return Currency::getPaymentCurrencies($this->id);
}

if ($this->currencies_mode == 'radio') {
$currencies = Currency::getPaymentCurrenciesSpecial($this->id);
$currency = $currencies['id_currency'];
if ($currency == -1) {
$id_currency = (int) $current_id_currency;
} elseif ($currency == -2) {
$id_currency = (int) Configuration::get('PS_CURRENCY_DEFAULT');
} else {
$id_currency = $currency;
}
}
if (!isset($id_currency) || empty($id_currency)) {
return false;
}

return Currency::getCurrencyInstance((int) $id_currency);


}

/**
* Allows specified payment modules to be used by a specific currency.
*
* @since 1.4.5
*
* @param int $id_currency
* @param array $id_module_list
*
* @return bool
*/
public static function addCurrencyPermissions($id_currency, array $id_module_list = [])
{
$values = '';
if (count($id_module_list) == 0) {
// fetch all installed module ids
$modules = static::getInstalledPaymentModules();
foreach ($modules as $module) {
$id_module_list[] = $module['id_module'];
}
}

foreach ($id_module_list as $id_module) {


$values .= '(' . (int) $id_module . ',' . (int) $id_currency . '),';
}

if (!empty($values)) {
return Db::getInstance()->execute('
INSERT INTO `' . _DB_PREFIX_ . 'module_currency` (`id_module`, `id_currency`)
VALUES ' . rtrim($values, ','));
}

return true;
}

/**
* List all installed and active payment modules.
*
* @see Module::getPaymentModules() if you need a list of module related to the user context
* @since 1.4.5
*
* @return array module information
*/
public static function getInstalledPaymentModules()
{
$hook_payment = 'Payment';
if (Db::getInstance()->getValue('SELECT `id_hook` FROM `' . _DB_PREFIX_ . 'hook` WHERE
`name` = \'paymentOptions\'')) {
$hook_payment = 'paymentOptions';
}

return Db::getInstance()->executeS('
SELECT DISTINCT m.`id_module`, h.`id_hook`, m.`name`, hm.`position`
FROM `' . _DB_PREFIX_ . 'module` m
LEFT JOIN `' . _DB_PREFIX_ . 'hook_module` hm ON hm.`id_module` = m.`id_module`'
. Shop::addSqlRestriction(false, 'hm') . '
LEFT JOIN `' . _DB_PREFIX_ . 'hook` h ON hm.`id_hook` = h.`id_hook`
INNER JOIN `' . _DB_PREFIX_ . 'module_shop` ms ON (m.`id_module` = ms.`id_module`
AND ms.id_shop=' . (int) Context::getContext()->shop->id . ')
WHERE h.`name` = \'' . pSQL($hook_payment) . '\'');
}

public static function preCall($module_name)


{
if (!parent::preCall($module_name)) {
return false;
}

if (($module_instance = Module::getInstanceByName($module_name))) {
/** @var PaymentModule $module_instance */
if (!$module_instance->currencies || ($module_instance->currencies &&
count(Currency::checkPaymentCurrencies($module_instance->id)))) {
return true;
}
}

return false;
}

/**
* @return MailPartialTemplateRenderer
*/
protected function getPartialRenderer()
{
if (!$this->partialRenderer) {
$this->partialRenderer = new MailPartialTemplateRenderer($this->context->smarty);
}

return $this->partialRenderer;
}

/**
* Fetch the content of $template_name inside the folder
* current_theme/mails/current_iso_lang/ if found, otherwise in
* mails/current_iso_lang.
*
* @param string $template_name template name with extension
* @param int $mail_type Mail::TYPE_HTML or Mail::TYPE_TEXT
* @param array $var sent to smarty as 'list'
*
* @return string
*/
protected function getEmailTemplateContent($template_name, $mail_type, $var)
{
$email_configuration = Configuration::get('PS_MAIL_TYPE');
if ($email_configuration != $mail_type && $email_configuration != Mail::TYPE_BOTH) {
return '';
}

return $this->getPartialRenderer()->render($template_name, $this->context->language, $var);


}

protected function createOrderFromCart(


Cart $cart,
Currency $currency,
$productList,
$addressId,
$context,
$reference,
$secure_key,
$payment_method,
$name,
$dont_touch_amount,
$amount_paid,
$warehouseId,
$cart_total_paid,
$debug,
$order_status,
$id_order_state,
$carrierId = null
){
$order = new Order();
$order->product_list = $productList;

$computingPrecision = Context::getContext()->getComputingPrecision();

if (Configuration::get('PS_TAX_ADDRESS_TYPE') == 'id_address_delivery') {
$address = new Address((int) $addressId);
$context->country = new Country((int) $address->id_country, (int) $cart->id_lang);
if (!$context->country->active) {
throw new PrestaShopException('The delivery address country is not active.');
}
}

$carrier = null;
if (!$cart->isVirtualCart() && isset($carrierId)) {
$carrier = new Carrier((int) $carrierId, (int) $cart->id_lang);
$order->id_carrier = (int) $carrier->id;
$carrierId = (int) $carrier->id;
} else {
$order->id_carrier = 0;
$carrierId = 0;
}

$order->id_customer = (int) $cart->id_customer;


$order->id_address_invoice = (int) $cart->id_address_invoice;
$order->id_address_delivery = (int) $addressId;
$order->id_currency = $currency->id;
$order->id_lang = (int) $cart->id_lang;
$order->id_cart = (int) $cart->id;
$order->reference = $reference;
$order->id_shop = (int) $context->shop->id;
$order->id_shop_group = (int) $context->shop->id_shop_group;

$order->secure_key = ($secure_key ? pSQL($secure_key) : pSQL($context->customer-


>secure_key));
$order->payment = $payment_method;
if (isset($name)) {
$order->module = $name;
}
$order->recyclable = $cart->recyclable;
$order->gift = (int) $cart->gift;
$order->gift_message = $cart->gift_message;
$order->mobile_theme = $cart->mobile_theme;
$order->conversion_rate = $currency->conversion_rate;
$amount_paid = !$dont_touch_amount ? Tools::ps_round((float) $amount_paid,
$computingPrecision) : $amount_paid;
$order->total_paid_real = 0;

$order->total_products = Tools::ps_round(
(float) $cart->getOrderTotal(false, Cart::ONLY_PRODUCTS, $order->product_list,
$carrierId),
$computingPrecision
);
$order->total_products_wt = Tools::ps_round(
(float) $cart->getOrderTotal(true, Cart::ONLY_PRODUCTS, $order->product_list,
$carrierId),
$computingPrecision
);
$order->total_discounts_tax_excl = Tools::ps_round(
(float) abs($cart->getOrderTotal(false, Cart::ONLY_DISCOUNTS, $order->product_list,
$carrierId)),
$computingPrecision
);
$order->total_discounts_tax_incl = Tools::ps_round(
(float) abs($cart->getOrderTotal(true, Cart::ONLY_DISCOUNTS, $order->product_list,
$carrierId)),
$computingPrecision
);
$order->total_discounts = $order->total_discounts_tax_incl;

$order->total_shipping_tax_excl = Tools::ps_round(
(float) $cart->getPackageShippingCost($carrierId, false, null, $order->product_list),
$computingPrecision
);
$order->total_shipping_tax_incl = Tools::ps_round(
(float) $cart->getPackageShippingCost($carrierId, true, null, $order->product_list),
$computingPrecision
);
$order->total_shipping = $order->total_shipping_tax_incl;

if (null !== $carrier && Validate::isLoadedObject($carrier)) {


$order->carrier_tax_rate = $carrier->getTaxesRate(new Address((int) $cart-
>{Configuration::get('PS_TAX_ADDRESS_TYPE')}));
}

$order->total_wrapping_tax_excl = Tools::ps_round(
(float) abs($cart->getOrderTotal(false, Cart::ONLY_WRAPPING, $order->product_list,
$carrierId)),
$computingPrecision
);
$order->total_wrapping_tax_incl = Tools::ps_round(
(float) abs($cart->getOrderTotal(true, Cart::ONLY_WRAPPING, $order->product_list,
$carrierId)),
$computingPrecision
);
$order->total_wrapping = $order->total_wrapping_tax_incl;

$order->total_paid_tax_excl = Tools::ps_round(
(float) $cart->getOrderTotal(false, Cart::BOTH, $order->product_list, $carrierId),
$computingPrecision
);
$order->total_paid_tax_incl = Tools::ps_round(
(float) $cart->getOrderTotal(true, Cart::BOTH, $order->product_list, $carrierId),
$computingPrecision
);
$order->total_paid = $order->total_paid_tax_incl;
$order->round_mode = Configuration::get('PS_PRICE_ROUND_MODE');
$order->round_type = Configuration::get('PS_ROUND_TYPE');

$order->invoice_date = '0000-00-00 00:00:00';


$order->delivery_date = '0000-00-00 00:00:00';

if ($debug) {
PrestaShopLogger::addLog('PaymentModule::validateOrder - Order is about to be added',
1, null, 'Cart', (int) $cart->id, true);
}

// Creating order
$result = $order->add();

if (!$result) {
PrestaShopLogger::addLog('PaymentModule::validateOrder - Order cannot be created', 3,
null, 'Cart', (int) $cart->id, true);
throw new PrestaShopException('Can\'t save Order');
}

// Amount paid by customer is not the right one -> Status = payment error
// We don't use the following condition to avoid the float precision issues :
https://www.php.net/manual/en/language.types.float.php
// if ($order->total_paid != $order->total_paid_real)
// We use number_format in order to compare two string
if ($order_status->logable
&& number_format(
$cart_total_paid,
$computingPrecision
) != number_format(
$amount_paid,
$computingPrecision
)
){
$id_order_state = Configuration::get('PS_OS_ERROR');
}

if ($debug) {
PrestaShopLogger::addLog('PaymentModule::validateOrder - OrderDetail is about to be
added', 1, null, 'Cart', (int) $cart->id, true);
}

// Insert new Order detail list using cart for the current order
$order_detail = new OrderDetail(null, null, $context);
$order_detail->createList($order, $cart, $id_order_state, $order->product_list, 0, true,
$warehouseId);

if ($debug) {
PrestaShopLogger::addLog('PaymentModule::validateOrder - OrderCarrier is about to be
added', 1, null, 'Cart', (int) $cart->id, true);
}

// Adding an entry in order_carrier table


if (null !== $carrier) {
$order_carrier = new OrderCarrier();
$order_carrier->id_order = (int) $order->id;
$order_carrier->id_carrier = $carrierId;
$order_carrier->weight = (float) $order->getTotalWeight();
$order_carrier->shipping_cost_tax_excl = (float) $order->total_shipping_tax_excl;
$order_carrier->shipping_cost_tax_incl = (float) $order->total_shipping_tax_incl;
$order_carrier->add();
}

return ['order' => $order, 'orderDetail' => $order_detail];


}

protected function createOrderCartRules(


Order $order,
Cart $cart,
$order_list,
$total_reduction_value_ti,
$total_reduction_value_tex,
$id_order_state
){
$cart_rule_used = [];
$computingPrecision = Context::getContext()->getComputingPrecision();

// prepare cart calculator to correctly get the value of each cart rule
$calculator = $cart->newCalculator($order->product_list, $cart->getCartRules(), $order-
>id_carrier, $computingPrecision);
$calculator->processCalculation();
$cartRulesData = $calculator->getCartRulesData();

$cart_rules_list = [];
foreach ($cartRulesData as $cartRuleData) {
$cartRule = $cartRuleData->getCartRule();
// Here we need to get actual values from cart calculator
$values = [
'tax_incl' => $cartRuleData->getDiscountApplied()->getTaxIncluded(),
'tax_excl' => $cartRuleData->getDiscountApplied()->getTaxExcluded(),
];

// If the reduction is not applicable to this order, then continue with the next one
if (!$values['tax_excl']) {
continue;
}

// IF
// This is not multi-shipping
// The value of the voucher is greater than the total of the order
// Partial use is allowed
// This is an "amount" reduction, not a reduction in % or a gift
// THEN
// The voucher is cloned with a new value corresponding to the remainder
$cartRuleReductionAmountConverted = $cartRule->reduction_amount;
if ((int) $cartRule->reduction_currency !== $cart->id_currency) {
$cartRuleReductionAmountConverted = Tools::convertPriceFull(
$cartRule->reduction_amount,
new Currency((int) $cartRule->reduction_currency),
new Currency($cart->id_currency)
);
}
$remainingValue = $cartRuleReductionAmountConverted - $values[$cartRule-
>reduction_tax ? 'tax_incl' : 'tax_excl'];
$remainingValue = Tools::ps_round($remainingValue,
_PS_PRICE_COMPUTE_PRECISION_);
if (count($order_list) == 1 && $remainingValue > 0 && $cartRule->partial_use == 1 &&
$cartRuleReductionAmountConverted > 0) {
// Create a new voucher from the original
$voucher = new CartRule((int) $cartRule->id); // We need to instantiate the CartRule
without lang parameter to allow saving it
unset($voucher->id);

// Set a new voucher code


$voucher->code = empty($voucher->code) ? substr(md5($order->id . '-' . $order-
>id_customer . '-' . $cartRule->id), 0, 16) : $voucher->code . '-2';
if (preg_match('/\-([0-9]{1,2})\-([0-9]{1,2})$/', $voucher->code, $matches) && $matches[1]
== $matches[2]) {
$voucher->code = preg_replace('/' . $matches[0] . '$/', '-' . (intval($matches[1]) + 1),
$voucher->code);
}

// Set the new voucher value


$voucher->reduction_amount = $remainingValue;
if ($voucher->reduction_tax) {
// Add total shipping amount only if reduction amount > total shipping
if ($voucher->free_shipping == 1 && $voucher->reduction_amount >= $order-
>total_shipping_tax_incl) {
$voucher->reduction_amount -= $order->total_shipping_tax_incl;
}
} else {
// Add total shipping amount only if reduction amount > total shipping
if ($voucher->free_shipping == 1 && $voucher->reduction_amount >= $order-
>total_shipping_tax_excl) {
$voucher->reduction_amount -= $order->total_shipping_tax_excl;
}
}
if ($voucher->reduction_amount <= 0) {
continue;
}

if ($this->context->customer->isGuest()) {
$voucher->id_customer = 0;
} else {
$voucher->id_customer = $order->id_customer;
}

$voucher->quantity = 1;
$voucher->reduction_currency = $order->id_currency;
$voucher->quantity_per_user = 1;
if ($voucher->add()) {
// If the voucher has conditions, they are now copied to the new voucher
CartRule::copyConditions($cartRule->id, $voucher->id);
$orderLanguage = new Language((int) $order->id_lang);

$params = [
'{voucher_amount}' => Tools::getContextLocale($this->context)-
>formatPrice($voucher->reduction_amount, $this->context->currency->iso_code),
'{voucher_num}' => $voucher->code,
'{firstname}' => $this->context->customer->firstname,
'{lastname}' => $this->context->customer->lastname,
'{id_order}' => $order->id,
'{order_name}' => $order->getUniqReference(),
];
Mail::Send(
(int) $order->id_lang,
'voucher',
Context::getContext()->getTranslator()->trans(
'New voucher for your order %s',
[$order->reference],
'Emails.Subject',
$orderLanguage->locale
),
$params,
$this->context->customer->email,
$this->context->customer->firstname . ' ' . $this->context->customer->lastname,
null, null, null, null, _PS_MAIL_DIR_, false, (int) $order->id_shop
);
}

$values['tax_incl'] = $order->total_products_wt - $total_reduction_value_ti;


$values['tax_excl'] = $order->total_products - $total_reduction_value_tex;
if (1 == $voucher->free_shipping) {
$values['tax_incl'] += $order->total_shipping_tax_incl;
$values['tax_excl'] += $order->total_shipping_tax_excl;
}
}
$total_reduction_value_ti += $values['tax_incl'];
$total_reduction_value_tex += $values['tax_excl'];

$order->addCartRule($cartRule->id, $cartRule->name, $values, 0, $cartRule-


>free_shipping);

if ($id_order_state != Configuration::get('PS_OS_ERROR') && $id_order_state !=


Configuration::get('PS_OS_CANCELED') && !in_array($cartRule->id, $cart_rule_used)) {
$cart_rule_used[] = $cartRule->id;

// Create a new instance of Cart Rule without id_lang, in order to update its quantity
$cart_rule_to_update = new CartRule((int) $cartRule->id);
$cart_rule_to_update->quantity = max(0, $cart_rule_to_update->quantity - 1);
$cart_rule_to_update->update();
}

$cart_rules_list[] = [
'voucher_name' => $cartRule->name,
'voucher_reduction' => ($values['tax_incl'] != 0.00 ? '-' : '') . Tools::getContextLocale($this-
>context)->formatPrice($values['tax_incl'], $this->context->currency->iso_code),
];
}

return $cart_rules_list;
}
}

Métodos de entrega
class DeliveryCore extends ObjectModel
{
/** @var int */
public $id_delivery;

/** @var int * */


public $id_shop;

/** @var int * */


public $id_shop_group;
/** @var int */
public $id_carrier;

/** @var int */


public $id_range_price;

/** @var int */


public $id_range_weight;

/** @var int */


public $id_zone;

/** @var float */


public $price;

/**
* @see ObjectModel::$definition
*/
public static $definition = [
'table' => 'delivery',
'primary' => 'id_delivery',
'fields' => [
'id_carrier' => ['type' => self::TYPE_INT, 'validate' => 'isUnsignedId', 'required' => true],
'id_range_price' => ['type' => self::TYPE_INT, 'validate' => 'isUnsignedId', 'required' => true],
'id_range_weight' => ['type' => self::TYPE_INT, 'validate' => 'isUnsignedId', 'required' =>
true],
'id_zone' => ['type' => self::TYPE_INT, 'validate' => 'isUnsignedId', 'required' => true],
'id_shop' => ['type' => self::TYPE_INT],
'id_shop_group' => ['type' => self::TYPE_INT],
'price' => ['type' => self::TYPE_FLOAT, 'validate' => 'isPrice', 'required' => true],
],
];

protected $webserviceParameters = [
'objectsNodeName' => 'deliveries',
'fields' => [
'id_carrier' => ['xlink_resource' => 'carriers'],
'id_range_price' => ['xlink_resource' => 'price_ranges'],
'id_range_weight' => ['xlink_resource' => 'weight_ranges'],
'id_zone' => ['xlink_resource' => 'zones'],
],
];

/**
* Get Object fields and values in array.
*
* @return array
*/
public function getFields()
{
$fields = parent::getFields();

// @todo add null management in definitions


if ($this->id_shop) {
$fields['id_shop'] = (int) $this->id_shop;
} else {
$fields['id_shop'] = null;
}

if ($this->id_shop_group) {
$fields['id_shop_group'] = (int) $this->id_shop_group;
} else {
$fields['id_shop_group'] = null;
}

return $fields;
}
}
Confirmación de pedido
class OrderMessageCore extends ObjectModel
{
/** @var array<string> Name */
public $name;

/** @var array<string> Message content */


public $message;

/** @var string Object creation date */


public $date_add;

/**
* @see ObjectModel::$definition
*/
public static $definition = [
'table' => 'order_message',
'primary' => 'id_order_message',
'multilang' => true,
'fields' => [
'date_add' => ['type' => self::TYPE_DATE, 'validate' => 'isDate'],

/* Lang fields */
'name' => ['type' => self::TYPE_STRING, 'lang' => true, 'validate' => 'isGenericName',
'required' => true, 'size' => 128],
'message' => ['type' => self::TYPE_STRING, 'lang' => true, 'validate' => 'isMessage',
'required' => true, 'size' => 1200],
],
];

protected $webserviceParameters = [
'fields' => [
'id' => ['sqlId' => 'id_discount_type', 'xlink_resource' => 'order_message_lang'],
'date_add' => ['sqlId' => 'date_add'],
],
];

public static function getOrderMessages($id_lang)


{
return Db::getInstance(_PS_USE_SQL_SLAVE_)->executeS('
SELECT om.id_order_message, oml.name, oml.message
FROM ' . _DB_PREFIX_ . 'order_message om
LEFT JOIN ' . _DB_PREFIX_ . 'order_message_lang oml ON (oml.id_order_message =
om.id_order_message)
WHERE oml.id_lang = ' . (int) $id_lang . '
ORDER BY name ASC');
}
}
Controles de seguridad implementados
class ValidateCore
{
public const ORDER_BY_REGEXP = '/^(?:(`?)[\w!_-]+\1\.)?(?:(`?)[\w!_-]+\2)$/';

const ADMIN_PASSWORD_LENGTH = 8;
const PASSWORD_LENGTH = 5;

public static function isIp2Long($ip)


{
return preg_match('#^-?[0-9]+$#', (string) $ip);
}

public static function isAnything()


{
return true;
}

/**
* Check for e-mail validity.
*
* @param string $email e-mail address to validate
*
* @return bool Validity is ok or not
*/
public static function isEmail($email)
{
return !empty($email) && (new EmailValidator())->isValid($email, new
MultipleValidationWithAnd([
new RFCValidation(),
new SwiftMailerValidation(), // special validation to be compatible with Swift Mailer
]));
}

/**
* Check for module URL validity.
*
* @param string $url module URL to validate
* @param array $errors Reference array for catching errors
*
* @return bool Validity is ok or not
*/
public static function isModuleUrl($url, &$errors)
{
if (!$url || $url == 'http://') {
$errors[] = Context::getContext()->getTranslator()->trans('Please specify module URL', [],
'Admin.Modules.Notification');
} elseif (substr($url, -4) != '.tar' && substr($url, -4) != '.zip' && substr($url, -4) != '.tgz' &&
substr($url, -7) != '.tar.gz') {
$errors[] = Context::getContext()->getTranslator()->trans('Unknown archive type.', [],
'Admin.Modules.Notification');
} else {
if ((strpos($url, 'http')) === false) {
$url = 'http://' . $url;
}
if (!is_array(@get_headers($url))) {
$errors[] = Context::getContext()->getTranslator()->trans('Invalid URL', [],
'Admin.Notifications.Error');
}
}
if (!count($errors)) {
return true;
}

return false;
}

/**
* Check for MD5 string validity.
*
* @param string $md5 MD5 string to validate
*
* @return bool Validity is ok or not
*/
public static function isMd5($md5)
{
return preg_match('/^[a-f0-9A-F]{32}$/', $md5);
}

/**
* Check for SHA1 string validity.
*
* @param string $sha1 SHA1 string to validate
*
* @return bool Validity is ok or not
*/
public static function isSha1($sha1)
{
return preg_match('/^[a-fA-F0-9]{40}$/', $sha1);
}

/**
* Check for a float number validity.
*
* @param float $float Float number to validate
*
* @return bool Validity is ok or not
*/
public static function isFloat($float)
{
return (string) ((float) $float) == (string) $float;
}

public static function isUnsignedFloat($float)


{
return (string) ((float) $float) == (string) $float && $float >= 0;
}

/**
* Check for a float number validity.
*
* @param float $float Float number to validate
*
* @return bool Validity is ok or not
*/
public static function isOptFloat($float)
{
return empty($float) || Validate::isFloat($float);
}

/**
* Check for a carrier name validity.
*
* @param string $name Carrier name to validate
*
* @return bool Validity is ok or not
*/
public static function isCarrierName($name)
{
return empty($name) || preg_match(Tools::cleanNonUnicodeSupport('/^[^<>;=#{}]*$/u'),
$name);
}

/**
* Check for an image size validity.
*
* @param string $size Image size to validate
*
* @return bool Validity is ok or not
*/
public static function isImageSize($size)
{
return preg_match('/^[0-9]{1,4}$/', $size);
}

/**
* Check whether given customer name is valid
*
* @param string $name Name to validate
*
* @return bool
*/
public static function isCustomerName($name)
{
$validatorBuilder = Validation::createValidatorBuilder();
$validatorBuilder->setConstraintValidatorFactory(
new CustomerNameValidatorFactory(new CharacterCleaner())
);
$validator = $validatorBuilder->getValidator();
$violations = $validator->validate($name, [
new CustomerName(),
]);

return (count($violations) !== 0) ? 0 : 1;


}

/**
* Check whether given name is valid
*
* @param string $name Name to validate
*
* @return bool
*/
public static function isName($name)
{
$validityPattern = Tools::cleanNonUnicodeSupport(
'/^[^0-9!<>,;?=+()@#"°{}_$%:¤|]*$/u'
);

return preg_match($validityPattern, $name);


}

/**
* Check for hook name validity.
*
* @param string $hook Hook name to validate
*
* @return bool Validity is ok or not
*/
public static function isHookName($hook)
{
return preg_match('/^[a-zA-Z0-9_-]+$/', $hook);
}

/**
* Check for sender name validity.
*
* @param string $mail_name Sender name to validate
*
* @return bool Validity is ok or not
*/
public static function isMailName($mail_name)
{
return is_string($mail_name) &&
preg_match(Tools::cleanNonUnicodeSupport('/^[^<>;=#{}]*$/u'), $mail_name);
}

/**
* Check for e-mail subject validity.
*
* @param string $mail_subject e-mail subject to validate
*
* @return bool Validity is ok or not
*/
public static function isMailSubject($mail_subject)
{
return preg_match(Tools::cleanNonUnicodeSupport('/^[^<>]*$/u'), $mail_subject);
}

/**
* Check for module name validity.
*
* @param string $module_name Module name to validate
*
* @return bool Validity is ok or not
*/
public static function isModuleName($module_name)
{
return is_string($module_name) && preg_match('/^[a-zA-Z0-9_-]+$/', $module_name);
}

/**
* Check for template name validity.
*
* @param string $tpl_name Template name to validate
*
* @return bool Validity is ok or not
*/
public static function isTplName($tpl_name)
{
return preg_match('/^[a-zA-Z0-9_-]+$/', $tpl_name);
}

/**
* Check for image type name validity.
*
* @param string $type Image type name to validate
*
* @return bool Validity is ok or not
*/
public static function isImageTypeName($type)
{
return preg_match('/^[a-zA-Z0-9_ -]+$/', $type);
}

/**
* Check for price validity.
*
* @param string $price Price to validate
*
* @return bool Validity is ok or not
*/
public static function isPrice($price)
{
return preg_match('/^[0-9]{1,10}(\.[0-9]{1,9})?$/', $price);
}

/**
* Check for price validity (including negative price).
*
* @param string $price Price to validate
*
* @return bool Validity is ok or not
*/
public static function isNegativePrice($price)
{
return preg_match('/^[-]?[0-9]{1,10}(\.[0-9]{1,9})?$/', $price);
}

/**
* Check for language code (ISO) validity.
*
* @param string $iso_code Language code (ISO) to validate
*
* @return bool Validity is ok or not
*/
public static function isLanguageIsoCode($iso_code)
{
return preg_match('/^[a-zA-Z]{2,3}$/', $iso_code);
}

public static function isLanguageCode($s)


{
return preg_match('/^[a-zA-Z]{2}(-[a-zA-Z]{2})?$/', $s);
}

/**
* @see https://en.wikipedia.org/wiki/IETF_language_tag#ISO_3166-1_and_UN_M.49
*
* @param string $s
*
* @return bool
*/
public static function isLocale($s)
{
return preg_match('/^[a-z]{2}-[A-Z]{2}$/', $s);
}

public static function isStateIsoCode($iso_code)


{
return preg_match('/^[a-zA-Z0-9]{1,4}((-)[a-zA-Z0-9]{1,4})?$/', $iso_code);
}

public static function isNumericIsoCode($iso_code)


{
return preg_match(NumericIsoCode::PATTERN, $iso_code);
}

/**
* Check for voucher name validity.
*
* @param string $voucher voucher to validate
*
* @return bool Validity is ok or not
*/
public static function isDiscountName($voucher)
{
return preg_match(Tools::cleanNonUnicodeSupport('/^[^!<>,;?=+()@"°{}_$%:]{3,32}$/u'),
$voucher);
}

/**
* Check for product or category name validity.
*
* @param string $name Product or category name to validate
*
* @return bool Validity is ok or not
*/
public static function isCatalogName($name)
{
return preg_match(Tools::cleanNonUnicodeSupport('/^[^<>;=#{}]*$/u'), $name);
}

/**
* Check for a message validity.
*
* @param string $message Message to validate
*
* @return bool Validity is ok or not
*/
public static function isMessage($message)
{
return !preg_match('/[<>{}]/i', $message);
}

/**
* Check for a country name validity.
*
* @param string $name Country name to validate
*
* @return bool Validity is ok or not
*/
public static function isCountryName($name)
{
return preg_match('/^[a-zA-Z -]+$/', $name);
}

/**
* Check for a link (url-rewriting only) validity.
*
* @param string $link Link to validate
*
* @return bool Validity is ok or not
*/
public static function isLinkRewrite($link)
{
if (Configuration::get('PS_ALLOW_ACCENTED_CHARS_URL')) {
return preg_match(Tools::cleanNonUnicodeSupport('/^[_a-zA-Z0-9\x{0600}-\x{06FF}\pL\pS-]
+$/u'), $link);
}

return preg_match('/^[_a-zA-Z0-9\-]+$/', $link);


}

/**
* Check for a route pattern validity.
*
* @param string $pattern to validate
*
* @return bool Validity is ok or not
*/
public static function isRoutePattern($pattern)
{
if (Configuration::get('PS_ALLOW_ACCENTED_CHARS_URL')) {
return preg_match(Tools::cleanNonUnicodeSupport('/^[_a-zA-Z0-9\x{0600}-\x{06FF}\(\)\.{}:\/\
pL\pS-]+$/u'), $pattern);
}

return preg_match('/^[_a-zA-Z0-9\(\)\.{}:\/\-]+$/', $pattern);


}

/**
* Check for a postal address validity.
*
* @param string $address Address to validate
*
* @return bool Validity is ok or not
*/
public static function isAddress($address)
{
return empty($address) || preg_match(Tools::cleanNonUnicodeSupport('/^[^!<>?=+@{}_$
%]*$/u'), $address);
}

/**
* Check for city name validity.
*
* @param string $city City name to validate
*
* @return bool Validity is ok or not
*/
public static function isCityName($city)
{
return preg_match(Tools::cleanNonUnicodeSupport('/^[^!<>;?=+@#"°{}_$%]*$/u'), $city);
}

/**
* Check for search query validity.
*
* @param string $search Query to validate
*
* @return bool Validity is ok or not
*/
public static function isValidSearch($search)
{
return preg_match(Tools::cleanNonUnicodeSupport('/^[^<>;=#{}]{0,64}$/u'), $search);
}

/**
* Check for standard name validity.
*
* @param string $name Name to validate
*
* @return bool Validity is ok or not
*/
public static function isGenericName($name)
{
return empty($name) || preg_match(Tools::cleanNonUnicodeSupport('/^[^<>={}]*$/u'), $name);
}

/**
* Check for HTML field validity (no XSS please !).
*
* @param string $html HTML field to validate
*
* @return bool Validity is ok or not
*/
public static function isCleanHtml($html, $allow_iframe = false)
{
$events = 'onmousedown|onmousemove|onmmouseup|onmouseover|onmouseout|onload|
onunload|onfocus|onblur|onchange';
$events .= '|onsubmit|ondblclick|onclick|onkeydown|onkeyup|onkeypress|onmouseenter|
onmouseleave|onerror|onselect|onreset|onabort|ondragdrop|onresize|onactivate|onafterprint|
onmoveend';
$events .= '|onafterupdate|onbeforeactivate|onbeforecopy|onbeforecut|onbeforedeactivate|
onbeforeeditfocus|onbeforepaste|onbeforeprint|onbeforeunload|onbeforeupdate|onmove';
$events .= '|onbounce|oncellchange|oncontextmenu|oncontrolselect|oncopy|oncut|
ondataavailable|ondatasetchanged|ondatasetcomplete|ondeactivate|ondrag|ondragend|
ondragenter|onmousewheel';
$events .= '|ondragleave|ondragover|ondragstart|ondrop|onerrorupdate|onfilterchange|onfinish|
onfocusin|onfocusout|onhashchange|onhelp|oninput|onlosecapture|onmessage|onmouseup|
onmovestart';
$events .= '|onoffline|ononline|onpaste|onpropertychange|onreadystatechange|onresizeend|
onresizestart|onrowenter|onrowexit|onrowsdelete|onrowsinserted|onscroll|onsearch|
onselectionchange';
$events .= '|onselectstart|onstart|onstop';

if (preg_match('/<[\s]*script/ims', $html) || preg_match('/(' . $events . ')[\s]*=/ims', $html) ||


preg_match('/.*script\:/ims', $html)) {
return false;
}

if (!$allow_iframe && preg_match('/<[\s]*(i?frame|form|input|embed|object)/ims', $html)) {


return false;
}

return true;
}

/**
* Check for product reference validity.
*
* @param string $reference Product reference to validate
*
* @return bool Validity is ok or not
*/
public static function isReference($reference)
{
return preg_match(Tools::cleanNonUnicodeSupport('/^[^<>;={}]*$/u'), $reference);
}

/**
* Check for password validity.
*
* @param string $passwd Password to validate
* @param int $size
*
* @return bool Validity is ok or not
*
* @deprecated 1.7.0
*/
public static function isPasswd($passwd, $size = Validate::PASSWORD_LENGTH)
{
return self::isPlaintextPassword($passwd, $size);
}

/**
* Check if plaintext password is valid
* Size is limited by `password_hash()` (72 chars).
*
* @param string $plaintextPasswd Password to validate
* @param int $size
*
* @return bool Indicates whether the given string is a valid plaintext password
*
* @since 1.7.0
*/
public static function isPlaintextPassword($plaintextPasswd, $size =
Validate::PASSWORD_LENGTH)
{
// The password lenght is limited by `password_hash()`
return Tools::strlen($plaintextPasswd) >= $size && Tools::strlen($plaintextPasswd) <= 72;
}

/**
* Check if hashed password is valid
* PrestaShop supports both MD5 and `PASSWORD_BCRYPT` (PHP API)
* The lengths are 32 (MD5) or 60 (`PASSWORD_BCRYPT`)
* Anything else is invalid.
*
* @param string $hashedPasswd Password to validate
* @param int $size
*
* @return bool Indicates whether the given string is a valid hashed password
*
* @since 1.7.0
*/
public static function isHashedPassword($hashedPasswd)
{
return Tools::strlen($hashedPasswd) == 32 || Tools::strlen($hashedPasswd) == 60;
}

public static function isPasswdAdmin($passwd)


{
return Validate::isPlaintextPassword($passwd, Validate::ADMIN_PASSWORD_LENGTH);
}

/**
* Check for configuration key validity.
*
* @param string $config_name Configuration key to validate
*
* @return bool Validity is ok or not
*/
public static function isConfigName($config_name)
{
return preg_match('/^[a-zA-Z_0-9-]+$/', $config_name);
}

/**
* Check date formats like http://php.net/manual/en/function.date.php.
*
* @param string $date_format date format to check
*
* @return bool Validity is ok or not
*/
public static function isPhpDateFormat($date_format)
{
// We can't really check if this is valid or not, because this is a string and you can write
whatever you want in it.
// That's why only < et > are forbidden (HTML)
return preg_match('/^[^<>]+$/', $date_format);
}

/**
* Check for date format.
*
* @param string $date Date to validate
*
* @return bool Validity is ok or not
*/
public static function isDateFormat($date)
{
return (bool) preg_match('/^([0-9]{4})-((0?[0-9])|(1[0-2]))-((0?[0-9])|([1-2][0-9])|(3[01]))( [0-9]{2}:
[0-9]{2}:[0-9]{2})?$/', $date);
}

/**
* Check for date validity.
*
* @param string $date Date to validate
*
* @return bool Validity is ok or not
*/
public static function isDate($date)
{
if (!preg_match('/^([0-9]{4})-((?:0?[0-9])|(?:1[0-2]))-((?:0?[0-9])|(?:[1-2][0-9])|(?:3[01]))( [0-9]{2}:
[0-9]{2}:[0-9]{2})?$/', $date, $matches)) {
return false;
}

return checkdate((int) $matches[2], (int) $matches[3], (int) $matches[1]);


}

public static function isDateOrNull($date)


{
if (null === $date || $date === '0000-00-00 00:00:00' || $date === '0000-00-00') {
return true;
}

return self::isDate($date);
}

/**
* Check for birthDate validity. To avoid year in two digits, disallow date < 200 years ago
*
* @param string $date birthdate to validate
* @param string $format optional format
*
* @return bool Validity is ok or not
*/
public static function isBirthDate($date, $format = 'Y-m-d')
{
if (empty($date) || $date == '0000-00-00') {
return true;
}

$d = DateTime::createFromFormat($format, $date);
if (!empty(DateTime::getLastErrors()['warning_count']) || false === $d) {
return false;
}
$twoHundredYearsAgo = new Datetime();
$twoHundredYearsAgo->sub(new DateInterval('P200Y'));

return $d->setTime(0, 0, 0) <= new Datetime() && $d->setTime(0, 0, 0) >=


$twoHundredYearsAgo;
}

/**
* Check for boolean validity.
*
* @param bool $bool Boolean to validate
*
* @return bool Validity is ok or not
*/
public static function isBool($bool)
{
return $bool === null || is_bool($bool) || preg_match('/^(0|1)$/', $bool);
}

/**
* Check for phone number validity.
*
* @param string $number Phone number to validate
*
* @return bool Validity is ok or not
*/
public static function isPhoneNumber($number)
{
return preg_match('/^[+0-9. ()\/-]*$/', $number);
}

/**
* Check for barcode validity (EAN-13).
*
* @param string $ean13 Barcode to validate
*
* @return bool Validity is ok or not
*/
public static function isEan13($ean13)
{
return !$ean13 || preg_match('/^[0-9]{0,13}$/', $ean13);
}

/**
* Check for ISBN.
*
* @param string $isbn validate
*
* @return bool Validity is ok or not
*/
public static function isIsbn($isbn)
{
return !$isbn || preg_match('/^[0-9-]{0,32}$/', $isbn);
}

/**
* Check for barcode validity (UPC).
*
* @param string $upc Barcode to validate
*
* @return bool Validity is ok or not
*/
public static function isUpc($upc)
{
return !$upc || preg_match('/^[0-9]{0,12}$/', $upc);
}

/**
* Check for MPN validity.
*
* @param string $mpn to validate
*
* @return bool Validity is ok or not
*/
public static function isMpn($mpn)
{
return Tools::strlen($mpn) <= 40;
}

/**
* Check for postal code validity.
*
* @param string $postcode Postal code to validate
*
* @return bool Validity is ok or not
*/
public static function isPostCode($postcode)
{
return empty($postcode) || preg_match('/^[a-zA-Z 0-9-]+$/', $postcode);
}

/**
* Check for zip code format validity.
*
* @param string $zip_code zip code format to validate
*
* @return bool Validity is ok or not
*/
public static function isZipCodeFormat($zip_code)
{
if (!empty($zip_code)) {
return preg_match('/^[NLCnlc 0-9-]+$/', $zip_code);
}

return true;
}

/**
* Check for table or identifier validity
* Mostly used in database for ordering : ASC / DESC.
*
* @param string $way Keyword to validate
*
* @return int Validity is ok or not
*/
public static function isOrderWay($way)
{
return $way === 'ASC' | $way === 'DESC' | $way === 'asc' | $way === 'desc';
}

/**
* Check for table or identifier validity
* Mostly used in database for ordering : ORDER BY field.
*
* @param string $order Field to validate
*
* @return bool Validity is ok or not
*/
public static function isOrderBy($order)
{
return preg_match(static::ORDER_BY_REGEXP, $order);
}

/**
* Check for table or identifier validity
* Mostly used in database for table names and id_table.
*
* @param string $table Table/identifier to validate
*
* @return bool Validity is ok or not
*/
public static function isTableOrIdentifier($table)
{
return preg_match('/^[a-zA-Z0-9_-]+$/', $table);
}

/**
* Check for tags list validity.
*
* @param string $list List to validate
*
* @return bool Validity is ok or not
*/
public static function isTagsList($list)
{
return preg_match(Tools::cleanNonUnicodeSupport('/^[^!<>;?=+#"°{}_$%]*$/u'), $list);
}

/**
* Check for product visibility.
*
* @param string $s visibility to check
*
* @return bool Validity is ok or not
*/
public static function isProductVisibility($s)
{
return preg_match('/^both|catalog|search|none$/i', $s);
}

/**
* Check for an integer validity.
*
* @param int $value Integer to validate
*
* @return bool Validity is ok or not
*/
public static function isInt($value)
{
return (string) (int) $value === (string) $value || $value === false;
}

/**
* Check for an integer validity (unsigned).
*
* @param int $value Integer to validate
*
* @return bool Validity is ok or not
*/
public static function isUnsignedInt($value)
{
return (is_numeric($value) || is_string($value)) && (string) (int) $value === (string) $value &&
$value < 4294967296 && $value >= 0;
}

/**
* Check for an percentage validity (between 0 and 100).
*
* @param float $value Float to validate
*
* @return bool Validity is ok or not
*/
public static function isPercentage($value)
{
return Validate::isFloat($value) && $value >= 0 && $value <= 100;
}

/**
* Check for an integer validity (unsigned)
* Mostly used in database for auto-increment.
*
* @param int $id Integer to validate
*
* @return bool Validity is ok or not
*/
public static function isUnsignedId($id)
{
return Validate::isUnsignedInt($id); /* Because an id could be equal to zero when there is no
association */
}

public static function isNullOrUnsignedId($id)


{
return $id === null || Validate::isUnsignedId($id);
}

/**
* Check object validity.
*
* @param object $object Object to validate
*
* @return bool Validity is ok or not
*/
public static function isLoadedObject($object)
{
return is_object($object) && $object->id;
}

/**
* Check color validity.
*
* @param string $color Color to validate
*
* @return bool Validity is ok or not
*/
public static function isColor($color)
{
return preg_match('/^(#[0-9a-fA-F]{6}|[a-zA-Z0-9-]*)$/', $color);
}

/**
* Check url validity (disallowed empty string).
*
* @param string $url Url to validate
*
* @return bool Validity is ok or not
*/
public static function isUrl($url)
{
return preg_match(Tools::cleanNonUnicodeSupport('/^[~:#,$%&_=\(\)\.\? \+\-@\/a-zA-Z0-9\pL\
pS-]+$/u'), $url);
}

/**
* Check tracking number validity (disallowed empty string).
*
* @param string $tracking_number Tracking number to validate
*
* @return bool Validity is ok or not
*/
public static function isTrackingNumber($tracking_number)
{
return preg_match('/^[~:#,%&_=\(\)\[\]\.\? \+\-@\/a-zA-Z0-9]+$/', $tracking_number);
}

/**
* Check url validity (allowed empty string).
*
* @param string $url Url to validate
*
* @return bool Validity is ok or not
*/
public static function isUrlOrEmpty($url)
{
return empty($url) || Validate::isUrl($url);
}

/**
* Check if URL is absolute.
*
* @param string $url URL to validate
*
* @return bool Validity is ok or not
*/
public static function isAbsoluteUrl($url)
{
if (!empty($url)) {
return preg_match('/^(https?:)?\/\/[$~:;#,%&_=\(\)\[\]\.\? \+\-@\/a-zA-Z0-9]+$/', $url);
}

return true;
}
public static function isMySQLEngine($engine)
{
return in_array($engine, ['InnoDB', 'MyISAM']);
}

public static function isUnixName($data)


{
return preg_match(Tools::cleanNonUnicodeSupport('/^[a-z0-9\._-]+$/ui'), $data);
}

public static function isTablePrefix($data)


{
// Even if "-" is theorically allowed, it will be considered a syntax error if you do not add
backquotes (`) around the table name
return preg_match(Tools::cleanNonUnicodeSupport('/^[a-z0-9_]+$/ui'), $data);
}

/**
* Check for standard name file validity.
*
* @param string $name Name to validate
*
* @return bool Validity is ok or not
*/
public static function isFileName($name)
{
return preg_match('/^[a-zA-Z0-9_.-]+$/', $name);
}

/**
* Check for standard name directory validity.
*
* @param string $dir Directory to validate
*
* @return bool Validity is ok or not
*/
public static function isDirName($dir)
{
return (bool) preg_match('/^[a-zA-Z0-9_.-]*$/', $dir);
}

/**
* Check for admin panel tab name validity.
*
* @param string $name Name to validate
*
* @return bool Validity is ok or not
*/
public static function isTabName($name)
{
return preg_match(Tools::cleanNonUnicodeSupport('/^[^<>]+$/u'), $name);
}

public static function isWeightUnit($unit)


{
return Validate::isGenericName($unit) & (Tools::strlen($unit) < 5);
}

public static function isDistanceUnit($unit)


{
return Validate::isGenericName($unit) & (Tools::strlen($unit) < 5);
}

public static function isSubDomainName($domain)


{
return preg_match('/^[a-zA-Z0-9-_]*$/', $domain);
}

public static function isVoucherDescription($text)


{
return preg_match('/^([^<>{}]|<br \/>)*$/i', $text);
}

/**
* Check if the value is a sort direction value (DESC/ASC).
*
* @param string $value
*
* @return bool Validity is ok or not
*/
public static function isSortDirection($value)
{
return $value !== null && ($value === 'ASC' || $value === 'DESC');
}

/**
* Customization fields' label validity.
*
* @param string $label
*
* @return bool Validity is ok or not
*/
public static function isLabel($label)
{
return preg_match(Tools::cleanNonUnicodeSupport('/^[^{}<>]*$/u'), $label);
}

/**
* Price display method validity.
*
* @param int $data Data to validate
*
* @return bool Validity is ok or not
*/
public static function isPriceDisplayMethod($data)
{
return $data == PS_TAX_EXC || $data == PS_TAX_INC;
}

/**
* @param string $dni to validate
*
* @return bool
*/
public static function isDniLite($dni)
{
return empty($dni) || (bool) preg_match('/^[0-9A-Za-z-.]{1,16}$/U', $dni);
}

/**
* Check if $data is a PrestaShop cookie object.
*
* @param mixed $data to validate
*
* @return bool
*/
public static function isCookie($data)
{
return is_object($data) && get_class($data) == 'Cookie';
}

/**
* Check if $data is a string.
*
* @param string $data Data to validate
*
* @return bool Validity is ok or not
*/
public static function isString($data)
{
return is_string($data);
}

/**
* Check if the data is a reduction type (amout or percentage).
*
* @param string $data Data to validate
*
* @return bool Validity is ok or not
*/
public static function isReductionType($data)
{
return $data === 'amount' || $data === 'percentage';
}

/**
* Check for bool_id.
*
* @param string $ids
*
* @return bool Validity is ok or not
*/
public static function isBoolId($ids)
{
return (bool) preg_match('#^[01]_[0-9]+$#', $ids);
}

/**
* Check the localization pack part selected.
*
* @param string $data Localization pack to check
*
* @return bool Validity is ok or not
*/
public static function isLocalizationPackSelection($data)
{
return in_array((string) $data, ['states', 'taxes', 'currencies', 'languages', 'units', 'groups']);
}

/**
* Check for PHP serialized data.
*
* @param string $data Serialized data to validate
*
* @return bool Validity is ok or not
*/
public static function isSerializedArray($data)
{
return $data === null || (is_string($data) && preg_match('/^a:[0-9]+:{.*;}$/s', $data));
}

/**
* Check if $string is a valid JSON string.
*
* @param string $string JSON string to validate
*
* @return bool Validity is ok or not
*/
public static function isJson($string)
{
json_decode($string);

return json_last_error() == JSON_ERROR_NONE;


}

/**
* Check for Latitude/Longitude.
*
* @param string $data Coordinate to validate
*
* @return bool Validity is ok or not
*/
public static function isCoordinate($data)
{
return $data === null || preg_match('/^\-?[0-9]{1,8}\.[0-9]{1,8}$/s', $data);
}

/**
* Check for Language Iso Code.
*
* @param string $iso_code
*
* @return bool Validity is ok or not
*/
public static function isLangIsoCode($iso_code)
{
return (bool) preg_match('/^[a-zA-Z]{2,3}$/s', $iso_code);
}

/**
* Check for Language File Name.
*
* @param string $file_name
*
* @return bool Validity is ok or not
*/
public static function isLanguageFileName($file_name)
{
return (bool) preg_match('/^[a-zA-Z]{2,3}\.(?:gzip|tar\.gz)$/s', $file_name);
}

/**
* @param array $ids
*
* @return bool return true if the array contain only unsigned int value and not empty
*/
public static function isArrayWithIds($ids)
{
if (!is_array($ids) || count($ids) < 1) {
return false;
}

foreach ($ids as $id) {


if ($id == 0 || !Validate::isUnsignedInt($id)) {
return false;
}
}

return true;
}

/**
* @param array $stock_management
*
* @return bool return true if is a valide stock management
*/
public static function isStockManagement($stock_management)
{
if (!in_array($stock_management, ['WA', 'FIFO', 'LIFO'])) {
return false;
}

return true;
}

/**
* Validate SIRET Code.
*
* @param string $siret SIRET Code
*
* @return bool Return true if is valid
*/
public static function isSiret($siret)
{
if (Tools::strlen($siret) != 14) {
return false;
}
$sum = 0;
for ($i = 0; $i != 14; ++$i) {
$tmp = ((($i + 1) % 2) + 1) * (int) ($siret[$i]);
if ($tmp >= 10) {
$tmp -= 9;
}
$sum += $tmp;
}

return $sum % 10 === 0;


}

/**
* Validate APE Code.
*
* @param string $ape APE Code
*
* @return bool Return true if is valid
*/
public static function isApe($ape)
{
return (bool) preg_match('/^[0-9]{3,4}[a-zA-Z]{1}$/s', $ape);
}

public static function isControllerName($name)


{
return (bool) (is_string($name) && preg_match(Tools::cleanNonUnicodeSupport('/^[0-9a-zA-Z-
_]*$/u'), $name));
}

public static function isPrestaShopVersion($version)


{
return preg_match('/^[0-1]\.[0-9]{1,2}(\.[0-9]{1,2}){0,2}$/', $version) && ip2long($version);
}

public static function isOrderInvoiceNumber($id)


{
return preg_match('/^(?:' . Configuration::get('PS_INVOICE_PREFIX', Context::getContext()-
>language->id) . ')\s*([0-9]+)$/i', $id);
}

public static function isThemeName($theme_name)


{
return (bool) preg_match('/^[\w-]{3,255}$/u', $theme_name);
}

/**
* Check if enable_insecure_rsh exists in
* this PHP version otherwise disable the
* oProxyCommand option.
*
* @return bool
*/
public static function isValidImapUrl($imapUrl)
{
if (false === ini_get('imap.enable_insecure_rsh')) {
return preg_match('~^((?!oProxyCommand).)*$~i', $imapUrl);
}

return true;
}
}

Referencias
 Roger S. Pressman (2010). Ingeniera del software un enfoque práctico.
recuperado de:
https://miscursos.iuv.edu.mx/pluginfile.php/157904/mod_resource/content/
4/lecturaclase1_.pdf.

 Solmalo (2018). Desarrollo web . Recuperado de :


https://miscursos.iuv.edu.mx/mod/page/ view.php?id=144015

También podría gustarte