Эх сурвалжийг харах

Merge branch 'master' of ssh://gogs.karmas.fr/KarmaSolutions/KarmaFW

Max F 4 жил өмнө
parent
commit
dde2d5c752

+ 2 - 2
composer.json

@@ -1,5 +1,5 @@
 {
-    "name": "karmasolutions/karmafw",
+    "name": "karma-solutions/karmafw",
     "description": "PHP framework",
     "authors": [
         {
@@ -7,7 +7,7 @@
             "email": "max@karma-solutions.fr"
         }
     ],
-    "minimum-stability": "dev",
+    "license": "MIT",
     "require": {
         "php": ">=5.4.0"
     },

+ 19 - 0
composer.json.bak

@@ -0,0 +1,19 @@
+{
+    "name": "karma-solutions/karmafw",
+    "description": "PHP framework",
+    "authors": [
+        {
+            "name": "Karma Solutions",
+            "email": "max@karma-solutions.fr"
+        }
+    ],
+    "license": "MIT",
+    "require": {
+        "php": ">=5.4.0"
+    },
+    "autoload": {
+        "psr-4": {
+            "KarmaFW\\": "src/"
+        }
+    }
+}

+ 0 - 51
src/App.php

@@ -210,55 +210,4 @@ class App
 		return $instances[$instance_name];
 	}
 
-
-	/*
-	public static function createOrmItem($table_name, $primary_key_values=[], $db=null)
-	{
-		return new SqlOrmModel($table_name, $primary_key_values, $db);
-	}
-	*/
-
-
-	/*
-	public static function routeCommand($argv)
-	{
-		if (! self::$booted) {
-			self::boot();
-		}
-
-		$arguments = array_slice($argv, 0);
-		$script_name = array_shift($arguments);
-		$command_name = array_shift($arguments);
-		$class_name = implode('', array_map('ucfirst', explode("_", $command_name)));
-
-		if (! empty($class_name)) {
-			$class_user = '\\App\\Commands\\' . $class_name;
-			$class_fw = '\\KarmaFW\\Commands\\' . $class_name;
-
-			if (class_exists($class_user)) {
-				$command = new $class_user;
-				$command->run($arguments);
-				exit(0);
-
-			} else if (class_exists($class_fw)) {
-				$command = new $class_fw;
-				$command->run($arguments);
-				exit(0);
-
-			} else {
-				echo "PHP Console script" . PHP_EOL . PHP_EOL; 
-				echo "Usage: php console.php <command> [arguments]" . PHP_EOL . PHP_EOL;
-				echo "Warning: invalid command" . PHP_EOL;
-			}
-
-		} else {
-			echo "PHP Console script" . PHP_EOL . PHP_EOL; 
-			echo "Usage: php console.php <command> [arguments]" . PHP_EOL . PHP_EOL;
-			echo "Warning: missing command" . PHP_EOL;
-		}
-
-		exit(1);
-	}
-	*/
-
 }

+ 22 - 0
src/App/Middlewares/CommandRouter.php

@@ -29,6 +29,8 @@ class CommandRouter
 		
 		$class_name = implode('', array_map('ucfirst', explode("_", $command_name)));
 
+		$class_name = str_replace('/', '\\', $class_name);
+
 		if (! empty($class_name)) {
 			$class_user = '\\App\\Commands\\' . $class_name;
 			$class_fw = '\\KarmaFW\\Commands\\' . $class_name;
@@ -61,6 +63,26 @@ class CommandRouter
 
 		} else {
 			$this->usage("missing command");
+
+			
+			$user_commands_files = glob(APP_DIR . '/src/Commands/*.php');
+			$user_commands = array_map(function ($platform_path) {
+				$path_infos = pathinfo($platform_path);
+				return $path_infos['filename'];
+			}, $user_commands_files);
+
+			echo PHP_EOL . "Available user commands :" . PHP_EOL;
+			echo ' - ' . implode(PHP_EOL . ' - ', $user_commands) . PHP_EOL;
+
+
+			$karmafw_commands_files = glob(APP_DIR . '/vendor/karmasolutions/karmafw/src/Commands/*.php');
+			$karmafw_commands = array_map(function ($platform_path) {
+				$path_infos = pathinfo($platform_path);
+				return $path_infos['filename'];
+			}, $karmafw_commands_files);
+
+			echo PHP_EOL . "Available karmafw commands :" . PHP_EOL;
+			echo ' - ' . implode(PHP_EOL . ' - ', $karmafw_commands) . PHP_EOL;
 		}
 
 		return $response;

+ 1 - 1
src/App/Middlewares/DebugBar.php

@@ -45,7 +45,7 @@ class DebugBar
 		$response = $next($request, $response);
 
 		$is_html = (empty($response->getContentType()) || strpos($response->getContentType(), 'text/html') === 0);
