Browse Source

Ajout du wiki

idleman 4 years ago
parent
commit
61ca6e2160

+ 4 - 0
function.php

@@ -36,6 +36,10 @@ function unhandledException($ex){
 	exit();
 }
 
+function get_OS(){
+	return strtoupper(substr(PHP_OS, 0, 3));
+}
+
 function ip(){
 	if(isset($_SERVER['HTTP_X_FORWARDED_FOR']))
 		$ip = $_SERVER['HTTP_X_FORWARDED_FOR'];

+ 94 - 0
js/plugins.js

@@ -485,6 +485,100 @@ $.fn.extend({
 		});
 	},
 
+	//Permet le resize d'un panel vertical
+	panelResize : function(options){
+		var obj = $(this);
+
+		var o = $.extend({
+			handlerClass : 'panel-resize-handler',
+			hoverClass : 'panel-resize-handler-hover',
+			handlerWidth : 10,
+			direction : 'right',
+			minWidth : 30,
+			maxWidth : 700,
+			update : function(data){}
+		}, options);
+
+		var handlerId = 'handler-'+generate_uuid(10);
+		var handler = $('<div id="'+handlerId+'" class="'+o.handlerClass+'" style="cursor:col-resize;opacity:0.1;position:absolute;z-index:1000;height:'+obj.outerHeight()+'px;width:'+o.handlerWidth+'px;"></div>');
+
+
+		var timeout = null;
+		var left;
+		if( o.direction== 'right'){
+			left = obj.position().left+obj.outerWidth()-(o.handlerWidth/2);
+		}else if (o.direction== 'left'){
+			left = obj.position().left-(o.handlerWidth/2);
+		}
+
+		handler.css({
+			left :left,
+			top : obj.position().top
+		});
+		handler.hover(function(){
+			clearTimeout(timeout);
+			handler.addClass(o.hoverClass);
+		},
+		function(){
+			timeout = setTimeout(function(){
+				handler.removeClass(o.hoverClass);
+			},300);
+		});
+
+		if(!window.handlerActive) window.handlerActive = {};
+		if(!window.handlerPosition) window.handlerPosition = {};
+
+		handler.mousedown(function(){
+			window.handlerPosition[handlerId] = handler.position().left;
+			window.handlerPanelWidth = obj.outerWidth();
+
+			window.handlerActive[handlerId] = true;
+		});
+
+		$(document).mouseup(function(){
+			window.handlerActive[handlerId] = false;
+
+
+
+			var left;
+			if( o.direction== 'right'){
+				left = obj.position().left+obj.outerWidth()-(o.handlerWidth/2);
+			}else if (o.direction== 'left'){
+				left = obj.position().left-(o.handlerWidth/2);
+			}
+
+			handler.css({left : left});
+
+		 	o.update({
+				element : obj,
+				handler : handler,
+				width : obj.outerWidth()
+			});
+		});
+
+		$(document).mousemove(function( event ) {
+		 	if(!window.handlerActive[handlerId])  return;
+
+		 	var newPosition = (event.pageX-2);
+		 	var negativeWidth = window.handlerPosition[handlerId] - newPosition;
+		 	var newWidth = window.handlerPanelWidth- (negativeWidth * (o.direction== 'right'?1:-1) );
+		 	if(newWidth < o.minWidth || newWidth <1 || newWidth > o.maxWidth ){
+		 		return;
+		 	}
+		 	handler.css({
+		 		left : newPosition+'px'
+		 	});
+
+		 	obj.css({
+		 		width : newWidth+'px',
+		 		maxWidth : newWidth+'px'
+		 	});
+
+		});
+
+		obj.after(handler);
+	},
+
 	fill: function (option,callback,progress){
 		
 		return this.each(function() {

+ 1 - 1
plugin/hackpoint/action.php

@@ -140,7 +140,7 @@ switch($_['action']){
 		$item->label = 'Documentation';
 		$item->sort = 0;
 		$item->type = 'readme';
-		$item->content = '# Documentation'.PHP_EOL.'Pour le moment, pas grand chose à dire...';
+		$item->content = '# Documentation'.PHP_EOL.'Utilisez le bouton + en bas de la barre latérale pour ajouter des ressources...';
 		$item->sketch = $sketch->id;
 		$item->save();
 

+ 8 - 1
plugin/hackpoint/install.php

@@ -1,16 +1,23 @@
 <?php 
 
-$enablePlugins = array('fr.idleman.hackpoint','fr.idleman.customiser','fr.idleman.part','fr.idleman.document','fr.idleman.notification','fr.idleman.navigation');
+$enablePlugins = array('fr.idleman.hackpoint','fr.idleman.customiser','fr.idleman.part','fr.idleman.document','fr.idleman.notification','fr.sys1.wiki','fr.idleman.navigation');
 
 $conf->put('core_theme','/plugin/customiser/theme/hackpoint/main.css');
+$conf->put('wiki_name','Le wiki');
+$conf->put('wiki_home','autre/bienvenue');
 
 $user = User::byLogin('admin');
 $user->name = 'NORRIS';
 $user->firstname = 'Chuck';
 $user->save();
+
+$user->preference('wiki_night_mode',true);
+
 $_SESSION['currentUser'] = serialize($user);
 $myUser = $user;
 
+
+
 $coreDir = File::dir().SLASH.'core';
 if(!file_exists($coreDir)) mkdir($coreDir);
 

+ 5 - 0
plugin/hackpoint/page.list.sketch.php

@@ -59,6 +59,11 @@ require_once(__DIR__.SLASH.'Sketch.class.php');
         <div class="no-sketch hidden">
           <h2><i class="far fa-meh-blank"></i> Howww !</h2>
           <p>Pas le moindre projet ici<br/> Il est temps de se mettre au boulot ...</p>
+         
+         <?php if($myUser->login==''): ?> 
+          <p><span class="text-warning pointer" onclick="event.stopPropagation();$('.user-dropdown-menu > .btn').click();$('#login').focus()">Connectez vous</span> pour ajouter de nouveaux projets.</p> 
+         <?php endif; ?>
+
           <?php if($myUser->can('hackpoint', 'edit')) : ?>
             <a href="action.php?action=hackpoint_sketch_add" class="btn btn-success"><i class="fas fa-plus"></i> Ajouter un projet</a>
           <?php endif; ?>

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

@@ -0,0 +1,145 @@
+<?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,$sort=0;
+	protected $TABLE_NAME = 'wiki_category';
+	public $fields =
+	array(
+		'id' => 'key',
+		'label' => 'string',
+		'slug' => 'string',
+		'icon' => 'string',
+		'color' => 'string',
+		'state' => 'string',
+		'sort' => 'int',
+		'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 $i=>$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->sort = $i;
+				$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 $i=>$file){
+			$path = str_replace($workspace.SLASH,'',wiki_os_decode($file));
+			if(!is_file($file) || isset($existing[$path]) || !file_exists($file)) 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(wiki_os_decode($file));
+			$page->category = $this->id;
+			$page->sort = $i;
+			$page->state = WikiPage::PUBLISHED;
+			$page->save();
+		}
+
+		$pages =  WikiPage::loadAll(array('category'=>$this->id),array('sort','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);
+	}
+}
+?>

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

@@ -0,0 +1,346 @@
+<?php
+/**
+ * Define a page.
+ * @author Valentin CARRUESCO
+ * @category Plugin
+ * @license copyright
+ */
+class WikiPage extends Entity{
+	public $id,$label,$content,$state,$path,$category,$slug,$sort = 0;
+	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',
+		'sort' => 'int',
+		'path' => 'string'
+	);
+
+    public static function defaultContent(){
+        $default = 'Mon contenu ici...';
+        $defaultFile = File::dir().'wiki'.SLASH.'default.md';
+        if(file_exists($defaultFile)) $default = file_get_contents($defaultFile);
+        return $default;
+    }
+
+	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, false)->fullName();
+	}
+	public function created(){
+		return relative_time($this->created);
+	}
+	public function updater(){
+		return User::byLogin($this->updater, false)->fullName();
+	}
+	public function updated(){
+		return relative_time($this->updated);
+	}
+	public function html(){
+		
+		$markdown = new WikiPageParsedown();
+		return $markdown->text($this->content);
+	}
+	public function content(){
+		$this->content = file_get_contents(self::workspace().SLASH.wiki_os_encode($this->path));
+	}
+
+}
+
+require_once(__DIR__.SLASH.'lib'.SLASH.'Parsedown.php');
+//Etend la classe parsedown pour y ajouter des features (ex:le souligné (__texte souligné__))
+class WikiPageParsedown extends Parsedown{
+
+	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"), 
+                        'text' => $text,
+                    ),
+                ),
+            );
+
+            return $Block;
+        }
+    }
+
+
+    protected function blockQuote($Line)
+    {
+        if (preg_match('/^>[ ]?+(.*+)/', $Line['text'], $matches))
+        {
+            $Block = array(
+                'element' => array(
+                    'name' => 'blockquote',
+                    'attributes' => array('class' => "blockquote"), 
+                    'handler' => array(
+                        'function' => 'linesElements',
+                        'argument' => (array) $matches[1],
+                        'destination' => 'elements',
+                    )
+                ),
+            );
+
+            return $Block;
+        }
+    }
+
+
+    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"), /* + sys1 */
+                '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 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"),
+            '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 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"), 
+                    'text' => $text,
+                ),
+            );
+        }
+    }
+
+    protected function inlineEmphasis($Excerpt)
+    {
+        if ( ! isset($Excerpt['text'][1]))
+        {
+            return;
+        }
+        $marker = $Excerpt['text'][0];
+        if(preg_match('/^__([^__]*)__/us', $Excerpt['text'], $matches)){
+        	$emphasis = 'u';
+        }else 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,
+                'attributes' => array(
+                        'class' => 'emphasis-underscore' ,
+                ),
+                'handler' => array(
+                    'function' => 'lineElements',
+                    'argument' => $matches[1],
+                    'destination' => 'elements',
+                )
+            ),
+        );
+    }
+}
+
+
+?>

