diff --git a/bl-kernel/admin/controllers/dashboard.php b/bl-kernel/admin/controllers/dashboard.php
index 46f5448f..63846b87 100644
--- a/bl-kernel/admin/controllers/dashboard.php
+++ b/bl-kernel/admin/controllers/dashboard.php
@@ -1,66 +1,16 @@
 <?php defined('BLUDIT') or die('Bludit CMS.');
 
+// ============================================================================
+// Authorization
+// ============================================================================
+
 // ============================================================================
 // Functions
 // ============================================================================
-function updateBludit() {
-	global $site;
-	global $syslog;
-
-	// New installation
-	if ($site->currentBuild()==0) {
-		$site->set(array('currentBuild'=>BLUDIT_BUILD));
-	}
-
-	// Check if Bludit need to be update
-	if ( ($site->currentBuild() < BLUDIT_BUILD) || isset($_GET['update']) ) {
-		Log::set('UPDATE SYSTEM - Starting.');
-
-		// Updates only for version less than Bludit v3.0 rc-3
-		if ($site->currentBuild()<='20180910') {
-			@mkdir(PATH_WORKSPACES, DIR_PERMISSIONS, true);
-			$plugins = array('simple-stats', 'pluginRSS', 'pluginSitemap', 'pluginTimeMachineX', 'pluginBackup');
-			foreach ($plugins as $plugin) {
-				if (pluginActivated($plugin)) {
-					Log::set('UPDATE SYSTEM - Re-enable plugin: '.$plugin);
-					deactivatePlugin($plugin);
-					activatePlugin($plugin);
-				}
-			}
-		}
-
-		// Updates only for version less than Bludit v3.1
-		if ($site->currentBuild()<='20180921') {
-			@mkdir(PATH_UPLOADS_PAGES, DIR_PERMISSIONS, true);
-			$site->set(array('imageRelativeToAbsolute'=>true, 'imageRestrict'=>false));
-		}
-
-		// Set the current build number
-		$site->set(array('currentBuild'=>BLUDIT_BUILD));
-		Log::set('UPDATE SYSTEM - Finished.');
-
-		// Add to syslog
-		$syslog->add(array(
-			'dictionaryKey'=>'system-updated',
-			'notes'=>'Bludit v'.BLUDIT_VERSION
-		));
-	}
-}
 
 // ============================================================================
-// Main before POST
+// Main
 // ============================================================================
 
-// ============================================================================
-// POST Method
-// ============================================================================
-
-// ============================================================================
-// Main after POST
-// ============================================================================
-
-// Try update Bludit
-updateBludit();
-
-// Title of the page
-$layout['title'] .= ' - '.$L->g('Dashboard');
\ No newline at end of file
+// HTML <title>
+$layout['title'] = $L->g('Dashboard') . ' - ' . $layout['title'];
\ No newline at end of file
diff --git a/bl-kernel/admin/controllers/plugins-settings.php b/bl-kernel/admin/controllers/plugins-settings.php
index 1d0d7981..97931461 100644
--- a/bl-kernel/admin/controllers/plugins-settings.php
+++ b/bl-kernel/admin/controllers/plugins-settings.php
@@ -29,14 +29,5 @@ if (!method_exists($plugin, 'form')) {
 	Redirect::page('plugins');
 }
 
-// Save the settings
-if ($_SERVER['REQUEST_METHOD'] == 'POST') {
-	$plugin->post();
-	$syslog->add(array(
-		'dictionaryKey'=>'plugin-configured',
-		'notes'=>$plugin->name()
-	));
-}
-
 // HTML <title>
 $layout['title'] = $L->g('Plugin'). ' [ ' .$plugin->name(). ' ] ' . ' - ' . $layout['title'];
\ No newline at end of file
diff --git a/bl-kernel/admin/controllers/settings.php b/bl-kernel/admin/controllers/settings.php
index 27e7dff1..010715f3 100644
--- a/bl-kernel/admin/controllers/settings.php
+++ b/bl-kernel/admin/controllers/settings.php
@@ -14,5 +14,5 @@ checkRole(array('admin'));
 // Main
 // ============================================================================
 
-// View HTML <title>
+// HTML <title>
 $layout['title'] = $L->g('Settings') . ' - ' . $layout['title'];
\ No newline at end of file
diff --git a/bl-kernel/admin/themes/booty/index.php b/bl-kernel/admin/themes/booty/index.php
index 79ca88a0..086fd993 100644
--- a/bl-kernel/admin/themes/booty/index.php
+++ b/bl-kernel/admin/themes/booty/index.php
@@ -33,13 +33,13 @@
 	echo HTML::jquery();
 	echo HTML::jsBootstrap();
 	echo HTML::jsSortable();
+	echo HTML::bootbox();
 	echo HTML::js(array(
 		'jquery.datetimepicker.full.min.js',
 		'jquery-ui.min.js',
 		'select2.full.min.js',
 		'tagsinput-revisited.min.js',
 		'functions.js',
-		'bootbox.all.min.js',
 		'api.js'
 	), DOMAIN_CORE_JS);
 	?>
