Browse Source

Core : upgrade divers

Valentin CARRUESCO 4 years ago
parent
commit
5a21ff3b66
90 changed files with 741 additions and 8858 deletions
  1. 1 1
      common.php
  2. 4 8
      constant-sample.php
  3. 1 1
      footer.php
  4. 54 20
      install.php
  5. 2 3
      plugin/customiser/action.php
  6. 3 3
      plugin/customiser/app.json
  7. 10 10
      plugin/customiser/customiser.plugin.php
  8. 1 1
      plugin/customiser/setting.customize.customiser.php
  9. 1 4
      plugin/customiser/setting.global.customiser.php
  10. 2 2
      plugin/customiser/theme/example/app.json
  11. 0 0
      plugin/customiser/theme/hackpoint/footer-pattern.png
  12. 64 4
      plugin/customiser/theme/hackpoint/main.css
  13. 0 20
      plugin/dashboard/Dashboard.class.php
  14. 0 52
      plugin/dashboard/DashboardWidget.class.php
  15. 0 255
      plugin/dashboard/action.php
  16. 0 13
      plugin/dashboard/app.json
  17. 0 218
      plugin/dashboard/css/main.css
  18. 0 99
      plugin/dashboard/css/widget.css
  19. 0 129
      plugin/dashboard/dashboard.plugin.php
  20. BIN
      plugin/dashboard/img/defaultBackground.jpg
  21. 0 303
      plugin/dashboard/js/main.js
  22. 0 2434
      plugin/dashboard/js/progressbar.js
  23. 0 36
      plugin/dashboard/js/widget.js
  24. 0 149
      plugin/dashboard/page.home.php
  25. 0 75
      plugin/dashboard/setting.dashboard.php
  26. 1 1
      plugin/document/WebDav.class.php
  27. 0 1
      plugin/document/action.php
  28. 2 2
      plugin/document/app.json
  29. 2 2
      plugin/document/document.plugin.php
  30. 3 2
      plugin/example/Contact.class.php
  31. 27 21
      plugin/example/action.php
  32. 7 2
      plugin/example/example.plugin.php
  33. 32 2
      plugin/example/js/main.js
  34. 8 8
      plugin/example/page.list.php
  35. 38 10
      plugin/example/page.sheet.php
  36. 7 5
      plugin/example/setting.example.php
  37. 2 1
      plugin/export/ExportModel.class.php
  38. 16 14
      plugin/export/action.php
  39. 1 0
      plugin/export/export.plugin.php
  40. 47 0
      plugin/export/js/component.js
  41. 3 49
      plugin/export/js/main.js
  42. 1 3
      plugin/export/modal.export.model.php
  43. 8 3
      plugin/export/page.sheet.php
  44. 8 3
      plugin/export/setting.export.php
  45. 2 2
      plugin/export/template/WordExport.class.php
  46. 4 0
      plugin/factory/Template.class.php
  47. 18 1
      plugin/factory/action.php
  48. 4 4
      plugin/factory/factory.plugin.php
  49. 1 1
      plugin/factory/page.factory.php
  50. 20 10
      plugin/factory/template/Erp plugin/action.php
  51. 2 2
      plugin/factory/template/Erp plugin/css/main.css
  52. 20 18
      plugin/factory/template/Erp plugin/js/main.js
  53. 72 0
      plugin/factory/template/Erp plugin/page.list.{{entity.readable}}.php
  54. 18 0
      plugin/factory/template/Erp plugin/page.sheet.{{entity.readable}}.php
  55. 1 1
      plugin/factory/template/Erp plugin/setting.global.{{plugin}}.php
  56. 73 0
      plugin/factory/template/Erp plugin/setting.list.{{plugin}}.{{entity.readable}}.php
  57. 7 1
      plugin/factory/template/Erp plugin/{{Entity}}.class.php
  58. 15 19
      plugin/factory/template/Erp plugin/{{plugin}}.plugin.php
  59. 2 2
      plugin/hackpoint/VirtualWebDav.class.php
  60. 5 0
      plugin/hackpoint/app.json
  61. 79 0
      plugin/hackpoint/css/install.css
  62. 8 5
      plugin/hackpoint/hackpoint.plugin.php
  63. BIN
      plugin/hackpoint/img/logo.png
  64. 24 0
      plugin/hackpoint/install.php
  65. 2 2
      plugin/navigation/app.json
  66. 2 2
      plugin/navigation/navigation.plugin.php
  67. 3 3
      plugin/notification/app.json
  68. 3 3
      plugin/notification/notification.plugin.php
  69. 0 141
      plugin/wiki/WikiCategory.class.php
  70. 0 60
      plugin/wiki/WikiPage.class.php
  71. 0 347
      plugin/wiki/action.php
  72. 0 13
      plugin/wiki/app.json
  73. 0 973
      plugin/wiki/css/main.css
  74. 0 6
      plugin/wiki/css/simplemde.min.css
  75. BIN
      plugin/wiki/img/logo.png
  76. 0 573
      plugin/wiki/js/main.js
  77. 0 6
      plugin/wiki/js/simplemde.min.js
  78. 0 1981
      plugin/wiki/lib/Parsedown.php
  79. 0 28
      plugin/wiki/modal.category.php
  80. 0 23
      plugin/wiki/page.category.php
  81. 0 44
      plugin/wiki/page.home.php
  82. 0 129
      plugin/wiki/page.index.php
  83. 0 14
      plugin/wiki/page.login.php
  84. 0 55
      plugin/wiki/page.page.php
  85. 0 108
      plugin/wiki/page.search.php
  86. 0 66
      plugin/wiki/page.shortcut.php
  87. 0 27
      plugin/wiki/setting.global.wiki.php
  88. 0 3
      plugin/wiki/template/Autre/Bienvenue.md
  89. 0 87
      plugin/wiki/template/Documentation/Syntaxe.md
  90. 0 129
      plugin/wiki/wiki.plugin.php

+ 1 - 1
common.php

@@ -58,7 +58,7 @@ if($myUser->login==null && isset($_COOKIE[COOKIE_NAME])){
 	$cookie = UserPreference::load(array('key'=>'cookie','value'=>$_COOKIE[COOKIE_NAME]));
 
 	if($cookie!=false){
-	    if(Plugin::is_active('fr.sys1.activedirectory'))
+	    if(Plugin::is_active('fr.idleman.activedirectory'))
 	        require_once(PLUGIN_PATH.'activedirectory'.SLASH.'activedirectory.plugin.php');
 	    
 	    $myUser = User::byLogin($cookie->user);

+ 4 - 8
constant-sample.php

@@ -21,15 +21,11 @@ define('CRYPTKEY','{{CRYPT_KEY}}');
 //logs toutes les requetes sans formattage
 define('BASE_DEBUG',false);
 
-define('COOKIE_NAME','erp-core-cookie');
+define('COOKIE_NAME','hackpoint-cookie');
 
-define('PROGRAM_NAME','Sys1 ERP');
-define('PROGRAM_UID','sys1/erp-core');
-define('PROGRAM_TECHNICIAN','valentin.morreel');
-//Windows
-define('REFERENCE_URL','https://projet.sys1.fr/action.php?action=reference_save_project');
-//Linux
-// define('REFERENCE_URL','http://projet.sys1.fr/action.php?action=reference_save_project');
+define('PROGRAM_NAME','Hackpoint');
+define('PROGRAM_UID','idleman.fr/hackpoint');
+define('PROGRAM_TECHNICIAN','valentin.carruesco');
 
 define('SOURCE_VERSION','1.0');
 define('BASE_VERSION','1.0');

+ 1 - 1
footer.php

@@ -10,7 +10,7 @@ $cacheVersion = isset($cacheVersion) ? $cacheVersion : 1;
 ?>
 	<footer class="footer noPrint">
 		<div class="container">
-			<span class="text-muted"><?php echo PROGRAM_NAME.' V'.SOURCE_VERSION.'b'.BASE_VERSION; ?>  by <a href="<?php echo $scheme; ?>://sys1.fr" target="_blank">@Sys1</a> <!--| <a href="file/guide/manuel-utilisateur.pdf" target="_blank"><i class="far fa-file-pdf"></i> Documentation</a> -->-  <?php echo $loadingTime; ?></span>
+			<span class="text-muted"><?php echo PROGRAM_NAME.' V'.SOURCE_VERSION.'b'.BASE_VERSION; ?>  by <a href="<?php echo $scheme; ?>://idleman.fr" target="_blank">@Sys1</a> <!--| <a href="file/guide/manuel-utilisateur.pdf" target="_blank"><i class="far fa-file-pdf"></i> Documentation</a> -->-  <?php echo $loadingTime; ?></span>
 		</div>
 		<div id="toTheTop" title="Retour en haut de page"></div>
 	</footer>

+ 54 - 20
install.php

@@ -14,7 +14,17 @@ try {
 	if(file_exists(__DIR__.DIRECTORY_SEPARATOR.'.git') && !file_exists(__DIR__.DIRECTORY_SEPARATOR.'.git'.DIRECTORY_SEPARATOR.'.htaccess')){
 		file_put_contents(__DIR__.DIRECTORY_SEPARATOR.'.git'.DIRECTORY_SEPARATOR.'.htaccess', 'deny for all');
 	}
-	
+
+	$custom = array('css' => '','js' => '','action' => '');
+	//Recherche de custom install dans les plugins
+	foreach(glob(__DIR__.DIRECTORY_SEPARATOR.'plugin'.DIRECTORY_SEPARATOR.'*'.DIRECTORY_SEPARATOR.'app.json') as $app){
+		$manifest = json_decode(file_get_contents($app),true);
+		if(!$manifest || !isset($manifest['install'])) continue;
+		$custom = $manifest['install'];
+		$custom['plugin'] =  'plugin/'.basename(dirname($app)).'/';
+		$custom['pluginPath'] =  __DIR__.DIRECTORY_SEPARATOR.'plugin'.DIRECTORY_SEPARATOR.basename(dirname($app)).DIRECTORY_SEPARATOR;
+	}
+
 	?>
 
 	<!DOCTYPE html>
@@ -34,23 +44,20 @@ try {
 		<link rel="stylesheet" href="css/fontawesome-all.min.css">
 		<!-- Custom styles for this template -->
 		<link href="css/main.css" rel="stylesheet">
-		<link href="css/theme.css" rel="stylesheet">
-		<!-- Plugin css files -->
-		<?php echo Plugin::callCss("css"); ?>
+		<?php if(!empty($custom['css'])): ?>
+			<link href="<?php echo $custom['plugin'].$custom['css']; ?>" rel="stylesheet">
+		<?php endif; ?>
+		
 	</head>
 
 	<body>
 		<!-- Fixed navbar -->
 		<nav class="navbar navbar-expand-md navbar-dark fixed-top bg-dark">
-			<a class="navbar-brand" href="install.php">Installateur</a>
+			<a class="navbar-brand" href="index.php"><img style="max-height: 40px;" src="img/logo/default-logo.png"> Installation</a>
 			<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarCollapse" aria-controls="navbarCollapse" aria-expanded="false" aria-label="Toggle navigation">
 				<span class="navbar-toggler-icon"></span>
 			</button>
-			<div class="collapse navbar-collapse" id="navbarCollapse">
-				<ul class="navbar-nav mr-auto">
-					<li class="nav-item active"><a class="nav-link" href="install.php">Installation <span class="sr-only">(current)</span></a></li>
-				</ul>
-			</div>
+			
 		</nav>
 
 		<!-- Begin page content -->
@@ -146,10 +153,33 @@ try {
 				$right->configure = true;
 				$right->save();
 			}
+
+			$enablePlugins = array('fr.sys1.factory','fr.sys1.dashboard','fr.sys1.notification','fr.sys1.navigation');
+
+
+			if(!empty($custom['action'])) require_once($custom['pluginPath'].$custom['action']);
+
 			//Activation des plugins par défaut
-			foreach (array('fr.sys1.factory','fr.sys1.dashboard','fr.sys1.notification','fr.sys1.navigation') as  $plugin) {
+			foreach ($enablePlugins as  $plugin) {
 				Plugin::state($plugin,true);
-			} ?>
+			} 
+
+			$states = Plugin::states();
+
+			//Activation des plugins pour les établissements
+			foreach(Firm::loadAll() as $firm){
+
+				foreach ($enablePlugins as  $plugin) {
+					$firms = $states[$plugin];	
+					$key = array_search($firm->id, $firms);
+					$firms[] = $firm->id;
+					$states[$plugin] = array_values($firms);
+					Plugin::states($states);
+				}
+			}
+			
+
+			?>
 
 			<div class="alert alert-success alert-dismissable">
 				<button aria-hidden="true" data-dismiss="alert" class="close" type="button">×</button>
@@ -163,9 +193,11 @@ try {
 			$root = str_replace("/install.php", "", $root );
 			$parts = explode('?',$root);
 			$root = array_shift($parts);
+
+
 			?>
 			<div class="row">
-				<form class="col-md-3" action="install.php" method="POST">
+				<form class="col-md-12 mt-3" action="install.php" method="POST">
 					<h3>Installation</h3>
 					<p>Merci de bien vouloir remplir les champs ci-dessous</p>
 					<label for="entity">Base de donnée</label>
@@ -192,6 +224,8 @@ try {
 				</form>
 			</div>
 			<?php
+
+			if(!empty($custom['form'])) require_once($custom['pluginPath'].$custom['form']);
 		}
 	} catch (Exception $e) { ?>
 			<div class="alert alert-danger">
@@ -203,11 +237,7 @@ try {
 	} ?>
 	</div>
 
-	<footer class="footer">
-		<div class="container">
-			<span class="text-muted">Installateur by <a href="mailto:valentin.carruesco@sys1.fr">@valentin carruesco<a></span>
-		</div>
-	</footer>
+
 
 	<!-- Bootstrap core JavaScript -->
 	<!-- Placed at the end of the document so the pages load faster -->
@@ -218,5 +248,9 @@ try {
 	<script src="js/vendor/mustache.min.js"></script>
 	<script src="js/plugins.js"></script>
 	<script src="js/main.js"></script>
-	<?php echo Plugin::callJs(); ?>
-</body>
+	<?php if(!empty($custom['js'])): ?>
+		<link href="<?php echo $custom['plugin'].$custom['js']; ?>" rel="stylesheet">
+	<?php endif; ?>
+
+</body>
+</html>

+ 2 - 3
plugin/customiser/action.php

@@ -4,7 +4,7 @@ switch($_['action']){
 	//Reset des paramètres du thème
 	case 'customiser_reset_theme':
 		global $myUser,$_,$conf;
-		if(!$myUser->can('customiser','configure')) throw new Exception("Permissions insuffisantes",403);
+		User::check_access('customiser','configure');
 		if(file_exists(__DIR__.SLASH.'css'.SLASH.'theme.css'))
 			unlink(__DIR__.SLASH.'css'.SLASH.'theme.css');
 		header('location: setting.php?section=global.customiser');
@@ -13,10 +13,9 @@ switch($_['action']){
 	case 'customiser_theme_save':
 		Action::write(function(&$response){
 			global $myUser,$_,$conf;
-			if(!$myUser->can('customiser','configure')) throw new Exception("Permissions insuffisantes",403);
+			User::check_access('customiser','configure');
 			
 			$conf->put('core_theme',$_['theme']);
-		
 		});
 	break;
 

+ 3 - 3
plugin/customiser/app.json

@@ -1,12 +1,12 @@
 {
-	"id": "fr.sys1.customiser",
+	"id": "fr.idleman.customiser",
 	"name": "Customiser",
 	"author" : {
 		"name" : "Administrateur SYS1",
-		"mail" : "developpement@sys1.fr"
+		"mail" : "developpement@idleman.fr"
 	},
 	"version": "1.0",
-	"url": "http://sys1.fr",
+	"url": "http://idleman.fr",
 	"licence": {"name": "Copyright","url" : ""},
 	"description": "Plugin de personnalisation rapide du thème",
 	"require" : {}

+ 10 - 10
plugin/customiser/customiser.plugin.php

@@ -1,13 +1,13 @@
 <?php
 //Fonction executée lors de l'activation du plugin
 function customiser_install($id){
-	if($id != 'fr.sys1.customiser') return;
+	if($id != 'fr.idleman.customiser') return;
 	Entity::install(__DIR__);
 }
 
 //Fonction executée lors de la désactivation du plugin
 function customiser_uninstall($id){
-	if($id != 'fr.sys1.customiser') return;
+	if($id != 'fr.idleman.customiser') return;
 	Entity::uninstall(__DIR__);
 }
 
@@ -27,12 +27,12 @@ function customiser_menu_setting(&$settingMenu){
 	global $_, $myUser;
 	
 	if(!$myUser->can('customiser','configure')) return;
-		$settingMenu[]= array(
-			'sort' =>1,
-			'url' => 'setting.php?section=global.customiser',
-			'icon' => 'fas fa-angle-right',
-			'label' => 'Thème'
-		);
+	$settingMenu[]= array(
+		'sort' =>1,
+		'url' => 'setting.php?section=global.customiser',
+		'icon' => 'fas fa-angle-right',
+		'label' => 'Thème'
+	);
 }
 
 //Déclaration des pages de réglages
@@ -50,8 +50,8 @@ if(!empty($conf->get('core_theme'))){
 //Déclation des assets
 if(file_exists(__DIR__.SLASH.'css'.SLASH.'theme.css'))
 Plugin::addCss("/css/theme.css?v=".filemtime(__DIR__.SLASH.'css'.SLASH.'theme.css')); 
-Plugin::addCss("/css/main.css?v=1"); 
-Plugin::addJs("/js/main.js?v=1"); 
+Plugin::addCss("/css/main.css"); 
+Plugin::addJs("/js/main.js"); 
 
 //Mapping hook / fonctions
 Plugin::addHook("install", "customiser_install");

+ 1 - 1
plugin/customiser/setting.customize.customiser.php

@@ -1,7 +1,7 @@
 <?php 
 <?php
 global $myUser,$conf;
-if(!$myUser->can('customiser','configure')) throw new Exception("Vous n'avez pas la permission pour executer cette fonctionnalité",403);
+User::check_access('customiser','configure');
 
 $themePath = __DIR__.SLASH.'css'.SLASH.'theme.css';
 if(!file_exists($themePath)) 

+ 1 - 4
plugin/customiser/setting.global.customiser.php

@@ -1,9 +1,8 @@
 <?php
 global $myUser,$conf;
-if(!$myUser->can('customiser','configure')) throw new Exception("Vous n'avez pas la permission pour executer cette fonctionnalité",403);
+User::check_access('customiser','configure');
 
 $themes = array(
-
 	"default" => array(
 		"label" => "Défaut",
 		"folder" => "",
@@ -24,8 +23,6 @@ foreach(glob(__DIR__.SLASH.'theme'.SLASH.'*'.SLASH.'app.json') as $themeFile){
 	$theme['last-update'] = filemtime($themeFile);
 	$themes[] = $theme;
 }
-
-
 ?>
 
 <div class="row customiser">

+ 2 - 2
plugin/customiser/theme/example/app.json

@@ -1,9 +1,9 @@
 {
-	"id": "fr.sys1.theme.example",
+	"id": "fr.idleman.theme.example",
 	"label": "Example",
 	"cover": "cover.jpg",
 	"author" : {
 		"name" : "Administrateur SYS1",
-		"mail" : "developpement@sys1.fr"
+		"mail" : "developpement@idleman.fr"
 	}
 }

+ 0 - 0
img/footer-pattern.png → plugin/customiser/theme/hackpoint/footer-pattern.png


+ 64 - 4
plugin/customiser/theme/hackpoint/main.css

@@ -1,7 +1,7 @@
 
 
 body{
-     background:url('../img/footer-pattern.png') repeat-x 0 bottom #3e4750!important;
+     background:url('footer-pattern.png') repeat-x 0 bottom #3e4750!important;
      color:#cecece;
 }
 
@@ -25,6 +25,21 @@ ul, li{
     list-style-type: none;
 }
 
+
+input:-webkit-autofill,
+input:-webkit-autofill:hover, 
+input:-webkit-autofill:focus,
+textarea:-webkit-autofill,
+textarea:-webkit-autofill:hover,
+textarea:-webkit-autofill:focus,
+select:-webkit-autofill,
+select:-webkit-autofill:hover,
+select:-webkit-autofill:focus {
+  background-color: #343940!important;
+  border-color: #343940!important;
+  color:#fefefe!important;
+}
+
 input.form-control,
 input.form-control:active,
 input.form-control:focus,
@@ -87,19 +102,20 @@ a:hover {
     color: #8fbcec;
 }
 
-.check-component{
+label.check-component{
     border:0;
 }
-.check-component .box{
+label.check-component .box{
     background-color: #25292d;
 }
-.check-component input:checked + .box {
+label.check-component input:checked + .box {
     background-color: #353535;
 }
 .table-striped tbody tr:nth-of-type(odd) {
     background-color: #343940;
 }
 .table {
+     color: #ebebeb;
     border-left: 1px solid #343940;
     border-bottom: 1px solid #343940;
     border-right: 1px solid #343940;
@@ -109,10 +125,26 @@ a:hover {
     vertical-align: top;
     border-top: 1px solid #343940;
 }
+.table > thead th {
+    border-bottom: 1px solid #343940;
+}
 .table tr > th:first-of-type, .table tr > td:first-of-type {
     border-right: 1px solid #343940;
 }
 
+.table > thead {
+    border-bottom: 1px solid #343940;
+}
+
+.table-bordered td, .table-bordered th {
+    border: 1px solid #343940;
+}
+
+.table-hover tbody tr:hover {
+    color: #ffffff;
+    background-color: rgba(0,0,0,.075);
+}
+
 .trumbowyg-box svg {
     fill: #e8e8e8;
 }
@@ -193,6 +225,34 @@ div[data-type="dropzone"] > ul > li {
     background: #343a40;
 }
 
+
+.list-group-item-action{
+    color: #fff;
+}
+
+.list-group-item-action.text-primary,.list-group-item-action.text-primary:hover,.list-group-item-action.text-primary:focus{
+    color: #fff!important;
+}
+.list-group-item-action:focus, .list-group-item-action:hover {
+    z-index: 1;
+    color: #fff;
+    text-decoration: none;
+    background-color: #000;
+}
+
+.list-group-item.active {
+    z-index: 2;
+    color: #fff;
+    background-color: #0a0a0a;
+    border-color: #000000;
+}
+
+/* wysiwyg*/
+
+.trumbowyg-button-pane{
+    border-bottom: 0px;
+}
+
 /* document */
 
 .hackpoint .document-container .tree-panel {

+ 0 - 20
plugin/dashboard/Dashboard.class.php

@@ -1,20 +0,0 @@
-<?php
-/**
- * Define a dashboard.
- * @author Idleman
- * @category Plugin
- * @license copyright
- */
-class Dashboard extends Entity{
-	public $id,$user,$label,$icon,$default;
-	protected $TABLE_NAME = 'dashboard_dashboard';
-	public $fields =
-	array(
-		'id' => 'key',
-		'user' => 'string',
-		'label' => 'string',
-		'icon' => 'string',
-		'default' => 'boolean'
-	);
-}
-?>

+ 0 - 52
plugin/dashboard/DashboardWidget.class.php

@@ -1,52 +0,0 @@
-<?php
-/**
- * Define a dashboardwidget.
- * @author Idleman
- * @category Plugin
- * @license copyright
- */
-class DashboardWidget extends Entity{
-	public $id,$minified,$position,$model,$dashboard,$title,$icon,$background,$width,$load,$configure,$move,$delete,$options,$js,$css,$content,$data,$description;
-	protected $TABLE_NAME = 'dashboard_dashboard_widget';
-	public $fields =
-	array(
-		'id' => 'key',
-		'model' => 'string',
-		'data' => 'longstring',
-		'position' => 'int',
-		'minified' => 'boolean',
-		'dashboard' => 'int'
-	);
-
-	function __construct(){
- 		parent::__construct();
- 		$this->options = array();
- 		$this->icon = 'fa-caret-right';
- 		$this->title = 'Sans titre';
- 		$this->width = 4;
- 	}
- 	function data($key=null,$value=null){
- 		$data = json_decode($this->data,true);
- 		if($key==null) return $data;
- 		if(is_array($key) && $value==null){
- 			foreach ($key as $k => $v) {
- 				$data[$k] = $v;
- 				$this->data = json_encode($data);
- 			}
- 			return true;
- 		}
- 		if($value==null) return isset($data[$key])?$data[$key]:'';
- 		$data[$key] = $value;
- 		$this->data = json_encode($data);
- 		return true;
- 	}
-
- 	public static function current(){
- 		global $_;
- 		$widget = new self();
- 		$widget->fromArray($_);
- 		$widget->data = self::getById($widget->id)->data;
- 		return $widget;
- 	}
-}
-?>

+ 0 - 255
plugin/dashboard/action.php

@@ -1,255 +0,0 @@
-<?php
-global $_,$conf;
-switch($_['action']){
-	/** DASHBOARD **/
-	//Récuperation d'une liste de dashboard
-	case 'dashboard_dashboard_search':
-	Action::write(function(&$response){
-		global $myUser,$_;
-		if(!$myUser->can('dashboard','read')) throw new Exception("Permissions insuffisantes",403);
-		require_once(__DIR__.SLASH.'Dashboard.class.php');
-		foreach(Dashboard::populate() as $dashboard){
-			$response['rows'][] = $dashboard;
-		}
-	});
-	break;
-	
-	//Ajout ou modification d'élément dashboard
-	case 'dashboard_dashboard_save':
-	Action::write(function(&$response){
-		global $myUser,$_;
-		if(!$myUser->can('dashboard','edit')) throw new Exception("Permissions insuffisantes",403);
-		require_once(__DIR__.SLASH.'Dashboard.class.php');
-		$item = Dashboard::provide();
-		$item->user = $myUser->login;
-		$item->label = $_['label'];
-		$item->icon = $_['icon'];
-		$item->default = $_['default'];
-		$item->save();
-	});
-	break;
-	
-	//Récuperation ou edition d'élément dashboard
-	case 'dashboard_dashboard_edit':
-	Action::write(function(&$response){
-		global $myUser,$_;
-		if(!$myUser->can('dashboard','edit')) throw new Exception("Permissions insuffisantes",403);
-		require_once(__DIR__.SLASH.'Dashboard.class.php');
-		$response = Dashboard::getById($_['id']);
-	});
-	break;
-
-	//Suppression d'élement dashboard
-	case 'dashboard_dashboard_delete':
-	Action::write(function(&$response){
-		global $myUser,$_;
-		if(!$myUser->can('dashboard','delete')) throw new Exception("Permissions insuffisantes",403);
-		require_once(__DIR__.SLASH.'Dashboard.class.php');
-		Dashboard::deleteById($_['id']);
-
-	});
-	break;
-
-	
-	//Sauvegarde des configurations de dashboard
-	case 'dashboard_setting_save':
-	Action::write(function(&$response){
-		global $myUser,$_,$conf;
-		if(!$myUser->can('dashboard','configure')) throw new Exception("Permissions insuffisantes",403);
-		foreach(Configuration::setting('dashboard') as $key=>$value){
-			if(!is_array($value)) continue;
-			$allowed[] = $key;
-		}
-		foreach ($_['fields'] as $key => $value) {
-			if(in_array($key, $allowed))
-				$conf->put($key,$value);
-		}
-	});
-	break;
-	
-	/** DASHBOARDWIDGET **/
-	//Récuperation d'une liste de dashboardwidget
-	case 'dashboard_dashboardwidget_search':
-		Action::write(function(&$response){
-			global $myUser,$_;
-			if(!$myUser->can('dashboard','read')) throw new Exception("Permissions insuffisantes",403);
-			require_once(__DIR__.SLASH.'DashboardWidget.class.php');
-
-			$models = array();
-			Plugin::callHook('widget',array(&$models));
-			foreach($models as $model):
-				$models[$model->model] = $model;
-			endforeach;
-
-			$widgets = DashboardWidget::loadAll(array('dashboard'=>$_['dashboard']),array('position'));
-			
-			foreach($widgets as $widget):
-				if(!isset($models[$widget->model])) continue;
-				$model = clone $models[$widget->model];
-				$model->id = $widget->id;
-				$model->position = $widget->position;
-				$model->minified = $widget->minified;
-				$model->dashboard = $widget->dashboard;
-
-				$response['rows'][] = $model;
-			endforeach;
-		});
-	break;
-	
-	//Ajout ou modification d'élément dashboardwidget
-	case 'dashboard_dashboardwidget_save':
-		Action::write(function(&$response){
-			global $myUser,$_;
-			if(!$myUser->can('dashboard','edit')) throw new Exception("Permissions insuffisantes",403);
-			require_once(__DIR__.SLASH.'DashboardWidget.class.php');
-			$item = DashboardWidget::provide();
-			$item->model = $_['model'];
-			$item->data = $_['data'];
-			$item->position = $_['position'];
-			$item->minified = $_['minified'];
-			$item->dashboard = $_['dashboard'];
-			$item->save();
-		});
-	break;
-	
-	//Récuperation ou edition d'élément dashboardwidget
-	case 'dashboard_dashboardwidget_edit':
-		Action::write(function(&$response){
-			global $myUser,$_;
-			if(!$myUser->can('dashboard','edit')) throw new Exception("Permissions insuffisantes",403);
-			require_once(__DIR__.SLASH.'DashboardWidget.class.php');
-			$response = DashboardWidget::getById($_['id']);
-		});
-	break;
-
-	//Suppression d'élement dashboardwidget
-	case 'dashboard_dashboardwidget_delete':
-		Action::write(function(&$response){
-			global $myUser,$_;
-			if(!$myUser->can('dashboard','delete')) throw new Exception("Permissions insuffisantes",403);
-			require_once(__DIR__.SLASH.'DashboardWidget.class.php');
-			require_once(__DIR__.SLASH.'Dashboard.class.php');
-			if(!$myUser->can('widget','delete')) throw new Exception("Permissions insuffisantes",403);
-			$widget = DashboardWidget::getById($_['widget']);
-			$dashboard = Dashboard::getById($_['dashboard']);
-			if($widget->dashboard!=$dashboard->id || $dashboard->user!=$myUser->login) 
-				throw new Exception("Vous ne pouvez supprimer que vos propres widgets");
-
-			$widget->deleteById($widget->id);
-			$response['message'] = 'Widget supprimé';
-		});
-	break;
-
-	case 'dashboard_dashboardwidget_refresh':
-		Action::write(function(&$response){
-			global $myUser,$_;
-			if(!$myUser->can('dashboard','read')) throw new Exception("Permissions insuffisantes",403);
-			$widgets = array();
-			Plugin::callHook('widget_refresh',array(&$widgets));
-			$response['rows'] = $widgets;
-		});
-	break;
-
-	
-	//Sauvegarde des configurations de dashboard
-	case 'dashboard_setting_save':
-		Action::write(function(&$response){
-			global $myUser,$_,$conf;
-			if(!$myUser->can('dashboard','configure')) throw new Exception("Permissions insuffisantes",403);
-			foreach(Configuration::setting('dashboard') as $key=>$value){
-				if(!is_array($value)) continue;
-				$allowed[] = $key;
-			}
-			foreach ($_['fields'] as $key => $value) {
-				if(in_array($key, $allowed))
-					$conf->put($key,$value);
-			}
-		});
-	break;
-
-	case 'dashboard_dashboardwidget_add':
-		Action::write(function(&$response){
-			global $myUser,$_;
-			if(!$myUser->can('dashboard','edit')) throw new Exception("Permissions insuffisantes",403);
-			require_once(__DIR__.SLASH.'DashboardWidget.class.php');
-			$widget = new DashboardWidget();
-			$widget->model = $_['widget'];
-			$widget->position = 666;
-			$widget->minified = false;
-			$widget->dashboard = $_['dashboard'];
-			$widget->save();
-			$response['message'] = 'Widget ajouté';
-		});
-	break;
-
-	case 'dashboard_dashboardwidget_save_position':
-		Action::write(function(&$response){
-			global $myUser,$_;
-			if(!$myUser->can('dashboard','update')) throw new Exception("Permissions insuffisantes",403);
-			require_once(__DIR__.SLASH.'Dashboard.class.php');
-			require_once(__DIR__.SLASH.'DashboardWidget.class.php');
-			$dashboard = Dashboard::getById($_['dashboard']);
-
-			if( $dashboard->user!=$myUser->login) 
-				throw new Exception("Vous ne pouvez modifier que vos propres widgets");
-
-			$dashboard_widgets = DashboardWidget::loadAll( array('dashboard' => $dashboard->id ) );
-
-			foreach($_['positions'] as $move){
-				foreach($dashboard_widgets as $dashboard_widget){
-					if($dashboard_widget->id!=$move['id']) continue;
-					$dashboard_widget->position = $move['position'];
-					$dashboard_widget->save();
-				}
-			}
-		});
-	break;
-
-
-	case 'dashboard_widget_clock_load':
-		require_once('DashboardWidget.class.php');
-		$widget = DashboardWidget::current();
-		$widget->title = 'Horloge';
-		$widget->content = '<div class="clockContainer">
-		<div class="clock" id="clock"></div>
-		</div>';
-		echo json_encode($widget);
-	break;
-
-	case 'dashboard_widget_log_load':
-		global $myUser;
-		require_once('DashboardWidget.class.php');
-		if(!$myUser->can('log','read')) throw new Exception("Permissions insuffisantes",403);
-		$widget = DashboardWidget::current();
-		$logs = Log::loadAll(array(),array('created DESC'),array(30));
-
-		$widget->title = '30 derniers logs';
-		$widget->content = '<table class="table table-stripped table-hover">';
-
-		$widget->content .= '<tr><th style="width:90px">Date</th><th>Libellé</th><th>Utilisateur</th></tr>';
-		foreach($logs as $log){
-			$widget->content .= '<tr><td><i class="fa fa-calendar-o"></i> '.date('d-m-y',$log->created).'<i class="fa fa-clock-o"></i> '.date('H:i:s',$log->created).'</td><td>'.$log->label().'</td><td><i class="fa fa-user"></i> '.$log->creator.'</td></tr>';
-		}
-		$widget->content .= '</ul>';
-
-		echo json_encode($widget);
-	break;
-
-	case 'dashboard_widget_profile_load':
-		global $myUser;
-		require_once('DashboardWidget.class.php');
-		$widget = DashboardWidget::current();
-		$widget->title = 'Profile';
-		$widget->content = '<div class="profileContainer">
-		<div class="profileHeader"></div>
-		<a href="account.php" class="profileImage"><img class="avatar-mini" src="'.$myUser->getAvatar().'"></a>
-		<h3>'.$myUser->fullname().'</h3>
-		<small>'.$myUser->function.'</small>
-		<a href="account.php" class="btn btn-dark block ml-2  mr-2" ><i class="far fa-meh-blank"></i> Editer mon profil</a>
-		</div>';
-		echo json_encode($widget);
-	break;
-
-
-}
-?>

+ 0 - 13
plugin/dashboard/app.json

@@ -1,13 +0,0 @@
-{
-	"id": "fr.idleman.dashboard",
-	"name": "Dashboard",
-	"author" : {
-		"name" : "Valentin CARRUESCO",
-		"mail" : ""
-	},
-	"version": "2.0",
-	"url": "http://idleman.fr",
-	"licence": {"name": "Copyright","url" : ""},
-	"description": "Dashboard avec widgets dynamiques",
-	"require" : {}
-}

+ 0 - 218
plugin/dashboard/css/main.css

@@ -1,218 +0,0 @@
-/** DASHBOARD **/
-
-/* Conteneur principal du plugin dashboard */
-.dashboard {
-	
-}
-
-/**
-** DASHBOARD
-**/
-
-#dashboardForm #icon{
-	font-family:"Font Awesome 5 Free";
-}
-
-#widgetList{
-	width:100%;
-	display: block;
-	margin-top: 10px;
-}
-
-#dashboard{
-	list-style-type:none;
-	margin:0;
-	padding:0;
-}
-#dashboard > li{
-	float:left;
-}
-
-#dashboardView{
-	width: 70px;
-    background-color: #343a40;
-    list-style-type: none;
-    color: #fafafa;
-    margin: 0;
-    padding: 0;
-    min-height: 100%;
-    position: fixed;
-    left: 0;
-    top: 50px;
-    border-top: 4px solid #454a4e;
-}
-
-#dashboardView li{
-	
-	cursor:pointer;
-	text-align: center;
-    font-size: 1.3em;
-	transition:background 0.2s ease-in-out;
-}
-
-#dashboardView li.dashboardView-title{
-	color: #cecece;
-    font-size: 11px;
-    cursor: default;
-    background-color: #475058;
-    font-weight: bold;
-}
-#dashboardView li.dashboard-item{
-	position:relative;
-	padding:10px;
-	transition: padding 0.2s ease-in-out;
-}
-#dashboardView li.dashboard-item > div{
-	padding:10px;
-	background-color: #020202;
-	border-radius: 100%;
-	text-align: center;
-	border: 2px solid #020202;
-	transition:all 0.2s ease-in-out;
-}
-#dashboardView li.dashboard-item i {
-	transition:all 0.2s ease-in-out;
-	font-weight: 200;
-}
-#dashboardView li.dashboard-item:hover{
-	padding-bottom:20px;
-}
-
-#dashboardView li.dashboard-item span {
-	transform: translateX(-105px);
-	font-size: 9px;
-	position:absolute;
-	bottom:3px;
-	left:0;
-	color:#808a94;
-	text-transform: uppercase;
-	display: block;
-	width: 100%;
-	text-align: center;
-	font-weight: bold;
-	overflow: hidden;
-	text-overflow: ellipsis;
-	opacity: 0;
-	transition: all 0.2s ease-in-out;
-}
-#dashboardView li.dashboard-item:hover span {
-	transform: translateX(0px);
-	opacity: 1;
-}
-
-#dashboardView li:hover{
-	background-color: #475058;
-}
-
-#dashboardView li[data-selected] > div{
-	
-    border-top: 2px solid #e019a3;
-	color:#ffffff;
-}
-
-.widget{
-	transition:width 0.2s linear,height 0.2s linear,opacity 0.2s linear;
-	display: table;
-	padding:20px;
-}
-
-
-
-.widget_window{
-	box-shadow: 0 0 3px 0 #cecece;
-	margin: 0 0 10px;
-	padding: 0;
-	text-align: left;
-	transition: opacity 0.2s linear 0s;
-	z-index: 100;
-	display: table-cell;
-}
-
-.widget_header{
-	background-color: #50597b;
-	padding: 5px;
-	border-radius: 0;
-	box-sizing: border-box;
-	color: #ffffff;
-	cursor: move;
-	transition:background 0.2s linear;
-}
-
-.widget_options{
-	float: right;
-	margin: -5px 0 0 10px;
-	padding: 0;
-}
-.widget_options li{
-	cursor: pointer;
-	display: inline-block;
-	opacity: 0.5;
-	padding: 5px;
-	transition: opacity 0.2s linear 0s;
-	vertical-align: top;
-}
-.widget_options li:hover{
-	opacity: 1;
-}
-.widget_content{
-	background-color: #ffffff;
-	border-top:0;
-	overflow:auto;
-	max-width:100%;
-	max-height:400px;
-	padding: 0px;
-}
-
-.dashboard-container{
-	padding:10px 0px 0 80px
-}
-
-.dashboard-widget-menu{
-	margin:0 0 0 15px;
-	padding:0;
-}
-.dashboard-widget-menu li{
-	list-style-type: none;
-	color:#cecece;
-	display: inline-block;
-	cursor: pointer;
-	padding:5px;
-}
-.dashboard-widget-menu li span{
-	opacity:0.7;
-	display: inline-block;
-	transition : all 0.2s ease-in-out;
-}
-.dashboard-widget-menu li:hover span{
-	opacity:1;
-	transform: translateX(5px);
-}
-
-.widgetDescription{
-	margin-top:20px;
-}
-
-.widgetColor{
-	display: inline-block;
-	vertical-align: top;
-	padding-right: 5px;
-	color:#333333;
-}
-.widgetColor small{
-	width:20px;
-	height:20px;
-	border-radius: 100%;
-	display: inline-block;
-	margin-right: 5px;
-	vertical-align: top;
-}
-@media only screen and (max-width: 500px) {
-	.widget_dropper{
-		display: none;
-	}
-	
-	.widget,.widget_window{
-		display: block;
-		width:100%;
-	}
-}

+ 0 - 99
plugin/dashboard/css/widget.css

@@ -1,99 +0,0 @@
-.clockContainer .daydate{
-	font-size: 10px;
-}
-.clockContainer{
-	background-color:#343a40;
-	width:100%;
-	height:100%;
-	min-height: 263px;
-	box-sizing:border-box;
-	text-align:center;
-	padding:20px;
-}
-.clock {	
-	height: 200px;
-	width: 200px;
-	margin:auto;
-	position:relative;
-	background-color:#343a40;
-}
-
-.progress > svg {
-	height: 100%;
-	display: block;
-}
-
-.progressbar-text{
-	position:absolute;
-	left:0px;
-	top:60px;
-	width:100%;
-	text-align:center;
-	font-weight:200;
-	color:#ffffff!important;
-}
-.progressbar-text .dayName,.progressbar-text .dayDate{
-	font-size:15px;
-	color:#cecece;
-	margin: 10px 0;
-	font-weight:400;
-}
-.progressbar-text .dayName{
-	font-weight:bold;
-	color:#ffffff;
-	margin-top: 0px ;
-}
-
-/* profile widget */
-.profileContainer{
-	padding-bottom: 10px;
-	text-align: center;
-}
-.profileContainer .profileHeader{
-	background-color: #8e44ad;
-	border-bottom:5px solid rgba(255, 255, 255,0.5);
-	height:100px;
-	width:100%;
-	background-image: url('../img/defaultBackground.jpg');
-	background-size: cover;
-}
-.profileContainer .profileImage{
-	margin-top: -60px;
-	cursor:pointer;
-	display: block;
-	text-align: center;
-}
-
-.profileContainer .profileImage img{
-    border-radius: 120px;
-    height: 120px;
-    width: auto;
-    border:4px solid #ffffff;
-}
-.profileContainer h3{
-    text-align: center;
-    margin-bottom: 0px;
-    font-size: 20px;
-    margin-top: 6px;
-}
-.profileContainer small{
-    text-align: center;
-    font-size: 15px;
-    font-style: italic;
-    color:#cecece;
- 
-}
-.profileToken{
-	display: block;
-
-}
-
-.profileToken input{
-	padding: 2px 4px;
-    font-size: 90%;
-    color: #c7254e;
-    background-color: #f9f2f4;
-    border-radius: 4px;
-    border:none;
-    text-align: center;
-}

+ 0 - 129
plugin/dashboard/dashboard.plugin.php

@@ -1,129 +0,0 @@
-<?php
-
-
-
-
-//Cette fonction va generer une page quand on clique sur dashboard dans menu
-function dashboard_page(){
-	global $_,$myUser,$conf;
-	if(isset($_['module'])) return;
-	$page = !isset($_['page']) ? 'home' : $_['page'];
-	$file = __DIR__.SLASH.'page.'.$page.'.php';
-	if(!file_exists($file)) throw new Exception("Page ".$page." inexistante");
-	require_once($file);
-}
-
-//Fonction executée lors de l'activation du plugin
-function dashboard_install($id){
-	if($id != 'fr.idleman.dashboard') return;
-	global $conf;
-	Entity::install(__DIR__);
-	$conf->put('dashboard_enable_sidebar',1);
-}
-
-//Fonction executée lors de la désactivation du plugin
-function dashboard_uninstall($id){
-	if($id != 'fr.idleman.dashboard') return;
-	Entity::uninstall(__DIR__);
-}
-
-//Déclaration des sections de droits du plugin
-function dashboard_section(&$sections){
-	$sections['dashboard'] = "Gestion des droits sur le plugin dashboard";
-}
-
-//cette fonction comprends toutes les actions du plugin qui ne nécessitent pas de vue html
-function dashboard_action(){
-	require_once(__DIR__.SLASH.'action.php');
-}
-
-//Déclaration du menu de réglages
-function dashboard_menu_setting(&$settingMenu){
-	global $_, $myUser;
-	
-	if(!$myUser->can('dashboard','configure')) return;
-		
-		$settingMenu[]= array(
-			'sort' =>1,
-			'url' => 'setting.php?section=dashboard',
-			'icon' => 'fas fa-angle-right',
-			'label' => 'Dashboard'
-		);
-
-	
-
-	
-}
-
-//Déclaration des pages de réglages
-function dashboard_content_setting(){
-	global $_;
-	if(file_exists(__DIR__.SLASH.'setting.'.$_['section'].'.php'))
-		require_once(__DIR__.SLASH.'setting.'.$_['section'].'.php');
-}
-
-
-//Déclaration des settings de base
-//Types possibles : text,select ( + "values"=> array('1'=>'Val 1'),password,checkbox. Un simple string définit une catégorie.
-// Configuration::setting('dashboard',array(
-//     "Général",
-// ));
-
-function dashboard_default_widget(&$widgets){
-	global $myUser;
-	require_once('DashboardWidget.class.php');
-	$modelWidget = new DashboardWidget();
-	$modelWidget->model = 'clock';
-	$modelWidget->title = 'Horloge';
-	$modelWidget->icon = 'far fa-clock';
-	$modelWidget->background = '#212529';
-	$modelWidget->load = 'action.php?action=dashboard_widget_clock_load';
-	$modelWidget->js = [Plugin::url().'/js/progressbar.js',Plugin::url().'/js/widget.js?v=2'];
-	$modelWidget->css = [Plugin::url().'/css/widget.css?v=2'];
-	$modelWidget->description = "Affiche l'heure en temps réel";
-	$widgets[] = $modelWidget;
-
-
-	$modelWidget = new DashboardWidget();
-	$modelWidget->model = 'profile';
-	$modelWidget->title = 'Profile';
-	$modelWidget->icon = 'far fa-user';
-	$modelWidget->background = '#007bff';
-	$modelWidget->load = 'action.php?action=dashboard_widget_profile_load';
-	$modelWidget->js = [Plugin::url().'/js/widget.js'];
-	$modelWidget->css = [Plugin::url().'/css/widget.css?v=2'];
-	$modelWidget->description = "Affiche les informations de profil";
-	$widgets[] = $modelWidget;
-
-
-	$modelWidget = new DashboardWidget();
-	$modelWidget->model = 'log';
-	$modelWidget->title = 'Logs';
-	$modelWidget->width = 8;
-	$modelWidget->options[] = array('function'=>'window.location = \'setting.php?section=log\';','icon'=>'fa-eye','label'=>'Voir tous les logs');
-	$modelWidget->icon = 'far fa-comment-dots';
-	$modelWidget->background = '#28a745';
-	$modelWidget->load = 'action.php?action=dashboard_widget_log_load';
-	$modelWidget->js = [Plugin::url().'/js/widget.js'];
-	$modelWidget->css = [Plugin::url().'/css/widget.css?v=2'];
-	$modelWidget->description = "Affiche les informations des 30 derniers logs";
-	if($myUser->can('log','read')) 
-		$widgets[] = $modelWidget;
-}
-
-
-//Déclation des assets
-Plugin::addCss("/css/main.css?v=1"); 
-Plugin::addJs("/js/main.js?v=1"); 
-
-//Mapping hook / fonctions
-Plugin::addHook("install", "dashboard_install");
-Plugin::addHook("uninstall", "dashboard_uninstall"); 
-Plugin::addHook("section", "dashboard_section");
-Plugin::addHook("page", "dashboard_page");  
-Plugin::addHook("action", "dashboard_action");  
-Plugin::addHook("menu_setting", "dashboard_menu_setting");    
-Plugin::addHook("content_setting", "dashboard_content_setting");   
-Plugin::addHook("widget", "dashboard_default_widget");
-
-?>

BIN
plugin/dashboard/img/defaultBackground.jpg


+ 0 - 303
plugin/dashboard/js/main.js

@@ -1,303 +0,0 @@
-//handle target
-var target = false;
-var refreshInterval = null;
-
-
-
-
-//CHARGEMENT DE LA PAGE
-$(function(){
-
-	if($.page()!='index' || $.urlParam('module')!=null) return;
-	dashboard_dashboardwidget_search();
-		
-	$('#dashboardView li').click(function(){
-		$('#dashboardView li').removeAttr('data-selected');
-		$(this).attr('data-selected',1);
-		dashboard_dashboardwidget_search();
-	});
-	$('#widgetList').change(function(){
-		var option = $('#widgetList option:selected').data();
-		option.text = $('#widgetList option:selected').text();
-		$('.widgetDescription').removeClass('hidden');
-		$('.widgetDescription h1 span').text(option.text);
-		$('.widgetDescription h1 i').attr('class',option.icon);
-		$('.widgetDescription p').text(option.description);
-		$('.widgetDescription .widgetColor small').css('background-color',option.background);
-		$('.widgetDescription .widgetColor span').text(option.background);
-	});
-
-	$('#dashboard').sortable({
-		update:function(event,ui){
-			var data = dashboard_dashboardwidget_save_position();
-		}
-	});
-	$( ".widget" ).disableSelection();
-
-});
-
-
-function init_setting_dashboard(){
-	dashboard_dashboard_search();
-}
-
-//Enregistrement des configurations
-function dashboard_setting_save(){
-	$.action({ 
-		action : 'dashboard_setting_save', 
-		fields :  $('#dashboard-setting-form').toJson() 
-	},function(){ $.message('info','Configuration enregistrée'); });
-}
-
-
-/** DASHBOARD **/
-	
-//Récuperation d'une liste de dashboard dans le tableau #dashboards
-function dashboard_dashboard_search(callback){
-	
-	$('#dashboards').fill({
-		action:'dashboard_dashboard_search'
-	},function(){
-		if(callback!=null) callback();
-	});
-
-}
-
-//Ajout ou modification d'élément dashboard
-function dashboard_dashboard_save(){
-	var data = $('#dashboard-form').toJson();
-	$.action(data,function(r){
-		$.message('success','Enregistré');
-		dashboard_dashboard_search();
-		$('#dashboard-form').attr('data-id','');
-	});
-}
-
-//Récuperation ou edition d'élément dashboard
-function dashboard_dashboard_edit(element){
-	var line = $(element).closest('tr');
-	$.action({action:'dashboard_dashboard_edit',id:line.attr('data-id')},function(r){
-		$.setForm('#dashboard-form',r);
-		$('#dashboard-form').attr('data-id',r.id);
-	});
-}
-
-
-//Suppression d'élement dashboard
-function dashboard_dashboard_delete(element){
-	if(!confirm('Êtes vous sûr de vouloir supprimer cet item ?')) return;
-	var line = $(element).closest('tr');
-	$.action({
-		action : 'dashboard_dashboard_delete',
-		id : line.attr('data-id')
-	},function(r){
-		line.remove();
-		$.message('info','Élement supprimé');
-	});
-}
-
-/** DASHBOARDWIDGET **/
-	
-//Récuperation d'une liste de dashboardwidget dans le tableau #dashboardwidgets
-function dashboard_dashboardwidget_search(callback){
-	
-	var dashboard = $('#dashboardView li[data-selected]').attr('data-id');
-	if(!dashboard || dashboard=='') return;
-	$.action({
-				action : 'dashboard_dashboardwidget_search',
-				dashboard : dashboard
-			},function(r){
-				$('#dashboard .widget:visible').remove();
-				for(var i in r.rows){
-					var widget = r.rows[i];
-					dashboard_dashboardwidget_append(widget);
-					dashboard_dashboardwidget_load(widget);
-				}
-				clearInterval(refreshInterval);
-				refreshInterval = setInterval(function(){
-					$.action({
-						action : 'dashboard_dashboardwidget_refresh',
-						dashboard : $('#dashboardView li[data-selected]').attr('data-id')
-					},function(r){
-
-						for(var id in r.rows){
-							var widget = r.rows[id];
-
-							if(widget.widget){
-								var header = $('.widget[data-id="'+id+'"]').find('.widget_header'); 
-								if(widget.widget.title) header.find('span').text(widget.widget.title);
-								if(widget.widget.icon) header.find('i').attr('class','fa '+widget.widget.icon);
-								if(widget.widget.background) header.css('backgroundColor',widget.widget.background);
-							}
-
-							if(widget.callback){
-								if(window[widget.callback]!=null) window[widget.callback]($('.widget[data-id="'+id+'"]'),widget.data);
-							}
-							
-						}
-
-
-					});
-				},3000);
-			});
-}
-
-
-//Mise à jour des infos d'un élement widget à partir d'un object data
-function dashboard_dashboardwidget_render(widget,data){
-	widget.attr('data-id',data.id);
-	widget.removeClass (function (index, css) {
-		return (css.match (/(^|\s)col-md-\S+/g) || []).join(' ');
-	});
-	widget.attr('data-id',data.id)
-	.attr('data-load',data.load)
-	.attr('data-configure',data.configure)
-	.attr('data-delete',data.delete)
-	.addClass('col-md-'+data.width)
-	.find('.widget_header')
-	.css('background',data.background)
-	.find('i:eq(0)').attr('class','fa '+data.icon);
-		
-	widget.find('.widget_header span:eq(0)').html(data.title);
-	widget.find('.widget_content').html(data.content);
-		
-	var options = '';
-	for(var k in data.options){
-		var option = data.options[k];
-		options+='<li onclick="'+option.function+'"><i class="fa '+option.icon+'"></i> '+option.label+'</li>';
-	}
-
-	if(data.configure) options+="<li title='Configurer' onclick='configure_widget(this);'><i class='fa fa-wrench'></i></li>";
-	options+="<li title='Supprimer' onclick='dashboard_dashboardwidget_delete(this);'><i class='fa fa-times'></i></li>";
-
-	widget.find('.widget_options').html(options);
-		
-	widget.data('data',data);
-	widget.show();
-	return widget;
-}
-
-//Modification d'un widget existant
-function dashboard_dashboardwidget_update(data){
-
-	var widget = $('.widget[data-id="'+data.id+'"]');
-
-	var data = $.extend(widget.data('data'), data);
-	dashboard_dashboardwidget_render(widget,data);
-}
-
-//Ajout ou modification d'élément dashboardwidget
-function dashboard_dashboardwidget_save(){
-	var data = $('#dashboardwidget-form').toJson();
-	$.action(data,function(r){
-		
-		
-			$('#dashboardwidget-form').attr('data-id','');
-			dashboard_dashboardwidget_search();
-		
-		$.message('success','Enregistré');
-	});
-}
-
-//Chargement du contenu php du widget
-function dashboard_dashboardwidget_load(widget){
-	$.getJSON(widget.load,$.extend(widget,{content:''}),function(r){
-		dashboard_dashboardwidget_update(r);
-		var data = $.extend($('.widget[data-id="'+widget.id+'"]').data('data'), r.widget);
-		var init = 'widget_'+widget.model+'_init';
-		if(window[init]!=null) window[init]();
-	});
-}
-
-//Ajout (manuel par l'user) d'un widget
-function dashboard_dashboardwidget_add(){
-	$.action({
-		action : 'dashboard_dashboardwidget_add',
-		dashboard : $('#dashboardView li[data-selected]').attr('data-id'),
-		widget : $('#widgetList').val()
-	},function(r){
-		if(r.message) $.message('info',r.message);
-		$('#dashboard_dashboardwidget_appendModal').modal('hide');
-		dashboard_dashboardwidget_search();
-	});
-}
-
-
-
-//Ajout (depuis le code) d'un widget
-function dashboard_dashboardwidget_append(data){
-	var tpl = $('#dashboard .widget:hidden').get(0).outerHTML;
-	var widget = $(tpl);
-	$('#dashboard').append(widget);
-	if(data.js!=null){
-		for(k in data.js){
-			var js = data.js[k];
-			if($('script[src="'+js+'"]').length!=0) continue;
-			var jsFile= document.createElement('script');
-			jsFile.setAttribute("type","text/javascript");
-			jsFile.setAttribute("src", js);
-			document.getElementsByTagName("body")[0].appendChild(jsFile);
-		}
-	}
-		
-	if(data.css!=null){
-		for(k in data.css){
-			var css = data.css[k];
-			if($('script[src="'+css+'"]').length!=0) continue;
-			var cssFile= document.createElement('link');
-			cssFile.setAttribute("rel","stylesheet");
-			cssFile.setAttribute("type","text/css");
-			cssFile.setAttribute("href", css);
-			document.getElementsByTagName("body")[0].appendChild(cssFile);
-		}
-	}
-
-	
-	dashboard_dashboardwidget_render(widget,data);
-}
-
-//Récuperation ou edition d'élément dashboardwidget
-function dashboard_dashboardwidget_edit(widget){
-	var line = $(element).closest('tr');
-	$.action({action:'dashboard_dashboardwidget_edit',id:line.attr('data-id')},function(r){
-		$.setForm('#dashboardwidget-form',r);
-		$('#dashboardwidget-form').attr('data-id',r.id);
-	});
-}
-
-//Suppression d'élement dashboardwidget
-function dashboard_dashboardwidget_delete(element){
-	var element = $(element).closest('.widget');
-	var data = element.data('data');
-	
-	$.action({
-		action : 'dashboard_dashboardwidget_delete',
-		dashboard : $('#dashboardView li[data-selected]').attr('data-id'),
-		widget : data.id,
-	},function(r){
-		element.remove();
-		if(r.message) $.message('info',r.message);
-		if(data.delete != null){
-			$.getJSON(data.delete,$.extend(data,{content:''}));
-		}
-	});
-}
-
-
-
-//Enregistrement de toutes les positions de widget
-function dashboard_dashboardwidget_save_position(){
-	var positions = [];
-	$('.widget:visible').each(function(i,element){
-		positions.push({id:$(element).attr('data-id'),position:$(element).index()});
-	});
-	
-	$.action({
-		action : 'dashboard_dashboardwidget_save_position',
-		dashboard : $('#dashboardView li[data-selected]').attr('data-id'),
-		positions : positions,
-	},function(r){
-		
-	});
-
-}

+ 0 - 2434
plugin/dashboard/js/progressbar.js

@@ -1,2434 +0,0 @@
-// ProgressBar.js 1.0.1
-// https://kimmobrunfeldt.github.io/progressbar.js
-// License: MIT
-
-(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.ProgressBar = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
-/* shifty - v1.5.2 - 2016-02-10 - http://jeremyckahn.github.io/shifty */
-;(function () {
-  var root = this || Function('return this')();
-
-/**
- * Shifty Core
- * By Jeremy Kahn - jeremyckahn@gmail.com
- */
-
-var Tweenable = (function () {
-
-  'use strict';
-
-  // Aliases that get defined later in this function
-  var formula;
-
-  // CONSTANTS
-  var DEFAULT_SCHEDULE_FUNCTION;
-  var DEFAULT_EASING = 'linear';
-  var DEFAULT_DURATION = 500;
-  var UPDATE_TIME = 1000 / 60;
-
-  var _now = Date.now
-       ? Date.now
-       : function () {return +new Date();};
-
-  var now = typeof SHIFTY_DEBUG_NOW !== 'undefined' ? SHIFTY_DEBUG_NOW : _now;
-
-  if (typeof window !== 'undefined') {
-    // requestAnimationFrame() shim by Paul Irish (modified for Shifty)
-    // http://paulirish.com/2011/requestanimationframe-for-smart-animating/
-    DEFAULT_SCHEDULE_FUNCTION = window.requestAnimationFrame
-       || window.webkitRequestAnimationFrame
-       || window.oRequestAnimationFrame
-       || window.msRequestAnimationFrame
-       || (window.mozCancelRequestAnimationFrame
-       && window.mozRequestAnimationFrame)
-       || setTimeout;
-  } else {
-    DEFAULT_SCHEDULE_FUNCTION = setTimeout;
-  }
-
-  function noop () {
-    // NOOP!
-  }
-
-  /**
-   * Handy shortcut for doing a for-in loop. This is not a "normal" each
-   * function, it is optimized for Shifty.  The iterator function only receives
-   * the property name, not the value.
-   * @param {Object} obj
-   * @param {Function(string)} fn
-   * @private
-   */
-  function each (obj, fn) {
-    var key;
-    for (key in obj) {
-      if (Object.hasOwnProperty.call(obj, key)) {
-        fn(key);
-      }
-    }
-  }
-
-  /**
-   * Perform a shallow copy of Object properties.
-   * @param {Object} targetObject The object to copy into
-   * @param {Object} srcObject The object to copy from
-   * @return {Object} A reference to the augmented `targetObj` Object
-   * @private
-   */
-  function shallowCopy (targetObj, srcObj) {
-    each(srcObj, function (prop) {
-      targetObj[prop] = srcObj[prop];
-    });
-
-    return targetObj;
-  }
-
-  /**
-   * Copies each property from src onto target, but only if the property to
-   * copy to target is undefined.
-   * @param {Object} target Missing properties in this Object are filled in
-   * @param {Object} src
-   * @private
-   */
-  function defaults (target, src) {
-    each(src, function (prop) {
-      if (typeof target[prop] === 'undefined') {
-        target[prop] = src[prop];
-      }
-    });
-  }
-
-  /**
-   * Calculates the interpolated tween values of an Object for a given
-   * timestamp.
-   * @param {Number} forPosition The position to compute the state for.
-   * @param {Object} currentState Current state properties.
-   * @param {Object} originalState: The original state properties the Object is
-   * tweening from.
-   * @param {Object} targetState: The destination state properties the Object
-   * is tweening to.
-   * @param {number} duration: The length of the tween in milliseconds.
-   * @param {number} timestamp: The UNIX epoch time at which the tween began.
-   * @param {Object} easing: This Object's keys must correspond to the keys in
-   * targetState.
-   * @private
-   */
-  function tweenProps (forPosition, currentState, originalState, targetState,
-    duration, timestamp, easing) {
-    var normalizedPosition =
-        forPosition < timestamp ? 0 : (forPosition - timestamp) / duration;
-
-
-    var prop;
-    var easingObjectProp;
-    var easingFn;
-    for (prop in currentState) {
-      if (currentState.hasOwnProperty(prop)) {
-        easingObjectProp = easing[prop];
-        easingFn = typeof easingObjectProp === 'function'
-          ? easingObjectProp
-          : formula[easingObjectProp];
-
-        currentState[prop] = tweenProp(
-          originalState[prop],
-          targetState[prop],
-          easingFn,
-          normalizedPosition
-        );
-      }
-    }
-
-    return currentState;
-  }
-
-  /**
-   * Tweens a single property.
-   * @param {number} start The value that the tween started from.
-   * @param {number} end The value that the tween should end at.
-   * @param {Function} easingFunc The easing curve to apply to the tween.
-   * @param {number} position The normalized position (between 0.0 and 1.0) to
-   * calculate the midpoint of 'start' and 'end' against.
-   * @return {number} The tweened value.
-   * @private
-   */
-  function tweenProp (start, end, easingFunc, position) {
-    return start + (end - start) * easingFunc(position);
-  }
-
-  /**
-   * Applies a filter to Tweenable instance.
-   * @param {Tweenable} tweenable The `Tweenable` instance to call the filter
-   * upon.
-   * @param {String} filterName The name of the filter to apply.
-   * @private
-   */
-  function applyFilter (tweenable, filterName) {
-    var filters = Tweenable.prototype.filter;
-    var args = tweenable._filterArgs;
-
-    each(filters, function (name) {
-      if (typeof filters[name][filterName] !== 'undefined') {
-        filters[name][filterName].apply(tweenable, args);
-      }
-    });
-  }
-
-  var timeoutHandler_endTime;
-  var timeoutHandler_currentTime;
-  var timeoutHandler_isEnded;
-  var timeoutHandler_offset;
-  /**
-   * Handles the update logic for one step of a tween.
-   * @param {Tweenable} tweenable
-   * @param {number} timestamp
-   * @param {number} delay
-   * @param {number} duration
-   * @param {Object} currentState
-   * @param {Object} originalState
-   * @param {Object} targetState
-   * @param {Object} easing
-   * @param {Function(Object, *, number)} step
-   * @param {Function(Function,number)}} schedule
-   * @param {number=} opt_currentTimeOverride Needed for accurate timestamp in
-   * Tweenable#seek.
-   * @private
-   */
-  function timeoutHandler (tweenable, timestamp, delay, duration, currentState,
-    originalState, targetState, easing, step, schedule,
-    opt_currentTimeOverride) {
-
-    timeoutHandler_endTime = timestamp + delay + duration;
-
-    timeoutHandler_currentTime =
-    Math.min(opt_currentTimeOverride || now(), timeoutHandler_endTime);
-
-    timeoutHandler_isEnded =
-      timeoutHandler_currentTime >= timeoutHandler_endTime;
-
-    timeoutHandler_offset = duration - (
-      timeoutHandler_endTime - timeoutHandler_currentTime);
-
-    if (tweenable.isPlaying()) {
-      if (timeoutHandler_isEnded) {
-        step(targetState, tweenable._attachment, timeoutHandler_offset);
-        tweenable.stop(true);
-      } else {
-        tweenable._scheduleId =
-          schedule(tweenable._timeoutHandler, UPDATE_TIME);
-
-        applyFilter(tweenable, 'beforeTween');
-
-        // If the animation has not yet reached the start point (e.g., there was
-        // delay that has not yet completed), just interpolate the starting
-        // position of the tween.
-        if (timeoutHandler_currentTime < (timestamp + delay)) {
-          tweenProps(1, currentState, originalState, targetState, 1, 1, easing);
-        } else {
-          tweenProps(timeoutHandler_currentTime, currentState, originalState,
-            targetState, duration, timestamp + delay, easing);
-        }
-
-        applyFilter(tweenable, 'afterTween');
-
-        step(currentState, tweenable._attachment, timeoutHandler_offset);
-      }
-    }
-  }
-
-
-  /**
-   * Creates a usable easing Object from a string, a function or another easing
-   * Object.  If `easing` is an Object, then this function clones it and fills
-   * in the missing properties with `"linear"`.
-   * @param {Object.<string|Function>} fromTweenParams
-   * @param {Object|string|Function} easing
-   * @return {Object.<string|Function>}
-   * @private
-   */
-  function composeEasingObject (fromTweenParams, easing) {
-    var composedEasing = {};
-    var typeofEasing = typeof easing;
-
-    if (typeofEasing === 'string' || typeofEasing === 'function') {
-      each(fromTweenParams, function (prop) {
-        composedEasing[prop] = easing;
-      });
-    } else {
-      each(fromTweenParams, function (prop) {
-        if (!composedEasing[prop]) {
-          composedEasing[prop] = easing[prop] || DEFAULT_EASING;
-        }
-      });
-    }
-
-    return composedEasing;
-  }
-
-  /**
-   * Tweenable constructor.
-   * @class Tweenable
-   * @param {Object=} opt_initialState The values that the initial tween should
-   * start at if a `from` object is not provided to `{{#crossLink
-   * "Tweenable/tween:method"}}{{/crossLink}}` or `{{#crossLink
-   * "Tweenable/setConfig:method"}}{{/crossLink}}`.
-   * @param {Object=} opt_config Configuration object to be passed to
-   * `{{#crossLink "Tweenable/setConfig:method"}}{{/crossLink}}`.
-   * @module Tweenable
-   * @constructor
-   */
-  function Tweenable (opt_initialState, opt_config) {
-    this._currentState = opt_initialState || {};
-    this._configured = false;
-    this._scheduleFunction = DEFAULT_SCHEDULE_FUNCTION;
-
-    // To prevent unnecessary calls to setConfig do not set default
-    // configuration here.  Only set default configuration immediately before
-    // tweening if none has been set.
-    if (typeof opt_config !== 'undefined') {
-      this.setConfig(opt_config);
-    }
-  }
-
-  /**
-   * Configure and start a tween.
-   * @method tween
-   * @param {Object=} opt_config Configuration object to be passed to
-   * `{{#crossLink "Tweenable/setConfig:method"}}{{/crossLink}}`.
-   * @chainable
-   */
-  Tweenable.prototype.tween = function (opt_config) {
-    if (this._isTweening) {
-      return this;
-    }
-
-    // Only set default config if no configuration has been set previously and
-    // none is provided now.
-    if (opt_config !== undefined || !this._configured) {
-      this.setConfig(opt_config);
-    }
-
-    this._timestamp = now();
-    this._start(this.get(), this._attachment);
-    return this.resume();
-  };
-
-  /**
-   * Configure a tween that will start at some point in the future.
-   *
-   * @method setConfig
-   * @param {Object} config The following values are valid:
-   * - __from__ (_Object=_): Starting position.  If omitted, `{{#crossLink
-   *   "Tweenable/get:method"}}get(){{/crossLink}}` is used.
-   * - __to__ (_Object=_): Ending position.
-   * - __duration__ (_number=_): How many milliseconds to animate for.
-   * - __delay__ (_delay=_): How many milliseconds to wait before starting the
-   *   tween.
-   * - __start__ (_Function(Object, *)_): Function to execute when the tween
-   *   begins.  Receives the state of the tween as the first parameter and
-   *   `attachment` as the second parameter.
-   * - __step__ (_Function(Object, *, number)_): Function to execute on every
-   *   tick.  Receives `{{#crossLink
-   *   "Tweenable/get:method"}}get(){{/crossLink}}` as the first parameter,
-   *   `attachment` as the second parameter, and the time elapsed since the
-   *   start of the tween as the third. This function is not called on the
-   *   final step of the animation, but `finish` is.
-   * - __finish__ (_Function(Object, *)_): Function to execute upon tween
-   *   completion.  Receives the state of the tween as the first parameter and
-   *   `attachment` as the second parameter.
-   * - __easing__ (_Object.<string|Function>|string|Function=_): Easing curve
-   *   name(s) or function(s) to use for the tween.
-   * - __attachment__ (_*_): Cached value that is passed to the
-   *   `step`/`start`/`finish` methods.
-   * @chainable
-   */
-  Tweenable.prototype.setConfig = function (config) {
-    config = config || {};
-    this._configured = true;
-
-    // Attach something to this Tweenable instance (e.g.: a DOM element, an
-    // object, a string, etc.);
-    this._attachment = config.attachment;
-
-    // Init the internal state
-    this._pausedAtTime = null;
-    this._scheduleId = null;
-    this._delay = config.delay || 0;
-    this._start = config.start || noop;
-    this._step = config.step || noop;
-    this._finish = config.finish || noop;
-    this._duration = config.duration || DEFAULT_DURATION;
-    this._currentState = shallowCopy({}, config.from) || this.get();
-    this._originalState = this.get();
-    this._targetState = shallowCopy({}, config.to) || this.get();
-
-    var self = this;
-    this._timeoutHandler = function () {
-      timeoutHandler(self,
-        self._timestamp,
-        self._delay,
-        self._duration,
-        self._currentState,
-        self._originalState,
-        self._targetState,
-        self._easing,
-        self._step,
-        self._scheduleFunction
-      );
-    };
-
-    // Aliases used below
-    var currentState = this._currentState;
-    var targetState = this._targetState;
-
-    // Ensure that there is always something to tween to.
-    defaults(targetState, currentState);
-
-    this._easing = composeEasingObject(
-      currentState, config.easing || DEFAULT_EASING);
-
-    this._filterArgs =
-      [currentState, this._originalState, targetState, this._easing];
-
-    applyFilter(this, 'tweenCreated');
-    return this;
-  };
-
-  /**
-   * @method get
-   * @return {Object} The current state.
-   */
-  Tweenable.prototype.get = function () {
-    return shallowCopy({}, this._currentState);
-  };
-
-  /**
-   * @method set
-   * @param {Object} state The current state.
-   */
-  Tweenable.prototype.set = function (state) {
-    this._currentState = state;
-  };
-
-  /**
-   * Pause a tween.  Paused tweens can be resumed from the point at which they
-   * were paused.  This is different from `{{#crossLink
-   * "Tweenable/stop:method"}}{{/crossLink}}`, as that method
-   * causes a tween to start over when it is resumed.
-   * @method pause
-   * @chainable
-   */
-  Tweenable.prototype.pause = function () {
-    this._pausedAtTime = now();
-    this._isPaused = true;
-    return this;
-  };
-
-  /**
-   * Resume a paused tween.
-   * @method resume
-   * @chainable
-   */
-  Tweenable.prototype.resume = function () {
-    if (this._isPaused) {
-      this._timestamp += now() - this._pausedAtTime;
-    }
-
-    this._isPaused = false;
-    this._isTweening = true;
-
-    this._timeoutHandler();
-
-    return this;
-  };
-
-  /**
-   * Move the state of the animation to a specific point in the tween's
-   * timeline.  If the animation is not running, this will cause the `step`
-   * handlers to be called.
-   * @method seek
-   * @param {millisecond} millisecond The millisecond of the animation to seek
-   * to.  This must not be less than `0`.
-   * @chainable
-   */
-  Tweenable.prototype.seek = function (millisecond) {
-    millisecond = Math.max(millisecond, 0);
-    var currentTime = now();
-
-    if ((this._timestamp + millisecond) === 0) {
-      return this;
-    }
-
-    this._timestamp = currentTime - millisecond;
-
-    if (!this.isPlaying()) {
-      this._isTweening = true;
-      this._isPaused = false;
-
-      // If the animation is not running, call timeoutHandler to make sure that
-      // any step handlers are run.
-      timeoutHandler(this,
-        this._timestamp,
-        this._delay,
-        this._duration,
-        this._currentState,
-        this._originalState,
-        this._targetState,
-        this._easing,
-        this._step,
-        this._scheduleFunction,
-        currentTime
-      );
-
-      this.pause();
-    }
-
-    return this;
-  };
-
-  /**
-   * Stops and cancels a tween.
-   * @param {boolean=} gotoEnd If `false` or omitted, the tween just stops at
-   * its current state, and the `finish` handler is not invoked.  If `true`,
-   * the tweened object's values are instantly set to the target values, and
-   * `finish` is invoked.
-   * @method stop
-   * @chainable
-   */
-  Tweenable.prototype.stop = function (gotoEnd) {
-    this._isTweening = false;
-    this._isPaused = false;
-    this._timeoutHandler = noop;
-
-    (root.cancelAnimationFrame            ||
-    root.webkitCancelAnimationFrame     ||
-    root.oCancelAnimationFrame          ||
-    root.msCancelAnimationFrame         ||
-    root.mozCancelRequestAnimationFrame ||
-    root.clearTimeout)(this._scheduleId);
-
-    if (gotoEnd) {
-      applyFilter(this, 'beforeTween');
-      tweenProps(
-        1,
-        this._currentState,
-        this._originalState,
-        this._targetState,
-        1,
-        0,
-        this._easing
-      );
-      applyFilter(this, 'afterTween');
-      applyFilter(this, 'afterTweenEnd');
-      this._finish.call(this, this._currentState, this._attachment);
-    }
-
-    return this;
-  };
-
-  /**
-   * @method isPlaying
-   * @return {boolean} Whether or not a tween is running.
-   */
-  Tweenable.prototype.isPlaying = function () {
-    return this._isTweening && !this._isPaused;
-  };
-
-  /**
-   * Set a custom schedule function.
-   *
-   * If a custom function is not set,
-   * [`requestAnimationFrame`](https://developer.mozilla.org/en-US/docs/Web/API/window.requestAnimationFrame)
-   * is used if available, otherwise
-   * [`setTimeout`](https://developer.mozilla.org/en-US/docs/Web/API/Window.setTimeout)
-   * is used.
-   * @method setScheduleFunction
-   * @param {Function(Function,number)} scheduleFunction The function to be
-   * used to schedule the next frame to be rendered.
-   */
-  Tweenable.prototype.setScheduleFunction = function (scheduleFunction) {
-    this._scheduleFunction = scheduleFunction;
-  };
-
-  /**
-   * `delete` all "own" properties.  Call this when the `Tweenable` instance
-   * is no longer needed to free memory.
-   * @method dispose
-   */
-  Tweenable.prototype.dispose = function () {
-    var prop;
-    for (prop in this) {
-      if (this.hasOwnProperty(prop)) {
-        delete this[prop];
-      }
-    }
-  };
-
-  /**
-   * Filters are used for transforming the properties of a tween at various
-   * points in a Tweenable's life cycle.  See the README for more info on this.
-   * @private
-   */
-  Tweenable.prototype.filter = {};
-
-  /**
-   * This object contains all of the tweens available to Shifty.  It is
-   * extensible - simply attach properties to the `Tweenable.prototype.formula`
-   * Object following the same format as `linear`.
-   *
-   * `pos` should be a normalized `number` (between 0 and 1).
-   * @property formula
-   * @type {Object(function)}
-   */
-  Tweenable.prototype.formula = {
-    linear: function (pos) {
-      return pos;
-    }
-  };
-
-  formula = Tweenable.prototype.formula;
-
-  shallowCopy(Tweenable, {
-    'now': now
-    ,'each': each
-    ,'tweenProps': tweenProps
-    ,'tweenProp': tweenProp
-    ,'applyFilter': applyFilter
-    ,'shallowCopy': shallowCopy
-    ,'defaults': defaults
-    ,'composeEasingObject': composeEasingObject
-  });
-
-  // `root` is provided in the intro/outro files.
-
-  // A hook used for unit testing.
-  if (typeof SHIFTY_DEBUG_NOW === 'function') {
-    root.timeoutHandler = timeoutHandler;
-  }
-
-  // Bootstrap Tweenable appropriately for the environment.
-  if (typeof exports === 'object') {
-    // CommonJS
-    module.exports = Tweenable;
-  } else if (typeof define === 'function' && define.amd) {
-    // AMD
-    define(function () {return Tweenable;});
-  } else if (typeof root.Tweenable === 'undefined') {
-    // Browser: Make `Tweenable` globally accessible.
-    root.Tweenable = Tweenable;
-  }
-
-  return Tweenable;
-
-} ());
-
-/*!
- * All equations are adapted from Thomas Fuchs'
- * [Scripty2](https://github.com/madrobby/scripty2/blob/master/src/effects/transitions/penner.js).
- *
- * Based on Easing Equations (c) 2003 [Robert
- * Penner](http://www.robertpenner.com/), all rights reserved. This work is
- * [subject to terms](http://www.robertpenner.com/easing_terms_of_use.html).
- */
-
-/*!
- *  TERMS OF USE - EASING EQUATIONS
- *  Open source under the BSD License.
- *  Easing Equations (c) 2003 Robert Penner, all rights reserved.
- */
-
-;(function () {
-
-  Tweenable.shallowCopy(Tweenable.prototype.formula, {
-    easeInQuad: function (pos) {
-      return Math.pow(pos, 2);
-    },
-
-    easeOutQuad: function (pos) {
-      return -(Math.pow((pos - 1), 2) - 1);
-    },
-
-    easeInOutQuad: function (pos) {
-      if ((pos /= 0.5) < 1) {return 0.5 * Math.pow(pos,2);}
-      return -0.5 * ((pos -= 2) * pos - 2);
-    },
-
-    easeInCubic: function (pos) {
-      return Math.pow(pos, 3);
-    },
-
-    easeOutCubic: function (pos) {
-      return (Math.pow((pos - 1), 3) + 1);
-    },
-
-    easeInOutCubic: function (pos) {
-      if ((pos /= 0.5) < 1) {return 0.5 * Math.pow(pos,3);}
-      return 0.5 * (Math.pow((pos - 2),3) + 2);
-    },
-
-    easeInQuart: function (pos) {
-      return Math.pow(pos, 4);
-    },
-
-    easeOutQuart: function (pos) {
-      return -(Math.pow((pos - 1), 4) - 1);
-    },
-
-    easeInOutQuart: function (pos) {
-      if ((pos /= 0.5) < 1) {return 0.5 * Math.pow(pos,4);}
-      return -0.5 * ((pos -= 2) * Math.pow(pos,3) - 2);
-    },
-
-    easeInQuint: function (pos) {
-      return Math.pow(pos, 5);
-    },
-
-    easeOutQuint: function (pos) {
-      return (Math.pow((pos - 1), 5) + 1);
-    },
-
-    easeInOutQuint: function (pos) {
-      if ((pos /= 0.5) < 1) {return 0.5 * Math.pow(pos,5);}
-      return 0.5 * (Math.pow((pos - 2),5) + 2);
-    },
-
-    easeInSine: function (pos) {
-      return -Math.cos(pos * (Math.PI / 2)) + 1;
-    },
-
-    easeOutSine: function (pos) {
-      return Math.sin(pos * (Math.PI / 2));
-    },
-
-    easeInOutSine: function (pos) {
-      return (-0.5 * (Math.cos(Math.PI * pos) - 1));
-    },
-
-    easeInExpo: function (pos) {
-      return (pos === 0) ? 0 : Math.pow(2, 10 * (pos - 1));
-    },
-
-    easeOutExpo: function (pos) {
-      return (pos === 1) ? 1 : -Math.pow(2, -10 * pos) + 1;
-    },
-
-    easeInOutExpo: function (pos) {
-      if (pos === 0) {return 0;}
-      if (pos === 1) {return 1;}
-      if ((pos /= 0.5) < 1) {return 0.5 * Math.pow(2,10 * (pos - 1));}
-      return 0.5 * (-Math.pow(2, -10 * --pos) + 2);
-    },
-
-    easeInCirc: function (pos) {
-      return -(Math.sqrt(1 - (pos * pos)) - 1);
-    },
-
-    easeOutCirc: function (pos) {
-      return Math.sqrt(1 - Math.pow((pos - 1), 2));
-    },
-
-    easeInOutCirc: function (pos) {
-      if ((pos /= 0.5) < 1) {return -0.5 * (Math.sqrt(1 - pos * pos) - 1);}
-      return 0.5 * (Math.sqrt(1 - (pos -= 2) * pos) + 1);
-    },
-
-    easeOutBounce: function (pos) {
-      if ((pos) < (1 / 2.75)) {
-        return (7.5625 * pos * pos);
-      } else if (pos < (2 / 2.75)) {
-        return (7.5625 * (pos -= (1.5 / 2.75)) * pos + 0.75);
-      } else if (pos < (2.5 / 2.75)) {
-        return (7.5625 * (pos -= (2.25 / 2.75)) * pos + 0.9375);
-      } else {
-        return (7.5625 * (pos -= (2.625 / 2.75)) * pos + 0.984375);
-      }
-    },
-
-    easeInBack: function (pos) {
-      var s = 1.70158;
-      return (pos) * pos * ((s + 1) * pos - s);
-    },
-
-    easeOutBack: function (pos) {
-      var s = 1.70158;
-      return (pos = pos - 1) * pos * ((s + 1) * pos + s) + 1;
-    },
-
-    easeInOutBack: function (pos) {
-      var s = 1.70158;
-      if ((pos /= 0.5) < 1) {
-        return 0.5 * (pos * pos * (((s *= (1.525)) + 1) * pos - s));
-      }
-      return 0.5 * ((pos -= 2) * pos * (((s *= (1.525)) + 1) * pos + s) + 2);
-    },
-
-    elastic: function (pos) {
-      // jshint maxlen:90
-      return -1 * Math.pow(4,-8 * pos) * Math.sin((pos * 6 - 1) * (2 * Math.PI) / 2) + 1;
-    },
-
-    swingFromTo: function (pos) {
-      var s = 1.70158;
-      return ((pos /= 0.5) < 1) ?
-          0.5 * (pos * pos * (((s *= (1.525)) + 1) * pos - s)) :
-          0.5 * ((pos -= 2) * pos * (((s *= (1.525)) + 1) * pos + s) + 2);
-    },
-
-    swingFrom: function (pos) {
-      var s = 1.70158;
-      return pos * pos * ((s + 1) * pos - s);
-    },
-
-    swingTo: function (pos) {
-      var s = 1.70158;
-      return (pos -= 1) * pos * ((s + 1) * pos + s) + 1;
-    },
-
-    bounce: function (pos) {
-      if (pos < (1 / 2.75)) {
-        return (7.5625 * pos * pos);
-      } else if (pos < (2 / 2.75)) {
-        return (7.5625 * (pos -= (1.5 / 2.75)) * pos + 0.75);
-      } else if (pos < (2.5 / 2.75)) {
-        return (7.5625 * (pos -= (2.25 / 2.75)) * pos + 0.9375);
-      } else {
-        return (7.5625 * (pos -= (2.625 / 2.75)) * pos + 0.984375);
-      }
-    },
-
-    bouncePast: function (pos) {
-      if (pos < (1 / 2.75)) {
-        return (7.5625 * pos * pos);
-      } else if (pos < (2 / 2.75)) {
-        return 2 - (7.5625 * (pos -= (1.5 / 2.75)) * pos + 0.75);
-      } else if (pos < (2.5 / 2.75)) {
-        return 2 - (7.5625 * (pos -= (2.25 / 2.75)) * pos + 0.9375);
-      } else {
-        return 2 - (7.5625 * (pos -= (2.625 / 2.75)) * pos + 0.984375);
-      }
-    },
-
-    easeFromTo: function (pos) {
-      if ((pos /= 0.5) < 1) {return 0.5 * Math.pow(pos,4);}
-      return -0.5 * ((pos -= 2) * Math.pow(pos,3) - 2);
-    },
-
-    easeFrom: function (pos) {
-      return Math.pow(pos,4);
-    },
-
-    easeTo: function (pos) {
-      return Math.pow(pos,0.25);
-    }
-  });
-
-}());
-
-// jshint maxlen:100
-/**
- * The Bezier magic in this file is adapted/copied almost wholesale from
- * [Scripty2](https://github.com/madrobby/scripty2/blob/master/src/effects/transitions/cubic-bezier.js),
- * which was adapted from Apple code (which probably came from
- * [here](http://opensource.apple.com/source/WebCore/WebCore-955.66/platform/graphics/UnitBezier.h)).
- * Special thanks to Apple and Thomas Fuchs for much of this code.
- */
-
-/**
- *  Copyright (c) 2006 Apple Computer, Inc. All rights reserved.
- *
- *  Redistribution and use in source and binary forms, with or without
- *  modification, are permitted provided that the following conditions are met:
- *
- *  1. Redistributions of source code must retain the above copyright notice,
- *  this list of conditions and the following disclaimer.
- *
- *  2. Redistributions in binary form must reproduce the above copyright notice,
- *  this list of conditions and the following disclaimer in the documentation
- *  and/or other materials provided with the distribution.
- *
- *  3. Neither the name of the copyright holder(s) nor the names of any
- *  contributors may be used to endorse or promote products derived from
- *  this software without specific prior written permission.
- *
- *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
- *  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
- *  IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
- *  ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
- *  LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
- *  CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
- *  SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
- *  INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
- *  CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- *  ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
- *  POSSIBILITY OF SUCH DAMAGE.
- */
-;(function () {
-  // port of webkit cubic bezier handling by http://www.netzgesta.de/dev/
-  function cubicBezierAtTime(t,p1x,p1y,p2x,p2y,duration) {
-    var ax = 0,bx = 0,cx = 0,ay = 0,by = 0,cy = 0;
-    function sampleCurveX(t) {
-      return ((ax * t + bx) * t + cx) * t;
-    }
-    function sampleCurveY(t) {
-      return ((ay * t + by) * t + cy) * t;
-    }
-    function sampleCurveDerivativeX(t) {
-      return (3.0 * ax * t + 2.0 * bx) * t + cx;
-    }
-    function solveEpsilon(duration) {
-      return 1.0 / (200.0 * duration);
-    }
-    function solve(x,epsilon) {
-      return sampleCurveY(solveCurveX(x, epsilon));
-    }
-    function fabs(n) {
-      if (n >= 0) {
-        return n;
-      } else {
-        return 0 - n;
-      }
-    }
-    function solveCurveX(x, epsilon) {
-      var t0,t1,t2,x2,d2,i;
-      for (t2 = x, i = 0; i < 8; i++) {
-        x2 = sampleCurveX(t2) - x;
-        if (fabs(x2) < epsilon) {
-          return t2;
-        }
-        d2 = sampleCurveDerivativeX(t2);
-        if (fabs(d2) < 1e-6) {
-          break;
-        }
-        t2 = t2 - x2 / d2;
-      }
-      t0 = 0.0;
-      t1 = 1.0;
-      t2 = x;
-      if (t2 < t0) {
-        return t0;
-      }
-      if (t2 > t1) {
-        return t1;
-      }
-      while (t0 < t1) {
-        x2 = sampleCurveX(t2);
-        if (fabs(x2 - x) < epsilon) {
-          return t2;
-        }
-        if (x > x2) {
-          t0 = t2;
-        }else {
-          t1 = t2;
-        }
-        t2 = (t1 - t0) * 0.5 + t0;
-      }
-      return t2; // Failure.
-    }
-    cx = 3.0 * p1x;
-    bx = 3.0 * (p2x - p1x) - cx;
-    ax = 1.0 - cx - bx;
-    cy = 3.0 * p1y;
-    by = 3.0 * (p2y - p1y) - cy;
-    ay = 1.0 - cy - by;
-    return solve(t, solveEpsilon(duration));
-  }
-  /**
-   *  getCubicBezierTransition(x1, y1, x2, y2) -> Function
-   *
-   *  Generates a transition easing function that is compatible
-   *  with WebKit's CSS transitions `-webkit-transition-timing-function`
-   *  CSS property.
-   *
-   *  The W3C has more information about CSS3 transition timing functions:
-   *  http://www.w3.org/TR/css3-transitions/#transition-timing-function_tag
-   *
-   *  @param {number} x1
-   *  @param {number} y1
-   *  @param {number} x2
-   *  @param {number} y2
-   *  @return {function}
-   *  @private
-   */
-  function getCubicBezierTransition (x1, y1, x2, y2) {
-    return function (pos) {
-      return cubicBezierAtTime(pos,x1,y1,x2,y2,1);
-    };
-  }
-  // End ported code
-
-  /**
-   * Create a Bezier easing function and attach it to `{{#crossLink
-   * "Tweenable/formula:property"}}Tweenable#formula{{/crossLink}}`.  This
-   * function gives you total control over the easing curve.  Matthew Lein's
-   * [Ceaser](http://matthewlein.com/ceaser/) is a useful tool for visualizing
-   * the curves you can make with this function.
-   * @method setBezierFunction
-   * @param {string} name The name of the easing curve.  Overwrites the old
-   * easing function on `{{#crossLink
-   * "Tweenable/formula:property"}}Tweenable#formula{{/crossLink}}` if it
-   * exists.
-   * @param {number} x1
-   * @param {number} y1
-   * @param {number} x2
-   * @param {number} y2
-   * @return {function} The easing function that was attached to
-   * Tweenable.prototype.formula.
-   */
-  Tweenable.setBezierFunction = function (name, x1, y1, x2, y2) {
-    var cubicBezierTransition = getCubicBezierTransition(x1, y1, x2, y2);
-    cubicBezierTransition.displayName = name;
-    cubicBezierTransition.x1 = x1;
-    cubicBezierTransition.y1 = y1;
-    cubicBezierTransition.x2 = x2;
-    cubicBezierTransition.y2 = y2;
-
-    return Tweenable.prototype.formula[name] = cubicBezierTransition;
-  };
-
-
-  /**
-   * `delete` an easing function from `{{#crossLink
-   * "Tweenable/formula:property"}}Tweenable#formula{{/crossLink}}`.  Be
-   * careful with this method, as it `delete`s whatever easing formula matches
-   * `name` (which means you can delete standard Shifty easing functions).
-   * @method unsetBezierFunction
-   * @param {string} name The name of the easing function to delete.
-   * @return {function}
-   */
-  Tweenable.unsetBezierFunction = function (name) {
-    delete Tweenable.prototype.formula[name];
-  };
-
-})();
-
-;(function () {
-
-  function getInterpolatedValues (
-    from, current, targetState, position, easing, delay) {
-    return Tweenable.tweenProps(
-      position, current, from, targetState, 1, delay, easing);
-  }
-
-  // Fake a Tweenable and patch some internals.  This approach allows us to
-  // skip uneccessary processing and object recreation, cutting down on garbage
-  // collection pauses.
-  var mockTweenable = new Tweenable();
-  mockTweenable._filterArgs = [];
-
-  /**
-   * Compute the midpoint of two Objects.  This method effectively calculates a
-   * specific frame of animation that `{{#crossLink
-   * "Tweenable/tween:method"}}{{/crossLink}}` does many times over the course
-   * of a full tween.
-   *
-   *     var interpolatedValues = Tweenable.interpolate({
-   *       width: '100px',
-   *       opacity: 0,
-   *       color: '#fff'
-   *     }, {
-   *       width: '200px',
-   *       opacity: 1,
-   *       color: '#000'
-   *     }, 0.5);
-   *
-   *     console.log(interpolatedValues);
-   *     // {opacity: 0.5, width: "150px", color: "rgb(127,127,127)"}
-   *
-   * @static
-   * @method interpolate
-   * @param {Object} from The starting values to tween from.
-   * @param {Object} targetState The ending values to tween to.
-   * @param {number} position The normalized position value (between `0.0` and
-   * `1.0`) to interpolate the values between `from` and `to` for.  `from`
-   * represents `0` and `to` represents `1`.
-   * @param {Object.<string|Function>|string|Function} easing The easing
-   * curve(s) to calculate the midpoint against.  You can reference any easing
-   * function attached to `Tweenable.prototype.formula`, or provide the easing
-   * function(s) directly.  If omitted, this defaults to "linear".
-   * @param {number=} opt_delay Optional delay to pad the beginning of the
-   * interpolated tween with.  This increases the range of `position` from (`0`
-   * through `1`) to (`0` through `1 + opt_delay`).  So, a delay of `0.5` would
-   * increase all valid values of `position` to numbers between `0` and `1.5`.
-   * @return {Object}
-   */
-  Tweenable.interpolate = function (
-    from, targetState, position, easing, opt_delay) {
-
-    var current = Tweenable.shallowCopy({}, from);
-    var delay = opt_delay || 0;
-    var easingObject = Tweenable.composeEasingObject(
-      from, easing || 'linear');
-
-    mockTweenable.set({});
-
-    // Alias and reuse the _filterArgs array instead of recreating it.
-    var filterArgs = mockTweenable._filterArgs;
-    filterArgs.length = 0;
-    filterArgs[0] = current;
-    filterArgs[1] = from;
-    filterArgs[2] = targetState;
-    filterArgs[3] = easingObject;
-
-    // Any defined value transformation must be applied
-    Tweenable.applyFilter(mockTweenable, 'tweenCreated');
-    Tweenable.applyFilter(mockTweenable, 'beforeTween');
-
-    var interpolatedValues = getInterpolatedValues(
-      from, current, targetState, position, easingObject, delay);
-
-    // Transform values back into their original format
-    Tweenable.applyFilter(mockTweenable, 'afterTween');
-
-    return interpolatedValues;
-  };
-
-}());
-
-/**
- * This module adds string interpolation support to Shifty.
- *
- * The Token extension allows Shifty to tween numbers inside of strings.  Among
- * other things, this allows you to animate CSS properties.  For example, you
- * can do this:
- *
- *     var tweenable = new Tweenable();
- *     tweenable.tween({
- *       from: { transform: 'translateX(45px)' },
- *       to: { transform: 'translateX(90xp)' }
- *     });
- *
- * `translateX(45)` will be tweened to `translateX(90)`.  To demonstrate:
- *
- *     var tweenable = new Tweenable();
- *     tweenable.tween({
- *       from: { transform: 'translateX(45px)' },
- *       to: { transform: 'translateX(90px)' },
- *       step: function (state) {
- *         console.log(state.transform);
- *       }
- *     });
- *
- * The above snippet will log something like this in the console:
- *
- *     translateX(60.3px)
- *     ...
- *     translateX(76.05px)
- *     ...
- *     translateX(90px)
- *
- * Another use for this is animating colors:
- *
- *     var tweenable = new Tweenable();
- *     tweenable.tween({
- *       from: { color: 'rgb(0,255,0)' },
- *       to: { color: 'rgb(255,0,255)' },
- *       step: function (state) {
- *         console.log(state.color);
- *       }
- *     });
- *
- * The above snippet will log something like this:
- *
- *     rgb(84,170,84)
- *     ...
- *     rgb(170,84,170)
- *     ...
- *     rgb(255,0,255)
- *
- * This extension also supports hexadecimal colors, in both long (`#ff00ff`)
- * and short (`#f0f`) forms.  Be aware that hexadecimal input values will be
- * converted into the equivalent RGB output values.  This is done to optimize
- * for performance.
- *
- *     var tweenable = new Tweenable();
- *     tweenable.tween({
- *       from: { color: '#0f0' },
- *       to: { color: '#f0f' },
- *       step: function (state) {
- *         console.log(state.color);
- *       }
- *     });
- *
- * This snippet will generate the same output as the one before it because
- * equivalent values were supplied (just in hexadecimal form rather than RGB):
- *
- *     rgb(84,170,84)
- *     ...
- *     rgb(170,84,170)
- *     ...
- *     rgb(255,0,255)
- *
- * ## Easing support
- *
- * Easing works somewhat differently in the Token extension.  This is because
- * some CSS properties have multiple values in them, and you might need to
- * tween each value along its own easing curve.  A basic example:
- *
- *     var tweenable = new Tweenable();
- *     tweenable.tween({
- *       from: { transform: 'translateX(0px) translateY(0px)' },
- *       to: { transform:   'translateX(100px) translateY(100px)' },
- *       easing: { transform: 'easeInQuad' },
- *       step: function (state) {
- *         console.log(state.transform);
- *       }
- *     });
- *
- * The above snippet will create values like this:
- *
- *     translateX(11.56px) translateY(11.56px)
- *     ...
- *     translateX(46.24px) translateY(46.24px)
- *     ...
- *     translateX(100px) translateY(100px)
- *
- * In this case, the values for `translateX` and `translateY` are always the
- * same for each step of the tween, because they have the same start and end
- * points and both use the same easing curve.  We can also tween `translateX`
- * and `translateY` along independent curves:
- *
- *     var tweenable = new Tweenable();
- *     tweenable.tween({
- *       from: { transform: 'translateX(0px) translateY(0px)' },
- *       to: { transform:   'translateX(100px) translateY(100px)' },
- *       easing: { transform: 'easeInQuad bounce' },
- *       step: function (state) {
- *         console.log(state.transform);
- *       }
- *     });
- *
- * The above snippet will create values like this:
- *
- *     translateX(10.89px) translateY(82.35px)
- *     ...
- *     translateX(44.89px) translateY(86.73px)
- *     ...
- *     translateX(100px) translateY(100px)
- *
- * `translateX` and `translateY` are not in sync anymore, because `easeInQuad`
- * was specified for `translateX` and `bounce` for `translateY`.  Mixing and
- * matching easing curves can make for some interesting motion in your
- * animations.
- *
- * The order of the space-separated easing curves correspond the token values
- * they apply to.  If there are more token values than easing curves listed,
- * the last easing curve listed is used.
- * @submodule Tweenable.token
- */
-
-// token function is defined above only so that dox-foundation sees it as
-// documentation and renders it.  It is never used, and is optimized away at
-// build time.
-
-;(function (Tweenable) {
-
-  /**
-   * @typedef {{
-   *   formatString: string
-   *   chunkNames: Array.<string>
-   * }}
-   * @private
-   */
-  var formatManifest;
-
-  // CONSTANTS
-
-  var R_NUMBER_COMPONENT = /(\d|\-|\.)/;
-  var R_FORMAT_CHUNKS = /([^\-0-9\.]+)/g;
-  var R_UNFORMATTED_VALUES = /[0-9.\-]+/g;
-  var R_RGB = new RegExp(
-    'rgb\\(' + R_UNFORMATTED_VALUES.source +
-    (/,\s*/.source) + R_UNFORMATTED_VALUES.source +
-    (/,\s*/.source) + R_UNFORMATTED_VALUES.source + '\\)', 'g');
-  var R_RGB_PREFIX = /^.*\(/;
-  var R_HEX = /#([0-9]|[a-f]){3,6}/gi;
-  var VALUE_PLACEHOLDER = 'VAL';
-
-  // HELPERS
-
-  /**
-   * @param {Array.number} rawValues
-   * @param {string} prefix
-   *
-   * @return {Array.<string>}
-   * @private
-   */
-  function getFormatChunksFrom (rawValues, prefix) {
-    var accumulator = [];
-
-    var rawValuesLength = rawValues.length;
-    var i;
-
-    for (i = 0; i < rawValuesLength; i++) {
-      accumulator.push('_' + prefix + '_' + i);
-    }
-
-    return accumulator;
-  }
-
-  /**
-   * @param {string} formattedString
-   *
-   * @return {string}
-   * @private
-   */
-  function getFormatStringFrom (formattedString) {
-    var chunks = formattedString.match(R_FORMAT_CHUNKS);
-
-    if (!chunks) {
-      // chunks will be null if there were no tokens to parse in
-      // formattedString (for example, if formattedString is '2').  Coerce
-      // chunks to be useful here.
-      chunks = ['', ''];
-
-      // If there is only one chunk, assume that the string is a number
-      // followed by a token...
-      // NOTE: This may be an unwise assumption.
-    } else if (chunks.length === 1 ||
-      // ...or if the string starts with a number component (".", "-", or a
-      // digit)...
-    formattedString[0].match(R_NUMBER_COMPONENT)) {
-      // ...prepend an empty string here to make sure that the formatted number
-      // is properly replaced by VALUE_PLACEHOLDER
-      chunks.unshift('');
-    }
-
-    return chunks.join(VALUE_PLACEHOLDER);
-  }
-
-  /**
-   * Convert all hex color values within a string to an rgb string.
-   *
-   * @param {Object} stateObject
-   *
-   * @return {Object} The modified obj
-   * @private
-   */
-  function sanitizeObjectForHexProps (stateObject) {
-    Tweenable.each(stateObject, function (prop) {
-      var currentProp = stateObject[prop];
-
-      if (typeof currentProp === 'string' && currentProp.match(R_HEX)) {
-        stateObject[prop] = sanitizeHexChunksToRGB(currentProp);
-      }
-    });
-  }
-
-  /**
-   * @param {string} str
-   *
-   * @return {string}
-   * @private
-   */
-  function  sanitizeHexChunksToRGB (str) {
-    return filterStringChunks(R_HEX, str, convertHexToRGB);
-  }
-
-  /**
-   * @param {string} hexString
-   *
-   * @return {string}
-   * @private
-   */
-  function convertHexToRGB (hexString) {
-    var rgbArr = hexToRGBArray(hexString);
-    return 'rgb(' + rgbArr[0] + ',' + rgbArr[1] + ',' + rgbArr[2] + ')';
-  }
-
-  var hexToRGBArray_returnArray = [];
-  /**
-   * Convert a hexadecimal string to an array with three items, one each for
-   * the red, blue, and green decimal values.
-   *
-   * @param {string} hex A hexadecimal string.
-   *
-   * @returns {Array.<number>} The converted Array of RGB values if `hex` is a
-   * valid string, or an Array of three 0's.
-   * @private
-   */
-  function hexToRGBArray (hex) {
-
-    hex = hex.replace(/#/, '');
-
-    // If the string is a shorthand three digit hex notation, normalize it to
-    // the standard six digit notation
-    if (hex.length === 3) {
-      hex = hex.split('');
-      hex = hex[0] + hex[0] + hex[1] + hex[1] + hex[2] + hex[2];
-    }
-
-    hexToRGBArray_returnArray[0] = hexToDec(hex.substr(0, 2));
-    hexToRGBArray_returnArray[1] = hexToDec(hex.substr(2, 2));
-    hexToRGBArray_returnArray[2] = hexToDec(hex.substr(4, 2));
-
-    return hexToRGBArray_returnArray;
-  }
-
-  /**
-   * Convert a base-16 number to base-10.
-   *
-   * @param {Number|String} hex The value to convert
-   *
-   * @returns {Number} The base-10 equivalent of `hex`.
-   * @private
-   */
-  function hexToDec (hex) {
-    return parseInt(hex, 16);
-  }
-
-  /**
-   * Runs a filter operation on all chunks of a string that match a RegExp
-   *
-   * @param {RegExp} pattern
-   * @param {string} unfilteredString
-   * @param {function(string)} filter
-   *
-   * @return {string}
-   * @private
-   */
-  function filterStringChunks (pattern, unfilteredString, filter) {
-    var pattenMatches = unfilteredString.match(pattern);
-    var filteredString = unfilteredString.replace(pattern, VALUE_PLACEHOLDER);
-
-    if (pattenMatches) {
-      var pattenMatchesLength = pattenMatches.length;
-      var currentChunk;
-
-      for (var i = 0; i < pattenMatchesLength; i++) {
-        currentChunk = pattenMatches.shift();
-        filteredString = filteredString.replace(
-          VALUE_PLACEHOLDER, filter(currentChunk));
-      }
-    }
-
-    return filteredString;
-  }
-
-  /**
-   * Check for floating point values within rgb strings and rounds them.
-   *
-   * @param {string} formattedString
-   *
-   * @return {string}
-   * @private
-   */
-  function sanitizeRGBChunks (formattedString) {
-    return filterStringChunks(R_RGB, formattedString, sanitizeRGBChunk);
-  }
-
-  /**
-   * @param {string} rgbChunk
-   *
-   * @return {string}
-   * @private
-   */
-  function sanitizeRGBChunk (rgbChunk) {
-    var numbers = rgbChunk.match(R_UNFORMATTED_VALUES);
-    var numbersLength = numbers.length;
-    var sanitizedString = rgbChunk.match(R_RGB_PREFIX)[0];
-
-    for (var i = 0; i < numbersLength; i++) {
-      sanitizedString += parseInt(numbers[i], 10) + ',';
-    }
-
-    sanitizedString = sanitizedString.slice(0, -1) + ')';
-
-    return sanitizedString;
-  }
-
-  /**
-   * @param {Object} stateObject
-   *
-   * @return {Object} An Object of formatManifests that correspond to
-   * the string properties of stateObject
-   * @private
-   */
-  function getFormatManifests (stateObject) {
-    var manifestAccumulator = {};
-
-    Tweenable.each(stateObject, function (prop) {
-      var currentProp = stateObject[prop];
-
-      if (typeof currentProp === 'string') {
-        var rawValues = getValuesFrom(currentProp);
-
-        manifestAccumulator[prop] = {
-          'formatString': getFormatStringFrom(currentProp)
-          ,'chunkNames': getFormatChunksFrom(rawValues, prop)
-        };
-      }
-    });
-
-    return manifestAccumulator;
-  }
-
-  /**
-   * @param {Object} stateObject
-   * @param {Object} formatManifests
-   * @private
-   */
-  function expandFormattedProperties (stateObject, formatManifests) {
-    Tweenable.each(formatManifests, function (prop) {
-      var currentProp = stateObject[prop];
-      var rawValues = getValuesFrom(currentProp);
-      var rawValuesLength = rawValues.length;
-
-      for (var i = 0; i < rawValuesLength; i++) {
-        stateObject[formatManifests[prop].chunkNames[i]] = +rawValues[i];
-      }
-
-      delete stateObject[prop];
-    });
-  }
-
-  /**
-   * @param {Object} stateObject
-   * @param {Object} formatManifests
-   * @private
-   */
-  function collapseFormattedProperties (stateObject, formatManifests) {
-    Tweenable.each(formatManifests, function (prop) {
-      var currentProp = stateObject[prop];
-      var formatChunks = extractPropertyChunks(
-        stateObject, formatManifests[prop].chunkNames);
-      var valuesList = getValuesList(
-        formatChunks, formatManifests[prop].chunkNames);
-      currentProp = getFormattedValues(
-        formatManifests[prop].formatString, valuesList);
-      stateObject[prop] = sanitizeRGBChunks(currentProp);
-    });
-  }
-
-  /**
-   * @param {Object} stateObject
-   * @param {Array.<string>} chunkNames
-   *
-   * @return {Object} The extracted value chunks.
-   * @private
-   */
-  function extractPropertyChunks (stateObject, chunkNames) {
-    var extractedValues = {};
-    var currentChunkName, chunkNamesLength = chunkNames.length;
-
-    for (var i = 0; i < chunkNamesLength; i++) {
-      currentChunkName = chunkNames[i];
-      extractedValues[currentChunkName] = stateObject[currentChunkName];
-      delete stateObject[currentChunkName];
-    }
-
-    return extractedValues;
-  }
-
-  var getValuesList_accumulator = [];
-  /**
-   * @param {Object} stateObject
-   * @param {Array.<string>} chunkNames
-   *
-   * @return {Array.<number>}
-   * @private
-   */
-  function getValuesList (stateObject, chunkNames) {
-    getValuesList_accumulator.length = 0;
-    var chunkNamesLength = chunkNames.length;
-
-    for (var i = 0; i < chunkNamesLength; i++) {
-      getValuesList_accumulator.push(stateObject[chunkNames[i]]);
-    }
-
-    return getValuesList_accumulator;
-  }
-
-  /**
-   * @param {string} formatString
-   * @param {Array.<number>} rawValues
-   *
-   * @return {string}
-   * @private
-   */
-  function getFormattedValues (formatString, rawValues) {
-    var formattedValueString = formatString;
-    var rawValuesLength = rawValues.length;
-
-    for (var i = 0; i < rawValuesLength; i++) {
-      formattedValueString = formattedValueString.replace(
-        VALUE_PLACEHOLDER, +rawValues[i].toFixed(4));
-    }
-
-    return formattedValueString;
-  }
-
-  /**
-   * Note: It's the duty of the caller to convert the Array elements of the
-   * return value into numbers.  This is a performance optimization.
-   *
-   * @param {string} formattedString
-   *
-   * @return {Array.<string>|null}
-   * @private
-   */
-  function getValuesFrom (formattedString) {
-    return formattedString.match(R_UNFORMATTED_VALUES);
-  }
-
-  /**
-   * @param {Object} easingObject
-   * @param {Object} tokenData
-   * @private
-   */
-  function expandEasingObject (easingObject, tokenData) {
-    Tweenable.each(tokenData, function (prop) {
-      var currentProp = tokenData[prop];
-      var chunkNames = currentProp.chunkNames;
-      var chunkLength = chunkNames.length;
-
-      var easing = easingObject[prop];
-      var i;
-
-      if (typeof easing === 'string') {
-        var easingChunks = easing.split(' ');
-        var lastEasingChunk = easingChunks[easingChunks.length - 1];
-
-        for (i = 0; i < chunkLength; i++) {
-          easingObject[chunkNames[i]] = easingChunks[i] || lastEasingChunk;
-        }
-
-      } else {
-        for (i = 0; i < chunkLength; i++) {
-          easingObject[chunkNames[i]] = easing;
-        }
-      }
-
-      delete easingObject[prop];
-    });
-  }
-
-  /**
-   * @param {Object} easingObject
-   * @param {Object} tokenData
-   * @private
-   */
-  function collapseEasingObject (easingObject, tokenData) {
-    Tweenable.each(tokenData, function (prop) {
-      var currentProp = tokenData[prop];
-      var chunkNames = currentProp.chunkNames;
-      var chunkLength = chunkNames.length;
-
-      var firstEasing = easingObject[chunkNames[0]];
-      var typeofEasings = typeof firstEasing;
-
-      if (typeofEasings === 'string') {
-        var composedEasingString = '';
-
-        for (var i = 0; i < chunkLength; i++) {
-          composedEasingString += ' ' + easingObject[chunkNames[i]];
-          delete easingObject[chunkNames[i]];
-        }
-
-        easingObject[prop] = composedEasingString.substr(1);
-      } else {
-        easingObject[prop] = firstEasing;
-      }
-    });
-  }
-
-  Tweenable.prototype.filter.token = {
-    'tweenCreated': function (currentState, fromState, toState, easingObject) {
-      sanitizeObjectForHexProps(currentState);
-      sanitizeObjectForHexProps(fromState);
-      sanitizeObjectForHexProps(toState);
-      this._tokenData = getFormatManifests(currentState);
-    },
-
-    'beforeTween': function (currentState, fromState, toState, easingObject) {
-      expandEasingObject(easingObject, this._tokenData);
-      expandFormattedProperties(currentState, this._tokenData);
-      expandFormattedProperties(fromState, this._tokenData);
-      expandFormattedProperties(toState, this._tokenData);
-    },
-
-    'afterTween': function (currentState, fromState, toState, easingObject) {
-      collapseFormattedProperties(currentState, this._tokenData);
-      collapseFormattedProperties(fromState, this._tokenData);
-      collapseFormattedProperties(toState, this._tokenData);
-      collapseEasingObject(easingObject, this._tokenData);
-    }
-  };
-
-} (Tweenable));
-
-}).call(null);
-
-},{}],2:[function(require,module,exports){
-// Circle shaped progress bar
-
-var Shape = require('./shape');
-var utils = require('./utils');
-
-var Circle = function Circle(container, options) {
-    // Use two arcs to form a circle
-    // See this answer http://stackoverflow.com/a/10477334/1446092
-    this._pathTemplate =
-        'M 50,50 m 0,-{radius}' +
-        ' a {radius},{radius} 0 1 1 0,{2radius}' +
-        ' a {radius},{radius} 0 1 1 0,-{2radius}';
-
-    this.containerAspectRatio = 1;
-
-    Shape.apply(this, arguments);
-};
-
-Circle.prototype = new Shape();
-Circle.prototype.constructor = Circle;
-
-Circle.prototype._pathString = function _pathString(opts) {
-    var widthOfWider = opts.strokeWidth;
-    if (opts.trailWidth && opts.trailWidth > opts.strokeWidth) {
-        widthOfWider = opts.trailWidth;
-    }
-
-    var r = 50 - widthOfWider / 2;
-
-    return utils.render(this._pathTemplate, {
-        radius: r,
-        '2radius': r * 2
-    });
-};
-
-Circle.prototype._trailString = function _trailString(opts) {
-    return this._pathString(opts);
-};
-
-module.exports = Circle;
-
-},{"./shape":7,"./utils":8}],3:[function(require,module,exports){
-// Line shaped progress bar
-
-var Shape = require('./shape');
-var utils = require('./utils');
-
-var Line = function Line(container, options) {
-    this._pathTemplate = 'M 0,{center} L 100,{center}';
-    Shape.apply(this, arguments);
-};
-
-Line.prototype = new Shape();
-Line.prototype.constructor = Line;
-
-Line.prototype._initializeSvg = function _initializeSvg(svg, opts) {
-    svg.setAttribute('viewBox', '0 0 100 ' + opts.strokeWidth);
-    svg.setAttribute('preserveAspectRatio', 'none');
-};
-
-Line.prototype._pathString = function _pathString(opts) {
-    return utils.render(this._pathTemplate, {
-        center: opts.strokeWidth / 2
-    });
-};
-
-Line.prototype._trailString = function _trailString(opts) {
-    return this._pathString(opts);
-};
-
-module.exports = Line;
-
-},{"./shape":7,"./utils":8}],4:[function(require,module,exports){
-module.exports = {
-    // Higher level API, different shaped progress bars
-    Line: require('./line'),
-    Circle: require('./circle'),
-    SemiCircle: require('./semicircle'),
-
-    // Lower level API to use any SVG path
-    Path: require('./path'),
-
-    // Base-class for creating new custom shapes
-    // to be in line with the API of built-in shapes
-    // Undocumented.
-    Shape: require('./shape'),
-
-    // Internal utils, undocumented.
-    utils: require('./utils')
-};
-
-},{"./circle":2,"./line":3,"./path":5,"./semicircle":6,"./shape":7,"./utils":8}],5:[function(require,module,exports){
-// Lower level API to animate any kind of svg path
-
-var Tweenable = require('shifty');
-var utils = require('./utils');
-
-var EASING_ALIASES = {
-    easeIn: 'easeInCubic',
-    easeOut: 'easeOutCubic',
-    easeInOut: 'easeInOutCubic'
-};
-
-var Path = function Path(path, opts) {
-    // Throw a better error if not initialized with `new` keyword
-    if (!(this instanceof Path)) {
-        throw new Error('Constructor was called without new keyword');
-    }
-
-    // Default parameters for animation
-    opts = utils.extend({
-        duration: 800,
-        easing: 'linear',
-        from: {},
-        to: {},
-        step: function() {}
-    }, opts);
-
-    var element;
-    if (utils.isString(path)) {
-        element = document.querySelector(path);
-    } else {
-        element = path;
-    }
-
-    // Reveal .path as public attribute
-    this.path = element;
-    this._opts = opts;
-    this._tweenable = null;
-
-    // Set up the starting positions
-    var length = this.path.getTotalLength();
-    this.path.style.strokeDasharray = length + ' ' + length;
-    this.set(0);
-};
-
-Path.prototype.value = function value() {
-    var offset = this._getComputedDashOffset();
-    var length = this.path.getTotalLength();
-
-    var progress = 1 - offset / length;
-    // Round number to prevent returning very small number like 1e-30, which
-    // is practically 0
-    return parseFloat(progress.toFixed(6), 10);
-};
-
-Path.prototype.set = function set(progress) {
-    this.stop();
-
-    this.path.style.strokeDashoffset = this._progressToOffset(progress);
-
-    var step = this._opts.step;
-    if (utils.isFunction(step)) {
-        var easing = this._easing(this._opts.easing);
-        var values = this._calculateTo(progress, easing);
-        var reference = this._opts.shape || this;
-        step(values, reference, this._opts.attachment);
-    }
-};
-
-Path.prototype.stop = function stop() {
-    this._stopTween();
-    this.path.style.strokeDashoffset = this._getComputedDashOffset();
-};
-
-// Method introduced here:
-// http://jakearchibald.com/2013/animated-line-drawing-svg/
-Path.prototype.animate = function animate(progress, opts, cb) {
-    opts = opts || {};
-
-    if (utils.isFunction(opts)) {
-        cb = opts;
-        opts = {};
-    }
-
-    var passedOpts = utils.extend({}, opts);
-
-    // Copy default opts to new object so defaults are not modified
-    var defaultOpts = utils.extend({}, this._opts);
-    opts = utils.extend(defaultOpts, opts);
-
-    var shiftyEasing = this._easing(opts.easing);
-    var values = this._resolveFromAndTo(progress, shiftyEasing, passedOpts);
-
-    this.stop();
-
-    // Trigger a layout so styles are calculated & the browser
-    // picks up the starting position before animating
-    this.path.getBoundingClientRect();
-
-    var offset = this._getComputedDashOffset();
-    var newOffset = this._progressToOffset(progress);
-
-    var self = this;
-    this._tweenable = new Tweenable();
-    this._tweenable.tween({
-        from: utils.extend({ offset: offset }, values.from),
-        to: utils.extend({ offset: newOffset }, values.to),
-        duration: opts.duration,
-        easing: shiftyEasing,
-        step: function(state) {
-            self.path.style.strokeDashoffset = state.offset;
-            var reference = opts.shape || self;
-            opts.step(state, reference, opts.attachment);
-        },
-        finish: function(state) {
-            if (utils.isFunction(cb)) {
-                cb();
-            }
-        }
-    });
-};
-
-Path.prototype._getComputedDashOffset = function _getComputedDashOffset() {
-    var computedStyle = window.getComputedStyle(this.path, null);
-    return parseFloat(computedStyle.getPropertyValue('stroke-dashoffset'), 10);
-};
-
-Path.prototype._progressToOffset = function _progressToOffset(progress) {
-    var length = this.path.getTotalLength();
-    return length - progress * length;
-};
-
-// Resolves from and to values for animation.
-Path.prototype._resolveFromAndTo = function _resolveFromAndTo(progress, easing, opts) {
-    if (opts.from && opts.to) {
-        return {
-            from: opts.from,
-            to: opts.to
-        };
-    }
-
-    return {
-        from: this._calculateFrom(easing),
-        to: this._calculateTo(progress, easing)
-    };
-};
-
-// Calculate `from` values from options passed at initialization
-Path.prototype._calculateFrom = function _calculateFrom(easing) {
-    return Tweenable.interpolate(this._opts.from, this._opts.to, this.value(), easing);
-};
-
-// Calculate `to` values from options passed at initialization
-Path.prototype._calculateTo = function _calculateTo(progress, easing) {
-    return Tweenable.interpolate(this._opts.from, this._opts.to, progress, easing);
-};
-
-Path.prototype._stopTween = function _stopTween() {
-    if (this._tweenable !== null) {
-        this._tweenable.stop();
-        this._tweenable = null;
-    }
-};
-
-Path.prototype._easing = function _easing(easing) {
-    if (EASING_ALIASES.hasOwnProperty(easing)) {
-        return EASING_ALIASES[easing];
-    }
-
-    return easing;
-};
-
-module.exports = Path;
-
-},{"./utils":8,"shifty":1}],6:[function(require,module,exports){
-// Semi-SemiCircle shaped progress bar
-
-var Shape = require('./shape');
-var Circle = require('./circle');
-var utils = require('./utils');
-
-var SemiCircle = function SemiCircle(container, options) {
-    // Use one arc to form a SemiCircle
-    // See this answer http://stackoverflow.com/a/10477334/1446092
-    this._pathTemplate =
-        'M 50,50 m -{radius},0' +
-        ' a {radius},{radius} 0 1 1 {2radius},0';
-
-    this.containerAspectRatio = 2;
-
-    Shape.apply(this, arguments);
-};
-
-SemiCircle.prototype = new Shape();
-SemiCircle.prototype.constructor = SemiCircle;
-
-SemiCircle.prototype._initializeSvg = function _initializeSvg(svg, opts) {
-    svg.setAttribute('viewBox', '0 0 100 50');
-};
-
-SemiCircle.prototype._initializeTextContainer = function _initializeTextContainer(
-    opts,
-    container,
-    textContainer
-) {
-    if (opts.text.style) {
-        // Reset top style
-        textContainer.style.top = 'auto';
-        textContainer.style.bottom = '0';
-
-        if (opts.text.alignToBottom) {
-            utils.setStyle(textContainer, 'transform', 'translate(-50%, 0)');
-        } else {
-            utils.setStyle(textContainer, 'transform', 'translate(-50%, 50%)');
-        }
-    }
-};
-
-// Share functionality with Circle, just have different path
-SemiCircle.prototype._pathString = Circle.prototype._pathString;
-SemiCircle.prototype._trailString = Circle.prototype._trailString;
-
-module.exports = SemiCircle;
-
-},{"./circle":2,"./shape":7,"./utils":8}],7:[function(require,module,exports){
-// Base object for different progress bar shapes
-
-var Path = require('./path');
-var utils = require('./utils');
-
-var DESTROYED_ERROR = 'Object is destroyed';
-
-var Shape = function Shape(container, opts) {
-    // Throw a better error if progress bars are not initialized with `new`
-    // keyword
-    if (!(this instanceof Shape)) {
-        throw new Error('Constructor was called without new keyword');
-    }
-
-    // Prevent calling constructor without parameters so inheritance
-    // works correctly. To understand, this is how Shape is inherited:
-    //
-    //   Line.prototype = new Shape();
-    //
-    // We just want to set the prototype for Line.
-    if (arguments.length === 0) {
-        return;
-    }
-
-    // Default parameters for progress bar creation
-    this._opts = utils.extend({
-        color: '#555',
-        strokeWidth: 1.0,
-        trailColor: null,
-        trailWidth: null,
-        fill: null,
-        text: {
-            style: {
-                color: null,
-                position: 'absolute',
-                left: '50%',
-                top: '50%',
-                padding: 0,
-                margin: 0,
-                transform: {
-                    prefix: true,
-                    value: 'translate(-50%, -50%)'
-                }
-            },
-            autoStyleContainer: true,
-            alignToBottom: true,
-            value: null,
-            className: 'progressbar-text'
-        },
-        svgStyle: {
-            display: 'block',
-            width: '100%'
-        },
-        warnings: false
-    }, opts, true);  // Use recursive extend
-
-    // If user specifies e.g. svgStyle or text style, the whole object
-    // should replace the defaults to make working with styles easier
-    if (utils.isObject(opts) && opts.svgStyle !== undefined) {
-        this._opts.svgStyle = opts.svgStyle;
-    }
-    if (utils.isObject(opts) && utils.isObject(opts.text) && opts.text.style !== undefined) {
-        this._opts.text.style = opts.text.style;
-    }
-
-    var svgView = this._createSvgView(this._opts);
-
-    var element;
-    if (utils.isString(container)) {
-        element = document.querySelector(container);
-    } else {
-        element = container;
-    }
-
-    if (!element) {
-        throw new Error('Container does not exist: ' + container);
-    }
-
-    this._container = element;
-    this._container.appendChild(svgView.svg);
-    if (this._opts.warnings) {
-        this._warnContainerAspectRatio(this._container);
-    }
-
-    if (this._opts.svgStyle) {
-        utils.setStyles(svgView.svg, this._opts.svgStyle);
-    }
-
-    // Expose public attributes before Path initialization
-    this.svg = svgView.svg;
-    this.path = svgView.path;
-    this.trail = svgView.trail;
-    this.text = null;
-
-    var newOpts = utils.extend({
-        attachment: undefined,
-        shape: this
-    }, this._opts);
-    this._progressPath = new Path(svgView.path, newOpts);
-
-    if (utils.isObject(this._opts.text) && this._opts.text.value !== null) {
-        this.setText(this._opts.text.value);
-    }
-};
-
-Shape.prototype.animate = function animate(progress, opts, cb) {
-    if (this._progressPath === null) {
-        throw new Error(DESTROYED_ERROR);
-    }
-
-    this._progressPath.animate(progress, opts, cb);
-};
-
-Shape.prototype.stop = function stop() {
-    if (this._progressPath === null) {
-        throw new Error(DESTROYED_ERROR);
-    }
-
-    // Don't crash if stop is called inside step function
-    if (this._progressPath === undefined) {
-        return;
-    }
-
-    this._progressPath.stop();
-};
-
-Shape.prototype.destroy = function destroy() {
-    if (this._progressPath === null) {
-        throw new Error(DESTROYED_ERROR);
-    }
-
-    this.stop();
-    this.svg.parentNode.removeChild(this.svg);
-    this.svg = null;
-    this.path = null;
-    this.trail = null;
-    this._progressPath = null;
-
-    if (this.text !== null) {
-        this.text.parentNode.removeChild(this.text);
-        this.text = null;
-    }
-};
-
-Shape.prototype.set = function set(progress) {
-    if (this._progressPath === null) {
-        throw new Error(DESTROYED_ERROR);
-    }
-
-    this._progressPath.set(progress);
-};
-
-Shape.prototype.value = function value() {
-    if (this._progressPath === null) {
-        throw new Error(DESTROYED_ERROR);
-    }
-
-    if (this._progressPath === undefined) {
-        return 0;
-    }
-
-    return this._progressPath.value();
-};
-
-Shape.prototype.setText = function setText(newText) {
-    if (this._progressPath === null) {
-        throw new Error(DESTROYED_ERROR);
-    }
-
-    if (this.text === null) {
-        // Create new text node
-        this.text = this._createTextContainer(this._opts, this._container);
-        this._container.appendChild(this.text);
-    }
-
-    // Remove previous text and add new
-    if (utils.isObject(newText)) {
-        utils.removeChildren(this.text);
-        this.text.appendChild(newText);
-    } else {
-        this.text.innerHTML = newText;
-    }
-};
-
-Shape.prototype._createSvgView = function _createSvgView(opts) {
-    var svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
-    this._initializeSvg(svg, opts);
-
-    var trailPath = null;
-    // Each option listed in the if condition are 'triggers' for creating
-    // the trail path
-    if (opts.trailColor || opts.trailWidth) {
-        trailPath = this._createTrail(opts);
-        svg.appendChild(trailPath);
-    }
-
-    var path = this._createPath(opts);
-    svg.appendChild(path);
-
-    return {
-        svg: svg,
-        path: path,
-        trail: trailPath
-    };
-};
-
-Shape.prototype._initializeSvg = function _initializeSvg(svg, opts) {
-    svg.setAttribute('viewBox', '0 0 100 100');
-};
-
-Shape.prototype._createPath = function _createPath(opts) {
-    var pathString = this._pathString(opts);
-    return this._createPathElement(pathString, opts);
-};
-
-Shape.prototype._createTrail = function _createTrail(opts) {
-    // Create path string with original passed options
-    var pathString = this._trailString(opts);
-
-    // Prevent modifying original
-    var newOpts = utils.extend({}, opts);
-
-    // Defaults for parameters which modify trail path
-    if (!newOpts.trailColor) {
-        newOpts.trailColor = '#eee';
-    }
-    if (!newOpts.trailWidth) {
-        newOpts.trailWidth = newOpts.strokeWidth;
-    }
-
-    newOpts.color = newOpts.trailColor;
-    newOpts.strokeWidth = newOpts.trailWidth;
-
-    // When trail path is set, fill must be set for it instead of the
-    // actual path to prevent trail stroke from clipping
-    newOpts.fill = null;
-
-    return this._createPathElement(pathString, newOpts);
-};
-
-Shape.prototype._createPathElement = function _createPathElement(pathString, opts) {
-    var path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
-    path.setAttribute('d', pathString);
-    path.setAttribute('stroke', opts.color);
-    path.setAttribute('stroke-width', opts.strokeWidth);
-
-    if (opts.fill) {
-        path.setAttribute('fill', opts.fill);
-    } else {
-        path.setAttribute('fill-opacity', '0');
-    }
-
-    return path;
-};
-
-Shape.prototype._createTextContainer = function _createTextContainer(opts, container) {
-    var textContainer = document.createElement('div');
-    textContainer.className = opts.text.className;
-
-    var textStyle = opts.text.style;
-    if (textStyle) {
-        if (opts.text.autoStyleContainer) {
-            container.style.position = 'relative';
-        }
-
-        utils.setStyles(textContainer, textStyle);
-        // Default text color to progress bar's color
-        if (!textStyle.color) {
-            textContainer.style.color = opts.color;
-        }
-    }
-
-    this._initializeTextContainer(opts, container, textContainer);
-    return textContainer;
-};
-
-// Give custom shapes possibility to modify text element
-Shape.prototype._initializeTextContainer = function(opts, container, element) {
-    // By default, no-op
-    // Custom shapes should respect API options, such as text.style
-};
-
-Shape.prototype._pathString = function _pathString(opts) {
-    throw new Error('Override this function for each progress bar');
-};
-
-Shape.prototype._trailString = function _trailString(opts) {
-    throw new Error('Override this function for each progress bar');
-};
-
-Shape.prototype._warnContainerAspectRatio = function _warnContainerAspectRatio(container) {
-    if (!this.containerAspectRatio) {
-        return;
-    }
-
-    var computedStyle = window.getComputedStyle(container, null);
-    var width = parseFloat(computedStyle.getPropertyValue('width'), 10);
-    var height = parseFloat(computedStyle.getPropertyValue('height'), 10);
-    if (!utils.floatEquals(this.containerAspectRatio, width / height)) {
-        console.warn(
-            'Incorrect aspect ratio of container',
-            '#' + container.id,
-            'detected:',
-            computedStyle.getPropertyValue('width') + '(width)',
-            '/',
-            computedStyle.getPropertyValue('height') + '(height)',
-            '=',
-            width / height
-        );
-
-        console.warn(
-            'Aspect ratio of should be',
-            this.containerAspectRatio
-        );
-    }
-};
-
-module.exports = Shape;
-
-},{"./path":5,"./utils":8}],8:[function(require,module,exports){
-// Utility functions
-
-var PREFIXES = 'Webkit Moz O ms'.split(' ');
-var FLOAT_COMPARISON_EPSILON = 0.001;
-
-// Copy all attributes from source object to destination object.
-// destination object is mutated.
-function extend(destination, source, recursive) {
-    destination = destination || {};
-    source = source || {};
-    recursive = recursive || false;
-
-    for (var attrName in source) {
-        if (source.hasOwnProperty(attrName)) {
-            var destVal = destination[attrName];
-            var sourceVal = source[attrName];
-            if (recursive && isObject(destVal) && isObject(sourceVal)) {
-                destination[attrName] = extend(destVal, sourceVal, recursive);
-            } else {
-                destination[attrName] = sourceVal;
-            }
-        }
-    }
-
-    return destination;
-}
-
-// Renders templates with given variables. Variables must be surrounded with
-// braces without any spaces, e.g. {variable}
-// All instances of variable placeholders will be replaced with given content
-// Example:
-// render('Hello, {message}!', {message: 'world'})
-function render(template, vars) {
-    var rendered = template;
-
-    for (var key in vars) {
-        if (vars.hasOwnProperty(key)) {
-            var val = vars[key];
-            var regExpString = '\\{' + key + '\\}';
-            var regExp = new RegExp(regExpString, 'g');
-
-            rendered = rendered.replace(regExp, val);
-        }
-    }
-
-    return rendered;
-}
-
-function setStyle(element, style, value) {
-    var elStyle = element.style;  // cache for performance
-
-    for (var i = 0; i < PREFIXES.length; ++i) {
-        var prefix = PREFIXES[i];
-        elStyle[prefix + capitalize(style)] = value;
-    }
-
-    elStyle[style] = value;
-}
-
-function setStyles(element, styles) {
-    forEachObject(styles, function(styleValue, styleName) {
-        // Allow disabling some individual styles by setting them
-        // to null or undefined
-        if (styleValue === null || styleValue === undefined) {
-            return;
-        }
-
-        // If style's value is {prefix: true, value: '50%'},
-        // Set also browser prefixed styles
-        if (isObject(styleValue) && styleValue.prefix === true) {
-            setStyle(element, styleName, styleValue.value);
-        } else {
-            element.style[styleName] = styleValue;
-        }
-    });
-}
-
-function capitalize(text) {
-    return text.charAt(0).toUpperCase() + text.slice(1);
-}
-
-function isString(obj) {
-    return typeof obj === 'string' || obj instanceof String;
-}
-
-function isFunction(obj) {
-    return typeof obj === 'function';
-}
-
-function isArray(obj) {
-    return Object.prototype.toString.call(obj) === '[object Array]';
-}
-
-// Returns true if `obj` is object as in {a: 1, b: 2}, not if it's function or
-// array
-function isObject(obj) {
-    if (isArray(obj)) {
-        return false;
-    }
-
-    var type = typeof obj;
-    return type === 'object' && !!obj;
-}
-
-function forEachObject(object, callback) {
-    for (var key in object) {
-        if (object.hasOwnProperty(key)) {
-            var val = object[key];
-            callback(val, key);
-        }
-    }
-}
-
-function floatEquals(a, b) {
-    return Math.abs(a - b) < FLOAT_COMPARISON_EPSILON;
-}
-
-// https://coderwall.com/p/nygghw/don-t-use-innerhtml-to-empty-dom-elements
-function removeChildren(el) {
-    while (el.firstChild) {
-        el.removeChild(el.firstChild);
-    }
-}
-
-module.exports = {
-    extend: extend,
-    render: render,
-    setStyle: setStyle,
-    setStyles: setStyles,
-    capitalize: capitalize,
-    isString: isString,
-    isFunction: isFunction,
-    isObject: isObject,
-    forEachObject: forEachObject,
-    floatEquals: floatEquals,
-    removeChildren: removeChildren
-};
-
-},{}]},{},[4])(4)
-});

+ 0 - 36
plugin/dashboard/js/widget.js

@@ -1,36 +0,0 @@
-var circle;
-
-function widget_clock_init(){
-	
-	circle = new ProgressBar.Circle('#clock', {
-		color: '#50C8FB',
-		duration: 3000,
-		easing: 'easeInOut',
-		text: {
-		style : { fontSize : '38px'},
-		value : '00:00:00'
-		}
-	});
-
-	refresh_clock();
-	setInterval(function(){
-		refresh_clock();
-	},1000);
-
-}
-
-function refresh_clock(){
-	var d = new Date();
-	var hour = d.getHours(); 
-	var minut = ("00" + d.getMinutes()).slice(-2) ; 
-	var second = ("00" + d.getSeconds()).slice(-2) ; 
-	var year = d.getFullYear() ; 
-	var month = ("00" + (d.getMonth()+1)).slice(-2) ; 
-	var day = ("00" + d.getDate()).slice(-2) ; 
-	var days = ["Dimanche","Lundi","Mardi","Mercredi","Jeudi","Vendredi","Samedi"];
-	var monthName = ["Janvier","Février","Mars","Avril","Mai","Juin","Juillet","Aout","Septembre","Octobre","Novembre","Décembre"];
-
-	var dayName = days[d.getDay()];
-	circle.setText(hour+':'+minut+':'+second+'<div class="dayName">'+dayName+'</div><div class="dayDate">'+day+' '+monthName[d.getMonth()]+' '+year+'</div>');
-	circle.set(second/60);
-}

+ 0 - 149
plugin/dashboard/page.home.php

@@ -1,149 +0,0 @@
-<?php 
-global $myUser,$conf; 
-require_once('Dashboard.class.php');
-require_once('DashboardWidget.class.php');
-
-$dashboards = Dashboard::loadAll(array('user'=>$myUser->login));
-
-
-
-?>
-<div class="dashboard-container" style="<?php echo count($dashboards)==1 ? 'padding-left: 0px;' : ''; ?>">
-	<?php if($myUser->connected()): ?>
-		<ul id="dashboardView" class="<?php echo count($dashboards)>1?'':'hidden' ?>" >
-			<li class="dashboardView-title">DASH</li>
-			<?php 
-			
-			if(count($dashboards)==0){
-				$defaultDash = Dashboard::provide();
-				$defaultDash->user = $myUser->login;
-				$defaultDash->label = 'Général';
-				$defaultDash->icon = 'far fa-bookmark';
-				$defaultDash->default = true;
-				$defaultDash->save();
-				$dashboards[] = $defaultDash;
-
-				foreach(array('profile','clock','log') as $i=>$widget){
-					$item = new DashboardWidget();
-					$item->model = $widget;
-					$item->position = $i;
-					$item->dashboard = $defaultDash->id;
-					$item->save();
-				}
-				
-			}
-			foreach($dashboards as $dashboard): ?>
-				<li class="dashboard-item" <?php echo $dashboard->default?'data-selected="1"':''; ?> data-id="<?php echo $dashboard->id; ?>"  title="<?php echo $dashboard->label; ?>"><span><?php echo $dashboard->label; ?></span><div><i class="fa <?php echo $dashboard->icon; ?>"></i></div></li>
-			<?php endforeach; ?>
-		</ul>
-		<div class="clear"></div>
-
-
-		<ul class="dashboard-widget-menu">
-		<li data-toggle="modal" data-target="#addWidgetModal">
-			<div title="Ajouter un widget"><i class="fas fa-plus"></i> <span>WIDGET</span></div>
-		</li>
-	</ul>
-	
-
-	<div class="row" id="dashboard">
-		<!-- MODEL WIDGET -->
-		<div class="col-md-4 widget" data-id="" style="display:none" >
-			<div class="widget_window">
-				<div class="widget_header">
-					<i class="fa fa-caret"></i> <span></span>
-					<ul class="widget_options"></ul>
-				</div>
-				<div class="widget_content"></div>
-				<div class="widget_footer"></div>
-			</div>
-		</div>
-
-	</div>
-		
-	<?php else: ?>
-		<div class="well"><h3>Bienvenue !</h3>
-			<blockquote>
-				<p>Merci de vous connecter pour acceder à l'ensemble des fonctionnalités.<br/> Vous pouvez vous connecter à tout moment via
-					le  formulaire de connexion en haut à droite du programme</p>
-					<small>HackBuddy</small>
-				</blockquote>
-			</div>
-		<?php endif; ?>
-
-		
-		<!-- Add wiget modal -->
-		<div class="modal fade" id="addWidgetModal" tabindex="-1" role="dialog" aria-labelledby="exampleModalLabel" aria-hidden="true">
-		  <div class="modal-dialog" role="document">
-		    <div class="modal-content">
-		      <div class="modal-header">
-		        <h5 class="modal-title" id="exampleModalLabel">Ajout d'un widget</h5>
-		        <button type="button" class="close" data-dismiss="modal" aria-label="Close">
-		          <span aria-hidden="true">&times;</span>
-		        </button>
-		      </div>
-		      <div class="modal-body">
-		       		<label for="widgetList">Sélectionnez le widget que vous souhaitez ajouter</label>
-						<div class="clearFix">
-							<?php
-								$models = array();
-								Plugin::callHook('widget',array(&$models));
-							?>
-							<select id="widgetList" class="left" size="10">
-								<option calue=""> - </option>
-								<?php
-								foreach($models as $model): ?>
-								<option data-description="<?php echo $model->description; ?>" data-background="<?php echo $model->background; ?>" data-icon="<?php echo $model->icon; ?>" value="<?php echo $model->model; ?>"><?php echo $model->title; ?></option>
-							<?php endforeach; ?>
-						</select>
-						<div class="widgetDescription hidden">
-							<h1>
-								<i class=''></i> <span></span>
-							</h1>
-								<p></p> 
-								<span class="text-muted">Couleur: </span>
-								<div class='widgetColor' >
-									<small style='background-color:"+option.background+"'></small><span></span>
-								</div>
-						</div>
-					</div>
-		      </div>
-		      <div class="modal-footer">
-		        <button type="button" class="btn btn-default" data-dismiss="modal">Fermer</button>
-				<button type="button" class="btn btn-primary" onclick="dashboard_dashboardwidget_add();">Ajouter</button>
-		      </div>
-		    </div>
-		  </div>
-		</div>
-
-
-		
-
-	<!-- Configure wiget modal -->
-	<div class="modal fade" id="configureWidgetModal" tabindex="-1" role="dialog" aria-labelledby="exampleModalLabel" aria-hidden="true">
-	  <div class="modal-dialog" role="document">
-	    <div class="modal-content">
-	      <div class="modal-header">
-	        <h5 class="modal-title" id="exampleModalLabel">Configuration d'un widget</h5>
-	        <button type="button" class="close" data-dismiss="modal" aria-label="Close">
-	          <span aria-hidden="true">&times;</span>
-	        </button>
-	      </div>
-	      <div class="modal-body">
-	        <div class="pluginContent">
-						<!-- Configuration plugin ici -->
-			</div>
-	      </div>
-	      <div class="modal-footer">
-	       	<button type="button" class="btn btn-default" data-dismiss="modal">Fermer</button>
-			<button type="button" class="btn btn-primary" onclick="dashboard_dashboardwidget_save_configuration();">Ajouter</button>
-	      </div>
-	    </div>
-	  </div>
-	</div>
-
-
-
-	
-	
-</div> 

+ 0 - 75
plugin/dashboard/setting.dashboard.php

@@ -1,75 +0,0 @@
-<?php
-global $myUser;
-if(!$myUser->connected()) throw new Exception("Vous devez être connecté pour accéder à cette fonctionnalité",401);
-if(!$myUser->can('dashboard','configure')) throw new Exception("Vous n'avez pas la permission pour executer cette fonctionnalité",403);
-require_once(__DIR__.SLASH.'Dashboard.class.php');
-?>
-
-
-<div class="row">
-    <div class="col-md-12">
-          <h3><i class="fas fa-wrench"></i> Réglages Dashboard
-                <?php if($myUser->can('dashboard', 'edit')) : ?>
-        <div onclick="dashboard_setting_save();" class="btn btn-success right"><i class="fas fa-check"></i> Enregistrer</div>
-        <?php endif; ?>
-          </h3>
-        <hr/>
-    </div>
-</div>
-<p>Veuillez remplir les informations ci dessous.</p>
-
-<!-- <div class="row">
-    
-    <div class="col-xl-12"> -->
-          <?php /*echo Configuration::html('dashboard');*/ ?>
-<!--     </div>
-</div> -->
-<div class="row">
-    <!-- search results -->
-    <div class="col-xl-12">
-
-        <h3>Pages de dashboard</h3>
-        <table id="dashboards" class="table table-striped " data-entity-search="dashboard_dashboard_search">
-            <thead>
-                <tr>
-                    <th data-sortable="label">Libellé</th>
-                    <th data-sortable="icon">Icône</th>
-                    <th data-sortable="default">Par défaut</th>
-                    <th></th>
-                </tr>
-            </thead>
-            
-            <thead>
-                <tr id="dashboard-form" data-action="dashboard_dashboard_save" data-id="">
-                    <th><input id="label" name="label" class="form-control" placeholder="" value="" type="text"></th>
-                    <th><input id="icon" name="icon" data-type="icon" class="form-control" placeholder="" value="" type="text"></th>
-                    <th><input id="default" name="default" class="form-control" data-type="checkbox" type="checkbox"></th>
-                    <th><div onclick="dashboard_dashboard_save();" class="btn btn-success"><i class="fas fa-check"></i> Enregistrer</div></th>
-                </tr>
-            </thead>
-            
-            <tbody>
-                <tr data-id="{{id}}" class="hidden">
-                    <td>{{label}}</td>
-                    <td><i class="{{icon}}"></i> {{icon}}</td>
-                    <td>{{#default}}<i class="fas fa-check text-success"></i>{{/default}}{{^default}}<i class="fas fa-times text-danger"></i>{{/default}}</td>
-                    <td>
-                        <div class="btn-group btn-group-sm" role="group">
-                            <div class="btn btn-info " onclick="dashboard_dashboard_edit(this);"><i class="fas fa-pencil-alt"></i></div>
-                            <div class="btn btn-danger " onclick="dashboard_dashboard_delete(this);"><i class="far fa-trash-alt"></i></div>
-                        </div>
-                    </td>
-                </tr>
-           </tbody>
-        </table>
-
-         <!-- Pagination -->
-       
-        <ul class="pagination">
-            <li class="page-item hidden" data-value="{{value}}" title="Voir la page {{label}}" onclick="$(this).parent().find('li').removeClass('active');$(this).addClass('active');dashboard_dashboard_search();">
-                <a class="page-link" href="#">{{label}}</a>
-            </li>
-        </ul>
-
-    </div>
-</div>

+ 1 - 1
plugin/document/WebDav.class.php

@@ -527,7 +527,7 @@ class WebDav{
 			unset($locks[base64_encode($file)]);
 		}else{
 			//Si les properties du verrou sont renseigné, on le save
-			$properties['token'] = isset($properties['token']) && $properties['token'] != '' ? $properties['token'] : 'fr.sys1:'.sha1($file);
+			$properties['token'] = isset($properties['token']) && $properties['token'] != '' ? $properties['token'] : 'fr.idleman:'.sha1($file);
 			$locks[base64_encode($file)] = $properties;
 		}
 

+ 0 - 1
plugin/document/action.php

@@ -158,7 +158,6 @@ switch($_['action']){
 
 				$response['rows'] = array();
 				$scanned = Element::root().$folder.SLASH.'*';
-				
 				//L'ui ne traite que les / quel que soit l'os
 				foreach (Element::browse($scanned) as $line) {
 					$line->path = str_replace('\\', '/', $line->path);

+ 2 - 2
plugin/document/app.json

@@ -1,11 +1,11 @@
 {
-	"id": "fr.sys1.document",
+	"id": "fr.idleman.document",
 	"name": "Documents",
 	"author" : {
 		"name" : "Valentin Carruesco"
 	},
 	"version": "2.0",
-	"url": "http://sys1.fr",
+	"url": "http://idleman.fr",
 	"licence": {"name": "Copyright","url" : ""},
 	"description": "Gestion Électronique de Documents (GED) partagée sur l'ERP",
 	"require" : {}

+ 2 - 2
plugin/document/document.plugin.php

@@ -27,7 +27,7 @@ function document_page(){
 
 //Fonction executée lors de l'activation du plugin
 function document_install($id){
-	if($id != 'fr.sys1.document') return;
+	if($id != 'fr.idleman.document') return;
 	Entity::install(__DIR__);
 	if(!file_exists(Element::root()))
 		mkdir(Element::root(),0755,true);
@@ -42,7 +42,7 @@ function document_install($id){
 
 //Fonction executée lors de la désactivation du plugin
 function document_uninstall($id){
-	if($id != 'fr.sys1.document') return;
+	if($id != 'fr.idleman.document') return;
 	Entity::uninstall(__DIR__);
 }
 

+ 3 - 2
plugin/example/Contact.class.php

@@ -1,7 +1,7 @@
 <?php
 
 class Contact extends Entity{
-	public $id,$label,$phone,$birth,$hour,$author,$address,$vehicle,$story,$login,$password,$icon;
+	public $id,$label,$phone,$birth,$hour,$author,$address,$vehicle,$story,$login,$password,$icon,$mycheckbox1;
 	protected $TABLE_NAME = 'plugin_contact';
 	public $fields =
 	array(
@@ -16,7 +16,8 @@ class Contact extends Entity{
 		'story' => 'longstring',
 		'login' => 'string',
 		'password' => 'string',
-		'icon'=>'string'
+		'icon'=>'string',
+		'mycheckbox1'=>'boolean'
 	);
 	
 	function picture(){

+ 27 - 21
plugin/example/action.php

@@ -8,9 +8,9 @@ switch($_['action']){
 	case 'contact_save':
 		Action::write(function(&$response){
 			global $myUser,$_;
-			if(!$myUser->can('example','edit')) throw new Exception("Permissions insuffisantes",403);
+			User::check_access('example','edit');
 			require_once(__DIR__.SLASH.'Contact.class.php');
-			require_once(__ROOT__.PLUGIN_PATH.'notification'.SLASH.'Notification.class.php');
+			require_once(PLUGIN_PATH.'notification'.SLASH.'Notification.class.php');
 
 			$contact = Contact::provide();
 			//on garde l'ancien objet a l'instant t pour le log comparatif (voir en fin d'action)
@@ -43,15 +43,14 @@ switch($_['action']){
 				Image::toJpg($logo['absolute']);
 			}
 
-			Plugin::callHook('emit_notification', array(
-				array(
+			// GESTION ENVOI NOTIFICATION
+			Plugin::callHook('emit_notification',array(array(
 					'label' => $title,
 					'html' => $msg,
-					'meta' => array(
-						'link' =>  ROOT_URL.'/index.php?module=example&page=sheet&id='.$contact->id
-					)
-				),
-				array($myUser->login)
+					'type' => "notice",
+					'meta' => array('link' => ROOT_URL.'/index.php?module=example&page=sheet&id='.$contact->id),
+					'recipients' => array($myUser->login) // recipients contient login
+				)
 			));
 
 			$response['id'] = $contact->id;
@@ -70,7 +69,7 @@ switch($_['action']){
 
 	Action::write(function(&$response){
 		global $myUser,$_;
-		if(!$myUser->can('example','read')) throw new Exception("Permissions insuffisantes",403);
+		User::check_access('example','read');
 		require_once(__DIR__.SLASH.'Contact.class.php');
 
 		$query = 'SELECT * FROM {{table}} WHERE 1';
@@ -85,13 +84,13 @@ switch($_['action']){
 		}
 
 		//Recherche avancée
-		if(isset($_['filters']['advanced'])) filter_secure_query($_['filters']['advanced'],array('label','phone','birth','author','vehicle'),$query,$data);
-		
+		if(isset($_['filters']['advanced'])) filter_secure_query($_['filters']['advanced'],array('label','phone','birth','author','vehicle', 'mycheckbox1'),$query,$data);
+
 		//Tri des colonnes
 		if(isset($_['sort'])) sort_secure_query($_['sort'],array('label','phone'),$query,$data);
 
 		//Pagination
-		$response['pagination'] = Contact::paginate(2,(!empty($_['page'])?$_['page']:0),$query,$data);
+		$response['pagination'] = Contact::paginate(20,(!empty($_['page'])?$_['page']:0),$query,$data);
 		
 		//Mise en forme des résultats
 		foreach (Contact::staticQuery($query,$data,true) as $contact) {
@@ -117,7 +116,14 @@ switch($_['action']){
 
 			$row['birth'] = date('d/m/Y',$contact->birth);			
 			$row['picture'] = $contact->picture().'&v='.time();
-			$response['rows'][]= $row;
+			$response['rows'][] = $row;
+		}
+
+		/* Mode export */
+		if($_['export'] == 'true'){
+		    $stream = Excel::exportArray($response['rows'],null,'Export');
+		    File::downloadStream($stream,'export-'.date('d-m-Y').'.xlsx');
+		    exit();
 		}
 	});
 	break;
@@ -126,7 +132,7 @@ switch($_['action']){
 	case 'contact_delete':
 	Action::write(function(&$response){
 		global $myUser,$_;
-		if(!$myUser->can('example','delete')) throw new Exception("Permissions insuffisantes",403);
+		User::check_access('example','delete');
 		require_once(__DIR__.SLASH.'Contact.class.php');
 		if(!isset($_['id']) || !is_numeric($_['id'])) throw new Exception("Id non spécifié ou non numerique");
 		
@@ -141,7 +147,7 @@ switch($_['action']){
 	case 'contact_delete_document':
 	Action::write(function(&$response){
 		global $myUser,$_;
-		if(!$myUser->can('example','delete')) throw new Exception("Permissions insuffisantes",403);
+		User::check_access('example','delete');
 		require_once(__DIR__.SLASH.'Contact.class.php');
 		if(!isset($_['path']) ) throw new Exception("Chemin non spécifié ou non numerique");
 		//Le premier argument est un namspace de sécurité 
@@ -154,7 +160,7 @@ switch($_['action']){
 	case 'contact_add_document':
 	Action::write(function(&$response){
 		global $myUser,$_;
-		if(!$myUser->can('example','edit')) throw new Exception("Permissions insuffisantes",403);
+		User::check_access('example','edit');
 		require_once(__DIR__.SLASH.'Contact.class.php');
 
 		$contact = Contact::provide();
@@ -174,7 +180,7 @@ switch($_['action']){
 	//Téléchargement des documents
 	case 'contact_download_document':
 		global $myUser,$_;
-		if(!$myUser->can('example','read')) throw new Exception("Permissions insuffisantes",403);
+		User::check_access('example','read');
 		$path = (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') ? utf8_decode($_['path']) : $_['path'];
 		File::downloadFile(File::dir().'contact'.SLASH.'documents'.SLASH.$path);
 	break;
@@ -182,7 +188,7 @@ switch($_['action']){
 	//Affichage de l'avatar
 	case 'contact_download_picture':
 		global $myUser,$_;
-		if(!$myUser->can('example','read')) throw new Exception("Permissions insuffisantes",403);
+		User::check_access('example','read');
 		try{
 			File::downloadFile(File::dir().'contact'.SLASH.$_['contact'].'.'.$_['extension']);
 		} catch(Exception $e){
@@ -194,7 +200,7 @@ switch($_['action']){
 	case 'contact_avatar_delete':
 	    Action::write(function(&$response){
 	        global $myUser,$_;
-	        if(!$myUser->can('example','edit')) throw new Exception("Permissions insuffisantes",403);
+	        User::check_access('example','edit');
 			require_once(__DIR__.SLASH.'Contact.class.php');
 	        $item = Contact::provide();
 	        if(!$item) throw new Exception("Aucun contact ne correspond en base");
@@ -213,7 +219,7 @@ switch($_['action']){
 	case 'example_contact_card':
 		Action::write(function(&$response){
 			global $myUser,$myFirm,$_;
-			if(!$myUser->can('example','read')) throw new Exception("Permissions insuffisantes",403);
+			User::check_access('example','read');
 			require_once(__DIR__.SLASH.'Contact.class.php');
 
 			$contact = Contact::provide();

+ 7 - 2
plugin/example/example.plugin.php

@@ -69,8 +69,11 @@ function example_content_setting(){
 }
 
 //Déclation des assets
-Plugin::addCss("/css/main.css?v=1"); 
-Plugin::addJs("/js/main.js?v=1"); 
+
+
+Plugin::addJs("/js/document.api.js");
+Plugin::addCss("/css/main.css"); 
+Plugin::addJs("/js/main.js");
 
 //Mapping hook / fonctions
 Plugin::addHook("install", "example_install");
@@ -82,6 +85,8 @@ Plugin::addHook("action", "example_action");
 Plugin::addHook("menu_setting", "example_menu_setting");    
 Plugin::addHook("content_setting", "example_content_setting");    
 
+
+
 global $myFirm;
 if($myFirm->has_plugin('fr.idleman.export')){
 	require_once(__ROOT__.PLUGIN_PATH.'export'.SLASH.'ExportModel.class.php');

+ 32 - 2
plugin/example/js/main.js

@@ -11,6 +11,16 @@ function init_plugin_example(){
 	});
 }
 
+function init_setting_example(){
+	
+	var doc = new DocumentApi('#example-doc');
+
+	doc.load();
+
+
+
+}
+
 /**
  * 
  * QUICKFORM
@@ -26,11 +36,31 @@ function contact_submit_quickform(){
 }
 
 //GESTION CONTACT
-function contact_search(){
+function contact_search(callback, exportMode){
 	$('#contacts').fill({
 		action:"contact_search",
 		filters : $('#filters').filters(),
-		sort : $('#contacts').sortable_table('get')
+		//Gestion du tri par colonnes de tableau (optionnel)
+		sort : $('#contacts').sortable_table('get'),
+		//Activation de l'export excel (optionnel)
+		export : !exportMode ? false : exportMode,
+		//exemple d'affichage de ligne personnalisé (optionnel)
+		showing : function(item,i){
+			item.css({
+				transform:'translateX(-200px)',
+				transition:'all 0.2s ease-in-out',
+				opacity : 0
+			}).removeClass('hidden');
+			setTimeout(function(){
+				item.css({
+					transform:'translateX(0px)',
+					opacity : 1
+				})
+			},(i+1)*20);
+		}
+	},function(response){
+		$('.results-count span').text(response.pagination.total);
+		if(callback!=null) callback();
 	});
 }
 

+ 8 - 8
plugin/example/page.list.php

@@ -1,8 +1,8 @@
 <?php
 global $myFirm;
-if(!$myUser->can('example','read')) throw new Exception("Vous n'avez pas la permission pour executer cette fonctionnalité",403);
+User::check_access('example','read');
 
-require_once('Contact.class.php');
+require_once(__DIR__.SLASH.'Contact.class.php');
 ?>
 
 <div class="row">
@@ -24,6 +24,7 @@ require_once('Contact.class.php');
             <option value="vehicle" data-filter-type="dictionnary" data-slug="vehicles" data-depth="2" data-disable-label>Véhicule</option>
             <option value="customList" data-filter-type="dictionnary" data-filter-source='<?php echo json_encode(array('c1'=>'Valeur 1','c2'=>'valeur 2')); ?>'>Ma liste custom</option>
             <option value="myNumber"  data-filter-type="number">Un nombre</option>
+            <option value="mycheckbox1"  data-filter-type="boolean">Ma checkbox 1</option>
         </select>
     </div>
 
@@ -33,14 +34,14 @@ require_once('Contact.class.php');
 		<?php endif; ?>
         
         <?php if($myUser->can('export', 'read') && $myFirm->has_plugin('fr.idleman.export')) : ?>
-        <div style="display: inline-block;" class="right mr-2" data-type="export_model" data-callback="contact_export_callback" data-parameters='<?php echo stripslashes(json_encode(array("plugin"=>"example","dataset"=>"contact-list"))); ?>'>
+        <div class="right mr-2 d-inline-block" data-type="export-model" data-default="testouille" data-callback="contact_export_callback" data-parameters='<?php echo stripslashes(json_encode(array("plugin"=>"example","dataset"=>"contact-list"))); ?>'>
             <div class="btn btn-primary"><i class="far fa-file"></i> Export modèle</div>
         </div>
         <?php endif; ?>
 	</div>
 </div>
 <br/>
-
+<h4 class="results-count"><span></span> Résultat(s) <div class="btn btn-dark btn-small" onclick="contact_search(null,true)"><i class="far fa-file-excel"></i> Exporter</div></h4>
 <div class="row">
 	<div class="col-xl-12">
 
@@ -77,11 +78,10 @@ require_once('Contact.class.php');
            </tbody>
         </table>
       
-      <!-- Pagination -->
-       
-        <ul class="pagination justify-content-center">
+        <!-- Pagination (data-range définit le nombre de pages max affichées avant et après la page courante) -->
+        <ul class="pagination justify-content-center" data-range="3">
             <li class="page-item hidden" data-value="{{value}}" title="Voir la page {{label}}" onclick="$(this).parent().find('li').removeClass('active');$(this).addClass('active');contact_search()">
-                <a class="page-link" href="#">{{label}}</a>
+                <span class="page-link">{{label}}</span>
             </li>
         </ul>
 

+ 38 - 10
plugin/example/page.sheet.php

@@ -1,7 +1,7 @@
 <?php
 global $myFirm;
-if(!$myUser->can('example','read')) throw new Exception("Vous n'avez pas la permission pour executer cette fonctionnalité",403);
-require_once('Contact.class.php');
+User::check_access('example','read');
+require_once(__DIR__.SLASH.'Contact.class.php');
 $contact = Contact::provide();
 
 ?>
@@ -32,7 +32,7 @@ $contact = Contact::provide();
 			<br>
 			<label for="author">Auteur</label>
 			<small class="text-muted" data-placement="right" data-tooltip="* data-multiple			:	Autorise l'utilisateur a sélectionner  des entité multiples" data-tooltip="* data-types (default: user) : définis les entité sélectionnables (ex: user,rank)	"> (Input data-type="user")</small>
-			<input type="text" data-type="user" data-multiple  data-types="user,rank" value="<?php echo $contact->author; ?>" placeholder="Auteur du contact" class="form-control" id="author" name="author"/>
+			<input type="text" data-type="user" data-multiple data-types="user,rank" value="<?php echo $contact->author; ?>" placeholder="Auteur du contact" class="form-control" id="author" name="author"/>
 			<br>
 			<label for="picture">Image</label>
 			<small class="text-muted"> (Input data-type="image")</small>
@@ -51,17 +51,38 @@ $contact = Contact::provide();
 			<select data-type="dictionnary" data-slug="vehicles" data-depth="3" data-value="<?php echo $contact->vehicle; ?>" class="form-control select-control" name="vehicle" id="vehicle"></select>
 
 
-
 			<label for="vehicle2">Véhicule (tags)</label>
 			<small class="text-muted" data-placement="right" data-tooltip="* data-multiple			:	Autorise l'utilisateur a sélectionner  des tag multiples
-* data-slug 			:	le slug de la liste mère à afficher">(Select data-type="dictionnary")</small>
+* data-slug 			:	le slug de la liste mère à afficher">(Select data-type="tag-list")</small>
 			<input type="text" data-type="tag-list" data-slug="vehicles" class="form-control" />
 
+			<label for="vehicle2">Catégories</label>
+			<small class="text-muted" data-placement="right" data-tooltip="* data-tags	(json)	:tags disponibles (eg. 
+
+			{
+				'bug':{'icon':'fas fa-bug','label':'Bug','color':'#cb2431'},
+				'feature':{'icon':'fas fa-check','label':'Demande','color':'#2cbe4e'}
+			}
+
+			)">(Select data-type="tagcloud")</small>
+			<input type="text" data-type="tagcloud" data-tags='{"bug":{"icon":"fas fa-bug","label":"Bug","color":"#cb2431"},"feature":{"icon":"fas fa-check","label":"Demande","color":"#2cbe4e"},"question":{"icon":"far fa-question-circle","label":"Question","color":"#00BCD4"}}' class="form-control"  value="bug,question"/>
+
 
 			<label for="country">Pays</label>
 			<small class="text-muted">(Select data-type="dictionnary")</small>
 			<select data-type="dictionnary" data-slug="countries" data-depth="3" data-id="country" data-value="" class="form-control select-control" name="country" id="country"></select>
 			<br>
+
+
+			<label for="country">Etat</label>
+			<small data-tooltip="* data-icon : l'icone affichée près du label (optionnel)" class="text-muted">(Select data-type="dropdown-select")</small><br/>
+			<select data-type="dropdown-select" id="state">
+            	<option selected="selected" value="active" style="background-color:#2cbe4e;color:#ffffff;" data-icon="far fa-check-circle">Actif</option>
+            	<option value="inactive" style="background-color:#c90000;color:#ffffff;" data-icon="far fa-times-circle">Inactif</option>
+            </select><br/>
+
+
+
 			<label for="address">Adresse</label>
 			<small class="text-muted"> (Input data-type="location")</small>
 			<input type="text" data-type="location" data-input-city="#city"  data-input-latitude="#latitude"  value="<?php echo $contact->address; ?>" class="form-control" id="address" name="address"/>
@@ -109,11 +130,16 @@ $contact = Contact::provide();
 			<small class="text-muted"> (canvas data-type="bar")</small>
 			<canvas data-type="bar" data-values="[2,3,4,7]" data-labels='["fib","on","acci"]' data-colors='["#f39c12","#2980b9","#27ae60"]'>Graphique example</canvas>
 			<br>
-			<div>
+			<div id="checkboxes">
 				<label>Input checkbox</label>
 				<small class="text-muted"> (Input data-type="checkbox")</small><br>
-				<input type="checkbox" id="checkbox" name="checkbox" data-class="blink_me" data-type="checkbox">
-				<label for="checkbox">Ma checkbox 1</label>
+				<input type="checkbox" id="mycheckbox1" name="mycheckbox1" data-class="blink_me" data-type="checkbox"><label for="mycheckbox1">Ma checkbox 1 (avec id, non encapsulée)</label><br>
+				<label for="checkbox_2"><input type="checkbox" id="checkbox_2" name="checkbox" data-class="" data-type="checkbox">Ma checkbox 2 (avec id, encapsulée)</label><br>
+				<label><input type="checkbox" name="checkbox" data-class="" data-type="checkbox">Ma checkbox 3 (sans id, encapsulée)</label><br>
+				<input type="checkbox" name="checkbox" data-class="" data-type="checkbox"><label>Ma checkbox 4 (sans id, non encapsulée)</label><br>
+				<input type="checkbox" id="checkbox_5" name="checkbox" data-class="" data-type="checkbox" disabled><label for="checkbox_5">Ma checkbox 5 (avec id, disabled)</label><br>
+				<label><input type="checkbox" name="checkbox" data-class="" data-type="checkbox" disabled>Ma checkbox 6 (sans id, disabled)</label><br>
+				<input type="checkbox" id="checkbox_7" name="checkbox" data-class="" data-type="checkbox" disabled checked><label for="checkbox_7">Ma checkbox 7 (disabled, checked)</label><br>
 			</div>
 			<br>
 			<div>
@@ -122,7 +148,9 @@ $contact = Contact::provide();
 				<input type="radio" id="case1" name="my-radio-button" data-type="radio" data-label="Hello"><br>
 				<label><input type="radio" id="case2" name="my-radio-button" data-type="radio">Toto</label><br>
 				<input type="radio" id="case3" name="my-radio-button" data-type="radio"><label for="case3">Coucou</label><br>
-				<input type="radio" name="my-radio-button" data-type="radio"><label>Sans id</label>
+				<input type="radio" id="case4" name="my-radio-button" data-type="radio" disabled><label for="case4">Disabled avec id</label><br>
+				<input type="radio" name="my-radio-button" data-type="radio"><label>Sans id</label><br>
+				<input type="radio" name="my-radio-button" data-type="radio" disabled><label>Disabled sans id</label>
 			</div>
 			<br>
 			<label for="label">Tableau d'une liste</label>
@@ -140,7 +168,7 @@ $contact = Contact::provide();
 		<div class="col-md-12 text-center noLabel">
 			<div class="btn btn-success" onclick="contact_save();"><i class="fas fa-check"></i> Enregistrer</div>
        		<?php if($myUser->can('export', 'read') && $myFirm->has_plugin('fr.idleman.export')) : ?>
-			<div style="display: inline-block;" class="offer-btn-export" data-type="export_model" data-callback="contact_export_callback" data-parameters='<?php echo stripslashes(json_encode(array("plugin"=>"example","dataset"=>"contact-sheet","id"=>$contact->id,"destination"=>addslashes('contact'.SLASH.'documents'.SLASH.$contact->id.SLASH)))); ?>'>
+			<div class="d-inline-block" data-type="export-model" data-callback="contact_export_callback" data-parameters='<?php echo stripslashes(json_encode(array("plugin"=>"example","dataset"=>"contact-sheet","id"=>$contact->id,"destination"=>addslashes('contact'.SLASH.'documents'.SLASH.$contact->id.SLASH)))); ?>'>
 				<div class="btn btn-primary"><i class="far fa-file"></i> Export modèle</div>
 			</div>
        		<?php endif; ?>

+ 7 - 5
plugin/example/setting.example.php

@@ -1,12 +1,14 @@
 <?php 
 global $myUser;
-if(!$myUser->can('example','configure')) throw new Exception("Permissions insuffisantes",403);
+User::check_access('example','configure');
 ?>
 <div class="row">
 	<div class="col-md-12">
-		<br>
-		<h3>Réglages Example</h3>
-		<hr/>
-		<p>Rien à configurer ici...</p>
+		
+
+		<div id="example-doc">Chargement en cours</div>
+
+
+
 	</div>
 </div>

+ 2 - 1
plugin/export/ExportModel.class.php

@@ -6,7 +6,7 @@
  * @license copyright
  */
 class ExportModel extends Entity{
-	public $id,$label,$description,$plugin,$dataset,$filename,$privacy,$export_format;
+	public $id,$label,$description,$plugin,$dataset,$slug,$filename,$privacy,$export_format;
 	protected $TABLE_NAME = 'export_model';
 	public $fields =
 	array(
@@ -15,6 +15,7 @@ class ExportModel extends Entity{
 		'description' => 'longstring',
 		'plugin' => 'string',
 		'dataset' => 'string',
+		'slug' => 'string',
 		'filename' => 'string',
 		'privacy' => 'string',
 		'export_format' => 'string'

+ 16 - 14
plugin/export/action.php

@@ -7,7 +7,7 @@ switch($_['action']){
 	case 'export_exportmodel_search':
 		Action::write(function(&$response){
 			global $myUser,$_;
-			if(!$myUser->can('export','read')) throw new Exception("Permissions insuffisantes",403);
+			User::check_access('export','read');
 			require_once(__DIR__.SLASH.'ExportModel.class.php');
 
 			if(isset($_['params']) && !empty($_['params'])){
@@ -60,11 +60,11 @@ switch($_['action']){
 				}
 
 				//Recherche avancée
-				if(isset($_['filters']['advanced'])) filter_secure_query($_['filters']['advanced'],array('label','description','filename','plugin','privacy'),$query,$data);
+				if(isset($_['filters']['advanced'])) filter_secure_query($_['filters']['advanced'],array('label','description','slug','filename','plugin','privacy'),$query,$data);
 
 				//Tri des colonnes
 				if(isset($_['sort']))
-					sort_secure_query($_['sort'],array('label','description','filename','plugin'),$query,$data);
+					sort_secure_query($_['sort'],array('label','description','slug','filename','plugin'),$query,$data);
 				else 
 					$query .= ' ORDER BY plugin ASC, created DESC';
 
@@ -88,7 +88,7 @@ switch($_['action']){
 	case 'export_exportmodel_save':
 		Action::write(function(&$response){
 			global $myUser,$_;
-			if(!$myUser->can('export','edit')) throw new Exception("Permissions insuffisantes",403);
+			User::check_access('export','edit');
 			require_once(__DIR__.SLASH.'ExportModel.class.php');
 
 			if(not_set_empty($_['label'])) throw new Exception('Champ "Libellé" obligatoire');
@@ -97,6 +97,7 @@ switch($_['action']){
 			if(not_set_empty($_['dataset'])) throw new Exception('Champ "Jeu de données" obligatoire');
 
 			$item = ExportModel::provide();
+			if(isset($_['slug']) && !empty($_['slug']) && ExportModel::load(array('slug'=>$_['slug'], 'id:!='=>$item->id))) throw new Exception("Le slug (".slugify($_['slug']).") est déjà utilisé pour un autre export modèle");
 			$newItem = isset($item->id) && !empty($item->id) ? false : true;
 			$docs = $item->documents();
 			//Ajout des fichiers joints
@@ -106,6 +107,7 @@ switch($_['action']){
 			$item->description = $_['description'];
 			$item->plugin = $_['plugin'];
 			$item->dataset = $_['dataset'];
+			$item->slug = slugify($_['slug']);
 			$item->privacy = $_['privacy']==1 ? ExportModel::PRIVACY_PRIVATE : ExportModel::PRIVACY_PUBLIC;
 			$item->save();
 
@@ -149,7 +151,7 @@ switch($_['action']){
 	case 'export_exportmodel_edit':
 		Action::write(function(&$response){
 			global $myUser,$_;
-			if(!$myUser->can('export','edit')) throw new Exception("Permissions insuffisantes",403);
+			User::check_access('export','edit');
 			require_once(__DIR__.SLASH.'ExportModel.class.php');
 			$response = ExportModel::getById($_['id']);
 		});
@@ -159,7 +161,7 @@ switch($_['action']){
 	case 'export_exportmodel_delete':
 		Action::write(function(&$response){
 			global $myUser,$_;
-			if(!$myUser->can('export','delete')) throw new Exception("Permissions insuffisantes",403);
+			User::check_access('export','delete');
 			require_once(__DIR__.SLASH.'ExportModel.class.php');
 			
 			$item = ExportModel::getById($_['id']);
@@ -184,7 +186,7 @@ switch($_['action']){
 	case 'export_exportmodel_add_document':
 		Action::write(function(&$response){
 			global $myUser,$_;
-			if(!$myUser->can('export','edit')) throw new Exception("Permissions insuffisantes",403);
+			User::check_access('export','edit');
 			require_once(__DIR__.SLASH.'ExportModel.class.php');
 
 			$exportmodel = ExportModel::provide();
@@ -224,7 +226,7 @@ switch($_['action']){
 	case 'export_exportmodel_delete_document':
 		Action::write(function(&$response){
 			global $myUser,$_;
-			if(!$myUser->can('export','delete')) throw new Exception("Permissions insuffisantes",403);
+			User::check_access('export','delete');
 			require_once(__DIR__.SLASH.'ExportModel.class.php');
 			if(!isset($_['path']) ) throw new Exception("Chemin non spécifié ou non numerique");
 			$path = (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') ? utf8_decode($_['path']) : $_['path'];
@@ -243,7 +245,7 @@ switch($_['action']){
 	//Téléchargement des documents
 	case 'export_exportmodel_download_document':
 		global $myUser,$_;
-		if(!$myUser->can('export','read')) throw new Exception("Permissions insuffisantes",403);
+		User::check_access('export','read');
 		$path = (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') ? utf8_decode($_['path']) : $_['path'];
 		File::downloadFile(File::dir().'export'.SLASH.'documents'.SLASH.$path);
 
@@ -253,7 +255,7 @@ switch($_['action']){
 	//Téléchargement des templates d'exemple
 	case 'export_exportmodel_download_template':
 		global $myUser,$_;
-		if(!$myUser->can('export','read')) throw new Exception("Permissions insuffisantes",403);
+		User::check_access('export','read');
 		if(!isset($_['extension']) || empty($_['extension'])) throw new Exception("Extension non précisée");
 		
 		$template = ExportModel::templates($_['extension']);
@@ -270,7 +272,7 @@ switch($_['action']){
 	case 'export_exportmodel_list_dataset':
 		Action::write(function(&$response){
 			global $myUser,$_;
-			if(!$myUser->can('export','read')) throw new Exception("Permissions insuffisantes",403);
+			User::check_access('export','read');
 			require_once(__DIR__.SLASH.'ExportModel.class.php');
 	
 			$params = isset($_['params']) ? $_['params'] : array();
@@ -286,7 +288,7 @@ switch($_['action']){
 	case 'export_exportmodel_get_dataset':
 		Action::write(function(&$response){
 			global $myUser,$_;
-			if(!$myUser->can('export','read')) throw new Exception("Permissions insuffisantes",403);
+			User::check_access('export','read');
 			require_once(__DIR__.SLASH.'ExportModel.class.php');
 	
 			$params = isset($_['params']) ? $_['params'] : array();
@@ -316,10 +318,10 @@ switch($_['action']){
 		try{
 			ob_start();
 			global $myUser,$_;
-			if(!$myUser->can('export','read')) throw new Exception("Permissions insuffisantes",403);
+			User::check_access('export','read');
 			require_once(__DIR__.SLASH.'ExportModel.class.php');
 
-			if(!isset($_['exportmodel']) || empty($_['exportmodel']) || base64_decode($_['exportmodel']) == 'none') throw new Exception("Il faut choisir un modèle d'export");
+			if(!isset($_['exportmodel']) || empty($_['exportmodel'])) throw new Exception("Il faut choisir un modèle d'export");
 
 			$params = isset($_['params']) ? json_decode(base64_decode($_['params']), true) : array();
 			$params['description'] = false;

+ 1 - 0
plugin/export/export.plugin.php

@@ -57,6 +57,7 @@ function export_content_setting(){
 //Déclation des assets
 Plugin::addCss("/css/main.css"); 
 Plugin::addJs("/js/main.js"); 
+Plugin::addJs("/js/component.js",true); 
 
 //Mapping hook / fonctions
 Plugin::addHook("install", "export_install");

+ 47 - 0
plugin/export/js/component.js

@@ -0,0 +1,47 @@
+//Initialisation du composant d'export modèle
+function init_components_export_model(input){
+	var cbLoaded = input.attr('data-callback') ? input.attr('data-callback') : '';
+	var cbParams = input.attr('data-callback-parameters') ? input.attr('data-callback-parameters').split(',') : [];
+	var parameters = JSON.parse(input.attr('data-parameters'));
+
+	$(document).ready(function(e){
+		input.on('click', function(e){
+			$.ajax({
+				type: 'GET',
+				url: 'plugin/export/modal.export.model.php',
+				async: true,
+				success : function(modal){
+					if(cbLoaded) window[cbLoaded].apply(null,cbParams);
+				}
+			}).done(function(modalContent){
+				if(!$('#export-modal').length)
+					$('body').append(modalContent);
+				var modal = $('#export-modal');
+				reset_inputs(modal);
+
+				$.action({
+					action: 'export_exportmodel_search',
+					params: parameters
+				}, function(r){
+					if(r.rows){
+						var defaultExport = input.attr('data-default');
+						var selectExport = $('#exportModel');
+						selectExport.find('option').remove();
+						$.each(r.rows,function(i, option){
+							var opt = $('<option value="'+option.id+'">'+option.label+' - '+option.description+'</option>');
+							if(defaultExport && defaultExport.length && option.slug === defaultExport) opt.attr('selected', true);
+							selectExport.append(opt);
+						});
+					}
+					$('#exportmodel-form').attr('data-parameters', JSON.stringify(parameters));
+					if($('#exportModel > option').length == 1){
+						export_exportmodel_export();
+						return;
+					}
+					init_components($('#export-modal'));
+					modal.modal('show');
+				});
+			});
+		});
+	});
+}

+ 3 - 49
plugin/export/js/main.js

@@ -21,46 +21,7 @@ function init_setting_export(parameter){
 		onSort : export_exportmodel_search
 	});
 }
-//Initialisation du composant d'export modèle
-function init_components_export_model(input){
-	var cbLoaded = input.attr('data-callback') ? input.attr('data-callback') : '';
-	var cbParams = input.attr('data-params') ? input.attr('data-params').split(',') : [];
-	var parameters = JSON.parse(input.attr('data-parameters'));
 
-	$(document).ready(function(e){
-		input.on('click', function(e){
-			$.ajax({
-				type: 'GET',
-				url: 'plugin/export/modal.export.model.php',
-				async: true,
-				success : function(modal){
-					if(cbLoaded) window[cbLoaded].apply(null,cbParams);
-				}
-			}).done(function(modalContent){
-				if(!$('#export-modal').length)
-					$('body').append(modalContent);
-				var modal = $('#export-modal');
-				reset_inputs(modal);
-
-				$.action({
-					action: 'export_exportmodel_search',
-					params: parameters
-				}, function(r){
-					if(r.rows){
-						var selectExport = $('#exportModel');
-						selectExport.find('option:not([value="none"])').remove();
-						$.each(r.rows,function(i, option){
-							selectExport.append('<option value="'+option.id+'">'+option.label+' - '+option.description+'</option>');
-						});
-					}
-					modal.modal('show');
-					init_components($('#export-modal'));
-					$('#exportmodel-form').attr('data-parameters', JSON.stringify(parameters));
-				});
-			});
-		});
-	});
-}
 
 /* EXPORTMODELE */
 //Récuperation d'une liste de exportmodel dans le tableau #exportmodels
@@ -83,7 +44,7 @@ function export_exportmodel_save(){
 		$('#exportmodel-form [data-type="dropzone"] ul > li').attr('data-path', r.relativePath);
 		$('#exportmodel-form [data-type="dropzone"] ul > li > a').attr('href', r.filePath);
 		$('#exportmodel-form [data-type="dropzone"] ul > li > i.fa-times').attr('onclick', 'export_exportmodel_delete_document(this)');
-		$('.options-box').load(document.URL +  ' .options-box');
+		$('.options-box').load(document.URL +  ' .options-box>*');
 		$.message('success','Export modèle enregistré');
 	});
 }
@@ -183,7 +144,7 @@ function export_exportmodel_list_dataset(){
 function export_exportmodel_get_dataset(element){
 	var container = $('#dataset-container');
 	container.addClass('hidden');
-	$('#empty-files').show();
+	$('#empty-files').removeClass('hidden');
 	$('li:not(.template)', container).remove();
 
 	var datasetSelect = $(element);
@@ -237,7 +198,7 @@ function export_exportmodel_get_dataset(element){
 
 		if(r.files && r.files.length){
 			var exampleFiles = $('.dataset-example-files');
-			$('#empty-files').hide();
+			$('#empty-files').addClass('hidden');
 			var aTpl = $('li.hidden',exampleFiles).get(0).outerHTML;
 			$.each(r.files, function(i, file){
 				var a = $(Mustache.render(aTpl, file));
@@ -252,18 +213,11 @@ function export_exportmodel_get_dataset(element){
 function export_exportmodel_export(element){
 	if(isProcessing) return;
 	var exportmodelId = $('#exportModel').val();
-	if(exportmodelId=='none') {
-		$.message('error', 'Il faut choisir un modèle d\'export');
-		return;
-	}
 	var parameters = $('#exportmodel-form').attr('data-parameters');
 	var url = window.location.href;
 	var previousUrl = url.substring(url.lastIndexOf('/')+1);
 
 	isProcessing = true;
-	// overlay_preloader();
-	// toggle_preloader('#send-offer-form', true);
-
 	var exportUrl = 'action.php?action=export_exportmodel_export&exportmodel=';
 	window.location = exportUrl+btoa(exportmodelId)+'&params='+btoa(parameters)+'&url='+btoa(previousUrl);
 	isProcessing = false;

+ 1 - 3
plugin/export/modal.export.model.php

@@ -12,9 +12,7 @@
 					<div id="exportmodel-form" class="row exportmodel-form" data-action="export_exportmodel_export" data-id="">
 						<div class="col-md-12">
 							<h4>Choix du modèle d'export :</h4>
-							<select name="exportModel" id="exportModel" class="form-control">
-								<option value="none"> - </option>
-							</select>
+							<select name="exportModel" id="exportModel" class="form-control"></select>
 							<br>
 							<a href="index.php?module=export&page=sheet" target="_blank"><i class="fas fa-plus"></i> Créer un nouvel export modèle</a>
 						</div>

+ 8 - 3
plugin/export/page.sheet.php

@@ -1,6 +1,5 @@
 <?php 
-if(!$myUser->connected()) throw new Exception("Vous devez être connecté pour accéder à cette fonctionnalité",401);
-if(!$myUser->can('export','configure')) throw new Exception("Vous n'avez pas la permission pour exécuter cette fonctionnalité",403);
+User::check_access('export','configure');
 require_once(__DIR__.SLASH.'ExportModel.class.php');
 $exportmodel = ExportModel::provide();
 
@@ -16,10 +15,16 @@ $exportmodel = ExportModel::provide();
 		</div>
 		<div class="col-md-8">
 			<div class="row">
-				<div class="col-md-12">
+				<div class="<?php echo $myUser->superadmin ? 'col-md-8' : 'col-md-12'; ?>">
 					<label for="label">Libellé : </label>
 					<input required id="label" name="label" class="form-control" placeholder="Nom export modèle" value="<?php echo html_decode_utf8($exportmodel->label); ?>" type="text">
 				</div>
+				<?php if($myUser->superadmin): ?>
+				<div class="col-md-4">
+					<label for="label">Slug : </label>
+					<input required id="slug" name="slug" class="form-control" placeholder="Slug export modèle" value="<?php echo html_decode_utf8($exportmodel->slug); ?>" type="text">
+				</div>
+				<?php endif; ?>
 				<div class="col-md-12"><br>
 					<label for="description">Description : </label>
 					<input id="description" name="description" class="form-control" placeholder="Description export modèle" value="<?php echo html_decode_utf8($exportmodel->description); ?>" type="text">

+ 8 - 3
plugin/export/setting.export.php

@@ -1,7 +1,6 @@
 <?php 
 global $myUser;
-if(!$myUser->connected()) throw new Exception("Vous devez être connecté pour accéder à cette fonctionnalité",401);
-if(!$myUser->can('export','configure')) throw new Exception("Vous n'avez pas la permission pour exécuter cette fonctionnalité",403);
+User::check_access('export','configure');
 require_once(__DIR__.SLASH.'ExportModel.class.php');
 
 $plugins = Plugin::getAll(true);
@@ -38,6 +37,9 @@ foreach ($plugins as $plugin)
                 <tr>
                     <th data-sortable="id">#</th>
                     <th data-sortable="label">Libellé</th>
+                    <?php if($myUser->superadmin): ?>
+                    <th data-sortable="slug">Slug</th>
+                    <?php endif; ?>
                     <th data-sortable="description">Description</th>
                     <th data-sortable="plugin">Plugin</th>
                     <th style="width: 150px;">Jeu de données</th>
@@ -49,6 +51,9 @@ foreach ($plugins as $plugin)
                 <tr data-id="{{id}}" class="hidden">
                     <td>{{id}}</td>
 	                <td class="exportmodel-{{class}}" title="Export modèle {{privacy}}">{{label}}</td>
+                    <?php if($myUser->superadmin): ?>
+                    <td><code>{{slug}}</code></td>
+                    <?php endif; ?>
 	                <td>{{description}}</td>
 	                <td>{{pluginName}}</td>
 	                <td>{{datasetName}}</td>
@@ -64,7 +69,7 @@ foreach ($plugins as $plugin)
         <!-- Pagination -->
         <ul class="pagination justify-content-center">
             <li class="page-item hidden" data-value="{{value}}" title="Voir la page {{label}}" onclick="$(this).parent().find('li').removeClass('active');$(this).addClass('active');export_exportmodel_search()">
-                <a class="page-link" href="#">{{label}}</a>
+                <span class="page-link">{{label}}</span>
             </li>
         </ul>
 	</div>

+ 2 - 2
plugin/export/template/WordExport.class.php

@@ -201,7 +201,7 @@ class WordExport
 
 		$pathParts = explode(SLASH, $img);
 		$imgParts = explode('.', end($pathParts));
-		preg_match('/(jpg|jpeg|png)/', end($imgParts), $ext);
+		preg_match('/(jpg|jpeg|png|gif)/', end($imgParts), $ext);
 		$ext = $ext[0];
 		$imgParts[array_search(end($imgParts), $imgParts)] = $ext;
 
@@ -215,7 +215,7 @@ class WordExport
 
 		$mimeTypes = $this->zip->getFromName('[Content_Types].xml', 0, ZipArchive::OVERWRITE);
 		if(strrpos($mimeTypes, '<Default Extension="'.$ext.'" ContentType="image/'.$cType.'"/>') === false) {
-			$mimeTypes = str_replace('</Types>', '<Default Extension="png" ContentType="image/png"/><Default Extension="jpeg" ContentType="image/jpeg"/><Default Extension="jpg" ContentType="image/jpeg"/></Types>', $mimeTypes);
+			$mimeTypes = str_replace('</Types>', '<Default Extension="gif" ContentType="image/gif"/><Default Extension="png" ContentType="image/png"/><Default Extension="jpeg" ContentType="image/jpeg"/><Default Extension="jpg" ContentType="image/jpeg"/></Types>', $mimeTypes);
 			// $mimeTypes = str_replace('</Types>', '<Default Extension="'.$ext.'" ContentType="image/'.$cType.'"/></Types>', $mimeTypes);
 			$this->zip->addFromString('[Content_Types].xml', $mimeTypes);
 		}

+ 4 - 0
plugin/factory/Template.class.php

@@ -82,6 +82,10 @@
 			        'label'=>'Booléen',
 			        'sql-type'=>'boolean',
 			        'input'=>'<input title="" type="checkbox" name="<?php echo ${{entity}}->{{key}}; ?>" id="<?php echo ${{entity}}->{{key}}; ?>" data-class="" data-type="checkbox">'),
+			    'mail' => array(
+			    	'label'=>'E-mail',
+			    	'sql-type'=>'string',
+			    	'input'=>'<input id="{{key}}" name="{{key}}" data-type="mail" pattern=".+@.+" class="form-control" placeholder="" value="<?php echo ${{entity}}->{{key}}; ?>" type="email">'),
 			  );
 	 		return $types;
  		}

+ 18 - 1
plugin/factory/action.php

@@ -125,13 +125,20 @@ switch($_['action']){
 			foreach ($_['fields']  as $key => $value){
 				if($key=='' || $key==1)continue;
 				if($value['label']=='') $value['label'] = ucfirst($key);
-				$value['key'] = $key;
+				
+				
+				$value['key'] = lcFirst(factory_sanitize($key));
+			
 				if(strpos($value['type'], 'entity-')!==false){
 					$type = $types['int'];
 					$links[] = array('key'=>$value['key'],'entity'=>str_replace('entity-','',$value['type']));
 				}else{
 					$type = $types[$value['type']];
 				}
+
+				$value['typeLabel'] = $type['label'];
+
+
 				$value['sql-type'] = $type['sql-type'];
 				$value['input'] = factory_render($type['input'],$value);
 
@@ -151,6 +158,7 @@ switch($_['action']){
 		$templates = Template::all($_['template']);
 		$template = $templates[$_['part']];
 
+
 		$data = array(
 			'Entity' => $entity,
 			'entity' => strtolower($entity),
@@ -167,6 +175,15 @@ switch($_['action']){
 			'filters' => $filters,
 			'description' => $description,
 		);
+
+		foreach(array('-','_','.') as $symbol){
+			$readable = factory_sanitize(str_replace(' ',$symbol,trim($_['entity'])),false,'_.-');
+			$readable = mb_strtolower($readable);
+			if(strpos($readable,$plugin.$symbol) === 0)
+				$readable = substr($readable, strlen($plugin.$symbol));
+
+			$data['entity'.$symbol.'readable'] = $readable;
+		}
 		
 		$stream = file($template['file']);
 		array_shift($stream);

+ 4 - 4
plugin/factory/factory.plugin.php

@@ -46,7 +46,7 @@ function factory_action(){
 	require_once(__DIR__.SLASH.'action.php');
 }
 
-function factory_sanitize($text, $tableName=false){
+function factory_sanitize($text, $tableName=false,$allowChars='_'){
 	$returned = '';
 	foreach (explode(' ',trim($text)) as $word) {
 		$returned .= $tableName ? mb_strtolower($word).'_' : ucfirst(mb_strtolower($word));
@@ -60,7 +60,7 @@ function factory_sanitize($text, $tableName=false){
 		'ö'=>'o', 'ø'=>'o', 'ù'=>'u', 'ú'=>'u', 'û'=>'u', 'ý'=>'y', 'þ'=>'b', 'ÿ'=>'y' );
 
 	$returned = strtr($returned, $unwanted);
-	return preg_replace('|[^a-z0-9_]|i','',$returned);
+	return preg_replace('|[^a-z0-9'.preg_quote($allowChars).']|i','',$returned);
 }
 
 function factory_render($sql,$data=array()){
@@ -139,8 +139,8 @@ function factory_section(&$sections){
 }
 
 //Déclation des assets
-Plugin::addCss("/css/main.css?v=1"); 
-Plugin::addJs("/js/main.js?v=1"); 
+Plugin::addCss("/css/main.css"); 
+Plugin::addJs("/js/main.js"); 
 
 //Mapping hook / fonctions
 Plugin::addHook("install", "factory_install");

+ 1 - 1
plugin/factory/page.factory.php

@@ -1,7 +1,7 @@
 <?php 
 global $myUser;
+User::check_access('factory','read');
 require_once(__DIR__.SLASH.'Template.class.php');
-if(!$myUser->can('factory', 'read')) throw new Exception("Permissions insuffisantes",403);
 ?>
 <div  id="factoryForm"  data-action="factory_render">
 <div class="row">

+ 20 - 10
plugin/factory/template/Erp plugin/action.php

@@ -4,10 +4,10 @@ global $_,$conf;
 switch($_['action']){
 	/** {{ENTITY}} **/
 	//Récuperation d'une liste de {{entity}}
-	case '{{plugin}}_{{entity}}_search':
+	case '{{plugin}}_{{entity_readable}}_search':
 		Action::write(function(&$response){
 			global $myUser,$_;
-			if(!$myUser->can('{{plugin}}','read')) throw new Exception("Permissions insuffisantes",403);
+			User::check_access('{{plugin}}','read');
 			require_once(__DIR__.SLASH.'{{Entity}}.class.php');{{:links}}
 			require_once(__DIR__.SLASH.'{{value.entity}}.class.php');{{/:links}}
 
@@ -28,7 +28,7 @@ switch($_['action']){
 			if(isset($_['sort'])) sort_secure_query($_['sort'],array('label'),$query,$data);
 
 			//Pagination
-			$response['pagination'] = {{Entity}}::paginate(2,(!empty($_['page'])?$_['page']:0),$query,$data);
+			$response['pagination'] = {{Entity}}::paginate(20,(!empty($_['page'])?$_['page']:0),$query,$data);
 
 			${{entity}}s = {{Entity}}::staticQuery($query,$data,true,{{linksCount}});
 			{{/~}}
@@ -41,14 +41,24 @@ switch($_['action']){
 				$row['{{value.key}}'] = ${{entity}}->join('{{value.key}}')->toArray();{{/:links}}
 				$response['rows'][] = $row;
 			}
+
+			{{~ Export de la recherche}}
+			/* Mode export */
+			if($_['export'] == 'true'){
+				$stream = Excel::exportArray($response['rows'],null,'Export');
+				File::downloadStream($stream,'export-demandes-'.date('d-m-Y').'.xlsx');
+				exit();
+			}
+			{{/~}}
+
 		});
 	break;
 	
 	//Ajout ou modification d'élément {{entity}}
-	case '{{plugin}}_{{entity}}_save':
+	case '{{plugin}}_{{entity_readable}}_save':
 		Action::write(function(&$response){
 			global $myUser,$_;
-			if(!$myUser->can('{{plugin}}','edit')) throw new Exception("Permissions insuffisantes",403);
+			User::check_access('{{plugin}}','edit');
 			require_once(__DIR__.SLASH.'{{Entity}}.class.php');
 			$item = {{Entity}}::provide();{{:fields}}
 			$item->{{value.key}} = $_['{{value.key}}'];{{/:fields}}
@@ -57,10 +67,10 @@ switch($_['action']){
 	break;
 	
 	{{~ Formulaire dans le tableau de liste }}//Récuperation ou edition d'élément {{entity}}
-	case '{{plugin}}_{{entity}}_edit':
+	case '{{plugin}}_{{entity_readable}}_edit':
 		Action::write(function(&$response){
 			global $myUser,$_;
-			if(!$myUser->can('{{plugin}}','edit')) throw new Exception("Permissions insuffisantes",403);
+			User::check_access('{{plugin}}','edit');
 			require_once(__DIR__.SLASH.'{{Entity}}.class.php');{{:links}}
 			require_once(__DIR__.SLASH.'{{value.entity}}.class.php');{{/:links}}
 			$response = {{Entity}}::getById($_['id'],{{linksCount}});
@@ -68,10 +78,10 @@ switch($_['action']){
 	break;{{/~}}
 
 	//Suppression d'élement {{entity}}
-	case '{{plugin}}_{{entity}}_delete':
+	case '{{plugin}}_{{entity_readable}}_delete':
 		Action::write(function(&$response){
 			global $myUser,$_;
-			if(!$myUser->can('{{plugin}}','delete')) throw new Exception("Permissions insuffisantes",403);
+			User::check_access('{{plugin}}','delete');
 			require_once(__DIR__.SLASH.'{{Entity}}.class.php');
 			{{~! Suppression logique}}{{Entity}}::deleteById($_['id']);{{/~}}
 			{{~ Suppression logique}}$item = {{Entity}}::getById($_['id']);
@@ -85,7 +95,7 @@ switch($_['action']){
 	case '{{plugin}}_setting_save':
 		Action::write(function(&$response){
 			global $myUser,$_,$conf;
-			if(!$myUser->can('{{plugin}}','configure')) throw new Exception("Permissions insuffisantes",403);
+			User::check_access('{{plugin}}','configure');
 			foreach(Configuration::setting('{{plugin}}') as $key=>$value){
 				if(!is_array($value)) continue;
 				$allowed[] = $key;

+ 2 - 2
plugin/factory/template/Erp plugin/css/main.css

@@ -11,10 +11,10 @@
 }
 
 /* formulaire d'édition de {{entity}} */
-.{{plugin}} .{{entity}}-form {
+.{{plugin}} .{{entity-readable}}-form {
 	
 }
 
-#{{entity}}-form {
+#{{entity-readable}}-form {
 
 }

+ 20 - 18
plugin/factory/template/Erp plugin/js/main.js

@@ -5,10 +5,10 @@ function init_plugin_{{plugin}}(){
 		default:
 		break;
 	}
-	{{plugin}}_{{entity}}_search();
+	{{plugin}}_{{entity_readable}}_search();
 	{{~! Recherche non avancée}}
-	$('#{{entity}}s').sortable_table({
-		onSort : {{plugin}}_{{entity}}_search
+	$('#{{entity-readable}}s').sortable_table({
+		onSort : {{plugin}}_{{entity_readable}}_search
 	});{{/~}}
 }
 
@@ -25,26 +25,28 @@ function {{plugin}}_setting_save(){
 /** {{ENTITY}} **/
 	
 //Récuperation d'une liste de {{entity}} dans le tableau #{{entity}}s
-function {{plugin}}_{{entity}}_search(callback){
+function {{plugin}}_{{entity_readable}}_search(callback{{~ Export de la recherche}},exportMode{{/~}}){
 	
 		
-	$('#{{entity}}s').fill({
-		action:'{{plugin}}_{{entity}}_search'{{~! Recherche non avancée}},
+	$('#{{entity-readable}}s').fill({
+		action:'{{plugin}}_{{entity_readable}}_search'{{~! Recherche non avancée}},
 		filters : $('#filters').filters(),
-		sort : $('#{{entity}}s').sortable_table('get'){{/~}}
-	},function(){
+		sort : $('#{{entity-readable}}s').sortable_table('get'){{/~}}
+		{{~ Export de la recherche}}export :  !exportMode ? false : exportMode;{{/~}}
+	},function(response){
+		$('.results-count span').text(response.pagination.total);
 		if(callback!=null) callback();
 	});
 }
 
 //Ajout ou modification d'élément {{entity}}
-function {{plugin}}_{{entity}}_save(){
-	var data = $('#{{entity}}-form').toJson();
+function {{plugin}}_{{entity_readable}}_save(){
+	var data = $('#{{entity-readable}}-form').toJson();
 	$.action(data,function(r){
 		
 		{{~ Formulaire dans le tableau de liste }}
-			$('#{{entity}}-form').attr('data-id','');
-			{{plugin}}_{{entity}}_search();
+			$('#{{entity-readable}}-form').attr('data-id','');
+			{{plugin}}_{{entity_readable}}_search();
 		{{/~}}
 		$.message('success','Enregistré');
 	});
@@ -52,20 +54,20 @@ function {{plugin}}_{{entity}}_save(){
 
 {{~ Formulaire dans le tableau de liste }}
 //Récuperation ou edition d'élément {{entity}}
-function {{plugin}}_{{entity}}_edit(element){
+function {{plugin}}_{{entity_readable}}_edit(element){
 	var line = $(element).closest('tr');
-	$.action({action:'{{plugin}}_{{entity}}_edit',id:line.attr('data-id')},function(r){
-		$.setForm('#{{entity}}-form',r);
-		$('#{{entity}}-form').attr('data-id',r.id);
+	$.action({action:'{{plugin}}_{{entity_readable}}_edit',id:line.attr('data-id')},function(r){
+		$.setForm('#{{entity-readable}}-form',r);
+		$('#{{entity-readable}}-form').attr('data-id',r.id);
 	});
 }
 {{/~}}
 //Suppression d'élement {{entity}}
-function {{plugin}}_{{entity}}_delete(element){
+function {{plugin}}_{{entity_readable}}_delete(element){
 	if(!confirm('Êtes vous sûr de vouloir supprimer cet item ?')) return;
 	var line = $(element).closest('tr');
 	$.action({
-		action : '{{plugin}}_{{entity}}_delete',
+		action : '{{plugin}}_{{entity_readable}}_delete',
 		id : line.attr('data-id')
 	},function(r){
 		line.remove();

+ 72 - 0
plugin/factory/template/Erp plugin/page.list.{{entity.readable}}.php

@@ -0,0 +1,72 @@
+{"label":"Liste entité","syntax":"php"}
+<?php
+global $myUser;
+User::check_access('{{plugin}}','read');
+require_once(__DIR__.SLASH.'{{Entity}}.class.php');
+?>
+
+<div class="row">
+    {{~! Recherche non avancée}}
+    <div class="col-md-8">
+        <select id="filters" data-type="filter" data-label="Recherche" data-function="{{plugin}}_{{entity_readable}}_search">
+            <option value="label"   data-filter-type="text">Libellé</option>
+        </select>
+    </div>
+    {{/~}}
+	<div class="col-md-4">
+		<?php if($myUser->can('{{plugin}}', 'edit')) : ?>
+		<a href="index.php?module={{plugin}}&page=sheet.{{entity.readable}}" class="btn btn-success right"><i class="fas fa-plus"></i> Ajouter</a>
+		<?php endif; ?>
+	</div>
+</div>
+<br/>
+<h4 class="results-count"><span></span> Résultat(s) <div class="btn btn-dark btn-small" onclick="{{plugin}}_{{entity_readable}}_search(null,true)"><i class="far fa-file-excel"></i> Exporter</div></h4>
+<div class="row">
+	<!-- search results -->
+	<div class="col-xl-12">
+		<table id="{{entity-readable}}s" class="table table-striped " data-entity-search="{{plugin}}_{{entity_readable}}_search">
+            <thead>
+                <tr>
+                    <th>#</th>{{:fields}}
+                    <th data-sortable="{{key}}">{{value.label}}</th>{{/:fields}}
+                    <th></th>
+                </tr>
+            </thead>
+            {{~ Formulaire dans le tableau de liste }}
+                <thead>
+                    <tr id="{{entity-readable}}-form" data-action="{{plugin}}_{{entity_readable}}_save" data-id="">
+                        <th>#</th>{{:fields}}
+                        <th><input id="{{key}}" name="{{key}}" class="form-control" placeholder="" value="" type="text"></th>{{/:fields}}
+                        <th><div onclick="{{plugin}}_{{entity_readable}}_save();" class="btn btn-success"><i class="fas fa-check"></i> Enregistrer</div></th>
+                    </tr>
+                </thead>
+            {{/~}}
+            <tbody>
+                <tr data-id="{{id}}" class="hidden">
+	                <td>{{id}}</td>{{:fields}}
+	                <td>{{{{value.key}}}}</td>{{/:fields}}
+	                <td>
+	                    <div class="btn-group btn-group-sm" role="group">
+                            {{~! Formulaire dans le tableau de liste }}
+                            <a class="btn btn-info" href="index.php?module={{plugin}}&page=sheet.{{entity.readable}}&id={{id}}"><i class="fas fa-pencil-alt"></i></a>
+                            {{/~}}
+                            {{~ Formulaire dans le tableau de liste }}
+                            <div class="btn btn-info " onclick="{{plugin}}_{{entity_readable}}_edit(this);"><i class="fas fa-pencil-alt"></i></div>
+                            {{/~}}
+                            <div class="btn btn-danger " onclick="{{plugin}}_{{entity_readable}}_delete(this);"><i class="far fa-trash-alt"></i></div>
+	                    </div>
+	                </td>
+                </tr>
+           </tbody>
+        </table>
+
+         {{~! Recherche non avancée}}<!-- Pagination (data-range définit le nombre de pages max affichées avant et après la page courante) -->
+       
+        <ul class="pagination justify-content-center"  data-range="5">
+            <li class="page-item hidden" data-value="{{value}}" title="Voir la page {{label}}" onclick="$(this).parent().find('li').removeClass('active');$(this).addClass('active');{{plugin}}_{{entity_readable}}_search();">
+                <span class="page-link">{{label}}</span>
+            </li>
+        </ul>{{/~}}
+
+	</div>
+</div>

+ 18 - 0
plugin/factory/template/Erp plugin/page.sheet.{{entity.readable}}.php

@@ -0,0 +1,18 @@
+{"label":"Fiche entité","syntax":"php"}
+<?php 
+User::check_access('{{plugin}}','read');
+require_once(__DIR__.SLASH.'{{Entity}}.class.php');
+${{entity}} = {{Entity}}::provide();
+?>
+<div class="{{plugin}}">
+	<div id="{{entity-readable}}-form" class="row {{entity-readable}}-form" data-action="{{plugin}}_{{entity_readable}}_save" data-id="<?php echo ${{entity}}->id; ?>">
+		<div class="col-md-12">
+			<h3>{{Entity}}</h3>{{:fields}}
+			<label for="{{value.key}}">{{value.label}}</label>
+			{{value.input}}{{/:fields}}
+			<br/>
+			<div onclick="{{plugin}}_{{entity_readable}}_save();" class="btn btn-success"><i class="fas fa-check"></i> Enregistrer</div>
+		</div>
+	</div>
+</div>
+

+ 1 - 1
plugin/factory/template/Erp plugin/setting.global.{{plugin}}.php

@@ -1,7 +1,7 @@
 {"label":"Setting global","syntax":"php"}
 <?php
 global $myUser,$conf;
-if(!$myUser->can('{{plugin}}','configure')) throw new Exception("Vous n'avez pas la permission pour executer cette fonctionnalité",403);
+User::check_access('{{plugin}}','configure');
 ?>
 
 <div class="row">

+ 73 - 0
plugin/factory/template/Erp plugin/setting.list.{{plugin}}.{{entity.readable}}.php

@@ -0,0 +1,73 @@
+{"label":"Setting liste","syntax":"php"}
+<?php
+global $myUser;
+User::check_access('{{plugin}}','configure');
+require_once(__DIR__.SLASH.'{{Entity}}.class.php');
+?>
+<br>
+<h3>Réglages {{plugin}}</h3>
+<hr/>
+<div class="row">
+    {{~! Recherche non avancée}}
+    <div class="col-md-8">
+        <select id="filters" data-type="filter" data-label="Recherche" data-function="{{plugin}}_{{entity_readable}}_search">
+            <option value="label"   data-filter-type="text">Libellé</option>
+        </select>
+    </div>
+    {{/~}}
+    <div class="col-md-4">
+        <?php if($myUser->can('{{plugin}}', 'edit')) : ?>
+        <a href="index.php?module={{plugin}}&page=sheet.{{entity.readable}}" class="btn btn-success right"><i class="fas fa-plus"></i> Ajouter</a>
+        <?php endif; ?>
+    </div>
+</div>
+<br/>
+<div class="row">
+    <!-- search results -->
+    <div class="col-xl-12">
+        <table id="{{entity-readable}}s" class="table table-striped " data-entity-search="{{plugin}}_{{entity_readable}}_search">
+            <thead>
+                <tr>
+                    <th>#</th>{{:fields}}
+                    <th data-sortable="{{key}}">{{value.label}}</th>{{/:fields}}
+                    <th></th>
+                </tr>
+            </thead>
+            {{~ Formulaire dans le tableau de liste }}
+                <thead>
+                    <tr id="{{entity-readable}}-form" data-action="{{plugin}}_{{entity_readable}}_save" data-id="">
+                        <th>#</th>{{:fields}}
+                        <th><input id="{{key}}" name="{{key}}" class="form-control" placeholder="" value="" type="text"></th>{{/:fields}}
+                        <th><div onclick="{{plugin}}_{{entity_readable}}_save();" class="btn btn-success"><i class="fas fa-check"></i> Enregistrer</div></th>
+                    </tr>
+                </thead>
+            {{/~}}
+            <tbody>
+                <tr data-id="{{id}}" class="hidden">
+                    <td>{{id}}</td>{{:fields}}
+                    <td>{{{{value.key}}}}</td>{{/:fields}}
+                    <td>
+                        <div class="btn-group btn-group-sm" role="group">
+                            {{~! Formulaire dans le tableau de liste }}
+                            <a class="btn btn-info" href="index.php?module={{plugin}}&page=sheet.{{entity.readable}}&id={{id}}"><i class="fas fa-pencil-alt"></i></a>
+                            {{/~}}
+                            {{~ Formulaire dans le tableau de liste }}
+                            <div class="btn btn-info " onclick="{{plugin}}_{{entity_readable}}_edit(this);"><i class="fas fa-pencil-alt"></i></div>
+                            {{/~}}
+                            <div class="btn btn-danger " onclick="{{plugin}}_{{entity_readable}}_delete(this);"><i class="far fa-trash-alt"></i></div>
+                        </div>
+                    </td>
+                </tr>
+           </tbody>
+        </table>
+
+         {{~! Recherche non avancée}}<!-- Pagination -->
+       
+        <ul class="pagination justify-content-center">
+            <li class="page-item hidden" data-value="{{value}}" title="Voir la page {{label}}" onclick="$(this).parent().find('li').removeClass('active');$(this).addClass('active');{{plugin}}_{{entity_readable}}_search();">
+                <span class="page-link">{{label}}</span>
+            </li>
+        </ul>{{/~}}
+
+    </div>
+</div>

+ 7 - 1
plugin/factory/template/Erp plugin/{{Entity}}.class.php

@@ -7,7 +7,10 @@
  * @license copyright
  */
 class {{Entity}} extends Entity{
-	public $id{{:fields}},${{value.key}}{{/:fields}};
+
+	public $id;{{:fields}}
+	public ${{value.key}}; //{{value.label}} ({{value.typeLabel}}){{/:fields}}
+	
 	protected $TABLE_NAME = '{{plugin}}_{{table}}';
 	public $fields =
 	array(
@@ -18,5 +21,8 @@ class {{Entity}} extends Entity{
 	public $links = array({{:links}}
 		'{{value.key}}' => '{{value.entity}}'{{;}},{{/;}}{{/:links}}
 	);
+
+	//Colonnes indexées
+	public $indexes = array();
 }
 ?>

+ 15 - 19
plugin/factory/template/Erp plugin/{{plugin}}.plugin.php

@@ -19,7 +19,7 @@ function {{plugin}}_menu(&$menuItems){
 function {{plugin}}_page(){
 	global $_,$myUser;
 	if(!isset($_['module']) || $_['module'] !='{{plugin}}') return;
-	$page = !isset($_['page']) ? 'list.{{entity}}' : $_['page'];
+	$page = !isset($_['page']) ? 'list.{{entity.readable}}' : $_['page'];
 	$file = __DIR__.SLASH.'page.'.$page.'.php';
 	if(!file_exists($file)) throw new Exception("Page ".$page." inexistante");
 	
@@ -30,7 +30,6 @@ function {{plugin}}_page(){
 function {{plugin}}_install($id){
 	if($id != 'fr.idleman.{{plugin}}') return;
 	Entity::install(__DIR__);
-
 }
 
 //Fonction executée lors de la désactivation du plugin
@@ -55,22 +54,19 @@ function {{plugin}}_menu_setting(&$settingMenu){
 	global $_, $myUser;
 	
 	if(!$myUser->can('{{plugin}}','configure')) return;
-		
-		$settingMenu[]= array(
-			'sort' =>1,
-			'url' => 'setting.php?section=list.{{plugin}}.{{entity}}',
-			'icon' => 'fas fa-angle-right',
-			'label' => '{{plugin}}'
-		);
-
-		$settingMenu[]= array(
-			'sort' =>1,
-			'url' => 'setting.php?section=global.{{plugin}}',
-			'icon' => 'fas fa-angle-right',
-			'label' => '{{plugin}} Général'
-		);
+	$settingMenu[]= array(
+		'sort' =>1,
+		'url' => 'setting.php?section=global.{{plugin}}',
+		'icon' => 'fas fa-angle-right',
+		'label' => '{{plugin}} Général'
+	);
 
-	
+	$settingMenu[]= array(
+		'sort' =>1,
+		'url' => 'setting.php?section=list.{{plugin}}.{{entity.readable}}',
+		'icon' => 'fas fa-angle-right',
+		'label' => '{{plugin}}'
+	);
 }
 {{/~}}
 {{~! Pas de page réglages }}//Déclaration des pages de réglages
@@ -109,8 +105,8 @@ function {{plugin}}_widget(&$widgets){
 
 
 //Déclation des assets
-Plugin::addCss("/css/main.css?v=1"); 
-Plugin::addJs("/js/main.js?v=1"); 
+Plugin::addCss("/css/main.css"); 
+Plugin::addJs("/js/main.js"); 
 
 //Mapping hook / fonctions
 Plugin::addHook("install", "{{plugin}}_install");

+ 2 - 2
plugin/hackpoint/WebDav.class.php → plugin/hackpoint/VirtualWebDav.class.php

@@ -1,6 +1,6 @@
 <?php
 
-class WebDav{
+class VirtualWebDav{
 	public $root,$folder,$logs,$version = 1,$user,$lockfile,$ignoreFiles = array();
 	public $on = array(
 		'login' => null,
@@ -527,7 +527,7 @@ class WebDav{
 			unset($locks[base64_encode($file)]);
 		}else{
 			//Si les properties du verrou sont renseigné, on le save
-			$properties['token'] = isset($properties['token']) && $properties['token'] != '' ? $properties['token'] : 'fr.sys1:'.sha1($file);
+			$properties['token'] = isset($properties['token']) && $properties['token'] != '' ? $properties['token'] : 'fr.idleman:'.sha1($file);
 			$locks[base64_encode($file)] = $properties;
 		}
 

+ 5 - 0
plugin/hackpoint/app.json

@@ -8,6 +8,11 @@
 	"version": "1.0",
 	"url": "http://idleman.fr",
 	"licence": {"name": "CC by nc sa","url" : ""},
+	"install": {
+		"css" : "css/install.css",
+		"js" : "js/install.js",
+		"action" : "install.php"
+	},
 	"description": "Gestion des projets de hack",
 	"require" : {}
 }

+ 79 - 0
plugin/hackpoint/css/install.css

@@ -0,0 +1,79 @@
+html,body,.footer{
+	background-color:#343a40;
+	color:#fefefe;
+}
+
+.navbar-brand img{
+	display:none;
+}
+
+
+.navbar-brand {
+    margin: auto;
+    background: url(../img/logo.png) no-repeat 0px center;
+    padding-left: 39px;
+    text-transform: uppercase;
+    color: #a9a9a9!important;
+}
+
+input.form-control,
+input.form-control:active,
+input.form-control:focus,
+select.form-control,
+select.form-control:active,
+select.form-control:focus,
+textarea.form-control,
+textarea.form-control:active,
+textarea.form-control:focus
+{
+    background-color: #282c31;
+    border-color: #282c31;
+    color:#fefefe;
+}
+
+.alert-danger {
+    color: #ffd7da;
+    background-color: #841721;
+   border: 0;
+}
+
+
+.alert-success {
+    color: #ceead5;
+    background-color: #28a745;
+    border-color: #28a745;
+}
+
+code {
+    font-size: 80%;
+    color: #ffffff;
+    background-color: #5a6673;
+    padding: 3px;
+}
+
+pre {
+    display: block;
+    font-size: 87.5%;
+    color: #cfd7de;
+}
+
+.close {
+    float: right;
+    font-size: 1.5rem;
+    font-weight: 700;
+    line-height: 1;
+    color: #fff;
+    text-shadow: 0 1px 0 #fff;
+    opacity: .5;
+}
+
+.list-group-item {
+
+    background-color: #25292d;
+}
+a {
+    color: #728ead;
+}
+a:hover {
+    color: #8fbcec;
+}

+ 8 - 5
plugin/hackpoint/hackpoint.plugin.php

@@ -24,7 +24,10 @@ function hackpoint_menu(&$menuItems){
 //Cette fonction va generer une page quand on clique sur hackpoint dans menu
 function hackpoint_page(){
 	global $_,$myUser;
-	if(!isset($_['module']) || $_['module'] !='hackpoint') return;
+
+
+
+	if(isset($_['module']) && $_['module'] !='hackpoint') return;
 	$page = !isset($_['page']) ? 'list.sketch' : $_['page'];
 	$file = __DIR__.SLASH.'page.'.$page.'.php';
 	if(!file_exists($file)) throw new Exception("Page ".$page." inexistante");
@@ -103,13 +106,13 @@ function hackpoint_dav($requested){
 
 
 	require_once(__ROOT__.'common.php');
-	require_once(__DIR__.SLASH.'WebDav.class.php');
+	require_once(__DIR__.SLASH.'VirtualWebDav.class.php');
 
 	$projectPath = preg_replace('|https?\:\/\/'.$_SERVER['HTTP_HOST'].'|i', '', ROOT_URL);
 
-	$server = new WebDav();
-	$server->logs = WebDav::logPath().SLASH.'dav-logs.txt';
-	$server->lockfile = WebDav::logPath().SLASH.'dav-lock.json';
+	$server = new VirtualWebDav();
+	$server->logs = VirtualWebDav::logPath().SLASH.'dav-logs.txt';
+	$server->lockfile = VirtualWebDav::logPath().SLASH.'dav-lock.json';
 	$server->root = str_replace('//','/',$projectPath.'/dav/hackpoint/');
 
 	$server->folder = '';

BIN
plugin/hackpoint/img/logo.png


+ 24 - 0
plugin/hackpoint/install.php

@@ -0,0 +1,24 @@
+<?php 
+
+$enablePlugins = array('fr.idleman.hackpoint','fr.idleman.customiser','fr.idleman.document','fr.idleman.notification','fr.idleman.navigation');
+
+$conf->put('core_theme','/plugin/customiser/theme/hackpoint/main.css');
+
+$user = User::byLogin('admin');
+$user->name = 'NORRIS';
+$user->firstname = 'Chuck';
+$user->save();
+$_SESSION['currentUser'] = serialize($user);
+$myUser = $user;
+
+$coreDir = File::dir().SLASH.'core';
+if(!file_exists($coreDir)) mkdir($coreDir);
+
+copy(__DIR__.SLASH.'img'.SLASH.'logo.png',$coreDir.SLASH.'logo.png');
+copy(__DIR__.SLASH.'img'.SLASH.'logo.png',$coreDir.SLASH.'logo.dark.png');
+copy(__DIR__.SLASH.'img'.SLASH.'logo.png',$coreDir.SLASH.'favicon.png');
+
+
+?>
+
+

+ 2 - 2
plugin/navigation/app.json

@@ -1,11 +1,11 @@
 {
-	"id": "fr.sys1.navigation",
+	"id": "fr.idleman.navigation",
 	"name": "Navigation",
 	"author" : {
 		"name" : "Valentin CARRUESCO"
 	},
 	"version": "2.0",
-	"url": "http://sys1.fr",
+	"url": "http://idleman.fr",
 	"licence": {"name": "Copyright","url" : ""},
 	"description": "Gestion des menus. Possibilité d'ajouter et de supprimer des éléments de menu personnalisables",
 	"require" : {}

+ 2 - 2
plugin/navigation/navigation.plugin.php

@@ -145,7 +145,7 @@ function navigation_page(){
 
 //Fonction executée lors de l'activation du plugin
 function navigation_install($id){
-	if($id != 'fr.sys1.navigation') return;
+	if($id != 'fr.idleman.navigation') return;
 	Entity::install(__DIR__);
 	require_once(__DIR__.SLASH.'MenuItem.class.php');
 	
@@ -181,7 +181,7 @@ function navigation_install($id){
 
 //Fonction executée lors de la désactivation du plugin
 function navigation_uninstall($id){
-	if($id != 'fr.sys1.navigation') return;
+	if($id != 'fr.idleman.navigation') return;
 	Entity::uninstall(__DIR__);
 }
 

+ 3 - 3
plugin/notification/app.json

@@ -1,12 +1,12 @@
 {
-	"id": "fr.sys1.notification",
+	"id": "fr.idleman.notification",
 	"name": "Notification",
 	"author" : {
 		"name" : "Valentin Carruesco"
 	},
 	"version": "1.0",
-	"url": "http://sys1.fr",
-	"licence": {"name": "Copyright","url" : "http://sys1.fr"},
+	"url": "http://idleman.fr",
+	"licence": {"name": "Copyright","url" : "http://idleman.fr"},
 	"description": "Gestion des notifications mail et popin au travers du programme",
 	"require" : {}
 }

+ 3 - 3
plugin/notification/notification.plugin.php

@@ -42,7 +42,7 @@ function notification_page(){
 
 //Fonction executée lors de l'activation du plugin
 function notification_install($id){
-	if($id != 'fr.sys1.notification') return;
+	if($id != 'fr.idleman.notification') return;
 	Entity::install(__DIR__);
 
 	global $myUser,$_;
@@ -69,7 +69,7 @@ function notification_install($id){
 
 //Fonction executée lors de la désactivation du plugin
 function notification_uninstall($id){
-	if($id != 'fr.sys1.notification') return;
+	if($id != 'fr.idleman.notification') return;
 	Entity::uninstall(__DIR__);
 }
 
@@ -141,7 +141,7 @@ function notification_methods(&$sendTypes){
 
 			// ENVOI MAIL
 			$mail = new Mail();
-			$mail->expeditor = 'Sys1 <no-reply@sys1.fr>';
+			$mail->expeditor = 'Sys1 <no-reply@idleman.fr>';
 			$mail->title = $infos['label'];
 			$mail->message = '<h3>'.$infos['label'].'</h3><p>'.$infos['html'].'</p>';
 			$mail->message .= (isset($infos['meta']['link'])) ? '<a href="'.$infos['meta']['link'].'">Accéder à l\'ERP</a>' : '';

+ 0 - 141
plugin/wiki/WikiCategory.class.php

@@ -1,141 +0,0 @@
-<?php
-/**
- * Define a category.
- * @author Valentin CARRUESCO
- * @category Plugin
- * @license copyright
- */
-require_once(__DIR__.SLASH.'WikiPage.class.php');
-
-class WikiCategory extends Entity{
-	public $id,$label,$icon,$color,$state,$path,$slug;
-	protected $TABLE_NAME = 'wiki_category';
-	public $fields =
-	array(
-		'id' => 'key',
-		'label' => 'string',
-		'slug' => 'string',
-		'icon' => 'string',
-		'color' => 'string',
-		'state' => 'string',
-		'path' => 'string'
-	);
-	const DEFAULT_CATEGORY = 'Non classé';
-
-	public function __construct(){
-		$this->color = self::color();
-		parent::__construct();
-	}
-
-	public static function synchronize(){
-		$workspace = WikiPage::workspace();
-		$defaultCategory = $workspace.SLASH.wiki_os_encode(self::DEFAULT_CATEGORY);
-		if(!file_exists($defaultCategory)) mkdir($defaultCategory,0755,true);
-
-		$existing =  array();
-
-		//MAJ de la base pour les dossiers supprimés
-		foreach(self::loadAll() as $category){
-			if(!file_exists($workspace.SLASH.wiki_os_encode($category->path)))
-				self::deleteById($category->id);
-			$existing[$category->path] = $category;
-		}
-
-		//MAJ de la base pour les nouveaux dossiers
-		foreach(glob($workspace.SLASH.'*') as $folder){
-			$path = str_replace($workspace.SLASH,'',wiki_os_decode($folder));
-			if(isset($existing[$path])) continue;
-			if(is_dir($folder)){
-				$category = new self();
-				$category->label = wiki_os_decode(basename($folder));
-				$category->slug = slugify($category->label);
-				$category->path = $path;
-				$category->icon = 'far fa-bookmark';
-				$category->save();
-			} else {
-				rename($folder, $defaultCategory.SLASH.basename($folder));
-			}
-		}
-	}
-
-	public function pages(){
-		$pages = array();
-		$workspace = WikiPage::workspace();
-
-		$existing =  array();
-		foreach(WikiPage::loadAll(array('category'=>$this->id)) as $page){
-			if(!file_exists($workspace.SLASH.wiki_os_encode($page->path))){
-				WikiPage::deleteById($page->id);
-				continue;
-			}
-			$existing[$page->path] = $page;
-		}
-		
-		foreach(glob($workspace.SLASH.wiki_os_encode($this->path).SLASH.'*.md') as $file){
-			$path = str_replace($workspace.SLASH,'',wiki_os_decode($file));
-			if(!is_file($file) || isset($existing[$path])) continue;
-			$page = new WikiPage();
-			$page->label = preg_replace('|\.md|i','',wiki_os_decode(basename($file)));
-			$page->slug = slugify($page->label);
-			$page->path = $path;
-			$page->content = file_get_contents($file);
-			$page->category = $this->id;
-			$page->state = WikiPage::PUBLISHED;
-			$page->save();
-		}
-
-		$pages =  WikiPage::loadAll(array('category'=>$this->id),array('label'));
-		return $pages;
-	}
-
-	public static function deleteById($id){
-		$category = self::getById($id);
-		parent::deleteById($id);
-		$workspace = WikiPage::workspace();
-		$path = $workspace.SLASH.wiki_os_encode($category->path);
-		if(file_exists($path) && is_dir($path)) self::remove_directory($path);
-	}
-
-	public static function remove_directory($dir){
-		if(!is_dir($dir)) return;
-		foreach (glob($dir.SLASH.'*') as $file) { 
-			if(is_dir($file)){
-				self::remove_directory($file);
-			} else {
-				unlink($file);
-			}
-		}
-		rmdir($dir);
-	}
-
-	public static function color(){
-		$colors = array(
-			'#4E5C6E',
-			'#19B7FF',
-			'#19B7FF',
-			'#7F6BFF',
-			'#FC7419',
-			'#FC2D2D',
-			'#FFE100',
-			'#14CF9F',
-			'#EE84F0',
-			'#2F362F'
-		);
-		return $colors[array_rand($colors)];
-	}
-
-	public function author(){
-		return User::byLogin($this->creator)->fullName();
-	}
-	public function created(){
-		return relative_time($this->created);
-	}
-	
-	public function updater(){
-		return User::byLogin($this->updater)->fullName();
-	}
-	public function updated(){
-		return relative_time($this->updated);
-	}
-}
-?>

+ 0 - 60
plugin/wiki/WikiPage.class.php

@@ -1,60 +0,0 @@
-<?php
-/**
- * Define a page.
- * @author Valentin CARRUESCO
- * @category Plugin
- * @license copyright
- */
-class WikiPage extends Entity{
-	public $id,$label,$content,$state,$path,$category,$slug;
-	const PUBLISHED = 'published';
-	const DRAFT = 'draft';
-	protected $TABLE_NAME = 'wiki_page';
-	public $links = array(
-		'category' => 'WikiCategory'
-	);
-	public $fields =
-	array(
-		'id' => 'key',
-		'label' => 'string',
-		'state' => 'string',
-		'category' => 'int',
-		'slug' => 'string',
-		'path' => 'string'
-	);
-
-	public static function workspace(){
-		return File::dir().'wiki'.SLASH.'pages';
-	}
-
-	public static function uploads(){
-		return File::dir().'wiki'.SLASH.'uploads';
-	}
-
-	public static function path_from_label($label){
-		return preg_replace('|[\?\\\/\*\:\|\<\>]|i', '-',$label);
-	}
-
-	public function author(){
-		return User::byLogin($this->creator)->fullName();
-	}
-	public function created(){
-		return relative_time($this->created);
-	}
-	public function updater(){
-		return User::byLogin($this->updater)->fullName();
-	}
-	public function updated(){
-		return relative_time($this->updated);
-	}
-	public function html(){
-		require_once(__DIR__.SLASH.'lib'.SLASH.'Parsedown.php');
-		$markdown = new Parsedown();
-		return $markdown->text($this->content);
-	}
-	public function content(){
-		$this->content = file_get_contents(self::workspace().SLASH.wiki_os_encode($this->path));
-	}
-
-}
-?>

+ 0 - 347
plugin/wiki/action.php

@@ -1,347 +0,0 @@
-<?php
-global $_,$conf;
-switch($_['action']){
-	/** COMMON **/
-	case 'wiki_logo_download':
-		$logoDir = File::dir().'wiki'.SLASH.'logo';
-		$logo = $logoDir.SLASH.'logo.png';
-		if(!file_exists($logoDir)) mkdir($logoDir,0755,true);
-		if(!file_exists($logo)) copy(__DIR__.SLASH.'img'.SLASH.'logo.png', $logo);
-
-		header('Content-Type: image/png');
-		echo file_get_contents($logo);
-	break;
-
-	case 'wiki_logo_delete':
-		Action::write(function(&$response){
-		    global $myUser,$_;
-		    if(!$myUser->can('wiki', 'configure')) throw new Exception("Permissions insuffisantes",403);
-
-		    foreach (glob(File::dir().'wiki'.SLASH."logo".SLASH."logo.*") as $filename)
-		        unlink($filename);
-		});
-	break;
-
-	/** HOME **/
-	case 'wiki_page_home':
-		Action::write(function(&$response){
-			global $myUser,$_;
-			if(!$myUser->can('wiki','read')) throw new Exception("Permissions insuffisantes",403);
-
-			ob_start();
-			require_once(__DIR__.SLASH.'page.home.php');
-			$response['content'] = ob_get_clean();
-		});
-	break;
-
-	case 'wiki_page_search':
-		Action::write(function(&$response){
-			global $myUser,$_;
-			if(!$myUser->can('wiki','read')) throw new Exception("Permissions insuffisantes",403);
-
-			ob_start();
-			require_once(__DIR__.SLASH.'page.search.php');
-			$response['content'] = ob_get_clean();
-		});
-	break;
-
-	case 'wiki_page_download':
-		require_once(__DIR__.SLASH.'WikiPage.class.php');
-		$workspace = WikiPage::workspace();
-		$page = WikiPage::getById($_['page']);
-		File::downloadFile($workspace.SLASH.wiki_os_encode($page->path),null,null,true);
-	break;
-
-	/** CATEGORY **/
-	//Récuperation d'une liste de page
-	case 'wiki_category_search':
-		Action::write(function(&$response){
-			global $myUser,$_;
-			if(!$myUser->can('wiki','read')) throw new Exception("Permissions insuffisantes",403);
-			require_once(__DIR__.SLASH.'WikiCategory.class.php');
-			require_once(__DIR__.SLASH.'WikiPage.class.php');
-			
-			$workspace = WikiPage::workspace();
-			if(!file_exists($workspace)) mkdir($workspace,0755,true);
-			
-			
-			foreach(WikiCategory::loadAll() as $category){
-				$response['rows'][] = $category;
-			}
-		});
-	break;
-
-	case 'wiki_category_edit':
-		require_once(__DIR__.SLASH.'modal.category.php');
-	break;
-
-	case 'wiki_category_open':
-		Action::write(function(&$response){
-			global $myUser,$_;
-			if(!$myUser->can('wiki','read')) throw new Exception("Permissions insuffisantes",403);
-			require_once(__DIR__.SLASH.'WikiCategory.class.php');
-			require_once(__DIR__.SLASH.'WikiPage.class.php');
-
-			$category = WikiCategory::load(array('slug'=>$_['category']));
-			$pages = $category->pages();
-			$recents = WikiPage::loadAll(array('category'=>$category->id),array('updated DESC'),array('10'), array('*'), 1);
-			ob_start();
-			require_once(__DIR__.SLASH.'page.category.php');
-			$response['content'] = ob_get_clean();
-			
-			$response['categorySlug'] = $category->slug;
-			$response['pages'] = $pages;
-		});
-	break;
-
-	case 'wiki_category_download':
-		try{
-		require_once(__DIR__.SLASH.'WikiCategory.class.php');
-		require_once(__DIR__.SLASH.'WikiPage.class.php');
-		$workspace = WikiPage::workspace();
-		$category = WikiCategory::getById($_['category']);
-		$path = $workspace.SLASH.wiki_os_encode($category->path);
-		$zipName = tempnam(sys_get_temp_dir(), "zip123");
-		if (!extension_loaded('zip')) throw new Exception("L'extension ZIP est manquante");
-		$zip = new ZipArchive();
-	    if (!$zip->open($zipName, ZIPARCHIVE::CREATE)) 
-	        throw new Exception ("Impossible de créer l'archive (problèmes de permissions ?");
-	    
-	    foreach(glob($path.SLASH.'*.md') as $file){
-	    	$zip->addFromString(basename($file), file_get_contents($file));
-	    }
-	    $zip->close();
-	    $stream = file_get_contents($zipName);
-	 
-	    unlink($zipName);
-	    File::downloadStream($stream,$category->slug.'.zip');
-	    }catch(Exception $e){
-	    	echo 'Erreur : '.$e->getMessage();
-	    }
-	break;
-
-	//Ajout ou modification d'élément page
-	case 'wiki_category_save':
-		Action::write(function(&$response){
-			global $myUser,$_;
-			if(!$myUser->can('wiki','edit')) throw new Exception("Permissions insuffisantes",403);
-			require_once(__DIR__.SLASH.'WikiCategory.class.php');
-			require_once(__DIR__.SLASH.'WikiPage.class.php');
-			$workspace = WikiPage::workspace();
-
-			$item = isset($_['id']) && is_numeric($_['id']) ? WikiCategory::getById($_['id']) : new WikiCategory();
-			
-			$item->icon = $_['icon'];
-			$item->color = $_['color'];
-
-			if($item->id==0){
-				$item->label = $_['label'];
-				$item->slug = slugify($item->label);
-				$item->path = WikiPage::path_from_label($item->label);
-				$dir = $workspace.SLASH.wiki_os_encode($item->path);
-				if(!file_exists($dir)) mkdir($dir,0755,true);
-			}else{
-				if($item->label!=$_['label']){
-					$oldDir = $workspace.SLASH.wiki_os_encode($item->path);
-
-					$item->label = $_['label'] ;
-					$item->slug = slugify($item->label);
-					$item->path = WikiPage::path_from_label($item->label);
-
-					$newDir = $workspace.SLASH.wiki_os_encode($item->path);
-					if(file_exists($newDir)) throw new Exception("Ce nom de catégorie est déja pris");
-					rename($oldDir, $newDir);
-				}
-			}
-
-			$item->save();
-			$response = $item->toArray();
-		});
-	break;
-
-	//Suppression d'élement page
-	case 'wiki_category_delete':
-		Action::write(function(&$response){
-			global $myUser,$_;
-			if(!$myUser->can('wiki','delete')) throw new Exception("Permissions insuffisantes",403);
-			require_once(__DIR__.SLASH.'WikiCategory.class.php');
-			require_once(__DIR__.SLASH.'WikiPage.class.php');
-			WikiPage::delete(array('category'=>$_['id']));
-			WikiCategory::deleteById($_['id']);
-			
-		});
-	break;
-
-	/** PAGE **/
-	//Ajout ou modification d'élément page
-	case 'wiki_page_save':
-		Action::write(function(&$response){
-			global $myUser,$_;
-			if(!$myUser->can('wiki','edit')) throw new Exception("Permissions insuffisantes",403);
-			require_once(__DIR__.SLASH.'WikiCategory.class.php');
-			require_once(__DIR__.SLASH.'WikiPage.class.php');
-
-			$page = WikiPage::provide();
-			$page->content = html_entity_decode($_['content']);
-			
-			if($page->id == 0 && isset($_['category'])){
-				$category = WikiCategory::getById($_['category']);
-				$page->state = WikiPage::PUBLISHED;
-				$page->category = $category->id;
-				$page->label = 'Nouvelle page - '.date('d/m/y h:i:s');
-				$page->path = $category->path.SLASH.WikiPage::path_from_label($page->label).'.md';
-				$page->slug = slugify($page->label);
-			} else {
-				$category = WikiCategory::getById($page->category);
-				
-				if($page->label!=$_['label']){
-					$oldPath = WikiPage::workspace().SLASH.wiki_os_encode($page->path);
-					$page->label = $_['label'];
-					$page->path = $category->path.SLASH.WikiPage::path_from_label($page->label).'.md';
-					$page->slug = slugify($page->label);
-
-					$newPath = WikiPage::workspace().SLASH.wiki_os_encode($page->path);
-					if(file_exists($newPath)) throw new Exception("Ce nom de page pour cette catégorie est déja pris");
-					unlink($oldPath);
-				}
-			}
-			if(isset($page->content)) file_put_contents(WikiPage::workspace().SLASH.wiki_os_encode($page->path),$page->content);
-			$page->save();
-			
-			ob_start();
-			require_once(__DIR__.SLASH.'page.page.php');
-			$response['content'] = ob_get_clean();
-			$response['page'] = $page->toArray();
-			$response['category'] = $category->toArray();
-		});
-	break;
-
-	//Suppression d'élement page
-	case 'wiki_page_delete':
-		Action::write(function(&$response){
-			global $myUser,$_;
-			if(!$myUser->can('wiki','delete')) throw new Exception("Permissions insuffisantes",403);
-			require_once(__DIR__.SLASH.'WikiCategory.class.php');
-			$page = WikiPage::getById($_['id']);
-			$category = WikiCategory::getById($page->category);
-			$response['category']= $category->toArray();
-			WikiPage::deleteById($page->id);
-			$path = WikiPage::workspace().SLASH.wiki_os_encode($page->path);
-			if(file_exists($path)) unlink($path);
-		});
-	break;
-
-	case 'wiki_page_open':
-		Action::write(function(&$response){
-			global $myUser,$_;
-			if(!$myUser->can('wiki','read')) throw new Exception("Permissions insuffisantes",403);
-			require_once(__DIR__.SLASH.'WikiCategory.class.php');
-			require_once(__DIR__.SLASH.'WikiPage.class.php');
-
-			$page = WikiPage::load(array('slug'=>$_['page']));
-			if(!$page){
-				$page = new WikiPage();
-				$page->label = $_['page'];
-			}
-			$category = WikiCategory::load(array('slug'=>$_['category']));
-
-			ob_start();
-			require_once(__DIR__.SLASH.'page.page.php');
-			$response['content'] = ob_get_clean();
-			$response['categorySlug'] = $category->slug;
-			if($page->id!=0) $response['pageSlug'] = $page->slug;
-		});
-	break;
-	
-	//Sauvegarde des configurations de wiki
-	case 'wiki_setting_save':
-		Action::write(function(&$response){
-			global $myUser,$_,$conf;
-			if(!$myUser->can('wiki','configure')) throw new Exception("Permissions insuffisantes",403);
-			foreach(Configuration::setting('wiki') as $key=>$value){
-				if(!is_array($value)) continue;
-				$allowed[] = $key;
-			}
-			foreach ($_['fields'] as $key => $value)
-				if(in_array($key, $allowed)) $conf->put($key,$value);
-
-			if(!empty($_FILES['logo']) && $_FILES['logo']['size']!=0 ){
-			    $logo = File::upload('logo','wiki'.SLASH.'logo'.SLASH.'logo.{{ext}}', 1048576, array('jpg','png','jpeg'));
-			    Image::resize($logo['absolute'], 38, 38, false);
-			    Image::toPng($logo['absolute']);
-			}
-		});
-	break;
-
-	case 'wiki_file_upload':
-		Action::write(function(&$response){
-			global $myUser,$_,$conf;
-			if(!$myUser->can('wiki','edit')) throw new Exception("Permissions insuffisantes",403);
-			require_once(__DIR__.SLASH.'WikiPage.class.php');
-			$uploads = WikiPage::uploads().SLASH;
-			if(!file_exists($uploads)) mkdir($uploads,0755,true);
-			$maxSize = $conf->get('wiki_max_size') * 1048576;
-			$extensions = explode(',',str_replace(' ', '', $conf->get('wiki_ext'))); 
-			$response['rows'] = array();
-
-			if(!is_array($_FILES['file']['name'])){
-				$_FILES['file']['name'] = array($_FILES['file']['name']);
-				$_FILES['file']['size'] = array($_FILES['file']['size']);
-				$_FILES['file']['tmp_name'] = array($_FILES['file']['tmp_name']);
-			}
-			for ($i=0; $i<count($_FILES['file']['name']); $i++) {
-
-				$extension = getExt($_FILES['file']['name'][$i]);
-				if($_FILES['file']['size'][$i] > $maxSize) throw new Exception("Taille du fichier ".$_FILES['file']['name'][$i]." trop grande, taille maximum :".readable_size($maxSize).' ('.$maxSize.' octets)');
-				if(!in_array($extension , $extensions)) throw new Exception("Extension '".$extension."' du fichier ".$_FILES['file']['name'][$i]." non permise, autorisé :".implode(', ',$extensions));
-				
-				$filePath = $uploads.wiki_os_encode($_FILES['file']['name'][$i]);
-
-				$u = 0;
-				while(file_exists($filePath)){
-					$u++;
-					$filePath = $uploads.$u.'_'.wiki_os_encode($_FILES['file']['name'][$i]);
-				}
-
-				rename($_FILES['file']['tmp_name'][$i],$filePath);
-
-				$row = array(
-					'name'=>$_FILES['file']['name'][$i],
-					'relative'=>str_replace($uploads,'',$filePath),
-					'absolute'=>$filePath,
-				);
-				
-				switch($extension){
-				case 'jpg':
-				case 'jpeg':
-				case 'gif':
-				case 'png':
-					$row['tag'] = '!['.$row['name'].'](action.php?action=wiki_file_read&file='.base64_encode($row['relative']).')';
-				break;
-				default:
-					$row['tag'] = '['.$row['name'].'](action.php?action=wiki_file_read&file='.base64_encode($row['relative']).')';
-				break;
-				}
-				$response['rows'][] = $row;
-				Log::put("Upload d'un élément : ".$filePath,'WIKI');
-			}
-		});
-	break;
-
-	case 'wiki_file_read':
-		global $myUser,$_,$conf;
-		if(!$myUser->can('wiki','read')) throw new Exception("Permissions insuffisantes",403);
-		File::downloadFile('file/wiki/uploads/'.base64_decode($_['file']));
-	break;
-
-	case 'wiki_night_mode':
-		Action::write(function(&$response){
-			global $myUser,$_,$conf;
-
-			$myUser->preference('wiki_night_mode', isset($_['nightmode']) && !empty($_['nightmode'])?true:false);
-			$myUser->loadPreferences();
-		});
-	break;
-
-}
-?>

+ 0 - 13
plugin/wiki/app.json

@@ -1,13 +0,0 @@
-{
-	"id": "fr.idleman.wiki",
-	"name": "Wiki",
-	"author" : {
-		"name" : "Valentin CARRUESCO",
-		"mail" : ""
-	},
-	"version": "1.0",
-	"url": "http://idleman.fr",
-	"licence": {"name": "Copyright","url" : ""},
-	"description": "Un simple wiki markdown permettant d'organiser des pages par catégorie.",
-	"require" : {}
-}

+ 0 - 973
plugin/wiki/css/main.css

@@ -1,973 +0,0 @@
-/** WIKI **/
-
-/* Conteneur principal du plugin wiki */
-.html.module-wiki *::selection {
-	color: #ffffff;
-	background-color: #444444;
-}
-.html.module-wiki {
-	height:100%;
-}
-.html.module-wiki body {
-	font-family: Roboto;
-	
-	color:rgb(23, 27, 53);
-	margin:0;
-	height:100%;
-	padding:0;
-}
-.wiki-preloader{
-    position: absolute;
-   	height:50px;
-   	width:150px;
-    top: 50%;
-    left: 50%;
-    margin-top: -25px;
-    margin-left: -75px;
-    bottom: 0;
-    right: 0;
-    text-align: center;
-    padding: 15px;
-    text-transform: uppercase;
-    transform: scale(0);
-    color: #4b569e;
-    opacity: 0;
-    transition: all 0.2s ease-in-out;
-    background: #ffffff;
-}
-.wiki-preloader.show{
-	opacity: 1;
-	transform: scale(1);
-	display:block;
-    
-}
-.wiki-preloader i{
-	font-size:40px;
-}
-.wiki-home-page {
-	padding-right: 25px;
-	width: 65%;
-}
-.wiki-home-page * {
-    max-width: 100%;
-}
-.wiki-home-activity {
-	width: 35%;
-}
-.wiki-home-page,
-.wiki-home-activity{
-	box-sizing: border-box;
-	display: inline-block;
-	vertical-align: top;
-	float: left
-}
-
-.wiki-login-box{
-	width:50%;
-	min-width: 200px;
-	max-width: 300px;
-	margin: auto;
-}
-.wiki-login-box i.wiki-logo{
-	width: 100%;
-    font-size: 80px;
-    text-align: center;
-    transition: all 0.3s ease-in-out;
-}
-.wiki-login-box:hover i.wiki-logo{
-	transform:translateY(-10px) scale(1.2);
-	text-shadow: 5px 5px orange;
-}
-.wiki-login-box .btn{
-	width:100%;
-	text-align: center;
-	font-weight: bold;
-}
-
-.wiki-login-box input{
-	margin:15px 0;
-	border: 2px solid #444444;
-	background: #ffffff;
-	text-align: center;
-}
-.wiki-login-box input:-webkit-autofill {
-    -webkit-box-shadow: 0 0 0 30px white inset;
-}
-.html.module-wiki #mainMenu {
-	display: none;
-	flex-direction: column;
-	flex-shrink: 0;
-	padding: 0;
-	margin: auto;
-	height: 100%;
-	width: 280px;
-	position: static;
-	color:rgb(78, 92, 110);
-	background: rgb(244, 247, 250);
-}
-.html.module-wiki .container-fluid{
-	padding: 0;
-	margin: 0;
-	width: auto;
-	min-height: 100%;
-	display: flex;
-	max-width: none;
-}
-.html.module-wiki #wiki-summary{
-	left: 0;
-	top: 0;
-	padding: 0;
-	margin: 0;
-	min-height: 100%;
-	width: 280px;
-	position: fixed;
-	box-sizing: border-box;
-	color:rgb(78, 92, 110);
-	background: rgb(244, 247, 250);
-	transition:all 0.2s ease-in-out;
-	transform: translateX(-280px);
-	z-index:101;
-    user-select: none;
-}
-.html.module-wiki #wiki-summary > h2{
-	padding: 10px;
-}
-.html.module-wiki #wiki-summary ul{
-	padding: 0px;
-	margin: 0;
-
-}
-.html.module-wiki #wiki-summary li{
-	border-bottom:1px solid #e9e9e9;
-	padding: 10px;
-	font-size: 12px;
-}
-
-.html.module-wiki #wiki-summary li a{
-	color:#444444;
-}
-.html.module-wiki #wiki-summary li.summary-h1{
-	text-transform: uppercase;
-}
-.html.module-wiki #wiki-summary li.summary-h2{
-	padding-left: 20px;
-}
-.html.module-wiki #wiki-summary li.summary-h3{
-	padding-left: 40px;
-}
-.html.module-wiki #wiki-summary li.summary-h4{
-	padding-left: 60px;
-}
-
-.html.module-wiki #wiki-summary ul li:before{
-	content: "\f0da";
-	font-family: "Font Awesome 5 Free";
-	font-size: 10px;
-	font-weight: 900;
-	padding-right: 5px;
-}
-.html.module-wiki #wiki-summary ul li{
-	list-style-type: none;
-	cursor: pointer;
-}
-.html.module-wiki #wiki-summary.show{
-	transform: translateX(0);
-}
-.html.module-wiki #sideMenu {
-	flex-direction: column;
-	flex-shrink: 0;
-	padding: 0;
-	margin: 0;
-	min-height: 100%;
-	width: 280px;
-	position: static;
-	color:rgb(78, 92, 110);
-	background: rgb(244, 247, 250);
-	user-select: none;
-	transition: background-color 0.2s ease-in-out;
-}
-.html.module-wiki #editor {
-	flex-direction: column;
-	padding: 60px;
-	width: 100%;
-	position: relative;
-	transition: background-color 0.2s ease-in-out;
-}
-.html.module-wiki .footer {
-	display:none;
-}
-.wiki-title{
-	font-size: 11px;
-    font-weight: 500;
-    text-transform: uppercase;
-    color: rgb(155, 166, 178);
-    letter-spacing: 0.04em;
-    padding-bottom: 8px;
-    margin-top: 30px;
-    margin-bottom: 10px;
-    border-bottom: 1px solid rgb(218, 225, 233);
-}
-.wiki-title + .no-pages {
-	opacity: 0.7;
-	padding: 15px 0;
-    line-height: 1.75em;
-}
-.wiki-small{
-    color: rgb(155, 166, 178);
-    font-size: 13px;
-}
-.category-recent{
-	margin: 0;
-	list-style-type: none;
-	padding: 0;
-}
-.category-recent > li{
-	margin: 0;
-	padding: 15px 0;
-	cursor: pointer;
-	opacity: 0.7;
-	transition: all 0.2s ease-in-out;
-}
-.category-recent > li:hover{
-	opacity: 1;
-	transform:translateX(10px);
-}
-.category-recent > li:before {
-	content: "\f105";
-	font-family: "Font Awesome 5 Free";
-	font-weight: 900;
-	opacity: 0;
-	transition: all 0.2s ease-in-out;
-	position: absolute;
-	left: -20px;
-	top: 50%;
-	transform: translateY(-50%);
-}
-.category-recent > li:hover:before {
-	opacity: 1;
-	/*transform: translateX(10px);*/
-}
-#wiki-modal{
-	width:100%;
-	height:100%;
-	position:absolute;
-	top:0;
-	left:0;
-	z-index: 101;
-	background:#ffffff;
-	transition: all 0.2s ease-in-out;
-	transform : translateX(-100%);
-	opacity : 0;
-	padding:15px;
-}
-.icon-bubble{
-	width: 20px;
-	padding-top: 3px;
-	box-sizing: border-box;
-	font-size: 10px;
-	text-align: center;
-    border-radius: 100%;
-    display: inline-block;
-    vertical-align: top;
-    background: #444444;
-    color: #ffffff;
-    height: 20px;
-}
-#wiki-modal .wiki-content{
-	max-width: 800px;
-	margin: auto;
-    padding-top: 20px;
-}
-#wiki-modal .close-button{
-	position: absolute;
-	right: 15px;
-	top: 15px;
-	cursor: pointer;
-}
-#wiki-modal.wiki-modal-open{
-	transform : translateX(0px);
-	opacity : 0.95;
-}
-.wiki-add-category{
-	cursor: pointer;
-	font-size: 12px;
-	font-weight: bold;
-	margin: 5px;
-
-}
-i.wiki-add-category{
-	transform: scale(0.8);
-	transition: all 0.2s ease-in-out;
-	opacity: 0.4;
-}
-i.wiki-add-category:hover {
-	transform: scale(1);
-	opacity: 1;
-}
-.wiki-header{
-	position: relative;
-    padding: 16px 24px;
-    transition: background 150ms ease-in-out 0s;
-}
-.wiki-header:hover{
-    background: rgba(0, 0, 0, 0.05);
-}
-.wiki-header .night-mode-toggler {
-	position: absolute;
-    top: 50%;
-    transform: translateY(-50%);
-    right: 24px;
-}
-/* TOGGLE NIGHT THEME */
-.toggle-box-label:empty {
-	margin-left: -10px;
-}
-.toggle-box-label:before,
-.toggle-box-label:after {
-	box-sizing: border-box;
-	margin: 0;
-	padding: 0;
-	/*transition*/
-	-webkit-transition: 0.25s ease-in-out;
-	-moz-transition: 0.25s ease-in-out;
-	-o-transition: 0.25s ease-in-out;
-	transition: 0.25s ease-in-out;
-	outline: none;
-}
-.toggle-box input[type=checkbox],
-.toggle-box input[type=checkbox]:active {
-	position: absolute;
-	top: -5000px;
-	height: 0;
-	width: 0;
-	opacity: 0;
-	border: none;
-	outline: none;
-}
-.toggle-box label {
-	display: inline-block;
-	position: relative;
-	padding: 0px;
-	margin-bottom: 0;
-	font-size: 14px;
-	line-height: 16px;
-	cursor: pointer;
-	color: rgba(149, 149, 149, 0.51);
-	font-weight: normal;
-}
-.toggle-box-label:before {
-	content: '';
-	display: block;
-	position: absolute;
-	z-index: 1;
-	line-height: 34px;
-	text-indent: 40px;
-	height: 16px;
-	width: 16px;
-	margin: 4px;
-	-webkit-border-radius: 100%;
-	-moz-border-radius: 100%;
-	border-radius: 100%;
-	right: 16px;
-	bottom: 0px;
-	background: #FFB200;
-	transform: rotate(-45deg);
-	box-shadow: 0 0 10px white;
-}
-.toggle-box-label:after {
-	content: "";
-	display: inline-block;
-	width: 40px;
-	height: 24px;
-	-webkit-border-radius: 16px;
-	-moz-border-radius: 16px;
-	border-radius: 16px;
-	background: rgba(255, 255, 255, 0.15);
-	vertical-align: middle;
-	margin: 0 10px;
-	margin-right: 0;
-	border: 2px solid #FFB200;
-}
-.toggle-box input[type=checkbox]:checked + .toggle-box-label:before {
-	right: 7px;
-	box-shadow: 5px 5px 0 0 #eee;
-	background: transparent;
-}
-.toggle-box input[type=checkbox]:checked + .toggle-box-label:after {
-	background: rgba(0, 0, 0, 0.15);
-	border: 2px solid white;
-}
-.toggle-box input[type=checkbox] + .toggle-box-label {
-	color: rgba(250, 250, 250, 0.51);
-	font-weight: bold;
-}
-.toggle-box input[type=checkbox]:checked + .toggle-box-label {
-	color: rgba(149, 149, 149, 0.51);
-	font-weight: normal;
-}
-.toggle-box input[type=checkbox]:checked + .toggle-box-label + .toggle-box-label {
-	color: rgba(250, 250, 250, 0.51);
-	font-weight: bold;
-}
-/* FIN TOGGLE NIGHT THEME */
-.page-editor-menu .dropdown-menu{
-    transform: translate3d(-129px, 19px, 0px)!important;
-}
-.wiki-logo{
-	width:38px;
-	height:38px;
-	border-radius: 4px;
-}
-.wiki-brand{
-    display: inline-block;
-    vertical-align: top;
-    min-height: 0px;
-    min-width: 0px;
-    padding-left: 8px;
-}
-.wiki-brand .brand-option{
-	display: inline-block;
-	cursor: pointer;
-}
-.wiki-brand-firm{
-    font-weight: 600;
-    color: rgb(23, 27, 53);
-    font-size: 16px;
-    max-height: 24px;
-}
-.wiki-brand-firm > .firm-label {
-    text-overflow: ellipsis;
-    max-width: 115px;
-    overflow: hidden;
-    display: inline-block;
-    margin-right: 5px;
-    font-size: 16px;
-    max-height: 24px;
-    white-space: nowrap;
-    float: left;
-}
-.wiki-brand-user{
-    font-size: 11px;
-    text-transform: uppercase;
-    font-weight: 500;
-    color: rgb(78, 92, 110);
-}
-.wiki-bubble{
-	color: rgb(255, 255, 255);
-    display: inline-block;
-    min-width: 15px;
-    font-size: 10px;
-    position: relative;
-    top: -2px;
-    left: 2px;
-    border-radius: 100%;
-    background: rgb(78, 92, 110);
-    padding: 0px 5px;
-}
-#wiki-categories {
-	min-height: 220px;
-}
-#wiki-main-menu,
-#wiki-categories {
-	margin: 24px 0px;
-    padding: 0px 24px;
-    overflow: auto;
-}
-#wiki-main-menu ul{
-	list-style-type: none;
-	margin: 0;
-	padding: 0;
-}
-#wiki-main-menu > ul > li{
-	padding: 5px;
-	cursor: pointer;
-    transition: all 0.1s ease-in-out;
-}
-#wiki-main-menu > ul > li i{
-	padding-right: 10px;
-}
-#wiki-main-menu > ul > li:hover{
-	color:#333333;
-}
-#wiki-categories ul{
-	list-style-type: none;
-	margin: 0;
-	padding: 0;
-}
-#wiki-categories ul > li > ul{
-	padding-left: 25px;
-	font-weight: normal;
-}
-#wiki-categories  > ul > li{
-	cursor: pointer;
-	padding: 5px;
-}
-#wiki-categories h3{
-    font-size: 11px;
-    font-weight: 600;
-    text-transform: uppercase;
-    color: rgb(78, 92, 110);
-    letter-spacing: 0.04em;
-    margin-bottom: 4px;
-}
-#wiki-categories  .category-option {
-	display: inline-block;
-	float: right;
-	cursor: pointer;
-	opacity: 0;
-	transform: translateX(-10px);
-	transition: all 0.2s ease-in-out;
-}
-#wiki-categories  > ul > li:hover .category-option{
-	transform: translateX(0px);
-	opacity: 1;
-}
-.wiki-page-content a{
-	color :#e91e63;
-}
-#wiki-categories .page{
-	font-size: 12px;
-	word-break: break-all;
-	white-space: nowrap; 
-	overflow: hidden;
-	text-overflow: ellipsis;
-}
-.wiki-page-content h1,.wiki-page-content h2,.wiki-page-content h3,.wiki-page-content h4,.wiki-page-content h5{
-	font-weight: 300;
-}
-.wiki-page-content .inline-code{
-	background-color: #444444;
-	padding:2px 5px;
-	color:#ffffff;
-}
-.wiki-page-content a .inline-code{
-	color:#ffc107;
-}
-.wiki-page-content .block-code{
-	background-color: #444444;
-	padding:2px 5px;
-	color:#ffffff;
-	display: block;
-	border-left: 5px solid #deeeff;
-}
-#editor .blockquote {
-    padding-left: 15px;
-    border-left: 5px solid #69abbd;
-}
-.html.module-wiki #editor.edition .CodeMirror{
-	border:0;
-}
-/*.html.module-wiki .CodeMirror-selectedtext {
-	color: #d0d0d0;
-	background-color: #444444;
-}*/
-.html.module-wiki .editor-toolbar:not(.fullscreen) {
-	position: -webkit-sticky; 
-	position: sticky; 
-	top: 0px; 
-	border:0;
-	background-color: #ffffff;
-	/*Make toolbar useable/visible when it becomes sticky*/
-	z-index: 100;
-	opacity: initial; 
-}
-/* Hovering would otherwise cause the text to become visible 'behind' the toolbar*/
-.html.module-wiki .editor-toolbar:hover {
-	opacity: initial;
-}
-.html.module-wiki .page-option{
-	display: inline-block;
-	position: fixed;
-	right: 20px;
-	top: 20px;
-	z-index: 105;
-    user-select: none;
-}
-.html.module-wiki .page-option li.page-empty-menu {
-	min-width: 16px;
-}
-.html.module-wiki .page-option li {
-	cursor: pointer;
-	display: inline-block;
-	transition: all 0.2s ease-in-out;
-}
-.html.module-wiki .page-option li .page-option-item > i {
-	opacity: 0.5;
-	transition: all 0.2s ease-in-out;
-}
-.html.module-wiki .page-option li .page-option-item > i.active,
-.html.module-wiki .page-option li .page-option-item.show > i,
-.html.module-wiki .page-option li:hover .page-option-item > i {
-	opacity: 1;
-}
-.html.module-wiki .page-option-save,
-.html.module-wiki .page-option-menu {
-	transform: scale(0);
-	opacity: 0;
-	cursor: pointer;
-	transition: all 0.2s ease-in-out;
-}
-.html.module-wiki .page-option-save.show,
-.html.module-wiki .page-option-menu.shown {
-	opacity: 1;
-	transform: scale(1);
-}
-.html.module-wiki li.hidden {
-	display: none;
-}
-.html.module-wiki .page-option-save > i {
-	color: #45B94F;
-}
-.html.module-wiki .page-option-save:hover > i {
-	color: #308237;
-}
-.category-open{
-	font-weight: bold;
-	color:#333333;
-}
-.page-open{
-	font-weight: bold;
-	color:#333333;
-}
-.wiki-breadcrumb {
-    position: absolute;
-    color: rgb(23, 27, 53);
-    font-size: 15px;
-    top: 20px;
-    left: 25px;	
-    user-select: none;
-    width: 90%;
-    transition: all 0.2s ease-in-out;
-}
-.wiki-breadcrumb span{
-	font-weight: bold;
-}
-.wiki-breadcrumb small{
-	color: #a8aeb4;
-	margin-left: 10px;
-}
-.wiki-breadcrumb .slash{
-	font-weight: bolder;
-	text-align: center;
-	color: rgb(154, 180, 214);
-	width: 10px;
-	height: 24px;
-	display: inline-block;
-}
-.wiki-breadcrumb .last-update {
-	margin-left: 0;
-}
-#page-label{
-	border-color: #ffffff;
-	display: inline-block;
-	width: auto;
-	padding:0;
-	transition: all 0.2s ease-in-out;
-}
-
-#page-label.form-control[readonly] {
-    background-color: #ffffff;
-}
-
-#page-label.show{
-	padding: 0 10px;
-	border-color: #cecece;
-	border-radius: 2rem;
-	transition: all 0.2s ease-in-out;
-}
-.tags-container {
-	margin: 15px 0;
-	padding-bottom: 5px;
-	display: block;
-}
-.tags-container.light-border-bottom {
-	border-bottom: 1px solid rgb(218, 225, 233);
-}
-.tags-container h6 {
-	color: rgb(155, 166, 178);
-}
-.tags-container .tag-item {
-	user-select: none;
-	display: inline-block;
-	padding: 0.4em 0.5em;
-	font-size: 85%;
-	font-weight: 700;
-	line-height: 1;
-	text-align: center;
-	white-space: nowrap;
-	vertical-align: baseline;
-	border-radius: .15rem;
-	padding-right: 1rem;
-	position: relative;
-}
-.tags-container .tag-item .fa-tag {
-	font-size: 0.8em;
-}
-.tags-container .tag-item .delete-tag {
-	cursor: pointer;
-	opacity: 1;
-	position: absolute;
-	right: 0.25rem;
-	transition: all 0.2s ease-in-out;
-}
-.tags-container .tag-item:hover .delete-tag {
-	opacity: 1;
-}
-.wiki_search_item i{
-	min-height: 26px;
-}
-.wiki_search_item input{
-	display: inline-block;
-	min-width: 160px;
-	border: 0;
-	padding: 0 5px;
-	border-radius: 2rem;
-	width: 85%;
-	opacity: 0;
-	font-size: 0.9em;
-	transition: all 0.2s ease-in-out;
-}
-.wiki_search_item span{
-	opacity: 1;
-	min-height: 26px;
-	display: inline-block;
-}
-.wiki-search-highlight{
-	background-color:#ffc107;
-	color:#111111;
-	display: inline-block;
-	padding:0 5px;
-	font-weight: bold;
-}
-
-.wiki-title-link{
-	font-size: 20px;
-	margin-left:10px;
-	display: inline-block;
-	opacity: 0;
-	cursor:pointer;
-	transition:all 0.2s ease-in-out;
-    transform: translateX(-10px);
-}
-#editor h1:hover .wiki-title-link,
-#editor h2:hover .wiki-title-link,
-#editor h3:hover .wiki-title-link,
-#editor h4:hover .wiki-title-link,
-#editor h5:hover .wiki-title-link,
-#editor h6:hover .wiki-title-link{
-	opacity: 1;
-	transform: rotate(0deg);
-}
-
-/* NIGHT MODE */
-.html.module-wiki.night-mode *::selection {
-	color: #2f3136;
-	background-color: #ffffff;
-}
-.html.module-wiki.night-mode {
-	color: #7e8082;
-}
-.html.module-wiki.night-mode #sideMenu {
-	color: #7e8082;
-	background-color: #2f3136;
-	transition: background-color 0.2s ease-in-out;
-}
-.html.module-wiki.night-mode #editor {
-	color: #D0D0D0;
-    background-color: #36393f;
-	transition: background-color 0.2s ease-in-out;
-}
-.html.module-wiki.night-mode .wiki-logo{
-	border: 1px solid #d0d0d0;
-}
-.html.module-wiki.night-mode .wiki-brand-user,
-.html.module-wiki.night-mode #wiki-categories h3 {
-	color: #727983;
-}
-.html.module-wiki.night-mode .wiki_search_item input,
-.html.module-wiki.night-mode #page-label.show {
-	background-color: #36393f;
-	border-color: #d0d0d0;
-	color: #d0d0d0;
-	transition: all 0.2s ease-in-out;
-}
-.html.module-wiki.night-mode #page-label {
-	background-color: #36393f;
-	border-color: #36393f;
-	color: #d0d0d0;
-	transition: all 0.2s ease-in-out;
-}
-.html.module-wiki.night-mode .wiki-breadcrumb {
-	color: #d0d0d0;
-	transition: all 0.2s ease-in-out;
-}
-.html.module-wiki.night-mode .wiki-brand-firm,
-.html.module-wiki.night-mode #wiki-main-menu > ul > li:hover,
-.html.module-wiki.night-mode .wiki-add-category:hover,
-.html.module-wiki.night-mode .category-open,
-.html.module-wiki.night-mode .page-open,
-.html.module-wiki.night-mode .dropdown-icon-item,
-.html.module-wiki.night-mode .dropdown-item,
-.html.module-wiki.night-mode #wiki-summary li a {
-    color: #d0d0d0;
-}
-.html.module-wiki.night-mode .dropdown-menu {
-	background-color: #36393f;
-    border-color: #616161;
-}
-.html.module-wiki.night-mode .dropdown-divider {
-	border-color: #616161;
-}
-.html.module-wiki.night-mode #wiki-modal {
-	color: #d0d0d0;
-    background: #36393f;
-}
-.html.module-wiki.night-mode .dropdown-item:hover {
-    background-color: #616161;
-}
-.html.module-wiki.night-mode .component-icon .dropdown-icon-item:focus,
-.html.module-wiki.night-mode .component-icon .dropdown-icon-item:active,
-.html.module-wiki.night-mode .component-icon .dropdown-icon-item:hover {
-    color: #484848;
-    background-color: #d6d6d6;
-    border-radius: 3px;
-}
-.html.module-wiki.night-mode .icon-chooser button {
-	color: #484848;
-	background-color: #d6d6d6;
-	border-color: #d6d6d6;
-}
-.html.module-wiki.night-mode .icon-chooser button:hover {
-	color: #2e2e2e;
-	background-color: #b0b0b0;
-	border-color: #b0b0b0;
-}
-.html.module-wiki.night-mode .form-control:focus {
-	border-color: #ced4da;
-}
-.html.module-wiki.night-mode .btn-primary,
-.html.module-wiki.night-mode .btn-success {
-    color: #474747;
-    background-color: #cecece;
-    border-color: #cecece;
-}
-.html.module-wiki.night-mode .btn-primary:hover,
-.html.module-wiki.night-mode .btn-success:hover {
-	color: #2e2e2e;
-	background-color: #b0b0b0;
-	border-color: #b0b0b0;
-}
-.html.module-wiki.night-mode .page-option-save:hover > i {
-	color: hsl(125, 46%, 75%);
-}
-.html.module-wiki.night-mode #wiki-summary {
-	background-color: #2f3136;
-	color: #d0d0d0;
-}
-.html.module-wiki.night-mode #wiki-summary li {
-    border-bottom: 1px solid #a5a5a5;
-}
-.html.module-wiki.night-mode #editor.edition .CodeMirror {
-	background: #36393f;
-	color: #d0d0d0;
-}
-.html.module-wiki.night-mode .CodeMirror-selectedtext {
-	color: #36393f;
-}
-.html.module-wiki.night-mode .CodeMirror-cursor {
-    border-left: 1px solid #efefef;
-}
-.html.module-wiki.night-mode .CodeMirror .CodeMirror-code .cm-comment {
-    background: #2f3136;
-    color: #e8e8e8;
-}
-.html.module-wiki.night-mode .editor-preview-side {
-    background-color: #36393f;
-    border: none;
-    border-left: 1px solid #ddd;
-}
-.html.module-wiki.night-mode .wiki-page-content a,
-.html.module-wiki.night-mode .CodeMirror .CodeMirror-code .cm-link {
-    color: #a18fff;
-}
-.html.module-wiki.night-mode .wiki-page-content pre code {
-    color: #ffd969;
-}
-.html.module-wiki.night-mode .editor-preview-side pre {
-    background: #2f3136;
-}
-.html.module-wiki.night-mode .editor-toolbar {
-	opacity: 1;
-}
-.html.module-wiki.night-mode .editor-toolbar.fullscreen:before,
-.html.module-wiki.night-mode .editor-toolbar.fullscreen:after {
-	width: 0;
-	height: 0;
-}
-.html.module-wiki.night-mode .editor-toolbar,
-.html.module-wiki.night-mode .editor-toolbar.fullscreen {
-    background: #36393f;
-}
-.html.module-wiki.night-mode .editor-toolbar a {
-    color: #d0d0d0!important;
-}
-.html.module-wiki.night-mode .editor-toolbar a.active,
-.html.module-wiki.night-mode .editor-toolbar a:hover {
-    background: #2f3136;
-    border-color: #d0d0d0;
-}
-
-.html.module-wiki.night-mode input,
-.html.module-wiki.night-mode textarea,
-.html.module-wiki.night-mode *[contenteditable] {
-  color: #d0d0d0;
-  caret-color: #d0d0d0;
-}
-.html.module-wiki.night-mode hr {
-    border-top: 1px solid rgba(255, 255, 255, 0.5);
-}
-/* FIN NIGHT MODE */
-
-
-#drag-overlay,
-#preloader-upload-container {
-	position: fixed; /* Sit on top of the page content */
-	display: none; /* Hidden by default */
-	width: 100%; /* Full width (cover the whole page) */
-	height: 100%; /* Full height (cover the whole page) */
-	top: 0; 
-	left: 0;
-	right: 0;
-	bottom: 0;
-	background-color: rgba(0,0,0,0.7); /* Black background with opacity */
-	z-index: 10000; /* Specify a stack order in case you're using a different order for other elements */
-	cursor: pointer; /* Add a pointer on hover */
-}
-#overlay-text,
-#preloader-upload-text {
-	position: absolute;
-	top: 50%;
-	left: 50%;
-	font-size: 30px;
-	color: white;
-	transform: translate(-50%,-50%);
-	-ms-transform: translate(-50%,-50%);
-	z-index: 10000;
-}
-#overlay-icon {
-	position: absolute;
-	top: 52%;
-	left: 49%;
-	font-size: 60px;
-	font-weight: lighter;
-	color: #F7D007FF;
-	transform: translate(-49%,-52%);
-	-ms-transform: translate(-49%,-52%);
-	z-index: 10000;
-	animation: floating 1.5s ease-in-out infinite;
-}
-@keyframes floating {
-	from {transform: translate(0,  0px); }
-	50%  {transform: translate(0, 15px); }
-	to   {transform: translate(0, -0px); }    
-}

File diff suppressed because it is too large
+ 0 - 6
plugin/wiki/css/simplemde.min.css


BIN
plugin/wiki/img/logo.png


+ 0 - 573
plugin/wiki/js/main.js

@@ -1,573 +0,0 @@
-var wikiEditor;
-
-//CHARGEMENT DE LA PAGE
-function init_plugin_wiki(){
-	wiki_category_search(function(){
-		var page = $.urlParam('page');
-		var category = $.urlParam('category');
-		
-		if(page && page!=''){
-			wiki_category_open(category,true,function(){
-				wiki_page_open(category,page);
-			});
-		} else if(category && category!=''){
-			wiki_category_open(category);
-		} else{
-			wiki_page_home();
-		}
-	});
-
-	$(document).ready(function(e){
-		//Champ recherche
-		$('.wiki_search_item input').blur(function(){
-			$('.wiki_search_item span').show().animate({opacity: '1'}, 150);
-			$(this).animate({opacity: '0'}, 150).hide();
-		}).keyup(function(e){
-			if(e.keyCode != 13) return;
-			wiki_search_item($(this));
-		});
-
-		//Smooth scrolling pour sommaire
-		var docRoot = $('html, body');
-		$('#wiki-summary').on('click', 'a[href^="#"]',function(e) {
-		    var href = $.attr(this, 'href');
-
-		    docRoot.animate({
-		        scrollTop: $(href).offset().top
-		    }, 500, function () {
-		        window.location.hash = href;
-		    });
-
-		    return false;
-		});
-
-		//Night mode
-		if($('#night-mode-check').attr('checked') && $('#night-mode-check').attr('checked').length)
-			$('html.module-wiki').addClass('night-mode');
-	});
-
-	wiki_document_upload();
-
-	//Récuperation des images en presse papier et upload 
-	window.addEventListener("paste", function(event){
-		var items = event.clipboardData.items;
-		if(event.clipboardData == false || event.clipboardData.items == undefined) return;
-	    var items = event.clipboardData.items;
-
-	    if( items.length == 0) return;
-	    var ajaxData = new FormData();
-	    for (var i = 0; i < items.length; i++) {
-	        if (items[i].type.indexOf("image") == -1) continue;
-	        var file = items[i].getAsFile();
-			ajaxData.append('file', file);
-	    }
-
-	     $.ajax({
-			url: 'action.php?action=wiki_file_upload',
-			type: 'POST',
-			data: ajaxData,
-			dataType: 'json',
-			cache: false,
-			contentType: false,
-			processData: false,
-			complete: function (data) {},
-			success: function (response) {
-				
-				if(response.error) return $.message('error',response.error,0);
-					var value = wikiEditor.value();
-					for(var k in response.rows){
-						var file = response.rows[k];
-						value += "\n"+file.tag;
-					}
-					wikiEditor.value(value);
-			},
-			error: function (data) {
-				$.message('error',data);
-			}
-		});
-	}, false);
-}
-
-function toggle_night_mode(element){
-	var wikiPage = $('html.module-wiki');
-	var data = {action: 'wiki_night_mode'};
-
-	if(element.checked){
-		data.nightmode = true;
-		wikiPage.addClass('night-mode');
-	} else {
-		wikiPage.removeClass('night-mode');
-	}
-	$.action(data, function(r){});
-}
-
-function wiki_search_item(input){
-	var input = $(input);
-	// if(!input.val().length) return
-	$('.search-title').length ? $('.category-recent').html('') : $('#editor').html('');
-	$('.wiki-preloader').addClass('show');
-	$.action({
-		action : 'wiki_page_search',
-		term : input.val(),
-	},function(r){
-		$('.wiki-preloader').removeClass('show');
-		$('#editor').html(r.content);
-	});
-}
-
-//Suppression d'un tag liée à la recherche en cours
-function wiki_tag_delete(element){
-	var tag = $(element).closest('span.tag-item');
-	// tag.remove();
-	var searchInput = $('.wiki_search_item input');
-	var tagKeyword = tag.text().trim();
-
-	var keywords = searchInput.val();
-	searchInput.val(keywords.replace(tagKeyword, '').trim());
-	
-	wiki_search_item(searchInput);
-}
-
-function wiki_change_url(category,page){
-	var url = window.location.pathname+'?module=wiki';
-	if(category) {
-		url +='&category='+category;
-		$('#wiki-categories .category').removeClass('category-open');
-		$('#wiki-categories .category[data-category="'+category+'"]').addClass('category-open');
-	}
-	if(page){
-		url += '&page='+page;
-		$('#wiki-categories .page').removeClass('page-open');
-		$('#wiki-categories .page[data-page="'+page+'"]').addClass('page-open');
-	}
-	url+= window.location.hash;
-	window.history.replaceState(null, null, url);
-}
-
-//Enregistrement des configurations
-function wiki_setting_save(){
-	var data = {}
-	data.action = 'wiki_setting_save';
-	data.fields = $('#wiki-setting-form').toJson();
-	data.logo = $('#wiki_logo')[0].files[0];
-
-	$.action(data,function(r){
-		$.message('success','Enregistré');
-	});
-}
-
-//Suppression image générale application
-function wiki_logo_delete(element){
-	if(!confirm('Êtes-vous sûr de vouloir supprimer l\'image ?')) return;
-	var imageComposer = $(element).parent().find("input[data-type='image']");
-	
-	$.action({
-	    action: 'wiki_logo_delete'
-	}, function(r){
-	    imageComposer.wrap('<form>').closest('form').get(0).reset();
-	    imageComposer.unwrap();
-	    $(element).next('img').attr('src', $(imageComposer).attr('data-default-src'));
-	    $(element).remove();
-	    $.message('info', 'Image supprimée');
-	});
-}
-
-/** COMMON **/
-function wiki_modal_open(data,callback){
-	var modal = $('#wiki-modal');
-	if(modal.length==0){
-		var modal = $('<div id="wiki-modal" class=""><i onclick="wiki_modal_close();" class="fa fa-times close-button"></i><div class="wiki-content"></div></div>');
-		$('body').append(modal);
-	}
-
-	modal.find('.wiki-content').load('action.php?action='+data.action,data,function(){
-		if(callback) callback();
-		init_components('#wiki-modal');
-		modal.addClass('wiki-modal-open');
-	});
-}
-
-function wiki_search(){
-	$('.wiki_search_item span').animate({opacity: '0'}, 150).hide();
-	$('.wiki_search_item input').show().animate({opacity: '1'}, 150).focus();
-}
-
-function wiki_modal_close(){
-	$('#wiki-modal').removeClass('wiki-modal-open');
-	$('body').removeClass('modal-open');
-}
-
-/** CATEGORY **/
-function wiki_category_search(callback){
-	$('#categories').fill({
-		action:'wiki_category_search'
-	},function(){
-		if(callback!=null) callback();
-	});
-}
-
-function wiki_category_edit(element,event){
-	event.stopPropagation();
-	var li = $(element).closest('li');
-
-	wiki_modal_open({
-		action : 'wiki_category_edit',
-		id : li.attr('data-id')
-	},function(){
-		$('body').addClass('modal-open');
-		$('#category-form #label').focus();
-	});
-}
-
-function wiki_category_save(){
-	var data = $('#category-form').toJson();
-	$.action(data,function(r){
-		$('body').removeClass('modal-open');
-		wiki_category_search();
-		wiki_modal_close();
-		wiki_category_open(r.slug);
-	});
-}
-
-function wiki_category_open(category,onlyTree,callback){
-	var tpl = $('#pageModel li').get(0).outerHTML;
-
-	$.action({
-		action : 'wiki_category_open',
-		category : category
-	},function(r){
-		var line = $('#wiki-categories .category[data-category="'+category+'"]');
-		$('#wiki-categories .category').removeClass('category-open').find('li').remove();
-		line.addClass('category-open').removeClass('category-closed');
-		
-		if(!onlyTree) $('#editor').html(r.content);
-		var ul = line.find('ul');
-		ul.find('li').remove();
-		var html = '';
-		for(var key in r.pages){
-			var page = r.pages[key];
-			page.categorySlug = r.categorySlug;
-			html+=Mustache.render(tpl,page);
-		}
-		ul.append(html);
-		wiki_change_url(r.categorySlug);
-		if(callback) callback();
-	});
-}
-
-function wiki_category_delete(element,event){
-	event.stopPropagation();
-	if(!confirm('Êtes-vous sûr de vouloir supprimer cette catégorie ?')) return;
-	var line = $(element).closest('li');
-	$.action({
-		action : 'wiki_category_delete',
-		id : line.attr('data-id')
-	},function(r){
-		line.remove();
-		$.message('info', 'Catégorie supprimée');
-	});
-}
-
-function wiki_category_download(element,event){
-	var line = $(element).closest('li');
-	window.location = 'action.php?action=wiki_category_download&category='+line.attr('data-id');
-}
-
-
-/** PAGE **/
-function wiki_page_summary(element){
-	var togglerIcon = $(element);
-	if($('#wiki-summary').hasClass('show')) {
-		togglerIcon.removeClass('active');
-		$('#wiki-summary').removeClass('show');
-	} else {
-		togglerIcon.addClass('active');
-		$('#wiki-summary').addClass('show');
-	}
-}
-function wiki_page_open(category,page,event,callback){
-	if(event) event.stopPropagation();
-
-	$.action({
-		action : 'wiki_page_open',
-		category : category,
-		page : page,
-	},function(r){
-		wiki_category_open(r.categorySlug, true, function(){
-			var summary = $('#wiki-summary ul');
-			var htmlSummary = '';
-		
-			$('#editor').html(r.content);
-			$('#editor').find('h1,h2,h3,h4,h5').each(function(i,title){
-				var tag = title.tagName.toLowerCase();
-				var title = $(title);
-				var slug = title.text().trim().toLowerCase().replace(/[^a-z]/ig,'-').replace(/[-]{2,}/ig,'-');
-				
-				title.addClass('pointer').attr({
-					'onclick': "wiki_copy_anchor(this);",
-					'id': slug
-				}).append('<i class="fas fa-link wiki-title-link"></i>');
-				
-				if(parseInt(tag.replace('h',''))<5) htmlSummary += '<li class="summary-'+tag+'"><a href="#'+slug+'">'+title.text()+'</a></li>';
-			});
-
-			summary.html(htmlSummary);
-			wiki_change_url(r.categorySlug,r.pageSlug);
-			if(window.location.hash!='' && $(window.location.hash).length>0)
-				$(window.location.hash).get(0).scrollIntoView();
-		});
-		if(callback) callback();
-	});
-}
-
-function wiki_copy_anchor(anchor){
-	var id = anchor.id;
-	var url = window.location.href.replace(/#.*/ig,'')+'#'+id;
-	var temp = $('<input>');
-	$('body').append(temp);
-	temp.val(url).select();
-	document.execCommand('copy');
-	temp.remove();
-	$.message('info','Adresse copiée dans le presse papier');
-}
-
-function wiki_page_home(){
-	$.action({
-		action : 'wiki_page_home',
-	},function(r){
-		$('#wiki-categories .category').removeClass('category-open').find('li').remove();
-		wiki_change_url();
-		$('#editor').html(r.content);
-	});
-}
-
-function wiki_page_edit(){
-	$('.wiki-page-content #content-text').show();
-	$('.wiki-page-content #content-html').hide();
-
-	var pageLabel = $('#page-label');
-	pageLabel.attr('contenteditable','true').addClass('show');
-
-	$('.page-option-menu').removeClass('shown');
-	$('.page-editor-menu').addClass('hidden');
-
-	$('.page-save-menu').removeClass('hidden');
-	$('.page-option-save').addClass('show');
-
-	$('#editor').addClass('edition');
-	wikiEditor = new SimpleMDE({ 
-		element: $("#content-text")[0],
-		spellChecker : false,
-		promptURLs: true,
-		autofocus : true,
-		showIcons: ["code", "table","heading-2","heading-3","clean-block","horizontal-rule"]
-	});
-}
-
-function wiki_page_add(event){
-	event.stopPropagation();
-	$.action({
-		action : 'wiki_page_save',
-		category : $('.category-open').attr('data-id'),
-		content : 'Mon contenu ici...',
-	},function(r){
-		wiki_category_open(r.category.slug,true,function(){
-			wiki_page_open(r.category.slug,r.page.slug,event,function(){
-				wiki_page_edit();
-			});
-		});
-	});
-}
-
-//Ajout ou modification d'élément page
-function wiki_page_save(){
-
-	$.action({
-		action : 'wiki_page_save',
-		id : $('.wiki-breadcrumb').attr('data-page'),
-		label : $('#page-label').text(),
-		content : wikiEditor.value(),
-	},function(r){
-		$('.page-option-save,#page-label').removeClass('show');
-		$('.page-option-menu').addClass('shown');
-		$('#editor').html(r.content).show();
-	
-		$('#page-label').removeAttr('contenteditable','true');
-		$('#editor').removeClass('edition');
-	});
-}
-
-//Suppression d'élement page
-function wiki_page_delete(element){
-	if(!confirm('Êtes vous sûr de vouloir supprimer cette page ?')) return;
-	
-	$.action({
-		action : 'wiki_page_delete',
-		id : $('.wiki-breadcrumb').attr('data-page')
-	},function(r){
-		$.message('info', 'Page supprimée');
-		wiki_category_open(r.category.slug);
-	});
-}
-
-function wiki_page_download(){
-	window.location = 'action.php?action=wiki_page_download&page='+$('.wiki-breadcrumb').attr('data-page');
-}
-
-
-function wiki_document_upload(){
-	var form = $('#upload-button form');
-	var input = form.find('input[type="file"]');
-	var zone = $('.container-fluid');
-	var droppedFiles = null;
-	var noBlink = null;
-	input.hide();
-	$('#upload-button > div').click(function(e){
-		input.trigger('click');
-		e.preventDefault();
-		e.stopPropagation();
-	});
-	input.on('change', function (e) {
-		if(!$('#editor').hasClass('edition')) return;
-		if(input.attr('data-label') == 1){
-			//Ajout de fichiers avec libellés
-			$("#file-modal").remove();
-			if(!$('#file-modal').length) {
-				var modal = '<div class="modal fade file-modal" id="file-modal" tabindex="-1" role="dialog" aria-labelledby="file-modal-label" aria-hidden="true"><div class="modal-dialog modal-lg" role="document"><div class="modal-content"><div class="modal-header"><h5 class="modal-title" id="file-modal-label">Libellé par fichier</h5><button type="button" class="close" data-dismiss="modal" aria-label="Fermer"><span aria-hidden="true">&times;</span></button></div><div class="modal-body"><section id="file-form" class="file-form">';
-				var tmpData = new FormData(form.get(0));
-
-				//Obligé de passer par l'event pour IE
-				if(!e.target.files) return;
-				$(e.target.files).each(function(index, value) {
-					modal += '<div class="form-group"><label class="form-control-label">'+value.name+'</label><input required onkeyup="if($(this).hasClass(\'label-required\')){$(this).removeClass(\'label-required\'); $(this).attr(\'placeholder\', \'Libellé\')}" type="text" value="" placeholder="Libellé" class="form-control" id="'+slugify(value.name)+'" name="'+slugify(value.name)+'"/></div>';
-				});
-				modal += '</section></div><div class="modal-footer"><div class="btn btn-light" data-dismiss="modal">Fermer</div><div class="btn btn-success" onclick="if(check_label_filled()){$(\'#upload-button form\').trigger(\'submit\'); $(\'#file-modal\').remove(); $(\'div.modal-backdrop\').remove(); $(\'body\').removeAttr(\'style\').removeClass(\'modal-open\'); $(\'#mainMenu, #mainMenu > button\').removeAttr(\'style\');}"><i class="fas fa-check"></i> Enregistrer</div></div></div></div></div>';
-				$('div.document-container').after(modal);
-				$('#file-modal:hidden').modal('show');
-			}
-			$("#file-modal").on("hidden.bs.modal", function () {
-				input.val('');
-			});
-		} else {
-			filesNb = $(this)[0].files.length;
-			//Sans libellés
-			form.trigger('submit');
-		}
-	});
-
-	zone.on('drag dragstart dragend dragover dragenter dragleave drop', function (e) {
-		e.preventDefault();
-		e.stopPropagation();
-	})
-	.on('dragover dragenter', function (e) {
-		if(!$('#editor').hasClass('edition')) return;
-		clearTimeout(noBlink);
-		$('#drag-overlay').css('display', 'block');
-		zone.addClass('drag-over');
-		e.preventDefault();
-		e.stopPropagation();
-	})
-	.on('dragleave dragend drop', function (e) {
-		if(!$('#editor').hasClass('edition')) return;
-		noBlink = setTimeout(function(){
-			$('#drag-overlay').css('display', 'none');
-			zone.removeClass('drag-over');
-		},500);
-
-		e.preventDefault();
-		e.stopPropagation();
-	})
-	.on('drop', function (e) {
-		if(!$('#editor').hasClass('edition')) return;
-		if(!e.originalEvent.dataTransfer) return;
-		droppedFiles = e.originalEvent.dataTransfer.files;
-		filesNb = droppedFiles.length;
-		for (var i=0, f; f=droppedFiles[i]; ++i) {
-			if(!f.type){
-				$.message('error', 'Impossible d\'envoyer un dossier / un élément sans extension.');
-				return;
-			}
-			if(!f.type && f.size%4096 == 0) {
-				$.message('error', 'Impossible d\'envoyer un dossier.');
-				return;
-			}
-		}
-		if(input.attr('data-label') == 1){
-			//Ajout de fichiers avec libellés
-			$("#file-modal").remove();
-			if(!$('#file-modal').length) {
-				var modal = '<div class="modal fade file-modal" id="file-modal" tabindex="-1" role="dialog" aria-labelledby="file-modal-label" aria-hidden="true"><div class="modal-dialog modal-lg" role="document"><div class="modal-content"><div class="modal-header"><h5 class="modal-title" id="file-modal-label">Libellé par fichier</h5><button type="button" class="close" data-dismiss="modal" aria-label="Fermer"><span aria-hidden="true">&times;</span></button></div><div class="modal-body"><section id="file-form" class="file-form">';
-
-				$(droppedFiles).each(function(index, value) {
-					modal += '<div class="form-group"><label class="form-control-label">'+value.name+'</label><input required onkeyup="if($(this).hasClass(\'label-required\')){$(this).removeClass(\'label-required\'); $(this).attr(\'placeholder\', \'Libellé\')}" type="text" value="" placeholder="Libellé" class="form-control" id="'+slugify(value.name)+'" name="'+slugify(value.name)+'"/></div>';
-				});
-				modal += '</section></div><div class="modal-footer"><div class="btn btn-light" data-dismiss="modal">Fermer</div><div class="btn btn-success" onclick="if(check_label_filled()){$(\'#upload-button form\').trigger(\'submit\'); $(\'#file-modal\').remove(); $(\'div.modal-backdrop\').remove(); $(\'body\').removeAttr(\'style\').removeClass(\'modal-open\'); $(\'#mainMenu, #mainMenu > button\').removeAttr(\'style\');}"><i class="fas fa-check"></i> Enregistrer</div></div></div></div></div>';
-				$('div.document-container').after(modal);
-				$('#file-modal:hidden').modal('show');
-			}
-			$("#file-modal").on("hidden.bs.modal", function () {
-				input.val('');
-			});
-		} else {
-			//Sans libellés
-			form.trigger('submit');
-		}
-	});
-
-	form.on('submit', function (e) {
-		e.preventDefault();   
-		if(!$('#editor').hasClass('edition')) return;
-		var ajaxData = new FormData();
-		
-		if (droppedFiles) {    	
-			$.each(droppedFiles, function (i, file) {
-				ajaxData.append(input.attr('name'), file);
-			});
-		} else {
-			ajaxData = new FormData(form.get(0));
-		}
-		ajaxData.append('path', $('#file-elements').attr('data-current-path'));
-		droppedFiles = null;
-
-		//Gestion libellés de fichiers
-		if(input.attr('data-label') == 1){
-			var labels = $('#file-modal input');
-			var labelData = {};
-			labels.each(function(i, label){
-				var label = $(label);
-				labelData[label.attr('id')] = label.val();
-			});
-			var labelArr = JSON.stringify(labelData);
-			ajaxData.append('labels', labelArr);
-		}
-		
-		$.ajax({
-			url: form.attr('action'),
-			type: form.attr('method'),
-			data: ajaxData,
-			dataType: 'json',
-			cache: false,
-			contentType: false,
-			processData: false,
-			complete: function (data) {},
-			success: function (response) {
-				
-				if(response.error) return $.message('error',response.error,0);
-				var resultMsg = filesNb==1 ? '1 document envoyé' : filesNb+' documents envoyés';
-				
-					var value = wikiEditor.value();
-					for(var k in response.rows){
-						var file = response.rows[k];
-						value += "\n"+file.tag;
-					}
-
-					wikiEditor.value(value);
-				
-				$.message('info',resultMsg);
-			},
-			error: function (data) {
-				$.message('error',data);
-			}
-		});
-	});
-}

File diff suppressed because it is too large
+ 0 - 6
plugin/wiki/js/simplemde.min.js


+ 0 - 1981
plugin/wiki/lib/Parsedown.php

@@ -1,1981 +0,0 @@
-<?php
-
-#
-#
-# Parsedown
-# http://parsedown.org
-#
-# (c) Emanuil Rusev
-# http://erusev.com
-#
-# For the full license information, view the LICENSE file that was distributed
-# with this source code.
-#
-#
-
-class Parsedown
-{
-    # ~
-
-    const version = '1.8.0-beta-5';
-
-    # ~
-
-    function text($text)
-    {
-        $Elements = $this->textElements($text);
-
-        # convert to markup
-        $markup = $this->elements($Elements);
-
-        # trim line breaks
-        $markup = trim($markup, "\n");
-
-        return $markup;
-    }
-
-    protected function textElements($text)
-    {
-        # make sure no definitions are set
-        $this->DefinitionData = array();
-
-        # standardize line breaks
-        $text = str_replace(array("\r\n", "\r"), "\n", $text);
-
-        # remove surrounding line breaks
-        $text = trim($text, "\n");
-
-        # split text into lines
-        $lines = explode("\n", $text);
-
-        # iterate through lines to identify blocks
-        return $this->linesElements($lines);
-    }
-
-    #
-    # Setters
-    #
-
-    function setBreaksEnabled($breaksEnabled)
-    {
-        $this->breaksEnabled = $breaksEnabled;
-
-        return $this;
-    }
-
-    protected $breaksEnabled;
-
-    function setMarkupEscaped($markupEscaped)
-    {
-        $this->markupEscaped = $markupEscaped;
-
-        return $this;
-    }
-
-    protected $markupEscaped;
-
-    function setUrlsLinked($urlsLinked)
-    {
-        $this->urlsLinked = $urlsLinked;
-
-        return $this;
-    }
-
-    protected $urlsLinked = true;
-
-    function setSafeMode($safeMode)
-    {
-        $this->safeMode = (bool) $safeMode;
-
-        return $this;
-    }
-
-    protected $safeMode;
-
-    function setStrictMode($strictMode)
-    {
-        $this->strictMode = (bool) $strictMode;
-
-        return $this;
-    }
-
-    protected $strictMode;
-
-    protected $safeLinksWhitelist = array(
-        'http://',
-        'https://',
-        'ftp://',
-        'ftps://',
-        'mailto:',
-        'data:image/png;base64,',
-        'data:image/gif;base64,',
-        'data:image/jpeg;base64,',
-        'irc:',
-        'ircs:',
-        'git:',
-        'ssh:',
-        'news:',
-        'steam:',
-    );
-
-    #
-    # Lines
-    #
-
-    protected $BlockTypes = array(
-        '#' => array('Header'),
-        '*' => array('Rule', 'List'),
-        '+' => array('List'),
-        '-' => array('SetextHeader', 'Table', 'Rule', 'List'),
-        '0' => array('List'),
-        '1' => array('List'),
-        '2' => array('List'),
-        '3' => array('List'),
-        '4' => array('List'),
-        '5' => array('List'),
-        '6' => array('List'),
-        '7' => array('List'),
-        '8' => array('List'),
-        '9' => array('List'),
-        ':' => array('Table'),
-        '<' => array('Comment', 'Markup'),
-        '=' => array('SetextHeader'),
-        '>' => array('Quote'),
-        '[' => array('Reference'),
-        '_' => array('Rule'),
-        '`' => array('FencedCode'),
-        '|' => array('Table'),
-        '~' => array('FencedCode'),
-    );
-
-    # ~
-
-    protected $unmarkedBlockTypes = array(
-        'Code',
-    );
-
-    #
-    # Blocks
-    #
-
-    protected function lines(array $lines)
-    {
-        return $this->elements($this->linesElements($lines));
-    }
-
-    protected function linesElements(array $lines)
-    {
-        $Elements = array();
-        $CurrentBlock = null;
-
-        foreach ($lines as $line)
-        {
-            if (chop($line) === '')
-            {
-                if (isset($CurrentBlock))
-                {
-                    $CurrentBlock['interrupted'] = (isset($CurrentBlock['interrupted'])
-                        ? $CurrentBlock['interrupted'] + 1 : 1
-                    );
-                }
-
-                continue;
-            }
-
-            while (($beforeTab = strstr($line, "\t", true)) !== false)
-            {
-                $shortage = 4 - mb_strlen($beforeTab, 'utf-8') % 4;
-
-                $line = $beforeTab
-                    . str_repeat(' ', $shortage)
-                    . substr($line, strlen($beforeTab) + 1)
-                ;
-            }
-
-            $indent = strspn($line, ' ');
-
-            $text = $indent > 0 ? substr($line, $indent) : $line;
-
-            # ~
-
-            $Line = array('body' => $line, 'indent' => $indent, 'text' => $text);
-
-            # ~
-
-            if (isset($CurrentBlock['continuable']))
-            {
-                $methodName = 'block' . $CurrentBlock['type'] . 'Continue';
-                $Block = $this->$methodName($Line, $CurrentBlock);
-
-                if (isset($Block))
-                {
-                    $CurrentBlock = $Block;
-
-                    continue;
-                }
-                else
-                {
-                    if ($this->isBlockCompletable($CurrentBlock['type']))
-                    {
-                        $methodName = 'block' . $CurrentBlock['type'] . 'Complete';
-                        $CurrentBlock = $this->$methodName($CurrentBlock);
-                    }
-                }
-            }
-
-            # ~
-
-            $marker = $text[0];
-
-            # ~
-
-            $blockTypes = $this->unmarkedBlockTypes;
-
-            if (isset($this->BlockTypes[$marker]))
-            {
-                foreach ($this->BlockTypes[$marker] as $blockType)
-                {
-                    $blockTypes []= $blockType;
-                }
-            }
-
-            #
-            # ~
-
-            foreach ($blockTypes as $blockType)
-            {
-                $Block = $this->{"block$blockType"}($Line, $CurrentBlock);
-
-                if (isset($Block))
-                {
-                    $Block['type'] = $blockType;
-
-                    if ( ! isset($Block['identified']))
-                    {
-                        if (isset($CurrentBlock))
-                        {
-                            $Elements[] = $this->extractElement($CurrentBlock);
-                        }
-
-                        $Block['identified'] = true;
-                    }
-
-                    if ($this->isBlockContinuable($blockType))
-                    {
-                        $Block['continuable'] = true;
-                    }
-
-                    $CurrentBlock = $Block;
-
-                    continue 2;
-                }
-            }
-
-            # ~
-
-            if (isset($CurrentBlock) and $CurrentBlock['type'] === 'Paragraph')
-            {
-                $Block = $this->paragraphContinue($Line, $CurrentBlock);
-            }
-
-            if (isset($Block))
-            {
-                $CurrentBlock = $Block;
-            }
-            else
-            {
-                if (isset($CurrentBlock))
-                {
-                    $Elements[] = $this->extractElement($CurrentBlock);
-                }
-
-                $CurrentBlock = $this->paragraph($Line);
-
-                $CurrentBlock['identified'] = true;
-            }
-        }
-
-        # ~
-
-        if (isset($CurrentBlock['continuable']) and $this->isBlockCompletable($CurrentBlock['type']))
-        {
-            $methodName = 'block' . $CurrentBlock['type'] . 'Complete';
-            $CurrentBlock = $this->$methodName($CurrentBlock);
-        }
-
-        # ~
-
-        if (isset($CurrentBlock))
-        {
-            $Elements[] = $this->extractElement($CurrentBlock);
-        }
-
-        # ~
-
-        return $Elements;
-    }
-
-    protected function extractElement(array $Component)
-    {
-        if ( ! isset($Component['element']))
-        {
-            if (isset($Component['markup']))
-            {
-                $Component['element'] = array('rawHtml' => $Component['markup']);
-            }
-            elseif (isset($Component['hidden']))
-            {
-                $Component['element'] = array();
-            }
-        }
-
-        return $Component['element'];
-    }
-
-    protected function isBlockContinuable($Type)
-    {
-        return method_exists($this, 'block' . $Type . 'Continue');
-    }
-
-    protected function isBlockCompletable($Type)
-    {
-        return method_exists($this, 'block' . $Type . 'Complete');
-    }
-
-    #
-    # Code
-
-    protected function blockCode($Line, $Block = null)
-    {
-        if (isset($Block) and $Block['type'] === 'Paragraph' and ! isset($Block['interrupted']))
-        {
-            return;
-        }
-
-        if ($Line['indent'] >= 4)
-        {
-            $text = substr($Line['body'], 4);
-
-            $Block = array(
-                'element' => array(
-                    'name' => 'pre',
-                    'element' => array(
-                        'name' => 'code',
-                        'attributes' => array('class' => "block-code"), /* + hackpoint */
-                        'text' => $text,
-                    ),
-                ),
-            );
-
-            return $Block;
-        }
-    }
-
-    protected function blockCodeContinue($Line, $Block)
-    {
-        if ($Line['indent'] >= 4)
-        {
-            if (isset($Block['interrupted']))
-            {
-                $Block['element']['element']['text'] .= str_repeat("\n", $Block['interrupted']);
-
-                unset($Block['interrupted']);
-            }
-
-            $Block['element']['element']['text'] .= "\n";
-
-            $text = substr($Line['body'], 4);
-
-            $Block['element']['element']['text'] .= $text;
-
-            return $Block;
-        }
-    }
-
-    protected function blockCodeComplete($Block)
-    {
-        return $Block;
-    }
-
-    #
-    # Comment
-
-    protected function blockComment($Line)
-    {
-        if ($this->markupEscaped or $this->safeMode)
-        {
-            return;
-        }
-
-        if (strpos($Line['text'], '<!--') === 0)
-        {
-            $Block = array(
-                'element' => array(
-                    'rawHtml' => $Line['body'],
-                    'autobreak' => true,
-                ),
-            );
-
-            if (strpos($Line['text'], '-->') !== false)
-            {
-                $Block['closed'] = true;
-            }
-
-            return $Block;
-        }
-    }
-
-    protected function blockCommentContinue($Line, array $Block)
-    {
-        if (isset($Block['closed']))
-        {
-            return;
-        }
-
-        $Block['element']['rawHtml'] .= "\n" . $Line['body'];
-
-        if (strpos($Line['text'], '-->') !== false)
-        {
-            $Block['closed'] = true;
-        }
-
-        return $Block;
-    }
-
-    #
-    # Fenced Code
-
-    protected function blockFencedCode($Line)
-    {
-        $marker = $Line['text'][0];
-
-        $openerLength = strspn($Line['text'], $marker);
-
-        if ($openerLength < 3)
-        {
-            return;
-        }
-
-        $infostring = trim(substr($Line['text'], $openerLength), "\t ");
-
-        if (strpos($infostring, '`') !== false)
-        {
-            return;
-        }
-
-        $Element = array(
-            'name' => 'code',
-            'attributes' => array('class' => "block-code"), /* + hackpoint */
-            'text' => '',
-        );
-
-        if ($infostring !== '')
-        {
-            $Element['attributes'] = array('class' => "language-$infostring");
-        }
-
-        $Block = array(
-            'char' => $marker,
-            'openerLength' => $openerLength,
-            'element' => array(
-                'name' => 'pre',
-                'element' => $Element,
-            ),
-        );
-
-        return $Block;
-    }
-
-    protected function blockFencedCodeContinue($Line, $Block)
-    {
-        if (isset($Block['complete']))
-        {
-            return;
-        }
-
-        if (isset($Block['interrupted']))
-        {
-            $Block['element']['element']['text'] .= str_repeat("\n", $Block['interrupted']);
-
-            unset($Block['interrupted']);
-        }
-
-        if (($len = strspn($Line['text'], $Block['char'])) >= $Block['openerLength']
-            and chop(substr($Line['text'], $len), ' ') === ''
-        ) {
-            $Block['element']['element']['text'] = substr($Block['element']['element']['text'], 1);
-
-            $Block['complete'] = true;
-
-            return $Block;
-        }
-
-        $Block['element']['element']['text'] .= "\n" . $Line['body'];
-
-        return $Block;
-    }
-
-    protected function blockFencedCodeComplete($Block)
-    {
-        return $Block;
-    }
-
-    #
-    # Header
-
-    protected function blockHeader($Line)
-    {
-        $level = strspn($Line['text'], '#');
-
-        if ($level > 6)
-        {
-            return;
-        }
-
-        $text = trim($Line['text'], '#');
-
-        if ($this->strictMode and isset($text[0]) and $text[0] !== ' ')
-        {
-            return;
-        }
-
-        $text = trim($text, ' ');
-
-        $Block = array(
-            'element' => array(
-                'name' => 'h' . min(6, $level),
-                'handler' => array(
-                    'function' => 'lineElements',
-                    'argument' => $text,
-                    'destination' => 'elements',
-                )
-            ),
-        );
-
-        return $Block;
-    }
-
-    #
-    # List
-
-    protected function blockList($Line, array $CurrentBlock = null)
-    {
-        list($name, $pattern) = $Line['text'][0] <= '-' ? array('ul', '[*+-]') : array('ol', '[0-9]{1,9}+[.\)]');
-
-        if (preg_match('/^('.$pattern.'([ ]++|$))(.*+)/', $Line['text'], $matches))
-        {
-            $contentIndent = strlen($matches[2]);
-
-            if ($contentIndent >= 5)
-            {
-                $contentIndent -= 1;
-                $matches[1] = substr($matches[1], 0, -$contentIndent);
-                $matches[3] = str_repeat(' ', $contentIndent) . $matches[3];
-            }
-            elseif ($contentIndent === 0)
-            {
-                $matches[1] .= ' ';
-            }
-
-            $markerWithoutWhitespace = strstr($matches[1], ' ', true);
-
-            $Block = array(
-                'indent' => $Line['indent'],
-                'pattern' => $pattern,
-                'data' => array(
-                    'type' => $name,
-                    'marker' => $matches[1],
-                    'markerType' => ($name === 'ul' ? $markerWithoutWhitespace : substr($markerWithoutWhitespace, -1)),
-                ),
-                'element' => array(
-                    'name' => $name,
-                    'elements' => array(),
-                ),
-            );
-            $Block['data']['markerTypeRegex'] = preg_quote($Block['data']['markerType'], '/');
-
-            if ($name === 'ol')
-            {
-                $listStart = ltrim(strstr($matches[1], $Block['data']['markerType'], true), '0') ?: '0';
-
-                if ($listStart !== '1')
-                {
-                    if (
-                        isset($CurrentBlock)
-                        and $CurrentBlock['type'] === 'Paragraph'
-                        and ! isset($CurrentBlock['interrupted'])
-                    ) {
-                        return;
-                    }
-
-                    $Block['element']['attributes'] = array('start' => $listStart);
-                }
-            }
-
-            $Block['li'] = array(
-                'name' => 'li',
-                'handler' => array(
-                    'function' => 'li',
-                    'argument' => !empty($matches[3]) ? array($matches[3]) : array(),
-                    'destination' => 'elements'
-                )
-            );
-
-            $Block['element']['elements'] []= & $Block['li'];
-
-            return $Block;
-        }
-    }
-
-    protected function blockListContinue($Line, array $Block)
-    {
-        if (isset($Block['interrupted']) and empty($Block['li']['handler']['argument']))
-        {
-            return null;
-        }
-
-        $requiredIndent = ($Block['indent'] + strlen($Block['data']['marker']));
-
-        if ($Line['indent'] < $requiredIndent
-            and (
-                (
-                    $Block['data']['type'] === 'ol'
-                    and preg_match('/^[0-9]++'.$Block['data']['markerTypeRegex'].'(?:[ ]++(.*)|$)/', $Line['text'], $matches)
-                ) or (
-                    $Block['data']['type'] === 'ul'
-                    and preg_match('/^'.$Block['data']['markerTypeRegex'].'(?:[ ]++(.*)|$)/', $Line['text'], $matches)
-                )
-            )
-        ) {
-            if (isset($Block['interrupted']))
-            {
-                $Block['li']['handler']['argument'] []= '';
-
-                $Block['loose'] = true;
-
-                unset($Block['interrupted']);
-            }
-
-            unset($Block['li']);
-
-            $text = isset($matches[1]) ? $matches[1] : '';
-
-            $Block['indent'] = $Line['indent'];
-
-            $Block['li'] = array(
-                'name' => 'li',
-                'handler' => array(
-                    'function' => 'li',
-                    'argument' => array($text),
-                    'destination' => 'elements'
-                )
-            );
-
-            $Block['element']['elements'] []= & $Block['li'];
-
-            return $Block;
-        }
-        elseif ($Line['indent'] < $requiredIndent and $this->blockList($Line))
-        {
-            return null;
-        }
-
-        if ($Line['text'][0] === '[' and $this->blockReference($Line))
-        {
-            return $Block;
-        }
-
-        if ($Line['indent'] >= $requiredIndent)
-        {
-            if (isset($Block['interrupted']))
-            {
-                $Block['li']['handler']['argument'] []= '';
-
-                $Block['loose'] = true;
-
-                unset($Block['interrupted']);
-            }
-
-            $text = substr($Line['body'], $requiredIndent);
-
-            $Block['li']['handler']['argument'] []= $text;
-
-            return $Block;
-        }
-
-        if ( ! isset($Block['interrupted']))
-        {
-            $text = preg_replace('/^[ ]{0,'.$requiredIndent.'}+/', '', $Line['body']);
-
-            $Block['li']['handler']['argument'] []= $text;
-
-            return $Block;
-        }
-    }
-
-    protected function blockListComplete(array $Block)
-    {
-        if (isset($Block['loose']))
-        {
-            foreach ($Block['element']['elements'] as &$li)
-            {
-                if (end($li['handler']['argument']) !== '')
-                {
-                    $li['handler']['argument'] []= '';
-                }
-            }
-        }
-
-        return $Block;
-    }
-
-    #
-    # Quote
-
-    protected function blockQuote($Line)
-    {
-        if (preg_match('/^>[ ]?+(.*+)/', $Line['text'], $matches))
-        {
-            $Block = array(
-                'element' => array(
-                    'name' => 'blockquote',
-                    'attributes' => array('class' => "blockquote"), /* + hackpoint */
-                    'handler' => array(
-                        'function' => 'linesElements',
-                        'argument' => (array) $matches[1],
-                        'destination' => 'elements',
-                    )
-                ),
-            );
-
-            return $Block;
-        }
-    }
-
-    protected function blockQuoteContinue($Line, array $Block)
-    {
-        if (isset($Block['interrupted']))
-        {
-            return;
-        }
-
-        if ($Line['text'][0] === '>' and preg_match('/^>[ ]?+(.*+)/', $Line['text'], $matches))
-        {
-            $Block['element']['handler']['argument'] []= $matches[1];
-
-            return $Block;
-        }
-
-        if ( ! isset($Block['interrupted']))
-        {
-            $Block['element']['handler']['argument'] []= $Line['text'];
-
-            return $Block;
-        }
-    }
-
-    #
-    # Rule
-
-    protected function blockRule($Line)
-    {
-        $marker = $Line['text'][0];
-
-        if (substr_count($Line['text'], $marker) >= 3 and chop($Line['text'], " $marker") === '')
-        {
-            $Block = array(
-                'element' => array(
-                    'name' => 'hr',
-                ),
-            );
-
-            return $Block;
-        }
-    }
-
-    #
-    # Setext
-
-    protected function blockSetextHeader($Line, array $Block = null)
-    {
-        if ( ! isset($Block) or $Block['type'] !== 'Paragraph' or isset($Block['interrupted']))
-        {
-            return;
-        }
-
-        if ($Line['indent'] < 4 and chop(chop($Line['text'], ' '), $Line['text'][0]) === '')
-        {
-            $Block['element']['name'] = $Line['text'][0] === '=' ? 'h1' : 'h2';
-
-            return $Block;
-        }
-    }
-
-    #
-    # Markup
-
-    protected function blockMarkup($Line)
-    {
-        if ($this->markupEscaped or $this->safeMode)
-        {
-            return;
-        }
-
-        if (preg_match('/^<[\/]?+(\w*)(?:[ ]*+'.$this->regexHtmlAttribute.')*+[ ]*+(\/)?>/', $Line['text'], $matches))
-        {
-            $element = strtolower($matches[1]);
-
-            if (in_array($element, $this->textLevelElements))
-            {
-                return;
-            }
-
-            $Block = array(
-                'name' => $matches[1],
-                'element' => array(
-                    'rawHtml' => $Line['text'],
-                    'autobreak' => true,
-                ),
-            );
-
-            return $Block;
-        }
-    }
-
-    protected function blockMarkupContinue($Line, array $Block)
-    {
-        if (isset($Block['closed']) or isset($Block['interrupted']))
-        {
-            return;
-        }
-
-        $Block['element']['rawHtml'] .= "\n" . $Line['body'];
-
-        return $Block;
-    }
-
-    #
-    # Reference
-
-    protected function blockReference($Line)
-    {
-        if (strpos($Line['text'], ']') !== false
-            and preg_match('/^\[(.+?)\]:[ ]*+<?(\S+?)>?(?:[ ]+["\'(](.+)["\')])?[ ]*+$/', $Line['text'], $matches)
-        ) {
-            $id = strtolower($matches[1]);
-
-            $Data = array(
-                'url' => $matches[2],
-                'title' => isset($matches[3]) ? $matches[3] : null,
-            );
-
-            $this->DefinitionData['Reference'][$id] = $Data;
-
-            $Block = array(
-                'element' => array(),
-            );
-
-            return $Block;
-        }
-    }
-
-    #
-    # Table
-
-    protected function blockTable($Line, array $Block = null)
-    {
-        if ( ! isset($Block) or $Block['type'] !== 'Paragraph' or isset($Block['interrupted']))
-        {
-            return;
-        }
-
-        if (
-            strpos($Block['element']['handler']['argument'], '|') === false
-            and strpos($Line['text'], '|') === false
-            and strpos($Line['text'], ':') === false
-            or strpos($Block['element']['handler']['argument'], "\n") !== false
-        ) {
-            return;
-        }
-
-        if (chop($Line['text'], ' -:|') !== '')
-        {
-            return;
-        }
-
-        $alignments = array();
-
-        $divider = $Line['text'];
-
-        $divider = trim($divider);
-        $divider = trim($divider, '|');
-
-        $dividerCells = explode('|', $divider);
-
-        foreach ($dividerCells as $dividerCell)
-        {
-            $dividerCell = trim($dividerCell);
-
-            if ($dividerCell === '')
-            {
-                return;
-            }
-
-            $alignment = null;
-
-            if ($dividerCell[0] === ':')
-            {
-                $alignment = 'left';
-            }
-
-            if (substr($dividerCell, - 1) === ':')
-            {
-                $alignment = $alignment === 'left' ? 'center' : 'right';
-            }
-
-            $alignments []= $alignment;
-        }
-
-        # ~
-
-        $HeaderElements = array();
-
-        $header = $Block['element']['handler']['argument'];
-
-        $header = trim($header);
-        $header = trim($header, '|');
-
-        $headerCells = explode('|', $header);
-
-        if (count($headerCells) !== count($alignments))
-        {
-            return;
-        }
-
-        foreach ($headerCells as $index => $headerCell)
-        {
-            $headerCell = trim($headerCell);
-
-            $HeaderElement = array(
-                'name' => 'th',
-                'handler' => array(
-                    'function' => 'lineElements',
-                    'argument' => $headerCell,
-                    'destination' => 'elements',
-                )
-            );
-
-            if (isset($alignments[$index]))
-            {
-                $alignment = $alignments[$index];
-
-                $HeaderElement['attributes'] = array(
-                    'style' => "text-align: $alignment;"
-                );
-            }
-
-            $HeaderElements []= $HeaderElement;
-        }
-
-        # ~
-
-        $Block = array(
-            'alignments' => $alignments,
-            'identified' => true,
-            'element' => array(
-                'name' => 'table',
-                'attributes' => array('class' => "table"), /* + hackpoint */
-                'elements' => array(),
-            ),
-        );
-
-        $Block['element']['elements'] []= array(
-            'name' => 'thead',
-        );
-
-        $Block['element']['elements'] []= array(
-            'name' => 'tbody',
-            'elements' => array(),
-        );
-
-        $Block['element']['elements'][0]['elements'] []= array(
-            'name' => 'tr',
-            'elements' => $HeaderElements,
-        );
-
-        return $Block;
-    }
-
-    protected function blockTableContinue($Line, array $Block)
-    {
-        if (isset($Block['interrupted']))
-        {
-            return;
-        }
-
-        if (count($Block['alignments']) === 1 or $Line['text'][0] === '|' or strpos($Line['text'], '|'))
-        {
-            $Elements = array();
-
-            $row = $Line['text'];
-
-            $row = trim($row);
-            $row = trim($row, '|');
-
-            preg_match_all('/(?:(\\\\[|])|[^|`]|`[^`]++`|`)++/', $row, $matches);
-
-            $cells = array_slice($matches[0], 0, count($Block['alignments']));
-
-            foreach ($cells as $index => $cell)
-            {
-                $cell = trim($cell);
-
-                $Element = array(
-                    'name' => 'td',
-                    'handler' => array(
-                        'function' => 'lineElements',
-                        'argument' => $cell,
-                        'destination' => 'elements',
-                    )
-                );
-
-                if (isset($Block['alignments'][$index]))
-                {
-                    $Element['attributes'] = array(
-                        'style' => 'text-align: ' . $Block['alignments'][$index] . ';',
-                    );
-                }
-
-                $Elements []= $Element;
-            }
-
-            $Element = array(
-                'name' => 'tr',
-                'elements' => $Elements,
-            );
-
-            $Block['element']['elements'][1]['elements'] []= $Element;
-
-            return $Block;
-        }
-    }
-
-    #
-    # ~
-    #
-
-    protected function paragraph($Line)
-    {
-        return array(
-            'type' => 'Paragraph',
-            'element' => array(
-                'name' => 'p',
-                'handler' => array(
-                    'function' => 'lineElements',
-                    'argument' => $Line['text'],
-                    'destination' => 'elements',
-                ),
-            ),
-        );
-    }
-
-    protected function paragraphContinue($Line, array $Block)
-    {
-        if (isset($Block['interrupted']))
-        {
-            return;
-        }
-
-        $Block['element']['handler']['argument'] .= "\n".$Line['text'];
-
-        return $Block;
-    }
-
-    #
-    # Inline Elements
-    #
-
-    protected $InlineTypes = array(
-        '!' => array('Image'),
-        '&' => array('SpecialCharacter'),
-        '*' => array('Emphasis'),
-        ':' => array('Url'),
-        '<' => array('UrlTag', 'EmailTag', 'Markup'),
-        '[' => array('Link'),
-        '_' => array('Emphasis'),
-        '`' => array('Code'),
-        '~' => array('Strikethrough'),
-        '\\' => array('EscapeSequence'),
-    );
-
-    # ~
-
-    protected $inlineMarkerList = '!*_&[:<`~\\';
-
-    #
-    # ~
-    #
-
-    public function line($text, $nonNestables = array())
-    {
-        return $this->elements($this->lineElements($text, $nonNestables));
-    }
-
-    protected function lineElements($text, $nonNestables = array())
-    {
-        $Elements = array();
-
-        $nonNestables = (empty($nonNestables)
-            ? array()
-            : array_combine($nonNestables, $nonNestables)
-        );
-
-        # $excerpt is based on the first occurrence of a marker
-
-        while ($excerpt = strpbrk($text, $this->inlineMarkerList))
-        {
-            $marker = $excerpt[0];
-
-            $markerPosition = strlen($text) - strlen($excerpt);
-
-            $Excerpt = array('text' => $excerpt, 'context' => $text);
-
-            foreach ($this->InlineTypes[$marker] as $inlineType)
-            {
-                # check to see if the current inline type is nestable in the current context
-
-                if (isset($nonNestables[$inlineType]))
-                {
-                    continue;
-                }
-
-                $Inline = $this->{"inline$inlineType"}($Excerpt);
-
-                if ( ! isset($Inline))
-                {
-                    continue;
-                }
-
-                # makes sure that the inline belongs to "our" marker
-
-                if (isset($Inline['position']) and $Inline['position'] > $markerPosition)
-                {
-                    continue;
-                }
-
-                # sets a default inline position
-
-                if ( ! isset($Inline['position']))
-                {
-                    $Inline['position'] = $markerPosition;
-                }
-
-                # cause the new element to 'inherit' our non nestables
-
-
-                $Inline['element']['nonNestables'] = isset($Inline['element']['nonNestables'])
-                    ? array_merge($Inline['element']['nonNestables'], $nonNestables)
-                    : $nonNestables
-                ;
-
-                # the text that comes before the inline
-                $unmarkedText = substr($text, 0, $Inline['position']);
-
-                # compile the unmarked text
-                $InlineText = $this->inlineText($unmarkedText);
-                $Elements[] = $InlineText['element'];
-
-                # compile the inline
-                $Elements[] = $this->extractElement($Inline);
-
-                # remove the examined text
-                $text = substr($text, $Inline['position'] + $Inline['extent']);
-
-                continue 2;
-            }
-
-            # the marker does not belong to an inline
-
-            $unmarkedText = substr($text, 0, $markerPosition + 1);
-
-            $InlineText = $this->inlineText($unmarkedText);
-            $Elements[] = $InlineText['element'];
-
-            $text = substr($text, $markerPosition + 1);
-        }
-
-        $InlineText = $this->inlineText($text);
-        $Elements[] = $InlineText['element'];
-
-        foreach ($Elements as &$Element)
-        {
-            if ( ! isset($Element['autobreak']))
-            {
-                $Element['autobreak'] = false;
-            }
-        }
-
-        return $Elements;
-    }
-
-    #
-    # ~
-    #
-
-    protected function inlineText($text)
-    {
-        $Inline = array(
-            'extent' => strlen($text),
-            'element' => array(),
-        );
-
-        $Inline['element']['elements'] = self::pregReplaceElements(
-            $this->breaksEnabled ? '/[ ]*+\n/' : '/(?:[ ]*+\\\\|[ ]{2,}+)\n/',
-            array(
-                array('name' => 'br'),
-                array('text' => "\n"),
-            ),
-            $text
-        );
-
-        return $Inline;
-    }
-
-    protected function inlineCode($Excerpt)
-    {
-        $marker = $Excerpt['text'][0];
-
-        if (preg_match('/^(['.$marker.']++)[ ]*+(.+?)[ ]*+(?<!['.$marker.'])\1(?!'.$marker.')/s', $Excerpt['text'], $matches))
-        {
-            $text = $matches[2];
-            $text = preg_replace('/[ ]*+\n/', ' ', $text);
-
-            return array(
-                'extent' => strlen($matches[0]),
-                'element' => array(
-                    'name' => 'code',
-                    'attributes' => array('class' => "inline-code"), /* + hackpoint */
-                    'text' => $text,
-                ),
-            );
-        }
-    }
-
-    protected function inlineEmailTag($Excerpt)
-    {
-        $hostnameLabel = '[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?';
-
-        $commonMarkEmail = '[a-zA-Z0-9.!#$%&\'*+\/=?^_`{|}~-]++@'
-            . $hostnameLabel . '(?:\.' . $hostnameLabel . ')*';
-
-        if (strpos($Excerpt['text'], '>') !== false
-            and preg_match("/^<((mailto:)?$commonMarkEmail)>/i", $Excerpt['text'], $matches)
-        ){
-            $url = $matches[1];
-
-            if ( ! isset($matches[2]))
-            {
-                $url = "mailto:$url";
-            }
-
-            return array(
-                'extent' => strlen($matches[0]),
-                'element' => array(
-                    'name' => 'a',
-                    'text' => $matches[1],
-                    'attributes' => array(
-                        'href' => $url,
-                    ),
-                ),
-            );
-        }
-    }
-
-    protected function inlineEmphasis($Excerpt)
-    {
-        if ( ! isset($Excerpt['text'][1]))
-        {
-            return;
-        }
-
-        $marker = $Excerpt['text'][0];
-
-        if ($Excerpt['text'][1] === $marker and preg_match($this->StrongRegex[$marker], $Excerpt['text'], $matches))
-        {
-            $emphasis = 'strong';
-        }
-        elseif (preg_match($this->EmRegex[$marker], $Excerpt['text'], $matches))
-        {
-            $emphasis = 'em';
-        }
-        else
-        {
-            return;
-        }
-
-        return array(
-            'extent' => strlen($matches[0]),
-            'element' => array(
-                'name' => $emphasis,
-                'handler' => array(
-                    'function' => 'lineElements',
-                    'argument' => $matches[1],
-                    'destination' => 'elements',
-                )
-            ),
-        );
-    }
-
-    protected function inlineEscapeSequence($Excerpt)
-    {
-        if (isset($Excerpt['text'][1]) and in_array($Excerpt['text'][1], $this->specialCharacters))
-        {
-            return array(
-                'element' => array('rawHtml' => $Excerpt['text'][1]),
-                'extent' => 2,
-            );
-        }
-    }
-
-    protected function inlineImage($Excerpt)
-    {
-        if ( ! isset($Excerpt['text'][1]) or $Excerpt['text'][1] !== '[')
-        {
-            return;
-        }
-
-        $Excerpt['text']= substr($Excerpt['text'], 1);
-
-        $Link = $this->inlineLink($Excerpt);
-
-        if ($Link === null)
-        {
-            return;
-        }
-
-        $Inline = array(
-            'extent' => $Link['extent'] + 1,
-            'element' => array(
-                'name' => 'img',
-                'attributes' => array(
-                    'src' => $Link['element']['attributes']['href'],
-                    'alt' => $Link['element']['handler']['argument'],
-                ),
-                'autobreak' => true,
-            ),
-        );
-
-        $Inline['element']['attributes'] += $Link['element']['attributes'];
-
-        unset($Inline['element']['attributes']['href']);
-
-        return $Inline;
-    }
-
-    protected function inlineLink($Excerpt)
-    {
-        $Element = array(
-            'name' => 'a',
-            'handler' => array(
-                'function' => 'lineElements',
-                'argument' => null,
-                'destination' => 'elements',
-            ),
-            'nonNestables' => array('Url', 'Link'),
-            'attributes' => array(
-                'href' => null,
-                'title' => null,
-            ),
-        );
-
-        $extent = 0;
-
-        $remainder = $Excerpt['text'];
-
-        if (preg_match('/\[((?:[^][]++|(?R))*+)\]/', $remainder, $matches))
-        {
-            $Element['handler']['argument'] = $matches[1];
-
-            $extent += strlen($matches[0]);
-
-            $remainder = substr($remainder, $extent);
-        }
-        else
-        {
-            return;
-        }
-
-        if (preg_match('/^[(]\s*+((?:[^ ()]++|[(][^ )]+[)])++)(?:[ ]+("[^"]*+"|\'[^\']*+\'))?\s*+[)]/', $remainder, $matches))
-        {
-            $Element['attributes']['href'] = $matches[1];
-
-            if (isset($matches[2]))
-            {
-                $Element['attributes']['title'] = substr($matches[2], 1, - 1);
-            }
-
-            $extent += strlen($matches[0]);
-        }
-        else
-        {
-            if (preg_match('/^\s*\[(.*?)\]/', $remainder, $matches))
-            {
-                $definition = strlen($matches[1]) ? $matches[1] : $Element['handler']['argument'];
-                $definition = strtolower($definition);
-
-                $extent += strlen($matches[0]);
-            }
-            else
-            {
-                $definition = strtolower($Element['handler']['argument']);
-            }
-
-            if ( ! isset($this->DefinitionData['Reference'][$definition]))
-            {
-                return;
-            }
-
-            $Definition = $this->DefinitionData['Reference'][$definition];
-
-            $Element['attributes']['href'] = $Definition['url'];
-            $Element['attributes']['title'] = $Definition['title'];
-        }
-
-        return array(
-            'extent' => $extent,
-            'element' => $Element,
-        );
-    }
-
-    protected function inlineMarkup($Excerpt)
-    {
-        if ($this->markupEscaped or $this->safeMode or strpos($Excerpt['text'], '>') === false)
-        {
-            return;
-        }
-
-        if ($Excerpt['text'][1] === '/' and preg_match('/^<\/\w[\w-]*+[ ]*+>/s', $Excerpt['text'], $matches))
-        {
-            return array(
-                'element' => array('rawHtml' => $matches[0]),
-                'extent' => strlen($matches[0]),
-            );
-        }
-
-        if ($Excerpt['text'][1] === '!' and preg_match('/^<!---?[^>-](?:-?+[^-])*-->/s', $Excerpt['text'], $matches))
-        {
-            return array(
-                'element' => array('rawHtml' => $matches[0]),
-                'extent' => strlen($matches[0]),
-            );
-        }
-
-        if ($Excerpt['text'][1] !== ' ' and preg_match('/^<\w[\w-]*+(?:[ ]*+'.$this->regexHtmlAttribute.')*+[ ]*+\/?>/s', $Excerpt['text'], $matches))
-        {
-            return array(
-                'element' => array('rawHtml' => $matches[0]),
-                'extent' => strlen($matches[0]),
-            );
-        }
-    }
-
-    protected function inlineSpecialCharacter($Excerpt)
-    {
-        if ($Excerpt['text'][1] !== ' ' and strpos($Excerpt['text'], ';') !== false
-            and preg_match('/^&(#?+[0-9a-zA-Z]++);/', $Excerpt['text'], $matches)
-        ) {
-            return array(
-                'element' => array('rawHtml' => '&' . $matches[1] . ';'),
-                'extent' => strlen($matches[0]),
-            );
-        }
-
-        return;
-    }
-
-    protected function inlineStrikethrough($Excerpt)
-    {
-        if ( ! isset($Excerpt['text'][1]))
-        {
-            return;
-        }
-
-        if ($Excerpt['text'][1] === '~' and preg_match('/^~~(?=\S)(.+?)(?<=\S)~~/', $Excerpt['text'], $matches))
-        {
-            return array(
-                'extent' => strlen($matches[0]),
-                'element' => array(
-                    'name' => 'del',
-                    'handler' => array(
-                        'function' => 'lineElements',
-                        'argument' => $matches[1],
-                        'destination' => 'elements',
-                    )
-                ),
-            );
-        }
-    }
-
-    protected function inlineUrl($Excerpt)
-    {
-        if ($this->urlsLinked !== true or ! isset($Excerpt['text'][2]) or $Excerpt['text'][2] !== '/')
-        {
-            return;
-        }
-
-        if (strpos($Excerpt['context'], 'http') !== false
-            and preg_match('/\bhttps?+:[\/]{2}[^\s<]+\b\/*+/ui', $Excerpt['context'], $matches, PREG_OFFSET_CAPTURE)
-        ) {
-            $url = $matches[0][0];
-
-            $Inline = array(
-                'extent' => strlen($matches[0][0]),
-                'position' => $matches[0][1],
-                'element' => array(
-                    'name' => 'a',
-                    'text' => $url,
-                    'attributes' => array(
-                        'href' => $url,
-                    ),
-                ),
-            );
-
-            return $Inline;
-        }
-    }
-
-    protected function inlineUrlTag($Excerpt)
-    {
-        if (strpos($Excerpt['text'], '>') !== false and preg_match('/^<(\w++:\/{2}[^ >]++)>/i', $Excerpt['text'], $matches))
-        {
-            $url = $matches[1];
-
-            return array(
-                'extent' => strlen($matches[0]),
-                'element' => array(
-                    'name' => 'a',
-                    'text' => $url,
-                    'attributes' => array(
-                        'href' => $url,
-                    ),
-                ),
-            );
-        }
-    }
-
-    # ~
-
-    protected function unmarkedText($text)
-    {
-        $Inline = $this->inlineText($text);
-        return $this->element($Inline['element']);
-    }
-
-    #
-    # Handlers
-    #
-
-    protected function handle(array $Element)
-    {
-        if (isset($Element['handler']))
-        {
-            if (!isset($Element['nonNestables']))
-            {
-                $Element['nonNestables'] = array();
-            }
-
-            if (is_string($Element['handler']))
-            {
-                $function = $Element['handler'];
-                $argument = $Element['text'];
-                unset($Element['text']);
-                $destination = 'rawHtml';
-            }
-            else
-            {
-                $function = $Element['handler']['function'];
-                $argument = $Element['handler']['argument'];
-                $destination = $Element['handler']['destination'];
-            }
-
-            $Element[$destination] = $this->{$function}($argument, $Element['nonNestables']);
-
-            if ($destination === 'handler')
-            {
-                $Element = $this->handle($Element);
-            }
-
-            unset($Element['handler']);
-        }
-
-        return $Element;
-    }
-
-    protected function handleElementRecursive(array $Element)
-    {
-        return $this->elementApplyRecursive(array($this, 'handle'), $Element);
-    }
-
-    protected function handleElementsRecursive(array $Elements)
-    {
-        return $this->elementsApplyRecursive(array($this, 'handle'), $Elements);
-    }
-
-    protected function elementApplyRecursive($closure, array $Element)
-    {
-        $Element = call_user_func($closure, $Element);
-
-        if (isset($Element['elements']))
-        {
-            $Element['elements'] = $this->elementsApplyRecursive($closure, $Element['elements']);
-        }
-        elseif (isset($Element['element']))
-        {
-            $Element['element'] = $this->elementApplyRecursive($closure, $Element['element']);
-        }
-
-        return $Element;
-    }
-
-    protected function elementApplyRecursiveDepthFirst($closure, array $Element)
-    {
-        if (isset($Element['elements']))
-        {
-            $Element['elements'] = $this->elementsApplyRecursiveDepthFirst($closure, $Element['elements']);
-        }
-        elseif (isset($Element['element']))
-        {
-            $Element['element'] = $this->elementsApplyRecursiveDepthFirst($closure, $Element['element']);
-        }
-
-        $Element = call_user_func($closure, $Element);
-
-        return $Element;
-    }
-
-    protected function elementsApplyRecursive($closure, array $Elements)
-    {
-        foreach ($Elements as &$Element)
-        {
-            $Element = $this->elementApplyRecursive($closure, $Element);
-        }
-
-        return $Elements;
-    }
-
-    protected function elementsApplyRecursiveDepthFirst($closure, array $Elements)
-    {
-        foreach ($Elements as &$Element)
-        {
-            $Element = $this->elementApplyRecursiveDepthFirst($closure, $Element);
-        }
-
-        return $Elements;
-    }
-
-    protected function element(array $Element)
-    {
-        if ($this->safeMode)
-        {
-            $Element = $this->sanitiseElement($Element);
-        }
-
-        # identity map if element has no handler
-        $Element = $this->handle($Element);
-
-        $hasName = isset($Element['name']);
-
-        $markup = '';
-
-        if ($hasName)
-        {
-            $markup .= '<' . $Element['name'];
-
-            if (isset($Element['attributes']))
-            {
-                foreach ($Element['attributes'] as $name => $value)
-                {
-                    if ($value === null)
-                    {
-                        continue;
-                    }
-
-                    $markup .= " $name=\"".self::escape($value).'"';
-                }
-            }
-        }
-
-        $permitRawHtml = false;
-
-        if (isset($Element['text']))
-        {
-            $text = $Element['text'];
-        }
-        // very strongly consider an alternative if you're writing an
-        // extension
-        elseif (isset($Element['rawHtml']))
-        {
-            $text = $Element['rawHtml'];
-
-            $allowRawHtmlInSafeMode = isset($Element['allowRawHtmlInSafeMode']) && $Element['allowRawHtmlInSafeMode'];
-            $permitRawHtml = !$this->safeMode || $allowRawHtmlInSafeMode;
-        }
-
-        $hasContent = isset($text) || isset($Element['element']) || isset($Element['elements']);
-
-        if ($hasContent)
-        {
-            $markup .= $hasName ? '>' : '';
-
-            if (isset($Element['elements']))
-            {
-                $markup .= $this->elements($Element['elements']);
-            }
-            elseif (isset($Element['element']))
-            {
-                $markup .= $this->element($Element['element']);
-            }
-            else
-            {
-                if (!$permitRawHtml)
-                {
-                    $markup .= self::escape($text, true);
-                }
-                else
-                {
-                    $markup .= $text;
-                }
-            }
-
-            $markup .= $hasName ? '</' . $Element['name'] . '>' : '';
-        }
-        elseif ($hasName)
-        {
-            $markup .= ' />';
-        }
-
-        return $markup;
-    }
-
-    protected function elements(array $Elements)
-    {
-        $markup = '';
-
-        $autoBreak = true;
-
-        foreach ($Elements as $Element)
-        {
-            if (empty($Element))
-            {
-                continue;
-            }
-
-            $autoBreakNext = (isset($Element['autobreak'])
-                ? $Element['autobreak'] : isset($Element['name'])
-            );
-            // (autobreak === false) covers both sides of an element
-            $autoBreak = !$autoBreak ? $autoBreak : $autoBreakNext;
-
-            $markup .= ($autoBreak ? "\n" : '') . $this->element($Element);
-            $autoBreak = $autoBreakNext;
-        }
-
-        $markup .= $autoBreak ? "\n" : '';
-
-        return $markup;
-    }
-
-    # ~
-
-    protected function li($lines)
-    {
-        $Elements = $this->linesElements($lines);
-
-        if ( ! in_array('', $lines)
-            and isset($Elements[0]) and isset($Elements[0]['name'])
-            and $Elements[0]['name'] === 'p'
-        ) {
-            unset($Elements[0]['name']);
-        }
-
-        return $Elements;
-    }
-
-    #
-    # AST Convenience
-    #
-
-    /**
-     * Replace occurrences $regexp with $Elements in $text. Return an array of
-     * elements representing the replacement.
-     */
-    protected static function pregReplaceElements($regexp, $Elements, $text)
-    {
-        $newElements = array();
-
-        while (preg_match($regexp, $text, $matches, PREG_OFFSET_CAPTURE))
-        {
-            $offset = $matches[0][1];
-            $before = substr($text, 0, $offset);
-            $after = substr($text, $offset + strlen($matches[0][0]));
-
-            $newElements[] = array('text' => $before);
-
-            foreach ($Elements as $Element)
-            {
-                $newElements[] = $Element;
-            }
-
-            $text = $after;
-        }
-
-        $newElements[] = array('text' => $text);
-
-        return $newElements;
-    }
-
-    #
-    # Deprecated Methods
-    #
-
-    function parse($text)
-    {
-        $markup = $this->text($text);
-
-        return $markup;
-    }
-
-    protected function sanitiseElement(array $Element)
-    {
-        static $goodAttribute = '/^[a-zA-Z0-9][a-zA-Z0-9-_]*+$/';
-        static $safeUrlNameToAtt  = array(
-            'a'   => 'href',
-            'img' => 'src',
-        );
-
-        if ( ! isset($Element['name']))
-        {
-            unset($Element['attributes']);
-            return $Element;
-        }
-
-        if (isset($safeUrlNameToAtt[$Element['name']]))
-        {
-            $Element = $this->filterUnsafeUrlInAttribute($Element, $safeUrlNameToAtt[$Element['name']]);
-        }
-
-        if ( ! empty($Element['attributes']))
-        {
-            foreach ($Element['attributes'] as $att => $val)
-            {
-                # filter out badly parsed attribute
-                if ( ! preg_match($goodAttribute, $att))
-                {
-                    unset($Element['attributes'][$att]);
-                }
-                # dump onevent attribute
-                elseif (self::striAtStart($att, 'on'))
-                {
-                    unset($Element['attributes'][$att]);
-                }
-            }
-        }
-
-        return $Element;
-    }
-
-    protected function filterUnsafeUrlInAttribute(array $Element, $attribute)
-    {
-        foreach ($this->safeLinksWhitelist as $scheme)
-        {
-            if (self::striAtStart($Element['attributes'][$attribute], $scheme))
-            {
-                return $Element;
-            }
-        }
-
-        $Element['attributes'][$attribute] = str_replace(':', '%3A', $Element['attributes'][$attribute]);
-
-        return $Element;
-    }
-
-    #
-    # Static Methods
-    #
-
-    protected static function escape($text, $allowQuotes = false)
-    {
-        return htmlspecialchars($text, $allowQuotes ? ENT_NOQUOTES : ENT_QUOTES, 'UTF-8');
-    }
-
-    protected static function striAtStart($string, $needle)
-    {
-        $len = strlen($needle);
-
-        if ($len > strlen($string))
-        {
-            return false;
-        }
-        else
-        {
-            return strtolower(substr($string, 0, $len)) === strtolower($needle);
-        }
-    }
-
-    static function instance($name = 'default')
-    {
-        if (isset(self::$instances[$name]))
-        {
-            return self::$instances[$name];
-        }
-
-        $instance = new static();
-
-        self::$instances[$name] = $instance;
-
-        return $instance;
-    }
-
-    private static $instances = array();
-
-    #
-    # Fields
-    #
-
-    protected $DefinitionData;
-
-    #
-    # Read-Only
-
-    protected $specialCharacters = array(
-        '\\', '`', '*', '_', '{', '}', '[', ']', '(', ')', '>', '#', '+', '-', '.', '!', '|', '~'
-    );
-
-    protected $StrongRegex = array(
-        '*' => '/^[*]{2}((?:\\\\\*|[^*]|[*][^*]*+[*])+?)[*]{2}(?![*])/s',
-        '_' => '/^__((?:\\\\_|[^_]|_[^_]*+_)+?)__(?!_)/us',
-    );
-
-    protected $EmRegex = array(
-        '*' => '/^[*]((?:\\\\\*|[^*]|[*][*][^*]+?[*][*])+?)[*](?![*])/s',
-        '_' => '/^_((?:\\\\_|[^_]|__[^_]*__)+?)_(?!_)\b/us',
-    );
-
-    protected $regexHtmlAttribute = '[a-zA-Z_:][\w:.-]*+(?:\s*+=\s*+(?:[^"\'=<>`\s]+|"[^"]*+"|\'[^\']*+\'))?+';
-
-    protected $voidElements = array(
-        'area', 'base', 'br', 'col', 'command', 'embed', 'hr', 'img', 'input', 'link', 'meta', 'param', 'source',
-    );
-
-    protected $textLevelElements = array(
-        'a', 'br', 'bdo', 'abbr', 'blink', 'nextid', 'acronym', 'basefont',
-        'b', 'em', 'big', 'cite', 'small', 'spacer', 'listing',
-        'i', 'rp', 'del', 'code',          'strike', 'marquee',
-        'q', 'rt', 'ins', 'font',          'strong',
-        's', 'tt', 'kbd', 'mark',
-        'u', 'xm', 'sub', 'nobr',
-                   'sup', 'ruby',
-                   'var', 'span',
-                   'wbr', 'time',
-    );
-}

+ 0 - 28
plugin/wiki/modal.category.php

@@ -1,28 +0,0 @@
-<?php 
-require_once __DIR__.SLASH.'WikiCategory.class.php'; 
-
-$category = new WikiCategory() ;
-$category->icon = 'far fa-bookmark';
-$category->color = WikiCategory::color();
-if(isset($_['id'])) $category = WikiCategory::getById($_['id']);
-
-?>
-<div onclick="wiki_category_save();" class="btn btn-success float-right"><i class="fas fa-check"></i> Enregistrer</div>
-<h1>Édition d'une catégorie</h1>
-<div class="clear"></div>
-<div id="category-form" class="row category-form" data-action="wiki_category_save" data-id="<?php echo $category->id; ?>">
-	<div class="col-md-12">
-		<label for="label">Titre :</label>
-		<div class="input-group">
-			<div class="input-group-prepend icon-chooser">
-				<input id="icon" class="form-control" data-type="icon" value="<?php echo $category->icon; ?>" type="text">
-			</div>
-			<input id="label" class="form-control" placeholder="eg. New Category" value="<?php echo $category->label; ?>" type="text">
-			<div class="input-group-append">
-				<input id="color" name="color" class="form-control"  value="<?php echo $category->color; ?>" type="color">
-			</div>
-		</div>
-		<br>
-		
-	</div>
-</div>

+ 0 - 23
plugin/wiki/page.category.php

@@ -1,23 +0,0 @@
-<?php if($myUser->can('wiki','edit')): ?>
-	<div onclick="wiki_page_add(event);" class="btn btn-primary float-right"><i class="far fa-file-alt"></i> Ajouter une page</div>
-<?php endif; ?>
-<h1><i style="color:<?php echo $category->color; ?>" class="<?php echo $category->icon; ?>"></i> <?php echo $category->label; ?></h1>
-<div class="clear"></div>
-
-<h3 class="wiki-title">Récemment ajouté</h3>
-<?php if(count($pages) == 0 ): ?>
-<p class="no-pages">
-	La catégorie est vide...<br>
-	Ajoutez une nouvelle page en cliquant sur le bouton <i>"Ajouter une page"</i>.
-<p>
-<?php else:  ?>
-<ul class="category-recent">
-	<?php foreach($recents as $page): ?>
-	<li data-category="<?php echo $page->join('category')->slug; ?>" data-page="<?php echo $page->slug; ?>" onclick="wiki_page_open($(this).attr('data-category'),$(this).attr('data-page'),event);">
-		<h5><?php echo $page->label; ?></h5>
-		<small class="wiki-small"> Par <?php echo $page->author(); ?> <?php echo $page->created(); ?></small>
-		<small class="last-update wiki-small"> - (Modifié par <?php echo $page->updater(); ?> <?php echo $page->updated(); ?>)</small>
-	</li>
-	<?php endforeach; ?>
-</ul>
-<?php endif; ?>

+ 0 - 44
plugin/wiki/page.home.php

@@ -1,44 +0,0 @@
-<?php
-global $conf;
-require_once(__DIR__.SLASH.'WikiCategory.class.php');
-require_once(__DIR__.SLASH.'WikiPage.class.php');
-$updated = WikiPage::loadAll(array(),array('updated DESC'),array('10'), array('*'), 1);
-?>
-<h1><i style="color:<?php echo random_hex_pastel_color('Accueil'); ?>;" class="fas fa-home"></i> Accueil</h1>
-
-<?php if($conf->get('wiki_home') != ''): 
-	list($category,$page) = explode('/', $conf->get('wiki_home'));
-	$category = WikiCategory::load(array('slug'=>$category));
-	if($home = WikiPage::load(array('category'=>$category->id,'slug'=>$page)))
-		$home->content();
-?>
-<div class="wiki-home-page">
-	<?php echo $home?$home->html():''; ?>
-</div>
-
-<?php endif; ?>
-
-<?php if(count($updated) == 0): ?>
-	<p>Aucune activitée enregistrée...<p>
-<?php else:  ?>
-<div class="wiki-home-activity">
-	<h3 class="wiki-title">Récemment Ajouté / Modifié </h3>
-
-	<ul class="category-recent">
-	<?php foreach($updated as $page): ?>
-		<li data-category="<?php echo $page->join('category')->slug; ?>"  data-page="<?php echo $page->slug; ?>" onclick="wiki_page_open($(this).attr('data-category'),$(this).attr('data-page'),event);">
-			<h5><i class="far fa-sticky-note"></i> <?php echo $page->label; ?></h5>
-			<small class="wiki-small">
-				<span class="font-weight-bold"><i class="<?php echo $page->join('category')->icon; ?>"></i> <?php echo $page->join('category')->label; ?></span>
-				<?php if($page->created == $page->updated): ?>
-				<span> - Ajouté <?php echo $page->created(); ?> par <i class="far fa-meh-blank"></i> <?php echo $page->author(); ?></span>
-				<?php else: ?>
-				<span> - Edité <?php echo $page->updated(); ?> par <i class="far fa-meh-blank"></i> <?php echo $page->updater(); ?></span>
-				<?php endif; ?>
-			</small>
-		</li>
-	<?php endforeach; ?>
-	</ul>
-</div>
-<?php endif; ?>
-<div class="clear"></div>

+ 0 - 129
plugin/wiki/page.index.php

@@ -1,129 +0,0 @@
-<?php
-global $myUser, $myFirm, $conf;
-if(!$myUser->can('wiki','read')){
-    require_once(__DIR__.SLASH.'page.login.php');
-    exit();
-}
-require_once(__DIR__.SLASH.'WikiCategory.class.php');
-require_once(__DIR__.SLASH.'WikiPage.class.php');
-WikiCategory::synchronize();
-$myUser->loadPreferences();
-
-$menu = array(
-    'home' => array(
-        'label' => 'Accueil',
-        'icon' => 'fas fa-home',
-        'css' => 'wiki_home_item',
-        'onclick' => 'wiki_page_home()'
-    ),
-    'search' => array(
-        'label' => '<span>Recherche</span><input class="form-control hidden" placeholder="Mot clé..." type="text">',
-        'icon' => 'fas fa-search',
-        'css' => 'wiki_search_item',
-        'onclick'=> 'wiki_search()'
-    )
-);
-?>
-
-<div id="wiki-summary">
-    <h2><i title="Sommaire" class="far fa-list-alt"></i> Sommaire</h2>
-    <ul></ul>
-</div>
-
-<div id="drag-overlay">
-    <div id="overlay-text"><i class="far fa-file-alt"></i>&nbsp;&nbsp;Glissez vos fichiers ici.</div>
-    <div id="overlay-icon"><i class="fas fa-cloud-upload-alt"></i></div>
-</div>
-
-<div id="sideMenu">
-    <div class="wiki-header">
-        <img src="action.php?action=wiki_logo_download" class="wiki-logo">
-        <div class="wiki-brand">
-            <div class="wiki-brand-firm">
-                <?php $firmName = empty($conf->get('wiki_name'))?$myFirm->label:$conf->get('wiki_name'); ?>
-                <span class="firm-label" title="<?php echo $firmName; ?>"><?php echo $firmName; ?></span>
-                <div class="brand-option dropdown">
-                    <i class="fas fa-caret-down" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"></i>
-                    <div class="dropdown-menu" aria-labelledby="dropdownMenuButton">
-                        <h6 class="dropdown-header">Général :</h6>
-                        <a class="dropdown-item" href="index.php"><i class="fas fa-home"></i> Accueil</a>
-                        <!-- <a class="dropdown-item" href="http://idleman.fr" target="_blank"><i class="fas fa-book"></i> Documentation</a> -->
-                        <!-- <a class="dropdown-item" href="index.php?module=wiki&page=shortcut" target="_blank"><i class="far fa-keyboard"></i> Raccourcis clavier</a> -->
-                        <?php if($myUser->can('wiki', 'edit')) : ?>
-                        <a class="dropdown-item" href="setting.php?section=global.wiki"><i class="fas fa-cogs"></i> Réglages</a>
-                        <?php endif; ?>
-                        <div class="dropdown-divider"></div>
-                        <a class="dropdown-item" href="action.php?action=logout&url=<?php echo base64_encode('index.php?module=wiki'); ?>"><i class="fas fa-sign-out-alt"></i> Déconnexion</a>
-                    </div>
-                </div>
-                <div class="clear"></div>
-            </div>
-            <div class="wiki-brand-user"><i class="far fa-meh-blank"></i> <?php echo $myUser->fullName(); ?></div>
-        </div>
-        <div class="night-mode-toggler" title="Activer / Désactiver le mode nuit du Wiki">
-            <div class="toggle-box">
-                <input <?php if(!empty($myUser->preference('wiki_night_mode'))) echo 'checked="checked"'; ?> type="checkbox" name="-toggler" id="night-mode-check" onchange="toggle_night_mode(this);" />
-                <label for="night-mode-check" class="toggle-box-label"></label>
-            </div>
-        </div>
-    </div>
-
-    <div id="wiki-main-menu">
-        <ul>
-            <?php foreach($menu as $link): ?>
-            <li class="<?php echo $link['css']; ?>" onclick="<?php echo $link['onclick']; ?>"><i class="<?php echo $link['icon']; ?>"></i> <?php echo $link['label']; ?> 
-            <?php if (isset($link['bubble'])): ?>
-                <div class="wiki-bubble"><?php echo $link['bubble']; ?></div>{{/bubble}}
-            <?php endif; ?>
-            </li>
-        <?php endforeach; ?>
-        </ul>
-    </div>
-    
-    <div id="wiki-categories">
-        <h3>Catégories
-            <?php if($myUser->can('wiki', 'edit')) : ?>
-            <i class="fas fa-plus wiki-add-category" title="Ajouter une nouvelle catégorie" onclick="wiki_category_edit(this,event);"></i>
-            <?php endif; ?>
-        </h3>
-        <ul id="categories">
-            <li data-category="{{slug}}" data-id="{{id}}" onclick="wiki_category_open($(this).attr('data-category'));" class="hidden category category-{{state}}">
-                <div class="icon-bubble"  style="background-color:{{color}};"><i class="{{icon}}"></i></div> {{{label}}}
-               
-                <div class="category-option dropdown">
-                    <i class=" fas fa-ellipsis-h" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"></i>
-                  
-                    <div class="dropdown-menu" aria-labelledby="dropdownMenuButton">
-                        <h6 class="dropdown-header">Catégorie :</h6>
-                        <?php if($myUser->can('wiki', 'edit')) : ?>
-                        <a class="dropdown-item" href="#" onclick="wiki_page_add(event);"><i class="far fa-file-alt"></i> Créer une page</a>
-                        <a class="dropdown-item" href="#" onclick="wiki_category_edit(this,event);"><i class="fas fa-pencil-alt"></i> Éditer</a>
-                        <a class="dropdown-item text-danger" href="#" onclick="wiki_category_delete(this,event);"><i class="far fa-trash-alt"></i> Supprimer</a>
-                        <div class="dropdown-divider"></div>
-                        <?php endif; ?>
-                        <a class="dropdown-item" href="#" onclick="wiki_category_download(this,event);"><i class="far fa-file-archive"></i> Télécharger</a>
-                    </div>
-                </div>
-                <ul></ul>
-            </li>
-        </ul>
-   
-        <ul id="pageModel" class="hidden">
-            <li data-category="{{categorySlug}}"  data-page="{{slug}}" data-id="{{id}}" class="page" onclick="wiki_page_open($(this).attr('data-category'),$(this).attr('data-page'),event);"><i class="far fa-sticky-note"></i> {{label}}</li>
-        </ul>
-        <div class="wiki-preloader"><i class="far fa-meh-blank fa-spin"></i><br>Recherche en cours...</div>
-        
-        <a class="dropdown-item hidden" href="#" id="upload-button">
-            <div><i class="fas fa-plus"></i> Fichier</div>
-            <form class="box" method="post" action="action.php?action=wiki_file_upload" enctype="multipart/form-data">
-                <input data-label="envoyer" type="file" name="file[]"  multiple />
-            </form>
-        </a>
-    </div>
-</div>
-
-<div id="editor"></div>
-
-
-
-

+ 0 - 14
plugin/wiki/page.login.php

@@ -1,14 +0,0 @@
-
-<?php require_once(__ROOT__.SLASH.'header.php'); 
-	$url = base64_encode('index.php?module=wiki');
-?>
-<div class="wiki-login-box">
-	<form action="action.php?action=login&url=<?php echo $url; ?>" method="POST">
-		<i class="far fa-bookmark wiki-logo"></i>
-		<input type="text" class="form-control" id="login"  name="login">
-		<input type="password" data-type="password" onkeypress="if(event.keyCode==13)$(this).parent().submit()" class="form-control" name="password" id="password">
-		<div onclick="$(this).parent().submit()" class="btn">Connexion</div>
-	</form>
-</div>
-
-<?php require_once(__ROOT__.SLASH.'footer.php'); ?>

+ 0 - 55
plugin/wiki/page.page.php

@@ -1,55 +0,0 @@
-<div class="wiki-breadcrumb" data-page="<?php echo $page->id; ?>">
-	<span><i style="color:<?php echo $category->color; ?>" class="<?php echo $category->icon; ?>"></i> <?php echo $category->label; ?></span>
-	<div class="slash">/</div>
-	<div class="form-control" id="page-label"><?php echo $page->label; ?></div>
-	<small title="Modifié par <?php echo $page->updater(); ?> <?php echo $page->updated(); ?>"> Créé par <i class="far fa-meh-blank"></i> <?php echo $page->author(); ?> <?php echo $page->created(); ?></small>
-
-</div>
-
-<?php if($page->id!=0): ?>
-<ul class="page-option">
-	<li class="page-summary-menu">
-		<div class="page-option-item page-option-summary" title="Afficher le sommaire de la page">
-			<i onclick="wiki_page_summary(this);" title="Sommaire" class="far fa-list-alt"></i>
-		</div>
-	</li>
-	<li class="page-empty-menu"></li>
-	<li class="page-save-menu hidden">
-		<div class="page-option-item page-option-save" title="Valider et sauvegarder les changements" onclick="wiki_page_save();">
-			<i class="fas fa-check"></i>
-		</div>
-	</li>
-	<li class="page-editor-menu">
-		<div class="page-option-item page-option-menu shown dropdown">
-			<i class=" fas fa-ellipsis-h" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"></i>
-			<div class="dropdown-menu" aria-labelledby="dropdownMenuButton">
-				<h6 class="dropdown-header">Page :</h6>
-				<a class="dropdown-item" title="Éditer la page" href="#" onclick="wiki_page_edit(this,event);"><i class="fas fa-pencil-alt"></i> Éditer</a>
-				<a class="dropdown-item text-danger" title="Supprimer la page" href="#" onclick="wiki_page_delete(this,event);"><i class="far fa-trash-alt"></i> Supprimer</a>
-				<div class="dropdown-divider"></div>
-				<a class="dropdown-item" href="#" onclick="wiki_page_download(this,event);"><i class="far fa-file-archive"></i> Télécharger</a>
-			</div>
-		</div>
-	</li>
-</ul>
-<?php endif; ?>
-
-<?php
-if($page->id==0): ?>
-	<h1><i class="far fa-grin-beam-sweat"></i> Page "<?php echo $page->label; ?>" introuvable</h1>
-	<p>Cette page n'existe pas ou n'existe plus, en tout cas on la trouve pas ...<p>
-<?php else :
-$page->content();
-if( $page->content == '' ): ?>
-	<h1><i class="far fa-sticky-note"></i> <?php echo $page->label; ?></h1>
-	<p>Cette page est bien vide..<p>
-	<div onclick="wiki_page_edit(this,event);" class="btn btn-success">Éditer la page</div>
-<?php endif; ?>
-<div class="wiki-page-content">
-	<div id="content-html">
-<?php echo $page->html(); ?>
-	</div>
-	<textarea class="hidden" id="content-text"><?php echo $page->content; ?></textarea>
-</div>
-
-<?php endif; ?>

+ 0 - 108
plugin/wiki/page.search.php

@@ -1,108 +0,0 @@
-<?php
-require_once(__DIR__.SLASH.'WikiCategory.class.php');
-require_once(__DIR__.SLASH.'WikiPage.class.php');
-
-$results = array();
-$terms = explode(' ',trim($_['term']));
-
-$rterms = array();
-foreach ($terms as $value) {
-	if(empty(trim($value)) || strlen($value)<=2) continue;
-	$rterms[] = str_replace('/','\/',preg_quote($value));
-}
-
-
-if(count($rterms)>0){
-	foreach(WikiPage::loadAll(array(),array('label'),array('100'), array('*'), 1) as $page){
-		$page->content();
-		$content = $page->label.' - '.$page->content;
-	
-		if(preg_match_all('/'.implode('|',$rterms).'/iUs',$content, $matches, PREG_OFFSET_CAPTURE)){
-			
-			foreach ($matches as $match) {
-				foreach ($match as $word) {
-					$offset = $word[1];
-					$word = $word[0];
-					$length = strlen($page->content);
-					$start = $offset-50;
-					$start = $start < 0 ? 0 : $start;
-					$end = $start+100 > $length -1 ? $length -1: 100;
-
-					$excerpt = substr($page->content,$start,$end).'...';
-					$excerpt = htmlentities($excerpt);
-					$excerpt = preg_replace('|(.*)('.$word.')(.*)|iUs', '$1<span class="wiki-search-highlight">$2</span>$3', $excerpt);
-
-					//Dans quel but ? (ligne dessous)
-					// if(isset($results[$page->id])) $excerpt = $results[$page->id]['excerpt'].' '.$excerpt; 
-
-					$results[$page->id] = array(
-						'type' => 'page', 
-						'item' => $page,
-						'excerpt' => $excerpt
-					); 
-				}
-			}
-		}
-	}
-
-	foreach(WikiCategory::staticQuery('SELECT * FROM {{table}} WHERE label REGEXP ? ',array(implode('|',$rterms)),true) as $category){
-		$results[$category->id] = array(
-			'type' => 'category', 
-			'item' => $category,
-			'excerpt' => 'Catégorie'
-		);
-	}
-}
-?>
-<h3 class="search-title"><i class="fas fa-search"></i> RECHERCHE</h3>
-<small class="tags-container <?php if(empty($results)) echo 'light-border-bottom'; ?>">
-	<?php if(isset($terms[0]) && !empty($terms[0])): ?>
-		<h6 class="d-inline-block no-select">Tags : </h6>
-		<?php foreach ($terms as $term):
-			if(empty($term)) continue;
-			$bgColor = random_hex_pastel_color($term);
-			$color = get_light($bgColor) >= 0.65 ? '#4c4c4c' : '#fdfdfd';
-			?>
-			<span class="ml-1 tag-item" style="<?php echo 'background-color:'.$bgColor.';color:'.$color.';'; ?>"><i class="fas fa-tag"></i> <?php echo htmlentities($term); ?> <i class="fas fa-times delete-tag" onclick="wiki_tag_delete(this);"></i></span>
-		<?php endforeach; ?>
-	<?php endif; ?>
-</small>
-<?php if(empty($results) && isset($terms[0]) && empty($terms[0])): ?>
-	<p class="mt-2"><i class="far fa-sad-tear"></i> Aucun mot-clé renseigné...<p>
-<?php elseif(empty($results)): ?>
-	<p class="mt-2"><i class="far fa-grin-beam-sweat"></i> Oops ! Rien trouvé..<p>
-<?php else: ?>
-
-<h3 class="wiki-title mt-0"><?php echo count($results); ?> Résultats</h3>
-
-<ul class="category-recent">
-<?php foreach($results as $result): 
-	$item = $result['item'];
-	if($result['type'] == 'page'): ?>
-
-	<li data-category="<?php echo $item->join('category')->slug; ?>" data-page="<?php echo $item->slug; ?>" onclick="wiki_page_open($(this).attr('data-category'),$(this).attr('data-page'),event);">
-		<h5><i class="far fa-sticky-note"></i> <?php echo $item->label; ?></h5>
-		<small class="wiki-small">
-			<span class="font-weight-bold"><i class="far fa-bookmark"></i> <?php echo $item->join('category')->label; ?></span>
-			<span> - Edité <?php echo $item->created(); ?> par <i class="far fa-meh-blank"></i> <?php echo $item->author(); ?></span>
-		</small>
-		<br>
-		<small>...<?php echo $result['excerpt']; ?></small>
-	</li>
-
-	<?php else: ?>
-
-	<li data-category="<?php echo $item->slug; ?>" onclick="wiki_category_open($(this).attr('data-category'),$(this).attr('data-page'),event);">
-		<h5><i class="far fa-bookmark"></i> <?php echo $item->label; ?></h5>
-		<small class="wiki-small">
-			<span class="font-weight-bold"><i class="far fa-bookmark"></i> <?php echo $item->label; ?></span>
-			<span> - Edité <?php echo $item->created(); ?> par <i class="far fa-meh-blank"></i> <?php echo $item->author(); ?></span>
-		</small>
-		<br>
-	</li>
-
-	<?php endif; ?>
-<?php endforeach; ?>
-</ul>
-
-<?php endif; ?>

+ 0 - 66
plugin/wiki/page.shortcut.php

@@ -1,66 +0,0 @@
-<table class="table">
-	<thead>
-		<tr>
-			<th align="left">Raccourcis</th>
-			<th align="left">Action</th>
-		</tr>
-	</thead>
-	<tbody>
-		<tr>
-			<td align="left"><em>Cmd-'</em></td>
-			<td align="left">toggle Blockquote</td>
-		</tr>
-		<tr>
-			<td align="left"><em>Cmd-B</em></td>
-			<td align="left">toggle Bold</td>
-		</tr>
-		<tr>
-			<td align="left"><em>Cmd-E</em></td>
-			<td align="left">clean Block</td>
-		</tr>
-		<tr>
-			<td align="left"><em>Cmd-H</em></td>
-			<td align="left">toggle Heading Smaller</td>
-		</tr>
-		<tr>
-			<td align="left"><em>Cmd-I</em></td>
-			<td align="left">toggle Italic</td>
-		</tr>
-		<tr>
-			<td align="left"><em>Cmd-K</em></td>
-			<td align="left">draw Link</td>
-		</tr>
-		<tr>
-			<td align="left"><em>Cmd-L</em></td>
-			<td align="left">toggle UnorderedList</td>
-		</tr>
-		<tr>
-			<td align="left"><em>Cmd-P</em></td>
-			<td align="left">toggle Preview</td>
-		</tr>
-		<tr>
-			<td align="left"><em>Cmd-Alt-C</em></td>
-			<td align="left">toggle CodeBlock</td>
-		</tr>
-		<tr>
-			<td align="left"><em>Cmd-Alt-I</em></td>
-			<td align="left">draw Image</td>
-		</tr>
-		<tr>
-			<td align="left"><em>Cmd-Alt-L</em></td>
-			<td align="left">toggle Ordered List</td>
-		</tr>
-		<tr>
-			<td align="left"><em>Shift-Cmd-H</em></td>
-			<td align="left">toggle Heading Bigger</td>
-		</tr>
-		<tr>
-			<td align="left"><em>F9</em></td>
-			<td align="left">toggle Side By Side</td>
-		</tr>
-		<tr>
-			<td align="left"><em>F11</em></td>
-			<td align="left">toggle Full Screen</td>
-		</tr>
-	</tbody>
-</table>

+ 0 - 27
plugin/wiki/setting.global.wiki.php

@@ -1,27 +0,0 @@
-<?php
-global $myUser,$conf;
-if(!$myUser->can('wiki','configure')) throw new Exception("Vous n'avez pas la permission pour executer cette fonctionnalité",403);
-?>
-
-<div class="row">
-	<div class="col-md-12">
-		<br>
-        <?php if($myUser->can('wiki', 'edit')) : ?>
-        <div onclick="wiki_setting_save();" class="btn btn-success float-right"><i class="fas fa-check"></i> Enregistrer</div>
-        <?php endif; ?>
-        <h3>Réglages Wiki</h3>
-        <div class="clear"></div>
-        <hr/>
-	</div>
-</div>
-
-<div class="row" id="wiki-settings-form" action="wiki_setting_save">
-	<!-- search results -->
-	<div class="col-xl-12">
-			<div class="col-md-6">
-				<label><strong>Logo du wiki</strong><small class="text-muted"> - Le logo affiché en haut à gauche. L'image sera redimensionnée en 38px par 38px.</small> : </label>
-				<input type="file" data-type="image" value="action.php?action=wiki_logo_download" class="form-control-file" id="wiki_logo" name="wiki_logo" data-delete="wiki_logo_delete(this)">
-			</div><br>
-			<?php echo Configuration::html('wiki'); ?>
-	</div>
-</div>

+ 0 - 3
plugin/wiki/template/Autre/Bienvenue.md

@@ -1,3 +0,0 @@
-## Bienvenue
-
-Ceci est une ``page d'exemple`` !

+ 0 - 87
plugin/wiki/template/Documentation/Syntaxe.md

@@ -1,87 +0,0 @@
-## Syntaxe
-
-
-# Titre 1
-## Titre 2
-### Titre 3
-#### Titre 4
-##### Titre 5
-###### Titre 6
-
-### Table
-
-
-header 1 | header 2
--------- | --------
-cell 1.1 | cell 1.2
-cell 2.1 | cell 2.2
-
-### Liens
-
-Lien automatique : <http://example.com>
-Un email : <me@example.com>
-
-[link](http://example.com)
-
-([link](/index.php)) in parentheses
-
-[`link`](http://example.com)
-
-### Code & citations
-
-    <?php
-
-    $message = 'Hello World!';
-    echo $message;
-
----
-
-    > not a quote
-    - not a list item
-    [not a reference]: http://foo.com
-
-Un `example en surbrillance`
-
-
-```
-<?php
-
-$message = 'fenced code block';
-echo $message;
-```
-
-~~~
-tilde
-~~~
-
-```php
-echo 'language identifier';
-```
-
-> Une citation
-
-### Format de texte
-**Du gras**, _de l'italique_, ~~du barré~~
-
-- un paragraphe
-
-### listes
-
-Liste non numérotée : 
-- Un
-    - sous liste 1
-    - sous liste 2
-- deux
-
-Liste  numérotée : 
-1. Un
-2. deux
-
-### Echappements
-
-Pour échapper un symbole markdown : \*non gras\*.
-
-### Média
-![Markdown Logo][image]
-
-[image]: /md.png

+ 0 - 129
plugin/wiki/wiki.plugin.php

@@ -1,129 +0,0 @@
-<?php
-//Déclaration d'un item de menu dans le menu principal
-function wiki_menu(&$menuItems){
-	global $_,$myUser;
-	if(!$myUser->can('wiki','read')) return;
-	$menuItems[] = array(
-		'sort'=>3,
-		'url'=>'index.php?module=wiki',
-		'label'=>'Wiki',
-		'icon'=> 'fab fa-wikipedia-w',
-		'color'=> '#e83535'
-	);
-}
-
-//Cette fonction va generer une page
-//quand on clique sur wiki dans menu
-function wiki_page(){
-	global $_,$myUser;
-	// if(!isset($_['module'])) echo '<script>window.location="index.php?module=wiki";</script>';
-	if(!isset($_['module']) || $_['module'] != 'wiki') return;
-	$page = !isset($_['page']) ? 'index' : $_['page'];
-	$file = __DIR__.SLASH.'page.index.php';
-	if(!file_exists($file)) throw new Exception("Page ".$page." inexistante");
-	
-	require_once($file);
-}
-
-//Fonction executée lors de l'activation du plugin
-function wiki_install($id){
-	if($id != 'fr.idleman.wiki') return;
-	global $conf;
-	Entity::install(__DIR__);
-	if(!file_exists(WikiPage::workspace()))	
-		File::copy(__DIR__.SLASH.'template', WikiPage::workspace());
-
-	$conf->put('wiki_ext','jpg,jpeg,bmp,gif,png,pdf,docx,xlsx,txt');
-	$conf->put('wiki_max_size',10);
-}
-
-//Fonction executée lors de la désactivation du plugin
-function wiki_uninstall($id){
-	if($id != 'fr.idleman.wiki') return;
-	// if(is_dir(File::dir().'wiki')) del_tree(File::dir().'wiki');
-	Entity::uninstall(__DIR__);
-}
-
-//Déclaration des sections de droits du plugin
-function wiki_section(&$sections){
-	$sections['wiki'] = "Gestion des droits sur le plugin wiki";
-	$sections['page'] = "Gestion des droits sur l'entité page";
-}
-
-//cette fonction comprends toutes les actions du plugin qui ne nécessitent pas de vue html
-function wiki_action(){
-	require_once(__DIR__.SLASH.'action.php');
-}
-
-//Déclaration du menu de réglages
-function wiki_menu_setting(&$settingMenu){
-	global $_, $myUser;
-	
-	if(!$myUser->can('wiki','configure')) return;
-	$settingMenu[]= array(
-		'sort' =>1,
-		'url' => 'setting.php?section=global.wiki',
-		'icon' => 'fas fa-angle-right',
-		'label' => 'Wiki'
-	);
-}
-
-//Déclaration des pages de réglages
-function wiki_content_setting(){
-	global $_;
-	if(file_exists(__DIR__.SLASH.'setting.'.$_['section'].'.php'))
-		require_once(__DIR__.SLASH.'setting.'.$_['section'].'.php');
-}
-
-function wiki_rewrite($path){
-	global $_,$myUser;
-	$parameters = explode('/',$path);
-
-	if(!isset($parameters[0]) || $parameters[0] != 'wiki') return;
-	$_['module'] = array_shift($parameters);
-	if(isset($parameters[0])) $category = $parameters[0];
-	if(isset($parameters[1])) $page = $parameters[1];
-
-	require_once(__ROOT__.SLASH.'header.php');
-	require_once(__DIR__.SLASH.'page.index.php');
-	require_once(__ROOT__.SLASH.'footer.php');
-}
-
-function wiki_os_encode($text){
-	return (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') ? utf8_decode($text) : $text;
-}
-
-function wiki_os_decode($text){
-	return (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') ? utf8_encode($text) : $text;
-}
-
-//Déclaration des settings de base
-Configuration::setting('wiki',array(
-    "Paramètres généraux",
-    'wiki_name' => array("label"=>"Nom du Wiki","legend"=>"Nom affiché en haut à gauche. Par défaut, nom de l'établissement","type"=>"text", "placeholder"=>"eg. The Wiki"),
-    'wiki_home' => array("label"=>"Page d'accueil","legend"=>"La page affichée à l'accueil sur le Wiki (format : categorie/page)","type"=>"text", "placeholder"=>'eg. Documentation/Syntaxe'),
-    "Paramètres fichiers",
-    'wiki_ext' => array("label"=>"Extensions fichiers autorisées","legend"=>"(séparées par virgules)","type"=>"text", "placeholder"=>"eg. jpg, jpeg, png, pdf..."),
-    'wiki_max_size' => array("label"=>"Taille maximum autorisée","legend"=>"La taille maximum des fichiers mis en ligne (en Mo)","type"=>"int", "placeholder"=>"eg. 25"),
-));
-
-//Déclation des assets
-Plugin::addCss("https://fonts.googleapis.com/css?family=Roboto:300,400,700");
-Plugin::addCss("/css/main.css?v=1"); 
-Plugin::addCss("/css/simplemde.min.css?v=1"); 
-Plugin::addJs("/js/simplemde.min.js?v=1"); 
-Plugin::addJs("/js/main.js?v=1"); 
-
-//Mapping hook / fonctions
-Plugin::addHook("install", "wiki_install");
-Plugin::addHook("uninstall", "wiki_uninstall"); 
-Plugin::addHook("section", "wiki_section");
-Plugin::addHook("menu_main", "wiki_menu"); 
-Plugin::addHook("page", "wiki_page");  
-Plugin::addHook("action", "wiki_action");  
-Plugin::addHook("rewrite", "wiki_rewrite");  
-Plugin::addHook("menu_setting", "wiki_menu_setting");    
-Plugin::addHook("content_setting", "wiki_content_setting");   
- 
-
-?>

Some files were not shown because too many files changed in this diff