+ 451 - 0
plugin/wiki/action.php

@@ -0,0 +1,451 @@
+<?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,$_;
+		    User::check_access('wiki','configure');
+
+		    foreach (glob(File::dir().'wiki'.SLASH."logo".SLASH."logo.*") as $filename)
+		        unlink($filename);
+
+		    Log::put("Suppression du logo",'Wiki');
+		});
+	break;
+
+	/** HOME **/
+	case 'wiki_page_home':
+		Action::write(function(&$response){
+			global $myUser,$_;
+			User::check_access('wiki','read');
+
+			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,$_;
+			User::check_access('wiki','read');
+			Log::put("Recherche lancée avec les mots clés : ".$_['term'],'Wiki');
+
+			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']);
+	    Log::put("Téléchargement de la page ".$workspace.SLASH.$page->path,'Wiki');
+	    
+		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,$_;
+			User::check_access('wiki','read');
+			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(array(), array('sort','label')) 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,$_;
+			User::check_access('wiki','read');
+			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;
+
+	 		Log::put("Consultation de la catégorie ".$category->toText(),'Wiki');
+		});
+	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);
+	 	Log::put("Téléchargement de la catégorie ".$workspace.SLASH.$category->path,'Wiki');
+	 	
+	    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,$_;
+			User::check_access('wiki','edit');
+			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();
+			Log::put("Création/Modification de catégorie :".$item->toText(),'Wiki');
+		});
+	break;
+
+	//Suppression d'élement page
+	case 'wiki_category_delete':
+		Action::write(function(&$response){
+			global $myUser,$_;
+			User::check_access('wiki','delete');
+			require_once(__DIR__.SLASH.'WikiCategory.class.php');
+			require_once(__DIR__.SLASH.'WikiPage.class.php');
+			$category = WikiCategory::getById($_['id']);
+			WikiPage::delete(array('category'=>$_['id']));
+			WikiCategory::deleteById($_['id']);
+			Log::put("Suppression de catégorie :".$category->toText(),'Wiki');
+		});
+	break;
+
+	/** PAGE **/
+
+
+	case 'wiki_page_move':
+	Action::write(function(&$response){
+		global $myUser,$_;
+		User::check_access('wiki','edit');
+		if(empty($_['category'])) throw new Exception("catégorie non spécifiée", 400);
+		require_once(__DIR__.SLASH.'WikiPage.class.php');
+		require_once(__DIR__.SLASH.'WikiCategory.class.php');
+
+		$page = WikiPage::provide('page');
+		$category = WikiCategory::getById($_['category']);
+
+		$page->category = $category->id;
+
+		$oldPath = $page->path;
+		$page->path = $category->path.SLASH.WikiPage::path_from_label($page->label).'.md';
+
+
+		$oldPath = WikiPage::workspace().SLASH.wiki_os_encode($oldPath);
+		$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");
+		if(!file_exists($oldPath)) throw new Exception("Impossible de retrouver l'ancien chemin de la page");
+		rename($oldPath, $newPath);
+
+		$page->save();
+
+	});
+	break;
+
+	//tri des pages
+	case 'wiki_page_sort':
+	Action::write(function(&$response){
+		global $myUser,$_;
+		User::check_access('wiki','edit');
+		if(empty($_['sort'])) throw new Exception("tri non spécifiée", 400);
+		require_once(__DIR__.SLASH.'WikiPage.class.php');
+
+		foreach ($_['sort'] as $sort => $id) {
+			$page = WikiPage::getById($id);
+			$page->sort = $sort;
+			$page->save();
+		}
+
+	});
+	break;
+
+	//tri des categories
+	case 'wiki_category_sort':
+	Action::write(function(&$response){
+		global $myUser,$_;
+		User::check_access('wiki','edit');
+		if(empty($_['sort'])) throw new Exception("tri non spécifiée", 400);
+		require_once(__DIR__.SLASH.'WikiCategory.class.php');
+
+		foreach ($_['sort'] as $sort => $id) {
+			$page = WikiCategory::getById($id);
+			$page->sort = $sort;
+			$page->save();
+		}
+
+	});
+	break;
+
+	//Ajout ou modification d'élément page
+	case 'wiki_page_save':
+		Action::write(function(&$response){
+			global $myUser,$_;
+			User::check_access('wiki','edit');
+			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->content = WikiPage::defaultContent();
+				$page->slug = slugify($page->label);
+			} else {
+				$category = WikiCategory::getById($page->category);
+				if(!isset($_['label']) || empty($_['label'])) throw new Exception("Le nom de la page ne peut être vide");
+				
+				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();
+
+			Log::put("Création/Modification de page :".$page->toText(),'Wiki');
+		});
+	break;
+
+	//Suppression d'élement page
+	case 'wiki_page_delete':
+		Action::write(function(&$response){
+			global $myUser,$_;
+			User::check_access('wiki','delete');
+			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);
+
+			Log::put("Suppression de page :".$page->toText(),'Wiki');
+		});
+	break;
+
+	case 'wiki_page_open':
+		Action::write(function(&$response){
+			global $myUser,$_;
+			User::check_access('wiki','read');
+			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;
+
+			Log::put("Consultation de page :".$page->toText(),'Wiki');
+		});
+	break;
+	
+	//Sauvegarde des configurations de wiki
+	case 'wiki_setting_save':
+		Action::write(function(&$response){
+			global $myUser,$_,$conf;
+			User::check_access('wiki','configure');
+			foreach(Configuration::setting('wiki') as $key=>$value){
+				if(!is_array($value)) continue;
+				$allowed[] = $key;
+			}
+
+			if(!empty($_['fields']['wiki_default_content'])){
+				$defaultFile = File::dir().'wiki'.SLASH.'default.md';
+				file_put_contents($defaultFile, $_['fields']['wiki_default_content']);
+				unset($_['fields']['wiki_default_content']);
+			}
+
+
+			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']);
+			}
+
+
+
+			Log::put("Enregistrement des réglages : ".implode(', ', $_['fields']),'Wiki');
+		});
+	break;
+
+	case 'wiki_file_upload':
+		Action::write(function(&$response){
+			global $myUser,$_,$conf;
+			User::check_access('wiki','edit');
+			if(!isset($_FILES['file']) || empty($_FILES)) return;
+			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]);
+				}
+
+
+				
+				
+				$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']).')';
+					rename($_FILES['file']['tmp_name'][$i],$filePath);
+				break;
+				case 'md':
+					$row['tag'] = file_get_contents($_FILES['file']['tmp_name'][$i]);
+				break;
+				default:
+					$row['tag'] = '['.$row['name'].'](action.php?action=wiki_file_read&file='.base64_encode($row['relative']).')';
+					rename($_FILES['file']['tmp_name'][$i],$filePath);
+				break;
+				}
+				$response['rows'][] = $row;
+				Log::put("Upload d'un élément : ".$filePath,'Wiki');
+			}
+		});
+	break;
+
+	case 'wiki_file_read':
+		global $myUser,$_,$conf;
+		User::check_access('wiki','read');
+		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;
+
+}
+?>

