Newwebshop/classes/ObjectModel.php

776 lines
22 KiB
PHP

<?php
/**
* ObjectModel - Base class for all database objects
*/
abstract class ObjectModel
{
/**
* 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 */
protected $lang_associated = null;
/** @var int|null Shop ID */
protected $id_shop = null;
/** @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;
/** @var string */
protected $table;
/** @var string */
protected $identifier;
/** @var array */
protected $tables = [];
/** @var array Tables */
protected $webserviceParameters = [];
/** @var string Path to image directory */
protected $image_dir = null;
/** @var string file type of image files */
protected $image_format = 'jpg';
/** @var TranslatorComponent */
protected $translator;
/** @var array Contains object definition */
public static $definition = [];
/** @var array Holds compiled definitions of each ObjectModel class */
protected static $loaded_classes = [];
/** @var array Contains current object definition */
protected $def;
/** @var array|null List of specific fields to update */
protected $update_fields = null;
/** @var Db|bool An instance of the db */
protected static $db = false;
/** @var array|null List of HTML field */
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;
/**
* Get repository class name
*
* @return null
*/
public static function getRepositoryClassName()
{
return null;
}
/**
* Reset static cache
*/
public static function resetStaticCache()
{
static::$loaded_classes = [];
}
/**
* Constructor
*
* @param int|null $id
* @param int|null $id_lang
* @param int|null $id_shop
* @param TranslatorComponent|null $translator
*/
public function __construct($id = null, $id_lang = null, $id_shop = null, $translator = null)
{
$class_name = get_class($this);
if (!isset(self::$loaded_classes[$class_name])) {
$this->def = self::getDefinition($class_name);
if (!Validate::isTableOrIdentifier($this->def['primary']) || !Validate::isTableOrIdentifier($this->def['table'])) {
throw new Exception('Identifier or table format not valid for class ' . $class_name);
}
self::$loaded_classes[$class_name] = get_object_vars($this);
} else {
foreach (self::$loaded_classes[$class_name] as $key => $value) {
$this->{$key} = $value;
}
}
if (null !== $translator) {
$this->translator = $translator;
}
if ($id) {
$this->id = (int) $id;
$this->loadFields();
}
if ($id_lang) {
$this->id_lang = (int) $id_lang;
}
if ($id_shop) {
$this->id_shop = (int) $id_shop;
}
}
/**
* Save object
*
* @param bool $null_values
* @param bool $auto_date
* @return bool
*/
public function save($null_values = false, $auto_date = true)
{
return (int) $this->id > 0 ? $this->update($null_values) : $this->add($auto_date, $null_values);
}
/**
* Add object to database
*
* @param bool $auto_date
* @param bool $null_values
* @return bool
*/
public function add($auto_date = true, $null_values = false)
{
if (isset($this->id) && !$this->force_id) {
$this->id = null;
}
// 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))) {
$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 Exception('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);
}
}
}
}
return $result;
}
/**
* Duplicate object
*
* @return ObjectModel|false
*/
public function duplicateObject()
{
$definition = self::getDefinition($this);
$res = Db::getInstance()->getRow(
'SELECT * FROM `' . _DB_PREFIX_ . $definition['table'] . '` WHERE `' . $definition['primary'] . '` = ' . (int) $this->id
);
if (!$res) {
return false;
}
unset($res[$definition['primary']]);
foreach ($res as $field => &$value) {
if (isset($definition['fields'][$field])) {
$value = self::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_ . $definition['table'] . '_lang`
WHERE `' . $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] = self::formatValue(
$value,
$definition['fields'][$field]['type'],
false,
true,
!empty($definition['fields'][$field]['allow_null'])
);
}
}
}
foreach ($result as $row2) {
$row2[$definition['primary']] = (int) $object_id;
if (!Db::getInstance()->insert($definition['table'] . '_lang', $row2)) {
return false;
}
}
}
$object_duplicated = new $definition['classname']((int) $object_id);
$object_duplicated->duplicateShops((int) $this->id);
return $object_duplicated;
}
/**
* Update object in database
*
* @param bool $null_values
* @return bool
*/
public function update($null_values = false)
{
$this->clearCache();
// Automatically fill dates
if (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 update
if (!$result = Db::getInstance()->update($this->def['table'], $this->getFields(), '`' . pSQL($this->def['primary']) . '` = ' . (int) $this->id, 0, $null_values)) {
return false;
}
// Database update for multishop fields related to the object
if (Shop::isTableAssociated($this->def['table'])) {
$fields = $this->getFieldsShop();
if (is_array($fields)) {
$fields[$this->def['primary']] = (int) $this->id;
foreach ($id_shop_list as $id_shop) {
$fields['id_shop'] = (int) $id_shop;
$where = $this->def['primary'] . ' = ' . (int) $this->id . ' AND id_shop = ' . (int) $id_shop;
$result &= Db::getInstance()->update($this->def['table'] . '_shop', $fields, $where, 0, $null_values);
}
}
}
// Database update for multilingual fields related to the object
if (!empty($this->def['multilang'])) {
$fields = $this->getFieldsLang();
if (is_array($fields)) {
$shops = Shop::getCompleteListOfShopsID();
$asso = Shop::getAssoTable($this->def['table'] . '_lang');
foreach ($fields as $field) {
$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;
$where = $this->def['primary'] . ' = ' . (int) $this->id . ' AND id_lang = ' . (int) $field['id_lang'] . ' AND id_shop = ' . (int) $id_shop;
$result &= Db::getInstance()->update($this->def['table'] . '_lang', $field, $where, 0, $null_values);
}
} else {
$where = $this->def['primary'] . ' = ' . (int) $this->id . ' AND id_lang = ' . (int) $field['id_lang'];
$result &= Db::getInstance()->update($this->def['table'] . '_lang', $field, $where, 0, $null_values);
}
}
}
}
return $result;
}
/**
* Delete object from database
*
* @return bool
*/
public function delete()
{
$result = true;
// Remove from database
if (!$result = Db::getInstance()->delete($this->def['table'], '`' . $this->def['primary'] . '` = ' . (int) $this->id)) {
return false;
}
// Database deletion for multilingual fields related to the object
if (!empty($this->def['multilang'])) {
$result &= Db::getInstance()->delete($this->def['table'] . '_lang', '`' . $this->def['primary'] . '` = ' . (int) $this->id);
}
// Database deletion for multishop fields related to the object
if (Shop::isTableAssociated($this->def['table'])) {
$result &= Db::getInstance()->delete($this->def['table'] . '_shop', '`' . $this->def['primary'] . '` = ' . (int) $this->id);
}
$this->clearCache();
return $result;
}
/**
* Delete selection
*
* @param array $ids
* @return bool
*/
public function deleteSelection(array $ids)
{
$result = true;
foreach ($ids as $id) {
$this->id = (int) $id;
$result = $result && $this->delete();
}
return $result;
}
/**
* Validate fields
*
* @param bool $die
* @param bool $error_return
* @return bool|string
*/
public function validateFields($die = true, $error_return = false)
{
$error = true;
$this->cacheFieldsRequiredDatabase();
$required_fields = (isset($this->id) && $this->id) ? $this->getCachedFieldsRequiredDatabase($all = false) : $this->getCachedFieldsRequiredDatabase($all = true);
if ($required_fields) {
$fields = $this->getFields();
foreach ($required_fields as $field) {
if (!isset($fields[$field]) || !$fields[$field]) {
$error = $this->displayFieldName($field, get_class($this), true);
break;
}
}
}
if (is_string($error)) {
if ($die) {
throw new Exception($error);
}
return $error_return ? $error : false;
}
return true;
}
/**
* Get fields
*
* @return array
*/
public function getFields()
{
$this->validateFields();
$fields = $this->formatFields(self::FORMAT_COMMON);
foreach ($this->def['fields'] as $field => $data) {
if ($field == 'id_' . $this->def['table']) {
$fields[$field] = $this->id;
}
}
return $fields;
}
/**
* Get fields shop
*
* @return array
*/
public function getFieldsShop()
{
$fields = $this->formatFields(self::FORMAT_SHOP);
return $fields;
}
/**
* Get fields lang
*
* @return array
*/
public function getFieldsLang()
{
$fields = $this->formatFields(self::FORMAT_LANG);
return $fields;
}
/**
* Format fields
*
* @param int $type
* @param int|null $id_lang
* @return array
*/
protected function formatFields($type, $id_lang = null)
{
$fields = [];
if (in_array($type, [self::FORMAT_LANG, self::FORMAT_SHOP])) {
$fields['id_' . $this->def['table']] = (int) $this->id;
}
if ($type == self::FORMAT_LANG) {
$fields['id_lang'] = (int) $id_lang;
}
if ($type == self::FORMAT_SHOP && !Shop::isTableAssociated($this->def['table'])) {
return $fields;
}
foreach ($this->def['fields'] as $field => $data) {
if (is_array($this->update_fields) && !in_array($field, $this->update_fields)) {
continue;
}
if ($type == self::FORMAT_LANG && empty($data['lang'])) {
continue;
}
if ($type == self::FORMAT_SHOP && empty($data['shop'])) {
continue;
}
if (isset($this->{$field})) {
$fields[$field] = self::formatValue($this->{$field}, $data['type'], false, true, (isset($data['allow_null']) ? $data['allow_null'] : false));
}
}
return $fields;
}
/**
* Format value
*
* @param mixed $value
* @param int $type
* @param bool $with_quotes
* @param bool $purify
* @param bool $allow_null
* @return string
*/
public static function formatValue($value, $type, $with_quotes = false, $purify = true, $allow_null = false)
{
if ($allow_null && $value === null) {
return 'NULL';
}
switch ($type) {
case self::TYPE_INT:
return (int) $value;
case self::TYPE_BOOL:
return (int) $value;
case self::TYPE_FLOAT:
return (float) $value;
case self::TYPE_DATE:
if (!$value) {
return 'NULL';
}
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:
return $value;
case self::TYPE_NOTHING:
return $value;
case self::TYPE_STRING:
default:
if ($with_quotes) {
return '\'' . pSQL($value) . '\'';
}
return pSQL($value);
}
}
/**
* Hydrate object
*
* @param array $data
* @param int|null $id_lang
* @return ObjectModel
*/
public function hydrate(array $data, $id_lang = null)
{
foreach ($data as $key => $value) {
if (property_exists($this, $key)) {
$this->{$key} = $value;
}
}
if ($id_lang) {
$this->id_lang = (int) $id_lang;
}
return $this;
}
/**
* Hydrate collection
*
* @param string $class
* @param array $datas
* @param int|null $id_lang
* @return array
*/
public static function hydrateCollection($class, array $datas, $id_lang = null)
{
$collection = [];
$fields = [];
if ($datas) {
$fields = array_keys($datas[0]);
}
foreach ($datas as $row) {
$obj = new $class();
$obj->hydrate($row, $id_lang);
$collection[] = $obj;
}
return $collection;
}
/**
* Get definition
*
* @param string|ObjectModel $class
* @param string|null $field
* @return array
*/
public static function getDefinition($class, $field = null)
{
if (is_object($class)) {
$class = get_class($class);
}
if (!isset(self::$definition[$class])) {
throw new Exception('Definition not found for class ' . $class);
}
if ($field === null) {
return self::$definition[$class];
}
return isset(self::$definition[$class]['fields'][$field]) ? self::$definition[$class]['fields'][$field] : null;
}
/**
* Display field name
*
* @param string $field
* @param string $class
* @param bool $htmlentities
* @param Context|null $context
* @return string
*/
public static function displayFieldName($field, $class = __CLASS__, $htmlentities = true, ?Context $context = null)
{
if (!$context) {
$context = Context::getContext();
}
$definition = self::getDefinition($class);
if (isset($definition['fields'][$field]['title'])) {
$field_name = $definition['fields'][$field]['title'];
} else {
$field_name = $field;
}
return $htmlentities ? htmlentities($field_name, ENT_QUOTES, 'utf-8') : $field_name;
}
/**
* Clear cache
*
* @param bool $all
*/
public function clearCache($all = false)
{
if ($all) {
self::$loaded_classes = [];
}
}
/**
* Enable cache
*/
public static function enableCache()
{
self::$cache_objects = true;
}
/**
* Disable cache
*/
public static function disableCache()
{
self::$cache_objects = false;
}
/**
* Load fields
*/
protected function loadFields()
{
// Load object from database if object id is present
$sql = 'SELECT * FROM `' . _DB_PREFIX_ . $this->def['table'] . '` WHERE `' . $this->def['primary'] . '` = ' . (int) $this->id;
$result = Db::getInstance()->getRow($sql);
if ($result) {
$this->hydrate($result);
}
}
/**
* Get fully qualified name
*
* @return string
*/
private function getFullyQualifiedName()
{
return str_replace('\\', '', get_class($this));
}
/**
* Get object name
*
* @return string
*/
public function getObjectName()
{
return get_class($this);
}
}