This method applies the conditionals which are common across the get_*()
methods and the count() method.

@param array $aData Data passed from the calling method

@return void
437210
protected function getCountCommon(array $aData = [])
    {
        //  If there's a search term, then we better get %LIKING%
        if (!empty($aData['keywords'])) {

            if (empty($aData['or_like'])) {
                $aData['or_like'] = [];
            }

            $iKeywordAsId = (int) preg_replace('/[^0-9]/', '', $aData['keywords']);

            if ($iKeywordAsId) {
                $aData['or_like'][] = [
                    'column' => $this->getTableAlias() . '.id',
                    'value'  => $iKeywordAsId,
                ];
            }

            $aData['or_like'][] = [
                'column' => $this->getTableAlias() . '.label',
                'value'  => $aData['keywords'],
            ];
        }

        // --------------------------------------------------------------------------

        //  Let the parent method handle sorting, etc
        parent::getCountCommon($aData);
    }
145482
protected function setDataUsers(array &$aData, bool $bSetCreated = true): Base
    {
        if ($this->isAutoSetUsers()) {
            if (isLoggedIn()) {
                if ($bSetCreated && empty($aData[$this->tableCreatedByColumn])) {
                    $aData[$this->tableCreatedByColumn] = activeUser('id');
                }
                if (empty($aData[$this->tableModifiedByColumn])) {
                    $aData[$this->tableModifiedByColumn] = activeUser('id');
                }
            } else {
                if ($bSetCreated && empty($aData[$this->tableCreatedByColumn])) {
                    $aData[$this->tableCreatedByColumn] = null;
                }
                if (empty($aData[$this->tableModifiedByColumn])) {
                    $aData[$this->tableModifiedByColumn] = null;
                }
            }
        }

        return $this;
    }
964429
public function submit(array $inputData = [])
    {
        $this->validated = false;
        $this->isSubmitted = true;
        $this->isValid = true;

        /** @var ElementInterface $element */
        foreach ($this->elements() as $name => $element) {
            if ($element->isDisabled()) {
                continue;
            }
            $value = $inputData[$name] ?? '';
            $this->rawData[$name] = $value;
            $value = $this->filter($name, $value);
            $this->data[$name] = $value;
            $element->withValue($value);

            // only validate elements which are not disabled.
            if (! $this->validate($name, $element->value())) {
                $this->isValid = false;
            }
        }
    }
329060
protected function _findInArray( array $aData, $mKey, $sDefault = '' )
    {
        $sVal = $sDefault;

        if ( array_key_exists( $mKey, $aData ) and is_scalar( $aData[$mKey] ) ) {
            $sVal = trim( (string) $aData[$mKey] );
        }

        return $sVal;
    }
922535
protected function autoSaveExpandableFieldsExtract(array &$aData)
    {
        $aFields           = [];
        $aOut              = [];
        $aExpandableFields = $this->getExpandableFields();

        foreach ($aExpandableFields as $oField) {
            if ($oField->auto_save) {
                $aFields[$oField->trigger] = $oField;
            }
        }

        foreach ($aData as $sKey => $mValue) {
            if (array_key_exists($sKey, $aFields)) {
                $aOut[$sKey]       = $aFields[$sKey];
                $aOut[$sKey]->data = $mValue;
                unset($aData[$sKey]);
            }
        }

        return $aOut;
    }
349261
private function _getModuleIdsFromExtensions($aData)
    {
        $aModuleIds = [];
        $oModule = $this->getModule();
        foreach ($aData as $aModule) {
            foreach ($aModule as $sFilePath) {
                $sModuleId = $oModule->getIdByPath($sFilePath);
                $aModuleIds[] = $sModuleId;
            }
        }

        return $aModuleIds;
    }
1187589
public function getCountCommonBuckets($aData = [])
    {
        if (!empty($aData['keywords'])) {
            if (empty($aData['or_like'])) {
                $aData['or_like'] = [];
            }
            $aData['or_like'][] = [
                'column' => 'b.label',
                'value'  => $aData['keywords'],
            ];
        }

        $this->getCountCommon($aData);
    }