-		$show_debugbar = ($load_debugbar && $is_html && $response->getStatus() == 200);
+		$show_debugbar = ($load_debugbar && $is_html && $response->getStatus() == 200 && (! defined('DISABLE_DEBUGBAR') || ! DISABLE_DEBUGBAR));
 
 		if ($show_debugbar) {
 

+ 9 - 5
src/App/Middlewares/SessionHandler.php

@@ -12,14 +12,18 @@ class SessionHandler
 	public function __invoke(Request $request, Response $response, callable $next)
 	{
 		//$savePath = ini_get('session.save_path');
-        //ini_set('session.save_path', $savePath);
-        //ini_set('session.save_handler', 'files');
+		//ini_set('session.save_path', $savePath);
+		//ini_set('session.save_handler', 'files');
 
-        /*
-        // Pour utiliser Redis
+		/*
+		// Pour utiliser Redis
 		ini_set('session.save_handler, "redis");
 		ini_set('session.save_path, "tcp://host1:6379?weight=1, tcp://host2:6379?weight=2&timeout=2.5, tcp://host3:6379?weight=2&read_timeout=2.5");
-        */
+		*/
+
+		$session_gc_maxlifetime = ini_get('session.gc_maxlifetime');
+		$session_duration = $session_gc_maxlifetime ? $session_gc_maxlifetime : 3600/2;
+		session_set_cookie_params($session_duration);
 
 		session_start();
 		

+ 26 - 0
src/App/Middlewares/UrlRouter.php

@@ -97,8 +97,34 @@ class UrlRouter
 				return $response->html($error_message, $error_code);
 			}
 
+			// ERROR 403
+			if (in_array($error_code, [401, 403])) {
+				// if $error_code is a 403 page not found
+				if (empty($error_message)) {
+					$error_message = '<title>Access denied</title><h1>Access denied</h1><p>Page access denied</p>';
+				}
+				return $response->html($error_message, $error_code);
+			}
+
+			// ERROR 400
+			if (in_array($error_code, [400])) {
+				// if $error_code is a 400 page not found
+				if (empty($error_message)) {
+					$error_message = '<title>Bad request</title><h1>Bad request</h1><p>Bad request</p>';
+				}
+				return $response->html($error_message, $error_code);
+			}
+
 
 			// ERROR 500
+			if (in_array($error_code, [500])) {
+				// if $error_code is a 500 page not found
+				if (empty($error_message)) {
+					$error_message = '<title>Server error</title><h1>Server error</h1><p>An error has occured</p>';
+				}
+				return $response->html($error_message, $error_code);
+			}
+
 
 			if (! $this->catch_exceptions) {
 				// on relance l'exception => pour laisser la gestion de l'erreur à un handler parent (ou le error_handler par defaut de PHP)

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

@@ -370,6 +370,14 @@ class SqlTable
 			$options['group_by'] = $options['group by'];
 		}
 
+		if (isset($options['group_by']) && is_array($options['group_by'])) {
+			$options['group_by'] = implode(', ', $options['group_by']);
+		}
+
+		if (isset($options['order_by']) && is_array($options['order_by'])) {
+			$options['order_by'] = implode(', ', $options['order_by']);
+		}
+
 		$limit_sql = isset($options['limit']) ? ("limit " . $options['limit']) : "";
 		$group_by_sql = isset($options['group_by']) ? ("group by " . $options['group_by']) : "";
 		$order_by_sql = isset($options['order_by']) ? ("order by " . $options['order_by']) : "";

+ 52 - 12
src/Database/Sql/WhereQuery.php

@@ -11,10 +11,12 @@ class WhereQuery
 	protected $table_name;
 	protected $db;
 	protected $where = [];
+	protected $alias = null;
 	protected $selects = [];
 	protected $join = [];
 	protected $sets = [];
 	protected $orders = [];
+	protected $groups = [];
 	protected $options = [];
 
 
@@ -36,6 +38,32 @@ class WhereQuery
 		return $this;
 	}
 
+	public function alias($alias)
+	{
+		$this->alias = $alias;
+		$this->options['alias'] = $this->alias;
+
+		return $this;
+	}
+
+	public function options($options)
+	{
+		$this->options += $options;
+		$this->options = $this->options;
+
+		return $this;
+	}
+
+	public function getOptions()
+	{
+		return $this->options;
+	}
+
+	public function setOptions($options=[])
+	{
+		$this->options = $options;
+	}
+
 
 	public function select($select)
 	{
@@ -68,13 +96,25 @@ class WhereQuery
 		return $this;
 	}
 
