diff --git a/config/schema.json b/config/schema.json index 0252e8ac5..e2063e4d5 100644 --- a/config/schema.json +++ b/config/schema.json @@ -108,14 +108,14 @@ }, "DB_DBMS": { "type": "string", - "description": "Database Management System running on the DB host. Possible values are: 'MYSQL', 'SQLSRV', 'MARIADB', 'POSTGRE', 'ORACLE'", + "description": "Database Management System running on the DB host. Possible values are: 'MYSQL', 'SQLSRV', 'MARIADB', 'POSTGRESQL', 'ORACLE'", "instant": true, "default": "MYSQL", "example": "MARIADB" }, "DB_CHARSET": { "type": "string", - "description": "Charset to use when manipulating strings with DBMS. Possible values are: 'utf8' (3 bytes) or 'utf8mb4' (4 bytes).", + "description": "Charset encoding to use when manipulating strings with DBMS. Possible values are: 'utf8' (3 bytes) or 'utf8mb4' (4 bytes).", "instant": true, "default": "utf8mb4", "example": "utf8" diff --git a/eq.lib.php b/eq.lib.php index 7ffbfcb78..90a1ed594 100644 --- a/eq.lib.php +++ b/eq.lib.php @@ -350,7 +350,7 @@ function defined($name) { } /** - * Retrieve a configuraiton parameter as a constant. + * Retrieve a configuration parameter as a constant. */ function constant($name, $default=null) { return (isset($GLOBALS['QN_CONFIG_ARRAY'][$name]))?$GLOBALS['QN_CONFIG_ARRAY'][$name]:$default; @@ -803,7 +803,7 @@ public static function announce(array $announcement) { // convert value from input format + validate type and usage constraints $f = Fields::create($config); // raises an Exception if assignment is not possible - $f->set($body[$param], 'json'); + $f->set($body[$param], 'json'); // not explicit type, but Content-Type from HTTP REQUEST try { $f->validate(); $result[$param] = $f->get(); @@ -1009,7 +1009,7 @@ public static function run($type, $operation, $body=[], $root=false) { if(count($parts) > 0) { // use first part as package name $resolved['package'] = array_shift($parts); - // use reamining parts to build script path + // use remaining parts to build script path if(count($parts) > 0) { $resolved['script'] = implode('/', $parts).'.php'; } @@ -1062,12 +1062,8 @@ public static function run($type, $operation, $body=[], $root=false) { } } - // #todo - remove this: L10N_TIMEZONE must be used in controllers producing front-end data (involving some output impacted by localization) - if(defined('L10N_TIMEZONE')) { - date_default_timezone_set(constant('L10N_TIMEZONE')); - } - // #todo - we should run this instead - in the meanwhile, value is the one set in schema.json - // date_default_timezone_set('UTC'); + // force timezone to UTC + date_default_timezone_set('UTC'); if(!$root) { // include and execute requested script @@ -1128,7 +1124,7 @@ public static function load_class($class_name) { // mark class as being loaded $GLOBALS['eQual_loading_classes'][$class_name] = true; $file_path = QN_BASEDIR.'/lib/'.str_replace('\\', '/', $class_name); - // use 'class.php' extention + // use 'class.php' extension if(file_exists($file_path.'.class.php')) { $result = include_once $file_path.'.class.php'; } diff --git a/lib/equal/db/DBConnection.class.php b/lib/equal/db/DBConnection.class.php index 643f43eb3..e4ae7029d 100644 --- a/lib/equal/db/DBConnection.class.php +++ b/lib/equal/db/DBConnection.class.php @@ -19,12 +19,28 @@ protected function __construct() { switch(constant('DB_DBMS')) { case 'MARIADB': case 'MYSQL' : - $this->dbConnection = new DBManipulatorMySQL(constant('DB_HOST'), constant('DB_PORT'), constant('DB_NAME'), constant('DB_USER'), constant('DB_PASSWORD')); + $this->dbConnection = new DBManipulatorMySQL( + constant('DB_HOST'), + constant('DB_PORT'), + constant('DB_NAME'), + constant('DB_USER'), + constant('DB_PASSWORD'), + constant('DB_CHARSET'), + constant('DB_COLLATION') + ); break; case 'SQLSRV' : - $this->dbConnection = new DBManipulatorSqlSrv(constant('DB_HOST'), constant('DB_PORT'), constant('DB_NAME'), constant('DB_USER'), constant('DB_PASSWORD')); + $this->dbConnection = new DBManipulatorSqlSrv( + constant('DB_HOST'), + constant('DB_PORT'), + constant('DB_NAME'), + constant('DB_USER'), + constant('DB_PASSWORD'), + constant('DB_CHARSET'), + constant('DB_COLLATION') + ); break; - case 'POSTGRE' : + case 'POSTGRESQL' : // #todo break; case 'ORACLE' : @@ -38,8 +54,19 @@ protected function __construct() { // add replica members, if any $i = 1; - while(defined('DB_'.$i.'_HOST') && defined('DB_'.$i.'_PORT') && defined('DB_'.$i.'_USER') && defined('DB_'.$i.'_PASSWORD') && defined('DB_'.$i.'_NAME')) { - $this->addReplicaMember(constant('DB_'.$i.'_HOST'), constant('DB_'.$i.'_PORT'), constant('DB_'.$i.'_NAME'), constant('DB_'.$i.'_USER'), constant('DB_'.$i.'_PASSWORD')); + while(defined('DB_'.$i.'_HOST') + && defined('DB_'.$i.'_PORT') + && defined('DB_'.$i.'_USER') + && defined('DB_'.$i.'_PASSWORD') + && defined('DB_'.$i.'_NAME')) { + + $this->addReplicaMember( + constant('DB_'.$i.'_HOST'), + constant('DB_'.$i.'_PORT'), + constant('DB_'.$i.'_NAME'), + constant('DB_'.$i.'_USER'), + constant('DB_'.$i.'_PASSWORD') + ); ++$i; } } @@ -79,9 +106,8 @@ public function disconnect() { return $this->dbConnection->disconnect(); } - /** - * Magic overloading method: catch any call and relay it to dbConnection object + * Magic overloading method: catch any call and relay it to DBConnection object * * @param string $name * @param array $arguments diff --git a/lib/equal/db/DBManipulator.class.php b/lib/equal/db/DBManipulator.class.php index fc8fb9205..3becd4376 100644 --- a/lib/equal/db/DBManipulator.class.php +++ b/lib/equal/db/DBManipulator.class.php @@ -9,62 +9,65 @@ class DBManipulator { /** - * DB server hostname + * DB server hostname. * * @var string - * @access protected */ protected $host; /** - * DB server connection port + * DB server TCP connection port. * * @var integer - * @access protected */ protected $port; /** - * DB name + * DB name to use for SQL queries. * * @var string - * @access protected */ protected $db_name; + /** + * Charset encoding to use for communications with DBMS. + * + * @var string + */ + protected $charset; + + /** + * Collation to use for storing data (applied on new tables and columns). + * + * @var string + */ + protected $collation; /** * DB user * * @var string - * @access protected */ protected $user_name; - /** * DB password * * @var string - * @access protected */ protected $password; - /** * Latest error id * * @var integer - * @access protected */ protected $last_id; - /** * Number of rows affected by last query * * @var integer - * @access protected */ protected $affected_rows; @@ -72,12 +75,11 @@ class DBManipulator { * Latest query * * @var string - * @access protected */ protected $last_query; /** - * @var \mysqli + * @var mixed */ protected $dbms_handler; @@ -96,21 +98,23 @@ class DBManipulator { * Initialize the DBMS data for SQL transactions * * @access public - * @param string DB server hostname + * @param string DB server hostname * @param string DB name * @param string Username to use for the connection * @param string Password to use for the connection - * @return void + * @return void */ - public final function __construct($host, $port, $db, $user, $pass) { + public final function __construct($host, $port, $db, $user, $pass, $charset=null, $collation=null) { $this->host = $host; $this->port = $port; $this->db_name = $db; $this->user_name = $user; $this->password = $pass; - $this->dbms_handler = null; + $this->charset = $charset; + $this->collation = $collation; - $this->members = []; + $this->dbms_handler = null; + $this->members = []; } diff --git a/lib/equal/db/DBManipulatorMySQL.class.php b/lib/equal/db/DBManipulatorMySQL.class.php index 532f3b8a2..116e87acd 100644 --- a/lib/equal/db/DBManipulatorMySQL.class.php +++ b/lib/equal/db/DBManipulatorMySQL.class.php @@ -51,8 +51,8 @@ public function connect($auto_select=true) { if($auto_select) { if($this->dbms_handler = mysqli_connect($this->host, $this->user_name, $this->password, $this->db_name, $this->port)) { if($result = $this->select($this->db_name)) { - $query = 'set names '.constant('DB_CHARSET'); - $query .= (defined('DB_COLLATION'))?' collate '.constant('DB_COLLATION'):''; + $query = 'set names '.$this->charset; + $query .= ($this->collation)?' collate '.$this->collation:''; mysqli_query($this->dbms_handler, $query); $result = true; } @@ -95,7 +95,7 @@ public function disconnect() { } public function createDatabase($db_name) { - $query = "CREATE DATABASE IF NOT EXISTS $db_name CHARACTER SET ".constant('DB_CHARSET')." COLLATE ".constant('DB_COLLATION').';'; + $query = "CREATE DATABASE IF NOT EXISTS $db_name CHARACTER SET ".$this->charset." COLLATE ".$this->collation.';'; $this->sendQuery($query); } @@ -122,7 +122,7 @@ public function getTableConstraints($table_name) { public function getQueryCreateTable($table_name) { // #memo - we must add at least one column, so as a convention we add the id column - return "CREATE TABLE IF NOT EXISTS `{$table_name}` (`id` int(11) NOT NULL AUTO_INCREMENT PRIMARY KEY) DEFAULT CHARSET=".constant('DB_CHARSET')." COLLATE=".constant('DB_COLLATION').";"; + return "CREATE TABLE IF NOT EXISTS `{$table_name}` (`id` int(11) NOT NULL AUTO_INCREMENT PRIMARY KEY) DEFAULT CHARSET=".$this->charset." COLLATE=".$this->collation.";"; } /** @@ -133,7 +133,7 @@ public function getQueryCreateTable($table_name) { * 'type' => int(11), * 'null' => false, * 'default' => 0, - * 'auto_incremnt' => false, + * 'auto_increment' => false, * 'primary' => false, * 'index' => false * ] diff --git a/lib/equal/db/DBManipulatorSqlSrv.class.php b/lib/equal/db/DBManipulatorSqlSrv.class.php index 7c4b913ce..3a5329e75 100644 --- a/lib/equal/db/DBManipulatorSqlSrv.class.php +++ b/lib/equal/db/DBManipulatorSqlSrv.class.php @@ -72,7 +72,7 @@ public function connect($auto_select=true) { if($auto_select) { $connection_info['Database'] = $this->db_name; - $connection_info['CharacterSet'] = constant('DB_CHARSET'); + $connection_info['CharacterSet'] = $this->charset; } if($this->dbms_handler = sqlsrv_connect($this->host, $connection_info)) { @@ -113,7 +113,7 @@ public function disconnect() { } public function createDatabase($db_name) { - $query = "USE master; CREATE DATABASE $db_name COLLATE ".constant('DB_COLLATION').";"; + $query = "USE master; CREATE DATABASE $db_name COLLATE ".$this->collation.";"; $this->sendQuery($query, 'create'); } @@ -157,7 +157,7 @@ public function getQueryCreateTable($table_name) { * 'type' => int(11), * 'null' => false, * 'default' => 0, - * 'auto_incremnt' => false, + * 'auto_increment' => false, * 'primary' => false, * 'index' => false * ] @@ -183,7 +183,7 @@ public function getQueryAddColumn($table_name, $column_name, $def) { $sql .= ';'; if(isset($def['primary']) && $def['primary']) { - $sql .= "ALTER TABLE [{$table_name}] ADD CONSTRAINT PK_{$column_name} PRIMARY KEY({$def['type']});"; + $sql .= "ALTER TABLE [{$table_name}] ADD CONSTRAINT PK_{$column_name} PRIMARY KEY({$def['type']});"; } return $sql; diff --git a/lib/equal/http/HttpRequest.class.php b/lib/equal/http/HttpRequest.class.php index 6fcf3d990..78e871c76 100644 --- a/lib/equal/http/HttpRequest.class.php +++ b/lib/equal/http/HttpRequest.class.php @@ -56,8 +56,7 @@ public function __construct($headline='', $headers=[], $body='') { } if(!HttpUri::isValid($uri)) { - echo 'invalid'; - // #todo : should we raise an Exception ? + throw new \Exception('invalid URI :'.$uri, QN_ERROR_UNKNOWN); } } // 3) retrieve method diff --git a/lib/equal/http/HttpResponse.class.php b/lib/equal/http/HttpResponse.class.php index ecb060c4f..b318e572a 100644 --- a/lib/equal/http/HttpResponse.class.php +++ b/lib/equal/http/HttpResponse.class.php @@ -33,10 +33,11 @@ public function __construct($headline, $headers=[], $body='') { } } + /** - * Sends a HTTP response to the output stream (stdout) - * This method can only be used with PHP context - * and is used as a helper to build the actual response of the current request + * Sends a HTTP response to the output stream (stdout). + * This method is meant to be used in conjunction with PHP context, + * and serves as helper to build the actual response of the current request. * */ public function send() { @@ -51,26 +52,43 @@ public function send() { $headers = $this->getHeaders(true); foreach($headers as $header => $value) { // we'll set content length afterward - if($header == 'Content-Length') continue; + if($header == 'Content-Length') { + continue; + } // cookies are handled in a second pass - if($header == 'Cookie') continue; + if($header == 'Cookie') { + continue; + } header($header.': '.$value); } // set cookies, if any foreach($this->headers()->getCookies() as $cookie => $value) { $hostname = isset($_SERVER['HTTP_HOST'])?$_SERVER['HTTP_HOST']:'localhost'; + // make sure the hostname does not contain a port number + $hostname = substr($hostname.':', 0, strpos($hostname.':', ':')); $params = $this->headers()->getCookieParams($cookie); $expires = (isset($params['expires']))?$params['expires']:time()+60*60*24*365; $path = (isset($params['path']))?$params['path']:'/'; $domain = (isset($params['domain']))?$params['domain']:$hostname; $secure = (isset($params['secure']))?$params['secure']:false; $httponly = (isset($params['httponly']))?$params['httponly']:false; - // #todo - handle samesite (as of PHP 7.3) - $samesite = (isset($params['samesite']))?$params['samesite']:false; - setcookie($cookie, $value, $expires, $path, $domain, $secure, $httponly); // equivalent to header("Set-Cookie: cookiename=cookievalue; expires=Tue, 06-Jan-2018 23:39:49 GMT; path=/; domain=example.net"); + setcookie($cookie, $value, $expires, $path, $domain, $secure, $httponly); + // #todo - handle samesite (as of PHP 7.3) + /* + $samesite = (isset($params['samesite']) && in_array($params['samesite'], ['None', 'Lax', 'Strict']))?$params['samesite']:'None'; + setcookie($name, $value, [ + 'expires' => $expires, + 'path' => $path, + 'domain' => $domain, + 'secure' => $secure, + 'httponly' => $httponly, + 'samesite' => 'None', + ]); + */ } + // retrieve body $body = $this->body(); diff --git a/lib/equal/http/HttpUri.class.php b/lib/equal/http/HttpUri.class.php index d8e7bdd7e..854aaf603 100644 --- a/lib/equal/http/HttpUri.class.php +++ b/lib/equal/http/HttpUri.class.php @@ -29,7 +29,7 @@ class HttpUri { * */ - + /** @var string $parts stores the parts of current URI */ private $parts = null; @@ -41,7 +41,7 @@ class HttpUri { 'https' => 443 ]; - + public function __construct($uri='') { // init $parts member to allow further methods calls even if provided URI is not valid $this->parts = [ @@ -54,11 +54,11 @@ public function __construct($uri='') { 'user' => null, 'pass' => null ]; - + $this->setUri($uri); - } - - + } + + /** * * re-build final URI from parts @@ -76,22 +76,22 @@ public function __toString() { return $this->getScheme().'://'.$this->getAuthority().$this->getPath().$query; } - // retrieve parameters associative array from current query string + // retrieve parameters associative array from current query string public function getParams() { $params = []; // retrieve parameters associative array from current query string parse_str($this->getQuery(), $params); return $params; } - + /** * Get the value of specified param from the query string, fallback to $default if not found. * * - * @return mixed If $param is an array of parameters names, returns an assiociative array containing values for each given parameter, otherwise returns the value of a single parameter. If given parameter is not found, returns specified default value (fallback to null) + * @return mixed If $param is an array of parameters names, returns an assiociative array containing values for each given parameter, otherwise returns the value of a single parameter. If given parameter is not found, returns specified default value (fallback to null) */ public function get($param, $default=null) { - // retrieve parameters associative array from current query string + // retrieve parameters associative array from current query string $params = $this->getParams(); // bulk get : $param is an array of parameters names if(is_array($param)) { @@ -110,18 +110,18 @@ public function get($param, $default=null) { $res = $default; if(isset($params[$param])) $res = $params[$param]; } - return $res; + return $res; } /** * Assign a new parameter to URI query string, or update an existing one to a new value. * This method overwrites existing parameter(s) from query string, if any. - * + * * @param $param mixed(string|array) For single assignement, $param is the name of the parameter to be set. In case of bulk assign, $param is an associative array with keys and values respectively holding parameters names and values. * @param $value mixed If $param is an array, $value is not taken under account (this argument is therefore optional) * @return HttpUri Returns current instance - */ - public function set($param, $value=null) { + */ + public function set($param, $value=null) { $params = []; // retrieve parameters associative array from current query string parse_str($this->getQuery(), $params); @@ -135,10 +135,10 @@ public function set($param, $value=null) { $params[$param] = $value; } // update query string - $this->setQuery(http_build_query($params)); + $this->setQuery(http_build_query($params)); return $this; } - + public function setUri($uri) { if(self::isValid($uri)) { $this->parts = parse_url($uri); @@ -150,7 +150,7 @@ public function setScheme($scheme) { $this->parts['scheme'] = strtolower($scheme); return $this; } - + public function setHost($host) { $this->parts['host'] = strtolower($host); return $this; @@ -175,7 +175,7 @@ public function setFragment($fragment) { $this->parts['fragment'] = $fragment; return $this; } - + public function setUser($user) { $this->parts['user'] = $user; return $this; @@ -185,7 +185,7 @@ public function setPassword($password) { $this->parts['pass'] = $password; return $this; } - + /** * Checks validity of provided URI * with support for internationalized domain name (IDN) support (non-ASCII chars) @@ -228,11 +228,11 @@ public function scheme() { } return $this->setScheme(...$args); } - + public function getHost() { return isset($this->parts['host'])?$this->parts['host']:''; } - + public function host() { $args = func_get_args(); if(count($args) < 1) { @@ -264,10 +264,10 @@ public function getAuthority() { $port = ''; } else $port = ':'.$port; - + return $user_info.$this->getHost().$port; } - + public function getPath() { return isset($this->parts['path'])?$this->parts['path']:''; } @@ -279,7 +279,7 @@ public function getQuery() { public function getFragment() { return isset($this->parts['fragment'])?$this->parts['fragment']:''; } - + public function getUser() { return isset($this->parts['user'])?$this->parts['user']:''; } @@ -289,7 +289,7 @@ public function getPassword() { } public function getBasePath() { - return str_replace(DIRECTORY_SEPARATOR, '/', dirname($this->parts['path'])); + return str_replace(DIRECTORY_SEPARATOR, '/', dirname($this->parts['path'])); } public function query() { @@ -301,5 +301,5 @@ public function query() { $query = $args[0]; return $this->setQuery($query); } - } + } } \ No newline at end of file diff --git a/lib/equal/orm/ObjectManager.class.php b/lib/equal/orm/ObjectManager.class.php index da378fee7..9f4d432e6 100644 --- a/lib/equal/orm/ObjectManager.class.php +++ b/lib/equal/orm/ObjectManager.class.php @@ -126,43 +126,45 @@ class ObjectManager extends Service { 'many2many' => array('contains'), ]; + /** + * Mapping of ORM types with their sized notations. + * + * Size is expressed this way: type[(length|precision[,scale])] + * and focus on the storage, in bytes, to allocate (rather than what value range can actually be stored). + * + * Each DBManipulator Service is in charge of converting ORM types matching its own native type. + * Types that are ambiguous regarding their size (string, text, binary) or require additional info (decimal place for real numbers representations). + * All ORM types have an equivalent in SQL. + */ public static $types_associations = [ - 'boolean' => 'tinyint(4)', - 'integer' => 'int(11)', - 'float' => 'decimal(10,2)', - 'string' => 'varchar(255)', - 'text' => 'text', - 'date' => 'date', - 'time' => 'time', - 'datetime' => 'datetime', - 'timestamp' => 'timestamp', - /* #deprecated */ - 'file' => 'longblob', - 'binary' => 'longblob', - 'many2one' => 'int(11)' + 'integer' => 'integer(4)', // 4 bytes integer + 'float' => 'float(10,4)', // 10 digits float holding 4 decimal digits + 'string' => 'string(255)', // 255 bytes string + 'text' => 'string(32000)', // 32 kilobytes string + 'binary' => 'binary(64000000)' // 64MB binary value ]; public static $usages_associations = [ - 'amount/percent' => 'decimal(5,4)', // float in interval [0, 1] (suitable for vat rate, completeness, success rate) - 'amount/rate' => 'decimal(10,4)', // float to be used as factor, with 4 decimal digits (change rate) - 'amount/rate:4' => 'decimal(10,4)', // float to be used as factor, with 4 decimal digits (change rate) - 'amount/money' => 'decimal(15,2)', - 'amount/money:2' => 'decimal(15,2)', - 'amount/money:4' => 'decimal(13,4)', // GAAP compliant - 'coordinate' => 'decimal(9,6)', // any float value from -180 to 180 with 6 decimal digits - 'coordinate/decimal' => 'decimal(9,6)', - 'country/iso-3166.numeric' => 'int', // 3-digits country code (ISO 3166-1) - 'country/iso-3166:2' => 'char(2)', // '2-letters country code (ISO 3166-1) - 'country/iso-3166:3' => 'char(3)', // '3-letters country code (ISO 3166-1) - 'currency/iso-4217' => 'char(3)', - 'language/iso-639' => 'char(5)', // locale representation : iso-639:2 OR {iso-639:2}-{iso-3166:2} - 'language/iso-639:2' => 'char(2)', // languages codes alpha 2 (ISO 639-1) - 'language/iso-639:3' => 'char(3)', // languages codes alpha 3 (ISO 639-3) - 'markup/html' => 'mediumtext', - 'text/html' => 'mediumtext', - 'text/plain' => 'text', // 64k chars - 'email' => 'varchar(255)', - 'phone' => 'varchar(20)' + 'amount/percent' => 'float(5,4)', // float in interval [0, 1] (suitable for vat rate, completeness, success rate) + 'amount/rate' => 'float(10,4)', // float to be used as factor, with 4 decimal digits (change rate) + 'amount/rate:4' => 'float(10,4)', // float to be used as factor, with 4 decimal digits (change rate) + 'amount/money' => 'float(15,2)', + 'amount/money:2' => 'float(15,2)', + 'amount/money:4' => 'float(13,4)', // GAAP compliant + 'coordinate' => 'float(9,6)', // any float value from -180 to 180 with 6 decimal digits + 'coordinate/decimal' => 'float(9,6)', + 'country/iso-3166.numeric' => 'integer(1)', // 3-digits country code (ISO 3166-1) + 'country/iso-3166:2' => 'string(2)', // 2-letters country code (ISO 3166-1) + 'country/iso-3166:3' => 'string(3)', // 3-letters country code (ISO 3166-1) + 'currency/iso-4217' => 'string(3)', + 'language/iso-639' => 'string(5)', // locale representation : iso-639:2 OR {iso-639:2}-{iso-3166:2} + 'language/iso-639:2' => 'string(2)', // languages codes alpha 2 (ISO 639-1) + 'language/iso-639:3' => 'string(3)', // languages codes alpha 3 (ISO 639-3) + 'markup/html' => 'string(64000)', // 64k chars html + 'text/html' => 'string(64000)', + 'text/plain' => 'string(64000)', // 64k chars text + 'email' => 'string(255)', + 'phone' => 'string(20)' ]; /** diff --git a/lib/equal/orm/usages/Usage.class.php b/lib/equal/orm/usages/Usage.class.php index b0cab4ce4..88b682db3 100644 --- a/lib/equal/orm/usages/Usage.class.php +++ b/lib/equal/orm/usages/Usage.class.php @@ -21,6 +21,9 @@ abstract class Usage { abstract public function getSqlType(): string; + /** + * Provides the generic (display) name of the type. + */ abstract public function getType(): string; abstract public function getConstraints(): array; diff --git a/lib/equal/php/Context.class.php b/lib/equal/php/Context.class.php index 1fdec131c..0f8f0eccd 100644 --- a/lib/equal/php/Context.class.php +++ b/lib/equal/php/Context.class.php @@ -303,6 +303,9 @@ private function getHttpUri() { $auth .= '@'; } $host = isset($_SERVER['HTTP_HOST'])?$_SERVER['HTTP_HOST']:'localhost'; + // make sure host does not contain a port number (strip if any) + $host = substr($host.':', 0, strpos($host.':', ':')); + $port = isset($_SERVER['SERVER_PORT'])?$_SERVER['SERVER_PORT']:80; // fallback to current script name (using CLI, REQUEST_URI is not set), i.e. '/index.php' $uri = '/'.$_SERVER['SCRIPT_NAME']; diff --git a/packages/core/actions/init/package.php b/packages/core/actions/init/package.php index 2eb8013eb..0099a2363 100644 --- a/packages/core/actions/init/package.php +++ b/packages/core/actions/init/package.php @@ -63,13 +63,13 @@ $db_class = get_class($db); -// 1) Check in manifest.json for prerequisite initialisation +// 1) Check in manifest.json for prerequisite initialization if(!isset($GLOBALS['QN_INIT_DEPENDENCIES'])) { $GLOBALS['QN_INIT_DEPENDENCIES'] = []; } -// mark current package as being initialised (to prevent recursion) +// mark current package as being initialized (to prevent recursion) $GLOBALS['QN_INIT_DEPENDENCIES'][$params['package']] = true; // retrieve manifest of target package, if any @@ -86,13 +86,20 @@ // initiate dependency packages that are not yet processed, if requested foreach($package_manifest['depends_on'] as $dependency) { if(!isset($GLOBALS['QN_INIT_DEPENDENCIES'][$dependency])) { - // init package of sub packages (perform as root script) - eQual::run('do', 'init_package', [ - 'package' => $dependency, - 'cascade' => $params['cascade'], - 'import' => $params['import'] && $params['import_cascade'] - ], - true); + try { + // init package of sub packages (perform as root script) + eQual::run('do', 'init_package', [ + 'package' => $dependency, + 'cascade' => $params['cascade'], + 'import' => $params['import'] && $params['import_cascade'] + ], + true); + } + catch(Exception $e) { + if($e->getCode()) { + throw new Exception("Unable to initialize dependency package {$dependency}: ".$e->getMessage(), QN_ERROR_UNKNOWN); + } + } } } } @@ -102,7 +109,7 @@ /* start-tables_init */ // retrieve schema for given package -$data = eQual::run('get', 'utils_sql-schema', ['package' => $params['package']]); +$data = eQual::run('get', 'utils_sql-schema', ['package' => $params['package'], 'full' => false]); // push each line/query into an array $queries = explode(";", $data['result']); diff --git a/packages/core/actions/test/package-consistency.php b/packages/core/actions/test/package-consistency.php index 6ee8dca2a..7d733a53c 100644 --- a/packages/core/actions/test/package-consistency.php +++ b/packages/core/actions/test/package-consistency.php @@ -395,7 +395,6 @@ 'date' => array('date', 'datetime'), 'time' => array('time'), 'datetime' => array('datetime'), - 'timestamp' => array('timestamp'), 'selection' => array('char', 'varchar'), 'file' => array('blob', 'mediumblob', 'longblob'), 'binary' => array('blob', 'mediumblob', 'longblob'), diff --git a/packages/core/actions/user/pass-update.php b/packages/core/actions/user/pass-update.php index c51ec06eb..b075b4d78 100644 --- a/packages/core/actions/user/pass-update.php +++ b/packages/core/actions/user/pass-update.php @@ -79,16 +79,10 @@ if(strlen($params['token'])) { // generate a JWT access token $access_token = $auth->token($user_id, constant('AUTH_ACCESS_TOKEN_VALIDITY')); - $refresh_token = $auth->token($user_id, constant('AUTH_REFRESH_TOKEN_VALIDITY')); $response->cookie('access_token', $access_token, [ 'expires' => time() + constant('AUTH_ACCESS_TOKEN_VALIDITY'), 'httponly' => true, 'secure' => constant('AUTH_TOKEN_HTTPS') - ]) - ->cookie('refresh_token', $refresh_token, [ - 'expires' => time() + constant('AUTH_REFRESH_TOKEN_VALIDITY'), - 'httponly' => true, - 'secure' => constant('AUTH_TOKEN_HTTPS') ]); } diff --git a/packages/core/actions/user/signout.php b/packages/core/actions/user/signout.php index 6c3ea3f1f..e07ea0b13 100644 --- a/packages/core/actions/user/signout.php +++ b/packages/core/actions/user/signout.php @@ -20,7 +20,6 @@ $context->httpResponse() - ->cookie('access_token', '', ['expires' => time(), 'httponly' => true, 'secure' => AUTH_TOKEN_HTTPS]) - ->cookie('refresh_token', '', ['expires' => time(), 'httponly' => true, 'secure' => AUTH_TOKEN_HTTPS]) + ->cookie('access_token', '', ['expires' => time(), 'httponly' => true, 'secure' => constant('AUTH_TOKEN_HTTPS')]) ->status(204) ->send(); \ No newline at end of file diff --git a/packages/core/classes/Mail.class.php b/packages/core/classes/Mail.class.php index fd75294f5..da85ff9e3 100644 --- a/packages/core/classes/Mail.class.php +++ b/packages/core/classes/Mail.class.php @@ -201,9 +201,16 @@ public static function flush() { // setup SMTP settings $mailer = new \Swift_Mailer($transport); + // #todo - store as setting + $max = 10; + $i = 0; // loop through messages foreach($queue as $file => $message) { - + ++$i; + // prevent handling handling more than $max messages + if($i > $max) { + break; + } $body = (isset($message['body']))?$message['body']:''; $subject = (isset($message['subject']))?$message['subject']:''; @@ -239,13 +246,15 @@ public static function flush() { } // send email $mailer->send($envelope); - // upon sucessful sending, remove the mail from the outbox + // upon successful sending, remove the mail from the outbox $filename = self::MESSAGE_FOLDER.'/'.$file; unlink($filename); // if the message is linked to a core\Mail object, update the latter's status if(isset($message['id'])) { self::id($message['id'])->update(['status' => 'sent', 'response_status' => 250]); } + // prevent flooding the SMTP + usleep(100 *1000); } catch(\Exception $e) { // sending failed: diff --git a/packages/core/classes/setting/Setting.class.php b/packages/core/classes/setting/Setting.class.php index 56e837ea5..c6d85431b 100644 --- a/packages/core/classes/setting/Setting.class.php +++ b/packages/core/classes/setting/Setting.class.php @@ -19,7 +19,7 @@ public static function getName() { } public static function getDescription() { - return "Configurations parameters may be used by any package and provide specfic information about current installation."; + return "Configurations parameters may be used by any package and provide specific information about current installation."; } public static function getColumns() { @@ -134,42 +134,48 @@ public static function getColumns() { } public static function onupdateCode($om, $ids, $values, $lang) { - $om->update(__CLASS__, $ids, ['name' => null], $lang); + $om->update(self::getType(), $ids, ['name' => null], $lang); + $settings = $om->read(self::getType(), $ids, ['setting_values_ids'], $lang); + foreach($settings as $oid => $setting) { + $om->update(SettingValue::getType(), $setting['setting_values_ids'], ['name' => null], $lang); + } } public static function onupdateSectionId($om, $ids, $values, $lang) { - $om->update(__CLASS__, $ids, ['name' => null, 'section' => null], $lang); + $om->update(self::getType(), $ids, ['name' => null, 'section' => null], $lang); + $settings = $om->read(self::getType(), $ids, ['setting_values_ids'], $lang); + foreach($settings as $oid => $setting) { + $om->update(SettingValue::getType(), $setting['setting_values_ids'], ['name' => null], $lang); + } } public static function onupdatePackage($om, $ids, $values, $lang) { - $om->update(__CLASS__, $ids, ['name' => null], $lang); + $om->update(self::getType(), $ids, ['name' => null], $lang); + $settings = $om->read(self::getType(), $ids, ['setting_values_ids'], $lang); + foreach($settings as $oid => $setting) { + $om->update(SettingValue::getType(), $setting['setting_values_ids'], ['name' => null], $lang); + } } public static function calcSection($om, $oids, $lang) { $result = []; - - $settings = $om->read(__CLASS__, $oids, ['section_id.code'], $lang); - + $settings = $om->read(self::getType(), $oids, ['section_id.code'], $lang); if($settings > 0 && count($settings)) { foreach($settings as $oid => $odata) { $result[$oid] = $odata['section_id.code']; } } - return $result; } public static function calcName($om, $oids, $lang) { $result = []; - - $settings = $om->read(__CLASS__, $oids, ['package', 'section', 'code'], $lang); - + $settings = $om->read(self::getType(), $oids, ['package', 'section', 'code'], $lang); if($settings > 0 && count($settings)) { foreach($settings as $oid => $odata) { $result[$oid] = $odata['package'].'.'.$odata['section'].'.'.$odata['code']; } } - return $result; } @@ -381,4 +387,4 @@ public static function parse_format($format, $map) { return vsprintf($result_format, $values); } -} \ No newline at end of file +} diff --git a/packages/core/classes/setting/SettingChoice.class.php b/packages/core/classes/setting/SettingChoice.class.php index ee5621df2..c6ea3e357 100644 --- a/packages/core/classes/setting/SettingChoice.class.php +++ b/packages/core/classes/setting/SettingChoice.class.php @@ -41,5 +41,4 @@ public static function getColumns() { ]; } - -} \ No newline at end of file +} diff --git a/packages/core/classes/setting/SettingSection.class.php b/packages/core/classes/setting/SettingSection.class.php index 3a9d8b3af..cae728023 100644 --- a/packages/core/classes/setting/SettingSection.class.php +++ b/packages/core/classes/setting/SettingSection.class.php @@ -50,11 +50,10 @@ public static function getColumns() { } public static function onupdateCode($om, $ids, $values, $lang) { - - $sections = $om->read(__CLASS__, $ids, ['settings_ids'], $lang); + $sections = $om->read(self::getType(), $ids, ['settings_ids'], $lang); if($sections > 0 && count($sections)) { foreach($sections as $oid => $odata) { - $om->update('core\setting\Setting', $odata['settings_ids'], ['name' => null], $lang); + $om->update(Setting::getType(), $odata['settings_ids'], ['name' => null, 'section' => null], $lang); } } @@ -65,4 +64,4 @@ public function getUnique() { ['code'] ]; } -} \ No newline at end of file +} diff --git a/packages/core/classes/setting/SettingValue.class.php b/packages/core/classes/setting/SettingValue.class.php index 6fbcf51a2..ec69cf01b 100644 --- a/packages/core/classes/setting/SettingValue.class.php +++ b/packages/core/classes/setting/SettingValue.class.php @@ -25,7 +25,7 @@ public static function getColumns() { 'type' => 'computed', 'result_type' => 'string', 'description' => "Code to serve as reference (might not be unique).", - 'function' => 'core\setting\SettingValue::calcName', + 'function' => 'calcName', 'store' => true, 'readonly' => true ], @@ -57,7 +57,7 @@ public static function getColumns() { public static function calcName($om, $oids, $lang) { $result = []; - $settingValues = $om->read(__CLASS__, $oids, ['setting_id.name'], $lang); + $settingValues = $om->read(self::getType(), $oids, ['setting_id.name'], $lang); foreach($settingValues as $oid => $odata) { $result[$oid] = $odata['setting_id.name']; @@ -71,4 +71,4 @@ public function getUnique() { ['setting_id', 'user_id'] ]; } -} \ No newline at end of file +} diff --git a/packages/core/data/utils/sql-schema.php b/packages/core/data/utils/sql-schema.php index e2e510423..cae21b7c7 100644 --- a/packages/core/data/utils/sql-schema.php +++ b/packages/core/data/utils/sql-schema.php @@ -144,7 +144,7 @@ } elseif($description['type'] == 'many2many') { if(!isset($m2m_tables[$description['rel_table']])) { - $m2m_tables[$description['rel_table']] = array($description['rel_foreign_key'], $description['rel_local_key']); + $m2m_tables[$description['rel_table']] = array(str_replace('_', '', $description['rel_foreign_key']), str_replace('_', '', $description['rel_local_key'])); } } $processed_columns[$table][$field] = true; @@ -159,9 +159,8 @@ foreach($m2m_tables as $table => $columns) { if(!$params['full']) { - $constraint_name = implode('_', $columns); $existing_constraints = $db->getTableConstraints($table); - if(in_array($constraint_name, $existing_constraints)) { + if(count(array_diff($columns, explode('_', $existing_constraints))) <= 0) { continue; } } diff --git a/packages/core/data/utils/sqldesigner/schema.php b/packages/core/data/utils/sqldesigner/schema.php index 9a2034ac2..7680af18c 100644 --- a/packages/core/data/utils/sqldesigner/schema.php +++ b/packages/core/data/utils/sqldesigner/schema.php @@ -19,7 +19,7 @@ 'accept-origin' => '*', 'cacheable' => true ], - 'providers' => ['context', 'orm'] + 'providers' => ['context', 'orm'] ]); @@ -29,7 +29,7 @@ $json = run('get', 'config_classes', ['package' => $params['package']]); $classes = json_decode($json, true); - + $src = << @@ -45,7 +45,7 @@ - + @@ -57,24 +57,24 @@ EOD; - + $types_associations = array( 'boolean' => 'TINYINT(4)', 'integer' => 'INT(11)', 'float' => 'DECIMAL(10,2)', - + 'string' => 'VARCHAR(255)', 'text' => 'MEDIUMTEXT', - 'html' => 'MEDIUMTEXT', - + 'html' => 'MEDIUMTEXT', + 'date' => 'DATETIME', 'time' => 'TIME', 'datetime' => 'DATETIME', - - 'file' => 'MEDIUMBLOB', + + 'file' => 'MEDIUMBLOB', 'binary' => 'MEDIUMBLOB', - + 'many2one' => 'BIGINT(11)', 'many2many' => 'BIGINT(11)' // convention : add/update rel_table ); @@ -96,33 +96,33 @@ $new_table = $xml->addChild('table'); - $new_table->addAttribute('name', $class_name); + $new_table->addAttribute('name', $class_name); foreach($schema as $field => $definition) { - - $type = $definition['type']; - + + $type = $definition['type']; + if(in_array($type, ['one2many', 'alias'])) { // skip for now continue; } - $new_row = $new_table->addChild('row'); + $new_row = $new_table->addChild('row'); if($type == 'function') { $type = $definition['result_type']; } else if(in_array($type, ['many2one', 'many2many'])) { - $new_relation = $new_row->addChild('relation'); - + $new_relation = $new_row->addChild('relation'); + if($type == 'many2many') { // create a new relation table if(!isset($relations[$definition['rel_table']])) { $relations[$definition['rel_table']] = true; - /* append table to XML doc */ + /* append table to XML doc */ $new_rel_table = $xml->addChild('table'); $new_rel_table->addAttribute('name', $definition['rel_table']); - + $new_rel_row = $new_rel_table->addChild('row'); $new_rel_row->addAttribute('name', $definition['rel_foreign_key']); $new_rel_row->addAttribute('null', 0); @@ -136,24 +136,24 @@ $new_rel_row->addAttribute('autoincrement', 0); $new_rel_row->addChild('datatype', $types_associations['many2one']); $new_rel_row->addChild('default', 'NULL'); - - + + $new_key = $new_rel_table->addChild('key'); $new_key->addAttribute('type', 'PRIMARY'); $new_key->addAttribute('name', ''); $new_key->addChild('part', $definition['rel_foreign_key']); - $new_key->addChild('part', $definition['rel_local_key']); + $new_key->addChild('part', $definition['rel_local_key']); } // create relation node $new_relation->addAttribute('table', $definition['rel_table']); $new_relation->addAttribute('row', $definition['rel_local_key']); } else { - // create relation node + // create relation node $new_relation->addAttribute('table', $definition['foreign_object']); $new_relation->addAttribute('row', 'id'); - } - } + } + } $new_row->addAttribute('name', $field); $new_row->addAttribute('null', 0); @@ -161,7 +161,7 @@ $new_row->addChild('datatype', $types_associations[$type]); $new_row->addChild('default', 'NULL'); } - + $new_key = $new_table->addChild('key'); $new_key->addAttribute('type', 'PRIMARY'); $new_key->addAttribute('name', ''); diff --git a/packages/core/i18n/fr/Mail.json b/packages/core/i18n/fr/Mail.json index 450aa5801..da997a48a 100644 --- a/packages/core/i18n/fr/Mail.json +++ b/packages/core/i18n/fr/Mail.json @@ -8,6 +8,11 @@ "description": "Destinataire principal du courriel", "help":"Destinataire repris dans le champ `to`." }, + "reply_to":{ + "label":"Réponse à", + "description": "Adresse de réponse.", + "help":"Adresse reprise dans le champ `reply-to` et à laquelle adresser les emails de réponse." + }, "status":{ "label":"Statut", "selection": { diff --git a/packages/core/init/data/core_Group.json b/packages/core/init/data/core_Group.json index 6d517a417..254ca0be7 100644 --- a/packages/core/init/data/core_Group.json +++ b/packages/core/init/data/core_Group.json @@ -21,97 +21,7 @@ "1", "2" ] - }, - { - "id": 8, - "name": "sales.catalog.administrators", - "display_name": "Sales \/ Catalog \/ Administrators", - "description": "Updating products and adding new products to the catalog." - }, - { - "id": 9, - "name": "sales.price_list.users", - "display_name": "Sales \/ Pricelists \/ Users", - "description": "Consulting pricelists." - }, - { - "id": 10, - "name": "sales.price_list.administrators", - "display_name": "Sales \/ Pricelists \/ Administrators", - "description": "Updating prices and fares." - }, - { - "id": 11, - "name": "sales.reductions.users", - "display_name": "Sales \/ Discount \/ Users", - "description": "Consultating discounts and discount lists." - }, - { - "id": 12, - "name": "sales.reductions.administrators", - "display_name": "Sales \/ Discount \/ Administrators", - "description": "Creating and updating discount lists." - }, - { - "id": 13, - "name": "sales.seasons.users", - "display_name": "Sales \/ Saisons \/ Utilisateurs", - "description": "Consultation des saisons et listes de saisons." - }, - { - "id": 14, - "name": "sales.seasons.administrators", - "display_name": "Sales \/ Seasons \/ Administrators", - "description": "Creating and updating Seasons." - }, - { - "id": 15, - "name": "point_of_sale.administrators", - "display_name": "Point of Sale \/ Administrators", - "description": "Managing and configuring PoS." - }, - { - "id": 16, - "name": "point_of_sale.users", - "display_name": "Point of Sale \/ Users", - "description": "Encodaging PoS orders." - }, - { - "id": 17, - "name": "settings", - "display_name": "Configuration Settings", - "description": "Updating global configuration." - }, - { - "id": 18, - "name": "sales.bookings.users", - "display_name": "Sales \/ Booking \/ Users", - "description": "Creating and consulting bookings." - }, - { - "id": 19, - "name": "sales.catalog.users", - "display_name": "Sales \/ Catalog \/ Userrs", - "description": "Consulting products catalog." - }, - { - "id": 20, - "name": "accounting.administrators", - "display_name": "Accounting \/ Administrators", - "description": "Updating settings related to accounting." - }, - { - "id": 21, - "name": "accounting.invoicing.administrators", - "display_name": "Accounting \/ Invoicing \/ Administrators", - "description": "Creating, updating and sending invoices and credit notess." - }, - { - "id": 22, - "name": "accounting.invoicing.users", - "display_name": "Accounting \/ Invoicing \/ Users", - "description": "Generating and sending invoices to customers." - } + } ] }, { @@ -127,83 +37,7 @@ "id": 2, "display_name": "Tous les utilisateurs", "description": "Groupe par défaut pour les utilisateurs." - }, - { - "id": 8, - "display_name": "Ventes \/ Catalogue \/ Administrateurs", - "description": "Modification de produits et ajout de nouveaux produits au catalogue." - }, - { - "id": 9, - "display_name": "Ventes \/ Listes de prix \/ Utilisateurs", - "description": "Consultation des listes de prix." - }, - { - "id": 10, - "display_name": "Ventes \/ Listes de prix \/ Administrateurs", - "description": "Modification des prix et tarifs." - }, - { - "id": 11, - "display_name": "Ventes \/ R\u00e9ductions \/ Utilisateurs", - "description": "Consultation des r\u00e9ductions et liste de r\u00e9ductions." - }, - { - "id": 12, - "display_name": "Ventes \/ R\u00e9ductions \/ Administrateurs", - "description": "Cr\u00e9ation et modification des listes de r\u00e9ductionss." - }, - { - "id": 13, - "display_name": "Ventes \/ Saisons \/ Utilisateurs", - "description": "Consultation des saisons et listes de saisons." - }, - { - "id": 14, - "name": "sales.seasons.administrators", - "display_name": "Ventes \/ Saisons \/ Administrateurs", - "description": "Modification et cr\u00e9ation des saisons." - }, - { - "id": 15, - "display_name": "Point de Vente \/ Administrateurs", - "description": "Gestion de la configuration de la caisse." - }, - { - "id": 16, - "display_name": "Point de Vente \/ Utilisateurs", - "description": "Encodage des consommation avec la caisse." - }, - { - "id": 17, - "display_name": "Param\u00e8tres de configuration", - "description": "Modification de la configuration globale." - }, - { - "id": 18, - "display_name": "Ventes \/ R\u00e9servations \/ Utilisateurs", - "description": "Cr\u00e9ation et consultation des r\u00e9servations." - }, - { - "id": 19, - "display_name": "Ventes \/ Catalogue \/ Utilisateurs", - "description": "Consultation du catalogue de produits." - }, - { - "id": 20, - "display_name": "Comptabilit\u00e9 \/ Administrateurs", - "description": "Modification des param\u00e8tres li\u00e9s \u00e0 la compta (r\u00e8gles comptables; plan comptable; r\u00e8gles de TVA; ...)." - }, - { - "id": 21, - "display_name": "Comptabilit\u00e9 \/ Facturation \/ Administrateurs", - "description": "Cr\u00e9ation, modification et suppression de factures et notes de cr\u00e9dit, envoi de factures aux clients." - }, - { - "id": 22, - "display_name": "Comptabilit\u00e9 \/ Facturation \/ Utilisateurs", - "description": "G\u00e9n\u00e9ration et envoi des factures aux clients." - } + } ] } ] \ No newline at end of file diff --git a/packages/core/init/data/core_Lang.json b/packages/core/init/data/core_Lang.json index 71a8cadfd..5dd714a9e 100644 --- a/packages/core/init/data/core_Lang.json +++ b/packages/core/init/data/core_Lang.json @@ -15,7 +15,7 @@ }, { "id": "3", - "name": "Dutch", + "name": "Nederlands", "code": "nl" } ] diff --git a/packages/core/init/data/core_Permission.json b/packages/core/init/data/core_Permission.json index d0028a3e3..5fdfd75da 100644 --- a/packages/core/init/data/core_Permission.json +++ b/packages/core/init/data/core_Permission.json @@ -5,42 +5,7 @@ "description": "assign READ on common objects", "data": [ { - "class_name": "sale\\booking\\Booking", - "domain": "", - "group_id": 2, - "user_id": null, - "rights": 2 - }, - { - "class_name": "lodging\\sale\\booking\\Booking", - "domain": "", - "group_id": 2, - "user_id": null, - "rights": 2 - }, - { - "class_name": "sale\\customer\\Customer", - "domain": "", - "group_id": 2, - "user_id": null, - "rights": 2 - }, - { - "class_name": "identity\\Establishment", - "domain": "", - "group_id": 2, - "user_id": null, - "rights": 2 - }, - { - "class_name": "lodging\\identity\\Center", - "domain": "", - "group_id": 2, - "user_id": null, - "rights": 2 - }, - { - "class_name": "sale\\booking\\BookingType", + "class_name": "core\\*", "domain": "", "group_id": 2, "user_id": null, diff --git a/packages/core/init/data/core_setting_Setting.json b/packages/core/init/data/core_setting_Setting.json index a132846b2..d0f51fcd2 100644 --- a/packages/core/init/data/core_setting_Setting.json +++ b/packages/core/init/data/core_setting_Setting.json @@ -1,17 +1,17 @@ [ { "name": "core\\setting\\Setting", - "lang": "fr", + "lang": "en", "data": [ { "id": 1, "code": "numbers.thousands_separator", - "title": "Séparateur de milliers", + "title": "Thousands separator", "package": "core", "form_control": "select", "section_id": 1, - "description":"Caract\u00e8re de s\u00e9paration de milliers", - "help": "Caract\u00e8re de s\u00e9paration de milliers (e.g. '.', ',', ' ').", + "description":"Character to use as thousands separator", + "help": "Thousands separator char (e.g. '.', ',', ' ').", "type": "string" }, { @@ -21,58 +21,58 @@ "package": "core", "form_control": "select", "section_id": 1, - "description":"Position du symbole de monnaie par rapport \u00e0 sa valeur", - "help": "Position du symbole de monnaie par rapport \u00e0 sa valeur ('before', 'after', 'decimal_separator').", + "description":"Position of the currency symbol relative to its value.", + "help": "Position of the currency symbol relative to its value ('before', 'after', 'decimal_separator').", "type": "string" }, { "id": 3, "code": "currency.decimal_precision", - "title": "Nombre de d\u00e9cimales (Prix)", + "title": "Number of decimal digits (Price)", "package": "core", "form_control": "select", "section_id": 1, - "description":"Nombre de d\u00e9cimales pour les prix", - "help": "Nombre de d\u00e9cimales \u00e0 conserver pour les champs \u00e0 usage 'Price'.", + "description":"Number of decimal digits for prices.", + "help": "Number of decimal digits to store for fields of usage 'Price'.", "type": "integer" }, { "id": 4, "code": "numbers.decimal_separator", - "title": "Type de d\u00e9cimales", + "title": "Decimal separator", "package": "core", "form_control": "select", "section_id": 1, - "description":"Type de d\u00e9cimales (e.g. '.', ',')", - "help": "Caract\u00e8re de s\u00e9paration entre les parties enti\u00e8re et d\u00e9cimale (e.g. '.', ',').", + "description":"Decimal separator (e.g. '.', ',')", + "help": "Character to use for separating integer and decimal parts of floating numbers (e.g. '.', ',').", "type": "string" }, { "id": 5, "code": "numbers.decimal_precision", - "title": "Nombre de d\u00e9cimales", + "title": "Number of decimal digits", "package": "core", "form_control": "select", "section_id": 1, - "description":"Nombre de d\u00e9cimales", - "help": "Nombre de d\u00e9cimales \u00e0 conserver pour les champs de type 'float'.", + "description":"Number of decimal digits", + "help": "Number of decimal digits to store for fields of type 'float'.", "type": "integer" }, { "id": 6, "code": "date_format", - "title": "Format de date", + "title": "Date format", "package": "core", "form_control": "select", "section_id": 1, - "description":"Format de date", - "help": "Format pour l'affichage des dates.", + "description":"Date format", + "help": "Format to use for displaying dates.", "type": "string" }, { "id": 7, "code": "time_format", - "title": "Format des heures", + "title": "Hours format", "package": "core", "form_control": "select", "section_id": 1, @@ -83,89 +83,89 @@ { "id": 8, "code": "company.id", - "title": "Entreprise", + "title": "Company", "package": "core", "form_control": "select", "section_id": 2, - "description":"Nom de l'entreprise", - "help": "Identifiant de l'entreprise principale de l'installation courante.", + "description":"Installation main company", + "help": "Identifier of the main company of the current installation.", "type": "integer" }, { "id": 9, "code": "formats.paper", - "title": "Format du papier", + "title": "Paper format", "package": "core", "form_control": "select", "section_id": 2, - "description":"Taille papier par d\u00e9faut", - "help": "Taille papier par d\u00e9faut (e.g. 'A4', 'legal_US', 'letter_US').", + "description":"Default size for paper documents", + "help": "Default size for paper documents (e.g. 'A4', 'legal_US', 'letter_US').", "type": "string" }, { "id": 10, "code": "account_creation", - "title": "Création de compte", + "title": "Account creation", "package": "core", "form_control": "toggle", "section_id": 3, - "description":"Autoriser la création de compte", - "help": "Autoriser les visiteurs \u00e0 cr\u00e9er un compte utilisateur.", + "description":"Allow accounts creation", + "help": "Allow visitors to create a user account.", "type": "boolean" }, { "id": 11, "code": "import", - "title": "Importer", + "title": "Import", "package": "core", "form_control": "toggle", "section_id": 3, - "description":"Autoriser l'importation", - "help": "Autoriser les utilisateurs \u00e0 importer des donn\u00e9es depuis des fichiers (CSV\/XLS\/XLSX\/ODS).", + "description":"Allow import", + "help": "Allow users to import data from files (CSV\/XLS\/XLSX\/ODS).", "type": "boolean" }, { "id": 12, "code": "export", - "title": "Exporter", + "title": "Export", "package": "core", "form_control": "toggle", "section_id": 3, - "description":"Autoriser l'exportation", - "help": "Autoriser les utilisateurs \u00e0 exporter des donn\u00e9es vers des fichiers (CSV\/XLS\/PDF).", + "description":"Allow export", + "help": "Allow users to export data by downloading files (CSV\/XLS\/PDF).", "type": "boolean" }, { "id": 13, "code": "currency", - "title": "Devise", + "title": "Currency", "package": "core", "form_control": "select", "section_id": 4, - "description":"Devise des prix", - "help": "Devise \u00e0 utiliser pour les champs \u00e0 usage 'Price' (ISO 4217).", + "description":"Prices currency", + "help": "Currency to use for fields of usage 'Price' (ISO 4217).", "type": "string" }, { "id": 14, "code": "length", - "title": "Longueur", + "title": "Length", "package": "core", "form_control": "select", "section_id": 4, - "description":"Unité de longueur", - "help": "Unit\u00e9 par d\u00e9faut pour les mesures de longueur.", + "description":"Length unit", + "help": "Default unit for length measures.", "type": "string" }, { "id": 15, "code": "weight", - "title": "Poids", + "title": "Weight", "package": "core", "form_control": "select", "section_id": 4, - "description":"Unité de poids", - "help": "Unit\u00e9 par d\u00e9faut pour les mesures de poids (e.g. 'kg' kilogram, 'lb' pound).", + "description":"Weight unit", + "help": "Default unit for weight measures (e.g. 'kg' kilogram, 'lb' pound).", "type": "string" }, { @@ -175,8 +175,8 @@ "package": "core", "form_control": "select", "section_id": 4, - "description":"Unité de volume", - "help": "Unit\u00e9 par d\u00e9faut pour les unit\u00e9s de volume (e.g. 'm3' cubic meter, 'ft3' cubic foot).", + "description":"Volume unit", + "help": "Default unit for volume measures (e.g. 'm3' cubic meter, 'ft3' cubic foot).", "type": "string" }, { @@ -186,174 +186,117 @@ "package": "core", "form_control": "select", "section_id": 4, - "description":"Unité de surface", - "help": "Unit\u00e9 par d\u00e9faut pour les unit\u00e9s de surface (e.g. 'm2' square meter, 'ft2' square foot).", + "description":"Surface unit", + "help": "Default unit for surface measures (e.g. 'm2' square meter, 'ft2' square foot).", "type": "string" + } + ] + }, + { + "name": "core\\setting\\Setting", + "lang": "fr", + "data": [ + { + "id": 1, + "title": "Séparateur de milliers", + "description":"Caract\u00e8re de s\u00e9paration de milliers", + "help": "Caract\u00e8re de s\u00e9paration de milliers (e.g. '.', ',', ' ')." }, { - "id": 18, - "code": "invoice.sequence.1", - "title": "Séquence de facturation Kaleo", - "package": "sale", - "form_control": "select", - "section_id": 5, - "description":"Séquence de facturation Kaleo", - "help": "Prochaine valeur de la s\u00e9quence pour les factures Kaleo.", - "type": "integer" + "id": 2, + "title": "Position de devise", + "description":"Position du symbole de monnaie par rapport \u00e0 sa valeur", + "help": "Position du symbole de monnaie par rapport \u00e0 sa valeur ('before', 'after', 'decimal_separator')." }, { - "id": 19, - "code": "invoice.sequence.2", - "title": "Séquence de facturation Villers", - "package": "sale", - "form_control": "select", - "section_id": 5, - "description":"Séquence de facturation Villers", - "help": "Prochaine valeur de la s\u00e9quence pour les factures Villers.", - "type": "integer" + "id": 3, + "title": "Nombre de d\u00e9cimales (Prix)", + "description":"Nombre de d\u00e9cimales pour les prix", + "help": "Nombre de d\u00e9cimales \u00e0 conserver pour les champs \u00e0 usage 'Price'." }, { - "id": 20, - "code": "invoice.sequence.3", - "title": "Séquence de facturation Moza\u00efk", - "package": "sale", - "form_control": "select", - "section_id": 5, - "description":"Séquence de facturation Moza\u00efk", - "help": "Prochaine valeur de la s\u00e9quence pour les factures Moza\u00efk.", - "type": "integer" + "id": 4, + "title": "Séparateur de de d\u00e9cimales", + "description":"Séparateur de d\u00e9cimales (e.g. '.', ',')", + "help": "Caract\u00e8re de s\u00e9paration entre les parties enti\u00e8re et d\u00e9cimale (e.g. '.', ',')." }, { - "id": 21, - "code": "invoice.fiscal_year", - "title": "Année comptable", - "package": "finance", - "form_control": "select", - "section_id": 5, - "description":"Année comptable courrante", - "help": "Ann\u00e9e courante d'exercice comptable.", - "type": "integer" + "id": 5, + "title": "Nombre de d\u00e9cimales", + "description":"Nombre de d\u00e9cimales", + "help": "Nombre de d\u00e9cimales \u00e0 conserver pour les champs de type 'float'." }, { - "id": 22, - "code": "invoice.sequence_format", - "title": "Format de séquence des factures", - "package": "finance", - "form_control": "string", - "section_id": 5, - "description":"Format de sequence des factures", - "help": "Format de sequence pour les factures (selon les conventions comptables).", - "type": "string" + "id": 6, + "title": "Format de date", + "description":"Format de date", + "help": "Format pour l'affichage des dates." }, { - "id": 23, - "code": "booking.sequence_format", - "title": "Format de réservation", - "package": "sale", - "form_control": "select", - "section_id": 6, - "description":"Format de réservation", - "help": "Format pour la num\u00e9rotation des r\u00e9servations.", - "type": "string" + "id": 7, + "title": "Format des heures", + "description":"Format des heures", + "help": "Format pour l'affichage des heures." }, { - "id": 24, - "code": "booking.sequence.1", - "title": "Séquence de réservation 1", - "package": "sale", - "form_control": "string", - "section_id": 6, - "description":"", - "help": "", - "type": "integer" + "id": 8, + "title": "Entreprise", + "description":"Entreprise de l'installation", + "help": "Identifiant de l'entreprise principale pour l'installation courante." }, { - "id": 25, - "code": "booking.sequence.2", - "title": "Séquence de réservation 2", - "package": "sale", - "form_control": "string", - "section_id": 6, - "description":"", - "help": "", - "type": "integer" + "id": 9, + "title": "Format du papier", + "description":"Taille papier par d\u00e9faut", + "help": "Taille papier par d\u00e9faut (e.g. 'A4', 'legal_US', 'letter_US')." }, { - "id": 26, - "code": "booking.sequence.3", - "title": "Séquence de réservation 3", - "package": "sale", - "form_control": "string", - "section_id": 6, - "description":"", - "help": null, - "type": "integer" + "id": 10, + "title": "Création de compte", + "description":"Autoriser la création de compte", + "help": "Autoriser les visiteurs \u00e0 cr\u00e9er un compte utilisateur." }, { - "id": 27, - "code": "booking.sequence.4", - "title": "Séquence de réservation 4", - "package": "sale", - "form_control": "string", - "section_id": 6, - "description":"", - "help": "", - "type": "integer" + "id": 11, + "title": "Importer", + "description":"Autoriser l'importation", + "help": "Autoriser les utilisateurs \u00e0 importer des donn\u00e9es depuis des fichiers (CSV\/XLS\/XLSX\/ODS)." }, { - "id": 28, - "code": "booking.sequence.5", - "title": "Séquence de réservation 5", - "package": "sale", - "form_control": "string", - "section_id": 6, - "description":"", - "help": null, - "type": "integer" + "id": 12, + "title": "Exporter", + "description":"Autoriser l'exportation", + "help": "Autoriser les utilisateurs \u00e0 exporter des donn\u00e9es vers des fichiers (CSV\/XLS\/PDF)." }, { - "id": 29, - "code": "booking.sequence.6", - "title": "Séquence de réservation 6", - "package": "sale", - "form_control": "string", - "section_id": 6, - "description":"", - "help": "", - "type": "integer" + "id": 13, + "title": "Devise", + "description":"Devise des prix", + "help": "Devise \u00e0 utiliser pour les champs \u00e0 usage 'Price' (ISO 4217)." }, { - "id": 30, - "code": "booking.sequence.7", - "title": "Séquence de réservation 7", - "package": "sale", - "form_control": "string", - "section_id": 6, - "description":"", - "help": null, - "type": "integer" + "id": 14, + "title": "Longueur", + "description":"Unité de longueur", + "help": "Unit\u00e9 par d\u00e9faut pour les mesures de longueur." }, { - "id": 31, - "code": "booking.sequence.8", - "title":"Séquence de réservation 8", - "package": "sale", - "form_control": "string", - "section_id": 6, - "description":"", - "help": "", - "type": "integer" + "id": 15, + "title": "Poids", + "description":"Unité de poids", + "help": "Unit\u00e9 par d\u00e9faut pour les mesures de poids (e.g. 'kg' kilogram, 'lb' pound)." }, { - "id": 32, - "code": "option.validity", - "title": "Jours de validit\u00e9 d'une option", - "package": "sale", - "form_control": "string", - "section_id": 6, - "description":"Jours de validit\u00e9 d'une option", - "help": "Nombre de jours de validit\u00e9 d'une option.", - "type": "integer" + "id": 16, + "title": "Volume", + "description":"Unité de volume", + "help": "Unit\u00e9 par d\u00e9faut pour les unit\u00e9s de volume (e.g. 'm3' cubic meter, 'ft3' cubic foot)." + }, + { + "id": 17, + "title": "Surface", + "description":"Unité de surface", + "help": "Unit\u00e9 par d\u00e9faut pour les unit\u00e9s de surface (e.g. 'm2' square meter, 'ft2' square foot)." } ] } diff --git a/packages/core/init/data/core_setting_SettingChoice.json b/packages/core/init/data/core_setting_SettingChoice.json index 481bede80..3b40a2ff8 100644 --- a/packages/core/init/data/core_setting_SettingChoice.json +++ b/packages/core/init/data/core_setting_SettingChoice.json @@ -1,7 +1,7 @@ [ { "name": "core\\setting\\SettingChoice", - "lang": "fr", + "lang": "en", "data": [ { "id": 101, @@ -18,37 +18,37 @@ { "id": 201, "setting_id":2, - "name":"avant", + "name":"before", "value":"before" }, { "id": 202, "setting_id":2, - "name":"après", + "name":"after", "value":"after" }, { "id": 301, "setting_id":3, - "name":"", + "name":"1", "value":"1" }, { "id": 302, "setting_id":3, - "name":"", + "name":"2", "value":"2" }, { "id": 303, "setting_id":3, - "name":"", + "name":"3", "value":"3" }, { "id": 401, "setting_id":4, - "name":"virgule", + "name":"comma", "value":"," }, { @@ -60,67 +60,67 @@ { "id": 501, "setting_id":5, - "name":"", + "name":"1", "value":"1" }, { "id": 502, "setting_id":5, - "name":"", + "name":"2", "value":"2" }, { "id": 503, "setting_id":5, - "name":"", + "name":"3", "value":"3" }, { "id": 601, "setting_id":6, - "name":"", + "name":"d/m/Y", "value":"d/m/Y" }, { "id": 602, "setting_id":6, - "name":"", + "name":"Y-m-d", "value":"Y-m-d" }, { "id": 603, "setting_id":6, - "name":"", + "name":"m/d/Y", "value":"m/d/Y" }, { "id": 701, "setting_id":7, - "name":"", + "name":"H:i", "value":"H:i" }, { "id": 801, "setting_id":8, - "name":"", + "name":"1", "value":"1" }, { "id": 901, "setting_id":9, - "name":"21,59 x 27,74", + "name":"21.59 x 27.74", "value":"US" }, { "id": 902, "setting_id":9, - "name":"21 x 29,7", + "name":"21 x 29.7", "value":"A4" }, { "id": 903, "setting_id":9, - "name":"29,7 x 41,99", + "name":"29.7 x 41.99", "value":"A3" }, { @@ -144,124 +144,140 @@ { "id": 1401, "setting_id":14, - "name":"mètre", + "name":"meter", "value":"m" }, { "id": 1501, "setting_id":15, - "name":"kilogramme", + "name":"kilograms", "value":"kg" }, { "id": 1601, "setting_id":16, - "name":"mètre cube", + "name":"cubic meter", "value":"m3" }, { "id": 1701, "setting_id":17, - "name":"mètre carré", + "name":"square meter", "value":"m2" + } + ] + }, + { + "name": "core\\setting\\SettingChoice", + "lang": "fr", + "data": [ + { + "id": 101, + "name":"virgule" + }, + { + "id": 102, + "name":"point" + }, + { + "id": 201, + "name":"avant" + }, + { + "id": 202, + "name":"après" + }, + { + "id": 301, + "name":"1" + }, + { + "id": 302, + "name":"1" + }, + { + "id": 303, + "name":"3" }, { - "id": 2201, - "setting_id":22, - "name":"", - "value":"%4d(year)-%02d(org)-%03d(sequence)" + "id": 401, + "name":"virgule" + }, + { + "id": 402, + "name":"point" + }, + { + "id": 501, + "name":"1" }, { - "id": 2301, - "setting_id":23, - "name":"", - "value":"%1d{center}%05d{sequence}" + "id": 502, + "name":"2" + }, + { + "id": 503, + "name":"3" + }, + { + "id": 601, + "name":"d/m/Y" }, { - "id": 1801, - "setting_id":18, - "name":"", - "value":"111" + "id": 602, + "name":"Y-m-d" }, { - - "id": 1901, - "setting_id":19, - "name":"", - "value":"111" + "id": 603, + "name":"m/d/Y" }, { - - "id": 2001, - "setting_id":20, - "name":"", - "value":"111" + "id": 701, + "name":"H:i" }, { - "id": 2101, - "setting_id":21, - "name":"", - "value":"2021" + "id": 801, + "name":"1" }, { - "id": 2201, - "setting_id":21, - "name":"", - "value":"2022" + "id": 901, + "name":"21,59 x 27,74" }, { - "id": 2401, - "setting_id":24, - "name":"", - "value":"11117" + "id": 902, + "name":"21 x 29,7" }, { - "id": 2501, - "setting_id":25, - "name":"", - "value":"11111" + "id": 903, + "name":"29,7 x 41,99" }, { - "id": 2601, - "setting_id":26, - "name":"", - "value":"11111" + "id": 904, + "name":"14,8 x 21" }, { - "id": 2701, - "setting_id":27, - "name":"", - "value":"11113" + "id": 1301, + "name":"USD" }, { - "id": 2801, - "setting_id":28, - "name":"", - "value":"11112" + "id": 1302, + "name":"EUR" }, { - "id": 2901, - "setting_id":29, - "name":"", - "value":"11111" + "id": 1401, + "name":"mètre" }, { - "id": 3001, - "setting_id":30, - "name":"", - "value":"11111" + "id": 1501, + "name":"kilogramme" }, { - "id": 3101, - "setting_id":31, - "name":"", - "value":"11111" + "id": 1601, + "name":"mètre cube" }, { - "id": 3201, - "setting_id":32, - "name":"", - "value":"10" + "id": 1701, + "name":"mètre carré" } ] } diff --git a/packages/core/init/data/core_setting_SettingSection.json b/packages/core/init/data/core_setting_SettingSection.json index 643e2bded..a4dc0ef16 100644 --- a/packages/core/init/data/core_setting_SettingSection.json +++ b/packages/core/init/data/core_setting_SettingSection.json @@ -1,43 +1,57 @@ [ + { + "name": "core\\setting\\SettingSection", + "lang": "en", + "data": [ + { + "id": 1, + "code": "locale", + "name": "Locale", + "description": "Regional settings" + }, + { + "id": 2, + "code": "main", + "name": "Main", + "description": "General settings & Formats" + }, + { + "id": 3, + "code": "security", + "name": "Security", + "description": "Security settings" + }, + { + "id": 4, + "code": "units", + "name": "Units", + "description": "Measures and system units" + } + ] + }, { "name": "core\\setting\\SettingSection", "lang": "fr", "data": [ { "id": 1, - "code": "locale", "name": "Locale", "description": "Paramètres régioniaux" }, { "id": 2, - "code": "main", "name": "Général", "description": "Paramètres généraux et de formats" }, { "id": 3, - "code": "security", "name": "Sécurité", "description": "Paramètres de sécurité" }, { "id": 4, - "code": "units", "name": "Unités", "description": "Unités de mesures et systèmes d'unités" - }, - { - "id": 5, - "code": "invoice", - "name": "Facturation", - "description": "Paramètres pour la création des factures" - }, - { - "id": 6, - "code": "booking", - "name": "Réservation", - "description": "Paramètres pour la création des réservations" } ] } diff --git a/packages/core/init/data/core_setting_SettingValue.json b/packages/core/init/data/core_setting_SettingValue.json index 5b6dd376b..debccb306 100644 --- a/packages/core/init/data/core_setting_SettingValue.json +++ b/packages/core/init/data/core_setting_SettingValue.json @@ -1,7 +1,7 @@ [ - { + { "name": "core\\setting\\SettingValue", - "lang": "fr", + "lang": "en", "data": [ { "id": 1, @@ -104,96 +104,6 @@ "name": "surface", "setting_id": 17, "value": "m2" - }, - { - "id": 18, - "name": "invoice.sequence_format", - "setting_id": 22, - "value": "%4d(year)-%02d(org)-%03d(sequence)" - }, - { - "id": 19, - "name": "booking.sequence_format", - "setting_id": 23, - "value": "%1d{center}%05d{sequence}" - }, - { - "id": 20, - "name": "invoice.sequence.1", - "setting_id": 18, - "value": "111" - }, - { - "id": 21, - "name": "invoice.sequence.2", - "setting_id": 19, - "value": "111" - }, - { - "id": 22, - "name": "invoice.sequence.3", - "setting_id": 20, - "value": "111" - }, - { - "id": 23, - "name": "invoice.fiscal_year", - "setting_id": 21, - "value": "2021" - }, - { - "id": 24, - "name": "booking.sequence.1", - "setting_id": 24, - "value": "11117" - }, - { - "id": 25, - "name": "booking.sequence.2", - "setting_id": 25, - "value": "11111" - }, - { - "id": 26, - "name": "booking.sequence.3", - "setting_id": 26, - "value": "11111" - }, - { - "id": 27, - "name": "booking.sequence.4", - "setting_id": 27, - "value": "11113" - }, - { - "id": 28, - "name": "booking.sequence.5", - "setting_id": 28, - "value": "11112" - }, - { - "id": 29, - "name": "booking.sequence.6", - "setting_id": 29, - "value": "11111" - }, - { - "id": 30, - "name": "booking.sequence.7", - "setting_id": 30, - "value": "11111" - }, - { - "id": 31, - "name": "booking.sequence.8", - "setting_id": 31, - "value": "11111" - }, - { - "id": 32, - "name": "option.validity", - "setting_id": 32, - "value": "10" } ] } diff --git a/packages/core/views/Mail.list.default.json b/packages/core/views/Mail.list.default.json index 712d914f7..4d2674cf2 100644 --- a/packages/core/views/Mail.list.default.json +++ b/packages/core/views/Mail.list.default.json @@ -23,7 +23,8 @@ "value": "created", "width": "15%", "widget": { - "usage": "datetime/short" + "usage": "datetime/short", + "sortable": true } }, { @@ -32,7 +33,8 @@ "value": "modified", "width": "15%", "widget": { - "usage": "datetime/short" + "usage": "datetime/short", + "sortable": true } }, { @@ -53,7 +55,10 @@ { "type": "field", "value": "status", - "width": "10%" + "width": "10%", + "widget": { + "sortable": true + } }, { "type": "field", diff --git a/public/assets/env/default.json b/public/assets/env/default.json index 59ed0c380..5772dc79a 100644 --- a/public/assets/env/default.json +++ b/public/assets/env/default.json @@ -8,6 +8,7 @@ "company_name": "YesBabylon SRL", "company_url": "https://yesbabylon.com", "app_name": "Symbiose", + "app_logo_url": "/assets/img/logo.svg", "version": "1.0", "license": "AGPL", "license_url": "https://www.gnu.org/licenses/agpl-3.0.en.html" diff --git a/public/assets/img/logo.svg b/public/assets/img/logo.svg new file mode 100644 index 000000000..96ca2633f --- /dev/null +++ b/public/assets/img/logo.svg @@ -0,0 +1,162 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +