Kaynağa Gözat

dev 2020-09

Max F 5 yıl önce
ebeveyn
işleme
3de7ca6ec2

+ 244 - 38
Readme.md

@@ -1,16 +1,133 @@
 
-# Nouveau projet
+```
+  _  __                          _______        __
+ | |/ /__ _ _ __ _ __ ___   __ _|  ___\ \      / /
+ | ' // _` | '__| '_ ` _ \ / _` | |_   \ \ /\ / / 
+ | . \ (_| | |  | | | | | | (_| |  _|   \ V  V /  
+ |_|\_\__,_|_|  |_| |_| |_|\__,_|_|      \_/\_/   
+```
+
+
+# Présentation
+
+KarmaFW est un mini framework PHP qui gère le routing, les templates et les connexions aux bases SQL.
+
+
+## Fonctionnalités
+
+- Routing Web
+- Templates PHP/HTML
+- Connexions SQL
+- FileUpload web
+- Envoi d'emails (SMTP)
+- Paiements: Paypal, Payplug, Stripe
+- Auth: GoogleAuthenticator, SmsAuthenticator
+- PDF: création de PDF (HTML to PDF)
+- SMS: Envoi de SMS (Free & SmsEnvoi.com)
+- Hooks PHP
+- Bitly: génération d'url bit.ly
+
+
+### Pré-requis
+
+Composer est nécessaire afin de gérer les autoload des classes PHP.
+
+
+## Structure d'une [app console](src/ConsoleApp.md)
+
+```
+bin
+    app_console.php
+config
+    config.php
+src
+    scripts
+        my_test_script.php
+    Models
+    helpers
+vendor
+    karmasolutions/karmafw
+```
+
+
+## Structure d'une [app web](src/)
+
+```
+config
+    config.php
+    routes.php
+public
+    .htaccess
+    index.php
+    images
+    css
+    js
+    vendor
+src
+  Controllers
+    MyAppController
+    HomeController
+  Models
+    User
+  helpers
+    helpers_myapp.php
+templates
+    homepage.tpl.php
+vendor
+    karmasolutions/karmafw
+```
+
+
+# Configuration
+
+Les paramètres de configuration de l'application se déclarent dans le fichier ./config/config.php
+
+Le nom de l'application est à définir dans la variable APP_NAME.
+```
+define('APP_NAME', "Mon app PHP");
+```
+
+### [Routing](src/Routing/)
+
+Les routes se déclarent dans le fichier ./config/routes.php
+  
+Chaque route est attribuée à la méthode d'un controller à renseigner.
+
 
+### [Templates](src/Templates/)
 
-## 0) se positionner dans le dossier du projet
+Le chemin d'accès aux fichiers de templates se fait dans la variable de config TPL_DIR.
+```
+define('TPL_DIR', APP_DIR . '/templates');
+```
+
+
+### [Database SQL](src/Database/Sql/)
+
+Les informations de connexions à MySQL se font dans la variable de config DB_DSN.
+```
+define('DB_DSN', 'mysql://user:password@localhost/db_name');
+```
 
-## 1) lancer `composer init`
 
-## 2) modifier composer.json
+# Utilisation
 
+
+## Création d'un nouveau projet
+
+```
+$ mkdir /var/www/my_app
+$ cd /var/www/my_app
+```
+
+
+1) Composer
+```
+$ composer init
 ```
-# Ajouter ceci dans composer.json
 
+Ajouter ceci dans composer.json :
+```
 {
     "repositories": [
         {
@@ -26,7 +143,12 @@
 ```
 
 
-## 3) créer le dossier public et le fichier public/index.php et le remplir avec ceci :
+3) DocumentIndex
+```
+mkdir -p public
+nano public/index.php
+```
+
 ```
 <?php
 
@@ -34,88 +156,131 @@
 define('APP_DIR', realpath(__DIR__ . '/..'));
 define('VENDOR_DIR', realpath(__DIR__ . '/../vendor'));
 
-require APP_DIR . '/config/config.php';
-
 
 // AUTOLOAD
 $loader = require VENDOR_DIR . '/autoload.php';
-$loader->setPsr4('App\\', __DIR__ . '/../src');
+$loader->setPsr4('MyApp\\', __DIR__ . '/../src');
 
 
-// ERRORS HANDLER
-$whoops = new \Whoops\Run;
-$whoops->prependHandler(new \Whoops\Handler\PrettyPageHandler);
-$whoops->register();
 
+// APP BOOT
+\KarmaFW\WebApp::boot();
 
-// ROUTE
-require APP_DIR . '/config/routes.php';
 
+// YOUR INIT CODE HERE
+\KarmaFW\WebApp::getDb()->execute("set names utf8");
 
-\KarmaFW\App::registerHelpersDir(APP_DIR . '/src/helpers');
 
-// APP BOOT & ROUTE
-\KarmaFW\App::boot();
-\KarmaFW\App::route();
+// APP ROUTE
+\KarmaFW\WebApp::route();
 
 ```
 
 
-## 4) créer le dossier config
+4) App config
+
+Créer le fichier config/config.php et le remplir avec ceci :
+```
+mkdir -p config
+nano config/config.php
+```
 
-## 4a) créer le fichier config/config.php et le remplir avec ceci :
 ```
 <?php
 
 ini_set('display_errors', 1);
 
+define('APP_NAME', "MyAPP");
+
+define('ENV', "dev");
+
 define('TPL_DIR', APP_DIR . '/templates');
 
 define('DB_DSN', 'mysql://root@localhost/myapp');
+```
 
-define('APP_NAME', "MyAPP");
-
+Créer le fichier config/routes.php et le remplir avec ceci :
+```
+nano config/routes.php
 ```
 
-## 4b) créer le fichier config/routes.php et le remplir avec ceci :
 ```
 <?php
 
-namespace App\config;
+namespace MyApp\config;
 
 use \KarmaFW\Routing\Router;
 
 
 // Homepage
-Router::get('/', ['App\\Controllers\\AppController', 'homepage'])->setName('home');
+Router::get('/', ['MyApp\\Controllers\\HomeController', 'homepage'])->setName('home');
 
 ```
 
-## 5) Homepage controller : src/Controllers/AppController.php
+
+## 5) Homepage controller : src/Controllers/MyAppController.php
+```
+mkdir -p src/Controllers
+nano src/Controllers/MyAppController.php
+```
+
+```
+<?php
+
+namespace MyApp\Controllers;
+
+use \KarmaFW\App;
+use \KarmaFW\Routing\Controllers\WebAppController;
+use \MyApp\Models\User;
+
+
+class MyAppController extends WebAppController
+{
+	protected $db;
+	protected $user;
+
+	public function __construct($request_uri=null, $request_method=null, $route=null)
+	{
+		parent::__construct($request_uri, $request_method, $route);
+
+		$this->db = App::getDb();
+
+		if (! empty($this->user_id)) {
+			$this->user = User::load($this->user_id);
+			$this->template->assign('user', $this->user);
+		}
+	}
+}
+
+```
+
+
+## 6) Homepage controller : src/Controllers/HomeController.php
+```
+nano src/Controllers/HomeController.php
+```
+
 ```
 <?php
 
-namespace App\Controllers;
+namespace MyApp\Controllers;
 
 use \KarmaFW\App;
-use \KarmaFW\Routing\Controllers\WebController;
 
 
-class AppController extends WebController
+class HomeController extends MyAppController
 {
 
 	public function homepage()
 	{
+		$this->template->assign('title', 'My APP');
+
 		$db = App::getDb();
-		$db->connect();
 
-		$rs = $db->execute('show databases');
-		$databases = $rs->fetchAll();
+		$databases = $db->executeSelect('show databases');
+		$this->template->assign('databases', $databases);
 
-		$template = App::createTemplate();
-		$template->assign('title', 'My APP');
-		$template->assign('databases', $databases);
-		$template->display('homepage.tpl.php');
+		$this->template->display('homepage.tpl.php');
 	}
 	
 }
@@ -123,7 +288,12 @@ class AppController extends WebController
 ```
 
 
-## 6) Homepage template : templates/homepage.tpl.php
+## 7) Homepage template : templates/homepage.tpl.php
+```
+mkdir -p templates
+nano templates/homepage.tpl.php
+```
+
 ```
 <html>
 <head>
@@ -138,3 +308,39 @@ class AppController extends WebController
 </html>
 
 ```
+
+
+
+## 8a) Layout : templates/layout.tpl.php
+```
+nano templates/layout.tpl.php
+```
+
+```
+<html>
+<head>
+	<title>{$title}</title>
+</head>
+<body>
+<h1>hello world</h1>
+
+{$child_content}
+
+</body>
+</html>
+
+```
+
+## 8b) Homepage template avec layout : templates/homepage2.tpl.php
+```
+nano templates/homepage2.tpl.php
+```
+
+```
+{layout layout.tpl.php}
+
+<pre>
+<?php print_r($databases); ?>
+</pre>
+
+```

+ 55 - 91
src/App.php

@@ -2,11 +2,10 @@
 
 namespace KarmaFW;
 
-use \KarmaFW\Routing\Router;
-use \KarmaFW\Lib\Hooks\HooksManager;
-use \KarmaFW\Database\Sql\SqlDb;
-use \KarmaFW\Database\Sql\SqlOrmModel;
-use \KarmaFW\Templates\PhpTemplate;
+use KarmaFW\Routing\Router;
+use KarmaFW\Lib\Hooks\HooksManager;
+use KarmaFW\Database\Sql\SqlDb;
+//use \KarmaFW\Database\Sql\SqlOrmModel;
 
 
 define('FW_SRC_DIR', __DIR__);
@@ -21,32 +20,25 @@ if (! defined('APP_DIR')) {
 class App
 {
 	protected static $booted = false;
-	protected static $session_user = false; // user connected with a session
-	protected static $helpers_dirs = [FW_SRC_DIR . "/../helpers"];
+	protected static $helpers_dirs = [FW_SRC_DIR . "/helpers", APP_DIR . "/src/helpers"];
+
+	public static $db = null;
+
 
 	public static function boot()
 	{
-		HooksManager::applyHook('app_boot__before', []);
-
-		// start session
-		if (empty(session_id())) {
-			if (defined('SESSION_NAME') && ! empty(SESSION_NAME)) {
-				session_name(SESSION_NAME);
-			}
+		if (defined('USE_HOOKS') && USE_HOOKS) {
+			HooksManager::applyHook('app.boot.before', []);
+		}
 
-			if (defined('SESSION_DURATION') && is_numeric(SESSION_DURATION)) {
-				ini_set('session.gc_maxlifetime', SESSION_DURATION);
-				session_set_cookie_params(SESSION_DURATION);
-				// Note: si cron est actif, il faut modifier la valeur de session.gc_maxlifetime dans /etc/php/7.3/apache2/php.ini (voir /etc/cron.d/php)
-			}
+		// TODO: config à migrer dans un fichier .env et .env.prod et .env.dev et .env.local (à charger dans cet ordre, avec overwrite)
+		require APP_DIR . '/config/config.php';
 
-			session_start();
-		}
 
 		// move fw_helpers at the end of the list (to be loaded the last one)
 		if (count(self::$helpers_dirs) > 1) {
-		$fw_helpers = array_shift(self::$helpers_dirs);
-		self::$helpers_dirs[] = $fw_helpers;
+			$fw_helpers = array_shift(self::$helpers_dirs);
+			self::$helpers_dirs[] = $fw_helpers;
 		}
 
 		// include helpers
@@ -68,75 +60,58 @@ class App
 		class_alias('\\KarmaFW\\Database\\Sql\\SqlTools', 'SqlTools');
 		
 
-		self::$booted = true;
-		HooksManager::applyHook('app_boot__after', []);
-	}	
-
-
-	public static function registerHelpersDir($dir)
-	{
-		self::$helpers_dirs[] = $dir;
-	}
+		if (defined('DB_DSN')) {
+			self::$db = static::getDb();
+		}
 
-	protected static function loadHelpers($dir)
-	{
-		$helpers = glob($dir . '/helpers_*.php');
 
-		foreach ($helpers as $helper) {
-			require $helper;
+		// ERRORS HANDLER   // NOTE => a déplacer dans \KarmaFW\WebApp::boot() ??
+		if (defined('ENV') && ENV == 'dev') {
+			$whoops = new \Whoops\Run;
+			$whoops->prependHandler(new \Whoops\Handler\PrettyPageHandler);
+			$whoops->register();
 		}
-	}
 
 
-	public static function route()
-	{
-		return self::routeUrl();
-	}
+		// LOAD ROUTES
+		require APP_DIR . '/config/routes.php'; // NOTE => a déplacer dans \KarmaFW\WebApp::boot() ??
 
 
-	public static function routeUrl()
-	{
-		if (! self::$booted) {
-			self::boot();
+		if (defined('USE_HOOKS') && USE_HOOKS) {
+			HooksManager::applyHook('app.boot.after', []);
 		}
 
-		// routing: parse l'url puis transfert au controller
 
-		HooksManager::applyHook('app_route__before', []);
+		self::$booted = true;
+	}	
 
-		$route = Router::routeByUrl( $_SERVER['REQUEST_METHOD'], $_SERVER['REQUEST_URI'], false );
 
-		HooksManager::applyHook('app_route__after', [$route]);
+	public static function registerHelpersDir($dir)
+	{
+		$dir = rtrim($dir, '/');
+		if (! in_array($dir, self::$helpers_dirs)) {
+			self::$helpers_dirs[] = $dir;
+		}
+	}
 
-		if ($route) {
-			//echo "success: route ok";
-			exit(0);
 
-		} else if ($route === null) {
-			// route found but callback is not callable
-			HooksManager::applyHook('app_route_404', []);
-			errorHttp(404, 'Warning: route callback is not callable', '404 Not Found');
-			exit(1);
+	public static function unregisterHelpersDir($dir)
+	{
+		$dir = rtrim($dir, '/');
+		$k = array_search($dir, self::$helpers_dirs);
+		if ($k !== false) {
+			unset(self::$helpers_dirs[$k]);
+		}
+	}
 
-		} else if ($route === 0) {
-			// route found but no callback defined
-			HooksManager::applyHook('app_route_404', []);
-			errorHttp(404, "Warning: route found but no callback defined", '404 Not Found');
-			exit(1);
 
-		} else if ($route === false) {
-			// no matching route
-			HooksManager::applyHook('app_route_404', []);
-			errorHttp(404, "Warning: no matching route", '404 Not Found');
-			exit(1);
+	protected static function loadHelpers($dir)
+	{
+		$helpers = glob($dir . '/helpers_*.php');
 
-		} else {
-			// other cases
-			HooksManager::applyHook('app_route_404', []);
-			errorHttp(404, "Warning: cannot route", '404 Not Found');
-			exit(1);
+		foreach ($helpers as $helper) {
+			require $helper;
 		}
-
 	}
 
 
@@ -190,6 +165,10 @@ class App
 		static $last_instance_name = null;
 
 		if (empty($instance_name)) {
+			if (! empty(self::$db)) {
+				return self::$db;
+			}
+
 			$instance_name = 'default';
 
 			//if (! empty($last_instance_name)) {
@@ -210,26 +189,11 @@ class App
 	}
 
 
-	public static function createTemplate($tpl_path=null, $variables=[], $layout=null, $templates_dirs=null)
-	{
-		return new PhpTemplate($tpl_path, $variables, $layout, $templates_dirs);
-	}
-
-
+	/*
 	public static function createOrmItem($table_name, $primary_key_values=[], $db=null)
 	{
 		return new SqlOrmModel($table_name, $primary_key_values, $db);
 	}
-	
-
-	public static function getUser()
-	{
-		return self::$session_user;
-	}
-
-	public static function setUser($user)
-	{
-		self::$session_user = $user;
-	}
+	*/
 
 }

+ 62 - 0
src/ConsoleApp.md

@@ -0,0 +1,62 @@
+
+
+
+## Structure d'une app console
+
+```
+bin
+    app_console.php
+config
+    config.php
+src
+    scripts
+        my_test_script.php
+    Models
+    helpers
+vendor
+    karmasolutions/karmafw
+```
+
+
+
+
+## Console command
+```
+mkdir -p bin
+nano bin/app_console.php
+```
+
+```
+<?php
+
+// CONFIG
+define('APP_DIR', realpath(__DIR__ . '/..'));
+define('VENDOR_DIR', realpath(__DIR__ . '/../vendor'));
+
+require APP_DIR . '/config/config.php';
+
+
+// AUTOLOAD
+$loader = require VENDOR_DIR . '/autoload.php';
+$loader->setPsr4('MyApp\\', __DIR__ . '/../src');
+
+
+// APP BOOT
+\KarmaFW\ConsoleApp::boot();
+
+
+// YOUR INIT CODE HERE
+\KarmaFW\ConsoleApp::getDb()->execute("set names utf8");
+
+
+// APP ROUTE
+\KarmaFW\ConsoleApp::routeFromArgs($argv);
+
+```
+
+
+## Exécution d'un script
+
+```
+$ php bin/app_console.php my_test_script [arguments]
+```

+ 70 - 0
src/ConsoleApp.php

@@ -0,0 +1,70 @@
+<?php
+
+namespace KarmaFW;
+
+use \KarmaFW\Routing\Router;
+use \KarmaFW\Lib\Hooks\HooksManager;
+
+
+class ConsoleApp extends App
+{
+	public static $session_user = false; // user connected with a session
+	public static $controller = null;
+
+
+	public static function boot()
+	{
+		parent::boot();
+
+		if (defined('USE_HOOKS') && USE_HOOKS) {
+			HooksManager::applyHook('consoleapp.boot.before', []);
+		}
+
+
+		if (defined('USE_HOOKS') && USE_HOOKS) {
+			HooksManager::applyHook('consoleapp.boot.after', []);
+		}
+
+	}
+
+
+	public static function routeFromArgs($argv=[])
+	{
+		if (! self::$booted) {
+			self::boot();
+		}
+
+		if (defined('USE_HOOKS') && USE_HOOKS) {
+			HooksManager::applyHook('consoleapp.route.before', []);
+		}
+
+		$bin_path = array_shift($argv);
+
+		if ( ($command = array_shift($argv)) === null ) {
+			// error: missing command parameter
+			throw new Exception("Command not specified", 1);
+		
+		} else {
+			$scripts_dir = APP_DIR . "/src/scripts";
+			$script_filepath = $scripts_dir . '/' . $command . '.php';
+
+			if (is_file($script_filepath)) {
+				require $script_filepath;
+
+			} else {
+				throw new Exception("Script file not found", 1);
+			}
+
+		}
+
+
+
+		if (defined('USE_HOOKS') && USE_HOOKS) {
+			HooksManager::applyHook('consoleapp.route.after', []);
+		}
+
+
+	}
+
+
+}

+ 198 - 27
src/Database/Sql/Readme.md

@@ -1,67 +1,238 @@
 
-// DROP DATABASE
 ```
-$db->dropDatabase('test', true);
+# Create connection
+$db = App::getDb('default', 'mysql://user:password@localhost/db_name');
+
+# Call known connection
+$db = App::getDb('default');
+
+# Call default connection (if known)
+$db = App::getDb();
 ```
-// returns boolean
 
+# MANAGE DATABASES
+
+## DROP DATABASE
+```
+$db->dropDatabase('test', true);
+```
+=> returns boolean
 
-// CREATE DATABASE
+## CREATE DATABASE
 ```
 $db->createDatabase('test', true);
 ```
-// returns boolean
+=> returns boolean
 
-// USE DATABASE
+## USE DATABASE
 ```
 $db->use('test');
 ```
-// returns boolean
+=> returns boolean
+
+
+# MANAGE TABLES
 
+## LIST TABLES
+```
+$db->listTables($table_name=null, $database_name=null);
+```
 
-// CREATE TABLE
+## CREATE TABLE
 ```
 $db->createTable('TEST', ['id' => 'int(11) not null auto_increment', 'my_int' => 'int(11) null', 'my_text' => "varchar(32) not null default ''"], ['primary key (id)'], true);
 ```
-// returns boolean
+=> returns boolean
+
 
 
-// INSERT ROW FROM OBJECT
+# MANAGE DATA
+
+## INSERT ROWS
+
+```
+$test_id = $db->executeInsert("insert into TEST (result) values ('ok')");
+```
+
+### INSERT ROW FROM ARRAY
+```
+$test = [
+    'my_int' => '111',
+    'my_text' => 'ok',
+];
+$db->getTable('TEST')->insert($test);
+```
+=> returns insert_id
+
+### INSERT ROW FROM OBJECT
 ```
 $test = new \StdClass;
 $test->my_int = '111';
 $test->my_text = 'ok';
 $db->getTable('TEST')->insert($test);
 ```
-// returns insert_id
+=> returns insert_id
 
-// INSERT ROW FROM ARRAY
+
+## UPDATE ROWS
 ```
-$test = [
-	'my_int' => '111',
-	'my_text' => 'ok',
-];
-$db->getTable('TEST')->insert($test);
+$nb_rows_affected = $db->executeUpdate("update TEST set result = 'ok' where test_id = 1");
+```
+
+```
+$update = ['my_int' => '11111', 'my_text' => 'ok ok'];
+$where = ['id' => 1];
+$db->getTable('TEST')->update($update, $where);
+```
+=> return affected_rows
+
+
+## DELETE ROWS
+
+```
+$nb_rows_affected = $db->executeDelete("delete from TEST where test_id = 1");
+```
+
+```
+$where = ['test_id' => 1];
+$db->getTable('TEST')->delete($where);
+```
+
+
+## FETCH DATA
+
+```
+$rows = $db->executeSelect("select * from TEST");
+```
+
+### GET 1 ROW
+```
+$test = $db->getTable('TEST')->one($where, $options);
+//$test = $db->getTable('TEST')->getOne($where, $options);
+//$test = $db->getTable('TEST')->selectOne($where, $options);
 ```
-// returns insert_id
+=> returns array
 
 
-// UPDATE ROW
+### GET MULTIPLE ROWS
 ```
-$db->getTable('TEST')->update(['my_int' => '11111', 'my_text' => 'ok ok'], ['id' => 1]);
+$tests = $db->getTable('TEST')->all($where, $options);
+//$tests = $db->getTable('TEST')->getAll($where, $options);
+//$tests = $db->getTable('TEST')->selectAll($where, $options);
+//$tests = $db->getTable('TEST')->select($where, $options);
+
+$tests_count = $db->getTable('TEST')->count($where, $options);
+//$tests_count = $db->getTable('TEST')->selectCount($where, $options);
+
+$tuple = $db->getTable('TEST')->getAllWithFoundRows($where, $options);
+
+$tuple = $db->getTable('TEST')->getAllPagination($where, $nb_per_page, $page_idx, $options);
+```
+=> returns array (2 dimensions)
+
+
+# Models
+
+
+## Use the getTable method
+
+```
+$test = $db->getTable('tests')->one($where, $options);
+//$test = $db->getTable('tests')->getOne($where, $options);
+//$test = $db->getTable('tests')->selectOne($where, $options);
+
+$tests = $db->getTable('tests')->all($where, $options);
+//$tests = $db->getTable('tests')->getAll($where, $options);
+//$tests = $db->getTable('tests')->selectAll($where, $options);
+//$tests = $db->getTable('tests')->select($where, $options);
+
+$tests_count = $db->getTable('tests')->count($where, $options);
+//$tests_count = $db->getTable('tests')->selectCount($where, $options);
+
+$tuple = $db->getTable('tests')->getAllWithFoundRows($where, $options);
+
+$tuple = $db->getTable('tests')->getAllPagination($where, $nb_per_page, $page_idx, $options);
+```
+
+```
+$nb_rows_affected = $db->getTable('tests')->update($update_data, $where, $options);
+$test_id = $db->getTable('tests')->insert($insert_data, $options);
+$nb_rows_affected = $db->getTable('tests')->delete($where, $options);
+```
+
+
+## Use the model object
+
+### Requirement: Create model file
+
+```
+nano src/Models/Test.php
 ```
-// return affected_rows
 
+```
+<?php
+
+namespace MyApp\Models;
+
+use \KarmaFW\App;
+use \KarmaFW\Database\Sql\SqlTableModel;
 
-// GET ROWS
+
+class Utilisateur extends SqlTableModel
+{
+    public static $table_name = 'tests';
+    public static $primary_key = ['test_id'];
+}
 ```
-$test = $db->getTable('TEST')->getAll();
+
+Then, use the model object :
 ```
-// returns array (2 dimensions)
+use \App\Models\Test.php
+
 
-// GET ROW
+$test = Test::load($test_id, $where, $options);
+
+$test = Test::one($where, $options);
+//$test = Test::getOne($where, $options);
+//$test = Test::selectOne($where, $options);
+
+$tests = Test::all($where, $options);
+//$tests = Test::getAll($where, $options);
+//$tests = Test::selectAll($where, $options);
+//$tests = Test::select($where, $options);
+
+$tests_count = Test::count($where, $options);
+//$tests_count = Test::selectCount($where, $options);
+
+$tuple = Test::getAllWithFoundRows($where, $options);
+$tuple = Test::getAllPagination($where, $nb_per_page, $page_idx, $options);
 ```
-$test = $db->getTable('TEST')->getOne();
+
 ```
-// returns array
+$test_id = Test::insert($insert_data, $options);
+Test::insertAll($rows_of_data, $options);
+Test::insertAll($rows_of_data, ['ignore' => true]);
+Test::insertAll($rows_of_data, ['on duplicate key' => 'test_name = values(test_name)']);
 
+$nb_rows_affected = Test::update($update_data, $where, $options);
+$nb_rows_affected = Test::delete($where, $options);
+```
+
+
+# Tools
+
+```
+$db->escape($var);
+# Returns an escaped string of $var
+```
+
+```
+$db->buildSqlWhere($var);
+# Builds a SQL where clause from an array
+```
+
+```
+Test::getEmpty();
+$db->getTable('tests')->getEmpty();
+# Returns an object with all expected keys and empty values
+```

+ 1 - 1
src/Database/Sql/SqlQuery.php

@@ -2,7 +2,7 @@
 
 namespace KarmaFW\Database\Sql;
 
-use KarmaFW\Database\Sql\SqlResultSetError;
+use \KarmaFW\Database\Sql\SqlResultSetError;
 
 
 class SqlQuery

+ 3 - 0
src/Database/Sql/SqlSchema.php

@@ -3,9 +3,12 @@
 namespace KarmaFW\Database\Sql;
 
 
+// TODO: a transformer en une classe trait de SqlDb
+
 class SqlSchema
 {
 	protected $db;
+	
 
 	public function __construct($db)
 	{

+ 7 - 0
src/Database/Sql/SqlTable.php

@@ -316,6 +316,13 @@ class SqlTable
 		return empty($row['nb']) ? 0 : $row['nb'];
 	}
 
+
+	public function one($where=null, $options=[])
+	{
+		// Alias of getOne
+		return $this->getOne($where, $options);
+	}
+
 	public function selectOne($where=null, $options=[])
 	{
 		// Alias of getOne

+ 15 - 1
src/Database/Sql/SqlTableModel.php

@@ -69,7 +69,7 @@ class SqlTableModel
 	{
 		$db = static::getDb();
 		static::checkTable();
-		return $db->getTable(static::$table_name)->select($where, $options);
+		return $db->getTable(static::$table_name)->getAll($where, $options);
 	}
 
 
@@ -210,6 +210,20 @@ class SqlTableModel
 	}
 
 
+	public static function where($where=[])
+	{
+		static::checkTable();
+		return (new WhereQuery(static::$table_name))->where($where);
+	}
+
+	/*
+	public static function select($select=[])
+	{
+		static::checkTable();
+		return (new WhereQuery(static::$table_name))->select($select);
+	}
+	*/
+
 
 	public static function getDefaultItem()
 	{

+ 5 - 3
src/Database/Sql/SqlTools.php

@@ -3,6 +3,8 @@
 namespace KarmaFW\Database\Sql;
 
 
+// TODO: a transformer en une classe trait de SqlDb
+
 class SqlTools
 {
     protected $db;
@@ -233,7 +235,7 @@ class SqlTools
             $words = explode(" ", $q);
 
             foreach ($words as $word_idx => $word) {
-                $word_idx_score = 100 * max(1, 10 - $word_idx); // au dela de 10 mots, on compte comme le 10e mot
+                $word_idx_score = max(1, 10 - $word_idx) * max(1, 10 - $word_idx); // au dela de 10 mots, on compte comme le 10e mot
 
                 $w = $db->escape($word);
                 $w2 = $db->escape("%" . $word . "%");
@@ -243,8 +245,8 @@ class SqlTools
                 foreach ($search_fields as $term_idx => $field) {
                     $conditions_or[] = $field . " like " . $w2;
 
-                    $term_idx_score = 10 * max(1, 10 - $term_idx); // au dela de 10 fields, on compte comme le 10e field
-                    $select_sums[] = "( if( locate(" . $w . ", ifnull(" . $field . ",'') ) > 0, 1, 0 ) * " . $word_idx_score . " * " . $term_idx_score . " * greatest( 100 - locate(" . $w . ", ifnull(" . $field . ", '')), 1) )";
+                    $term_idx_score = max(1, 10 - $term_idx) * max(1, 10 - $term_idx); // au dela de 10 fields, on compte comme le 10e field
+                    $select_sums[] = "( if( locate(" . $w . ", ifnull(" . $field . ",'') ) > 0, 1, 0 ) * (1 / length(" . $field . ")) * " . $word_idx_score . " * " . $term_idx_score . " * greatest( 100 - locate(" . $w . ", ifnull(" . $field . ", '')), 1) )";
                 }
 
                 $word_condition = "(" . implode(" or ", $conditions_or) . ")";

+ 152 - 0
src/Database/Sql/WhereQuery.php

@@ -0,0 +1,152 @@
+<?php
+
+namespace KarmaFW\Database\Sql;
+
+use \KarmaFW\App;
+//use \KarmaFW\Database\Sql\SqlResultSetError;
+
+
+class WhereQuery
+{
+	protected $table_name;
+	protected $db;
+	protected $where = [];
+	protected $selects = [];
+	protected $join = [];
+	protected $sets = [];
+	protected $orders = [];
+	protected $options = [];
+
+
+	public function __construct($table_name, $db=null)
+	{
+		$this->table_name = $table_name;
+
+		if (empty($db)) {
+			$db = App::getDb();
+		}
+		$this->db = $db;
+	}
+
+
+	public function where($where)
+	{
+		$this->where = $where + $this->where;
+
+		return $this;
+	}
+
+
+	public function select($select)
+	{
+		if (! is_array($select)) {
+			$select = [ (string) $select ];
+		}
+		
+		$this->selects = $select + $this->selects;
+		$this->options['select'] = $this->selects;
+
+		return $this;
+	}
+
+	public function set(array $set)
+	{
+		$this->sets = $set + $this->sets;
+
+		return $this;
+	}
+
+	public function join($join)
+	{
+		if (! is_array($join)) {
+			$join = [ (string) $join ];
+		}
+
+		$this->join = $join + $this->join;
+		$this->options['join'] = $this->join;
+
+		return $this;
+	}
+
+	public function order($order)
+	{
+		if (! is_array($order)) {
+			$order = [ (string) $order ];
+		}
+		
+		$this->orders = $order + $this->orders;
+		$this->options['order by'] = $this->orders;
+
+		return $this;
+	}
+
+
+
+	public function insert($options=[])
+	{
+		return $this->db->getTable($this->table_name)->insert($this->sets, $options);
+	}
+
+	public function update($options=[])
+	{
+		return $this->db->getTable($this->table_name)->update($this->sets, $this->where, $options);
+	}
+
+	public function delete($options=[])
+	{
+		return $this->db->getTable($this->table_name)->delete($this->where, $options);
+	}
+
+
+	public function get($options=[])
+	{
+		if (empty($options['select'])) {
+			$options['select'] = empty($this->selects) ? '*' : $this->selects;
+		}
+		return $this->db->getTable($this->table_name)->all($this->where, $options);
+	}
+
+	public function getAll($options=[])
+	{
+		return $this->get($options);
+	}
+
+
+	public function getOne($options=[])
+	{
+		$select = empty($this->selects) ? '*' : $this->selects;
+		if (empty($options['select'])) {
+			$options['select'] = $select;
+		}
+		return $this->db->getTable($this->table_name)->one($this->where, $options);
+	}
+
+	public function getCount($options=[])
+	{
+		$select = empty($this->selects) ? '*' : $this->selects;
+		if (empty($options['select'])) {
+			$options['select'] = $select;
+		}
+		return $this->db->getTable($this->table_name)->count($this->where, $options);
+	}
+
+	public function getAllWithFoundRows($options=[])
+	{
+		$select = empty($this->selects) ? '*' : $this->selects;
+		if (empty($options['select'])) {
+			$options['select'] = $select;
+		}
+		return $this->db->getTable($this->table_name)->getAllWithFoundRows($this->where, $options);
+	
+	}
+
+	public function getAllPagination($nb_per_page=10, $page_idx=1, $options=[])
+	{
+		$select = empty($this->selects) ? '*' : $this->selects;
+		if (empty($options['select'])) {
+			$options['select'] = $select;
+		}
+		return $this->db->getTable($this->table_name)->getAllPagination($this->where, $nb_per_page, $page_idx, $options);
+	}
+
+}

+ 9 - 0
src/Lib/Hooks/HooksManager.php

@@ -2,6 +2,15 @@
 
 namespace KarmaFW\Lib\Hooks;
 
+
+// DEFINE YOUR CUSTOM HOOKS
+/*
+\KarmaFW\Lib\Hooks\HooksManager::addHookAction('webcontroller.init', function ($controller) {
+    echo "webcontroller hooked<hr />";
+});
+*/
+
+
 class HooksManager {
 	// source: https://stackoverflow.com/questions/5931324/what-is-a-hook-in-php
 

+ 104 - 0
src/Readme.md

@@ -0,0 +1,104 @@
+
+
+# App
+
+```
+class App
+```
+
+
+```
+public static function boot()
+# This method loads helpers files and init the SQL db connection.
+```
+
+```
+public static function registerHelpersDir($dir)
+# Add a directory to the helpers directories
+```
+
+```
+public static function unregisterHelpersDir($dir)
+# Remove a directory from the helpers directories
+```
+
+```
+public static function loadHelpers($dir)
+# Load all helpers files in a directory
+```
+
+[SQL db connection](Database/Sql/)
+```
+public static function getDb($instance_name=null, $dsn=null)
+# Load a SQL db connection
+```
+
+```
+public static function createOrmItem($table_name, $primary_key_values=[], $db=null)
+```
+
+
+# WebApp
+
+```
+class WebApp extends App
+```
+
+
+```
+public static function boot()
+# This method init the PHP session. It also calls App:boot()
+```
+
+
+[PHP/HTML template](Templates/)
+```
+public static function createTemplate($tpl_path=null, $variables=[], $layout=null, $templates_dirs=null)
+# Create a PHP/HTML template object
+```
+
+
+```
+public static function getUser()
+# 
+```
+
+```
+public static function setUser(array $user)
+# 
+```
+
+```
+public static function route
+# [Routing](Routing/) : Parse the current url and calls the defined controller method regarding the routes configuration.
+```
+
+```
+public static function error($http_status = 500, $meta_title = 'Server Error', $h1 = 'Error 500 - Server Error', $message = 'an error has occured')
+# Display an error page
+```
+
+```
+public static function error400($title = 'Bad request', $message = '')
+# 
+```
+
+```
+public static function error403($title = 'Forbidden', $message = 'You are not allowed')
+# 
+```
+
+```
+public static function error404($title = 'Page not Found', $message = "The page you're looking for doesn't exist")
+# 
+```
+
+```
+public static function error500($title = 'Internal Server Error', $message = 'An error has occured')
+# 
+```
+
+```
+public static function error503(title = 'Service Unavailable', $message = 'The service is unavailable')
+# 
+```

+ 33 - 0
src/Routing/Controllers/AppController.php

@@ -0,0 +1,33 @@
+<?php
+
+namespace KarmaFW\Routing\Controllers;
+
+use \KarmaFW\App;
+use \KarmaFW\Lib\Hooks\HooksManager;
+
+
+class AppController
+{
+	protected $db = null;
+
+
+	public function __construct()
+	{
+
+		if (defined('USE_HOOKS') && USE_HOOKS) {
+			HooksManager::applyHook('appcontroller.before', [$this]);
+		}
+
+		if (defined('DB_DSN')) {
+			$this->db = App::getDb();
+		}
+
+		if (defined('USE_HOOKS') && USE_HOOKS) {
+			HooksManager::applyHook('appcontroller.after', [$this]);
+		}
+
+		//echo "DEBUG " . __CLASS__ . ": controller instanced<hr />" . PHP_EOL;
+	}
+
+
+}

+ 104 - 0
src/Routing/Controllers/WebAppController.php

@@ -0,0 +1,104 @@
+<?php
+
+namespace KarmaFW\Routing\Controllers;
+
+use \KarmaFW\WebApp;
+use \KarmaFW\Lib\Hooks\HooksManager;
+
+
+class WebAppController extends AppController
+{
+	protected $request_uri = null;
+	protected $request_method = null;
+	protected $route = null;
+	protected $template;
+	protected $user_id;
+	protected $flash;
+
+	
+	public function __construct($request_uri=null, $request_method=null, $route=null)
+	{
+		parent::__construct($request_uri, $request_method, $route);
+
+		if (defined('USE_HOOKS') && USE_HOOKS) {
+			HooksManager::applyHook('webcontroller.before', [$this]);
+		}
+
+		$this->user_id = session('user_id');
+
+		$this->flash = session('flash');
+		$_SESSION['flash'] = []; // ['success' => 'action done !', 'error' => 'an error occured', 'warning' => 'notice ...']
+
+		if (defined('TPL_DIR')) {
+			$this->template = WebApp::createTemplate();
+
+			$this->template->assign('user_id', $this->user_id);
+			$this->template->assign('flash', $this->flash);
+
+			if (defined('APP_NAME')) {
+				$this->template->assign('meta_title', APP_NAME);
+				$this->template->assign('meta_description', APP_NAME);
+				$this->template->assign('h1', APP_NAME);
+			}
+		}
+
+		if (defined('USE_HOOKS') && USE_HOOKS) {
+			HooksManager::applyHook('webcontroller.after', [$this]);
+		}
+	}
+
+	public function getRoute()
+	{
+		return $this->route;
+	}
+
+	public function getRequestMethod()
+	{
+		return $this->request_method;
+	}
+
+	public function getRequestUri()
+	{
+		return $this->request_uri;
+	}
+
+	public function getTemplate()
+	{
+		return $this->template;
+	}
+
+
+
+
+	public function error($http_status, $meta_title=null, $h1=null, $message=null)
+	{
+		return WebApp::error($http_status, $meta_title, $h1, $message);
+	}
+
+	public function error400($title = 'Bad request', $message = '')
+	{
+		return $this->error(400, $title, $title, $message);
+	}
+
+	public function error403($title = 'Forbidden', $message = 'you are not allowed')
+	{
+		return $this->error(403, $title, $title, $message);
+	}
+
+	public function error404($title = 'Page not found', $message = "The page you're looking for doesn't exist")
+	{
+		return $this->error(404, $title, $title, $message);
+	}
+
+	public function error500($title = 'Internal Server Error', $message = 'An error has occured')
+	{
+		return $this->error(500, $title, $title, $message);
+	}
+
+	public function error503($title = 'Service Unavailable', $message = 'The service is unavailable')
+	{
+		return $this->error(503, $title, $title, $message);
+	}
+
+
+}

+ 0 - 42
src/Routing/Controllers/WebController.php

@@ -1,42 +0,0 @@
-<?php
-
-namespace KarmaFW\Routing\Controllers;
-
-use \KarmaFW\Lib\Hooks\HooksManager;
-
-
-class WebController
-{
-	protected $route = null;
-	protected $request_method = null;
-	protected $request_uri = null;
-
-
-	public function __construct($route, $request_method, $request_uri)
-	{
-		$this->route = $route;
-		$this->request_method = $request_method;
-		$this->request_uri = $request_uri;
-		
-		//echo "DEBUG " . __CLASS__ . ": controller instanced<hr />" . PHP_EOL;
-
-		HooksManager::applyHook('webcontroller__init', [$this]);
-	}
-
-
-	public function getRoute()
-	{
-		return $this->route;
-	}
-
-	public function getRequestMethod()
-	{
-		return $this->request_method;
-	}
-
-	public function getRequestUri()
-	{
-		return $this->request_uri;
-	}
-
-}

+ 32 - 0
src/Routing/Readme.md

@@ -0,0 +1,32 @@
+
+
+Exemple de fichier de routes ( ./config/routes.php )
+
+```
+use \KarmaFW\Routing\Router;
+
+
+// Homepage
+Router::get('/', ['App\\Controllers\\PublicController', 'homepage'])->setName('home');
+
+
+// login/logout
+Router::get('/logout', ['App\\Controllers\\PublicController', 'logout'])->setName('logout');
+Router::get('/login', ['App\\Controllers\\PublicController', 'login'])->setName('login');
+Router::post('/login', ['App\\Controllers\\PublicController', 'login_post']);
+
+
+// clients (example)
+Router::get('/clients', ['App\\Controllers\\ClientController', 'clients_list'])->setName('clients_list');
+Router::get('/clients/nouveau-client', ['App\\Controllers\\ClientController', 'client_new'])->setName('client_new');
+Router::get('/clients/([0-9]+)-([^/]+)$', ['App\\Controllers\\ClientController', 'client_edit'], 'regex', ['client_id', 'slug'])->setName('client_edit');
+Router::post('/clients/save-client', ['App\\Controllers\\ClientController', 'client_save'])->setName('client_save');
+Router::delete('/clients/delete-client', ['App\\Controllers\\ClientController', 'client_delete'])->setName('client_delete');
+
+
+
+// 404
+Router::get('.*', ['App\\Controllers\\ErrorController', 'error404'], 'regex');
+
+```
+

+ 5 - 0
src/Routing/Route.php

@@ -86,6 +86,11 @@ class Route
 		return $this->matched_params;
 	}
 
+	public function getMatchedPrefix()
+	{
+		return $this->matched_prefix;
+	}
+
 	// Get route name
 	public function getName()
 	{

+ 40 - 2
src/Routing/Router.php

@@ -2,6 +2,8 @@
 
 namespace KarmaFW\Routing;
 
+use \KarmaFW\WebApp;
+
 
 class Router
 {
@@ -54,6 +56,11 @@ class Router
 		return $route;
 	}
 
+	public static function error404($callback=null)
+	{
+		return self::all('.*', $callback, 'regex');
+	}
+
 
 	// Allow whatever method (GET, POST, HEAD, OPTION, DELETE, PUT, ...)
 	public static function all($url_match, $callback=null, $type_match='exact', $regex_params=[])
@@ -73,6 +80,36 @@ class Router
 		return self::Add('POST', $url_match, $callback, $type_match, $regex_params);
 	}
 
+	// DELETE method
+	public static function delete($url_match, $callback=null, $type_match='exact', $regex_params=[])
+	{
+		return self::Add('DELETE', $url_match, $callback, $type_match, $regex_params);
+	}
+
+	// PUT method
+	public static function put($url_match, $callback=null, $type_match='exact', $regex_params=[])
+	{
+		return self::Add('PUT', $url_match, $callback, $type_match, $regex_params);
+	}
+
+	// HEAD method
+	public static function head($url_match, $callback=null, $type_match='exact', $regex_params=[])
+	{
+		return self::Add('HEAD', $url_match, $callback, $type_match, $regex_params);
+	}
+
+	// PATCH method
+	public static function patch($url_match, $callback=null, $type_match='exact', $regex_params=[])
+	{
+		return self::Add('PATCH', $url_match, $callback, $type_match, $regex_params);
+	}
+
+	// OPTIONS method
+	public static function options($url_match, $callback=null, $type_match='exact', $regex_params=[])
+	{
+		return self::Add('OPTIONS', $url_match, $callback, $type_match, $regex_params);
+	}
+
 
 	// Lookup the first matching route then execute it 
 	public static function routeByUrl($request_method, $request_uri, $debug = false)
@@ -128,8 +165,9 @@ class Router
 		if (gettype($callback) == 'array') {
 			//echo " => ARRAY !<br />" . PHP_EOL;
 			//pre($callback, 1);
-			$class = new $callback[0]($route, $request_method, $request_uri);
-			call_user_func([$class, $callback[1]], $matched_params);
+			$controller = new $callback[0]($request_uri, $request_method, $route);
+			WebApp::$controller = $controller;
+			call_user_func([$controller, $callback[1]], $matched_params);
 
 		} else {
 			//echo " => FUNCTION !<br />" . PHP_EOL;

+ 29 - 0
src/WebApp.md

@@ -0,0 +1,29 @@
+
+
+## Structure d'une app web
+
+```
+config
+    config.php
+    routes.php
+public
+    .htaccess
+    index.php
+    images
+    css
+    js
+    vendor
+src
+  Controllers
+    MyAppController
+    HomeController
+  Models
+    User
+  helpers
+    helpers_myapp.php
+templates
+    homepage.tpl.php
+vendor
+    karmasolutions/karmafw
+```
+

+ 193 - 0
src/WebApp.php

@@ -0,0 +1,193 @@
+<?php
+
+namespace KarmaFW;
+
+use \KarmaFW\Routing\Router;
+use \KarmaFW\Routing\Controllers\WebAppController;
+use \KarmaFW\Lib\Hooks\HooksManager;
+use \KarmaFW\Templates\PhpTemplate;
+
+
+class WebApp extends App
+{
+	public static $session_user = false; // user connected with a session
+	public static $controller = null;
+
+
+	public static function boot()
+	{
+		parent::boot();
+
+		if (defined('USE_HOOKS') && USE_HOOKS) {
+			HooksManager::applyHook('webapp.boot.before', []);
+		}
+
+		// start session
+		if (empty(session_id())) {
+			if (defined('SESSION_NAME') && ! empty(SESSION_NAME)) {
+				session_name(SESSION_NAME);
+			}
+
+			if (defined('SESSION_DURATION') && is_numeric(SESSION_DURATION)) {
+				ini_set('session.gc_maxlifetime', SESSION_DURATION);
+				session_set_cookie_params(SESSION_DURATION);
+				// Note: si cron est actif, il faut modifier la valeur de session.gc_maxlifetime dans /etc/php/7.3/apache2/php.ini (voir /etc/cron.d/php)
+			}
+
+			session_start();
+		}
+
+		if (defined('USE_HOOKS') && USE_HOOKS) {
+			HooksManager::applyHook('webapp.boot.after', []);
+		}
+	}
+
+
+	public static function createTemplate($tpl_path=null, $variables=[], $layout=null, $templates_dirs=null)
+	{
+		return new PhpTemplate($tpl_path, $variables, $layout, $templates_dirs);
+	}
+
+
+	/*
+	public static function getUser()
+	{
+		return self::$session_user;
+	}
+
+	public static function setUser($user)
+	{
+		self::$session_user = $user;
+	}
+	*/
+
+
+	public static function route()
+	{
+		if (! self::$booted) {
+			self::boot();
+		}
+
+		// routing: parse l'url puis transfert au controller
+
+		if (defined('USE_HOOKS') && USE_HOOKS) {
+			HooksManager::applyHook('webapp.route.before', []);
+		}
+
+		$route = Router::routeByUrl( $_SERVER['REQUEST_METHOD'], $_SERVER['REQUEST_URI'], false );
+
+		if (defined('USE_HOOKS') && USE_HOOKS) {
+			HooksManager::applyHook('webapp.route.after', [$route]);
+		}
+
+		if ($route) {
+			//echo "success: route ok";
+			if (defined('USE_HOOKS') && USE_HOOKS) {
+				HooksManager::applyHook('webapp.route.success', [$_SERVER['REQUEST_URI'], $_SERVER['REQUEST_METHOD'], $route]);
+			}
+			exit(0);
+
+		} else if ($route === null) {
+			// route found but callback is not callable
+			self::error404('Page not Found', 'Warning: route callback is not callable');
+			exit(1);
+
+		} else if ($route === 0) {
+			// route found but no callback defined
+			if (defined('USE_HOOKS') && USE_HOOKS) {
+				HooksManager::applyHook('webapp.route.error', []);
+			}
+			self::error404('Page not Found', "Warning: route found but no callback defined");
+			exit(1);
+
+		} else if ($route === false) {
+			// no matching route
+			if (defined('USE_HOOKS') && USE_HOOKS) {
+				HooksManager::applyHook('webapp.route.error', []);
+			}
+			self::error404('Page not Found', "Warning: no matching route");
+			exit(1);
+
+		} else {
+			// other cases
+			if (defined('USE_HOOKS') && USE_HOOKS) {
+				HooksManager::applyHook('webapp.route.error', []);
+			}
+			self::error404('Page not Found', "Warning: cannot route");
+			exit(1);
+		}
+
+	}
+
+
+	public static function error($http_status = 500, $meta_title = 'Server Error', $h1 = 'Error 500 - Server Error', $message = 'an error has occured')
+	{
+		if (! self::$controller) {
+			self::$controller = new WebAppController();
+		}
+		if (self::$controller && $template = self::$controller->getTemplate()) {
+			$template->assign('meta_title', $meta_title);
+			$template->assign('h1', $h1);
+			$template->assign('p', $message);
+			$template->assign('http_status', $http_status);
+
+			$error_template = 'error.tpl.php';
+			if (defined('ERROR_TEMPLATE')) {
+				$error_template = ERROR_TEMPLATE;
+			}
+
+			$template->display($error_template);
+
+		} else {
+			//header("HTTP/1.0 " . $error_code . " " . $title);
+
+			$output_html = '';
+			$output_html .= '<html>' . PHP_EOL;
+			$output_html .= '<head>' . PHP_EOL;
+			if (! empty($meta_title)) {
+				$output_html .= '<title>' . $meta_title . '</title>' . PHP_EOL;
+			}
+			$output_html .= '</head>' . PHP_EOL;
+			$output_html .= '<body>' . PHP_EOL;
+			if (! empty($h1)) {
+				$output_html .= '<h1>' . $h1 . '</h1>' . PHP_EOL;
+			}
+			if (! empty($message)) {
+				$output_html .= '<p>' . $message . '</p>' . PHP_EOL;
+			}
+			$output_html .= '</body>' . PHP_EOL;
+			$output_html .= '</html>' . PHP_EOL;
+
+			echo $output_html;
+		}
+
+		exit;
+	}
+
+
+	public static function error400($title = 'Bad request', $message = '')
+	{
+		return self::error(400, $title, $title, $message);
+	}
+
+	public static function error403($title = 'Forbidden', $message = 'You are not allowed')
+	{
+		return self::error(403, $title, $title, $message);
+	}
+
+	public static function error404($title = 'Page not Found', $message = "The page you're looking for doesn't exist")
+	{
+		return self::error(404, $title, $title, $message);
+	}
+
+	public static function error500($title = 'Internal Server Error', $message = 'An error has occured')
+	{
+		return $this->error(500, $title, $title, $message);
+	}
+
+	public static function error503($title = 'Service Unavailable', $message = 'The service is unavailable')
+	{
+		return $this->error(503, $title, $title, $message);
+	}
+
+}

+ 4 - 0
helpers/helpers_array.php → src/helpers/helpers_array.php

@@ -82,6 +82,10 @@ if (! function_exists('arrayToList')) {
 if (! function_exists('import_xls')) {
 	function import_xls($filepath, $fields=[], $encode_utf8=false) {
 		$spreadsheet = PhpSpreadsheetIOFactory::load($filepath);
+
+		//$reader = new \PhpOffice\PhpSpreadsheet\Reader\Xlsx();
+		//$reader->setReadDataOnly(true);
+		//$spreadsheet = $reader->load($filepath);
 		
 		$sheetData = $spreadsheet->getActiveSheet()->toArray();
 		//pre($sheetData, 1);

+ 43 - 0
helpers/helpers_default.php → src/helpers/helpers_default.php

@@ -177,6 +177,9 @@ if (! function_exists('getRouteUrl')) {
 
 if (! function_exists('date_us_to_fr')) {
 	function date_us_to_fr($date_us, $include_time=false) {
+		if (empty($date_us)) {
+			return null;
+		}
 		$time = ($include_time) ? substr($date_us, 10) : "";
 		$date_us = substr($date_us, 0, 10);
 		$parts = explode('-', $date_us);
@@ -184,8 +187,28 @@ if (! function_exists('date_us_to_fr')) {
 	}
 }
 
+if (! function_exists('date_us2_to_fr')) {
+	function date_us2_to_fr($date_us, $include_time=false) {
+		if (empty($date_us)) {
+			return null;
+		}
+		$time = ($include_time) ? substr($date_us, 10) : "";
+		$date_us = substr($date_us, 0, 10);
+		$parts = explode('/', $date_us);
+		$parts2 = [
+			substr('00' . $parts[1], -2),
+			substr('00' . $parts[0], -2),
+			$parts[2],
+		];
+		return implode('/', $parts2) . $time;
+	}
+}
+
 if (! function_exists('date_fr_to_us')) {
 	function date_fr_to_us($date_fr, $include_time=false) {
+		if (empty($date_fr)) {
+			return null;
+		}
 		$time = ($include_time) ? substr($date_fr, 10) : "";
 		$date_fr = substr($date_fr, 0, 10);
 		$parts = explode('/', $date_fr);
@@ -220,3 +243,23 @@ if (! function_exists('get_url_path')) {
 	}
 }
 
+
+if (! function_exists('rrmdir')) {
+	function rrmdir($src) {
+		// https://www.php.net/manual/fr/function.rmdir.php
+	    $dir = opendir($src);
+	    while(false !== ( $file = readdir($dir)) ) {
+	        if (( $file != '.' ) && ( $file != '..' )) {
+	            $full = $src . '/' . $file;
+	            if ( is_dir($full) ) {
+	                rrmdir($full);
+	            }
+	            else {
+	                unlink($full);
+	            }
+	        }
+	    }
+	    closedir($dir);
+	    rmdir($src);
+	}
+}

+ 0 - 0
helpers/helpers_form.php → src/helpers/helpers_form.php


+ 0 - 0
helpers/helpers_smtp_hooks.php → src/helpers/helpers_smtp_hooks.php