/home/sharedstore/public_html/prestashop.sharedstore.ma/classes
NameSizeModeActions
assets/-0755rm
cache/-0755rm
checkout/-0755rm
container/-0755rm
controller/-0755rm
db/-0755rm
exception/-0755rm
form/-0755rm
helper/-0755rm
lang/-0755rm
log/-0755rm
module/-0755rm
order/-0755rm
pdf/-0755rm
product/-0755rm
proxy/-0755rm
range/-0755rm
shop/-0755rm
Smarty/-0755rm
stock/-0755rm
tax/-0755rm
tree/-0755rm
webservice/-0755rm
.htaccess1700644editdlrm
Access.php135730644editdlrm
Address.php230160644editdlrm
AddressChecksumCore.php18510644editdlrm
AddressFormat.php251250644editdlrm
Alias.php51060644editdlrm
Attachment.php115570644editdlrm
AttributeGroup.php134880644editdlrm
Carrier.php656660644editdlrm
Cart.php2211920644editdlrm
CartRule.php927650644editdlrm
Category.php891710644editdlrm
Chart.php41770644editdlrm
ChecksumInterface.php12460644editdlrm
CMS.php117250644editdlrm
CMSCategory.php241480644editdlrm
CMSRole.php18220644editdlrm
Combination.php180120644editdlrm
Configuration.php271560644editdlrm
ConfigurationKPI.php91260644editdlrm
ConfigurationTest.php123090644editdlrm
Connection.php102620644editdlrm
ConnectionsSource.php49260644editdlrm
Contact.php39540644editdlrm
Context.php183720644editdlrm
Cookie.php178060644editdlrm
Country.php167230644editdlrm
CSV.php31100644editdlrm
Currency.php371670644editdlrm
Curve.php29930644editdlrm
Customer.php535760644editdlrm
CustomerAddress.php12950644editdlrm
CustomerMessage.php58900644editdlrm
CustomerSession.php27180644editdlrm
CustomerThread.php98480644editdlrm
Customization.php137540644editdlrm
CustomizationField.php29150644editdlrm
DateRange.php25320644editdlrm
Delivery.php33170644editdlrm
Dispatcher.php488100644editdlrm
Employee.php240620644editdlrm
EmployeeSession.php27180644editdlrm
Feature.php116560644editdlrm
FeatureFlag.php32260644editdlrm
FeatureValue.php82490644editdlrm
FileUploader.php38200644editdlrm
Gender.php28760644editdlrm
Group.php153230644editdlrm
GroupReduction.php90250644editdlrm
Guest.php85290644editdlrm
Hook.php509090644editdlrm
Image.php324180644editdlrm
ImageManager.php325490644editdlrm
ImageType.php71550644editdlrm
index.php13720644editdlrm
Language.php635470644editdlrm
Link.php638140644editdlrm
LocalizationPack.php236040644editdlrm
Mail.php343120644editdlrm
Manufacturer.php217450644editdlrm
ManufacturerAddress.php13110644editdlrm
Media.php330210644editdlrm
Message.php66260644editdlrm
Meta.php185770644editdlrm
Notification.php84700644editdlrm
ObjectModel.php837730644editdlrm
Pack.php263320644editdlrm
Page.php45980644editdlrm
PaymentFree.php13620644editdlrm
PaymentModule.php611970644editdlrm
PhpEncryption.php32910644editdlrm
PhpEncryptionEngine.php39490644editdlrm
PrestaShopAutoload.php97990644editdlrm
PrestaShopBackup.php117020644editdlrm
PrestaShopCollection.php217900644editdlrm
PrestaShopLogger.php85230644editdlrm
Product.php3180560644editdlrm
ProductAssembler.php68950644editdlrm
ProductAttribute.php138080644editdlrm
ProductDownload.php102940644editdlrm
ProductPresenterFactory.php39830644editdlrm
ProductSale.php130000644editdlrm
ProductSupplier.php85370644editdlrm
Profile.php85230644editdlrm
QqUploadedFileForm.php53170644editdlrm
QqUploadedFileXhr.php58250644editdlrm
QuickAccess.php64020644editdlrm
RequestSql.php214530644editdlrm
Risk.php24190644editdlrm
Search.php592290644editdlrm
SearchEngine.php28790644editdlrm
SpecificPrice.php332120644editdlrm
SpecificPriceRule.php143740644editdlrm
State.php79560644editdlrm
Store.php63940644editdlrm
Supplier.php183390644editdlrm
SupplierAddress.php12990644editdlrm
Tab.php222230644editdlrm
Tag.php135580644editdlrm
Tools.php1409030644editdlrm
Translate.php120590644editdlrm
TranslatedConfiguration.php47920644editdlrm
Upgrader.php118590644editdlrm
Uploader.php111040644editdlrm
Validate.php398010644editdlrm
ValidateConstraintTranslator.php26110644editdlrm
WarehouseAddress.php13020644editdlrm
Zone.php35000644editdlrm
Edit: /home/sharedstore/public_html/prestashop.sharedstore.ma/classes/ObjectModel.php (83773B)
* @copyright Since 2007 PrestaShop SA and Contributors * @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0) */ use PrestaShop\PrestaShop\Adapter\ServiceLocator; use PrestaShop\PrestaShop\Core\Image\ImageFormatConfiguration; use PrestaShopBundle\Translation\TranslatorComponent; abstract class ObjectModelCore implements \PrestaShop\PrestaShop\Core\Foundation\Database\EntityInterface { /** * List of field types. */ public const TYPE_INT = 1; public const TYPE_BOOL = 2; public const TYPE_STRING = 3; public const TYPE_FLOAT = 4; public const TYPE_DATE = 5; public const TYPE_HTML = 6; public const TYPE_NOTHING = 7; public const TYPE_SQL = 8; /** * List of data to format. */ public const FORMAT_COMMON = 1; public const FORMAT_LANG = 2; public const FORMAT_SHOP = 3; /** * List of association types. */ public const HAS_ONE = 1; public const HAS_MANY = 2; /** @var int|null Object ID */ public $id; /** @var int|null Language ID */ protected $id_lang = null; /** @var Language|null Language ID * This is the same Language as the $id_lang except in the following case: * If $id_lang is invalid (e.g. due to a removed language) $lang_associated is the default language */ protected $lang_associated = null; /** @var int|null Shop ID */ protected $id_shop = null; /** * This field contains the list of shop that you intend to update. When add or update is called the ObjectModel * uses the IDs from the Context by default, except when this field is not empty. In this case the shop IDs * contained in this field override the ones from the Context allowing you to control exactly which shops are * impacted. * * @var array List of shop IDs */ public $id_shop_list = []; /** @var bool */ protected $get_shop_from_context = true; /** @var array|null Holds required fields for each ObjectModel class */ protected static $fieldsRequiredDatabase = null; /** * @deprecated 1.5.0.1 Define property using $definition['table'] property instead. * * @var string */ protected $table; /** * @deprecated 1.5.0.1 Define property using $definition['table'] property instead. * * @var string */ protected $identifier; /** * @deprecated 1.5.0.1 Define property using $definition['table'] property instead. * * @var array */ protected $fieldsRequired = []; /** * @deprecated 1.5.0.1 Define property using $definition['table'] property instead. * * @var array */ protected $fieldsSize = []; /** * @deprecated 1.5.0.1 Define property using $definition['table'] property instead. * * @var array */ protected $fieldsValidate = []; /** * @deprecated 1.5.0.1 Define property using $definition['table'] property instead. * * @var array */ protected $fieldsRequiredLang = []; /** * @deprecated 1.5.0.1 Define property using $definition['table'] property instead. * * @var array */ protected $fieldsSizeLang = []; /** * @deprecated 1.5.0.1 Define property using $definition['table'] property instead. * * @var array */ protected $fieldsValidateLang = []; /** * @deprecated 1.5.0.1 * * @var array */ protected $tables = []; /** @var array Tables */ protected $webserviceParameters = []; /** @var string Path to image directory. Used for image deletion. */ protected $image_dir = null; /** @var string file type of image files. */ protected $image_format = 'jpg'; /** @var TranslatorComponent */ protected $translator; /** * @var array Contains object definition * * @since 1.5.0.1 */ public static $definition = []; /** * Holds compiled definitions of each ObjectModel class. * Values are assigned during object initialization. * * @var array */ protected static $loaded_classes = []; /** @var array Contains current object definition. */ protected $def; /** @var array|null List of specific fields to update (all fields if null). */ protected $update_fields = null; /** @var Db|bool An instance of the db in order to avoid calling Db::getInstance() thousands of times. */ protected static $db = false; /** @var array|null List of HTML field (based on self::TYPE_HTML) */ public static $htmlFields = null; /** @var bool Enables to define an ID before adding object. */ public $force_id = false; /** * @var bool if true, objects are cached in memory */ protected static $cache_objects = true; /** * @return null */ public static function getRepositoryClassName() { return null; } /** * reset static cache (eg unit testing purpose). */ public static function resetStaticCache() { static::$loaded_classes = []; } /** * Returns object validation rules (fields validity). * * @param string $class Child class name for static use (optional) * * @return array Validation rules (fields validity) */ public static function getValidationRules($class = __CLASS__) { $object = new $class(); return [ 'required' => $object->fieldsRequired, 'size' => $object->fieldsSize, 'validate' => $object->fieldsValidate, 'requiredLang' => $object->fieldsRequiredLang, 'sizeLang' => $object->fieldsSizeLang, 'validateLang' => $object->fieldsValidateLang, ]; } /** * Builds the object. * * @param int|null $id if specified, loads and existing object from DB (optional) * @param int|null $id_lang required if object is multilingual (optional) * @param int|null $id_shop ID shop for objects with multishop tables * @param TranslatorComponent|null $translator * * @throws PrestaShopDatabaseException * @throws PrestaShopException */ public function __construct($id = null, $id_lang = null, $id_shop = null, $translator = null) { $class_name = get_class($this); if (!isset(ObjectModel::$loaded_classes[$class_name])) { $this->def = ObjectModel::getDefinition($class_name); $this->setDefinitionRetrocompatibility(); if (!Validate::isTableOrIdentifier($this->def['primary']) || !Validate::isTableOrIdentifier($this->def['table'])) { throw new PrestaShopException('Identifier or table format not valid for class ' . $class_name); } ObjectModel::$loaded_classes[$class_name] = get_object_vars($this); } else { foreach (ObjectModel::$loaded_classes[$class_name] as $key => $value) { $this->{$key} = $value; } } if (null !== $translator) { $this->translator = $translator; } if ($id_lang !== null) { $this->id_lang = (Language::getLanguage($id_lang) !== false) ? $id_lang : Configuration::get('PS_LANG_DEFAULT'); } if ($id_shop && $this->isMultishop()) { $this->id_shop = (int) $id_shop; $this->get_shop_from_context = false; } if ($this->isMultishop() && !$this->id_shop) { $this->id_shop = Context::getContext()->shop->id; } if ($id) { /** @var \PrestaShop\PrestaShop\Adapter\EntityMapper $entity_mapper */ $entity_mapper = ServiceLocator::get('\\PrestaShop\\PrestaShop\\Adapter\\EntityMapper'); $entity_mapper->load($id, $this->id_lang, $this, $this->def, $this->id_shop, self::$cache_objects); } } protected function trans($id, array $parameters = [], $domain = null, $locale = null) { if (null === $this->translator) { $this->translator = Context::getContext()->getTranslator(); if (null === $this->translator) { return $id; } } if (isset($parameters['_raw'])) { @trigger_error( 'The _raw parameter is deprecated and will be removed in the next major version.', E_USER_DEPRECATED ); unset($parameters['_raw']); return $this->translator->trans($id, $parameters, $domain, $locale); } return htmlspecialchars($this->translator->trans($id, $parameters, $domain, $locale), ENT_NOQUOTES); } /** * Prepare fields for ObjectModel class (add, update) * All fields are verified (pSQL, intval, ...). * * @return array All object fields * * @throws PrestaShopException */ public function getFields() { $this->validateFields(); $fields = $this->formatFields(self::FORMAT_COMMON); // For retro compatibility if (Shop::isTableAssociated($this->def['table'])) { $fields = array_merge($fields, $this->getFieldsShop()); } // Ensure that we get something to insert if (!$fields && isset($this->id) && Validate::isUnsignedId($this->id)) { $fields[$this->def['primary']] = $this->id; } return $fields; } /** * Prepare fields for multishop * Fields are not validated here, we consider they are already validated in getFields() method, * this is not the best solution but this is the only one possible for retro compatibility. * * @since 1.5.0.1 * * @return array All object fields */ public function getFieldsShop() { $fields = $this->formatFields(self::FORMAT_SHOP); if (!$fields && isset($this->id) && Validate::isUnsignedId($this->id)) { $fields[$this->def['primary']] = $this->id; } return $fields; } /** * Prepare multilang fields. * * @since 1.5.0.1 * * @return array * * @throws PrestaShopException */ public function getFieldsLang() { // Backward compatibility if (method_exists($this, 'getTranslationsFieldsChild')) { return $this->getTranslationsFieldsChild(); } $this->validateFieldsLang(); $is_lang_multishop = $this->isLangMultishop(); $fields = []; if (!is_int($this->id_lang) || $this->id_lang <= 0) { foreach (Language::getIDs(false) as $id_lang) { $fields[$id_lang] = $this->formatFields(self::FORMAT_LANG, $id_lang); $fields[$id_lang]['id_lang'] = $id_lang; if ($this->id_shop && $is_lang_multishop) { $fields[$id_lang]['id_shop'] = (int) $this->id_shop; } } } else { $fields = [$this->id_lang => $this->formatFields(self::FORMAT_LANG, $this->id_lang)]; $fields[$this->id_lang]['id_lang'] = $this->id_lang; if ($this->id_shop && $is_lang_multishop) { $fields[$this->id_lang]['id_shop'] = (int) $this->id_shop; } } return $fields; } /** * Returns the language related to the object or the default one if it doesn't exists * * @return Language * * @throws PrestaShopDatabaseException * @throws PrestaShopException */ public function getAssociatedLanguage(): Language { if (null !== $this->lang_associated) { return $this->lang_associated; } $this->lang_associated = new Language($this->id_lang); if (null === $this->lang_associated->id) { $this->lang_associated = new Language((int) Configuration::get('PS_LANG_DEFAULT')); } return $this->lang_associated; } /** * Formats values of each fields. * * @since 1.5.0.1 * * @param int $type FORMAT_COMMON or FORMAT_LANG or FORMAT_SHOP * @param int $id_lang If this parameter is given, only take lang fields * * @return array */ protected function formatFields($type, $id_lang = null) { $fields = []; // Set primary key in fields if (isset($this->id)) { $fields[$this->def['primary']] = $this->id; } foreach ($this->def['fields'] as $field => $data) { // Only get fields we need for the type // E.g. if only lang fields are filtered, ignore fields without lang => true // For type FORMAT_LANG only multi lang fields are returned if (($type == self::FORMAT_LANG && empty($data['lang'])) // For type FORMAT_SHOP only multi shop fields are returned || ($type == self::FORMAT_SHOP && empty($data['shop'])) // For type FORMAT_COMMON only fields that are neither multi shop nor multi lang are returned (one exception though in case // shop === both, then the field is returned for both FORMAT_SHOP and FORMAT_COMMON types) || ($type == self::FORMAT_COMMON && ((!empty($data['shop']) && $data['shop'] !== 'both') || !empty($data['lang'])))) { continue; } // This field contains a list of fields that need to be updated, and only those fields So this method must filter out the fields // that do not belong in $this->update_fields Since fields have already been filtered beforehand regarding of the requested type // the check here is only about checking that the data has been marked as "to be updated" if (is_array($this->update_fields)) { if (empty($this->update_fields[$field])) { // Regular fields updates (multi shop or not) are identified by their field name continue; } elseif ($type == self::FORMAT_LANG && empty($this->update_fields[$field][$id_lang])) { // Multi lang updates are identified by associated language ID continue; } } // Get field value, if value is multilang and field is empty, use value from default lang $value = $this->$field; if ($type == self::FORMAT_LANG && $id_lang && is_array($value)) { if (!empty($value[$id_lang])) { $value = $value[$id_lang]; } elseif (!empty($data['required'])) { $value = $value[Configuration::get('PS_LANG_DEFAULT')]; } else { $value = ''; } } $purify = isset($data['validate']) && Tools::strtolower($data['validate']) == 'iscleanhtml'; // Format field value $fields[$field] = ObjectModel::formatValue($value, $data['type'], false, $purify, !empty($data['allow_null'])); } return $fields; } /** * Formats a value. * * @param mixed $value * @param int $type * @param bool $with_quotes * @param bool $purify * @param bool $allow_null * * @return mixed */ public static function formatValue($value, $type, $with_quotes = false, $purify = true, $allow_null = false) { if ($allow_null && $value === null) { return ['type' => 'sql', 'value' => 'NULL']; } switch ($type) { case self::TYPE_INT: return (int) $value; case self::TYPE_BOOL: return (int) $value; case self::TYPE_FLOAT: return (float) str_replace(',', '.', $value ?? ''); case self::TYPE_DATE: if (!$value) { $value = '0000-00-00'; } if ($with_quotes) { return '\'' . pSQL($value) . '\''; } return pSQL($value); case self::TYPE_HTML: if ($purify) { $value = Tools::purifyHTML($value); } if ($with_quotes) { return '\'' . pSQL($value, true) . '\''; } return pSQL($value, true); case self::TYPE_SQL: if ($with_quotes) { return '\'' . pSQL($value, true) . '\''; } return pSQL($value, true); case self::TYPE_NOTHING: return $value; case self::TYPE_STRING: default: if ($with_quotes) { return '\'' . pSQL($value) . '\''; } return pSQL($value); } } private function getFullyQualifiedName() { return str_replace('\\', '', get_class($this)); } /** * Get object name * Used for read/write in required fields table. * * @return string */ public function getObjectName() { return get_class($this); } /** * Saves current object to database (add or update). * * @param bool $null_values * @param bool $auto_date * * @return bool Insertion result * * @throws PrestaShopException */ public function save($null_values = false, $auto_date = true) { return (int) $this->id > 0 ? $this->update($null_values) : $this->add($auto_date, $null_values); } /** * Adds current object to the database. * * @param bool $auto_date * @param bool $null_values * * @return bool Insertion result * * @throws PrestaShopDatabaseException * @throws PrestaShopException */ public function add($auto_date = true, $null_values = false) { if (isset($this->id) && !$this->force_id) { $this->id = null; } // @hook actionObjectAddBefore Hook::exec('actionObjectAddBefore', ['object' => $this]); Hook::exec('actionObject' . $this->getFullyQualifiedName() . 'AddBefore', ['object' => $this]); // Automatically fill dates if ($auto_date && property_exists($this, 'date_add')) { $this->date_add = date('Y-m-d H:i:s'); } if ($auto_date && property_exists($this, 'date_upd')) { $this->date_upd = date('Y-m-d H:i:s'); } $id_shop_list = []; if (Shop::isTableAssociated($this->def['table'])) { $id_shop_list = Shop::getContextListShopID(); if (count($this->id_shop_list)) { $id_shop_list = $this->id_shop_list; } } // Database insertion if (Shop::checkIdShopDefault($this->def['table']) && array_key_exists('id_shop_default', get_object_vars($this))) { /* @phpstan-ignore-next-line */ $this->id_shop_default = (in_array(Configuration::get('PS_SHOP_DEFAULT'), $id_shop_list) == true) ? Configuration::get('PS_SHOP_DEFAULT') : min($id_shop_list); } if (!$result = Db::getInstance()->insert($this->def['table'], $this->getFields(), $null_values)) { return false; } // Get object id in database if force_id is not true if (empty($this->id)) { $this->id = Db::getInstance()->Insert_ID(); } // Database insertion for multishop fields related to the object if (Shop::isTableAssociated($this->def['table'])) { $fields = $this->getFieldsShop(); $fields[$this->def['primary']] = (int) $this->id; foreach ($id_shop_list as $id_shop) { $fields['id_shop'] = (int) $id_shop; $result &= Db::getInstance()->insert($this->def['table'] . '_shop', $fields, $null_values); } } if (!$result) { return false; } // Database insertion for multilingual fields related to the object if (!empty($this->def['multilang'])) { $fields = $this->getFieldsLang(); if ($fields && is_array($fields)) { $shops = Shop::getCompleteListOfShopsID(); $asso = Shop::getAssoTable($this->def['table'] . '_lang'); foreach ($fields as $field) { foreach (array_keys($field) as $key) { if (!Validate::isTableOrIdentifier($key)) { throw new PrestaShopException('key ' . $key . ' is not table or identifier'); } } $field[$this->def['primary']] = (int) $this->id; if ($asso !== false && $asso['type'] == 'fk_shop') { foreach ($shops as $id_shop) { $field['id_shop'] = (int) $id_shop; $result &= Db::getInstance()->insert($this->def['table'] . '_lang', $field); } } else { $result &= Db::getInstance()->insert($this->def['table'] . '_lang', $field); } } } } // @hook actionObjectAddAfter Hook::exec('actionObjectAddAfter', ['object' => $this]); Hook::exec('actionObject' . $this->getFullyQualifiedName() . 'AddAfter', ['object' => $this]); return $result; } /** * Takes current object ID, gets its values from database, * saves them in a new row and loads newly saved values as a new object. * * @return ObjectModel|false * * @throws PrestaShopDatabaseException */ public function duplicateObject() { $definition = ObjectModel::getDefinition($this); $res = Db::getInstance()->getRow( ' SELECT * FROM `' . _DB_PREFIX_ . bqSQL($definition['table']) . '` WHERE `' . bqSQL($definition['primary']) . '` = ' . (int) $this->id ); if (!$res) { return false; } unset($res[$definition['primary']]); foreach ($res as $field => &$value) { if (isset($definition['fields'][$field])) { $value = ObjectModel::formatValue( $value, $definition['fields'][$field]['type'], false, true, !empty($definition['fields'][$field]['allow_null']) ); } } if (!Db::getInstance()->insert($definition['table'], $res)) { return false; } $object_id = Db::getInstance()->Insert_ID(); if (isset($definition['multilang']) && $definition['multilang']) { $result = Db::getInstance()->executeS(' SELECT * FROM `' . _DB_PREFIX_ . bqSQL($definition['table']) . '_lang` WHERE `' . bqSQL($definition['primary']) . '` = ' . (int) $this->id); if (!$result) { return false; } foreach ($result as $rowKey => $row) { foreach ($row as $field => $value) { if (isset($definition['fields'][$field])) { $result[$rowKey][$field] = ObjectModel::formatValue( $value, $definition['fields'][$field]['type'], false, true, !empty($definition['fields'][$field]['allow_null']) ); } } } // Keep $row2, you cannot use $row because there is an unexplicated conflict with the previous usage of this variable foreach ($result as $row2) { $row2[$definition['primary']] = (int) $object_id; if (!Db::getInstance()->insert($definition['table'] . '_lang', $row2)) { return false; } } } /** @var ObjectModel $object_duplicated */ $object_duplicated = new $definition['classname']((int) $object_id); $object_duplicated->duplicateShops((int) $this->id); Hook::exec('actionObjectDuplicateAfter', ['oldObject' => $this, 'newObject' => $object_duplicated]); Hook::exec('actionObject' . $this->getFullyQualifiedName() . 'DuplicateAfter', ['oldObject' => $this, 'newObject' => $object_duplicated]); return $object_duplicated; } /** * Updates the current object in the database. * * @param bool $null_values * * @return bool * * @throws PrestaShopDatabaseException * @throws PrestaShopException */ public function update($null_values = false) { // @hook actionObjectUpdateBefore Hook::exec('actionObjectUpdateBefore', ['object' => $this]); Hook::exec('actionObject' . $this->getFullyQualifiedName() . 'UpdateBefore', ['object' => $this]); $this->clearCache(); // Automatically fill dates if (property_exists($this, 'date_upd')) { $this->date_upd = date('Y-m-d H:i:s'); if (isset($this->update_fields) && is_array($this->update_fields) && count($this->update_fields)) { $this->update_fields['date_upd'] = true; } } // Automatically fill dates if (property_exists($this, 'date_add') && $this->date_add == null) { $this->date_add = date('Y-m-d H:i:s'); if (isset($this->update_fields) && is_array($this->update_fields) && count($this->update_fields)) { $this->update_fields['date_add'] = true; } } $id_shop_list = Shop::getContextListShopID(); if (count($this->id_shop_list)) { $id_shop_list = $this->id_shop_list; } /* @phpstan-ignore-next-line */ if (Shop::checkIdShopDefault($this->def['table']) && array_key_exists('id_shop_default', get_object_vars($this)) && !$this->id_shop_default) { /* @phpstan-ignore-next-line */ $this->id_shop_default = (in_array(Configuration::get('PS_SHOP_DEFAULT'), $id_shop_list) == true) ? Configuration::get('PS_SHOP_DEFAULT') : min($id_shop_list); } // Database update $fieldsToUpdate = $this->getFields(); if (!$result = Db::getInstance()->update($this->def['table'], $fieldsToUpdate, '`' . pSQL($this->def['primary']) . '` = ' . (int) $this->id, 0, $null_values)) { return false; } // Database insertion for multishop fields related to the object if (Shop::isTableAssociated($this->def['table'])) { $multiShopFieldsToUpdate = $this->getFieldsShop(); $multiShopFieldsToUpdate[$this->def['primary']] = (int) $this->id; if (is_array($this->update_fields)) { $update_fields = $this->update_fields; $this->update_fields = null; $all_fields = $this->getFieldsShop(); $all_fields[$this->def['primary']] = (int) $this->id; $this->update_fields = $update_fields; } else { $all_fields = $multiShopFieldsToUpdate; } foreach ($id_shop_list as $id_shop) { $multiShopFieldsToUpdate['id_shop'] = (int) $id_shop; $all_fields['id_shop'] = (int) $id_shop; $where = $this->def['primary'] . ' = ' . (int) $this->id . ' AND id_shop = ' . (int) $id_shop; // A little explanation of what we do here we want to create multishop entry when update is called: // - if the shop is already associated the data is updated // - if we are in a single shop context the association is created // - if the id_shop_list has been forced the association is created $shop_exists = Db::getInstance()->getValue('SELECT ' . $this->def['primary'] . ' FROM ' . _DB_PREFIX_ . $this->def['table'] . '_shop WHERE ' . $where); if ($shop_exists) { $result &= Db::getInstance()->update($this->def['table'] . '_shop', $multiShopFieldsToUpdate, $where, 0, $null_values); } elseif (Shop::getContext() == Shop::CONTEXT_SHOP || count($this->id_shop_list)) { $result &= Db::getInstance()->insert($this->def['table'] . '_shop', $all_fields, $null_values); } } } // Database update for multilingual fields related to the object if (isset($this->def['multilang']) && $this->def['multilang']) { $multiLangFieldsToUpdate = $this->getFieldsLang(); if (is_array($multiLangFieldsToUpdate)) { foreach ($multiLangFieldsToUpdate as $field) { foreach (array_keys($field) as $key) { if (!Validate::isTableOrIdentifier($key)) { throw new PrestaShopException('key ' . $key . ' is not a valid table or identifier'); } } // If this table is linked to multishop system, update / insert for all shops from context if ($this->isLangMultishop()) { $id_shop_list = Shop::getContextListShopID(); if (count($this->id_shop_list)) { $id_shop_list = $this->id_shop_list; } foreach ($id_shop_list as $id_shop) { $field['id_shop'] = (int) $id_shop; $where = pSQL($this->def['primary']) . ' = ' . (int) $this->id . ' AND id_lang = ' . (int) $field['id_lang'] . ' AND id_shop = ' . (int) $id_shop; if (Db::getInstance()->getValue('SELECT COUNT(*) FROM ' . pSQL(_DB_PREFIX_ . $this->def['table']) . '_lang WHERE ' . $where)) { $result &= Db::getInstance()->update($this->def['table'] . '_lang', $field, $where); } else { $result &= Db::getInstance()->insert($this->def['table'] . '_lang', $field); } } } else { // If this table is not linked to multishop system ... $where = pSQL($this->def['primary']) . ' = ' . (int) $this->id . ' AND id_lang = ' . (int) $field['id_lang']; if (Db::getInstance()->getValue('SELECT COUNT(*) FROM ' . pSQL(_DB_PREFIX_ . $this->def['table']) . '_lang WHERE ' . $where)) { $result &= Db::getInstance()->update($this->def['table'] . '_lang', $field, $where); } else { $result &= Db::getInstance()->insert($this->def['table'] . '_lang', $field, $null_values); } } } } } // @hook actionObjectUpdateAfter Hook::exec('actionObjectUpdateAfter', ['object' => $this]); Hook::exec('actionObject' . $this->getFullyQualifiedName() . 'UpdateAfter', ['object' => $this]); return $result; } /** * Deletes current object from database. * * @return bool True if delete was successful * * @throws PrestaShopException */ public function delete() { // @hook actionObjectDeleteBefore Hook::exec('actionObjectDeleteBefore', ['object' => $this]); Hook::exec('actionObject' . $this->getFullyQualifiedName() . 'DeleteBefore', ['object' => $this]); $this->clearCache(); $result = true; // Remove association to multishop table if (count($this->id_shop_list)) { $shopIdsList = $this->id_shop_list; } else { $shopIdsList = Shop::getContextListShopID(); } $shopIdsList = array_map('intval', $shopIdsList); if (Shop::isTableAssociated($this->def['table'])) { $result &= Db::getInstance()->delete($this->def['table'] . '_shop', '`' . $this->def['primary'] . '`=' . (int) $this->id . ' AND id_shop IN (' . implode(', ', $shopIdsList) . ')'); } if ($this->isLangMultishop()) { $result &= Db::getInstance()->delete($this->def['table'] . '_lang', '`' . $this->def['primary'] . '`=' . (int) $this->id . ' AND id_shop IN (' . implode(', ', $shopIdsList) . ')'); } // Database deletion $has_multishop_entries = $this->hasMultishopEntries(); // Database deletion for multilingual fields related to the object if (!empty($this->def['multilang']) && !$has_multishop_entries) { $result &= Db::getInstance()->delete($this->def['table'] . '_lang', '`' . bqSQL($this->def['primary']) . '` = ' . (int) $this->id); } if ($result && !$has_multishop_entries) { $result &= Db::getInstance()->delete($this->def['table'], '`' . bqSQL($this->def['primary']) . '` = ' . (int) $this->id); } if (!$result) { return false; } // @hook actionObjectDeleteAfter Hook::exec('actionObjectDeleteAfter', ['object' => $this]); Hook::exec('actionObject' . $this->getFullyQualifiedName() . 'DeleteAfter', ['object' => $this]); return $result; } /** * Deletes multiple objects from the database at once. * * @param array $ids array of objects IDs * * @return bool */ public function deleteSelection($ids) { $result = true; foreach ($ids as $id) { $this->id = (int) $id; $result = $result && $this->delete(); } return $result; } /** * Does a soft delete on current object, using the "deleted" field in DB * If the model object has no "deleted" property or no "deleted" definition field it will throw an exception * * @return bool * * @throws PrestaShopDatabaseException * @throws PrestaShopException */ public function softDelete() { $definitions = ObjectModel::getDefinition($this); if (empty($definitions['fields']['deleted'])) { throw new PrestaShopException('Field "deleted" is missing from definition in object model ' . get_class($this)); } if (!array_key_exists('deleted', get_object_vars($this))) { throw new PrestaShopException('Property "deleted" is missing in object model ' . get_class($this)); } /* @phpstan-ignore-next-line */ $this->deleted = true; return $this->update(); } /** * Toggles object status in database. * * @return bool Update result * * @throws PrestaShopException */ public function toggleStatus() { // Object must have a variable called 'active' if (!property_exists($this, 'active')) { throw new PrestaShopException('property "active" is missing in object ' . get_class($this)); } // Update only active field $this->setFieldsToUpdate(['active' => true]); // Update active status on object $this->active = !(int) $this->active; // Change status to active/inactive return $this->update(false); } /** * @deprecated 1.5.0.1 (use getFieldsLang()) * * @param array $fields_array * * @return array * * @throws PrestaShopException */ protected function getTranslationsFields($fields_array) { $fields = []; if ($this->id_lang == null) { foreach (Language::getIDs(false) as $id_lang) { $this->makeTranslationFields($fields, $fields_array, $id_lang); } } else { $this->makeTranslationFields($fields, $fields_array, $this->id_lang); } return $fields; } /** * @deprecated 1.5.0.1 * * @param array $fields * @param array $fields_array * @param int $id_language * * @throws PrestaShopException */ protected function makeTranslationFields(&$fields, &$fields_array, $id_language) { $fields[$id_language]['id_lang'] = $id_language; $fields[$id_language][$this->def['primary']] = (int) $this->id; if ($this->id_shop && $this->isLangMultishop()) { $fields[$id_language]['id_shop'] = (int) $this->id_shop; } foreach ($fields_array as $k => $field) { $html = false; $field_name = $field; if (is_array($field)) { $field_name = $k; $html = (isset($field['html'])) ? $field['html'] : false; } /* Check fields validity */ if (!Validate::isTableOrIdentifier($field_name)) { throw new PrestaShopException('identifier is not table or identifier : ' . $field_name); } // Copy the field, or the default language field if it's both required and empty if ((!$this->id_lang && isset($this->{$field_name}[$id_language]) && !empty($this->{$field_name}[$id_language])) || ($this->id_lang && isset($this->$field_name) && !empty($this->$field_name))) { $fields[$id_language][$field_name] = $this->id_lang ? pSQL($this->$field_name, $html) : pSQL($this->{$field_name}[$id_language], $html); } elseif (in_array($field_name, $this->fieldsRequiredLang)) { $fields[$id_language][$field_name] = pSQL($this->id_lang ? $this->$field_name : $this->{$field_name}[Configuration::get('PS_LANG_DEFAULT')], $html); } else { $fields[$id_language][$field_name] = ''; } } } /** * Checks if object field values are valid before database interaction. * * @param bool $die * @param bool $error_return * * @return bool|string true, false or error message * * @throws PrestaShopException */ public function validateFields($die = true, $error_return = false) { foreach ($this->def['fields'] as $field => $data) { if (!empty($data['lang'])) { continue; } if (is_array($this->update_fields) && empty($this->update_fields[$field]) && isset($this->def['fields'][$field]['shop']) && $this->def['fields'][$field]['shop']) { continue; } $message = $this->validateField($field, $this->$field); if ($message !== true) { if ($die) { throw new PrestaShopException($message); } return $error_return ? $message : false; } } return true; } /** * Checks if multilingual object field values are valid before database interaction. * * @param bool $die * @param bool $errorReturn * * @return bool|string true, false or error message * * @throws PrestaShopException */ public function validateFieldsLang($die = true, $errorReturn = false) { $defaultLang = (int) Configuration::get('PS_LANG_DEFAULT'); foreach ($this->def['fields'] as $field => $data) { if (empty($data['lang'])) { continue; } $values = $this->$field; // If the object has not been loaded in multilanguage, then the value is the one for the current language of the object if (!is_array($values)) { $values = [$this->id_lang => $values]; } // The value for the default must always be set, so we put an empty string if it does not exists if (!isset($values[$defaultLang])) { $values[$defaultLang] = ''; } foreach ($values as $id_lang => $value) { if (is_array($this->update_fields) && empty($this->update_fields[$field][$id_lang])) { continue; } $message = $this->validateField($field, $value, $id_lang); if ($message !== true) { if ($die) { throw new PrestaShopException($message); } return $errorReturn ? $message : false; } } } return true; } /** * Validate a single field. * * @since 1.5.0.1 * * @param string $field Field name * @param mixed $value Field value * @param int|null $id_lang Language ID * @param array $skip array of fields to skip * @param bool $human_errors if true, uses more descriptive, translatable error strings * * @return bool|string true or error message string * * @throws PrestaShopException */ public function validateField($field, $value, $id_lang = null, $skip = [], $human_errors = false) { static $ps_lang_default = null; static $ps_allow_html_iframe = null; if ($ps_lang_default === null) { $ps_lang_default = Configuration::get('PS_LANG_DEFAULT'); } if ($ps_allow_html_iframe === null) { $ps_allow_html_iframe = (int) Configuration::get('PS_ALLOW_HTML_IFRAME'); } $this->cacheFieldsRequiredDatabase(); $data = $this->def['fields'][$field]; // Check if field is required $required_fields = $this->getCachedFieldsRequiredDatabase(); if (!$id_lang || $id_lang == $ps_lang_default) { if (!in_array('required', $skip) && (!empty($data['required']) || in_array($field, $required_fields))) { if (Tools::isEmpty($value)) { if ($human_errors) { return $this->trans('The %s field is required.', [$this->displayFieldName($field, get_class($this))], 'Admin.Notifications.Error'); } else { return $this->trans('Property %s is empty.', [get_class($this) . '->' . $field], 'Admin.Notifications.Error'); } } } } // Default value if (!$value && !empty($data['default'])) { $value = $data['default']; $this->$field = $value; } // Check field values if (!in_array('values', $skip) && !empty($data['values']) && is_array($data['values']) && !in_array($value, $data['values'])) { return $this->trans('Property %1$s has a bad value (allowed values are: %2$s).', [get_class($this) . '->' . $field, implode(', ', $data['values'])], 'Admin.Notifications.Error'); } // Check field size if (!in_array('size', $skip) && !empty($data['size'])) { $size = $data['size']; if (!is_array($data['size'])) { $size = ['min' => 0, 'max' => $data['size']]; } $length = Tools::strlen($value); if ($length < $size['min'] || $length > $size['max']) { if ($human_errors) { if (isset($data['lang']) && $data['lang']) { $language = new Language((int) $id_lang); return $this->trans('Your entry in field %1$s (language %2$s) exceeds max length %3$d chars (incl. html tags).', [$this->displayFieldName($field, get_class($this)), $language->name, $size['max']], 'Admin.Notifications.Error'); } else { return $this->trans('The %1$s field is too long (%2$d chars max).', [$this->displayFieldName($field, get_class($this)), $size['max']], 'Admin.Notifications.Error'); } } else { return $this->trans( 'The length of property %1$s is currently %2$d chars. It must be between %3$d and %4$d chars.', [ get_class($this) . '->' . $field, $length, $size['min'], $size['max'], ], 'Admin.Notifications.Error' ); } } } // Range validation allows you to check if the value is between the defined boundaries (min and max options) if (!in_array('range', $skip) && isset($data['range']['min'], $data['range']['max'])) { $range = $data['range']; if ($value < $range['min'] || $value > $range['max']) { return $this->trans( 'The range of property %1$s is currently %2$d. It must be between %3$d and %4$d.', [ get_class($this) . '->' . $field, $value, $range['min'], $range['max'], ], 'Admin.Notifications.Error' ); } } // Check field validator if (!in_array('validate', $skip) && !empty($data['validate'])) { if (!method_exists('Validate', $data['validate'])) { throw new PrestaShopException($this->trans('Validation function not found: %s.', [$data['validate']], 'Admin.Notifications.Error')); } if (!empty($value)) { $res = true; if (Tools::strtolower($data['validate']) === 'iscleanhtml') { if (!call_user_func(['Validate', $data['validate']], $value, $ps_allow_html_iframe)) { $res = false; } } else { if (!call_user_func(['Validate', $data['validate']], $value)) { $res = false; } } if (!$res) { if ($human_errors) { return $this->trans('The %s field is invalid.', [$this->displayFieldName($field, get_class($this))], 'Admin.Notifications.Error'); } else { return $this->trans('Property %s is not valid', [get_class($this) . '->' . $field], 'Admin.Notifications.Error'); } } } } return true; } /** * Returns field name translation. * * @param string $field Field name * @param string $class ObjectModel class name * @param bool $htmlentities If true, applies htmlentities() to result string * @param Context|null $context Context object * * @return string */ public static function displayFieldName($field, $class = __CLASS__, $htmlentities = true, Context $context = null) { global $_FIELDS; if (!isset($context)) { $context = Context::getContext(); } if ($_FIELDS === null && file_exists(_PS_TRANSLATIONS_DIR_ . $context->language->iso_code . '/fields.php')) { @trigger_error( 'Translating ObjectModel fields using fields.php is deprecated since version 8.0.0.', E_USER_DEPRECATED ); include_once _PS_TRANSLATIONS_DIR_ . $context->language->iso_code . '/fields.php'; } $key = $class . '_' . md5($field); return (is_array($_FIELDS) && array_key_exists($key, $_FIELDS)) ? ($htmlentities ? htmlentities($_FIELDS[$key], ENT_QUOTES, 'utf-8') : $_FIELDS[$key]) : $field; } /** * Validates submitted values and returns an array of errors, if any. * * @param bool $htmlentities if true, uses htmlentities() for field name translations in errors * * @return array */ public function validateController($htmlentities = true) { $this->cacheFieldsRequiredDatabase(); $errors = []; $required_fields_database = $this->getCachedFieldsRequiredDatabase(); foreach ($this->def['fields'] as $field => $data) { $value = Tools::getValue($field, $this->{$field}); // Check if field is required by user if (in_array($field, $required_fields_database)) { $data['required'] = true; } // Checking for required fields if (isset($data['required']) && $data['required'] && empty($value) && $value !== '0') { if (!$this->id || $field != 'passwd') { $errors[$field] = '' . self::displayFieldName($field, get_class($this), $htmlentities) . ' ' . $this->trans('is required.', [], 'Admin.Notifications.Error'); } } // Checking for maximum fields sizes if (isset($data['size']) && !empty($value) && Tools::strlen($value) > $data['size']) { $errors[$field] = $this->trans( '%1$s is too long. Maximum length: %2$d', [self::displayFieldName($field, get_class($this), $htmlentities), $data['size']], 'Admin.Notifications.Error' ); } // Checking for fields validity // Hack for postcode required for country which does not have postcodes if (!empty($value) || $value === '0' || ($field == 'postcode' && $value == '0')) { if (isset($data['validate']) && (!call_user_func('Validate::' . $data['validate'], $value) && (!empty($value) || $data['required']))) { $errors[$field] = $this->trans( '%s is invalid.', [ '' . self::displayFieldName($field, get_class($this), $htmlentities) . '', ], 'Admin.Notifications.Error' ); } else { if (isset($data['copy_post']) && !$data['copy_post']) { continue; } if ($field == 'passwd') { /** @var \PrestaShop\PrestaShop\Core\Crypto\Hashing $crypto */ $crypto = ServiceLocator::get('\\PrestaShop\\PrestaShop\\Core\\Crypto\\Hashing'); if ($value = Tools::getValue($field)) { $this->{$field} = $crypto->hash($value, _COOKIE_KEY_); } } else { $this->{$field} = $value; } } } } return $errors; } /** * Returns webservice parameters of this object. * * @param string|null $ws_params_attribute_name * * @return array */ public function getWebserviceParameters($ws_params_attribute_name = null) { $this->cacheFieldsRequiredDatabase(); $default_resource_parameters = [ 'objectSqlId' => $this->def['primary'], 'retrieveData' => [ 'className' => get_class($this), 'retrieveMethod' => 'getWebserviceObjectList', 'params' => [], 'table' => $this->def['table'], ], 'fields' => [ 'id' => ['sqlId' => $this->def['primary'], 'i18n' => false], ], ]; if ($ws_params_attribute_name === null) { $ws_params_attribute_name = 'webserviceParameters'; } if (!isset($this->{$ws_params_attribute_name}['objectNodeName'])) { $default_resource_parameters['objectNodeName'] = $this->def['table']; } if (!isset($this->{$ws_params_attribute_name}['objectsNodeName'])) { $default_resource_parameters['objectsNodeName'] = $this->def['table'] . 's'; } if (isset($this->{$ws_params_attribute_name}['associations'])) { foreach ($this->{$ws_params_attribute_name}['associations'] as $assoc_name => &$association) { if (!array_key_exists('setter', $association) || (isset($association['setter']) && !$association['setter'])) { $association['setter'] = Tools::toCamelCase('set_ws_' . $assoc_name); } if (!array_key_exists('getter', $association)) { $association['getter'] = Tools::toCamelCase('get_ws_' . $assoc_name); } } } if (isset($this->{$ws_params_attribute_name}['retrieveData'], $this->{$ws_params_attribute_name}['retrieveData']['retrieveMethod'])) { unset($default_resource_parameters['retrieveData']['retrieveMethod']); } $resource_parameters = array_merge_recursive($default_resource_parameters, $this->{$ws_params_attribute_name}); $required_fields = $this->getCachedFieldsRequiredDatabase(); foreach ($this->def['fields'] as $field_name => $details) { if (!isset($resource_parameters['fields'][$field_name])) { $resource_parameters['fields'][$field_name] = []; } $current_field = []; $current_field['sqlId'] = $field_name; if (isset($details['size'])) { $current_field['maxSize'] = $details['size']; } if (isset($details['lang'])) { $current_field['i18n'] = $details['lang']; } else { $current_field['i18n'] = false; } if ((isset($details['required']) && $details['required'] === true) || in_array($field_name, $required_fields)) { $current_field['required'] = true; } else { $current_field['required'] = false; } if (isset($details['validate'])) { $current_field['validateMethod'] = ( array_key_exists('validateMethod', $resource_parameters['fields'][$field_name]) ? array_merge($resource_parameters['fields'][$field_name]['validateMethod'], [$details['validate']]) : [$details['validate']] ); } $resource_parameters['fields'][$field_name] = array_merge($resource_parameters['fields'][$field_name], $current_field); if (isset($details['ws_modifier'])) { $resource_parameters['fields'][$field_name]['modifier'] = $details['ws_modifier']; } } if (isset($this->date_add)) { $resource_parameters['fields']['date_add']['setter'] = false; } if (isset($this->date_upd)) { $resource_parameters['fields']['date_upd']['setter'] = false; } foreach ($resource_parameters['fields'] as $key => $resource_parameters_field) { if (!isset($resource_parameters_field['sqlId'])) { $resource_parameters['fields'][$key]['sqlId'] = $key; } } return $resource_parameters; } /** * Returns webservice object list. * * @param string $sql_join * @param string $sql_filter * @param string $sql_sort * @param string $sql_limit * * @return array|null * * @throws PrestaShopDatabaseException */ public function getWebserviceObjectList($sql_join, $sql_filter, $sql_sort, $sql_limit) { $assoc = Shop::getAssoTable($this->def['table']); $class_name = WebserviceRequest::$ws_current_classname; $vars = get_class_vars($class_name); if ($assoc !== false) { if ($assoc['type'] !== 'fk_shop') { $multi_shop_join = ' LEFT JOIN `' . _DB_PREFIX_ . bqSQL($this->def['table']) . '_' . bqSQL($assoc['type']) . '` AS `multi_shop_' . bqSQL($this->def['table']) . '` ON (main.`' . bqSQL($this->def['primary']) . '` = `multi_shop_' . bqSQL($this->def['table']) . '`.`' . bqSQL($this->def['primary']) . '`)'; $sql_filter = 'AND `multi_shop_' . bqSQL($this->def['table']) . '`.id_shop = ' . Context::getContext()->shop->id . ' ' . $sql_filter; $sql_join = $multi_shop_join . ' ' . $sql_join; } else { $vars = get_class_vars($class_name); $or = []; foreach ($vars['shopIDs'] as $id_shop) { $or[] = '(main.id_shop = ' . (int) $id_shop . (isset($this->def['fields']['id_shop_group']) ? ' OR (id_shop = 0 AND id_shop_group=' . (int) Shop::getGroupFromShop((int) $id_shop) . ')' : '') . ')'; } $prepend = ''; if (count($or)) { $prepend = 'AND (' . implode('OR', $or) . ')'; } $sql_filter = $prepend . ' ' . $sql_filter; } } $query = ' SELECT DISTINCT main.`' . bqSQL($this->def['primary']) . '` FROM `' . _DB_PREFIX_ . bqSQL($this->def['table']) . '` AS main ' . $sql_join . ' WHERE 1 ' . $sql_filter . ' ' . ($sql_sort != '' ? $sql_sort : '') . ' ' . ($sql_limit != '' ? $sql_limit : ''); return Db::getInstance(_PS_USE_SQL_SLAVE_)->executeS($query); } /** * Validate required fields. * * @param bool $htmlentities * * @return array * * @throws PrestaShopException */ public function validateFieldsRequiredDatabase($htmlentities = true) { $this->cacheFieldsRequiredDatabase(); $errors = []; $required_fields = $this->getCachedFieldsRequiredDatabase(); foreach ($this->def['fields'] as $field => $data) { if (!in_array($field, $required_fields)) { continue; } if (!method_exists('Validate', $data['validate'])) { throw new PrestaShopException('Validation function not found. ' . $data['validate']); } $value = Tools::getValue($field, null); if ($value === null) { $errors[$field] = $this->trans('The %s field is required.', [self::displayFieldName($field, get_class($this), $htmlentities)], 'Admin.Notifications.Error'); } } return $errors; } /** * Returns an array of required fields. * * @param bool $all if true, returns required fields of all object classes * * @return array|null * * @throws PrestaShopDatabaseException */ public function getFieldsRequiredDatabase($all = false) { return Db::getInstance()->executeS(' SELECT id_required_field, object_name, field_name FROM ' . _DB_PREFIX_ . 'required_field ' . (!$all ? 'WHERE object_name = \'' . pSQL($this->getObjectName()) . '\'' : '')); } /** * Returns true if required field exists. * * @param string $field_name to search * @param bool $all if true, returns required fields of all object classes * * @return bool */ public function isFieldRequired($field_name, $all = false) { if (empty($field_name)) { return false; } else { return (bool) Db::getInstance()->getValue(' SELECT id_required_field FROM ' . _DB_PREFIX_ . 'required_field WHERE field_name = "' . Db::getInstance()->escape($field_name) . '" ' . (!$all ? ' AND object_name = \'' . pSQL($this->getObjectName()) . '\'' : '')); } } /** * Caches data about required objects fields in memory. * * @param bool $all if true, caches required fields of all object classes */ public function cacheFieldsRequiredDatabase($all = true) { if (!is_array(self::$fieldsRequiredDatabase)) { $fields = $this->getFieldsRequiredDatabase((bool) $all); if ($fields) { foreach ($fields as $row) { self::$fieldsRequiredDatabase[$row['object_name']][(int) $row['id_required_field']] = pSQL($row['field_name']); } } else { self::$fieldsRequiredDatabase = []; } } } /** * Get required fields list for this model or for all the models. * * @param bool $all : whether it should return required fields for this model or all the models * * @return array */ public function getCachedFieldsRequiredDatabase($all = false) { $this->cacheFieldsRequiredDatabase($all); if ($all) { return self::$fieldsRequiredDatabase; } $objectName = $this->getObjectName(); return !empty(self::$fieldsRequiredDatabase[$objectName]) ? self::$fieldsRequiredDatabase[$objectName] : []; } /** * Sets required field for this class in the database. * * @param array $fields * * @return bool * * @throws PrestaShopDatabaseException */ public function addFieldsRequiredDatabase($fields) { if (!is_array($fields)) { return false; } $objectName = $this->getObjectName(); if (!Db::getInstance()->execute( 'DELETE FROM ' . _DB_PREFIX_ . 'required_field' . " WHERE object_name = '" . Db::getInstance()->escape($objectName) . "'" ) ) { return false; } foreach ($fields as $field) { if (!Db::getInstance()->insert( 'required_field', ['object_name' => $objectName, 'field_name' => pSQL($field)] )) { return false; } } return true; } /** * Clears cache entries that have this object's ID. * * @param bool $all If true, clears cache for all objects */ public function clearCache($all = false) { if ($all) { Cache::clean('objectmodel_' . $this->def['classname'] . '_*'); } elseif ($this->id) { Cache::clean('objectmodel_' . $this->def['classname'] . '_' . (int) $this->id . '_*'); } } /** * Checks if current object is associated to a shop. * * @since 1.5.0.1 * * @param int|null $id_shop * * @return bool */ public function isAssociatedToShop($id_shop = null) { if ($id_shop === null) { $id_shop = Context::getContext()->shop->id; } $cache_id = 'objectmodel_shop_' . $this->def['classname'] . '_' . (int) $this->id . '-' . (int) $id_shop; if (!ObjectModel::$cache_objects || !Cache::isStored($cache_id)) { $associated = (bool) Db::getInstance()->getValue( ' SELECT id_shop FROM `' . pSQL(_DB_PREFIX_ . $this->def['table']) . '_shop` WHERE `' . $this->def['primary'] . '` = ' . (int) $this->id . ' AND id_shop = ' . (int) $id_shop ); if (!ObjectModel::$cache_objects) { return $associated; } Cache::store($cache_id, $associated); return $associated; } return Cache::retrieve($cache_id); } /** * This function associate an item to its context. * * @param int|array $id_shops * * @return bool|void * * @throws PrestaShopDatabaseException */ public function associateTo($id_shops) { if (!$this->id) { return; } if (!is_array($id_shops)) { $id_shops = [$id_shops]; } $data = []; foreach ($id_shops as $id_shop) { if (!$this->isAssociatedToShop($id_shop)) { $data[] = [ $this->def['primary'] => (int) $this->id, 'id_shop' => (int) $id_shop, ]; } } if ($data) { return Db::getInstance()->insert($this->def['table'] . '_shop', $data); } return true; } /** * Gets the list of associated shop IDs. * * @since 1.5.0.1 * * @return array * * @throws PrestaShopDatabaseException */ public function getAssociatedShops() { if (!Shop::isTableAssociated($this->def['table'])) { return []; } $list = []; $sql = 'SELECT id_shop FROM `' . _DB_PREFIX_ . $this->def['table'] . '_shop` WHERE `' . $this->def['primary'] . '` = ' . (int) $this->id; foreach (Db::getInstance()->executeS($sql) as $row) { $list[] = (int) $row['id_shop']; } return $list; } /** * Copies shop association data from object with specified ID. * * @since 1.5.0.1 * * @param int $id * * @return bool|void * * @throws PrestaShopDatabaseException */ public function duplicateShops($id) { if (!Shop::isTableAssociated($this->def['table'])) { return false; } $sql = 'SELECT id_shop FROM ' . _DB_PREFIX_ . $this->def['table'] . '_shop WHERE ' . $this->def['primary'] . ' = ' . (int) $id; if ($results = Db::getInstance()->executeS($sql)) { $ids = []; foreach ($results as $row) { $ids[] = $row['id_shop']; } return $this->associateTo($ids); } return false; } /** * Checks if there is more than one entry in associated shop table for current object. * * @since 1.5.0.1 * * @return bool */ public function hasMultishopEntries() { if (!Shop::isTableAssociated($this->def['table']) || !Shop::isFeatureActive()) { return false; } return (bool) Db::getInstance()->getValue('SELECT COUNT(*) FROM `' . _DB_PREFIX_ . $this->def['table'] . '_shop` WHERE `' . $this->def['primary'] . '` = ' . (int) $this->id); } /** * Checks if object is multi-shop object. * * @return bool */ public function isMultishop() { return Shop::isTableAssociated($this->def['table']) || !empty($this->def['multilang_shop']); } /** * Checks if a field is a multi-shop field. * * @param string $field * * @return bool */ public function isMultiShopField($field) { return !empty($this->def['fields'][$field]['shop']); } /** * Checks if the object is both multi-language and multi-shop. * * @return bool */ public function isLangMultishop() { return !empty($this->def['multilang']) && !empty($this->def['multilang_shop']); } /** * Updates a table and splits the common datas and the shop datas. * * @since 1.5.0.1 * * @param string $classname * @param array $data * @param string $where * * @return bool */ public static function updateMultishopTable($classname, $data, $where = '') { $def = ObjectModel::getDefinition($classname); $update_data = []; foreach ($data as $field => $value) { if (!isset($def['fields'][$field])) { continue; } if (!empty($def['fields'][$field]['shop'])) { if ($value === null && !empty($def['fields'][$field]['allow_null'])) { $update_data[] = "a.$field = NULL"; $update_data[] = "{$def['table']}_shop.$field = NULL"; } else { $update_data[] = "a.$field = '$value'"; $update_data[] = "{$def['table']}_shop.$field = '$value'"; } } else { if ($value === null && !empty($def['fields'][$field]['allow_null'])) { $update_data[] = "a.$field = NULL"; } else { $update_data[] = "a.$field = '$value'"; } } } $sql = 'UPDATE ' . _DB_PREFIX_ . $def['table'] . ' a ' . Shop::addSqlAssociation($def['table'], 'a', true, null, true) . ' SET ' . implode(', ', $update_data) . (!empty($where) ? ' WHERE ' . $where : ''); return Db::getInstance()->execute($sql); } /** * Returns the shop ID used to fetch initial object data. * * @return int */ public function getShopId(): int { return (int) $this->id_shop; } /** * Delete images associated with the object. * * @param bool $force_delete * * @return bool */ public function deleteImage($force_delete = false) { if (!$this->id) { return false; } if ($force_delete || !$this->hasMultishopEntries()) { /* Deleting object images and thumbnails (cache) */ if ($this->image_dir) { if (file_exists($this->image_dir . $this->id . '.' . $this->image_format) && !unlink($this->image_dir . $this->id . '.' . $this->image_format)) { return false; } } if (file_exists(_PS_TMP_IMG_DIR_ . $this->def['table'] . '_' . $this->id . '.' . $this->image_format) && !unlink(_PS_TMP_IMG_DIR_ . $this->def['table'] . '_' . $this->id . '.' . $this->image_format)) { return false; } if (file_exists(_PS_TMP_IMG_DIR_ . $this->def['table'] . '_mini_' . $this->id . '.' . $this->image_format) && !unlink(_PS_TMP_IMG_DIR_ . $this->def['table'] . '_mini_' . $this->id . '.' . $this->image_format)) { return false; } // Get image formats we will be deleting. It would probably be easier to use ImageFormatConfiguration::SUPPORTED_FORMATS, // but we want to avoid any behavior change in minor/patch version. $configuredImageFormats = ServiceLocator::get(ImageFormatConfiguration::class)->getGenerationFormats(); $types = ImageType::getImagesTypes(); foreach ($types as $image_type) { if (file_exists($this->image_dir . $this->id . '-' . stripslashes($image_type['name']) . '.' . $this->image_format) && !unlink($this->image_dir . $this->id . '-' . stripslashes($image_type['name']) . '.' . $this->image_format)) { return false; } foreach ($configuredImageFormats as $imageFormat) { $file = $this->image_dir . $this->id . '-' . stripslashes($image_type['name']) . '.' . $imageFormat; if (file_exists($file)) { unlink($file); } $file = $this->image_dir . $this->id . '-' . stripslashes($image_type['name']) . '2x.' . $imageFormat; if (file_exists($file)) { unlink($file); } } } } return true; } /** * Checks if an object exists in database. * * @param int $id_entity * @param string $table Deprecated since 1.7.8.x * * @return bool */ public static function existsInDatabase($id_entity, $table = null) { if ($table !== null) { Tools::displayParameterAsDeprecated('table'); } if ($table !== null && static::class == 'ObjectModel') { $primary = 'id_' . bqSQL($table); } else { $object_def = static::$definition; if (empty($object_def['table']) || empty($object_def['primary'])) { return false; } $table = $object_def['table']; $primary = $object_def['primary']; } $row = Db::getInstance()->getRow( (new DbQuery()) ->select('e.`' . $primary . '` as id') ->from($table, 'e') ->where('e.`' . $primary . '` = ' . (int) $id_entity), false ); return isset($row['id']); } /** * Checks if an object type exists in the database. * * @since 1.5.0.1 * * @param string|null $table Name of table linked to entity * @param bool $has_active_column True if the table has an active column * * @return bool */ public static function isCurrentlyUsed($table = null, $has_active_column = false) { if ($table === null) { $table = self::$definition['table']; } $query = new DbQuery(); $query->select('`id_' . bqSQL($table) . '`'); $query->from($table); if ($has_active_column) { $query->where('`active` = 1'); } return (bool) Db::getInstance()->getValue($query); } /** * Fill an object with given data. Data must be an array with this syntax: * array(objProperty => value, objProperty2 => value, etc.). * * @since 1.5.0.1 * * @param array $data * @param int|null $id_lang */ public function hydrate(array $data, $id_lang = null) { $this->id_lang = $id_lang; if (isset($data[$this->def['primary']])) { $this->id = (int) $data[$this->def['primary']]; } foreach ($data as $key => $value) { if (property_exists($this, $key)) { $this->$key = $value; } } } /** * Fill (hydrate) a list of objects in order to get a collection of these objects. * * @since 1.5.0.1 * * @param string $class Class of objects to hydrate * @param array $datas List of data (multi-dimensional array) * @param int|null $id_lang * * @return array * * @throws PrestaShopException */ public static function hydrateCollection($class, array $datas, $id_lang = null) { if (!class_exists($class)) { throw new PrestaShopException("Class '$class' not found"); } $collection = []; $rows = []; if ($datas) { $definition = ObjectModel::getDefinition($class); if (!array_key_exists($definition['primary'], $datas[0])) { throw new PrestaShopException("Identifier '{$definition['primary']}' not found for class '$class'"); } foreach ($datas as $row) { // Get object common properties $id = $row[$definition['primary']]; if (!isset($rows[$id])) { $rows[$id] = $row; } // Get object lang properties if (isset($row['id_lang']) && !$id_lang) { foreach ($definition['fields'] as $field => $data) { if (!empty($data['lang'])) { if (!is_array($rows[$id][$field])) { $rows[$id][$field] = []; } $rows[$id][$field][$row['id_lang']] = $row[$field]; } } } } } // Hydrate objects foreach ($rows as $row) { /** @var ObjectModel $obj */ $obj = new $class(); $obj->hydrate($row, $id_lang); $collection[] = $obj; } return $collection; } /** * Returns object definition. * * @param string|object $class Name of object * @param string|null $field Name of field if we want the definition of one field only * * @return array */ public static function getDefinition($class, $field = null) { if (is_object($class)) { $class = get_class($class); } if ($field === null) { $cache_id = 'objectmodel_def_' . $class; } if ($field !== null || !Cache::isStored($cache_id)) { /** @var array $definition */ $definition = $class::$definition; $definition['classname'] = $class; if (!empty($definition['multilang'])) { $definition['associations'][PrestaShopCollection::LANG_ALIAS] = [ 'type' => self::HAS_MANY, 'field' => $definition['primary'], 'foreign_field' => $definition['primary'], ]; } if ($field) { return isset($definition['fields'][$field]) ? $definition['fields'][$field] : null; } if (isset($cache_id)) { Cache::store($cache_id, $definition); } return $definition; } return Cache::retrieve($cache_id); } /** * Retrocompatibility for classes without $definition static. * * @TODO Remove this in 1.6 ! * * @since 1.5.0.1 */ protected function setDefinitionRetrocompatibility() { // Retrocompatibility with $table property ($definition['table']) if (isset($this->def['table'])) { $this->table = $this->def['table']; } else { $this->def['table'] = $this->table; } // Retrocompatibility with $identifier property ($definition['primary']) if (isset($this->def['primary'])) { $this->identifier = $this->def['primary']; } else { $this->def['primary'] = $this->identifier; } // Check multilang retrocompatibility if (method_exists($this, 'getTranslationsFieldsChild')) { $this->def['multilang'] = true; } // Retrocompatibility with $fieldsValidate, $fieldsRequired and $fieldsSize properties ($definition['fields']) if (isset($this->def['fields'])) { foreach ($this->def['fields'] as $field => $data) { $suffix = (isset($data['lang']) && $data['lang']) ? 'Lang' : ''; if (isset($data['validate'])) { $this->{'fieldsValidate' . $suffix}[$field] = $data['validate']; } if (isset($data['required']) && $data['required']) { $this->{'fieldsRequired' . $suffix}[] = $field; } if (isset($data['size'])) { $this->{'fieldsSize' . $suffix}[$field] = $data['size']; } } } else { $this->def['fields'] = []; $suffixs = ['', 'Lang']; foreach ($suffixs as $suffix) { foreach ($this->{'fieldsValidate' . $suffix} as $field => $validate) { $this->def['fields'][$field]['validate'] = $validate; if ($suffix == 'Lang') { $this->def['fields'][$field]['lang'] = true; } } foreach ($this->{'fieldsRequired' . $suffix} as $field) { $this->def['fields'][$field]['required'] = true; if ($suffix == 'Lang') { $this->def['fields'][$field]['lang'] = true; } } foreach ($this->{'fieldsSize' . $suffix} as $field => $size) { $this->def['fields'][$field]['size'] = $size; if ($suffix == 'Lang') { $this->def['fields'][$field]['lang'] = true; } } } } } /** * Return the field value for the specified language if the field is multilang, * else the field value. * * @since 1.5.0.1 * * @param string $field_name * @param int|null $id_lang * * @return mixed * * @throws PrestaShopException */ public function getFieldByLang($field_name, $id_lang = null) { $definition = ObjectModel::getDefinition($this); // Is field in definition? if ($definition && isset($definition['fields'][$field_name])) { $field = $definition['fields'][$field_name]; // Is field multilang? if (isset($field['lang']) && $field['lang']) { if (is_array($this->{$field_name})) { return $this->{$field_name}[$id_lang ? $id_lang : Context::getContext()->language->id]; } } return $this->{$field_name}; } else { throw new PrestaShopException('Could not load field from definition.'); } } /** * Set a list of specific fields to update * array(field1 => true, field2 => false, * langfield1 => array(1 => true, 2 => false)). * * @since 1.5.0.1 * * @param array>|null $fields */ public function setFieldsToUpdate(?array $fields) { $this->update_fields = $fields; } /** * @return array>|null */ public function getFieldsToUpdate(): ?array { return $this->update_fields; } /** * Enables object caching. */ public static function enableCache() { ObjectModel::$cache_objects = true; } /** * Disables object caching. */ public static function disableCache() { ObjectModel::$cache_objects = false; } /** * Return HtmlFields for object. */ public function getHtmlFields() { $isDefinitionValid = !empty($this->def) && is_array($this->def) && array_key_exists('table', $this->def); if (!$isDefinitionValid) { return false; } if (isset(self::$htmlFields[$this->def['table']])) { return self::$htmlFields[$this->def['table']]; } self::$htmlFields[$this->def['table']] = []; if (array_key_exists('fields', $this->def)) { foreach ($this->def['fields'] as $name => $field) { if (is_array($field) && array_key_exists('type', $field) && self::TYPE_HTML === $field['type']) { self::$htmlFields[$this->def['table']][] = $name; } } } return self::$htmlFields[$this->def['table']]; } /** * @return int[] */ protected function getShopIdsList(): array { if (count($this->id_shop_list)) { $shopIdsList = $this->id_shop_list; } else { $shopIdsList = Shop::getContextListShopID(); } return $shopIdsList; } }