624744
protected function setDataToken(array &$aData, $bIsCreate = true): Base
    {
        if ($bIsCreate && $this->isAutoSetTokens() && empty($aData[$this->tableTokenColumn])) {
            if (empty($this->tableTokenColumn)) {
                throw new ModelException(static::class . '::create() Token column variable not set', 1);
            }
            $aData[$this->tableTokenColumn] = $this->generateToken();
        } elseif (!$bIsCreate) {
            //  Automatically set tokens are permanent and immutable
            if ($this->isAutoSetTokens() && !empty($aData[$this->tableTokenColumn])) {
                unset($aData[$this->tableTokenColumn]);
            }
        }

        return $this;
    }
1199353
public function update($iId, array $aData = []): bool
    {
        $sAlias = $this->getTableAlias(true);
        $sTable = $this->getTableName(true);
        $oDb    = Factory::service('Database');

        // --------------------------------------------------------------------------

        //  Execute the data pre-write hooks
        $this
            ->prepareUpdateData($aData)
            ->prepareWriteData($aData);

        // --------------------------------------------------------------------------

        //  If there are any expandable fields which should automatically save,
        //  then separate them out now
        $aAutoSaveExpandableFields = $this->autoSaveExpandableFieldsExtract($aData);

        // --------------------------------------------------------------------------

        $this
            ->setDataTimestamps($aData, false)
            ->setDataUsers($aData, false)
            ->setDataSlug($aData, false, $iId)
            ->setDataToken($aData, false);

        // --------------------------------------------------------------------------

        if ($this->saveToDb($aData, $iId)) {

            //  Save any expandable objects
            $this->autoSaveExpandableFieldsSave($iId, $aAutoSaveExpandableFields);

            //  Clear the cache
            foreach ($this->aCacheColumns as $sColumn) {
                $sKey = strtoupper($sColumn) . ':' . json_encode($iId);
                $this->unsetCachePrefix($sKey);
            }

            $this->triggerEvent(static::EVENT_UPDATED, [$iId]);

            return true;

        } else {
            return false;
        }
    }
705742
public function UserQuery($SafeData = FALSE) {
      if ($SafeData) {
         $this->SQL->Select('u.UserID, u.Name, u.Photo, u.About, u.Gender, u.CountVisits, u.InviteUserID, u.DateFirstVisit, u.DateLastActive, u.DateInserted, u.DateUpdated, u.Score, u.Admin, u.Deleted, u.CountDiscussions, u.CountComments');
      } else {
         $this->SQL->Select('u.*');
      }
      $this->SQL->From('User u');
//      $this->SQL->Select('i.Name', '', 'InviteName')
//         ->From('User u')
//         ->Join('User as i', 'u.InviteUserID = i.UserID', 'left');
   }
137950
protected function getCountCommon(array $aData = [])
    {
        if (empty($aData['select'])) {
            $aData['select'] = [
                $this->getTableAlias() . '.*',
                'ea.ref email_ref',
            ];
        }

        //  Common joins
        $oDb = Factory::service('Database');
        $oDb->join(NAILS_DB_PREFIX . 'email_archive ea', $this->getTableAlias() . '.email_id = ea.id', 'LEFT');

        parent::getCountCommon($aData);
    }
238380
protected function applySearchConditionals(array &$aData, $sKeywords)
    {
        if (empty($aData['or_like'])) {
            $aData['or_like'] = [];
        }

        $sAlias = $this->getTableAlias(true);

        foreach ($this->searchableFields as $mField) {

            //  If the field is an array then search across the columns concatenated together
            if (is_array($mField)) {

                $sMappedFields = array_map(function ($sInput) use ($sAlias) {
                    if (strpos($sInput, '.') !== false) {
                        return $sInput;
                    } else {
                        return $sAlias . $sInput;
                    }
                }, $mField);

                $aData['or_like'][] = ['CONCAT_WS(" ", ' . implode(',', $sMappedFields) . ')', $sKeywords];
            } else {
                if (strpos($mField, '.') !== false) {
                    $aData['or_like'][] = [$mField, $sKeywords];
                } else {
                    $aData['or_like'][] = [$sAlias . $mField, $sKeywords];
                }
            }
        }
    }
996367
protected function injectLocalisationQuery(array &$aData): void
    {
        /** @var Locale $oLocale */
        $oLocale = Factory::service('Locale');
        $sTable  = $this->getTableName();
        $sAlias  = $this->getTableAlias();

        /**
         * Restrict to a specific locale by passing in USE_LOCALE to the data array
         * Pass NO_LOCALISE_FILTER to the data array the developer can return items for all locales
         */

        if (!array_key_exists('select', $aData)) {
            $aData['select'] = [
                $sAlias . '.*',
            ];
        }

        if (array_key_exists('USE_LOCALE', $aData)) {
            if ($aData['USE_LOCALE'] instanceof \Nails\Common\Factory\Locale) {
                $oUserLocale = $aData['USE_LOCALE'];
            } else {
                list($sLanguage, $sRegion) = $oLocale::parseLocaleString($aData['USE_LOCALE']);
                $oUserLocale = $this->getLocale($sLanguage, $sRegion);
            }

            if (!array_key_exists('where', $aData)) {
                $aData['where'] = [];
            }

            $aData['where'][] = ['language', $oUserLocale->getLanguage()];
            $aData['where'][] = ['region', $oUserLocale->getRegion()];

        } elseif (!array_key_exists('NO_LOCALISE_FILTER', $aData)) {

            $oUserLocale      = $oLocale->get();
            $sUserLanguage    = $oUserLocale->getLanguage();
            $sUserRegion      = $oUserLocale->getRegion();
            $oDefaultLocale   = $oLocale->getDefautLocale();
            $sDefaultLanguage = $oDefaultLocale->getLanguage();
            $sDefaultRegion   = $oDefaultLocale->getRegion();

            $sQueryExact    = 'SELECT COUNT(*) FROM ' . $sTable . ' sub_1 WHERE sub_1.id = ' . $sAlias . '.id AND sub_1.' . static::$sColumnLanguage . ' = "' . $sUserLanguage . '" AND sub_1.' . static::$sColumnRegion . ' = "' . $sUserRegion . '"';
            $sQueryLanguage = 'SELECT COUNT(*) FROM ' . $sTable . ' sub_2 WHERE sub_2.id = ' . $sAlias . '.id AND sub_2.' . static::$sColumnLanguage . ' = "' . $sUserLanguage . '" AND sub_2.' . static::$sColumnRegion . ' != "' . $sUserRegion . '"';


            if ($oLocale::MODEL_FALLBACK_TO_DEFAULT_LOCALE) {
                $aConditionals = [
                    '((' . $sQueryExact . ') = 1 AND ' . static::$sColumnLanguage . ' = "' . $sUserLanguage . '" AND ' . static::$sColumnRegion . ' = "' . $sUserRegion . '")',
                    '((' . $sQueryExact . ') = 0 AND ' . static::$sColumnLanguage . ' = "' . $sDefaultLanguage . '" AND ' . static::$sColumnRegion . ' = "' . $sDefaultRegion . '")',
                ];
            } else {
                $aConditionals = [
                    '((' . $sQueryExact . ') = 1 AND ' . static::$sColumnLanguage . ' = "' . $sUserLanguage . '" AND ' . static::$sColumnRegion . ' = "' . $sUserRegion . '")',
                    '((' . $sQueryExact . ') = 0 AND ' . static::$sColumnLanguage . ' = "' . $sUserLanguage . '")',
                    '((' . $sQueryExact . ') = 0 AND (' . $sQueryLanguage . ') = 0 AND ' . static::$sColumnLanguage . ' = "' . $sDefaultLanguage . '" AND ' . static::$sColumnRegion . ' = "' . $sDefaultRegion . '")',
                ];
            }

            if (!array_key_exists('where', $aData)) {
                $aData['where'] = [];
            }

            $aData['where'][] = '(' . implode(' OR ', $aConditionals) . ')';
        }

        //  Ensure each row knows about the other items available
        $sQuery = 'SELECT GROUP_CONCAT(CONCAT(`others`.`language`, \'_\', `others`.`region`)) FROM ' . $sTable . ' `others` WHERE `others`.`id` = `' . $sAlias . '`.`id`';
        if (!$this->isDestructiveDelete()) {
            $sQuery .= ' AND `others`.`' . $this->getColumn('deleted') . '` = 0';
        }
        $aData['select'][] = '(' . $sQuery . ') available_locales';
    }