+ 13 - 0
plugin/wiki/app.json

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

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

@@ -0,0 +1,1070 @@
+/** WIKI **/
+.html.module-wiki *::-webkit-scrollbar {
+	width: 6px;
+	height: 6px;
+}
+.html.module-wiki *::-webkit-scrollbar-button {
+	width: 0px;
+	height: 0px;
+}
+.html.module-wiki *::-webkit-scrollbar-thumb {
+	background: #cecece;
+	border: 0px none #ffffff;
+	border-radius: 50px;
+}
+.html.module-wiki *::-webkit-scrollbar-thumb:hover {
+	background: #707070;
+}
+.html.module-wiki *::-webkit-scrollbar-thumb:active {
+	background: #949494;
+}
+.html.module-wiki *::-webkit-scrollbar-track {
+	background: transparent;
+	border: 0px none #ffffff;
+	border-radius: 50px;
+}
+.html.module-wiki *::-webkit-scrollbar-track:hover {
+	background: #d4d4d4;
+}
+.html.module-wiki *::-webkit-scrollbar-track:active {
+	background: #d3d3d3;
+}
+.html.module-wiki *::-webkit-scrollbar-corner {
+	background: transparent;
+}
+
+/* Conteneur principal du plugin wiki */
+.html.module-wiki *::selection {
+	color: #ffffff;
+	background-color: #444444;
+}
+.html.module-wiki {
+	height: 100%;
+}
+.html.module-wiki .form-control {
+	height: auto;
+}
+.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.form-control:active,
+.wiki-login-box input.form-control:focus {
+	border: 2px solid orange;
+	outline: 0;
+	box-shadow: 0 0 0 0 rgba(0,0,0,0);
+	color: #000;
+}
+.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;
+	margin: 50px 0 0 0;
+	width: auto;
+	min-height: calc(100% - 50px);
+	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;
+	/*height: 100vh;*/
+	height: calc(100vh - 50px);
+	width: 280px;
+	position: static;
+	color:#4e5c6e;
+	background: #f4f7fa;
+	user-select: none;
+	transition: background-color 0.2s ease-in-out;
+}
+
+
+
+#wiki-categories .wiki-category-hover{
+	background: #e0e3e5;
+}
+
+#sideMenu li:not(.category-open):hover {
+    color: #20252d;
+    transition: color 0.015s ease-in-out;
+}
+.html.module-wiki #editor {
+	flex-direction: column;
+	/*height: 100vh;*/
+	height: calc(100vh - 50px);
+	overflow-y: auto;
+	padding: 15px 30px;
+	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: 2000;
+	background:#ffffff;
+	transition: all 0.2s ease-in-out;
+	transform: translateX(-100%);
+	opacity: 0;
+	padding: 25px;
+}
+.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: 55px;
+}
+#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: 15px;
+    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;
+}
+.module-wiki .container-fluid .dropdown-menu {
+    font-size: 0.85em;
+}
+.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-main-menu,
+#wiki-categories {
+	margin: 15px 0px;
+	padding: 0px 15px;
+	overflow: auto;
+}
+#wiki-categories {
+	min-height: 220px;
+	padding-right: 0;
+    margin-bottom: 0;
+}
+#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{
+	height: calc(100vh - 270px);
+    overflow: auto;
+}
+#wiki-categories ul{
+	list-style-type: none;
+	margin: 0;
+	padding: 0;
+}
+#wiki-categories ul > li > ul{
+	padding-left: 20px;
+	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;
+	transition: all 0.2s ease-in-out;
+}
+#wiki-categories  > ul > li:hover .category-option{
+	transform: translateX(0px);
+	opacity: 1;
+}
+.wiki-page-content .table th,
+.wiki-page-content .table td {
+	border: 1px solid #e9ecef;
+}
+.wiki-page-content img {
+	max-width: 100%;
+}
+.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{
+	padding: 0 5px;
+    border-radius: 3px;
+    display: inline-block;
+    position: fixed;
+    right: 20px;
+    top: 70px;
+    z-index: 105;
+    user-select: none;
+    background-color: rgba(255,255,255,0.8);
+    transition: all 0.1s linear;
+}
+.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 linear;
+}
+.html.module-wiki .page-option li .page-option-item > i {
+	opacity: 0.5;
+}
+.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;
+}
+.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 {
+	margin-bottom: 15px;
+    /*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;
+	vertical-align: middle;
+	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 */
+/* Possibilité d'utiliser les prefs
+ * utilisateur avec la règle qui suit.
+ * Voir: https://developer.mozilla.org/fr/docs/Web/CSS/@media/prefers-color-scheme
+ */
+/*@media (prefers-color-scheme: dark) {
+
+}
+*/
+.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 #sideMenu li:not(.category-open):hover {
+    color: #949698fa;
+    transition: color 0.015s ease-in-out;
+}
+.module-wiki.night-mode .wiki-page-content * {
+    color: #D0D0D0;
+}
+.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 {
+	background-color: transparent;
+}
+.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,
+.html.module-wiki.night-mode .editor-preview-active {
+    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,
+.html.module-wiki.night-mode .editor-preview-active 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.disabled-for-preview a:not(.no-disable),
+.html.module-wiki.night-mode .editor-toolbar.disabled-for-preview,
+.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,
+.html.module-wiki.night-mode .editor-toolbar.disabled-for-preview 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: #F7D007;
+	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
+ 6 - 0
plugin/wiki/css/simplemde.min.css


BIN
plugin/wiki/img/logo.png


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

@@ -0,0 +1,702 @@
+var wikiEditor;
+var inEdition = false;
+
+//CHARGEMENT DE LA PAGE
+function init_plugin_wiki(){
+	if($('#sideMenu').length) {
+		$('#sideMenu').panelResize({
+			minWidth : 300
+		});
+	}
+
+	wiki_category_search(function(){
+		var page = $.urlParam('page');
+		var category = $.urlParam('category');
+
+		if(page && page!=''){
+			wiki_page_open(category,page);
+		} else if(category && category!=''){
+			wiki_category_open(category);
+		} else{
+			wiki_page_home();
+		}
+
+		$('#wiki-categories #categories').sortable({
+			cursorAt: { top:15 },
+			distance: 10,
+			axis: "y",
+			containment: "parent",
+			update: function( event, ui ) {
+				var sort = [];
+				//tri d'une page au sein d'une catégorie
+	      		$('#wiki-categories .category:visible').each(function(i,element){
+	      			sort.push($(element).attr('data-id'));
+	      		});
+	      		$.action({
+					action : 'wiki_category_sort',
+					sort : sort
+				});
+			}
+		});
+	});
+
+	window.onbeforeunload = function(){
+		if(inEdition) return "Êtes-vous sûr de vouloir quitter la page sans sauvegarder ?";
+	}
+
+	//Champ recherche
+	$('.wiki_search_item input').blur(function(){
+		$('.wiki_search_item span').removeClass('hidden').animate({opacity: '1'}, 150);
+		$(this).animate({opacity: '0'}, 150).addClass('hidden');
+	}).keyup(function(e){
+		if(e.keyCode != 13) return;
+		wiki_search_item($(this));
+	});
+
+	//Smooth scrolling pour sommaire
+	var editor = $('.module-wiki #editor');
+	$('#wiki-summary').on('click', 'a[href^="#"]',function(e) {
+		e.preventDefault();
+
+	    var href = $.attr(this, 'href');
+	    editor.animate({
+	        scrollTop: $(href).offset().top
+	    }, 500, function () {
+	        window.location.hash = href;
+	    });
+	});
+
+
+
+	//Night mode
+	if($('#night-mode-check').prop('checked') == true)
+		$('html.module-wiki').addClass('night-mode');
+
+	wiki_document_upload();
+
+	//Récuperation des images en presse papier et upload
+	window.addEventListener("paste", function(event){
+		var clipboardData = event.clipboardData || window.clipboardData;
+	    if(clipboardData.getData('Text').length) return;
+
+		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){
+	if(inEdition && !confirm('Êtes vous sûr de vouloir quitter la page sans sauvegarder ?')) return;
+	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(){
+	var search = $('.wiki_search_item');
+	if($('input', search).is(':visible')) return;
+	$('span', search).animate({opacity: '0'}, 100).addClass('hidden');
+	$('input', search).val('').removeClass('hidden').animate({opacity: '1'}, 100).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){
+
+	if(inEdition && !confirm('Êtes vous sûr de vouloir quitter la page sans sauvegarder ?')) return;
+
+	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');
+
+		$('#wiki-categories .category > ul').sortable({
+			axis: "y",
+			distance: 10,
+
+			update: function( event, ui ) {
+				var sort = [];
+				//tri d'une page au sein d'une catégorie
+	      		$('.page',this).each(function(i,element){
+	      			sort.push($(element).attr('data-id'));
+	      		});
+	      		$.action({
+					action : 'wiki_page_sort',
+					sort : sort
+				});
+			}
+		});
+
+		$( "#wiki-categories .category" ).droppable({
+		  classes: {
+	        "ui-droppable-hover": "wiki-category-hover"
+	      },
+	      tolerance  : 'fit',
+	      drop: function( event, ui ) {
+
+	      	var category = $(this);
+	      	var categorySlug= $(this).attr('data-category');
+	      	var page = ui.draggable;
+
+	      	if(isNaN(category.attr('data-id')) || isNaN(page.attr('data-id'))) return;
+
+	      	//si on drop dans la catégorie dans laquelle la page est déja, on ne fait rien
+	      	if(page.attr('data-category') == categorySlug) return;
+
+
+	      	//Déplacement dans une autre catégorie
+	      	$.action({
+				action : 'wiki_page_move',
+				category : category.attr('data-id'),
+				page : page.attr('data-id'),
+			},function(r){
+				wiki_category_open(categorySlug,null,function(){
+					$('[data-id="'+page.attr('data-id')+'"]',category).trigger('click');
+				});
+				var liPage = ui.draggable.remove();
+			});
+
+	      }
+	    });
+
+		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);
+	wiki_page_summary_refresh();
+	if($('#wiki-summary').hasClass('show')) {
+		togglerIcon.removeClass('active');
+		$('#wiki-summary').removeClass('show');
+	} else {
+		togglerIcon.addClass('active');
+		$('#wiki-summary').addClass('show');
+	}
+}
+function wiki_page_summary_refresh(){
+	var summary = $('#wiki-summary ul');
+	var htmlSummary = '';
+	$('#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-z0-9]/ig,'-').replace(/[-]{2,}/ig,'-');
+
+		title.find('.wiki-title-link').remove();
+		var link = $('<i class="fas fa-link wiki-title-link"></i>');
+		title.attr('id', slug).append(link);
+
+		link
+			.addClass('pointer')
+			.attr('onclick' , "wiki_copy_anchor(this);")
+
+		if(parseInt(tag.replace('h',''))<5) htmlSummary += '<li class="summary-'+tag+'"><a href="#'+slug+'">'+title.text()+'</a></li>';
+	});
+	summary.html(htmlSummary);
+}
+function wiki_page_open(category,page,event,callback){
+	if(event) event.stopPropagation();
+	if(inEdition && !confirm('Êtes vous sûr de vouloir quitter la page sans sauvegarder ?')) return;
+	
+	$.action({
+		action : 'wiki_page_open',
+		category : category,
+		page : page,
+	},function(r){
+		wiki_category_open(r.categorySlug, true, function(){
+
+		$('#editor').html(r.content);
+		wiki_page_summary_refresh();
+
+		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 title = $(anchor).parent();
+	var id = title.attr('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(callback){
+	$('.wiki-page-content #content-text').removeClass('hidden');
+	$('.wiki-page-content #content-html').addClass('hidden');
+
+	var pageLabel = $('#page-label');
+	pageLabel.attr('contenteditable','true').addClass('show');
+	pageLabel.keydown(function(e){
+		if(e.keyCode == 13) return false;
+	});
+
+	$('.page-option-menu').removeClass('shown');
+	$('.page-editor-menu').addClass('hidden');
+
+	$('.page-save-menu').removeClass('hidden');
+	$('.page-option-save').addClass('show');
+
+	inEdition = true;
+	$('#editor').addClass('edition');
+	wikiEditor = new SimpleMDE({
+		element: $("#content-text")[0],
+		spellChecker : false,
+		promptURLs: true,
+		autofocus : true,
+		toolbar : [
+		'bold',"italic",{
+			'name' : 'underline',
+			action: function customFunction(editor){
+				var selected = editor.codemirror.getSelection();
+				var newValue = '';
+				if(selected.match(/^__.*__$/gi)){
+					newValue = selected.replace(/^__(.*)__$/gi,"$1");
+				}else{
+					newValue = "__"+selected+"__";
+				}
+				editor.codemirror.replaceSelection(newValue);
+
+			},
+			className: "fas fa-underline",
+		},"heading-1","heading-2","heading-3","|", "unordered-list", "ordered-list","code","link","image","quote","table","clean-block","horizontal-rule","|","preview","side-by-side","fullscreen"
+
+		],
+
+		//showIcons: ["code", "table","heading-2","heading-3","clean-block","horizontal-rule"]
+	});
+	if(callback) callback();
+}
+
+function wiki_page_add(element, event){
+	event.stopPropagation();
+	var category = !element ? $('.category-open').attr('data-id') : $(element).closest('li.category').attr('data-id');
+
+	$.action({
+		action : 'wiki_page_save',
+		category : category,
+		content : 'Mon contenu ici...',
+	},function(r){
+		wiki_page_open(r.category.slug,r.page.slug,event,function(){
+			wiki_page_edit(function(){
+				$('.html.module-wiki #editor.edition .CodeMirror').click();
+			});
+		});
+	});
+}
+
+//Ajout ou modification d'élément page
+function wiki_page_save(){
+	var pageId = $('.wiki-breadcrumb').attr('data-page');
+
+	$.action({
+		action : 'wiki_page_save',
+		id : pageId,
+		label : $('#page-label').text(),
+		content : wikiEditor.value(),
+	},function(r){
+		var editor = $('#editor');
+		var currentPage = $('#categories li.category-open li[data-id="'+pageId+'"].page-open');
+
+		$('.page-option-save, #page-label').removeClass('show');
+		$('.page-option-menu').addClass('shown');
+		editor.html(r.content).removeClass('hidden');
+
+		$('#page-label').removeAttr('contenteditable','true');
+		editor.removeClass('edition');
+
+		currentPage.html(currentPage.children().get(0).outerHTML+' '+r.page.label).attr('data-page', r.page.slug);
+		wiki_change_url(r.category.slug, r.page.slug);
+		wiki_page_summary_refresh();
+		inEdition = false;
+	});
+}
+
+//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.addClass('hidden');
+	$('#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) {
+			var ext = f.name.split('.').pop();
+			if(!f.type && ext=='' ){
+				$.message('error', 'Impossible d\'envoyer un dossier / un élément sans extension.');
+				return;
+			}
+			if(!f.type && ext=='' && 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
+ 6 - 0
plugin/wiki/js/simplemde.min.js


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

@@ -0,0 +1,1979 @@
+<?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',
+                        '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',
+            '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"),
+                    '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"), 
+                '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"),
+                    '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',
+    );
+}

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

@@ -0,0 +1,28 @@
+<?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>

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

@@ -0,0 +1,23 @@
+<?php if($myUser->can('wiki','edit')): ?>
+	<div onclick="wiki_page_add(null, 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; ?>

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

@@ -0,0 +1,48 @@
+<?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') != ''): 
+	$homePage = explode('/', $conf->get('wiki_home'));
+	if(count($homePage) >= 2):
+		list($category,$page) = $homePage;
+
+		$category = WikiCategory::load(array('slug'=>$category));
+		if($category && $home = WikiPage::load(array('category'=>$category->id,'slug'=>$page)))
+			$home->content();
+?>
+	<div class="wiki-home-page">
+		<?php echo isset($home) && $home ? $home->html() : ''; ?>
+	</div>
+	<?php endif; ?>
+<?php endif; ?>
+
+<?php if(count($updated) == 0): ?>
+	<p>Aucune activitée récente 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>

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

@@ -0,0 +1,125 @@
+<?php
+//require_once('header.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-fw 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-fw 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" class="noPrint">
+    <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 py-1" aria-labelledby="dropdownMenuButton">
+                        <h6 class="dropdown-header">Général :</h6>
+                        <a class="dropdown-item px-3" href="index.php"><i class="fas fa-fw fa-home"></i> Accueil</a>
+                        <!-- <a class="dropdown-item" href="http://sys1.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 px-3" href="setting.php?section=global.wiki"><i class="fas fa-fw fa-cogs"></i> Réglages</a>
+                        <?php endif; ?>
+                        <div class="dropdown-divider my-1"></div>
+                        <a class="dropdown-item px-3" href="action.php?action=logout&url=<?php echo base64_encode('index.php?module=wiki'); ?>"><i class="fas fa-fw 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}}" title="{{label}}" 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(this, 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}}" title="{{label}}" 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>
+

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

@@ -0,0 +1,12 @@
+<?php require_once(__ROOT__.SLASH.'header.php'); 
+	$url = base64_encode('index.php?module=wiki');
+?>
+<div class="wiki-login-box">
+	<form class="login-form" data-action="login" data-url="<?php echo $url; ?>">
+		<i class="far fa-bookmark wiki-logo"></i>
+		<input type="text" class="form-control form-control-sm" id="login" name="login">
+		<input type="password" data-type="password" class="form-control form-control-sm" name="password" id="password">
+		<div class="btn btn-light" id="login-button" onclick="core_login(this);">Connexion</div>
+	</form>
+</div>
+<?php require_once(__ROOT__.SLASH.'footer.php'); ?>

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

@@ -0,0 +1,56 @@
+<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 class="noPrint" 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 noPrint">
+	<li class="page-summary-menu">
+		<div class="page-option-item page-option-summary noPrint" 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 noPrint">
+			<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();"><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="#" title="Imprimer la page" onclick="window.print();"><i class="fas fa-print"></i> Imprimer</a>
+				<a class="dropdown-item" href="#" title="Télécharger la page" 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 == '' ): ?>
+	<div onclick="wiki_page_edit();" class="btn btn-primary float-right"><i class="fas fa-pencil-alt"></i> Éditer la page</div>
+	<h1><i class="far fa-sticky-note"></i> <?php echo $page->label; ?></h1>
+	<div class="clear"></div>
+	<p class="mt-3">Cette page est bien vide...<p>
+<?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; ?>

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

@@ -0,0 +1,108 @@
+<?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(), 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; ?>

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

@@ -0,0 +1,66 @@
+<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>

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

@@ -0,0 +1,27 @@
+<?php
+global $myUser,$conf;
+User::check_access('wiki','configure');
+?>
+
+<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>

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

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

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

@@ -0,0 +1,87 @@
+## 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

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

@@ -0,0 +1,130 @@
+<?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.sys1.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,md,docx,doc,xls,xlsx,ppt,pptx');
+	$conf->put('wiki_max_size',10);
+}
+
+//Fonction executée lors de la désactivation du plugin
+function wiki_uninstall($id){
+	if($id != 'fr.sys1.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 (get_OS() === 'WIN') ? utf8_decode($text) : $text;
+}
+
+function wiki_os_decode($text){
+	return (get_OS() === '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"),
+    'wiki_default_content' => array("label"=>"Contenu par défaut","legend"=>"Contenu affiché à la création d'une page","type"=>"textarea"),
+));
+
+//Déclation des assets
+Plugin::addCss("https://fonts.googleapis.com/css?family=Roboto:300,400,700");
+Plugin::addCss("/css/main.css"); 
+Plugin::addCss("/css/simplemde.min.css"); 
+Plugin::addJs("/js/simplemde.min.js"); 
+Plugin::addJs("/js/main.js"); 
+
+//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