-	public function order($order)
+	public function group($group_by)
+	{
+		if (! is_array($group_by)) {
+			$group_by = [ (string) $group_by ];
+		}
+		
+		$this->groups = $group_by + $this->groups;
+		$this->options['order by'] = $this->groups;
+
+		return $this;
+	}
+
+	public function order($order_by)
 	{
-		if (! is_array($order)) {
-			$order = [ (string) $order ];
+		if (! is_array($order_by)) {
+			$order_by = [ (string) $order_by ];
 		}
 		
-		$this->orders = $order + $this->orders;
+		$this->orders = $order_by + $this->orders;
 		$this->options['order by'] = $this->orders;
 
 		return $this;
@@ -84,17 +124,17 @@ class WhereQuery
 
 	public function insert($options=[])
 	{
-		return $this->db->getTable($this->table_name)->insert($this->sets, $options);
+		return $this->db->getTable($this->table_name)->insert($this->sets, $this->options + $options);
 	}
 
 	public function update($options=[])
 	{
-		return $this->db->getTable($this->table_name)->update($this->sets, $this->where, $options);
+		return $this->db->getTable($this->table_name)->update($this->sets, $this->where, $this->options + $options);
 	}
 
 	public function delete($options=[])
 	{
-		return $this->db->getTable($this->table_name)->delete($this->where, $options);
+		return $this->db->getTable($this->table_name)->delete($this->where, $this->options + $options);
 	}
 
 
@@ -103,7 +143,7 @@ class WhereQuery
 		if (empty($options['select'])) {
 			$options['select'] = empty($this->selects) ? '*' : $this->selects;
 		}
-		return $this->db->getTable($this->table_name)->all($this->where, $options);
+		return $this->db->getTable($this->table_name)->all($this->where, $this->options + $options);
 	}
 
 	public function getAll($options=[])
@@ -118,7 +158,7 @@ class WhereQuery
 		if (empty($options['select'])) {
 			$options['select'] = $select;
 		}
-		return $this->db->getTable($this->table_name)->one($this->where, $options);
+		return $this->db->getTable($this->table_name)->one($this->where, $this->options + $options);
 	}
 
 	public function getCount($options=[])
@@ -127,7 +167,7 @@ class WhereQuery
 		if (empty($options['select'])) {
 			$options['select'] = $select;
 		}
-		return $this->db->getTable($this->table_name)->count($this->where, $options);
+		return $this->db->getTable($this->table_name)->count($this->where, $this->options + $options);
 	}
 
 	public function getAllWithFoundRows($options=[])
@@ -136,7 +176,7 @@ class WhereQuery
 		if (empty($options['select'])) {
 			$options['select'] = $select;
 		}
-		return $this->db->getTable($this->table_name)->getAllWithFoundRows($this->where, $options);
+		return $this->db->getTable($this->table_name)->getAllWithFoundRows($this->where, $this->options + $options);
 	
 	}
 
@@ -146,7 +186,7 @@ class WhereQuery
 		if (empty($options['select'])) {
 			$options['select'] = $select;
 		}
-		return $this->db->getTable($this->table_name)->getAllPagination($this->where, $nb_per_page, $page_idx, $options);
+		return $this->db->getTable($this->table_name)->getAllPagination($this->where, $nb_per_page, $page_idx, $this->options + $options);
 	}
 
 }

+ 1 - 1
src/Http/Request.php

@@ -116,7 +116,7 @@ class Request
 
 	public function getFullUrl()
 	{
-		$scheme = $this->isSecure() ? 'https://' : 'http:';
+		$scheme = $this->isSecure() ? 'https://' : 'http://';
 		return $scheme . $this->SERVER['SERVER_NAME'] . $this->url;
 	}
 

+ 21 - 4
src/Http/Response.php

@@ -178,7 +178,7 @@ class Response
 		return $this->setBody($body);
 	}
 	
