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

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()] ?? null;
  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.                             continue;
  479.                         }
  480.                         if(
  481.                             $objProduct->hasVariants()
  482.                             && !$objProduct->isVariant()
  483.                             && \in_array($strField$objProduct->getType()->getVariantAttributes(), true)
  484.                             && ($defaultVariant Product::findDefaultVariantOfProduct($objProduct))
  485.                         ) {
  486.                             $arrData[$strField][$objProduct->id] = strtolower(
  487.                                 str_replace('"'''$defaultVariant->$strField)
  488.                             );
  489.                             continue;
  490.                         }
  491.                         $arrData[$strField][$objProduct->id] = strtolower(
  492.                             str_replace('"'''$objProduct->$strField)
  493.                         );
  494.                     }
  495.                     $arrParam[] = &$arrData[$strField];
  496.                     $arrParam[] = $arrConfig[0];
  497.                     $arrParam[] = $arrConfig[1];
  498.                 }
  499.                 // Add product array as the last item.
  500.                 // This will sort the products array based on the sorting of the passed in arguments.
  501.                 $arrParam[] = &$arrProducts;
  502.                 array_multisort(...$arrParam);
  503.             }
  504.             $objProducts = new Collection($arrProducts, static::$strTable);
  505.         }
  506.         return $objProducts;
  507.     }
  508.     /**
  509.      * Return select statement to load product data including multilingual fields
  510.      *
  511.      * @param array $arrOptions an array of columns
  512.      *
  513.      * @return string
  514.      */
  515.     protected static function buildFindQuery(array $arrOptions)
  516.     {
  517.         $objBase         DcaExtractor::getInstance($arrOptions['table']);
  518.         $hasTranslations = (static::countTranslatedProducts() > 0);
  519.         $hasVariants     = (ProductType::countByVariants() > 0);
  520.         $arrJoins  = array();
  521.         $arrFields = array(
  522.             $arrOptions['table'] . '.*',
  523.             "'" str_replace('-''_'$GLOBALS['TL_LANGUAGE']) . "' AS language",
  524.         );
  525.         if ($hasVariants) {
  526.             $arrFields[] = sprintf(
  527.                 'IF(%s.pid>0, parent.type, %s.type) AS type',
  528.                 $arrOptions['table'],
  529.                 $arrOptions['table']
  530.             );
  531.         }
  532.         if ($hasTranslations) {
  533.             foreach (Attribute::getMultilingualFields() as $attribute) {
  534.                 $arrFields[] = "IFNULL(translation.$attribute, " $arrOptions['table'] . ".$attribute) AS $attribute";
  535.             }
  536.         }
  537.         foreach (Attribute::getFetchFallbackFields() as $attribute) {
  538.             $arrFields[] = "{$arrOptions['table']}.$attribute AS {$attribute}_fallback";
  539.         }
  540.         if ($hasTranslations) {
  541.             $arrJoins[] = sprintf(
  542.                 " LEFT OUTER JOIN %s translation ON %s.id=translation.pid AND translation.language='%s'",
  543.                 $arrOptions['table'],
  544.                 $arrOptions['table'],
  545.                 str_replace('-''_'$GLOBALS['TL_LANGUAGE'])
  546.             );
  547.             $arrOptions['group'] = (empty($arrOptions['group']) ? '' $arrOptions['group'].', ') . 'translation.id';
  548.         }
  549.         if ($hasVariants) {
  550.             $arrJoins[] = sprintf(
  551.                 ' LEFT OUTER JOIN %s parent ON %s.pid=parent.id',
  552.                 $arrOptions['table'],
  553.                 $arrOptions['table']
  554.             );
  555.         }
  556.         $arrFields[] = 'GROUP_CONCAT(c.page_id) AS product_categories';
  557.         $arrJoins[] = sprintf(
  558.             ' LEFT OUTER JOIN %s c ON %s=c.pid',
  559.             ProductCategory::getTable(),
  560.             ($hasVariants "IFNULL(parent.id, {$arrOptions['table']}.id)" "{$arrOptions['table']}.id")
  561.         );
  562.         if ('c.sorting' === ($arrOptions['order'] ?? '')) {
  563.             $arrFields[] = 'c.sorting';
  564.             $arrOptions['group'] = (null === $arrOptions['group'] ? '' $arrOptions['group'].', ') . 'c.id';
  565.         }
  566.         if ($objBase->hasRelations()) {
  567.             $intCount 0;
  568.             foreach ($objBase->getRelations() as $strKey => $arrConfig) {
  569.                 // Automatically join the single-relation records
  570.                 if (('eager' === $arrConfig['load'] || ($arrOptions['eager'] ?? false))
  571.                     && ('hasOne' === $arrConfig['type'] || 'belongsTo' === $arrConfig['type'])
  572.                 ) {
  573.                     if (\is_array($arrOptions['joinAliases'] ?? null)
  574.                         && ($key array_search($arrConfig['table'], $arrOptions['joinAliases'], true)) !== false
  575.                     ) {
  576.                         $strJoinAlias $key;
  577.                         unset($arrOptions['joinAliases'][$key]);
  578.                     } else {
  579.                         ++$intCount;
  580.                         $strJoinAlias 'j' $intCount;
  581.                     }
  582.                     $objRelated DcaExtractor::getInstance($arrConfig['table']);
  583.                     foreach ($objRelated->getFields() as $strField => $config) {
  584.                         $arrFields[] = $strJoinAlias '.' $strField ' AS ' $strKey '__' $strField;
  585.                     }
  586.                     $arrJoins[] = sprintf(
  587.                         ' LEFT JOIN %s %s ON %s.%s=%s.id',
  588.                         $arrConfig['table'],
  589.                         $strJoinAlias,
  590.                         $arrOptions['table'],
  591.                         $strKey,
  592.                         $strJoinAlias
  593.                     );
  594.                 }
  595.             }
  596.         }
  597.         // Generate the query
  598.         $strQuery 'SELECT ' implode(', '$arrFields) . ' FROM ' $arrOptions['table'] . implode(''$arrJoins);
  599.         // Where condition
  600.         if (!\is_array($arrOptions['column'] ?? null)) {
  601.             $arrOptions['column'] = array($arrOptions['table'] . '.' $arrOptions['column'] . '=?');
  602.         }
  603.         // The model must never find a language record
  604.         $strQuery .= " WHERE {$arrOptions['table']}.language='' AND " implode(' AND '$arrOptions['column']);
  605.         // Group by
  606.         if (($arrOptions['group'] ?? null) !== null) {
  607.             $strQuery .= ' GROUP BY ' $arrOptions['group'];
  608.         }
  609.         // Order by
  610.         if (($arrOptions['order'] ?? null) !== null) {
  611.             $strQuery .= ' ORDER BY ' $arrOptions['order'];
  612.         }
  613.         return $strQuery;
  614.     }
  615.     /**
  616.      * Build a query based on the given options to count the number of products.
  617.      *
  618.      * @param array $arrOptions The options array
  619.      *
  620.      * @return string
  621.      */
  622.     protected static function buildCountQuery(array $arrOptions)
  623.     {
  624.         $hasTranslations = (static::countTranslatedProducts() > 0);
  625.         $hasVariants     = (ProductType::countByVariants() > 0);
  626.         $arrJoins  = array();
  627.         $arrFields = array(
  628.             $arrOptions['table'] . '.id',
  629.             "'" str_replace('-''_'$GLOBALS['TL_LANGUAGE']) . "' AS language",
  630.         );
  631.         if ($hasVariants) {
  632.             $arrFields[] = sprintf(
  633.                 'IF(%s.pid>0, parent.type, %s.type) AS type',
  634.                 $arrOptions['table'],
  635.                 $arrOptions['table']
  636.             );
  637.         }
  638.         if ($hasTranslations) {
  639.             foreach (Attribute::getMultilingualFields() as $attribute) {
  640.                 $arrFields[] = "IFNULL(translation.$attribute, " $arrOptions['table'] . ".$attribute) AS $attribute";
  641.             }
  642.         }
  643.         if ($hasTranslations) {
  644.             $arrJoins[] = sprintf(
  645.                 " LEFT OUTER JOIN %s translation ON %s.id=translation.pid AND translation.language='%s'",
  646.                 $arrOptions['table'],
  647.                 $arrOptions['table'],
  648.                 str_replace('-''_'$GLOBALS['TL_LANGUAGE'])
  649.             );
  650.             $arrOptions['group'] = (!empty($arrOptions['group']) ? $arrOptions['group'].', ' '') . 'translation.id, tl_iso_product.id';
  651.         }
  652.         if ($hasVariants) {
  653.             $arrJoins[] = sprintf(
  654.                 ' LEFT OUTER JOIN %s parent ON %s.pid=parent.id',
  655.                 $arrOptions['table'],
  656.                 $arrOptions['table']
  657.             );
  658.         }
  659.         $arrJoins[] = sprintf(
  660.             ' LEFT OUTER JOIN %s c ON %s=c.pid',
  661.             ProductCategory::getTable(),
  662.             ($hasVariants "IFNULL(parent.id, {$arrOptions['table']}.id)" "{$arrOptions['table']}.id")
  663.         );
  664.         // Generate the query
  665.         $strWhere '';
  666.         $strQuery '
  667.             SELECT
  668.                 ' implode(', '$arrFields) . '
  669.             FROM ' $arrOptions['table'] . implode(''$arrJoins);
  670.         // Where condition
  671.         if (!empty($arrOptions['column'])) {
  672.             if (!\is_array($arrOptions['column'])) {
  673.                 $arrOptions['column'] = array($arrOptions['table'] . '.' $arrOptions['column'] . '=?');
  674.             }
  675.             $strWhere ' AND ' implode(' AND '$arrOptions['column']);
  676.         }
  677.         // The model must never find a language record
  678.         $strQuery .= " WHERE {$arrOptions['table']}.language=''" $strWhere;
  679.         // Group by
  680.         if ($arrOptions['group'] !== null) {
  681.             $strQuery .= ' GROUP BY ' $arrOptions['group'];
  682.         }
  683.         return 'SELECT COUNT(*) AS count FROM ('.$strQuery.') c1';
  684.     }
  685.     /**
  686.      * Return select statement to load product data including multilingual fields
  687.      *
  688.      * @param array $arrOptions     an array of columns
  689.      * @param array $arrJoinAliases an array of table join aliases
  690.      *
  691.      * @return string
  692.      *
  693.      * @deprecated  use buildFindQuery introduced in Contao 3.3
  694.      */
  695.     protected static function buildQueryString($arrOptions$arrJoinAliases = array('t' => 'tl_iso_producttype'))
  696.     {
  697.         $arrOptions['joinAliases'] = $arrJoinAliases;
  698.         return static::buildFindQuery((array) $arrOptions);
  699.     }
  700. }