diff --git a/bl-kernel/admin/themes/gris b/bl-kernel/admin/themes/gris
deleted file mode 160000
index c97e1681..00000000
--- a/bl-kernel/admin/themes/gris
+++ /dev/null
@@ -1 +0,0 @@
-Subproject commit c97e1681dda9576128298d299e42973646df2475
diff --git a/bl-kernel/ajax/delete-image.php b/bl-kernel/ajax/delete-image.php
deleted file mode 100644
index 0135a6ad..00000000
--- a/bl-kernel/ajax/delete-image.php
+++ /dev/null
@@ -1,43 +0,0 @@
-<?php defined('BLUDIT') or die('Bludit CMS.');
-header('Content-Type: application/json');
-
-/*
-| Delete an image from a particular page
-|
-| @_POST['filename']	string	Name of the file to delete
-| @_POST['uuid']	string	Page UUID
-|
-| @return	array
-*/
-
-// $_POST
-// ----------------------------------------------------------------------------
-$filename = isset($_POST['filename']) ? $_POST['filename'] : false;
-$uuid = empty($_POST['uuid']) ? false : $_POST['uuid'];
-// ----------------------------------------------------------------------------
-
-if ($filename===false) {
-	ajaxResponse(1, 'The filename is empty.');
-}
-
-if ($uuid && IMAGE_RESTRICT) {
-	$imagePath = PATH_UPLOADS_PAGES.$uuid.DS;
-	$thumbnailPath = PATH_UPLOADS_PAGES.$uuid.DS.'thumbnails'.DS;
-} else {
-	$imagePath = PATH_UPLOADS;
-	$thumbnailPath = PATH_UPLOADS_THUMBNAILS;
-}
-
-// Delete image
-if (Sanitize::pathFile($imagePath.$filename)) {
-	Filesystem::rmfile($imagePath.$filename);
-}
-
-// Delete thumbnail
-if (Sanitize::pathFile($thumbnailPath.$filename)) {
-	Filesystem::rmfile($thumbnailPath.$filename);
-}
-
-ajaxResponse(0, 'Image deleted.');
-
-?>
\ No newline at end of file
diff --git a/bl-kernel/ajax/list-images.php b/bl-kernel/ajax/list-images.php
deleted file mode 100644
index 7f608ff8..00000000
--- a/bl-kernel/ajax/list-images.php
+++ /dev/null
@@ -1,62 +0,0 @@
-<?php defined('BLUDIT') or die('Bludit CMS.');
-header('Content-Type: application/json');
-
-/*
-| Returns a list of images from a particular page
-|
-| @_POST['pageNumber']	int	Page number for the paginator
-| @_POST['path']	string	Pre-defined name for the directory to read, its pre-defined to avoid security issues
-| @_POST['uuid']	string	Page UUID
-|
-| @return	array
-*/
-
-// $_POST
-// ----------------------------------------------------------------------------
-// $_POST['pageNumber'] > 0
-$pageNumber = empty($_POST['pageNumber']) ? 1 : (int)$_POST['pageNumber'];
-$pageNumber = $pageNumber - 1;
-
-$path = empty($_POST['path']) ? false : $_POST['path'];
-$uuid = empty($_POST['uuid']) ? false : $_POST['uuid'];
-// ----------------------------------------------------------------------------
-
-// Set the path to get the file list
-if ($path=='thumbnails') {
-	if ($uuid && IMAGE_RESTRICT) {
-		$path = PATH_UPLOADS_PAGES.$uuid.DS.'thumbnails'.DS;
-	} else {
-		$path = PATH_UPLOADS_THUMBNAILS;
-	}
-} else {
-	ajaxResponse(1, 'Invalid path.');
-}
-
-// Get all files from the directory $path, also split the array by numberOfItems
-// The function listFiles split in chunks
-$listOfFilesByPage = Filesystem::listFiles($path, '*', '*', MEDIA_MANAGER_SORT_BY_DATE, MEDIA_MANAGER_NUMBER_OF_FILES);
-
-// Check if the page number exists in the chunks
-if (isset($listOfFilesByPage[$pageNumber])) {
-
-	// Get only the filename from the chunk
-	$files = array();
-	foreach ($listOfFilesByPage[$pageNumber] as $file) {
-		$filename = basename($file);
-		array_push($files, $filename);
-	}
-
-	// Returns the number of chunks for the paginator
-	// Returns the files inside the chunk
-	ajaxResponse(0, 'List of files and number of chunks.', array(
-		'numberOfPages'=>count($listOfFilesByPage),
-		'files'=>$files
-	));
-}
-
-ajaxResponse(0, 'List of files and number of chunks.', array(
-	'numberOfPages'=>0,
-	'files'=>array()
-));
-
-?>
\ No newline at end of file
diff --git a/bl-kernel/ajax/logo-remove.php b/bl-kernel/ajax/logo-remove.php
deleted file mode 100644
index 55f2b699..00000000
--- a/bl-kernel/ajax/logo-remove.php
+++ /dev/null
@@ -1,22 +0,0 @@
-<?php defined('BLUDIT') or die('Bludit CMS.');
-header('Content-Type: application/json');
-
-/*
-| Delete the site logo
-| This script delete the file and set and empty string in the database
-|
-| @return	array
-*/
-
-// Delete the file
-$logoFilename = $site->logo(false);
-if ($logoFilename) {
-	Filesystem::rmfile(PATH_UPLOADS.$logoFilename);
-}
-
-// Remove the logo from the database
-$site->set(array('logo'=>''));
-
-ajaxResponse(0, 'Logo removed.');
-
-?>
\ No newline at end of file
diff --git a/bl-kernel/ajax/logo-upload.php b/bl-kernel/ajax/logo-upload.php
deleted file mode 100644
index 6f1ad4e2..00000000
--- a/bl-kernel/ajax/logo-upload.php
+++ /dev/null
@@ -1,70 +0,0 @@
-<?php defined('BLUDIT') or die('Bludit CMS.');
-header('Content-Type: application/json');
-
-/*
-| Upload site logo
-| The final filename is the site's name and the extension is the same as the file uploaded
-|
-| @_FILES['inputFile']	multipart/form-data	File from form
-|
-| @return	array
-*/
-
-if (!isset($_FILES['inputFile'])) {
-	ajaxResponse(1, 'Error trying to upload the site logo.');
-}
-
-// Check path traversal on $filename
-if (Text::stringContains($_FILES['inputFile']['name'], DS, false)) {
-	$message = 'Path traversal detected.';
-	Log::set($message, LOG_TYPE_ERROR);
-	ajaxResponse(1, $message);
-}
-
-// File extension
-$fileExtension = Filesystem::extension($_FILES['inputFile']['name']);
-$fileExtension = Text::lowercase($fileExtension);
-if (!in_array($fileExtension, $GLOBALS['ALLOWED_IMG_EXTENSIONS'])) {
-	$message = $L->g('File type is not supported. Allowed types:').' '.implode(', ',$GLOBALS['ALLOWED_IMG_EXTENSIONS']);
-	Log::set($message, LOG_TYPE_ERROR);
-	ajaxResponse(1, $message);
-}
-
-// File MIME Type
-$fileMimeType = Filesystem::mimeType($_FILES['inputFile']['tmp_name']);
-if ($fileMimeType!==false) {
-	if (!in_array($fileMimeType, $GLOBALS['ALLOWED_IMG_MIMETYPES'])) {
-		$message = $L->g('File mime type is not supported. Allowed types:').' '.implode(', ',$GLOBALS['ALLOWED_IMG_MIMETYPES']);
-		Log::set($message, LOG_TYPE_ERROR);
-		ajaxResponse(1, $message);
-	}
-}
-
-// Final filename
-$filename = 'logo.'.$fileExtension;
-if (Text::isNotEmpty( $site->title() )) {
-	$filename = $site->title().'.'.$fileExtension;
-}
-
-// Delete old image
-$oldFilename = $site->logo(false);
-if ($oldFilename) {
-	Filesystem::rmfile(PATH_UPLOADS.$oldFilename);
-}
-
-// Move from temporary directory to uploads
-Filesystem::mv($_FILES['inputFile']['tmp_name'], PATH_UPLOADS.$filename);
-
-// Permissions
-chmod(PATH_UPLOADS.$filename, 0644);
-
-// Store the filename in the database
-$site->set(array('logo'=>$filename));
-
-ajaxResponse(0, 'Image uploaded.', array(
-	'filename'=>$filename,
-	'absoluteURL'=>DOMAIN_UPLOADS.$filename,
-	'absolutePath'=>PATH_UPLOADS.$filename
-));
-
-?>
diff --git a/bl-kernel/ajax/profile-picture-upload.php b/bl-kernel/ajax/profile-picture-upload.php
deleted file mode 100644
index 2160be55..00000000
--- a/bl-kernel/ajax/profile-picture-upload.php
+++ /dev/null
@@ -1,74 +0,0 @@
-<?php defined('BLUDIT') or die('Bludit CMS.');
-header('Content-Type: application/json');
-
-// $_POST
-// ----------------------------------------------------------------------------
-// (string) $_POST['username']
-$username = empty($_POST['username']) ? false : $_POST['username'];
-// ----------------------------------------------------------------------------
-
-if ($username===false) {
-	ajaxResponse(1, 'Error in username.');
-}
-
-if ( ($login->role()!='admin') && ($login->username()!=$username) ) {
-	ajaxResponse(1, 'Error in username.');
-}
-
-if (!isset($_FILES['profilePictureInputFile'])) {
-	ajaxResponse(1, 'Error trying to upload the profile picture.');
-}
-
-// Check path traversal
-if (Text::stringContains($username, DS, false)) {
-	$message = 'Path traversal detected.';
-	Log::set($message, LOG_TYPE_ERROR);
-	ajaxResponse(1, $message);
-}
-
-// Check file extension
-$fileExtension = Filesystem::extension($_FILES['profilePictureInputFile']['name']);
-$fileExtension = Text::lowercase($fileExtension);
-if (!in_array($fileExtension, $GLOBALS['ALLOWED_IMG_EXTENSIONS']) ) {
-	$message = $L->g('File type is not supported. Allowed types:').' '.implode(', ',$GLOBALS['ALLOWED_IMG_EXTENSIONS']);
-	Log::set($message, LOG_TYPE_ERROR);
-	ajaxResponse(1, $message);
-}
-
-// Check file MIME Type
-$fileMimeType = Filesystem::mimeType($_FILES['profilePictureInputFile']['tmp_name']);
-if ($fileMimeType!==false) {
-	if (!in_array($fileMimeType, $GLOBALS['ALLOWED_IMG_MIMETYPES'])) {
-		$message = $L->g('File mime type is not supported. Allowed types:').' '.implode(', ',$GLOBALS['ALLOWED_IMG_MIMETYPES']);
-		Log::set($message, LOG_TYPE_ERROR);
-		ajaxResponse(1, $message);
-	}
-}
-
-// Tmp filename
-$tmpFilename = $username.'.'.$fileExtension;
-
-// Final filename
-$filename = $username.'.png';
-
-// Move from temporary directory to uploads folder
-rename($_FILES['profilePictureInputFile']['tmp_name'], PATH_TMP.$tmpFilename);
-
-// Resize and convert to png
-$image = new Image();
-$image->setImage(PATH_TMP.$tmpFilename, PROFILE_IMG_WIDTH, PROFILE_IMG_HEIGHT, 'crop');
-$image->saveImage(PATH_UPLOADS_PROFILES.$filename, PROFILE_IMG_QUALITY, false, true);
-
-// Delete temporary file
-Filesystem::rmfile(PATH_TMP.$tmpFilename);
-
-// Permissions
-chmod(PATH_UPLOADS_PROFILES.$filename, 0644);
-
-ajaxResponse(0, 'Image uploaded.', array(
-	'filename'=>$filename,
-	'absoluteURL'=>DOMAIN_UPLOADS_PROFILES.$filename,
-	'absolutePath'=>PATH_UPLOADS_PROFILES.$filename
-));
-
-?>
diff --git a/bl-kernel/ajax/upload-images.php b/bl-kernel/ajax/upload-images.php
deleted file mode 100644
index c2fb4e9c..00000000
--- a/bl-kernel/ajax/upload-images.php
+++ /dev/null
@@ -1,97 +0,0 @@
-<?php defined('BLUDIT') or die('Bludit CMS.');
-header('Content-Type: application/json');
-
-/*
-| Upload an image to a particular page
-|
-| @_POST['uuid']	string	Page uuid
-|
-| @return		array
-*/
-
-// $_POST
-// ----------------------------------------------------------------------------
-$uuid = empty($_POST['uuid']) ? false : $_POST['uuid'];
-// ----------------------------------------------------------------------------
-
-// Check path traversal on $uuid
-if ($uuid) {
-	if (Text::stringContains($uuid, DS, false)) {
-		$message = 'Path traversal detected.';
-		Log::set($message, LOG_TYPE_ERROR);
-		ajaxResponse(1, $message);
-	}
-}
-
-// Set upload directory
-if ($uuid && IMAGE_RESTRICT) {
-	$imageDirectory = PATH_UPLOADS_PAGES.$uuid.DS;
-	$thumbnailDirectory = $imageDirectory.'thumbnails'.DS;
-	if (!Filesystem::directoryExists($thumbnailDirectory)) {
-		Filesystem::mkdir($thumbnailDirectory, true);
-	}
-} else {
-	$imageDirectory = PATH_UPLOADS;
-	$thumbnailDirectory = PATH_UPLOADS_THUMBNAILS;
-}
-
-$images = array();
-foreach ($_FILES['images']['name'] as $uuid=>$filename) {
-	// Check for errors
-	if ($_FILES['images']['error'][$uuid] != 0) {
-		$message = $L->g('Maximum load file size allowed:').' '.ini_get('upload_max_filesize');
-		Log::set($message, LOG_TYPE_ERROR);
-		ajaxResponse(1, $message);
-	}
-
-	// Convert URL characters such as spaces or quotes to characters
-	$filename = urldecode($filename);
-
-	// Check path traversal on $filename
-	if (Text::stringContains($filename, DS, false)) {
-		$message = 'Path traversal detected.';
-		Log::set($message, LOG_TYPE_ERROR);
-		ajaxResponse(1, $message);
-	}
-
-	// Check file extension
-	$fileExtension = Filesystem::extension($filename);
-	$fileExtension = Text::lowercase($fileExtension);
-	if (!in_array($fileExtension, $GLOBALS['ALLOWED_IMG_EXTENSIONS']) ) {
-		$message = $L->g('File type is not supported. Allowed types:').' '.implode(', ',$GLOBALS['ALLOWED_IMG_EXTENSIONS']);
-		Log::set($message, LOG_TYPE_ERROR);
-		ajaxResponse(1, $message);
-	}
-
-	// Check file MIME Type
-	$fileMimeType = Filesystem::mimeType($_FILES['images']['tmp_name'][$uuid]);
-	if ($fileMimeType!==false) {
-		if (!in_array($fileMimeType, $GLOBALS['ALLOWED_IMG_MIMETYPES'])) {
-			$message = $L->g('File mime type is not supported. Allowed types:').' '.implode(', ',$GLOBALS['ALLOWED_IMG_MIMETYPES']);
-			Log::set($message, LOG_TYPE_ERROR);
-			ajaxResponse(1, $message);
-		}
-	}
-
-	// Move from PHP tmp file to Bludit tmp directory
-	Filesystem::mv($_FILES['images']['tmp_name'][$uuid], PATH_TMP.$filename);
-
-	// Transform the image and generate the thumbnail
-	$image = transformImage(PATH_TMP.$filename, $imageDirectory, $thumbnailDirectory);
-
-	if ($image) {
-		chmod($image, 0644);
-		$filename = Filesystem::filename($image);
-		array_push($images, $filename);
-	} else {
-		$message = 'Error after transformImage() function.';
-		Log::set($message, LOG_TYPE_ERROR);
-		ajaxResponse(1, $message);
-	}
-}
-
-ajaxResponse(0, 'Images uploaded.', array(
-	'images'=>$images
-));
-
-?>
diff --git a/bl-kernel/boot/init.php b/bl-kernel/boot/init.php
index 6c941bd7..ac4ef92e 100644
--- a/bl-kernel/boot/init.php
+++ b/bl-kernel/boot/init.php
@@ -83,7 +83,7 @@ include(PATH_KERNEL.'language.class.php');
 include(PATH_KERNEL.'site.class.php');
 include(PATH_KERNEL.'categories.class.php');
 include(PATH_KERNEL.'syslog.class.php');