-	public function json($json, $download_file_name=null, $status=200, $content_type='application/json; charset=utf8')
+	public function json($json, $download_file_name=null, $status=200, $content_type='application/json; charset=utf8', $add_content_disposition=true)
 	{
 		if (! is_string($json) || ! in_array(substr($json, 0, 1), ['"', "'", '[', '{'])) {
 			$json = json_encode($json);
@@ -187,12 +187,16 @@ class Response
 		
 		$this->download_file_name = $download_file_name;
 
+		if ($add_content_disposition && !empty($this->download_file_name)) {
+			$this->headers['Content-disposition'] = 'attachment; filename="' . basename($this->download_file_name) . '"';
+		}
+
 		return $this->setBody($json)
 				->setContentType($content_type)
 				->setStatus($status);
 	}
 
-	public function csv(array $rows, $download_file_name=null, $status=200, $content_type='text/csv; charset=utf8')
+	public function csv(array $rows, $download_file_name=null, $status=200, $content_type='text/csv; charset=utf8', $add_content_disposition=true)
 	{
 		if (is_array($rows)) {
 			// transform array to csv
@@ -200,19 +204,28 @@ class Response
 		} else {
 			$body = "";
 		}
+		//return $this->download($json, $download_file_name, $status, $content_type);
 		
 		$this->download_file_name = $download_file_name;
 
+		if ($add_content_disposition && !empty($this->download_file_name)) {
+			$this->headers['Content-disposition'] = 'attachment; filename="' . basename($this->download_file_name) . '"';
+		}
+
 		return $this->setBody($body)
 				->setContentType($content_type)
 				->setStatus($status);
 	}
 	
-	public function download($file_path, $download_file_name=null, $status=200, $content_type='application/octet-stream')
+	public function download($file_path, $download_file_name=null, $status=200, $content_type='application/octet-stream', $add_content_disposition=true)
 	{
 		$this->download_file_path = $file_path;
 		$this->download_file_name = empty($download_file_name) ? basename($file_path) : $download_file_name;
 
+		if ($add_content_disposition && !empty($this->download_file_name)) {
+			$this->headers['Content-disposition'] = 'attachment; filename="' . basename($this->download_file_name) . '"';
+		}
+
 		return $this->setBody('')
 				->setContentType($content_type)
 				->setStatus($status);
@@ -325,7 +338,7 @@ class Response
 			$this->setContentType($content_type);
 
 			$this->headers['Content-Transfer-Encoding'] = "Binary";
-			$this->headers['Content-disposition'] = 'attachment; filename="' . basename($this->download_file_name) . '"';
+			//$this->headers['Content-disposition'] = 'attachment; filename="' . basename($this->download_file_name) . '"';
 		}
 
 		if (empty($this->headers['Content-Type']) && ! empty($this->content_type)) {
@@ -398,6 +411,8 @@ class Response
 			->setStatus($status)
 			->setBody('');
 
+		// TODO: throw response immediately
+
 		return $this;
 	}
 
@@ -408,6 +423,8 @@ class Response
 			->setContentType($content_type)
 			->setBody($body);
 
+		// TODO: throw response immediately
+
 		return $this;
 	}
 

+ 17 - 14
src/Routing/Controllers/WebAppController.php

@@ -83,7 +83,7 @@ class WebAppController extends AppController
 
 
 
-	public function error($http_status = 500, $meta_title = 'Server Error', $h1 = 'Error 500 - Server Error', $message = 'an error has occured')
+	protected function showError($http_status = 500, $meta_title = 'Server Error', $h1 = 'Error 500 - Server Error', $message = 'an error has occured')
 	{
 		if ($template = $this->getTemplate()) {
 			$template->assign('meta_title', $meta_title);
@@ -97,7 +97,8 @@ class WebAppController extends AppController
 			}
 
 			//$template->display($error_template);
-			return $this->response->html( $template->fetch($error_template) , $http_status);
+			//return $this->response->html( $template->fetch($error_template) , $http_status);
+			$response_content = $template->fetch($error_template);
 
 		} else {
 			//header("HTTP/1.0 " . $http_status . " " . $meta_title);
@@ -121,35 +122,37 @@ class WebAppController extends AppController
 
 			//echo $output_html;
 
-			return $this->response->html($output_html, $http_status);
+			//return $this->response->html($output_html, $http_status);
+			$response_content = $output_html;
 		}
 
+		
+		throw new \Exception($response_content, $http_status);
 	}
 
-	public function error400($title = 'Bad request', $message = '')
+	protected function showError400($title = 'Bad request', $message = '')
 	{
-		return $this->error(400, $title, $title, $message);
+		return $this->showError(400, $title, $title, $message);
 	}
 
-	public function error403($title = 'Forbidden', $message = 'you are not allowed')
+	protected function showError403($title = 'Forbidden', $message = 'you are not allowed')
 	{
-		return $this->error(403, $title, $title, $message);
+		return $this->showError(403, $title, $title, $message);
 	}
 
-	public function error404($title = 'Page not found', $message = "The page you're looking for doesn't exist")
+	protected function showError404($title = 'Page not found', $message = "The page you're looking for doesn't exist")
 	{
-		return $this->error(404, $title, $title, $message);
+		return $this->showError(404, $title, $title, $message);
 	}
 
-	public function error500($title = 'Internal Server Error', $message = 'An error has occured')
+	protected function showError500($title = 'Internal Server Error', $message = 'An error has occured')
 	{
-		return $this->error(500, $title, $title, $message);
+		return $this->showError(500, $title, $title, $message);
 	}
 
-	public function error503($title = 'Service Unavailable', $message = 'The service is unavailable')
+	protected function showError503($title = 'Service Unavailable', $message = 'The service is unavailable')
 	{
-		return $this->error(503, $title, $title, $message);
+		return $this->showError(503, $title, $title, $message);
 	}
 