518696
protected function getCountCommonCompileLikes(array &$aData)
    {
        $oDb = Factory::service('Database');

        $aLikes = [
            'like'        => 'AND',
            'or_like'     => 'OR',
            'not_like'    => 'AND',
            'or_not_like' => 'OR',
        ];

        $aLikeCompiled = [];

        foreach ($aLikes as $sLikeType => $sLikeGlue) {

            if (!empty($aData[$sLikeType])) {

                $aLikeCompiled[$sLikeType] = [];

                if (is_array($aData[$sLikeType])) {

                    /**
                     * The value is an array. For each element we need to compile as appropriate
                     * and add to $aLikeCompiled.
                     */

                    foreach ($aData[$sLikeType] as $mWhere) {

                        if (is_string($mWhere)) {

                            /**
                             * The value is a straight up string, assume this is a compiled
                             * where string,
                             */

                            $aLikeCompiled[$sLikeType][] = $mWhere;

                        } else {

                            /**
                             * The value is an array, try and determine the various parts
                             * of the query. We use strings which are unlikely to be found
                             * as falsey values (such as null) are perfectly likely.
                             */

                            //  Work out column
                            $mColumn = isset($mWhere['column']) ? $mWhere['column'] : '[NAILS-COL-NOT-FOUND]';

                            if ($mColumn === '[NAILS-COL-NOT-FOUND]') {
                                $mColumn = isset($mWhere[0]) && is_string($mWhere[0]) ? $mWhere[0] : null;
                            }

                            //  Work out value
                            $mVal = isset($mWhere['value']) ? $mWhere['value'] : '[NAILS-VAL-NOT-FOUND]';

                            if ($mVal === '[NAILS-VAL-NOT-FOUND]') {
                                $mVal = isset($mWhere[1]) ? $mWhere[1] : null;
                            }

                            //  Escaped?
                            $bEscape = isset($mWhere['escape']) ? (bool) $mWhere['escape'] : '[NAILS-ESCAPE-NOT-FOUND]';

                            if ($bEscape === '[NAILS-ESCAPE-NOT-FOUND]') {
                                $bEscape = isset($mWhere[2]) ? $mWhere[2] : true;
                            }

                            if ($bEscape) {
                                $mVal = $oDb->escape_like_str($mVal);
                            }

                            //  If the $mColumn is an array then we should concat them together
                            if (is_array($mColumn)) {
                                $mColumn = 'CONCAT_WS(" ", ' . implode(',', $mColumn) . ')';
                            }

                            //  Got something?
                            if ($mColumn) {
                                switch ($sLikeType) {

                                    case 'like' :
                                    case 'or_like' :
                                        $aLikeCompiled[$sLikeType][] = $mColumn . ' LIKE "%' . $mVal . '%"';
                                        break;

                                    case 'not_like' :
                                    case 'or_not_like' :
                                        $aLikeCompiled[$sLikeType][] = $mColumn . ' NOT LIKE "%' . $mVal . '%"';
                                        break;
                                }
                            }
                        }
                    }

                } elseif (is_string($aData[$sLikeType])) {

                    /**
                     * The value is a straight up string, assume this is a compiled
                     * where string,
                     */

                    $aLikeCompiled[$sLikeType][] = $aData[$sLikeType];
                }
            }
        }

        /**
         * Now we need to compile all the conditionals into one big super query.
         * $aWhereStr is an array of the compressed where strings... will make
         * sense shortly...
         */

        if (!empty($aLikeCompiled)) {

            $aWhereStr = [];

            foreach ($aLikeCompiled as $sLikeType => $sValue) {
                $aWhereStr[] = '(' . implode(' ' . $aLikes[$sLikeType] . ' ', $sValue) . ')';
            }

            //  And reduce $aWhereStr to an actual string, like the name suggests
            $aWhereStr = implode(' AND ', $aWhereStr);
            $oDb->where($aWhereStr);
        }
    }
