vendor/isotope/isotope-core/system/modules/isotope/library/Isotope/Model/Product.php line 336

Open in your IDE?
  1. <?php
  2. /*
  3.  * Isotope eCommerce for Contao Open Source CMS
  4.  *
  5.  * Copyright (C) 2009 - 2019 terminal42 gmbh & Isotope eCommerce Workgroup
  6.  *
  7.  * @link       https://isotopeecommerce.org
  8.  * @license    https://opensource.org/licenses/lgpl-3.0.html
  9.  */
  10. namespace Isotope\Model;
  11. use Contao\Database;
  12. use Contao\Date;
  13. use Contao\DcaExtractor;
  14. use Contao\Model;
  15. use Isotope\Interfaces\IsotopeProduct;
  16. use Isotope\Model\Attribute;
  17. use Isotope\RequestCache\Filter;
  18. use Model\Collection;
  19. /**
  20.  * The basic Isotope product model
  21.  *
  22.  * @property int    $id
  23.  * @property int    $pid
  24.  * @property int    $gid
  25.  * @property int    $tstamp
  26.  * @property string $language
  27.  * @property int    $dateAdded
  28.  * @property int    $type
  29.  * @property array  $pages
  30.  * @property array  $orderPages
  31.  * @property array  $inherit
  32.  * @property bool   $fallback
  33.  * @property string $alias
  34.  * @property string $sku
  35.  * @property string $name
  36.  * @property string $teaser
  37.  * @property string $description
  38.  * @property string $meta_title
  39.  * @property string $meta_description
  40.  * @property string $meta_keywords
  41.  * @property bool   $shipping_exempt
  42.  * @property array  $images
  43.  * @property bool   $protected
  44.  * @property array  $groups
  45.  * @property bool   $guests
  46.  * @property array  $cssID
  47.  * @property bool   $published
  48.  * @property string $start
  49.  * @property string $stop
  50.  */
  51. abstract class Product extends TypeAgent implements IsotopeProduct
  52. {
  53.     /**
  54.      * Table name
  55.      * @var string
  56.      */
  57.     protected static $strTable 'tl_iso_product';
  58.     /**
  59.      * Interface to validate attribute
  60.      * @var string
  61.      */
  62.     protected static $strInterface '\Isotope\Interfaces\IsotopeProduct';
  63.     /**
  64.      * List of types (classes) for this model
  65.      * @var array
  66.      */
  67.     protected static $arrModelTypes = array();
  68.     /**
  69.      * Currently active product (LIFO queue)
  70.      * @var array
  71.      */
  72.     protected static $arrActive = array();
  73.     /**
  74.      * Get categories (pages) assigned to this product
  75.      *
  76.      * @param bool $blnPublished Only return published categories (pages)
  77.      *
  78.      * @return array
  79.      */
  80.     abstract public function getCategories($blnPublished false);
  81.     /**
  82.      * Get product that is currently active (needed e.g. for insert tag replacement)
  83.      *
  84.      * @return IsotopeProduct|null
  85.      */
  86.     public static function getActive()
  87.     {
  88.         return === \count(static::$arrActive) ? null end(static::$arrActive);
  89.     }
  90.     /**
  91.      * Set product that is currently active (needed e.g. for insert tag replacement)
  92.      *
  93.      * @param IsotopeProduct $objProduct
  94.      */
  95.     public static function setActive(IsotopeProduct $objProduct)
  96.     {
  97.         static::$arrActive[] = $objProduct;
  98.     }
  99.     /**
  100.      * Unset product that is currently active (prevent later use of it)
  101.      */
  102.     public static function unsetActive()
  103.     {
  104.         array_pop(static::$arrActive);
  105.     }
  106.     /**
  107.      * Find all published products
  108.      *
  109.      * @param array $arrOptions
  110.      *
  111.      * @return Collection|Product[]|null
  112.      */
  113.     public static function findPublished(array $arrOptions = array())
  114.     {
  115.         return static::findPublishedBy(array(), array(), $arrOptions);
  116.     }
  117.     /**
  118.      * Find published products by condition
  119.      *
  120.      * @param mixed $arrColumns
  121.      * @param mixed $arrValues
  122.      * @param array $arrOptions
  123.      *
  124.      * @return Collection|Product[]|null
  125.      */
  126.     public static function findPublishedBy($arrColumns$arrValues, array $arrOptions = array())
  127.     {
  128.         $t = static::$strTable;
  129.         $arrValues = (array) $arrValues;
  130.         if (!\is_array($arrColumns)) {
  131.             $arrColumns = array(static::$strTable '.' $arrColumns '=?');
  132.         }
  133.         // Add publish check to $arrColumns as the first item to enable SQL keys
  134.         if (BE_USER_LOGGED_IN !== true) {
  135.             $time Date::floorToMinute();
  136.             array_unshift(
  137.                 $arrColumns,
  138.                 "$t.published='1' AND ($t.start='' OR $t.start<'$time') AND ($t.stop='' OR $t.stop>'" . ($time 60) . "')"
  139.             );
  140.         }
  141.         return static::findBy($arrColumns$arrValues$arrOptions);
  142.     }
  143.     /**
  144.      * Find a single product by primary key
  145.      *
  146.      * @param int   $intId
  147.      * @param array $arrOptions
  148.      *
  149.      * @return static|null
  150.      */
  151.     public static function findPublishedByPk($intId, array $arrOptions = array())
  152.     {
  153.         $arrOptions array_merge(
  154.             array(
  155.                 'return'    => 'Model'
  156.             ),
  157.             $arrOptions
  158.         );
  159.         return static::findPublishedBy(static::$strPk, (int) $intId$arrOptions);
  160.     }
  161.     /**
  162.      * Find a single product by its ID or alias
  163.      *
  164.      * @param mixed $varId      The ID or alias
  165.      * @param array $arrOptions An optional options array
  166.      *
  167.      * @return static|null      The model or null if the result is empty
  168.      */
  169.     public static function findPublishedByIdOrAlias($varId, array $arrOptions = array())
  170.     {
  171.         $t = static::$strTable;
  172.         $arrColumns = array("($t.id=? OR $t.alias=?)");
  173.         $arrValues  = array(is_numeric($varId) ? $varId 0$varId);
  174.         $arrOptions array_merge(
  175.             array(
  176.                 'limit'     => 1,
  177.                 'return'    => 'Model'
  178.             ),
  179.             $arrOptions
  180.         );
  181.         return static::findPublishedBy($arrColumns$arrValues$arrOptions);
  182.     }
  183.     /**
  184.      * Find products by IDs
  185.      *
  186.      * @param array $arrIds
  187.      * @param array $arrOptions
  188.      *
  189.      * @return Product[]|Collection
  190.      */
  191.     public static function findPublishedByIds(array $arrIds, array $arrOptions = array())
  192.     {
  193.         if (=== \count($arrIds)) {
  194.             return null;
  195.         }
  196.         return static::findPublishedBy(
  197.             array(static::$strTable '.id IN (' implode(','array_map('intval'$arrIds)) . ')'),
  198.             null,
  199.             $arrOptions
  200.         );
  201.     }
  202.     /**
  203.      * Return collection of published product variants by product PID
  204.      *
  205.      * @param int   $intPid
  206.      * @param array $arrOptions
  207.      *
  208.      * @return Collection|Product[]|null
  209.      */
  210.     public static function findPublishedByPid($intPid, array $arrOptions = array())
  211.     {
  212.         return static::findPublishedBy('pid', (int) $intPid$arrOptions);
  213.     }
  214.     /**
  215.      * Return collection of published products by categories
  216.      *
  217.      * @param array $arrCategories
  218.      * @param array $arrOptions
  219.      *
  220.      * @return Collection|Product[]|null
  221.      */
  222.     public static function findPublishedByCategories(array $arrCategories, array $arrOptions = array())
  223.     {
  224.         return static::findPublishedBy(
  225.             array('c.page_id IN (' implode(','array_map('intval'$arrCategories)) . ')'),
  226.             null,
  227.             $arrOptions
  228.         );
  229.     }
  230.     /**
  231.      * Find a single frontend-available product by primary key
  232.      *
  233.      * @param int   $intId
  234.      * @param array $arrOptions
  235.      *
  236.      * @return static|null
  237.      */
  238.     public static function findAvailableByPk($intId, array $arrOptions = array())
  239.     {
  240.         $objProduct = static::findPublishedByPk($intId$arrOptions);
  241.         if (null === $objProduct || !$objProduct->isAvailableInFrontend()) {
  242.             return null;
  243.         }
  244.         return $objProduct;
  245.     }
  246.     /**
  247.      * Find a single frontend-available product by its ID or alias
  248.      *
  249.      * @param mixed $varId      The ID or alias
  250.      * @param array $arrOptions An optional options array
  251.      *
  252.      * @return Product|null     The model or null if the result is empty
  253.      */
  254.     public static function findAvailableByIdOrAlias($varId, array $arrOptions = array())
  255.     {
  256.         $objProduct = static::findPublishedByIdOrAlias($varId$arrOptions);
  257.         if (null === $objProduct || !$objProduct->isAvailableInFrontend()) {
  258.             return null;
  259.         }
  260.         return $objProduct;
  261.     }
  262.     /**
  263.      * Find frontend-available products by IDs
  264.      *
  265.      * @param array $arrIds
  266.      * @param array $arrOptions
  267.      *
  268.      * @return Collection|Product[]|null
  269.      */
  270.     public static function findAvailableByIds(array $arrIds, array $arrOptions = array())
  271.     {
  272.         $objProducts = static::findPublishedByIds($arrIds$arrOptions);
  273.         if (null === $objProducts) {
  274.             return null;
  275.         }
  276.         $arrProducts = [];
  277.         foreach ($objProducts as $objProduct) {
  278.             if ($objProduct->isAvailableInFrontend()) {
  279.                 $arrProducts[] = $objProduct;
  280.             }
  281.         }
  282.         if (=== \count($arrProducts)) {
  283.             return null;
  284.         }
  285.         return new Collection($arrProducts, static::$strTable);
  286.     }
  287.     /**
  288.      * Find frontend-available products by condition
  289.      *
  290.      * @param mixed $arrColumns
  291.      * @param mixed $arrValues
  292.      * @param array $arrOptions
  293.      *
  294.      * @return Collection
  295.      */
  296.     public static function findAvailableBy($arrColumns$arrValues, array $arrOptions = array())
  297.     {
  298.         $objProducts = static::findPublishedBy($arrColumns$arrValues$arrOptions);
  299.         if (null === $objProducts) {
  300.             return null;
  301.         }
  302.         $arrProducts = [];
  303.         foreach ($objProducts as $objProduct) {
  304.             if ($objProduct->isAvailableInFrontend()) {
  305.                 $arrProducts[] = $objProduct;
  306.             }
  307.         }
  308.         if (=== \count($arrProducts)) {
  309.             return null;
  310.         }
  311.         return new Collection($arrProducts, static::$strTable);
  312.     }
  313.     /**
  314.      * Find variant of a product
  315.      *
  316.      * @param IsotopeProduct $objProduct
  317.      * @param array          $arrVariant
  318.      * @param array          $arrOptions
  319.      *
  320.      * @return Model|null
  321.      */
  322.     public static function findVariantOfProduct(
  323.         IsotopeProduct $objProduct,
  324.         array $arrVariant,
  325.         array $arrOptions = array()
  326.     ) {
  327.         $t = static::$strTable;
  328.         $arrColumns = array(
  329.             "$t.id IN (" implode(','$objProduct->getVariantIds()) . ')',
  330.             "$t." implode("=? AND $t."array_keys($arrVariant)) . '=?'
  331.         );
  332.         $arrOptions array_merge(
  333.             array(
  334.                  'limit'  => 1,
  335.                  'column' => $arrColumns,
  336.                  'value'  => $arrVariant,
  337.                  'return' => 'Model'
  338.             ),
  339.             $arrOptions
  340.         );
  341.         return static::find($arrOptions);
  342.     }
  343.     /**
  344.      * Finds the default variant of a product.
  345.      *
  346.      * @param IsotopeProduct $objProduct
  347.      * @param array          $arrOptions
  348.      *
  349.      * @return static|null
  350.      */
  351.     public static function findDefaultVariantOfProduct(IsotopeProduct $objProduct, array $arrOptions = array())
  352.     {
  353.         static $cache;
  354.         if (null === $cache) {
  355.             $cache = [];
  356.             $data  Database::getInstance()->execute(
  357.                 "SELECT id, pid FROM tl_iso_product WHERE pid>0 AND language='' AND fallback='1'"
  358.             );
  359.             while ($data->next()) {
  360.                 $cache[$data->pid] = $data->id;
  361.             }
  362.         }
  363.         $defaultId $cache[$objProduct->getProductId()];
  364.         if ($defaultId || !\in_array($defaultId$objProduct->getVariantIds())) {
  365.             return null;
  366.         }
  367.         return static::findByPk($defaultId$arrOptions);
  368.     }
  369.     /**
  370.      * Returns the number of published products.
  371.      *
  372.      * @param array $arrOptions
  373.      *
  374.      * @return int
  375.      */
  376.     public static function countPublished(array $arrOptions = array())
  377.     {
  378.         return static::countPublishedBy(array(), array(), $arrOptions);
  379.     }
  380.     /**
  381.      * Return the number of products matching certain criteria
  382.      *
  383.      * @param mixed $arrColumns
  384.      * @param mixed $arrValues
  385.      * @param array $arrOptions
  386.      *
  387.      * @return int
  388.      */
  389.     public static function countPublishedBy($arrColumns$arrValues, array $arrOptions = array())
  390.     {
  391.         $t = static::$strTable;
  392.         $arrValues = (array) $arrValues;
  393.         if (!\is_array($arrColumns)) {
  394.             $arrColumns = array(static::$strTable '.' $arrColumns '=?');
  395.         }
  396.         // Add publish check to $arrColumns as the first item to enable SQL keys
  397.         if (BE_USER_LOGGED_IN !== true) {
  398.             $time Date::floorToMinute();
  399.             array_unshift(
  400.                 $arrColumns,
  401.                 "
  402.                     $t.published='1'
  403.                     AND ($t.start='' OR $t.start<'$time')
  404.                     AND ($t.stop='' OR $t.stop>'" . ($time 60) . "')
  405.                 "
  406.             );
  407.         }
  408.         return static::countBy($arrColumns$arrValues$arrOptions);
  409.     }
  410.     /**
  411.      * Gets the number of translation records in the product table.
  412.      * Mostly useful to see if there are any translations at all to optimize queries.
  413.      *
  414.      * @return int
  415.      */
  416.     public static function countTranslatedProducts()
  417.     {
  418.         static $result;
  419.         if (null === $result) {
  420.             $result Database::getInstance()->query(
  421.                 "SELECT COUNT(*) AS total FROM tl_iso_product WHERE language!=''"
  422.             )->total;
  423.         }
  424.         return $result;
  425.     }
  426.     /**
  427.      * Return a model or collection based on the database result type
  428.      *
  429.      * @param array $arrOptions
  430.      *
  431.      * @return Product|Product[]|Collection|null
  432.      */
  433.     protected static function find(array $arrOptions)
  434.     {
  435.         $arrOptions['group'] = static::getTable() . '.id' . (null === ($arrOptions['group'] ?? null) ? '' ', '.$arrOptions['group']);
  436.         $objProducts parent::find($arrOptions);
  437.         if (null === $objProducts) {
  438.             return null;
  439.         }
  440.         /** @var Filter[] $arrFilters */
  441.         $arrFilters $arrOptions['filters'] ?? null;
  442.         $arrSorting $arrOptions['sorting'] ?? null;
  443.         $hasFilters \is_array($arrFilters) && !== \count($arrFilters);
  444.         $hasSorting \is_array($arrSorting) && !== \count($arrSorting);
  445.         if ($hasFilters || $hasSorting) {
  446.             /** @var static[] $arrProducts */
  447.             $arrProducts $objProducts->getModels();
  448.             if ($hasFilters) {
  449.                 $arrProducts array_filter($arrProducts, function ($objProduct) use ($arrFilters) {
  450.                     $arrGroups = [];
  451.                     foreach ($arrFilters as $objFilter) {
  452.                         $blnMatch $objFilter->matches($objProduct);
  453.                         if ($objFilter->hasGroup()) {
  454.                             $arrGroups[$objFilter->getGroup()] = $arrGroups[$objFilter->getGroup()] ? : $blnMatch;
  455.                         } elseif (!$blnMatch) {
  456.                             return false;
  457.                         }
  458.                     }
  459.                     return !\in_array(false$arrGroupstrue);
  460.                 });
  461.             }
  462.             // $arrProducts can be empty if the filter removed all records
  463.             if ($hasSorting && !== \count($arrProducts)) {
  464.                 $arrParam = array();
  465.                 $arrData  = array();
  466.                 foreach ($arrSorting as $strField => $arrConfig) {
  467.                     foreach ($arrProducts as $objProduct) {
  468.                         // Both SORT_STRING and SORT_REGULAR are case sensitive, strings starting with a capital letter
  469.                         // will come before strings starting with a lowercase letter. To perform a case insensitive
  470.                         // search, force the sorting order to be determined by a lowercase copy of the original value.
  471.                         // Temporary fix for price attribute (see #945)
  472.                         if ('price' === $strField) {
  473.                             if (null !== $objProduct->getPrice()) {
  474.                                 $arrData[$strField][$objProduct->id] = $objProduct->getPrice()->getAmount();
  475.                             } else {
  476.                                 $arrData[$strField][$objProduct->id] = 0;
  477.                             }
  478.                         } else {
  479.                             $arrData[$strField][$objProduct->id] = strtolower(
  480.                                 str_replace('"'''$objProduct->$strField)
  481.                             );
  482.                         }
  483.                     }
  484.                     $arrParam[] = &$arrData[$strField];
  485.                     $arrParam[] = $arrConfig[0];
  486.                     $arrParam[] = $arrConfig[1];
  487.                 }
  488.                 // Add product array as the last item.
  489.                 // This will sort the products array based on the sorting of the passed in arguments.
  490.                 $arrParam[] = &$arrProducts;
  491.                 \call_user_func_array('array_multisort'$arrParam);
  492.             }
  493.             $objProducts = new Collection($arrProducts, static::$strTable);
  494.         }
  495.         return $objProducts;
  496.     }
  497.     /**
  498.      * Return select statement to load product data including multilingual fields
  499.      *
  500.      * @param array $arrOptions an array of columns
  501.      *
  502.      * @return string
  503.      */
  504.     protected static function buildFindQuery(array $arrOptions)
  505.     {
  506.         $objBase         DcaExtractor::getInstance($arrOptions['table']);
  507.         $hasTranslations = (static::countTranslatedProducts() > 0);
  508.         $hasVariants     = (ProductType::countByVariants() > 0);
  509.         $arrJoins  = array();
  510.         $arrFields = array(
  511.             $arrOptions['table'] . '.*',
  512.             "'" str_replace('-''_'$GLOBALS['TL_LANGUAGE']) . "' AS language",
  513.         );
  514.         if ($hasVariants) {
  515.             $arrFields[] = sprintf(
  516.                 'IF(%s.pid>0, parent.type, %s.type) AS type',
  517.                 $arrOptions['table'],
  518.                 $arrOptions['table']
  519.             );
  520.         }
  521.         if ($hasTranslations) {
  522.             foreach (Attribute::getMultilingualFields() as $attribute) {
  523.                 $arrFields[] = "IFNULL(translation.$attribute, " $arrOptions['table'] . ".$attribute) AS $attribute";
  524.             }
  525.         }
  526.         foreach (Attribute::getFetchFallbackFields() as $attribute) {
  527.             $arrFields[] = "{$arrOptions['table']}.$attribute AS {$attribute}_fallback";
  528.         }
  529.         if ($hasTranslations) {
  530.             $arrJoins[] = sprintf(
  531.                 " LEFT OUTER JOIN %s translation ON %s.id=translation.pid AND translation.language='%s'",
  532.                 $arrOptions['table'],
  533.                 $arrOptions['table'],
  534.                 str_replace('-''_'$GLOBALS['TL_LANGUAGE'])
  535.             );
  536.             $arrOptions['group'] = (null === $arrOptions['group'] ? '' $arrOptions['group'].', ') . 'translation.id';
  537.         }
  538.         if ($hasVariants) {
  539.             $arrJoins[] = sprintf(
  540.                 ' LEFT OUTER JOIN %s parent ON %s.pid=parent.id',
  541.                 $arrOptions['table'],
  542.                 $arrOptions['table']
  543.             );
  544.         }
  545.         $arrFields[] = 'GROUP_CONCAT(c.page_id) AS product_categories';
  546.         $arrJoins[] = sprintf(
  547.             ' LEFT OUTER JOIN %s c ON %s=c.pid',
  548.             ProductCategory::getTable(),
  549.             ($hasVariants "IFNULL(parent.id, {$arrOptions['table']}.id)" "{$arrOptions['table']}.id")
  550.         );
  551.         if ('c.sorting' === ($arrOptions['order'] ?? '')) {
  552.             $arrFields[] = 'c.sorting';
  553.             $arrOptions['group'] = (null === $arrOptions['group'] ? '' $arrOptions['group'].', ') . 'c.id';
  554.         }
  555.         if ($objBase->hasRelations()) {
  556.             $intCount 0;
  557.             foreach ($objBase->getRelations() as $strKey => $arrConfig) {
  558.                 // Automatically join the single-relation records
  559.                 if (('eager' === $arrConfig['load'] || ($arrOptions['eager'] ?? false))
  560.                     && ('hasOne' === $arrConfig['type'] || 'belongsTo' === $arrConfig['type'])
  561.                 ) {
  562.                     if (\is_array($arrOptions['joinAliases'] ?? null)
  563.                         && ($key array_search($arrConfig['table'], $arrOptions['joinAliases'], true)) !== false
  564.                     ) {
  565.                         $strJoinAlias $key;
  566.                         unset($arrOptions['joinAliases'][$key]);
  567.                     } else {
  568.                         ++$intCount;
  569.                         $strJoinAlias 'j' $intCount;
  570.                     }
  571.                     $objRelated DcaExtractor::getInstance($arrConfig['table']);
  572.                     foreach ($objRelated->getFields() as $strField => $config) {
  573.                         $arrFields[] = $strJoinAlias '.' $strField ' AS ' $strKey '__' $strField;
  574.                     }
  575.                     $arrJoins[] = sprintf(
  576.                         ' LEFT JOIN %s %s ON %s.%s=%s.id',
  577.                         $arrConfig['table'],
  578.                         $strJoinAlias,
  579.                         $arrOptions['table'],
  580.                         $strKey,
  581.                         $strJoinAlias
  582.                     );
  583.                 }
  584.             }
  585.         }
  586.         // Generate the query
  587.         $strQuery 'SELECT ' implode(', '$arrFields) . ' FROM ' $arrOptions['table'] . implode(''$arrJoins);
  588.         // Where condition
  589.         if (!\is_array($arrOptions['column'] ?? null)) {
  590.             $arrOptions['column'] = array($arrOptions['table'] . '.' $arrOptions['column'] . '=?');
  591.         }
  592.         // The model must never find a language record
  593.         $strQuery .= " WHERE {$arrOptions['table']}.language='' AND " implode(' AND '$arrOptions['column']);
  594.         // Group by
  595.         if (($arrOptions['group'] ?? null) !== null) {
  596.             $strQuery .= ' GROUP BY ' $arrOptions['group'];
  597.         }
  598.         // Order by
  599.         if (($arrOptions['order'] ?? null) !== null) {
  600.             $strQuery .= ' ORDER BY ' $arrOptions['order'];
  601.         }
  602.         return $strQuery;
  603.     }
  604.     /**
  605.      * Build a query based on the given options to count the number of products.
  606.      *
  607.      * @param array $arrOptions The options array
  608.      *
  609.      * @return string
  610.      */
  611.     protected static function buildCountQuery(array $arrOptions)
  612.     {
  613.         $hasTranslations = (static::countTranslatedProducts() > 0);
  614.         $hasVariants     = (ProductType::countByVariants() > 0);
  615.         $arrJoins  = array();
  616.         $arrFields = array(
  617.             $arrOptions['table'] . '.id',
  618.             "'" str_replace('-''_'$GLOBALS['TL_LANGUAGE']) . "' AS language",
  619.         );
  620.         if ($hasVariants) {
  621.             $arrFields[] = sprintf(
  622.                 'IF(%s.pid>0, parent.type, %s.type) AS type',
  623.                 $arrOptions['table'],
  624.                 $arrOptions['table']
  625.             );
  626.         }
  627.         if ($hasTranslations) {
  628.             foreach (Attribute::getMultilingualFields() as $attribute) {
  629.                 $arrFields[] = "IFNULL(translation.$attribute, " $arrOptions['table'] . ".$attribute) AS $attribute";
  630.             }
  631.         }
  632.         if ($hasTranslations) {
  633.             $arrJoins[] = sprintf(
  634.                 " LEFT OUTER JOIN %s translation ON %s.id=translation.pid AND translation.language='%s'",
  635.                 $arrOptions['table'],
  636.                 $arrOptions['table'],
  637.                 str_replace('-''_'$GLOBALS['TL_LANGUAGE'])
  638.             );
  639.             $arrOptions['group'] = (null === $arrOptions['group'] ? '' $arrOptions['group'].', ') . 'translation.id, tl_iso_product.id';
  640.         }
  641.         if ($hasVariants) {
  642.             $arrJoins[] = sprintf(
  643.                 ' LEFT OUTER JOIN %s parent ON %s.pid=parent.id',
  644.                 $arrOptions['table'],
  645.                 $arrOptions['table']
  646.             );
  647.         }
  648.         $arrJoins[] = sprintf(
  649.             ' LEFT OUTER JOIN %s c ON %s=c.pid',
  650.             ProductCategory::getTable(),
  651.             ($hasVariants "IFNULL(parent.id, {$arrOptions['table']}.id)" "{$arrOptions['table']}.id")
  652.         );
  653.         // Generate the query
  654.         $strWhere '';
  655.         $strQuery '
  656.             SELECT
  657.                 ' implode(', '$arrFields) . '
  658.             FROM ' $arrOptions['table'] . implode(''$arrJoins);
  659.         // Where condition
  660.         if (!empty($arrOptions['column'])) {
  661.             if (!\is_array($arrOptions['column'])) {
  662.                 $arrOptions['column'] = array($arrOptions['table'] . '.' $arrOptions['column'] . '=?');
  663.             }
  664.             $strWhere ' AND ' implode(' AND '$arrOptions['column']);
  665.         }
  666.         // The model must never find a language record
  667.         $strQuery .= " WHERE {$arrOptions['table']}.language=''" $strWhere;
  668.         // Group by
  669.         if ($arrOptions['group'] !== null) {
  670.             $strQuery .= ' GROUP BY ' $arrOptions['group'];
  671.         }
  672.         return 'SELECT COUNT(*) AS count FROM ('.$strQuery.') c1';
  673.     }
  674.     /**
  675.      * Return select statement to load product data including multilingual fields
  676.      *
  677.      * @param array $arrOptions     an array of columns
  678.      * @param array $arrJoinAliases an array of table join aliases
  679.      *
  680.      * @return string
  681.      *
  682.      * @deprecated  use buildFindQuery introduced in Contao 3.3
  683.      */
  684.     protected static function buildQueryString($arrOptions$arrJoinAliases = array('t' => 'tl_iso_producttype'))
  685.     {
  686.         $arrOptions['joinAliases'] = $arrJoinAliases;
  687.         return static::buildFindQuery((array) $arrOptions);
  688.     }
  689. }