-
 }

+ 92 - 53
src/Templates/LightweightTemplate.php

@@ -14,14 +14,28 @@ class LightweightTemplate {
 	static $cache_enabled = (ENV == 'prod') || true;
 	static $tpl_last_updated = null;
 
-
+	protected $tpl_cache_enabled = true; // and if $cache_enabled is true
 	protected $data = [];
 
+
 	public function __construct($tpl_path=null, $variables=[], $layout=null) 
 	{
 		$this->data = $variables;
 	}
 
+	
+	public function disableCache() 
+	{
+		$this->tpl_cache_enabled = false;
+	}
+
+
+	public function enableCache() 
+	{
+		$this->tpl_cache_enabled = true;
+	}
+
+
 	public function assign($k, $v=null) 
 	{
 		if (is_array($k)) {
@@ -34,17 +48,20 @@ class LightweightTemplate {
 			$this->data[$k] = $v;
 		}
 	}
+
 	
 	public function getVariables() 
 	{
 		return $this->data;
 	}
+
 	
 	public function getVar($var_name, $default_value=null) 
 	{
 		return isset($this->data[$var_name]) ? $this->data[$var_name] : $default_value;
 	}
 
+
 	public function fetch($tpl=null, $extra_vars=[], $layout=null, $options=[]) 
 	{
 		ob_start();
@@ -54,16 +71,19 @@ class LightweightTemplate {
 		return $content;
 	}
 
+
 	public function display($tpl=null, $extra_vars=[], $layout=null, $options=[]) 
 	{
+		$allow_template_debug_traces = empty($options['deny_template_debug']);
 		$tpl_data = $this->data + $extra_vars;
-		self::view($tpl, $tpl_data);
+		self::view($tpl, $tpl_data, $allow_template_debug_traces);
 		return true;
 	}
 
 	
-	public static function view($file, $tpl_data = array()) {
-		$cached_file = self::cache($file);
+	public static function view($file, $tpl_data = array(), $allow_template_debug_traces=true) {
+		$cached_file = self::cache($file, $allow_template_debug_traces);
+		
 	    extract($tpl_data, EXTR_SKIP);
 
 		$debugbar = App::getData('debugbar');
@@ -78,7 +98,7 @@ class LightweightTemplate {
 	}
 
 
-	protected static function cache($file) {
+	protected static function cache($file, $allow_template_debug_traces=true) {
 		if (!file_exists(self::$cache_path)) {
 		  	if (! @mkdir(self::$cache_path, 0744)) {
 		  		throw new \Exception("Cannot create templates cache dir " . self::$cache_path, 1);
@@ -91,30 +111,38 @@ class LightweightTemplate {
 	    if ($cached_file_exists) {
 		    $cached_file_updated = filemtime($cached_file);
 		    $file_path = strpos($file, '/') === 0 ? $file : (self::$tpl_path . '/' . $file);
-		    self::$tpl_last_updated = filemtime($file_path);
+			self::$tpl_last_updated = filemtime($file_path);
+			
 	    } else {
 	    	$cached_file_updated = null;
 	    }
 
 	    if (ENV == 'dev') {
 	    	// on force le parcours de tous les fichiers inclus pour avoir la vraie valeur de self::$tpl_last_updated
-	    	$code = self::includeFiles($file);
+	    	$code = self::includeFiles($file, 0, null, null, $allow_template_debug_traces);
 	    }
 
 	    if (!self::$cache_enabled || ! $cached_file_exists || $cached_file_updated < self::$tpl_last_updated) {
+			// (re)regenere le template
+
 	    	if (! isset($code)) {
-				$code = self::includeFiles($file);
-	    	}
-			$code = self::compileCode($code);
+				// si env != 'dev' (car pour env=dev on a déjà rempli la variable $code. voir quelques lignes au-dessus)
+				$code = self::includeFiles($file, 0, null, null, $allow_template_debug_traces);
+			}
+			
+			$code = self::compileCode($code); // compilation du template
+
 	        file_put_contents($cached_file, '<?php class_exists(\'' . __CLASS__ . '\') or exit; ?>' . PHP_EOL . $code);
 
-	    } else {
+	    } else if (ENV == 'dev') {
+			// affichage des infos du cached_template dans la debugbar
+
 	    	//header('X-Template: cached'); // TODO: $response->addHeader(...)
 
 			$debugbar = App::getData('debugbar');
 			if ($debugbar) {
 				if (isset($debugbar['templates'])) {
-					$debugbar_message_idx = $debugbar['templates']->addMessage([
+					$debugbar['templates']->addMessage([
 						'tpl' => $cached_file,
 						'content_length' => filesize($cached_file),
 						'content_length_str' => formatSize(filesize($cached_file)),
@@ -127,12 +155,14 @@ class LightweightTemplate {
 		return $cached_file;
 	}
 
+
 	public static function clearCache() {
 		foreach(glob(self::$cache_path . '/*') as $file) {
 			unlink($file);
 		}
 	}
 
+
 	protected static function compileCode($code) {
 		$code = self::compileBlock($code);
 		$code = self::compileYield($code);
@@ -140,11 +170,12 @@ class LightweightTemplate {
 		$code = self::compileModules($code);
 		$code = self::compileEscapedEchos($code);
 		$code = self::compileEchos($code);
-		//$code = self::compilePHP($code);
+
 		return $code;
 	}
 
-	protected static function includeFiles($file, $level=0, $caller_file=null, $parent_file=null) {
+
+	protected static function includeFiles($file, $level=0, $caller_file=null, $parent_file=null, $allow_template_debug_traces=true) {
 		$file_path = strpos($file, '/') === 0 ? $file : (self::$tpl_path . '/' . $file);
 
 		if (! is_file($file_path)) {
@@ -189,6 +220,7 @@ class LightweightTemplate {
 
 
 		if (defined('ENV') && ENV == 'dev') {
+			// show dev/debug infos when on development ENV
 			$tpl_infos = '';
 			if ($caller_file) {
 				$tpl_infos .= ' layout for ' . $caller_file . '';
@@ -200,10 +232,12 @@ class LightweightTemplate {
 				$tpl_infos .= ' with layout ' . $layout . '';
 			}
 
-			$begin = '<!-- [' . $level . '] BEGIN TEMPLATE #' . $tpl_idx . ' : ' . $file . ' (size: ' . formatSize(strlen($code)) . ' - ' . $tpl_infos . ') -->';
-			$end = '<!-- [' . $level . '] END TEMPLATE #' . $tpl_idx . ' : ' . $file . ' (size: ' . formatSize(strlen($code)) . ' - ' . $tpl_infos . ') -->';
+			if ($allow_template_debug_traces) {
+				$begin = '<!-- [' . $level . '] BEGIN TEMPLATE #' . $tpl_idx . ' : ' . $file . ' (size: ' . formatSize(strlen($code)) . ' - ' . $tpl_infos . ') -->';
+				$end = '<!-- [' . $level . '] END TEMPLATE #' . $tpl_idx . ' : ' . $file . ' (size: ' . formatSize(strlen($code)) . ' - ' . $tpl_infos . ') -->';
+				$code = PHP_EOL . $begin . PHP_EOL . $code . PHP_EOL . $end . PHP_EOL;
+			}
 
-			$code = PHP_EOL . $begin . PHP_EOL . $code . PHP_EOL . $end . PHP_EOL;
 		}
 
 		// Layout (2)
@@ -211,9 +245,10 @@ class LightweightTemplate {
 			$value = $layout_matches[0];
 			$layout = $value[1];
 
-			$layout_code = self::includeFiles($layout, $level-1, $file);
+			$layout_code = self::includeFiles($layout, $level-1, $file, null, $allow_template_debug_traces);
 			$code = str_replace($value[0], '', $code);
-			
+
+			// @content
 			$layout_code = str_replace('<' . '?=$child_content?' . '>', '{@content}', $layout_code);
 			$layout_code = str_replace('{$child_content}', '{@content}', $layout_code);
 			$layout_code = str_replace('{@content}', $code, $layout_code);
@@ -224,7 +259,7 @@ class LightweightTemplate {
 		// includes
 		preg_match_all('/{include ?\'?(.*?)\'? ?}/i', $code, $matches, PREG_SET_ORDER);
 		foreach ($matches as $value) {
-			$included_code = self::includeFiles($value[1], $level+1, null, $file);
+			$included_code = self::includeFiles($value[1], $level+1, null, $file, $allow_template_debug_traces);
 			$code = str_replace($value[0], $included_code, $code);
 		}
 
@@ -249,27 +284,16 @@ class LightweightTemplate {
 		return $code;
 	}
 
-	protected static function compilePHP($code) {
-		/* return preg_replace('~\{%\s*(.+?)\s*\%}~is', '<?php $1 ?>', $code); */
-		return $code;
-	}
 
 	protected static function compileModules($code) {
 
 		// url => {url clients_list}
 		$code = preg_replace('/{routeUrl /', '{url ', $code); // for compatibility with old templates
+		$code = preg_replace('/{route /', '{url ', $code); // for compatibility with old templates
 		preg_match_all('~{url (.*?)}~is', $code, $matches, PREG_SET_ORDER);
 		foreach ($matches as $value) {
-			//pre($matches); exit;
-			if (false) {
-				// {url clients_list} => "/clients/"
-				$code = str_replace($value[0], getRouteUrl($value[1]), $code);
-				// si on utilise des prefix d'urls, le prefix sera hardcodé dans le cache et crééra des erreurs d'urls quand on passera sur une url d'un prefix different
-
-			} else {
-				// {url clients_list} => "< ?=getRouteUrl('clients_list')? >" (dans espaces)
-				$code = str_replace($value[0], '<?=getRouteUrl("' . $value[1] . '")?>', $code);
-			}
+			// {url clients_list} => "< ?=getRouteUrl('clients_list')? >" (dans espaces)
+			$code = str_replace($value[0], '<?=getRouteUrl("' . $value[1] . '")?>', $code);
 		}
 
 		// foreach => {foreach $list as $item}<div>...</div>{/foreach}
@@ -284,7 +308,10 @@ class LightweightTemplate {
 		foreach ($matches as $value) {
 			
 			$replaced = '<' . '?php elseif ( $1 ) : ?' . '>';
-			$value[2] = preg_replace('/{elseif (.*?) ?}/', $replaced, $value[2]);
+			$value[2] = preg_replace('/\{ ?elseif (.*?) ?\}/', $replaced, $value[2]);
+
+			$replaced = '<' . '?php else : ?' . '>';
+			$value[2] = preg_replace('/\{ ?else ?\}/', $replaced, $value[2]);
 
 			$replaced = PHP_EOL . '<' . '?php if (' . $value[1] . ') : ?' . '>' . PHP_EOL . $value[2] . PHP_EOL . '<' . '?php endif; ?' . '>';
 			$code = str_replace($value[0], $replaced, $code);
@@ -293,43 +320,55 @@ class LightweightTemplate {
 		return $code;
 	}
 
-	protected static function compileEchos($code, $strict=true) {
-		// compile PHP variables (method 1) => {$my_var}
-		if ($strict) {
-			$code = preg_replace('~\{\$(.+?)}~is', '<?php echo \$$1 ?>', $code);
-		} else {
-			$code = preg_replace('~\{\$(.+?)}~is', '<?php echo isset(\$$1) ? (\$$1) : ""; ?>', $code);
-		}
-		// compile PHP variables (method 2) => {{ $my_var }}
-		return preg_replace('~\{{\s*(.+?)\s*\}}~is', '<?php echo $1 ?>', $code);
+
+	protected static function compileEchos($code) {
+		// compile PHP optional variables => {{$my_optional_var}} => echo (isset($my_optional_var) ? $my_optional_var : '')
+		$code = preg_replace('~\{{\$(.+?)}}~is', '<?php echo isset(\$$1) ? (\$$1) : ""; ?>', $code);
+
+		// compile PHP strict variables => {$my_var} => echo $my_var
+		$code = preg_replace('~\{\$(.+?)}~is', '<?php echo \$$1 ?>', $code);
+
+		// compile PHP code => {{ my_php_code }} => echo my_php_code
+		$code = preg_replace('~\{{\s*(.+?)\s*\}}~is', '<?php echo $1 ?>', $code);
+
+		return $code;
 	}
 
+
 	protected static function compileEscapedEchos($code) {
 		// compile PHP escaped variables => {{{ $my_var }}}
 		return preg_replace('~\{{{\s*(.+?)\s*\}}}~is', '<?php echo htmlentities($1, ENT_QUOTES, \'UTF-8\') ?>', $code);
 	}
 
+
 	protected static function compileBlock($code) {
 		preg_match_all('~{block ?(.*?) ?}(.*?){/block}~is', $code, $matches, PREG_SET_ORDER);
+
 		foreach ($matches as $value) {
-			if (!array_key_exists($value[1], self::$blocks)) self::$blocks[$value[1]] = '';
-			if (strpos($value[2], '@parent') === false) {
-				self::$blocks[$value[1]] = $value[2];
-			} else {
-				self::$blocks[$value[1]] = str_replace('@parent', self::$blocks[$value[1]], $value[2]);
+			$block_outer = $value[0];
+			$block_name = $value[1];
+			$block_inner = $value[2];
+
+			if (!array_key_exists($block_name, self::$blocks)) {
+				self::$blocks[$block_name] = [];
 			}
-			$code = str_replace($value[0], '', $code);
+			
+			self::$blocks[$block_name][] = $block_inner;
+
+			$code = str_replace($block_outer, '', $code);
 		}
 		return $code;
 	}
 
+
 	protected static function compileYield($code) {
 		// compile yields => {yield my_block}
-		foreach(self::$blocks as $block => $value) {
-			$code = preg_replace('/{yield ' . $block . ' ?}/', $value, $code);
+		foreach(self::$blocks as $block_name => $block_chunks) {
+			$block_replace = implode('', $block_chunks);
+			$code = preg_replace('/{yield ' . $block_name . ' ?}/', $block_replace, $code);
 		}
 		$code = preg_replace('/{yield ?(.*?) ?}/i', '', $code);
 		return $code;
 	}
 
-}
+}

+ 2 - 2
src/helpers/helpers_array.php

@@ -233,8 +233,8 @@ if (! function_exists('get_csv')) {
 				$line = array();
 				foreach ($fields as $field) {
 					$val = $row[$field];
-					if (is_numeric($val) && substr($val."", 0, 1) === "0" && strlen($val."") > 1) {
-						// pour exporter correctement dans Excel les numeros de telephone commencant par 0
+					if (is_numeric($val) && strlen($val."") > 1) {
+						// pour exporter correctement dans Excel les valeurs numériques
 						$val = '="' . $val . '"';
 					}
 					$line[] = $val;

+ 69 - 0
src/helpers/helpers_date.php

@@ -0,0 +1,69 @@
+<?php
+
+function isWorkingDay($timestamp) {
+
+    // Dimanche(0) ou Samedi(6)
+    $day = date('w', $timestamp);
+    if ($day == 0 || $day == 6) {
+        return false;
+    }
+
+    $jour = date('d', $timestamp);
+    $mois = date('m', $timestamp);
+    $annee = date('Y', $timestamp);
+
+    if ($jour == 1 && $mois == 1) {
+        return false;
+    } // 1er janvier
+
+    if ($jour == 1 && $mois == 5) {
+        return false;
+    } // 1er mai
+
+    if ($jour == 8 && $mois == 5) {
+        return false;
+    } // 8 mai
+
+    if ($jour == 14 && $mois == 7) {
+        return false;
+    } // 14 juillet
+
+    if ($jour == 15 && $mois == 8) {
+        return false;
+    } // 15 aout
+
+    if ($jour == 1 && $mois == 11) {
+        return false;
+    } // 1er novembre
+
+    if ($jour == 11 && $mois == 11) {
+        return false;
+    } // 11 novembre
+
+    if ($jour == 25 && $mois == 12) {
+        return false;
+    } // 25 décembre
+
+
+    // Pâques
+    $date_paques = easter_date($annee);
+    $jour_paques = date('d', $date_paques);
+    $mois_paques = date('m', $date_paques);
+    if ($jour_paques == $jour && $mois_paques == $mois) {
+        return false;
+    }
+
+    // Ascension
+    $date_ascension = mktime(date("H", $date_paques), date("i", $date_paques), date("s", $date_paques), date("m", $date_paques), date("d", $date_paques) + 39, date("Y", $date_paques));
+    if (date('d', $date_ascension) == $jour && date('m', $date_ascension) == $mois) {
+        return false;
+    }
+
+    // Pentecote
+    $date_pentecote = mktime(date("H", $date_paques), date("i", $date_paques), date("s", $date_paques), date("m", $date_paques), date("d", $date_paques) + 50, date("Y", $date_paques));
+    if (date('d', $date_pentecote) == $jour && date('m', $date_pentecote) == $mois) {
+        return false;
+    }
+
+    return true;
+}

+ 43 - 0
src/helpers/helpers_default.php

@@ -422,3 +422,46 @@ if (! function_exists('formatDuration')) {
 	    }
 	}
 }
+
+
+if (! function_exists('IPv6ToIPv4')) {
+	function IPv6ToIPv4($ip) {
+		// source: https://stackoverflow.com/questions/12435582/php-serverremote-addr-shows-ipv6
+
+		/*
+		Fonctionne uniquement pour les IPv4 encapsulées dans IPv6 :
+
+		::ffff:192.000.002.123
+		::ffff:192.0.2.123
+		0000:0000:0000:0000:0000:ffff:c000:027b
+		::ffff:c000:027b
+		::ffff:c000:27b
+		192.000.002.123
+		192.0.2.123
+		*/
+
+
+		// Known prefix
+		$v4mapped_prefix_hex = '00000000000000000000ffff';
+		//$v4mapped_prefix_bin = pack("H*", $v4mapped_prefix_hex); // PHP < 5.4
+		$v4mapped_prefix_bin = hex2bin($v4mapped_prefix_hex);  // PHP >= 5.4
+
+		// Parse
+		$addr_bin = inet_pton($ip);
+		if( $addr_bin === FALSE ) {
+			// Unparsable? How did they connect?!?
+			return null;
+		}
+
+		// Check prefix
+		if( substr($addr_bin, 0, strlen($v4mapped_prefix_bin)) == $v4mapped_prefix_bin) {
+			// Strip prefix
+			$addr_bin = substr($addr_bin, strlen($v4mapped_prefix_bin));
+		}
+
+		// Convert back to printable address in canonical form
+		$ip4 = inet_ntop($addr_bin);
+
+		return $ip4;
+	}
+}