151326
public static function assert($messageData): void
    {
        Assertion::isArray($messageData, 'MessageData must be an array');
        Assertion::keyExists($messageData, 'message_name', 'MessageData must contain a key message_name');
        Assertion::keyExists($messageData, 'uuid', 'MessageData must contain a key uuid');
        Assertion::keyExists($messageData, 'payload', 'MessageData must contain a key payload');
        Assertion::keyExists($messageData, 'metadata', 'MessageData must contain a key metadata');
        Assertion::keyExists($messageData, 'created_at', 'MessageData must contain a key created_at');

        self::assertMessageName($messageData['message_name']);
        self::assertUuid($messageData['uuid']);
        self::assertPayload($messageData['payload']);
        self::assertMetadata($messageData['metadata']);
        self::assertCreatedAt($messageData['created_at']);
    }
296178
protected function prepareInvoice(array &$aData, int $iInvoiceId = null): void
    {
        //  Always has an uppercase state
        if (array_key_exists('state', $aData)) {
            $aData['state'] = !empty($aData['state']) ? $aData['state'] : self::STATE_DRAFT;
            $aData['state'] = strtoupper(trim($aData['state']));
        }

        //  Always has terms
        if (array_key_exists('terms', $aData)) {
            $aData['terms'] = !empty($aData['terms']) ? $aData['terms'] : 0;
        }

        //  Always has a date
        if (array_key_exists('dated', $aData) && empty($aData['dated'])) {
            $oDate          = Factory::factory('DateTime');
            $aData['dated'] = $oDate->format('Y-m-d');
        }

        //  Calculate the due date
        if (!array_key_exists('due', $aData) && !empty($aData['dated'])) {

            if (array_key_exists('terms', $aData)) {
                $iTerms = (int) $aData['terms'];
            } else {
                $iTerms = (int) appSetting('default_payment_terms', 'nails/module-invoice');
            }

            $oDate = new \DateTime($aData['dated']);
            $oDate->add(new \DateInterval('P' . $iTerms . 'D'));
            $aData['due'] = $oDate->format('Y-m-d');
        }

        //  Always has a currency
        if (array_key_exists('currency', $aData)) {
            $aData['currency'] = strtoupper(trim($aData['currency']));
        }

        //  Callback data is encoded as JSON
        if (array_key_exists('callback_data', $aData)) {
            $aData['callback_data'] = json_encode($aData['callback_data']);
        }

        //  Sanitize each item
        if (array_key_exists('items', $aData)) {

            $iCounter = 0;
            $aTaxIds  = [];
            foreach ($aData['items'] as &$aItem) {

                if ($aItem instanceof Item) {
                    $aItem = $aItem->toArray();
                }

                //  Has an ID or is null
                $aItem['id'] = !empty($aItem['id']) ? (int) $aItem['id'] : null;

                //  Currency is always the same as the invoice
                $aItem['currency'] = $aData['currency'];

                //  Always has a unit
                $aItem['unit'] = !empty($aItem['unit']) ? strtoupper(trim($aItem['unit'])) : null;

                //  Always has a unit cost
                $aItem['unit_cost'] = !empty($aItem['unit_cost']) ? (float) $aItem['unit_cost'] : 0;

                //  Always has a quantity
                $aItem['quantity'] = !empty($aItem['quantity']) ? (float) $aItem['quantity'] : 0;

                //  Always has a tax_id
                $aItem['tax_id'] = !empty($aItem['tax_id']) ? (int) $aItem['tax_id'] : null;
                if (!empty($aItem['tax_id'])) {
                    $aTaxIds[] = $aItem['tax_id'];
                }

                //  Give it an order
                $aItem['order'] = $iCounter;
                $iCounter++;
            }

            $aTaxIds = array_unique($aTaxIds);
            $aTaxIds = array_filter($aTaxIds);
        }

        // --------------------------------------------------------------------------

        //  Now check for errors

        //  Invalid ref
        if (array_key_exists('ref', $aData)) {
            $oInvoice = $this->getByRef($aData['ref']);
            if (!empty($oInvoice) && $iInvoiceId != $oInvoice->id) {
                throw new InvoiceException('Reference "' . $aData['ref'] . '" is already in use.', 1);
            }
        }

        //  Invalid state
        if (array_key_exists('state', $aData)) {
            $aStates = $this->getStates();
            if (!array_key_exists($aData['state'], $aStates)) {
                throw new InvoiceException('State "' . $aData['ref'] . '" does not exist.', 1);
            }
        }

        //  Invalid Customer ID
        if (array_key_exists('customer_id', $aData) && !empty($aData['customer_id'])) {
            $oCustomerModel = Factory::model('Customer', 'nails/module-invoice');
            if (!$oCustomerModel->getById($aData['customer_id'])) {
                throw new InvoiceException('"' . $aData['customer_id'] . '" is not a valid customer ID.', 1);
            }
        }

        //  Invalid Email
        if (array_key_exists('email', $aData) && !empty($aData['email'])) {
            Factory::helper('email');
            if (!valid_email($aData['email'])) {
                throw new InvoiceException('"' . $aData['email'] . '" is not a valid email address.', 1);
            }
        }

        //  Invalid currency
        if (array_key_exists('currency', $aData)) {
            $oCurrency = Factory::service('Currency', 'nails/module-currency');
            try {
                $oCurrency->getByIsoCode($aData['currency']);
            } catch (\Exception $e) {
                throw new InvoiceException('"' . $aData['currency'] . '" is not a valid currency.', 1);
            }
        }

        //  Invalid Tax IDs
        if (!empty($aTaxIds)) {
            $oTaxModel = Factory::model('Tax', 'nails/module-invoice');
            $aTaxRates = $oTaxModel->getByIds($aTaxIds);
            if (count($aTaxRates) != count($aTaxIds)) {
                throw new InvoiceException('An invalid Tax Rate was supplied.', 1);
            }
        }

        //  Missing items
        if (array_key_exists('items', $aData) && $aData['state'] !== self::STATE_DRAFT && empty($aData['items'])) {

            throw new InvoiceException(
                'At least one line item must be provided if saving a non-draft invoice.',
                1
            );

        } elseif (array_key_exists('items', $aData)) {

            //  Check each item
            $oItemModel = Factory::model('InvoiceItem', 'nails/module-invoice');
            foreach ($aData['items'] as &$aItem) {

                //  Has a positive quantity
                if ($aItem['quantity'] <= 0) {
                    throw new InvoiceException('Each item must have a positive quantity.', 1);
                }

                //  Has a valid unit
                $aUnits = $oItemModel->getUnits();
                if (!empty($aItem['unit']) && !array_key_exists($aItem['unit'], $aUnits)) {
                    throw new InvoiceException('Unit "' . $aItem['unit'] . '" does not exist.', 1);
                }

                //  Has a label
                if (empty($aItem['label'])) {
                    throw new InvoiceException('Each item must be given a label.', 1);
                }
            }

            //  Calculate totals
            //  @todo: do this properly considering currencies etc
            $aData['sub_total'] = 0;
            $aData['tax_total'] = 0;

            foreach ($aData['items'] as &$aItem) {

                //  Add to sub total
                $aItem['sub_total'] = $aItem['quantity'] * $aItem['unit_cost'];

                //  Calculate tax
                if (!empty($aItem['tax_id']) && !empty($aTaxRates)) {
                    foreach ($aTaxRates as $oTaxRate) {
                        if ($oTaxRate->id == $aItem['tax_id']) {
                            $aItem['tax_total'] = $aItem['sub_total'] * $oTaxRate->rate_decimal;
                        }
                    }
                } else {
                    $aItem['tax_total'] = 0;
                }

                //  Ensure integers
                $aItem['unit_cost'] = intval($aItem['unit_cost']);
                $aItem['sub_total'] = intval($aItem['sub_total']);
                $aItem['tax_total'] = intval($aItem['tax_total']);

                //  Grand total
                $aItem['grand_total'] = $aItem['sub_total'] + $aItem['tax_total'];

                //  Update invoice total
                $aData['sub_total'] += $aItem['sub_total'];
                $aData['tax_total'] += $aItem['tax_total'];
            }

            $aData['grand_total'] = $aData['sub_total'] + $aData['tax_total'];
        }
    }
1277255
public function setProcessing($iRefundId, $aData = [])
    {
        $aData['status'] = self::STATUS_PROCESSING;
        return $this->update($iRefundId, $aData);
    }