-include(PATH_KERNEL.'pagex.class.php');
+include(PATH_KERNEL.'page.class.php');
 include(PATH_KERNEL.'category.class.php');
 include(PATH_KERNEL.'tag.class.php');
 include(PATH_KERNEL.'user.class.php');
@@ -99,7 +99,6 @@ include(PATH_KERNEL.'functions.php');
 include(PATH_HELPERS.'text.class.php');
 include(PATH_HELPERS.'log.class.php');
 include(PATH_HELPERS.'date.class.php');
-include(PATH_HELPERS.'theme.class.php');
 include(PATH_HELPERS.'session.class.php');
 include(PATH_HELPERS.'redirect.class.php');
 include(PATH_HELPERS.'sanitize.class.php');
@@ -113,7 +112,6 @@ include(PATH_HELPERS.'tcp.class.php');
 include(PATH_HELPERS.'dom.class.php');
 include(PATH_HELPERS.'cookie.class.php');
 include(PATH_HELPERS.'bootstrap.class.php');
-
 include(PATH_HELPERS.'html.class.php');
 
 if (file_exists(PATH_KERNEL.'bludit.pro.php')) {
diff --git a/bl-kernel/helpers/html.class.php b/bl-kernel/helpers/html.class.php
index 4d6cd376..c51190f1 100644
--- a/bl-kernel/helpers/html.class.php
+++ b/bl-kernel/helpers/html.class.php
@@ -52,6 +52,12 @@ class HTML {
 		return '<script src="'.DOMAIN_CORE_VENDORS.'jquery/jquery.min.js?version='.BLUDIT_VERSION.'"></script>'.PHP_EOL;
 	}
 
+	public static function bootbox($attributes='')
+	{
+		// https://bootbox.com/
+		return '<script '.$attributes.' src="'.DOMAIN_CORE_VENDORS.'bootbox/bootbox.all.min.js?version='.BLUDIT_VERSION.'"></script>'.PHP_EOL;
+	}
+
 	public static function jsBootstrap($attributes='')
 	{
 		// https://getbootstrap.com/
diff --git a/bl-kernel/helpers/theme.class.php b/bl-kernel/helpers/theme.class.php
deleted file mode 100644
index 6446eea2..00000000
--- a/bl-kernel/helpers/theme.class.php
+++ /dev/null
@@ -1,280 +0,0 @@
-<?php
-
-// DEPRECATED CLASS
-// WILL BE REMOVED IN BLUDIT V4
-class Theme {
-
-	public static function socialNetworks()
-	{
-		global $site;
-		$socialNetworks = array(
-			'github'=>'Github',
-			'gitlab'=>'GitLab',
-			'twitter'=>'Twitter',
-			'facebook'=>'Facebook',
-			'instagram'=>'Instagram',
-			'codepen'=>'Codepen',
-			'linkedin'=>'Linkedin',
-			'xing'=>'Xing',
-			'mastodon'=>'Mastodon',
-			'vk'=>'VK'
-		);
-
-		foreach ($socialNetworks as $key=>$label) {
-			if (!$site->{$key}()) {
-				unset($socialNetworks[$key]);
-			}
-		}
-		return $socialNetworks;
-	}
-
-	public static function title()
-	{
-		global $site;
-		return $site->title();
-	}
-
-	public static function description()
-	{
-		global $site;
-		return $site->description();
-	}
-
-	public static function slogan()
-	{
-		global $site;
-		return $site->slogan();
-	}
-
-	public static function footer()
-	{
-		global $site;
-		return $site->footer();
-	}
-
-	public static function lang()
-	{
-		global $language;
-		return $language->currentLanguageShortVersion();
-	}
-
-	public static function rssUrl()
-	{
-		if (pluginActivated('pluginRSS')) {
-			return DOMAIN_BASE.'rss.xml';
-		}
-		return false;
-	}
-
-	public static function sitemapUrl()
-	{
-		if (pluginActivated('pluginSitemap')) {
-			return DOMAIN_BASE.'sitemap.xml';
-		}
-		return false;
-	}
-
-	// Returns the absolute URL of the site
-	// Ex. https://example.com the method returns https://example.com/
-	// Ex. https://example.com/bludit/ the method returns https://example.com/bludit/
-	public static function siteUrl()
-	{
-		return DOMAIN_BASE;
-	}
-
-	// Returns the absolute URL of admin panel
-	// Ex. https://example.com/admin/ the method returns https://example.com/admin/
-	// Ex. https://example.com/bludit/admin/ the method returns https://example.com/bludit/admin/
-	public static function adminUrl()
-	{
-		return DOMAIN_ADMIN;
-	}
-
-	public static function metaTags($tag)
-	{
-		if ($tag=='title') {
-			return self::metaTagTitle();
-		} elseif ($tag=='description') {
-			return self::metaTagDescription();
-		}
-	}
-
-	public static function metaTagTitle()
-	{
-		global $url;
-		global $site;
-		global $tags;
-		global $categories;
-		global $WHERE_AM_I;
-		global $page;
-
-		if ($WHERE_AM_I=='page') {
-			$format = $site->titleFormatPages();
-			$format = Text::replace('{{page-title}}', $page->title(), $format);
-			$format = Text::replace('{{page-description}}', $page->description(), $format);
-		} elseif ($WHERE_AM_I=='tag') {
-			try {
-				$tagKey = $url->slug();
-				$tag = new Tag($tagKey);
-				$format = $site->titleFormatTag();
-				$format = Text::replace('{{tag-name}}', $tag->name(), $format);
-			} catch (Exception $e) {
-				// Tag doesn't exist
-			}
-
-		} elseif ($WHERE_AM_I=='category') {
-			try {
-				$categoryKey = $url->slug();
-				$category = new Category($categoryKey);
-				$format = $site->titleFormatCategory();
-				$format = Text::replace('{{category-name}}', $category->name(), $format);
-			} catch (Exception $e) {
-				// Category doesn't exist
-			}
-		} else {
-			$format = $site->titleFormatHomepage();
-		}
-
-		$format = Text::replace('{{site-title}}', $site->title(), $format);
-		$format = Text::replace('{{site-slogan}}', $site->slogan(), $format);
-		$format = Text::replace('{{site-description}}', $site->description(), $format);
-
-		return '<title>'.$format.'</title>'.PHP_EOL;
-	}
-
-	public static function metaTagDescription()
-	{
-		global $site;
-		global $WHERE_AM_I;
-		global $page;
-		global $url;
-
-		$description = $site->description();
-
-		if ($WHERE_AM_I=='page') {
-			$description = $page->description();
-		} elseif ($WHERE_AM_I=='category') {
-			try {
-				$categoryKey = $url->slug();
-				$category = new Category($categoryKey);
-				$description = $category->description();
-			} catch (Exception $e) {
-				// description from the site
-			}
-		}
-
-		return '<meta name="description" content="'.$description.'">'.PHP_EOL;
-	}
-
-	// DEPRECATED v3.0.0
-	// Return the metatag <title> with a predefine structure
-	public static function headTitle()
-	{
-		return self::metaTagTitle();
-	}
-
-	// DEPRECATED v3.0.0
-	// Return the metatag <decription> with a predefine structure
-	public static function headDescription()
-	{
-		return self::metaTagDescription();
-	}
-
-	public static function charset($charset)
-	{
-		return '<meta charset="'.$charset.'">'.PHP_EOL;
-	}
-
-	public static function viewport($content)
-	{
-		return '<meta name="viewport" content="'.$content.'">'.PHP_EOL;
-	}
-
-	public static function src($file, $base=DOMAIN_THEME)
-	{
-		return $base.$file;
-	}
-
-	public static function css($files, $base=DOMAIN_THEME)
-	{
-		if( !is_array($files) ) {
-			$files = array($files);
-		}
-
-		$links = '';
-		foreach($files as $file) {
-			$links .= '<link rel="stylesheet" type="text/css" href="'.$base.$file.'?version='.BLUDIT_VERSION.'">'.PHP_EOL;
-		}
-
-		return $links;
-	}
-
-	public static function javascript($files, $base=DOMAIN_THEME, $attributes='')
-	{
-		if( !is_array($files) ) {
-			$files = array($files);
-		}
-
-		$scripts = '';
-		foreach($files as $file) {
-			$scripts .= '<script '.$attributes.' src="'.$base.$file.'?version='.BLUDIT_VERSION.'"></script>'.PHP_EOL;
-		}
-
-		return $scripts;
-	}
-
-	public static function js($files, $base=DOMAIN_THEME, $attributes='')
-	{
-		return self::javascript($files, $base, $attributes);
-	}
-
-	public static function plugins($type, $args = array())
-	{
-		global $plugins;
-		foreach ($plugins[$type] as $plugin) {
-			echo call_user_func_array(array($plugin, $type), $args);
-		}
-	}
-
-	public static function favicon($file='favicon.png', $typeIcon='image/png')
-	{
-		return '<link rel="icon" href="'.DOMAIN_THEME.$file.'" type="'.$typeIcon.'">'.PHP_EOL;
-	}
-
-	public static function keywords($keywords)
-	{
-		if (is_array($keywords)) {
-			$keywords = implode(',', $keywords);
-		}
-		return '<meta name="keywords" content="'.$keywords.'">'.PHP_EOL;
-	}
-
-	public static function jquery()
-	{
-		return '<script src="'.DOMAIN_CORE_JS.'jquery.min.js?version='.BLUDIT_VERSION.'"></script>'.PHP_EOL;
-	}
-
-	public static function jsBootstrap($attributes='')
-	{
-		return '<script '.$attributes.' src="'.DOMAIN_CORE_JS.'bootstrap.bundle.min.js?version='.BLUDIT_VERSION.'"></script>'.PHP_EOL;
-	}
-
-	public static function cssBootstrap()
-	{
-		return '<link rel="stylesheet" type="text/css" href="'.DOMAIN_CORE_CSS.'bootstrap.min.css?version='.BLUDIT_VERSION.'">'.PHP_EOL;
-	}
-
-	public static function cssBootstrapIcons()
-	{
-		return '<link rel="stylesheet" type="text/css" href="'.DOMAIN_CORE_CSS.'/bootstrap-icons/bootstrap-icons.css?version='.BLUDIT_VERSION.'">'.PHP_EOL;
-	}
-
-	public static function jsSortable($attributes='')
-	{
-		// https://github.com/psfpro/bootstrap-html5sortable
-		return '<script '.$attributes.' src="'.DOMAIN_CORE_JS.'jquery.sortable.min.js?version='.BLUDIT_VERSION.'"></script>'.PHP_EOL;
-	}
-
-}
-
-?>
diff --git a/bl-kernel/js/bludit-ajax.php b/bl-kernel/js/bludit-ajax.php
deleted file mode 100644
index e15fef05..00000000
--- a/bl-kernel/js/bludit-ajax.php
+++ /dev/null
@@ -1,148 +0,0 @@
-/*
-	DEPRECATED CLASS
-	WILL BE REMOVED IN BLUDIT V4
-*/
-
-class bluditAjax {
-
-	constructor(apiURL, apiToken, apiAuth, tokenCSRF) {
-		this.apiURL = "http://localhost:9000/api/";
-		this.apiToken = '45643a4071fad6a12261bb0763550feb';
-		this.apiAuth = '18a8410f0043d004c2e87f404170e112';
-		this.tokenCSRF = tokenCSRF;
-	}
-
-	static async savePage(uuid, title, content) {
-		let url = this.apiURL+"pages";
-		try {
-			const response = await fetch(url, {
-				credentials: "same-origin",
-				method: "POST",
-				body: JSON.stringify({
-					tokenCSRF: this.tokenCSRF,
-					token: this.apiToken,
-					authentication: this.apiAuth,
-					uuid: uuid,
-					title: title,
-					content: content
-				}),
-				headers: new Headers({
-					"Content-Type": "application/json"
-				}),
-			});
-			const json = await response.json();
-			return json.data.key;
-		}
-		catch (err) {
-			console.log(err);
-			return true;
-		}
-	}
-
-	static async saveAsDraft(uuid, title, content) {
-		let url = HTML_PATH_ADMIN_ROOT+"ajax/save-as-draft"
-		try {
-			const response = await fetch(url, {
-				credentials: 'same-origin',
-				method: "POST",
-				headers: new Headers({
-					'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8'
-				}),
-				body: new URLSearchParams({
-					'tokenCSRF': tokenCSRF,
-					'uuid': "autosave-" + uuid,
-					'title': title,
-					'content': content,
-					'type': 'autosave'
-				}),
-			});
-			const json = await response.json();
-			return json;
-		}
-		catch (err) {
-			console.log(err);
-			return true;
-		}
-	}
-
-	static async removeLogo() {
-		let url = HTML_PATH_ADMIN_ROOT+"ajax/logo-remove"
-		try {
-			const response = await fetch(url, {
-				credentials: 'same-origin',
-				method: "POST",
-				headers: new Headers({
-					'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8'
-				}),
-				body: new URLSearchParams({
-					'tokenCSRF': tokenCSRF
-				}),
-			});
-			const json = await response.json();
-			return json;
-		}
-		catch (err) {
-			console.log(err);
-			return true;
-		}
-	}
-
-	// Alert the user when the user is not logged
-	userLogged(callBack) {
-		var ajaxRequest;
-		if (ajaxRequest) {
-			ajaxRequest.abort();
-		}
-
-		console.log("[INFO] [BLUDIT AJAX] [userLogged()] Checking if the user is logged.");
-
-		ajaxRequest = $.ajax({
-			type: "GET",
-			url: HTML_PATH_ADMIN_ROOT+"ajax/user-logged"
-		});
-
-		ajaxRequest.done(function (response, textStatus, jqXHR) {
-			console.log("[INFO] [BLUDIT AJAX] [userLogged()] The user is logged.");
-		});
-
-		ajaxRequest.fail(function (jqXHR, textStatus, errorThrown) {
-			// The fail is produced by admin.php when the user is not logged the ajax request is not possible and returns 401
-			console.log("[INFO] [BLUDIT AJAX] [userLogged()] The user is NOT logged.");
-			if (jqXHR.status==401) {
-				callBack("You are not logged in anymore, so Bludit can't save your settings and content.");
-			}
-		});
-	}
-
-	generateSlug(text, parentKey, currentKey, callBack) {
-		var ajaxRequest;
-		if (ajaxRequest) {
-			ajaxRequest.abort();
-		}
-
-		ajaxRequest = $.ajax({
-		    type: "POST",
-		    data: {
-			tokenCSRF: tokenCSRF,
-			text: text,
-			parentKey: parentKey,
-			currentKey: currentKey
-		    },
-		    url: HTML_PATH_ADMIN_ROOT+"ajax/generate-slug"
-		});
-
-		ajaxRequest.done(function (response, textStatus, jqXHR) {
-			console.log("Bludit AJAX: generateSlug(): done handler");
-			callBack.val(response["slug"]);
-		});
-
-		ajaxRequest.fail(function (jqXHR, textStatus, errorThrown) {
-			console.log("Bludit AJAX: generateSlug(): fail handler");
-		});
-
-		ajaxRequest.always(function () {
-			console.log("Bludit AJAX: generateSlug(): always handler");
-		});
-	    }
-
-}
diff --git a/bl-kernel/js/token-autocomplete.js b/bl-kernel/js/token-autocomplete.js
deleted file mode 100644
index 58c4f365..00000000
--- a/bl-kernel/js/token-autocomplete.js
+++ /dev/null
@@ -1,371 +0,0 @@
-var __assign = (this && this.__assign) || function () {
-    __assign = Object.assign || function(t) {
-        for (var s, i = 1, n = arguments.length; i < n; i++) {
-            s = arguments[i];
-            for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
-                t[p] = s[p];
-        }
-        return t;
-    };
-    return __assign.apply(this, arguments);
-};
-var TokenAutocomplete = /** @class */ (function () {
-    function TokenAutocomplete(options) {
-        this.KEY_BACKSPACE = 8;
-        this.KEY_ENTER = 13;
-        this.KEY_UP = 38;
-        this.KEY_DOWN = 40;
-        this.defaults = {
-            name: '',
-            selector: '',
-            noMatchesText: null,
-            initialTokens: null,
-            initialSuggestions: null,
-            suggestionsUri: '',
-            suggestionRenderer: TokenAutocomplete.Autocomplete.defaultRenderer,
-            minCharactersForSuggestion: 1
-        };
-        this.options = __assign(__assign({}, this.defaults), options);
-        var passedContainer = document.querySelector(this.options.selector);
-        if (!passedContainer) {
-            throw new Error('passed selector does not point to a DOM element.');
-        }
-        this.container = passedContainer;
-        this.container.classList.add('token-autocomplete-container');
-        if (!Array.isArray(this.options.initialTokens) && !Array.isArray(this.options.initialSuggestions)) {
-            this.parseTokensAndSuggestions();
-        }
-        this.hiddenSelect = document.createElement('select');
-        this.hiddenSelect.id = this.container.id + '-select';
-        this.hiddenSelect.name = this.options.name;
-        this.hiddenSelect.setAttribute('multiple', 'true');
-        this.hiddenSelect.style.display = 'none';
-        this.textInput = document.createElement('span');
-        this.textInput.id = this.container.id + '-input';
-        this.textInput.classList.add('token-autocomplete-input');
-        this.textInput.setAttribute('data-placeholder', 'enter some text');
-        this.textInput.contentEditable = 'true';
-        this.container.appendChild(this.textInput);
-        this.container.appendChild(this.hiddenSelect);
-        this.select = new TokenAutocomplete.MultiSelect(this);
-        this.autocomplete = new TokenAutocomplete.Autocomplete(this);
-        this.debug(false);
-        var me = this;
-        if (Array.isArray(this.options.initialTokens)) {
-            this.options.initialTokens.forEach(function (token) {
-                if (typeof token === 'object') {
-                    me.select.addToken(token.value, token.text);
-                }
-            });
-        }
-        this.textInput.addEventListener('keydown', function (event) {
-            if (event.which == me.KEY_ENTER || event.keyCode == me.KEY_ENTER) {
-                event.preventDefault();
-                var highlightedSuggestion = me.autocomplete.suggestions.querySelector('.token-autocomplete-suggestion-highlighted');
-                if (highlightedSuggestion !== null) {
-                    if (highlightedSuggestion.classList.contains('token-autocomplete-suggestion-active')) {
-                        me.select.removeTokenWithText(highlightedSuggestion.textContent);
-                    }
-                    else {
-                        me.select.addToken(highlightedSuggestion.getAttribute('data-value'), highlightedSuggestion.textContent);
-                    }
-                }
-                else {
-                    me.select.addToken(me.textInput.textContent, me.textInput.textContent);
-                }
-                me.clearCurrentInput();
-            }
-            else if (me.textInput.textContent === '' && (event.which == me.KEY_BACKSPACE || event.keyCode == me.KEY_BACKSPACE)) {
-                event.preventDefault();
-                me.select.removeLastToken();
-            }
-        });
-        this.textInput.addEventListener('keyup', function (event) {
-            var _a, _b;
-            if ((event.which == me.KEY_UP || event.keyCode == me.KEY_UP) && me.autocomplete.suggestions.childNodes.length > 0) {
-                var highlightedSuggestion = me.autocomplete.suggestions.querySelector('.token-autocomplete-suggestion-highlighted');
-                var aboveSuggestion = (_a = highlightedSuggestion) === null || _a === void 0 ? void 0 : _a.previousSibling;
-                if (aboveSuggestion != null) {
-                    me.autocomplete.highlightSuggestion(aboveSuggestion);
-                }
-                return;
-            }
-            if ((event.which == me.KEY_DOWN || event.keyCode == me.KEY_DOWN) && me.autocomplete.suggestions.childNodes.length > 0) {
-                var highlightedSuggestion = me.autocomplete.suggestions.querySelector('.token-autocomplete-suggestion-highlighted');
-                var belowSuggestion = (_b = highlightedSuggestion) === null || _b === void 0 ? void 0 : _b.nextSibling;
-                if (belowSuggestion != null) {
-                    me.autocomplete.highlightSuggestion(belowSuggestion);
-                }
-                return;
-            }
-            me.autocomplete.hideSuggestions();
-            me.autocomplete.clearSuggestions();
-            var value = me.textInput.textContent || '';
-            if (value.length >= me.options.minCharactersForSuggestion) {
-                if (Array.isArray(me.options.initialSuggestions)) {
-                    me.options.initialSuggestions.forEach(function (suggestion) {
-                        if (typeof suggestion !== 'object') {
-                            // the suggestion is of wrong type and therefore ignored
-                            return;
-                        }
-                        if (value.localeCompare(suggestion.text.slice(0, value.length), undefined, { sensitivity: 'base' }) === 0) {
-                            // The suggestion starts with the query text the user entered and will be displayed
-                            me.autocomplete.addSuggestion(suggestion);
-                        }
-                    });
-                    if (me.autocomplete.suggestions.childNodes.length > 0) {
-                        me.autocomplete.highlightSuggestionAtPosition(0);
-                    }
-                    else if (me.options.noMatchesText) {
-                        me.autocomplete.addSuggestion({ value: '_no_match_', text: me.options.noMatchesText, description: null });
-                    }
-                }
-                else if (me.options.suggestionsUri.length > 0) {
-                    me.autocomplete.requestSuggestions(value);
-                }
-            }
-        });
-        this.container.tokenAutocomplete = this;
-    }
-    /**
-     * Searches the element given as a container for option elements and creates active tokens (when the option is marked selected)
-     * and suggestions (all options found) from these. During this all found options are removed from the DOM.
-     */
-    TokenAutocomplete.prototype.parseTokensAndSuggestions = function () {
-        var initialTokens = [];
-        var initialSuggestions = [];
-        var options = this.container.querySelectorAll('option');
-        var me = this;
-        options.forEach(function (option) {
-            if (option.text != null) {
-                if (option.hasAttribute('selected')) {
-                    initialTokens.push({ value: option.value, text: option.text });
-                }
-                initialSuggestions.push({ value: option.value, text: option.text, description: null });
-            }
-            me.container.removeChild(option);
-        });
-        if (initialTokens.length > 0) {
-            this.options.initialTokens = initialTokens;
-        }
-        if (initialSuggestions.length > 0) {
-            this.options.initialSuggestions = initialSuggestions;
-        }
-    };
-    /**
-     * Clears the currently present tokens and creates new ones from the given input value.
-     *
-     * @param {(Array\|string)} value - either the name of a single token or a list of tokens to create
-     */
-    TokenAutocomplete.prototype.val = function (value) {
-        this.select.clear();
-        if (Array.isArray(value)) {
-            var me_1 = this;
-            value.forEach(function (token) {
-                if (typeof token === 'object') {
-                    me_1.select.addToken(token.value, token.text);
-                }
-            });
-        }
-        else {
-            this.select.addToken(value.value, value.text);
-        }
-    };
-    TokenAutocomplete.prototype.clearCurrentInput = function () {
-        this.textInput.textContent = '';
-    };
-    TokenAutocomplete.prototype.debug = function (state) {
-        if (state) {
-            this.log = console.log.bind(window.console);
-        }
-        else {
-            this.log = function () { };
-        }
-    };
-    var _a;
-    TokenAutocomplete.MultiSelect = /** @class */ (function () {
-        function class_1(parent) {
-            this.parent = parent;
-            this.container = parent.container;
-            this.options = parent.options;
-        }
-        /**
-         * Adds a token with the specified name to the list of currently prensent tokens displayed to the user and the hidden select.
-         *
-         * @param {string} tokenText - the name of the token to create
-         */
-        class_1.prototype.addToken = function (tokenValue, tokenText) {
-            if (tokenValue === null || tokenText === null) {
-                return;
-            }
-            var option = document.createElement('option');
-            option.text = tokenText;
-            option.value = tokenValue;
-            option.setAttribute('selected', 'true');
-            option.setAttribute('data-text', tokenText);
-            option.setAttribute('data-value', tokenValue);
-            this.parent.hiddenSelect.add(option);
-            var token = document.createElement('span');
-            token.classList.add('token-autocomplete-token');
-            token.setAttribute('data-text', tokenText);
-            option.setAttribute('data-value', tokenValue);
-            token.textContent = tokenText;
-            var deleteToken = document.createElement('span');
-            deleteToken.classList.add('token-autocomplete-token-delete');
-            deleteToken.textContent = '\u00D7';
-            token.appendChild(deleteToken);
-            var me = this;
-            deleteToken.addEventListener('click', function (event) {
-                me.removeToken(token);
-            });
-            this.container.insertBefore(token, this.parent.textInput.nextSibling);
-            this.parent.log('added token', token);
-        };
-        /**
-         * Completely clears the currently present tokens from the field.
-         */
-        class_1.prototype.clear = function () {
-            var tokens = this.container.querySelectorAll('.token-autocomplete-token');
-            var me = this;
-            tokens.forEach(function (token) { me.removeToken(token); });
-        };
-        /**
-         * Removes the last token in the list of currently present token. This is the last added token next to the input field.
-         */
-        class_1.prototype.removeLastToken = function () {
-            var tokens = this.container.querySelectorAll('.token-autocomplete-token');
-            var token = tokens[tokens.length - 1];
-            this.removeToken(token);
-        };
-        /**
-         * Removes the specified token from the list of currently present tokens.
-         *
-         * @param {Element} token - the token to remove
-         */
-        class_1.prototype.removeToken = function (token) {
-            var _a, _b;
-            this.container.removeChild(token);
-            var tokenText = token.getAttribute('data-text');
-            var hiddenOption = this.parent.hiddenSelect.querySelector('option[data-text="' + tokenText + '"]');
-            (_b = (_a = hiddenOption) === null || _a === void 0 ? void 0 : _a.parentElement) === null || _b === void 0 ? void 0 : _b.removeChild(hiddenOption);
-            this.parent.log('removed token', token.textContent);
-        };
-        class_1.prototype.removeTokenWithText = function (tokenText) {
-            if (tokenText === null) {
-                return;
-            }
-            var token = this.container.querySelector('.token-autocomplete-token[data-text="' + tokenText + '"]');
-            if (token !== null) {
-                this.removeToken(token);
-            }
-        };
-        return class_1;
-    }());
-    TokenAutocomplete.Autocomplete = (_a = /** @class */ (function () {
-            function class_2(parent) {
-                this.parent = parent;
-                this.container = parent.container;
-                this.options = parent.options;
-                this.renderer = parent.options.suggestionRenderer;
-                this.suggestions = document.createElement('ul');
-                this.suggestions.id = this.container.id + '-suggestions';
-                this.suggestions.classList.add('token-autocomplete-suggestions');
-                this.container.appendChild(this.suggestions);
-            }
-            /**
-             * Hides the suggestions dropdown from the user.
-             */
-            class_2.prototype.hideSuggestions = function () {
-                this.suggestions.style.display = '';
-            };
-            /**
-             * Shows the suggestions dropdown to the user.
-             */
-            class_2.prototype.showSuggestions = function () {
-                this.suggestions.style.display = 'block';
-            };
-            class_2.prototype.highlightSuggestionAtPosition = function (index) {
-                var suggestions = this.suggestions.querySelectorAll('li');
-                suggestions.forEach(function (suggestion) {
-                    suggestion.classList.remove('token-autocomplete-suggestion-highlighted');
-                });
-                suggestions[index].classList.add('token-autocomplete-suggestion-highlighted');
-            };
-            class_2.prototype.highlightSuggestion = function (suggestion) {
-                this.suggestions.querySelectorAll('li').forEach(function (suggestion) {
-                    suggestion.classList.remove('token-autocomplete-suggestion-highlighted');
-                });
-                suggestion.classList.add('token-autocomplete-suggestion-highlighted');
-            };
-            /**
-             * Removes all previous suggestions from the dropdown.
-             */
-            class_2.prototype.clearSuggestions = function () {
-                this.suggestions.innerHTML = '';
-            };
-            /**
-             * Loads suggestions matching the given query from the rest service behind the URI given as an option while initializing the field.
-             *
-             * @param query the query to search suggestions for
-             */
-            class_2.prototype.requestSuggestions = function (query) {
-                var me = this;
-                var request = new XMLHttpRequest();
-                request.onload = function () {
-                    if (Array.isArray(request.response)) {
-                        request.response.forEach(function (suggestion) {
-                            me.addSuggestion(suggestion);
-                        });
-                    }
-                };
-                request.open('GET', me.options.suggestionsUri + '?query=' + query, true);
-                request.responseType = 'json';
-                request.setRequestHeader('Content-type', 'application/json');
-                request.send();
-            };
-            /**
-             * Adds a suggestion with the given text matching the users input to the dropdown.
-             *
-             * @param {string} suggestionText - the text that should be displayed for the added suggestion
-             */
-            class_2.prototype.addSuggestion = function (suggestion) {
-                var element = this.renderer(suggestion);
-                element.setAttribute('data-value', suggestion.value);
-                var me = this;
-                element.addEventListener('click', function (_event) {
-                    if (suggestion.text == me.options.noMatchesText) {
-                        return true;
-                    }
-                    if (element.classList.contains('token-autocomplete-suggestion-active')) {
-                        me.parent.select.removeTokenWithText(suggestion.text);
-                    }
-                    else {
-                        me.parent.select.addToken(suggestion.value, suggestion.text);
-                    }
-                    me.clearSuggestions();
-                    me.hideSuggestions();
-                    me.parent.clearCurrentInput();
-                });
-                if (this.container.querySelector('.token-autocomplete-token[data-text="' + suggestion.text + '"]') !== null) {
-                    element.classList.add('token-autocomplete-suggestion-active');
-                }
-                this.suggestions.appendChild(element);
-                this.showSuggestions();
-                me.parent.log('added suggestion', suggestion);
-            };
-            return class_2;
-        }()),
-        _a.defaultRenderer = function (suggestion) {
-            var option = document.createElement('li');
-            option.textContent = suggestion.text;
-            if (suggestion.description) {
-                var description = document.createElement('small');
-                description.textContent = suggestion.description;
-                description.classList.add('token-autocomplete-suggestion-description');
-                option.appendChild(description);
-            }
-            return option;
-        },
-        _a);
-    return TokenAutocomplete;
-}());
diff --git a/bl-kernel/pagex.class.php b/bl-kernel/page.class.php
similarity index 100%
rename from bl-kernel/pagex.class.php
rename to bl-kernel/page.class.php
diff --git a/bl-kernel/js/bootbox.all.min.js b/bl-kernel/vendors/bootbox/bootbox.all.min.js
similarity index 100%
rename from bl-kernel/js/bootbox.all.min.js
rename to bl-kernel/vendors/bootbox/bootbox.all.min.js