浏览代码

bug fixes

Diego Najar 3 年之前
父节点
当前提交
cf4e0cc755

+ 4 - 1
.github/issue_template.md

@@ -7,10 +7,13 @@ Complete here.
 ### Bludit version
 Complete here.
 
+### Hosting or Webserver name
+Complete here.
+
 ### PHP version
 If you do not know delete this line.
 
 ### PHP logs
 If you do not know delete this line.
 
-The default settings of the PHP Error Log file varies from OS to OS. The location of the error log file itself can be set manually in the php.ini file. On a Windows server, in IIS, it may be something like `error_log = C:\log_files\php_errors.log` in Linux it may be a value of `/var/log/php_errors.log`.
+The default settings for the PHP error log file vary from operating system to system. The location of the error log file itself can be set manually in the php.ini file. On a Windows server, in IIS, it may be something like `error_log = C:\log_files\php_errors.log` in Linux it may be a value of `/var/log/php_errors.log`.

+ 1 - 1
.gitignore

@@ -30,4 +30,4 @@ bl-themes/small
 bl-themes/future-imperfect
 bl-themes/social-network
 Dockerfile
-conf/*
+conf/*

+ 1 - 1
README.md

@@ -39,5 +39,5 @@ PHPStan
 -------
 https://phpstan.org/
 ```
-docker run --rm -v $(pwd):/app ghcr.io/phpstan/phpstan:0.12.89 analyse -c /app/phpstan.neon /app
+docker run --rm -v $(pwd):/app ghcr.io/phpstan/phpstan:0.12.96 analyse -c /app/phpstan.neon /app
 ```

+ 2 - 1
bl-kernel/abstract/dbjson.class.php

@@ -6,6 +6,7 @@ class dbJSON {
 	public $dbBackup;
 	public $file;
 	public $firstLine;
+	protected $dbFields; // These fields are defined in the extended classes
 
 	// $file, the JSON file.
 	// $firstLine, TRUE if you want to remove the first line, FALSE otherwise
@@ -101,7 +102,7 @@ class dbJSON {
 	{
 		// NULL is returned if the json cannot be decoded
 		$decode = json_decode($data, true);
-		if ($decode===NULL) {
+		if ($decode===null) {
 			Log::set(__METHOD__.LOG_SEP.'Error trying to read the JSON file: '.$this->file, LOG_TYPE_ERROR);
 			return false;
 		}

+ 0 - 5
bl-kernel/admin/themes/booty/css/99-lightmode.css

@@ -30,11 +30,6 @@ a.nav-link:hover,
     border-color: #dee2e6;
 }
 
-.list-group-item {
-  background-color: inherit;
-  border: 0 none;
-}
-
 .list-group-item a {
   text-decoration: none;
 }

+ 2 - 3
bl-kernel/admin/themes/booty/index.php

@@ -14,6 +14,7 @@
 	<?php
 		echo HTML::cssBootstrap();
 		echo HTML::cssBootstrapIcons();
+		echo HTML::cssSelect2();
 		echo HTML::css(array(
 			'01-bludit.css',
 			'02-bootstrap-hacks.css'
@@ -22,8 +23,6 @@
 		echo HTML::css(array(
 			'jquery.datetimepicker.min.css',
 			'jquery-ui.min.css',
-			'select2.min.css',
-			'select2-bootstrap4.min.css',
 			'tagsinput-revisited.min.css'
 		), DOMAIN_CORE_CSS);
 
@@ -44,10 +43,10 @@
 	echo HTML::jsBootstrap();
 	echo HTML::jsSortable();
 	echo HTML::bootbox();
+	echo HTML::jsSelect2();
 	echo HTML::js(array(
 		'jquery.datetimepicker.full.min.js',
 		'jquery-ui.min.js',
-		'select2.full.min.js',
 		'tagsinput-revisited.min.js',
 		'functions.js',
 		'api.js'

+ 1 - 1
bl-kernel/admin/views/categories.php

@@ -49,7 +49,7 @@ foreach ($categories->keys() as $key) {
 	try {
 		$category = new Category($key);
 		echo '<tr>';
-		echo '<td class="pt-4 pb-4"><a href="'.HTML_PATH_ADMIN_ROOT.'edit-category/'.$key.'">'.$category->name().'</a></td>';
+		echo '<td class="pt-4 pb-4"><i class="bi bi-bookmark"></i><a href="'.HTML_PATH_ADMIN_ROOT.'edit-category/'.$key.'">'.$category->name().'</a></td>';
 		echo '<td class="pt-4 pb-4"><span>'.$category->description().'</span></td>';
 		echo '<td class="pt-4 pb-4"><a href="'.$category->permalink().'">'.$category->permalink().'</a></td>';
 		echo '</tr>';

+ 3 - 3
bl-kernel/admin/views/content.php

@@ -134,7 +134,7 @@ function table($type)
 
 					echo '<td class="pt-4 pb-4">
 					<div>
-						<span>' . ($page->title() ? $page->title() : '<span class="text-muted">' . $L->g('Empty title') . '</span> ') . '</span>
+						<i class="bi bi-file-text"></i><span>' . ($page->title() ? $page->title() : '<span class="text-muted">' . $L->g('Empty title') . '</span> ') . '</span>
 					</div>
 					<div class="mt-1">
 						<a class="me-2" target="_blank" href="' . $page->permalink() . '">' . $L->g('View') . '</a>
@@ -160,7 +160,7 @@ function table($type)
 
 						echo '<td class="ps-3 pt-4 pb-4">
 						<div>
-							<span>' . ($child->title() ? $child->title() : '<span class="text-muted">' . $L->g('Empty title') . '</span> ') . '</span>
+							<i class="bi bi-file-text"></i><span>' . ($child->title() ? $child->title() : '<span class="text-muted">' . $L->g('Empty title') . '</span> ') . '</span>
 						</div>
 						<div class="mt-1">
 							<a class="me-2" target="_blank" href="' . $child->permalink() . '">' . $L->g('View') . '</a>
@@ -188,7 +188,7 @@ function table($type)
 
 				echo '<td class="pt-4 pb-4">
 					<div>
-						' . ($page->title() ? $page->title() : '<span class="text-muted">' . $L->g('Empty title') . '</span> ') . '
+						<i class="bi bi-file-text"></i>' . ($page->title() ? $page->title() : '<span class="text-muted">' . $L->g('Empty title') . '</span> ') . '
 					</div>
 					<div class="mt-1">
 						<a class="me-2" target="_blank" href="' . $page->permalink() . '">' . $L->g('View') . '</a>

+ 2 - 0
bl-kernel/admin/views/dashboard.php

@@ -1,5 +1,7 @@
 <?php defined('BLUDIT') or die('Bludit CMS.'); ?>
 
+<?php createPage(array()); ?>
+
 <script>
 	// ============================================================================
 	// Variables for the view

+ 1 - 1
bl-kernel/admin/views/developers.php

@@ -28,7 +28,7 @@
 
 echo Bootstrap::pageTitle(array('title'=>$L->g('Developers'), 'icon'=>'gears'));
 
-echo '<h2 class="mb-4 mt-4"><b>PHP version: '.phpversion().'</b></h2>';
+echo '<h2 class="mt-4 mb-4"><b>PHP version: '.phpversion().'</b></h2>';
 
 // PHP Ini
 $uploadOptions = array(

+ 67 - 59
bl-kernel/admin/views/editor.php

@@ -85,26 +85,38 @@
 		return true;
 	}
 
-	// Open the modal and store the current value
-	// The current value is store to recover it if the user click on the button "Cancel"
+	/*
+		Open the modal and store the current value
+		The current value is store to recover it if the user click on the button "Cancel"
+	 */
 	function openModal(fieldName) {
 		var value = $('#' + fieldName).val();
 		localStorage.setItem(fieldName, value);
 		$('#modal-' + fieldName).modal('show');
 	}
 
-	// Close the modal when the user click in the button "Cancel"
-	// The function also recover the old value
-	function closeModal(fieldName) {
-		var value = localStorage.getItem(fieldName);
-		$('#' + fieldName).val(value);
+	/*
+		Close the modal
+		The function also recover the old value
+	*/
+	function closeModal(fieldName, revertValue=false) {
+		if (revertValue) {
+			var value = localStorage.getItem(fieldName);
+			$('#' + fieldName).val(value);
+		}
 		$('#modal-' + fieldName).modal('hide');
 	}
 
+	/*
+		Disable the "Save" button
+	*/
 	function disableBtnSave() {
 		$('#btnSave').addClass('btn-primary-disabled').attr('data-current', 'saved').html('<i class="bi bi-check-square"></i><?php $L->p('Saved') ?>');
 	}
 
+	/*
+		Enable the "Save" button
+	*/
 	function enableBtnSave() {
 		$('#btnSave').removeClass('btn-primary-disabled').attr('data-current', 'unsaved').html('<i class="bi bi-save"></i><?php $L->p('Save') ?>');
 	}
@@ -194,7 +206,7 @@
 		});
 
 		$('#btnCancelDescription').on('click', function() {
-			closeModal('description');
+			closeModal('description', true);
 		});
 
 		// Modal date events
@@ -209,7 +221,7 @@
 		});
 
 		$('#btnCancelDate').on('click', function() {
-			closeModal('date');
+			closeModal('date', true);
 		});
 
 		// Modal friendly-url events
@@ -224,7 +236,7 @@
 		});
 
 		$('#btnCancelFriendlyURL').on('click', function() {
-			closeModal('friendlyURL');
+			closeModal('friendlyURL', true);
 		});
 
 		$('#btnGenURLFromTitle').on('click', function() {
@@ -269,7 +281,7 @@
 		});
 
 		$('#btnCancelType').on('click', function() {
-			closeModal('type');
+			closeModal('type', true);
 		});
 
 		// Modal SEO events
@@ -284,7 +296,7 @@
 		});
 
 		$('#btnCancelSeo').on('click', function() {
-			closeModal('seo');
+			closeModal('seo', true);
 		});
 
 		// Modal parent events
@@ -299,7 +311,7 @@
 		});
 
 		$('#btnCancelParent').on('click', function() {
-			closeModal('parent');
+			closeModal('parent', true);
 		});
 
 	});
@@ -328,6 +340,37 @@
 			disableBtnSave();
 		}, 1000 * 60 * AUTOSAVE_INTERVAL);
 
+		$("#parent").select2({
+			placeholder: '',
+			allowClear: true,
+			theme: 'bootstrap-5',
+			minimumInputLength: 2,
+			dropdownParent: $('#modal-parent'),
+			ajax: {
+				url: HTML_PATH_ADMIN_ROOT + 'ajax/get-published',
+				data: function(params) {
+					var query = {
+						checkIsParent: true,
+						query: params.term
+					}
+					return query;
+				},
+				processResults: function(data) {
+					return data;
+				}
+			},
+			escapeMarkup: function(markup) {
+				return markup;
+			},
+			templateResult: function(data) {
+				var html = data.text;
+				if (data.type == 'static') {
+					html += '<span class="badge badge-pill badge-light">' + data.type + '</span>';
+				}
+				return html;
+			}
+		});
+
 	});
 </script>
 
@@ -487,11 +530,9 @@
 	<div class="modal-dialog">
 		<div class="modal-content">
 			<div class="modal-body">
-				<div class="m-0">
-					<label for="parent" class="fw-bold mb-2">Parent page</label>
-					<select id="parent" name="parent" class="custom-select"></select>
-					<div class="form-text"><?php echo $L->g('Start typing a page title to see a list of suggestions.') ?></div>
-				</div>
+			<div class="col-sm-10">
+				<select id="parent" name="parent" class="form-select" data-current-value="" data-save="true"></select>
+			</div>
 			</div>
 			<div class="modal-footer ps-2 pe-2 pt-1 pb-1">
 				<button id="btnCancelParent" type="button" class="btn btn-sm btn-secondary"><i class="bi bi-x"></i>Cancel</button>
@@ -500,44 +541,11 @@
 		</div>
 	</div>
 </div>
-<script>
-	$(document).ready(function() {
-		var parent = $("#parent").select2({
-			placeholder: "",
-			allowClear: true,
-			theme: "bootstrap4",
-			minimumInputLength: 2,
-			ajax: {
-				url: HTML_PATH_ADMIN_ROOT + "ajax/get-published",
-				data: function(params) {
-					var query = {
-						checkIsParent: true,
-						query: params.term
-					}
-					return query;
-				},
-				processResults: function(data) {
-					return data;
-				}
-			},
-			escapeMarkup: function(markup) {
-				return markup;
-			},
-			templateResult: function(data) {
-				var html = data.text;
-				if (data.type == "static") {
-					html += '<span class="badge badge-pill badge-light">' + data.type + '</span>';
-				}
-				return html;
-			}
-		});
-	});
-</script>
 <!-- End Modal Parent -->
 
 <div class="container-fluid h-100">
 	<div class="row h-100">
-		<div class="col-sm-9 d-flex flex-column h-100">
+		<div class="col-sm-9 d-flex flex-column" style="height: 85%">
 
 			<!-- Toolbar > Save, Preview, Type and Options -->
 			<div id="editorToolbar" class="d-flex align-items-center mb-2">
@@ -645,13 +653,13 @@
 
 			<h6 class="text-uppercase mt-4">More options</h6>
 			<ul class="list-group">
-				<li class="list-group-item p-0 pt-3"><a onclick="fmOpen()" href="#"><i class="bi bi-files"></i>Files & images</a></li>
-				<li class="list-group-item p-0 pt-3"><a onclick="openModal('description')" href="#"><i class="bi bi-info-square"></i>Description</a></li>
-				<li class="list-group-item p-0 pt-3"><a onclick="openModal('date')" href="#"><i class="bi bi-calendar"></i>Publish date</a></li>
-				<li class="list-group-item p-0 pt-3"><a onclick="openModal('friendlyURL')" href="#"><i class="bi bi-link"></i>Change URL</a></li>
-				<li class="list-group-item p-0 pt-3"><a onclick="openModal('type')" href="#"><i class="bi bi-eye"></i>Type</a></li>
-				<li class="list-group-item p-0 pt-3"><a onclick="openModal('seo')" href="#"><i class="bi bi-compass"></i>SEO features</a></li>
-				<li class="list-group-item p-0 pt-3"><a onclick="openModal('parent')" href="#"><i class="bi bi-diagram-2"></i>Parent page</a></li>
+				<li class="list-group-item p-0 pt-3 bg-transparent border-0"><a onclick="fmOpen()" href="#"><i class="bi bi-files"></i>Files & images</a></li>
+				<li class="list-group-item p-0 pt-3 bg-transparent border-0"><a onclick="openModal('description')" href="#"><i class="bi bi-info-square"></i>Description</a></li>
+				<li class="list-group-item p-0 pt-3 bg-transparent border-0"><a onclick="openModal('date')" href="#"><i class="bi bi-calendar"></i>Publish date</a></li>
+				<li class="list-group-item p-0 pt-3 bg-transparent border-0"><a onclick="openModal('friendlyURL')" href="#"><i class="bi bi-link"></i>Change URL</a></li>
+				<li class="list-group-item p-0 pt-3 bg-transparent border-0"><a onclick="openModal('type')" href="#"><i class="bi bi-eye"></i>Type</a></li>
+				<li class="list-group-item p-0 pt-3 bg-transparent border-0"><a onclick="openModal('seo')" href="#"><i class="bi bi-compass"></i>SEO features</a></li>
+				<li class="list-group-item p-0 pt-3 bg-transparent border-0"><a onclick="openModal('parent')" href="#"><i class="bi bi-diagram-2"></i>Parent page</a></li>
 
 			</ul>
 

+ 2 - 1
bl-kernel/admin/views/editor/file-manager.php

@@ -104,9 +104,10 @@
 		}
 
 		$.each(files, function(key, file) {
+			console.log(file);
 			var row = '<tr>' +
 				'<td class="align-middle">' +
-				'	<img style="width: 32px" src="<?php echo HTML_PATH_CORE_IMG ?>default.svg" />' +
+				'	<img style="width: 32px" src="'+ file.thumbnailSmall +'" />' +
 				'</td>' +
 				'<td class="align-middle">' + file.filename + '</td>' +
 				'<td class="align-middle">' + file.mime + '</td>' +

+ 92 - 11
bl-kernel/admin/views/settings.php

@@ -145,8 +145,45 @@
 	// Initlization for the view
 	// ============================================================================
 	$(document).ready(function() {
-		// nothing here yet
-		// how do you hang your toilet paper ? over or under ?
+		$("#homepage").select2({
+			placeholder: "Search for a page",
+			allowClear: true,
+			theme: "bootstrap-5",
+			minimumInputLength: 2,
+			ajax: {
+				url: HTML_PATH_ADMIN_ROOT+"ajax/get-published",
+				data: function (params) {
+					var query = { query: params.term }
+					return query;
+				},
+				processResults: function (data) {
+					return data;
+				},
+				escapeMarkup: function(markup) {
+					return markup;
+				}
+			}
+		});
+
+		$("#pageNotFound").select2({
+			placeholder: "Search for a page",
+			allowClear: true,
+			theme: "bootstrap-5",
+			minimumInputLength: 2,
+			ajax: {
+				url: HTML_PATH_ADMIN_ROOT+"ajax/get-published",
+				data: function (params) {
+					var query = { query: params.term }
+					return query;
+				},
+				processResults: function (data) {
+					return data;
+				},
+				escapeMarkup: function(markup) {
+					return markup;
+				}
+			}
+		});
 	});
 </script>
 
@@ -288,19 +325,37 @@
 
 		echo Bootstrap::formTitle(array('title' => $L->g('Predefined pages')));
 
+		try {
+			$options = array();
+			if (!empty($site->homepage())) {
+				$tmp = new Page($site->homepage());
+				$options = array($site->homepage()=>$tmp->title());
+			}
+		} catch (Exception $e) {
+			// continue
+		}
 		echo Bootstrap::formSelect(array(
 			'name' => 'homepage',
 			'label' => $L->g('Homepage'),
-			'options' => array(), // Complete via Ajax
+			'options' => $options, // Complete via Ajax
 			'selected' => false,
 			'tip' => $L->g('Returning page for the main page'),
 			'data' => array('save' => 'true')
 		));
 
+		try {
+			$options = array();
+			if (!empty($site->pageNotFound())) {
+				$tmp = new Page($site->pageNotFound());
+				$options = array($site->pageNotFound()=>$tmp->title());
+			}
+		} catch (Exception $e) {
+			// continue
+		}
 		echo Bootstrap::formSelect(array(
 			'name' => 'pageNotFound',
 			'label' => $L->g('Page not found'),
-			'options' => array(), // Complete via Ajax
+			'options' => $options, // Complete via Ajax
 			'selected' => false,
 			'tip' => $L->g('Returning page when the page doesnt exist'),
 			'data' => array('save' => 'true')
@@ -516,28 +571,54 @@
 	<!-- Images tab -->
 	<div class="tab-pane" id="images" role="tabpanel" aria-labelledby="images-tab">
 		<?php
-		echo Bootstrap::formTitle(array('title' => $L->g('Thumbnails')));
+		echo Bootstrap::formTitle(array('title' => $L->g('Thumbnail small')));
+
+		echo Bootstrap::formInputText(array(
+			'name' => 'thumbnailSmallWidth',
+			'label' => $L->g('Width'),
+			'value' => $site->thumbnailSmallWidth(),
+			'tip' => $L->g('Thumbnail width in pixels'),
+			'data' => array('save' => 'true')
+		));
+
+		echo Bootstrap::formInputText(array(
+			'name' => 'thumbnailSmallHeight',
+			'label' => $L->g('Height'),
+			'value' => $site->thumbnailSmallHeight(),
+			'tip' => $L->g('Thumbnail height in pixels'),
+			'data' => array('save' => 'true')
+		));
+
+		echo Bootstrap::formInputText(array(
+			'name' => 'thumbnailSmallQuality',
+			'label' => $L->g('Quality'),
+			'value' => $site->thumbnailSmallQuality(),
+			'tip' => $L->g('Thumbnail quality in percentage'),
+			'data' => array('save' => 'true')
+		));
+
+		echo Bootstrap::formTitle(array('title' => $L->g('Thumbnail medium')));
 
 		echo Bootstrap::formInputText(array(
-			'name' => 'thumbnailWidth',
+			'name' => 'thumbnailMediumWidth',
 			'label' => $L->g('Width'),
-			'value' => $site->thumbnailWidth(),
+			'value' => $site->thumbnailMediumWidth(),
 			'tip' => $L->g('Thumbnail width in pixels'),
 			'data' => array('save' => 'true')
 		));
 
 		echo Bootstrap::formInputText(array(
-			'name' => 'thumbnailHeight',
+			'name' => 'thumbnailMediumHeight',
 			'label' => $L->g('Height'),
-			'value' => $site->thumbnailHeight(),
+			'value' => $site->thumbnailMediumHeight(),
 			'tip' => $L->g('Thumbnail height in pixels'),
 			'data' => array('save' => 'true')
 		));
 
 		echo Bootstrap::formInputText(array(
-			'name' => 'thumbnailQuality',
+			'name' => 'thumbnailMediumQuality',
 			'label' => $L->g('Quality'),
-			'value' => $site->thumbnailQuality(),
+			'value' => $site->thumbnailMediumQuality(),
 			'tip' => $L->g('Thumbnail quality in percentage'),
 			'data' => array('save' => 'true')
 		));

+ 1 - 1
bl-kernel/admin/views/users.php

@@ -52,7 +52,7 @@ foreach ($list as $username) {
 	try {
 		$user = new User($username);
 		echo '<tr>';
-		echo '<td class="pt-4 pb-4"><a href="'.HTML_PATH_ADMIN_ROOT.'edit-user/'.$username.'">'.$username.'</a></td>';
+		echo '<td class="pt-4 pb-4"><i class="bi bi-person"></i><a href="'.HTML_PATH_ADMIN_ROOT.'edit-user/'.$username.'">'.$username.'</a></td>';
 		echo '<td class="pt-4 pb-4 d-none d-lg-table-cell">'.$user->nickname().'</td>';
 		echo '<td class="pt-4 pb-4">'.$user->email().'</td>';
 		echo '<td class="pt-4 pb-4">'.($user->enabled()?'<b>'.$L->g('Enabled').'</b>':'<b class="text-danger">'.$L->g('Disabled').'</b>').'</td>';

+ 20 - 22
bl-kernel/ajax/get-published.php

@@ -20,33 +20,31 @@ $query = isset($_GET['query']) ? Text::lowercase($_GET['query']) : false;
 $checkIsParent = empty($_GET['checkIsParent']) ? false : true;
 // ----------------------------------------------------------------------------
 if ($query===false) {
-	ajaxResponse(1, 'Invalid query.');
+    ajaxResponse(1, 'Invalid query.');
 }
 
 $result = array();
 $pagesKey = $pages->getDB();
 foreach ($pagesKey as $pageKey) {
-	try {
-		$page = new Page($pageKey);
-		if ($page->isParent() || !$checkIsParent) {
-			// Check page status
-			if ($page->published() || $page->sticky() || $page->isStatic()) {
-				// Check if the query contains in the title
-				$lowerTitle = Text::lowercase($page->title());
-				if (Text::stringContains($lowerTitle, $query)) {
-					$tmp = array('disabled'=>false);
-					$tmp['id'] = $page->key();
-					$tmp['text'] = $page->title();
-					$tmp['type'] = $page->type();
-					array_push($result, $tmp);
-				}
-			}
-		}
-	} catch (Exception $e) {
-		// continue
-	}
+    try {
+        $page = new Page($pageKey);
+        if ($page->isParent() || !$checkIsParent) {
+            // Check page status
+            if ($page->published() || $page->sticky() || $page->isStatic()) {
+                // Check if the query contains in the title
+                $lowerTitle = Text::lowercase($page->title());
+                if (Text::stringContains($lowerTitle, $query)) {
+                    $tmp = array('disabled'=>false);
+                    $tmp['id'] = $page->key();
+                    $tmp['text'] = $page->title();
+                    $tmp['type'] = $page->type();
+                    array_push($result, $tmp);
+                }
+            }
+        }
+    } catch (Exception $e) {
+        // continue
+    }
 }
 
 exit (json_encode(array('results'=>$result)));
-
-?>

+ 24 - 27
bl-kernel/boot/init.php

@@ -12,12 +12,12 @@ define('DEBUG_MODE', TRUE);
 define('DEBUG_TYPE', 'INFO'); // INFO, TRACE
 error_reporting(0); // Turn off all error reporting
 if (DEBUG_MODE) {
-	// Turn on all error reporting
-	ini_set("display_errors", 0);
-	ini_set('display_startup_errors',0);
-	ini_set("html_errors", 1);
-	ini_set('log_errors', 1);
-	error_reporting(E_ALL | E_STRICT | E_NOTICE);
+    // Turn on all error reporting
+    ini_set("display_errors", 0);
+    ini_set('display_startup_errors',0);
+    ini_set("html_errors", 1);
+    ini_set('log_errors', 1);
+    error_reporting(E_ALL | E_STRICT | E_NOTICE);
 }
 
 // PHP paths
@@ -44,7 +44,6 @@ define('PATH_WORKSPACES',		PATH_CONTENT.'workspaces'.DS);
 
 define('PATH_UPLOADS_PAGES',	PATH_UPLOADS.'pages'.DS);
 define('PATH_UPLOADS_PROFILES',	PATH_UPLOADS.'profiles'.DS);
-define('PATH_UPLOADS_THUMBNAILS',PATH_UPLOADS.'thumbnails'.DS);
 
 define('PATH_ADMIN',			PATH_KERNEL.'admin'.DS);
 define('PATH_ADMIN_THEMES',		PATH_ADMIN.'themes'.DS);
@@ -116,7 +115,7 @@ include(PATH_HELPERS.'bootstrap.class.php');
 include(PATH_HELPERS.'html.class.php');
 
 if (file_exists(PATH_KERNEL.'bludit.pro.php')) {
-	include(PATH_KERNEL.'bludit.pro.php');
+    include(PATH_KERNEL.'bludit.pro.php');
 }
 
 // Objects
@@ -138,26 +137,26 @@ $syslog 	= new Syslog();
 $base = '';
 
 if (!empty($_SERVER['DOCUMENT_ROOT']) && !empty($_SERVER['SCRIPT_NAME']) && empty($base)) {
-	$base = str_replace($_SERVER['DOCUMENT_ROOT'], '', $_SERVER['SCRIPT_NAME']);
-	$base = dirname($base);
+    $base = str_replace($_SERVER['DOCUMENT_ROOT'], '', $_SERVER['SCRIPT_NAME']);
+    $base = dirname($base);
 } elseif (empty($base)) {
-	$base = empty( $_SERVER['SCRIPT_NAME'] ) ? $_SERVER['PHP_SELF'] : $_SERVER['SCRIPT_NAME'];
-	$base = dirname($base);
+    $base = empty( $_SERVER['SCRIPT_NAME'] ) ? $_SERVER['PHP_SELF'] : $_SERVER['SCRIPT_NAME'];
+    $base = dirname($base);
 }
 
 if (strpos($_SERVER['REQUEST_URI'], $base)!==0) {
-	$base = '/';
+    $base = '/';
 } elseif ($base!=DS) {
-	$base = trim($base, '/');
-	$base = '/'.$base.'/';
+    $base = trim($base, '/');
+    $base = '/'.$base.'/';
 } else {
-	// Workaround for Windows Web Servers
-	$base = '/';
+    // Workaround for Windows Web Servers
+    $base = '/';
 }
 
-define('HTML_PATH_ROOT', 		$base);
-define('HTML_PATH_THEMES',		HTML_PATH_ROOT.'bl-themes/');
-define('HTML_PATH_THEME',		HTML_PATH_THEMES.$site->theme().'/');
+define('HTML_PATH_ROOT', 		    $base);
+define('HTML_PATH_THEMES',		    HTML_PATH_ROOT.'bl-themes/');
+define('HTML_PATH_THEME',		    HTML_PATH_THEMES.$site->theme().'/');
 define('HTML_PATH_THEME_CSS',		HTML_PATH_THEME.'css/');
 define('HTML_PATH_THEME_JS',		HTML_PATH_THEME.'js/');
 define('HTML_PATH_THEME_IMG',		HTML_PATH_THEME.'img/');
@@ -165,16 +164,15 @@ define('HTML_PATH_ADMIN_ROOT',		HTML_PATH_ROOT.ADMIN_URI_FILTER.'/');
 define('HTML_PATH_ADMIN_THEME',		HTML_PATH_ROOT.'bl-kernel/admin/themes/'.$site->adminTheme().'/');
 define('HTML_PATH_ADMIN_THEME_JS',	HTML_PATH_ADMIN_THEME.'js/');
 define('HTML_PATH_ADMIN_THEME_CSS',	HTML_PATH_ADMIN_THEME.'css/');
-define('HTML_PATH_CORE_JS',		HTML_PATH_ROOT.'bl-kernel/js/');
+define('HTML_PATH_CORE_JS',		    HTML_PATH_ROOT.'bl-kernel/js/');
 define('HTML_PATH_CORE_VENDORS',	HTML_PATH_ROOT.'bl-kernel/vendors/');
 define('HTML_PATH_CORE_CSS',		HTML_PATH_ROOT.'bl-kernel/css/');
 define('HTML_PATH_CORE_IMG',		HTML_PATH_ROOT.'bl-kernel/img/');
-define('HTML_PATH_CONTENT',		HTML_PATH_ROOT.'bl-content/');
-define('HTML_PATH_UPLOADS',		HTML_PATH_ROOT.'bl-content/uploads/');
+define('HTML_PATH_CONTENT',		    HTML_PATH_ROOT.'bl-content/');
+define('HTML_PATH_UPLOADS',		    HTML_PATH_ROOT.'bl-content/uploads/');
 define('HTML_PATH_UPLOADS_PAGES',	HTML_PATH_UPLOADS.'pages/');
-define('HTML_PATH_UPLOADS_PROFILES',	HTML_PATH_UPLOADS.'profiles/');
-define('HTML_PATH_UPLOADS_THUMBNAILS',	HTML_PATH_UPLOADS.'thumbnails/');
-define('HTML_PATH_PLUGINS',		HTML_PATH_ROOT.'bl-plugins/');
+define('HTML_PATH_UPLOADS_PROFILES',HTML_PATH_UPLOADS.'profiles/');
+define('HTML_PATH_PLUGINS',		    HTML_PATH_ROOT.'bl-plugins/');
 
 // --- Objects with dependency ---
 $language = new Language( $site->language() );
@@ -236,7 +234,6 @@ define('DOMAIN_ADMIN_THEME_JS',		DOMAIN.HTML_PATH_ADMIN_THEME_JS);
 define('DOMAIN_UPLOADS',		DOMAIN.HTML_PATH_UPLOADS);
 define('DOMAIN_UPLOADS_PAGES',		DOMAIN.HTML_PATH_UPLOADS_PAGES);
 define('DOMAIN_UPLOADS_PROFILES',	DOMAIN.HTML_PATH_UPLOADS_PROFILES);
-define('DOMAIN_UPLOADS_THUMBNAILS',	DOMAIN.HTML_PATH_UPLOADS_THUMBNAILS);
 define('DOMAIN_PLUGINS',		DOMAIN.HTML_PATH_PLUGINS);
 define('DOMAIN_CONTENT',		DOMAIN.HTML_PATH_CONTENT);
 

+ 51 - 47
bl-kernel/boot/rules/60.plugins.php

@@ -5,42 +5,42 @@
 // ============================================================================
 
 $plugins = array(
-	'siteHead'=>array(),
-	'siteBodyBegin'=>array(),
-	'siteBodyEnd'=>array(),
-	'siteSidebar'=>array(),
-	'beforeSiteLoad'=>array(),
-	'afterSiteLoad'=>array(),
-
-	'pageBegin'=>array(),
-	'pageEnd'=>array(),
-
-	'beforeAdminLoad'=>array(),
-	'afterAdminLoad'=>array(),
-	'adminHead'=>array(),
-	'adminBodyBegin'=>array(),
-	'adminBodyEnd'=>array(),
-	'adminSidebar'=>array(),
-	'adminContentSidebar'=>array(),
-	'dashboard'=>array(),
-
-	'beforeAll'=>array(),
-	'afterAll'=>array(),
-
-	'paginator'=>array(),
-
-	'beforePageModify'=>array(),
-	'beforePageDelete'=>array(),
-
-	'afterPageCreate'=>array(),
-	'afterPageModify'=>array(),
-	'afterPageDelete'=>array(),
-
-	'loginHead'=>array(),
-	'loginBodyBegin'=>array(),
-	'loginBodyEnd'=>array(),
-
-	'all'=>array() // $plugins['all'] keep installed and not installed plugins
+	'siteHead' => array(),
+	'siteBodyBegin' => array(),
+	'siteBodyEnd' => array(),
+	'siteSidebar' => array(),
+	'beforeSiteLoad' => array(),
+	'afterSiteLoad' => array(),
+
+	'pageBegin' => array(),
+	'pageEnd' => array(),
+
+	'beforeAdminLoad' => array(),
+	'afterAdminLoad' => array(),
+	'adminHead' => array(),
+	'adminBodyBegin' => array(),
+	'adminBodyEnd' => array(),
+	'adminSidebar' => array(),
+	'adminContentSidebar' => array(),
+	'dashboard' => array(),
+
+	'beforeAll' => array(),
+	'afterAll' => array(),
+
+	'paginator' => array(),
+
+	'beforePageModify' => array(),
+	'beforePageDelete' => array(),
+
+	'afterPageCreate' => array(),
+	'afterPageModify' => array(),
+	'afterPageDelete' => array(),
+
+	'loginHead' => array(),
+	'loginBodyBegin' => array(),
+	'loginBodyEnd' => array(),
+
+	'all' => array() // $plugins['all'] keep installed and not installed plugins
 );
 
 // This array has only the installed plugins
@@ -69,8 +69,8 @@ function buildPlugins()
 	// Load plugins clasess
 	$list = Filesystem::listDirectories(PATH_PLUGINS);
 	foreach ($list as $pluginPath) {
-		if (file_exists($pluginPath.DS.'plugin.php')) {
-			include_once($pluginPath.DS.'plugin.php');
+		if (file_exists($pluginPath . DS . 'plugin.php')) {
+			include_once($pluginPath . DS . 'plugin.php');
 		}
 	}
 
@@ -81,17 +81,17 @@ function buildPlugins()
 		$Plugin = new $pluginClass;
 
 		// Check if the plugin is translated
-		$languageFilename = PATH_PLUGINS.$Plugin->directoryName().DS.'languages'.DS.$site->language().'.json';
+		$languageFilename = PATH_PLUGINS . $Plugin->directoryName() . DS . 'languages' . DS . $site->language() . '.json';
 		if (!Sanitize::pathFile($languageFilename)) {
-			$languageFilename = PATH_PLUGINS.$Plugin->directoryName().DS.'languages'.DS.DEFAULT_LANGUAGE_FILE;
+			$languageFilename = PATH_PLUGINS . $Plugin->directoryName() . DS . 'languages' . DS . DEFAULT_LANGUAGE_FILE;
 		}
 
 		$database = file_get_contents($languageFilename);
 		$database = json_decode($database, true);
 
 		// Set name and description from the language file
-		$Plugin->setMetadata('name',$database['plugin-data']['name']);
-		$Plugin->setMetadata('description',$database['plugin-data']['description']);
+		$Plugin->setMetadata('name', $database['plugin-data']['name']);
+		$Plugin->setMetadata('description', $database['plugin-data']['description']);
 
 		// Remove name and description from the language and includes new words to the global language dictionary
 		unset($database['plugin-data']);
@@ -117,7 +117,7 @@ function buildPlugins()
 			}
 
 			// Insert the plugin into the hooks
-			foreach ($pluginsHooks as $hook=>$value) {
+			foreach ($pluginsHooks as $hook => $value) {
 				if (method_exists($Plugin, $hook)) {
 					array_push($plugins[$hook], $Plugin);
 				}
@@ -125,14 +125,18 @@ function buildPlugins()
 		}
 
 		// Sort the plugins by the position for the site sidebar
-		uasort($plugins['siteSidebar'], function ($a, $b) {
-				return $a->position()>$b->position();
+		uasort(
+			$plugins['siteSidebar'],
+			function ($a, $b) {
+				return $a->position() <=> $b->position();
 			}
 		);
 
 		// Sort the plugins by the position for the dashboard
-		uasort($plugins['dashboard'], function ($a, $b) {
-				return $a->position()>$b->position();
+		uasort(
+			$plugins['dashboard'],
+			function ($a, $b) {
+				return $a->position() <=> $b->position();
 			}
 		);
 	}

文件差异内容过多而无法显示
+ 696 - 689
bl-kernel/functions.php


+ 310 - 309
bl-kernel/helpers/filesystem.class.php

@@ -2,320 +2,321 @@
 
 class Filesystem {
 
-	// Returns an array with the absolutes directories.
-	public static function listDirectories($path, $regex='*', $sortByDate=false)
-	{
-		$directories = glob($path.$regex, GLOB_ONLYDIR);
-
-		if(empty($directories)) {
-			return array();
-		}
-
-		if($sortByDate) {
-			usort($directories,
-			      function($a, $b) {
-				      return filemtime($b) - filemtime($a);
-			      }
-			);
-		}
-
-		return $directories;
-	}
-
-	// Returns an array with the list of files with the absolute path
-	// $sortByDate = TRUE, the first file is the newer file
-	// $chunk = amount of chunks, FALSE if you don't want to chunk
-	public static function listFiles($path, $regex='*', $extension='*', $sortByDate=false, $chunk=false)
-	{
-		Log::set('list files = '.$path.$regex.'.'.$extension, LOG_TYPE_INFO);
-		$files = glob($path.$regex.'.'.$extension);
-
-		if (empty($files)) {
-			return array();
-		}
-
-		if ($sortByDate) {
-			usort($files,
-				function($a, $b) {
-					return filemtime($b) - filemtime($a);
-				}
-			);
-		}
-
-		// Split the list of files into chunks
-		// http://php.net/manual/en/function.array-chunk.php
-		if ($chunk) {
-			return array_chunk($files, $chunk);
-		}
-
-		return $files;
-	}
-
-	public static function mkdir($pathname, $recursive=false)
-	{
-		return mkdir($pathname, DIR_PERMISSIONS, $recursive);
-	}
-
-	public static function rmdir($pathname)
-	{
-		Log::set('rmdir = '.$pathname, LOG_TYPE_INFO);
-		return rmdir($pathname);
-	}
-
-	public static function mv($oldname, $newname)
-	{
-		Log::set('mv '.$oldname.' '.$newname, LOG_TYPE_INFO);
-		return rename($oldname, $newname);
-	}
-
-	public static function rmfile($filename)
-	{
-		Log::set('rmfile = '.$filename, LOG_TYPE_INFO);
-		return unlink($filename);
-	}
-
-	public static function fileExists($filename)
-	{
-		return file_exists($filename);
-	}
-
-	public static function directoryExists($path)
-	{
-		return file_exists($path);
-	}
-
-	// Copy recursive a directory to another
-	// If the destination directory not exists is created
-	// $source = /home/diego/example or /home/diego/example/
-	// $destination = /home/diego/newplace or /home/diego/newplace/
-	public static function copyRecursive($source, $destination, $skipDirectory=false)
-	{
-		$source 	= rtrim($source, DS);
-		$destination 	= rtrim($destination, DS);
-
-		// Check $source directory if exists
-		if (!self::directoryExists($source)) {
-			return false;
-		}
-
-		// Check $destionation directory if exists
-		if (!self::directoryExists($destination)) {
-			// Create the $destination directory
-			if (!mkdir($destination, DIR_PERMISSIONS, true)) {
-				return false;
-			}
-		}
-
-		foreach ($iterator = new RecursiveIteratorIterator(
-				new RecursiveDirectoryIterator($source, RecursiveDirectoryIterator::SKIP_DOTS),
-				RecursiveIteratorIterator::SELF_FIRST) as $item) {
-
-			$currentDirectory = dirname($item->getPathName());
-			if ($skipDirectory !== $currentDirectory) {
-				if ($item->isDir()) {
-					@mkdir($destination.DS.$iterator->getSubPathName());
-				} else {
-					copy($item, $destination.DS.$iterator->getSubPathName());
-				}
-			}
-		}
-
-		return true;
-	}
-
-	// Delete a file or directory recursive
-	// The directory is delete
-	public static function deleteRecursive($source, $deleteDirectory=true)
-	{
-		Log::set('deleteRecursive = '.$source, LOG_TYPE_INFO);
-
-		if (!self::directoryExists($source)) {
-			return false;
-		}
-
-		foreach (new RecursiveIteratorIterator(
-			new RecursiveDirectoryIterator($source, FilesystemIterator::SKIP_DOTS),
-			RecursiveIteratorIterator::CHILD_FIRST) as $item) {
-				if ($item->isFile() || $item->isLink()) {
-					unlink($item);
-				} else {
-					rmdir($item);
-				}
-		}
-
-		if ($deleteDirectory) {
-			return rmdir($source);
-		}
-		return true;
-	}
-
-	// Compress a file or directory
-	// $source = /home/diego/example
-	// $destionation = /tmp/example.zip
-	public static function zip($source, $destination)
-	{
-		if (!extension_loaded('zip')) {
-			return false;
-		}
-
-		if (!file_exists($source)) {
-			return false;
-		}
-
-		$zip = new ZipArchive();
-		if (!$zip->open($destination, ZIPARCHIVE::CREATE)) {
-			return false;
-		}
-
-		if (is_dir($source) === true) {
-			$iterator = new RecursiveDirectoryIterator($source);
-			$iterator->setFlags(RecursiveDirectoryIterator::SKIP_DOTS);
-			$files = new RecursiveIteratorIterator($iterator, RecursiveIteratorIterator::SELF_FIRST);
-
-			foreach ($files as $file) {
-				$file = realpath($file);
-				if (is_dir($file)) {
-					$zip->addEmptyDir(ltrim(str_replace($source, '', $file), "/\\"));
-				} elseif (is_file($file)) {
-					$zip->addFromString(ltrim(str_replace($source, '', $file), "/\\"), file_get_contents($file));
-				}
-			}
-		} elseif (is_file($source)) {
-			$zip->addFromString(basename($source), file_get_contents($source));
-		}
-
-		return $zip->close();
-	}
-
-	// Uncompress a zip file
-	// $source = /home/diego/example.zip
-	// $destionation = /home/diego/content
-	public static function unzip($source, $destination)
-	{
-		if (!extension_loaded('zip')) {
-			return false;
-		}
-
-		if (!file_exists($source)) {
-			return false;
-		}
-
-		$zip = new ZipArchive();
-		if (!$zip->open($source)) {
-			return false;
-		}
-
-		$zip->extractTo($destination);
-		return $zip->close();
-	}
-
-	/*
-	 | Returns the next filename if the filename already exist otherwise returns the original filename
+    // Returns an array with the absolutes directories.
+    public static function listDirectories($path, $regex='*', $sortByDate=false)
+    {
+        $directories = glob($path.$regex, GLOB_ONLYDIR);
+
+        if(empty($directories)) {
+            return array();
+        }
+
+        if($sortByDate) {
+            usort($directories,
+                  function($a, $b) {
+                      return filemtime($b) - filemtime($a);
+                  }
+            );
+        }
+
+        return $directories;
+    }
+
+    // Returns an array with the list of files with the absolute path
+    // $sortByDate = TRUE, the first file is the newer file
+    // $chunk = amount of chunks, FALSE if you don't want to chunk
+    public static function listFiles($path, $regex='*', $extension='*', $sortByDate=false, $chunk=false)
+    {
+        Log::set('list files = '.$path.$regex.'.'.$extension, LOG_TYPE_INFO);
+        $files = glob($path.$regex.'.'.$extension);
+
+        if (empty($files)) {
+            return array();
+        }
+
+        if ($sortByDate) {
+            usort($files,
+                function($a, $b) {
+                    return filemtime($b) - filemtime($a);
+                }
+            );
+        }
+
+        // Split the list of files into chunks
+        // http://php.net/manual/en/function.array-chunk.php
+        if ($chunk) {
+            return array_chunk($files, $chunk);
+        }
+
+        return $files;
+    }
+
+    public static function mkdir($pathname, $recursive=false)
+    {
+        return mkdir($pathname, DIR_PERMISSIONS, $recursive);
+    }
+
+    public static function rmdir($pathname)
+    {
+        Log::set('rmdir = '.$pathname, LOG_TYPE_INFO);
+        return rmdir($pathname);
+    }
+
+    public static function mv($oldname, $newname)
+    {
+        Log::set('mv '.$oldname.' '.$newname, LOG_TYPE_INFO);
+        return rename($oldname, $newname);
+    }
+
+    public static function rmfile($filename)
+    {
+        Log::set('rmfile = '.$filename, LOG_TYPE_INFO);
+        return unlink($filename);
+    }
+
+    public static function fileExists($filename)
+    {
+        return file_exists($filename);
+    }
+
+    public static function directoryExists($path)
+    {
+        return file_exists($path);
+    }
+
+    // Copy recursive a directory to another
+    // If the destination directory not exists is created
+    // $source = /home/diego/example or /home/diego/example/
+    // $destination = /home/diego/newplace or /home/diego/newplace/
+    public static function copyRecursive($source, $destination, $skipDirectory=false)
+    {
+        $source 	= rtrim($source, DS);
+        $destination 	= rtrim($destination, DS);
+
+        // Check $source directory if exists
+        if (!self::directoryExists($source)) {
+            return false;
+        }
+
+        // Check $destionation directory if exists
+        if (!self::directoryExists($destination)) {
+            // Create the $destination directory
+            if (!mkdir($destination, DIR_PERMISSIONS, true)) {
+                return false;
+            }
+        }
+
+        foreach ($iterator = new RecursiveIteratorIterator(
+                new RecursiveDirectoryIterator($source, RecursiveDirectoryIterator::SKIP_DOTS),
+                RecursiveIteratorIterator::SELF_FIRST) as $item) {
+
+            $currentDirectory = dirname($item->getPathName());
+            if ($skipDirectory !== $currentDirectory) {
+                if ($item->isDir()) {
+                    @mkdir($destination.DS.$iterator->getSubPathName());
+                } else {
+                    copy($item, $destination.DS.$iterator->getSubPathName());
+                }
+            }
+        }
+
+        return true;
+    }
+
+    // Delete a file or directory recursive
+    // The directory is delete
+    public static function deleteRecursive($source, $deleteDirectory=true)
+    {
+        Log::set('deleteRecursive = '.$source, LOG_TYPE_INFO);
+
+        if (!self::directoryExists($source)) {
+            return false;
+        }
+
+        foreach (new RecursiveIteratorIterator(
+            new RecursiveDirectoryIterator($source, FilesystemIterator::SKIP_DOTS),
+            RecursiveIteratorIterator::CHILD_FIRST) as $item) {
+                if ($item->isFile() || $item->isLink()) {
+                    unlink($item);
+                } else {
+                    rmdir($item);
+                }
+        }
+
+        if ($deleteDirectory) {
+            return rmdir($source);
+        }
+        return true;
+    }
+
+    /**
+     * Compress a file or directory.
+     * $source = /home/diego/example
+     * $destionation = /tmp/example.zip
+     */
+    public static function zip(string $source, string $destination): bool
+    {
+        if (!extension_loaded('zip')) {
+            return false;
+        }
+
+        if (!file_exists($source)) {
+            return false;
+        }
+
+        $zip = new ZipArchive();
+        if (!$zip->open($destination, ZIPARCHIVE::CREATE)) {
+            return false;
+        }
+
+        if (is_dir($source) === true) {
+            $iterator = new RecursiveDirectoryIterator($source);
+            $iterator->setFlags(RecursiveDirectoryIterator::SKIP_DOTS);
+            $files = new RecursiveIteratorIterator($iterator, RecursiveIteratorIterator::SELF_FIRST);
+
+            foreach ($files as $file) {
+                $file = realpath($file);
+                if (is_dir($file)) {
+                    $zip->addEmptyDir(ltrim(str_replace($source, '', $file), "/\\"));
+                } elseif (is_file($file)) {
+                    $zip->addFromString(ltrim(str_replace($source, '', $file), "/\\"), file_get_contents($file));
+                }
+            }
+        } elseif (is_file($source)) {
+            $zip->addFromString(basename($source), file_get_contents($source));
+        }
+
+        return $zip->close();
+    }
+
+    // Uncompress a zip file
+    // $source = /home/diego/example.zip
+    // $destionation = /home/diego/content
+    public static function unzip($source, $destination)
+    {
+        if (!extension_loaded('zip')) {
+            return false;
+        }
+
+        if (!file_exists($source)) {
+            return false;
+        }
+
+        $zip = new ZipArchive();
+        if (!$zip->open($source)) {
+            return false;
+        }
+
+        $zip->extractTo($destination);
+        return $zip->close();
+    }
+
+    /*
+     | Returns the next filename if the filename already exist otherwise returns the original filename
          |
          | @path	string	Path
          | @filename	string	Filename
          |
          | @return	string
          */
-	public static function nextFilename($path=PATH_UPLOADS, $filename) {
-		// Clean filename and get extension
-		$fileExtension 	= pathinfo($filename, PATHINFO_EXTENSION);
-		$fileExtension 	= Text::lowercase($fileExtension);
-		$filename 	= pathinfo($filename, PATHINFO_FILENAME);
-		$filename 	= Text::removeSpaces($filename);
-		$filename 	= Text::removeQuotes($filename);
-
-		// Search for the next filename
-		$tmpName = $filename.'.'.$fileExtension;
-		if (Sanitize::pathFile($path.$tmpName)) {
-			$number = 0;
-			$tmpName = $filename.'_'.$number.'.'.$fileExtension;
-			while (Sanitize::pathFile($path.$tmpName)) {
-				$number = $number + 1;
-				$tmpName = $filename.'_'.$number.'.'.$fileExtension;
-			}
-		}
-		return $tmpName;
-	}
-
-	/*
-	 | Returns the filename
-	 | Example:
-	 |	@file	/home/diego/dog.jpg
-	 |	@return dog.jpg
-         |
-         | @file	string	Full path of the file
-         |
-         | @return	string
-         */
-	public static function filename($file) {
-		return basename($file);
-	}
-
-	/*
-	 | Returns the file extension
-	 | Example:
-	 |	@file	/home/diego/dog.jpg
-	 |	@return jpg
-         |
-         | @file	string	Full path of the file
-         |
-         | @return	string
-         */
-	public static function extension($file) {
-		return pathinfo($file, PATHINFO_EXTENSION);
-	}
-
-	/**
-	 * Get Size of file or directory in bytes
-	 * @param  [string] $fileOrDirectory
-	 * @return [int|bool]                  [bytes or false on error]
-	 */
-	public static function getSize($fileOrDirectory) {
-		// Files
-		if (is_file($fileOrDirectory)) {
-			return filesize($fileOrDirectory);
-		}
-		// Directories
-		if (file_exists($fileOrDirectory)) {
-		    $size = 0;
-		    foreach(new RecursiveIteratorIterator(new RecursiveDirectoryIterator($fileOrDirectory, FilesystemIterator::SKIP_DOTS)) as $file){
-				try {
-					$size += $file->getSize();
-				} catch (Exception $e) {
-					// SplFileInfo::getSize RuntimeException will be thrown on broken symlinks/errors
-				}
-		    }
-		    return $size;
-		}
-		return false;
-	}
-
-	public static function bytesToHumanFileSize($bytes, $decimals = 2) {
-	    $size = array('B','kB','MB','GB','TB','PB','EB','ZB','YB');
-	    $factor = floor((strlen($bytes) - 1) / 3);
-	    return sprintf("%.{$decimals}f ", $bytes / pow(1024, $factor)) . @$size[$factor];
-	}
-
-	/*	Returns the mime type of the file === Bludit v4
-
-		@file		string			Full path of the file. Example: /home/diego/dog.jpg
-
-		@return		string|bool		Mime type or FALSE if not possible to get the mime type. Example: image/jpeg
-	*/
-	public static function mimeType($file) {
-		if (function_exists('mime_content_type')) {
-			return mime_content_type($file);
-		}
-
-		if (function_exists('finfo_file')) {
-			$fileinfo = finfo_open(FILEINFO_MIME_TYPE);
-			$mimeType = finfo_file($fileinfo, $file);
-			finfo_close($fileinfo);
-			return $mimeType;
-		}
-
-		return false;
-	}
+    public static function nextFilename($filename, $path=PATH_UPLOADS) {
+        // Clean filename and get extension
+        $fileExtension 	= pathinfo($filename, PATHINFO_EXTENSION);
+        $fileExtension 	= Text::lowercase($fileExtension);
+        $filename 	= pathinfo($filename, PATHINFO_FILENAME);
+        $filename 	= Text::removeSpaces($filename);
+        $filename 	= Text::removeQuotes($filename);
+
+        // Search for the next filename
+        $tmpName = $filename.'.'.$fileExtension;
+        if (Sanitize::pathFile($path.$tmpName)) {
+            $number = 0;
+            $tmpName = $filename.'_'.$number.'.'.$fileExtension;
+            while (Sanitize::pathFile($path.$tmpName)) {
+                $number = $number + 1;
+                $tmpName = $filename.'_'.$number.'.'.$fileExtension;
+            }
+        }
+        return $tmpName;
+    }
+
+    /**
+     * Returns the file filename witout the extension. === Bludit v4
+     * @param   string      $file       Filename with the path
+     * @return  string                  Extension. Example: /home/diego/dog.jpg -> dog.jpg
+     */
+    public static function basename(string $file): string {
+        return pathinfo($file, PATHINFO_BASENAME);
+    }
+
+    /**
+     * Returns the file filename witout the extension. === Bludit v4
+     * @param   string      $file       Filename with the path
+     * @return  string                  Extension. Example: /home/diego/dog.jpg -> dog
+     */
+    public static function filename(string $file): string {
+        return pathinfo($file, PATHINFO_FILENAME);
+    }
+
+    /**
+     * Returns the file extension. === Bludit v4
+     * @param   string      $file       Filename with the path
+     * @return  string                  Extension. Example: /home/diego/dog.jpg -> jpg
+     */
+    public static function extension(string $file): string {
+        return pathinfo($file, PATHINFO_EXTENSION);
+    }
+
+    /**
+     * Get size of file or directory in bytes
+     * @param  string		$fileOrDirectory
+     * @return int|bool		Bytes or false on error
+     */
+    public static function getSize($fileOrDirectory) {
+        // Files
+        if (is_file($fileOrDirectory)) {
+            return filesize($fileOrDirectory);
+        }
+        // Directories
+        if (file_exists($fileOrDirectory)) {
+            $size = 0;
+            foreach(new RecursiveIteratorIterator(new RecursiveDirectoryIterator($fileOrDirectory, FilesystemIterator::SKIP_DOTS)) as $file){
+                try {
+                    $size += $file->getSize();
+                } catch (Exception $e) {
+                    // SplFileInfo::getSize RuntimeException will be thrown on broken symlinks/errors
+                }
+            }
+            return $size;
+        }
+        return false;
+    }
+
+    public static function bytesToHumanFileSize($bytes, $decimals = 2) {
+        $size = array('B','kB','MB','GB','TB','PB','EB','ZB','YB');
+        $factor = floor((strlen($bytes) - 1) / 3);
+        return sprintf("%.{$decimals}f ", $bytes / pow(1024, $factor)) . @$size[$factor];
+    }
+
+    /*	Returns the mime type of the file === Bludit v4
+
+        @file		string			Full path of the file. Example: /home/diego/dog.jpg
+
+        @return		string|bool		Mime type or FALSE if not possible to get the mime type. Example: image/jpeg
+    */
+    public static function mimeType($file) {
+        if (function_exists('mime_content_type')) {
+            return mime_content_type($file);
+        }
+
+        if (function_exists('finfo_file')) {
+            $fileinfo = finfo_open(FILEINFO_MIME_TYPE);
+            $mimeType = finfo_file($fileinfo, $file);
+            finfo_close($fileinfo);
+            return $mimeType;
+        }
+
+        return false;
+    }
 
 }

+ 12 - 0
bl-kernel/helpers/html.class.php

@@ -82,6 +82,18 @@ class HTML {
 		return '<script '.$attributes.' src="'.DOMAIN_CORE_VENDORS.'bootstrap-html5sortable/jquery.sortable.min.js?version='.BLUDIT_VERSION.'"></script>'.PHP_EOL;
 	}
 
+	public static function jsSelect2($attributes='')
+	{
+		return '<script '.$attributes.' src="'.DOMAIN_CORE_VENDORS.'select2/select2.min.js?version='.BLUDIT_VERSION.'"></script>'.PHP_EOL;
+	}
+
+	public static function cssSelect2()
+	{
+		$html = '<link rel="stylesheet" type="text/css" href="'.DOMAIN_CORE_VENDORS.'select2/select2.min.css?version='.BLUDIT_VERSION.'">'.PHP_EOL;
+		$html .= '<link rel="stylesheet" type="text/css" href="'.DOMAIN_CORE_VENDORS.'select2/select2-bootstrap-5-theme.min.css?version='.BLUDIT_VERSION.'">'.PHP_EOL;
+		return $html;
+	}
+
 	/*	Generates a dynamiclly the meta tag title for the themes === Bludit v4
 
 		@return				string		Returns the meta tag title <title>...</title>

+ 4 - 1
bl-kernel/helpers/text.class.php

@@ -218,7 +218,10 @@ class Text {
 		return mb_stripos($string, $substring, 0, CHARSET);
 	}
 
-	public static function stringContains($string, $substring, $caseSensitive=true)
+    /**
+     * Return TRUE if the string contains the substring, FALSE otherwhise. === Bludit v4
+     */
+	public static function stringContains(string $string, string $substring, bool $caseSensitive=true): bool
 	{
 		return (self::stringPosition($string, $substring, $caseSensitive) !== false);
 	}

文件差异内容过多而无法显示
+ 0 - 1
bl-kernel/js/select2.full.min.js


+ 0 - 1
bl-kernel/js/variables.php

@@ -10,7 +10,6 @@ echo 'var HTML_PATH_ADMIN_ROOT = "'.HTML_PATH_ADMIN_ROOT.'";'.PHP_EOL;
 echo 'var HTML_PATH_ADMIN_THEME = "'.HTML_PATH_ADMIN_THEME.'";'.PHP_EOL;
 echo 'var HTML_PATH_CORE_IMG = "'.HTML_PATH_CORE_IMG.'";'.PHP_EOL;
 echo 'var HTML_PATH_UPLOADS = "'.HTML_PATH_UPLOADS.'";'.PHP_EOL;
-echo 'var HTML_PATH_UPLOADS_THUMBNAILS = "'.HTML_PATH_UPLOADS_THUMBNAILS.'";'.PHP_EOL;
 echo 'var BLUDIT_VERSION = "'.BLUDIT_VERSION.'";'.PHP_EOL;
 echo 'var BLUDIT_BUILD = "'.BLUDIT_BUILD.'";'.PHP_EOL;
 echo 'var DOMAIN = "'.DOMAIN.'";'.PHP_EOL;

+ 0 - 20
bl-kernel/page.class.php

@@ -9,7 +9,6 @@ class Page {
 		global $pages;
 
 		$this->vars['key'] = $key;
-
 		// If key is FALSE, the page is create with default values, like an empty page
 		// Useful for Page Not Found
 		if ($key===false) {
@@ -332,25 +331,6 @@ class Page {
 		return $filename;
 	}
 
-	// Returns the endpoint of the thumbnail cover image, FALSE if the page doesn't have a cover image
-	public function thumbCoverImage()
-	{
-		$filename = $this->coverImage(false);
-		if ($filename==false) {
-			return false;
-		}
-
-		// Check is external cover image
-		if (filter_var($filename, FILTER_VALIDATE_URL)) {
-			return $filename;
-		}
-
-		if (IMAGE_RESTRICT) {
-			return DOMAIN_UPLOADS_PAGES.$this->uuid().'/thumbnails/'.$filename;
-		}
-		return DOMAIN_UPLOADS_THUMBNAILS.$filename;
-	}
-
 	// Returns TRUE if the content has the text splited
 	public function readMore()
 	{

+ 824 - 839
bl-kernel/pages.class.php

@@ -2,845 +2,830 @@
 
 class Pages extends dbJSON {
 
-	protected $parentKeyList = array();
-	protected $dbFields = array(
-		'title'=>'',
-		'description'=>'',
-		'username'=>'',	// Username key
-		'tags'=>array(),
-		'type'=>'published', // published, static, draft, sticky, scheduled
-		'date'=>'',
-		'dateModified'=>'',
-		'position'=>0,
-		'coverImage'=>'',
-		'category'=>'',	// Category key
-		'md5file'=>'',
-		'uuid'=>'',
-		'allowComments'=>true,
-		'template'=>'',
-		'noindex'=>false,
-		'nofollow'=>false,
-		'noarchive'=>false,
-		'custom'=>array()
-	);
-
-	function __construct()
-	{
-		parent::__construct(DB_PAGES);
-	}
-
-	public function getDefaultFields()
-	{
-		return $this->dbFields;
-	}
-
-	/*	Get the database row associated to a page
-
-		@key			string				The key of the page to be fetch
-		@return		array/boolean		Return an array with the database for a page, FALSE otherwise
-	*/
-	public function getPageDB($key)
-	{
-		if ($this->exists($key)) {
-			return $this->db[$key];
-		}
-
-		return false;
-	}
-
-	/*	Check if a page key exists in the database
-
-		@return		boolean			Return TRUE if the page exists, FALSE otherwise
-	*/
-	public function exists($key)
-	{
-		return isset ($this->db[$key]);
-	}
-
-	/*	Create a new page === Bludit v4
-
-		@args			array			The array $args supports all the keys from the variable $dbFields. If you don't pass all the keys, the default values are used.
-		@return		string/boolean	Returns the page key if the page is successfully created, FALSE otherwise
-	*/
-	public function add($args)
-	{
-		$row = array();
-
-		// Predefined values
-		foreach ($this->dbFields as $field=>$value) {
-			if ($field=='tags') {
-				$tags = '';
-				if (isset($args['tags'])) {
-					$tags = $args['tags'];
-				}
-				$finalValue = $this->generateTags($tags);
-			} elseif ($field=='custom') {
-				if (isset($args['custom'])) {
-					global $site;
-					$customFields = $site->customFields();
-					foreach ($args['custom'] as $customField=>$customValue) {
-						$html = Sanitize::html($customValue);
-						// Store the custom field as defined type
-						settype($html, $customFields[$customField]['type']);
-						$row['custom'][$customField]['value'] = $html;
-					}
-					unset($args['custom']);
-					continue;
-				}
-			} elseif (isset($args[$field])) {
-				// Sanitize if will be stored on database
-				$finalValue = Sanitize::html($args[$field]);
-			} else {
-				// Default value for the field if not defined
-				$finalValue = $value;
-			}
-			// Store the value as defined type
-			settype($finalValue, gettype($value));
-			$row[$field] = $finalValue;
-		}
-
-		// Content
-		// This variable is not belong to the database so is not defined in $row
-		$contentRaw = (empty($args['content'])?'':$args['content']);
-
-		// Parent
-		// This variable is not belong to the database so is not defined in $row
-		$parent = '';
-		if (!empty($args['parent'])) {
-			$parent = $args['parent'];
-			$row['type'] = $this->db[$parent]['type']; // get the parent type
-		}
-
-		// Slug from the title or the content
-		// This variable is not belong to the database so is not defined in $row
-		if (empty($args['slug'])) {
-			if (!empty($row['title'])) {
-				$slug = $this->generateSlug($row['title']);
-			} else {
-				$slug = $this->generateSlug($contentRaw);
-			}
-		} else {
-			$slug = $args['slug'];
-		}
-
-		// Generate key
-		// This variable is not belong to the database so is not defined in $row
-		$key = $this->generateKey($slug, $parent);
-
-		// Generate UUID
-		if (empty($row['uuid'])) {
-			$row['uuid'] = $this->generateUUID();
-		}
-
-		// Validate date
-		if (!Valid::date($row['date'], DB_DATE_FORMAT)) {
-			$row['date'] = Date::current(DB_DATE_FORMAT);
-		}
-
-		// Schedule page
-		if (($row['date']>Date::current(DB_DATE_FORMAT)) && ($row['type']=='published')) {
-			$row['type'] = 'scheduled';
-		}
-
-		// Create the directory
-		if (Filesystem::mkdir(PATH_PAGES.$key, true) === false) {
-			Log::set(__METHOD__.LOG_SEP.'An error occurred while trying to create the directory: '.PATH_PAGES.$key, LOG_TYPE_ERROR);
-			return false;
-		}
-
-		// Create the upload directory for the page
-		if (Filesystem::mkdir(PATH_UPLOADS_PAGES.$key, true) === false) {
-			Log::set(__METHOD__.LOG_SEP.'An error occurred while trying to create the directory: '.PATH_UPLOADS_PAGES.$key, LOG_TYPE_ERROR);
-			return false;
-		}
-
-		// Create the thumbnails directory for the page
-		if (Filesystem::mkdir(PATH_UPLOADS_THUMBNAILS.$key, true) === false) {
-			Log::set(__METHOD__.LOG_SEP.'An error occurred while trying to create the directory: '.PATH_UPLOADS_THUMBNAILS.$key, LOG_TYPE_ERROR);
-			return false;
-		}
-
-		// Create the index.txt and save the file
-		if (file_put_contents(PATH_PAGES.$key.DS.FILENAME, $contentRaw) === false) {
-			Log::set(__METHOD__.LOG_SEP.'An error occurred while trying to create the file: '.FILENAME, LOG_TYPE_ERROR);
-			return false;
-		}
-
-		// Checksum MD5
-		$row['md5file'] = md5_file(PATH_PAGES.$key.DS.FILENAME);
-
-		// Insert into database
-		$this->db[$key] = $row;
-
-		// Sort database
-		$this->sortBy();
-
-		// Save database
-		$this->save();
-
-		return $key;
-	}
-
-	/*	Edit a page === Bludit v4
-
-		@args			array			The array $args supports all the keys from the variable $dbFields. If you don't pass all the keys, the default values are used.
-		@return		string/boolean	Returns the page key if the page is successfully edited, FALSE otherwise
-	*/
-	public function edit($args)
-	{
-		// This is the new row for the table and is going to replace the old row
-		$row = array();
-
-		// Current key
-		// This variable is not belong to the database so is not defined in $row
-		$key = $args['key'];
-
-		// Check values from the arguments ($args)
-		// If some field is missing the current value is taken
-		foreach ($this->dbFields as $field=>$value) {
-			if ( ($field=='tags') && isset($args['tags'])) {
-				$finalValue = $this->generateTags($args['tags']);
-			} elseif ($field=='custom') {
-				if (isset($args['custom'])) {
-					global $site;
-					$customFields = $site->customFields();
-					foreach ($args['custom'] as $customField=>$customValue) {
-						$html = Sanitize::html($customValue);
-						// Store the custom field as defined type
-						settype($html, $customFields[$customField]['type']);
-						$row['custom'][$customField]['value'] = $html;
-					}
-					unset($args['custom']);
-					continue;
-				}
-			} elseif (isset($args[$field])) {
-				// Sanitize if will be stored on database
-				$finalValue = Sanitize::html($args[$field]);
-			} else {
-				// Default value from the current row
-				$finalValue = $this->db[$key][$field];
-			}
-			settype($finalValue, gettype($value));
-			$row[$field] = $finalValue;
-		}
-
-		// Parent
-		// This variable is not belong to the database so is not defined in $row
-		$parent = '';
-		if (!empty($args['parent'])) {
-			$parent = $args['parent'];
-			$row['type'] = $this->db[$parent]['type']; // get the parent type
-		}
-
-		// Slug
-		// If the user change the slug the page key changes
-		// If the user send an empty slug the page key doesn't change
-		// This variable is not belong to the database so is not defined in $row
-		if (empty($args['slug'])) {
-			$explode = explode('/', $key);
-			$slug = end($explode);
-		} else {
-			$slug = $args['slug'];
-		}
-
-		// New key
-		// The key of the page can change if the user change the slug or the parent, -
-		// - if the user doesn't change the slug or the parent the key is going to be the same -
-		// - as the current key.
-		// This variable is not belong to the database so is not defined in $row
-		$newKey = $this->generateKey($slug, $parent, false, $key);
-
-		// if the date in the arguments is not valid, take the value from the old row
-		if (!Valid::date($row['date'], DB_DATE_FORMAT)) {
-			$row['date'] = $this->db[$key]['date'];
-		}
-
-		// Modified date
-		$row['dateModified'] = Date::current(DB_DATE_FORMAT);
-
-		// Schedule page
-		if (($row['date']>Date::current(DB_DATE_FORMAT)) && ($row['type']=='published')) {
-			$row['type'] = 'scheduled';
-		}
-
-		// Move the directory from old key to new key only if the keys are different
-		if ($newKey!==$key) {
-			if (Filesystem::mv(PATH_PAGES.$key, PATH_PAGES.$newKey) === false) {
-				Log::set(__METHOD__.LOG_SEP.'An error occurred while trying to move the directory: '.PATH_PAGES.$newKey, LOG_TYPE_ERROR);
-				return false;
-			}
-
-			if (Filesystem::mv(PATH_UPLOADS_PAGES.$key, PATH_UPLOADS_PAGES.$newKey) === false) {
-				Log::set(__METHOD__.LOG_SEP.'An error occurred while trying to move the directory: '.PATH_UPLOADS_PAGES.$newKey, LOG_TYPE_ERROR);
-				return false;
-			}
-
-			if (Filesystem::mv(PATH_UPLOADS_THUMBNAILS.$key, PATH_UPLOADS_THUMBNAILS.$newKey) === false) {
-				Log::set(__METHOD__.LOG_SEP.'An error occurred while trying to move the directory: '.PATH_UPLOADS_THUMBNAILS.$newKey, LOG_TYPE_ERROR);
-				return false;
-			}
-		}
-
-		// If the content was passed via arguments replace the content
-		if (isset($args['content'])) {
-			// Make the index.txt and save the file.
-			if (file_put_contents(PATH_PAGES.$newKey.DS.FILENAME, $args['content'])===false) {
-				Log::set(__METHOD__.LOG_SEP.'An error occurred while trying to save the new content in the file: '.PATH_PAGES.$newKey.DS.FILENAME, LOG_TYPE_ERROR);
-				return false;
-			}
-		}
-
-		// Remove the old row
-		unset($this->db[$key]);
-
-		// Reindex Orphan Children
-		$this->reindexChildren($key, $newKey);
-
-		// Checksum MD5
-		$row['md5file'] = md5_file(PATH_PAGES.$newKey.DS.FILENAME);
-
-		// Insert in database the new row
-		$this->db[$newKey] = $row;
-
-		// Sort database
-		$this->sortBy();
-
-		// Save database
-		$this->save();
-
-		return $newKey;
-	}
-
-	/*	Delete a page === Bludit v4
-
-		@key			string			The key of the page to be deleted
-		@return		boolean			Returns TRUE if the page was deleted successfully, FALSE otherwise
-	*/
-	public function delete($key)
-	{
-		// This is need it, because if the key is empty the Filesystem::deleteRecursive is going to delete PATH_PAGES
-		if (empty($key)) {
-			Log::set(__METHOD__.LOG_SEP.'Empty page key.', LOG_TYPE_ERROR);
-			return false;
-		}
-
-		// Page doesn't exist in database
-		if (!$this->exists($key)) {
-			Log::set(__METHOD__.LOG_SEP.'The page doesn\'t exist. Key: '.$key, LOG_TYPE_ERROR);
-			return false;
-		}
-
-		// Delete directory and files
-		if (Filesystem::deleteRecursive(PATH_PAGES.$key) === false) {
-			Log::set(__METHOD__.LOG_SEP.'An error occurred while trying to delete the directory: '.PATH_PAGES.$key, LOG_TYPE_ERROR);
-		}
-
-		// Delete upload directory
-		if (Filesystem::deleteRecursive(PATH_UPLOADS_PAGES.$key) === false) {
-			Log::set(__METHOD__.LOG_SEP.'An error occurred while trying to delete the directory: '.PATH_UPLOADS_PAGES.$key, LOG_TYPE_ERROR);
-		}
-
-		// Delete thumbnail directory
-		if (Filesystem::deleteRecursive(PATH_UPLOADS_THUMBNAILS.$key) === false) {
-			Log::set(__METHOD__.LOG_SEP.'An error occurred while trying to delete the directory: '.PATH_UPLOADS_THUMBNAILS.$key, LOG_TYPE_ERROR);
-		}
-
-		// Remove from database
-		unset($this->db[$key]);
-
-		// Save the database
-		$this->save();
-
-		return true;
-	}
-
-	// Delete all pages from a user
-	public function deletePagesByUser($args)
-	{
-		$username = $args['username'];
-
-		foreach ($this->db as $key=>$fields) {
-			if ($fields['username']===$username) {
-				$this->delete($key);
-			}
-		}
-
-		return true;
-	}
-
-	// Link all pages to a new user
-	public function transferPages($args)
-	{
-		$oldUsername = $args['oldUsername'];
-		$newUsername = isset($args['newUsername']) ? $args['newUsername'] : 'admin';
-
-		foreach ($this->db as $key=>$fields) {
-			if ($fields['username']===$oldUsername) {
-				$this->db[$key]['username'] = $newUsername;
-			}
-		}
-
-		return $this->save();
-	}
-
-	// This function reindex the orphan children with the new parent key
-	// If a page has subpages and the page change his key is necesarry check the children key
-	public function reindexChildren($oldParentKey, $newParentKey) {
-		if ($oldParentKey==$newParentKey){
-			return false;
-		}
-		$tmp = $this->db;
-		foreach ($tmp as $key=>$fields) {
-			if (Text::startsWith($key, $oldParentKey.'/')) {
-				$newKey = Text::replace($oldParentKey.'/', $newParentKey.'/', $key);
-				$this->db[$newKey] = $this->db[$key];
-				unset($this->db[$key]);
-			}
-		}
-	}
-
-	// Set field = value
-	public function setField($key, $field, $value)
-	{
-		if ($this->exists($key)) {
-			settype($value, gettype($this->dbFields[$field]));
-			$this->db[$key][$field] = $value;
-			return $this->save();
-		}
-		return false;
-	}
-
-	// Returns a database with all pages
-	// $onlyKeys = true; Returns only the pages keys
-	// $onlyKeys = false; Returns part of the database, I do not recommend use this
-	public function getDB($onlyKeys=true)
-	{
-		$tmp = $this->db;
-		if ($onlyKeys) {
-			return array_keys($tmp);
-		}
-		return $tmp;
-	}
-
-	// Returns a database with published pages
-	// $onlyKeys = true; Returns only the pages keys
-	// $onlyKeys = false; Returns part of the database, I do not recommend use this
-	public function getPublishedDB($onlyKeys=true)
-	{
-		$tmp = $this->db;
-		foreach ($tmp as $key=>$fields) {
-			if ($fields['type']!='published') {
-				unset($tmp[$key]);
-			}
-		}
-		if ($onlyKeys) {
-			return array_keys($tmp);
-		}
-		return $tmp;
-	}
-
-	// Returns an array with a list of keys/database of static pages
-	// By default the static pages are sort by position
-	public function getStaticDB($onlyKeys=true)
-	{
-		$tmp = $this->db;
-		foreach ($tmp as $key=>$fields) {
-			if ($fields['type']!='static') {
-				unset($tmp[$key]);
-			}
-		}
-		uasort($tmp, array($this, 'sortByPositionLowToHigh'));
-		if ($onlyKeys) {
-			return array_keys($tmp);
-		}
-		return $tmp;
-	}
-
-	// Returns an array with a list of keys/database of draft pages
-	public function getDraftDB($onlyKeys=true)
-	{
-		$tmp = $this->db;
-		foreach ($tmp as $key=>$fields) {
-			if($fields['type']!='draft') {
-				unset($tmp[$key]);
-			}
-		}
-		if ($onlyKeys) {
-			return array_keys($tmp);
-		}
-		return $tmp;
-	}
-
-	// Returns an array with a list of keys/database of scheduled pages
-	public function getScheduledDB($onlyKeys=true)
-	{
-		$tmp = $this->db;
-		foreach ($tmp as $key=>$fields) {
-			if($fields['type']!='scheduled') {
-				unset($tmp[$key]);
-			}
-		}
-		if ($onlyKeys) {
-			return array_keys($tmp);
-		}
-		return $tmp;
-	}
-
-	// Returns an array with a list of keys of sticky pages
-	public function getStickyDB($onlyKeys=true)
-	{
-		$tmp = $this->db;
-		foreach ($tmp as $key=>$fields) {
-			if($fields['type']!='sticky') {
-				unset($tmp[$key]);
-			}
-		}
-		if ($onlyKeys) {
-			return array_keys($tmp);
-		}
-		return $tmp;
-	}
-
-	// Returns the next number of the bigger position
-	public function nextPositionNumber()
-	{
-		$tmp = 1;
-		foreach ($this->db as $key=>$fields) {
-			if ($fields['position']>$tmp) {
-				$tmp = $fields['position'];
-			}
-		}
-		return ++$tmp;
-	}
-
-	// Returns the next page key of the current page key
-	public function nextPageKey($currentKey)
-	{
-		if ($this->db[$currentKey]['type']=='published') {
-			$keys = array_keys($this->db);
-			$position = array_search($currentKey, $keys) - 1;
-			if (isset($keys[$position])) {
-				$nextKey = $keys[$position];
-				if ($this->db[$nextKey]['type']=='published') {
-					return $nextKey;
-				}
-			}
-		}
-		return false;
-	}
-
-	// Returns the previous page key of the current page key
-	public function previousPageKey($currentKey)
-	{
-		if ($this->db[$currentKey]['type']=='published') {
-			$keys = array_keys($this->db);
-			$position = array_search($currentKey, $keys) + 1;
-			if (isset($keys[$position])) {
-				$prevKey = $keys[$position];
-				if ($this->db[$prevKey]['type']=='published') {
-					return $prevKey;
-				}
-			}
-		}
-		return false;
-	}
-
-	// Returns an array with a list of key of pages, FALSE if out of range
-	// The database is sorted by date or by position
-	// (int) $pageNumber, the page number
-	// (int) $numberOfItems, amount of items to return, if -1 returns all the items
-	// (boolean) $onlyPublished, TRUE to return only published pages
-	public function getList($pageNumber, $numberOfItems, $published=true, $static=false, $sticky=false, $draft=false, $scheduled=false)
-	{
-		$list = array();
-		foreach ($this->db as $key=>$fields) {
-			if ($published && $fields['type']=='published') {
-				array_push($list, $key);
-			} elseif ($static && $fields['type']=='static') {
-				array_push($list, $key);
-			} elseif ($sticky && $fields['type']=='sticky') {
-				array_push($list, $key);
-			} elseif ($draft && $fields['type']=='draft') {
-				array_push($list, $key);
-			} elseif ($scheduled && $fields['type']=='scheduled') {
-				array_push($list, $key);
-			}
-		}
-
-		if ($numberOfItems==-1) {
-			return $list;
-		}
-
-		// The first page number is 1, so the real is 0
-		$realPageNumber = $pageNumber - 1;
-
-		$total = count($list);
-		$init = (int) $numberOfItems * $realPageNumber;
-		$end  = (int) min( ($init + $numberOfItems - 1), $total );
-		$outrange = $init<0 ? true : $init>$end;
-		if (!$outrange) {
-			return array_slice($list, $init, $numberOfItems, true);
-		}
-
-		return false;
-	}
-
-	// Returns the amount of pages
-	// (boolean) $onlyPublished, TRUE returns the total of published pages (without draft and scheduled)
-	// (boolean) $onlyPublished, FALSE returns the total of pages
-	public function count($onlyPublished=true)
-	{
-		if ($onlyPublished) {
-			$db = $this->getPublishedDB(false);
-			return count($db);
-		}
-
-		return count($this->db);
-	}
-
-	// Returns an array with all parents pages key. A parent page is not a child
-	public function getParents()
-	{
-		$db = $this->getPublishedDB();
-		foreach ($db as $key=>$pageKey) {
-			// if the key has slash then is a child
-			if (Text::stringContains($pageKey, '/')) {
-				unset($db[$key]);
-			}
-		}
-		return $db;
-	}
-
-	public function getChildren($parentKey)
-	{
-		$tmp = $this->db;
-		$list = array();
-		foreach ($tmp as $key=>$fields) {
-			if (Text::startsWith($key, $parentKey.'/')) {
-				array_push($list, $key);
-			}
-		}
-		return $list;
-	}
-
-	public function sortBy()
-	{
-		if (ORDER_BY=='date') {
-			return $this->sortByDate(true);
-		}
-		return $this->sortByPosition(false);
-	}
-
-	// Sort pages by position
-	public function sortByPosition($HighToLow=false)
-	{
-		if($HighToLow) {
-			uasort($this->db, array($this, 'sortByPositionHighToLow'));
-		} else {
-			uasort($this->db, array($this, 'sortByPositionLowToHigh'));
-		}
-		return true;
-	}
-
-	private function sortByPositionLowToHigh($a, $b)
-	{
-		return $a['position']>$b['position'];
-	}
-	private function sortByPositionHighToLow($a, $b)
-	{
-		return $a['position']<$b['position'];
-	}
-
-	// Sort pages by date
-	public function sortByDate($HighToLow=true)
-	{
-		if($HighToLow) {
-			uasort($this->db, array($this, 'sortByDateHighToLow'));
-		} else {
-			uasort($this->db, array($this, 'sortByDateLowToHigh'));
-		}
-		return true;
-	}
-
-	private function sortByDateLowToHigh($a, $b)
-	{
-		return $a['date']>$b['date'];
-	}
-	private function sortByDateHighToLow($a, $b)
-	{
-		return $a['date']<$b['date'];
-	}
-
-	function generateUUID() {
-		return md5( uniqid().time() );
-	}
-
-	// Returns the UUID of a page, by the page key
-	function getUUID($key)
-	{
-		if ($this->exists($key)) {
-			return $this->db[$key]['uuid'];
-		}
-		return false;
-	}
-
-	// Returns the page key by the uuid
-	// if the UUID doesn't exits returns FALSE
-	function getByUUID($uuid)
-	{
-		foreach ($this->db as $key=>$value) {
-			if ($value['uuid']==$uuid) {
-				return $key;
-			}
-		}
-		return false;
-	}
-
-
-	// Returns string without HTML tags and truncated
-	private function generateSlug($text, $truncateLength=60)
-	{
-		$tmpslug = Text::removeHTMLTags($text);
-		$tmpslug = Text::removeLineBreaks($tmpslug);
-		$tmpslug = Text::truncate($tmpslug, $truncateLength, '');
-		return $tmpslug;
-	}
-
-	// Returns TRUE if there are new pages published, FALSE otherwise
-	public function scheduler()
-	{
-		// Get current date
-		$currentDate = Date::current(DB_DATE_FORMAT);
-		$saveDatabase = false;
-
-		// The database need to be sorted by date
-		foreach($this->db as $pageKey=>$fields) {
-			if($fields['type']=='scheduled') {
-				if($fields['date']<=$currentDate) {
-					$this->db[$pageKey]['type'] = 'published';
-					$saveDatabase = true;
-				}
-			}
-			elseif( ($fields['type']=='published') && (ORDER_BY=='date') ) {
-				break;
-			}
-		}
-
-		if($saveDatabase) {
-			if( $this->save() === false ) {
-				Log::set(__METHOD__.LOG_SEP.'Error occurred when trying to save the database file.');
-				return false;
-			}
-
-			Log::set(__METHOD__.LOG_SEP.'New pages published from the scheduler.');
-			return true;
-		}
-
-		return false;
-	}
-
-	// Generate a valid Key/Slug
-	public function generateKey($text, $parent=false, $returnSlug=false, $oldKey='')
-	{
-		global $L;
-		global $site;
-
-		if (Text::isEmpty($text)) {
-			$text = $L->g('empty');
-		}
-
-		if (Text::isEmpty($parent)) {
-			$newKey = Text::cleanUrl($text);
-		} else {
-			$newKey = Text::cleanUrl($parent).'/'.Text::cleanUrl($text);
-		}
-
-		// cleanURL can return empty string
-		if (Text::isEmpty($newKey)) {
-			$newKey = $L->g('empty');
-		}
-
-		if ($newKey!==$oldKey) {
-			// Verify if the key is already been used
-			if (isset($this->db[$newKey])) {
-				$i = 0;
-				while (isset($this->db[$newKey.'-'.$i])) {
-					$i++;
-				}
-				$newKey = $newKey.'-'.$i;
-			}
-		}
-
-		if ($returnSlug) {
-			$explode = explode('/', $newKey);
-			if (isset($explode[1])) {
-				return $explode[1];
-			}
-			return $explode[0];
-		}
-
-		return $newKey;
-	}
-
-	// Returns an Array, array('tagSlug'=>'tagName')
-	// (string) $tags, tag list separated by comma.
-	public function generateTags($tags)
-	{
-		$tmp = array();
-		$tags = trim($tags);
-		if (empty($tags)) {
-			return $tmp;
-		}
-
-		$tags = explode(',', $tags);
-		foreach ($tags as $tag) {
-			$tag = trim($tag);
-			$tagKey = Text::cleanUrl($tag);
-			$tmp[$tagKey] = $tag;
-		}
-		return $tmp;
-	}
-
-	/*	Change all pages linked to the old category key to the new category key === Bludit v4
-
-		@oldCategoryKey		string		The old category key
-		@newCategoryKey		string		The new category key
-		@return			boolean		Returns TRUE if the database was saved, FALSE otherwise
-	*/
-	public function changeCategory($oldCategoryKey, $newCategoryKey)
-	{
-		foreach ($this->db as $key=>$value) {
-			if ($value['category']===$oldCategoryKey) {
-				$this->db[$key]['category'] = $newCategoryKey;
-			}
-		}
-		return $this->save();
-	}
-
-	// Insert custom fields to all the pages in the database
-	// The structure for the custom fields need to be a valid JSON format
-	// The custom fields are incremental, this means the custom fields are never deleted
-	// The pages only store the value of the custom field, the structure of the custom fields are in the database site.php
-	public function setCustomFields($fields)
-	{
-		$customFields = json_decode($fields, true);
-		if (json_last_error() != JSON_ERROR_NONE) {
-			return false;
-		}
-		foreach ($this->db as $pageKey=>$pageFields) {
-			foreach ($customFields as $customField=>$customValues) {
-				if (!isset($pageFields['custom'][$customField])) {
-					$defaultValue = '';
-					if (isset($customValues['default'])) {
-						$defaultValue = $customValues['default'];
-					}
-					$this->db[$pageKey]['custom'][$customField]['value'] = $defaultValue;
-				}
-			}
-		}
-
-		return $this->save();
-	}
+    protected $parentKeyList = array();
+    protected $dbFields = array(
+        'title'=>'',
+        'description'=>'',
+        'username'=>'',	// Username key
+        'tags'=>array(),
+        'type'=>'draft', // published, static, draft, sticky, scheduled
+        'date'=>'',
+        'dateModified'=>'',
+        'position'=>0,
+        'coverImage'=>'',
+        'category'=>'',	// Category key
+        'md5file'=>'',
+        'uuid'=>'',
+        'allowComments'=>true,
+        'template'=>'',
+        'noindex'=>false,
+        'nofollow'=>false,
+        'noarchive'=>false,
+        'custom'=>array()
+    );
+
+    function __construct()
+    {
+        parent::__construct(DB_PAGES);
+    }
+
+    public function getDefaultFields()
+    {
+        return $this->dbFields;
+    }
+
+    /*	Get the database row associated to a page
+
+        @key			string				The key of the page to be fetch
+        @return		array/boolean		Return an array with the database for a page, FALSE otherwise
+    */
+    public function getPageDB($key)
+    {
+        if ($this->exists($key)) {
+            return $this->db[$key];
+        }
+
+        return false;
+    }
+
+    /*	Check if a page key exists in the database
+
+        @return		boolean			Return TRUE if the page exists, FALSE otherwise
+    */
+    public function exists($key)
+    {
+        return isset ($this->db[$key]);
+    }
+
+    /*	Create a new page === Bludit v4
+
+        @args			array			The array $args supports all the keys from the variable $dbFields. If you don't pass all the keys, the default values are used.
+        @return		string/boolean	Returns the page key if the page is successfully created, FALSE otherwise
+    */
+    public function add($args)
+    {
+        $row = array();
+
+        // Predefined values
+        foreach ($this->dbFields as $field=>$value) {
+            if ($field=='tags') {
+                $tags = '';
+                if (isset($args['tags'])) {
+                    $tags = $args['tags'];
+                }
+                $finalValue = $this->generateTags($tags);
+            } elseif ($field=='custom') {
+                if (isset($args['custom'])) {
+                    global $site;
+                    $customFields = $site->customFields();
+                    foreach ($args['custom'] as $customField=>$customValue) {
+                        $html = Sanitize::html($customValue);
+                        // Store the custom field as defined type
+                        settype($html, $customFields[$customField]['type']);
+                        $row['custom'][$customField]['value'] = $html;
+                    }
+                    unset($args['custom']);
+                    continue;
+                }
+            } elseif (isset($args[$field])) {
+                // Sanitize if will be stored on database
+                $finalValue = Sanitize::html($args[$field]);
+            } else {
+                // Default value for the field if not defined
+                $finalValue = $value;
+            }
+            // Store the value as defined type
+            settype($finalValue, gettype($value));
+            $row[$field] = $finalValue;
+        }
+
+        // Content
+        // This variable is not belong to the database so is not defined in $row
+        $contentRaw = (empty($args['content'])?'':$args['content']);
+
+        // Parent
+        // This variable is not belong to the database so is not defined in $row
+        $parent = '';
+        if (!empty($args['parent'])) {
+            $parent = $args['parent'];
+            $row['type'] = $this->db[$parent]['type']; // get the parent type
+        }
+
+        // Slug from the title or the content
+        // This variable is not belong to the database so is not defined in $row
+        if (empty($args['slug'])) {
+            if (!empty($row['title'])) {
+                $slug = $this->generateSlug($row['title']);
+            } else {
+                $slug = $this->generateSlug($contentRaw);
+            }
+        } else {
+            $slug = $args['slug'];
+        }
+
+        // Generate key
+        // This variable is not belong to the database so is not defined in $row
+        $key = $this->generateKey($slug, $parent);
+
+        // Generate UUID
+        if (empty($row['uuid'])) {
+            $row['uuid'] = $this->generateUUID();
+        }
+
+        // Validate date
+        if (!Valid::date($row['date'], DB_DATE_FORMAT)) {
+            $row['date'] = Date::current(DB_DATE_FORMAT);
+        }
+
+        // Schedule page
+        if (($row['date']>Date::current(DB_DATE_FORMAT)) && ($row['type']=='published')) {
+            $row['type'] = 'scheduled';
+        }
+
+        // Create the directory
+        if (Filesystem::mkdir(PATH_PAGES.$key, true) === false) {
+            Log::set(__METHOD__.LOG_SEP.'An error occurred while trying to create the directory: '.PATH_PAGES.$key, LOG_TYPE_ERROR);
+            return false;
+        }
+
+        // Create the upload directory for the page
+        if (Filesystem::mkdir(PATH_UPLOADS_PAGES.$key, true) === false) {
+            Log::set(__METHOD__.LOG_SEP.'An error occurred while trying to create the directory: '.PATH_UPLOADS_PAGES.$key, LOG_TYPE_ERROR);
+            return false;
+        }
+
+        // Create the index.txt and save the file
+        if (file_put_contents(PATH_PAGES.$key.DS.FILENAME, $contentRaw) === false) {
+            Log::set(__METHOD__.LOG_SEP.'An error occurred while trying to create the file: '.FILENAME, LOG_TYPE_ERROR);
+            return false;
+        }
+
+        // Checksum MD5
+        $row['md5file'] = md5_file(PATH_PAGES.$key.DS.FILENAME);
+
+        // Insert into database
+        $this->db[$key] = $row;
+
+        // Sort database
+        $this->sortBy();
+
+        // Save database
+        $this->save();
+
+        return $key;
+    }
+
+    /*	Edit a page === Bludit v4
+
+        @args			array			The array $args supports all the keys from the variable $dbFields. If you don't pass all the keys, the default values are used.
+        @return		string/boolean	Returns the page key if the page is successfully edited, FALSE otherwise
+    */
+    public function edit($args)
+    {
+        // This is the new row for the table and is going to replace the old row
+        $row = array();
+
+        // Current key
+        // This variable is not belong to the database so is not defined in $row
+        $key = $args['key'];
+
+        // Check values from the arguments ($args)
+        // If some field is missing the current value is taken
+        foreach ($this->dbFields as $field=>$value) {
+            if ( ($field=='tags') && isset($args['tags'])) {
+                $finalValue = $this->generateTags($args['tags']);
+            } elseif ($field=='custom') {
+                if (isset($args['custom'])) {
+                    global $site;
+                    $customFields = $site->customFields();
+                    foreach ($args['custom'] as $customField=>$customValue) {
+                        $html = Sanitize::html($customValue);
+                        // Store the custom field as defined type
+                        settype($html, $customFields[$customField]['type']);
+                        $row['custom'][$customField]['value'] = $html;
+                    }
+                    unset($args['custom']);
+                    continue;
+                }
+            } elseif (isset($args[$field])) {
+                // Sanitize if will be stored on database
+                $finalValue = Sanitize::html($args[$field]);
+            } else {
+                // Default value from the current row
+                $finalValue = $this->db[$key][$field];
+            }
+            settype($finalValue, gettype($value));
+            $row[$field] = $finalValue;
+        }
+
+        // Parent
+        // This variable is not belong to the database so is not defined in $row
+        $parent = '';
+        if (!empty($args['parent'])) {
+            $parent = $args['parent'];
+            $row['type'] = $this->db[$parent]['type']; // get the parent type
+        }
+
+        // Slug
+        // If the user change the slug the page key changes
+        // If the user send an empty slug the page key doesn't change
+        // This variable is not belong to the database so is not defined in $row
+        if (empty($args['slug'])) {
+            $explode = explode('/', $key);
+            $slug = end($explode);
+        } else {
+            $slug = $args['slug'];
+        }
+
+        // New key
+        // The key of the page can change if the user change the slug or the parent, -
+        // - if the user doesn't change the slug or the parent the key is going to be the same -
+        // - as the current key.
+        // This variable is not belong to the database so is not defined in $row
+        $newKey = $this->generateKey($slug, $parent, false, $key);
+
+        // if the date in the arguments is not valid, take the value from the old row
+        if (!Valid::date($row['date'], DB_DATE_FORMAT)) {
+            $row['date'] = $this->db[$key]['date'];
+        }
+
+        // Modified date
+        $row['dateModified'] = Date::current(DB_DATE_FORMAT);
+
+        // Schedule page
+        if (($row['date']>Date::current(DB_DATE_FORMAT)) && ($row['type']=='published')) {
+            $row['type'] = 'scheduled';
+        }
+
+        // Move the directory from old key to new key only if the keys are different
+        if ($newKey!==$key) {
+            if (Filesystem::mv(PATH_PAGES.$key, PATH_PAGES.$newKey) === false) {
+                Log::set(__METHOD__.LOG_SEP.'An error occurred while trying to move the directory: '.PATH_PAGES.$newKey, LOG_TYPE_ERROR);
+                return false;
+            }
+
+            if (Filesystem::mv(PATH_UPLOADS_PAGES.$key, PATH_UPLOADS_PAGES.$newKey) === false) {
+                Log::set(__METHOD__.LOG_SEP.'An error occurred while trying to move the directory: '.PATH_UPLOADS_PAGES.$newKey, LOG_TYPE_ERROR);
+                return false;
+            }
+        }
+
+        // If the content was passed via arguments replace the content
+        if (isset($args['content'])) {
+            // Make the index.txt and save the file.
+            if (file_put_contents(PATH_PAGES.$newKey.DS.FILENAME, $args['content'])===false) {
+                Log::set(__METHOD__.LOG_SEP.'An error occurred while trying to save the new content in the file: '.PATH_PAGES.$newKey.DS.FILENAME, LOG_TYPE_ERROR);
+                return false;
+            }
+        }
+
+        // Remove the old row
+        unset($this->db[$key]);
+
+        // Reindex Orphan Children
+        $this->reindexChildren($key, $newKey);
+
+        // Checksum MD5
+        $row['md5file'] = md5_file(PATH_PAGES.$newKey.DS.FILENAME);
+
+        // Insert in database the new row
+        $this->db[$newKey] = $row;
+
+        // Sort database
+        $this->sortBy();
+
+        // Save database
+        $this->save();
+
+        return $newKey;
+    }
+
+    /*	Delete a page === Bludit v4
+
+        @key			string			The key of the page to be deleted
+        @return		boolean			Returns TRUE if the page was deleted successfully, FALSE otherwise
+    */
+    public function delete($key)
+    {
+        // This is need it, because if the key is empty the Filesystem::deleteRecursive is going to delete PATH_PAGES
+        if (empty($key)) {
+            Log::set(__METHOD__.LOG_SEP.'Empty page key.', LOG_TYPE_ERROR);
+            return false;
+        }
+
+        // Page doesn't exist in database
+        if (!$this->exists($key)) {
+            Log::set(__METHOD__.LOG_SEP.'The page doesn\'t exist. Key: '.$key, LOG_TYPE_ERROR);
+            return false;
+        }
+
+        // Delete directory and files
+        if (Filesystem::deleteRecursive(PATH_PAGES.$key) === false) {
+            Log::set(__METHOD__.LOG_SEP.'An error occurred while trying to delete the directory: '.PATH_PAGES.$key, LOG_TYPE_ERROR);
+        }
+
+        // Delete upload directory
+        if (Filesystem::deleteRecursive(PATH_UPLOADS_PAGES.$key) === false) {
+            Log::set(__METHOD__.LOG_SEP.'An error occurred while trying to delete the directory: '.PATH_UPLOADS_PAGES.$key, LOG_TYPE_ERROR);
+        }
+
+        // Remove from database
+        unset($this->db[$key]);
+
+        // Save the database
+        $this->save();
+
+        return true;
+    }
+
+    // Delete all pages from a user
+    public function deletePagesByUser($args)
+    {
+        $username = $args['username'];
+
+        foreach ($this->db as $key=>$fields) {
+            if ($fields['username']===$username) {
+                $this->delete($key);
+            }
+        }
+
+        return true;
+    }
+
+    // Link all pages to a new user
+    public function transferPages($args)
+    {
+        $oldUsername = $args['oldUsername'];
+        $newUsername = isset($args['newUsername']) ? $args['newUsername'] : 'admin';
+
+        foreach ($this->db as $key=>$fields) {
+            if ($fields['username']===$oldUsername) {
+                $this->db[$key]['username'] = $newUsername;
+            }
+        }
+
+        return $this->save();
+    }
+
+    // This function reindex the orphan children with the new parent key
+    // If a page has subpages and the page change his key is necesarry check the children key
+    public function reindexChildren($oldParentKey, $newParentKey) {
+        if ($oldParentKey==$newParentKey){
+            return false;
+        }
+        $tmp = $this->db;
+        foreach ($tmp as $key=>$fields) {
+            if (Text::startsWith($key, $oldParentKey.'/')) {
+                $newKey = Text::replace($oldParentKey.'/', $newParentKey.'/', $key);
+                $this->db[$newKey] = $this->db[$key];
+                unset($this->db[$key]);
+            }
+        }
+    }
+
+    // Set field = value
+    public function setField($key, $field, $value)
+    {
+        if ($this->exists($key)) {
+            settype($value, gettype($this->dbFields[$field]));
+            $this->db[$key][$field] = $value;
+            return $this->save();
+        }
+        return false;
+    }
+
+    // Returns a database with all pages
+    // $onlyKeys = true; Returns only the pages keys
+    // $onlyKeys = false; Returns part of the database, I do not recommend use this
+    public function getDB($onlyKeys=true)
+    {
+        $tmp = $this->db;
+        if ($onlyKeys) {
+            return array_keys($tmp);
+        }
+        return $tmp;
+    }
+
+    // Returns a database with published pages
+    // $onlyKeys = true; Returns only the pages keys
+    // $onlyKeys = false; Returns part of the database, I do not recommend use this
+    public function getPublishedDB($onlyKeys=true)
+    {
+        $tmp = $this->db;
+        foreach ($tmp as $key=>$fields) {
+            if ($fields['type']!='published') {
+                unset($tmp[$key]);
+            }
+        }
+        if ($onlyKeys) {
+            return array_keys($tmp);
+        }
+        return $tmp;
+    }
+
+    // Returns an array with a list of keys/database of static pages
+    // By default the static pages are sort by position
+    public function getStaticDB($onlyKeys=true)
+    {
+        $tmp = $this->db;
+        foreach ($tmp as $key=>$fields) {
+            if ($fields['type']!='static') {
+                unset($tmp[$key]);
+            }
+        }
+        uasort($tmp, array($this, 'sortByPositionLowToHigh'));
+        if ($onlyKeys) {
+            return array_keys($tmp);
+        }
+        return $tmp;
+    }
+
+    // Returns an array with a list of keys/database of draft pages
+    public function getDraftDB($onlyKeys=true)
+    {
+        $tmp = $this->db;
+        foreach ($tmp as $key=>$fields) {
+            if($fields['type']!='draft') {
+                unset($tmp[$key]);
+            }
+        }
+        if ($onlyKeys) {
+            return array_keys($tmp);
+        }
+        return $tmp;
+    }
+
+    // Returns an array with a list of keys/database of scheduled pages
+    public function getScheduledDB($onlyKeys=true)
+    {
+        $tmp = $this->db;
+        foreach ($tmp as $key=>$fields) {
+            if($fields['type']!='scheduled') {
+                unset($tmp[$key]);
+            }
+        }
+        if ($onlyKeys) {
+            return array_keys($tmp);
+        }
+        return $tmp;
+    }
+
+    // Returns an array with a list of keys of sticky pages
+    public function getStickyDB($onlyKeys=true)
+    {
+        $tmp = $this->db;
+        foreach ($tmp as $key=>$fields) {
+            if($fields['type']!='sticky') {
+                unset($tmp[$key]);
+            }
+        }
+        if ($onlyKeys) {
+            return array_keys($tmp);
+        }
+        return $tmp;
+    }
+
+    // Returns the next number of the bigger position
+    public function nextPositionNumber()
+    {
+        $tmp = 1;
+        foreach ($this->db as $key=>$fields) {
+            if ($fields['position']>$tmp) {
+                $tmp = $fields['position'];
+            }
+        }
+        return ++$tmp;
+    }
+
+    // Returns the next page key of the current page key
+    public function nextPageKey($currentKey)
+    {
+        if ($this->db[$currentKey]['type']=='published') {
+            $keys = array_keys($this->db);
+            $position = array_search($currentKey, $keys) - 1;
+            if (isset($keys[$position])) {
+                $nextKey = $keys[$position];
+                if ($this->db[$nextKey]['type']=='published') {
+                    return $nextKey;
+                }
+            }
+        }
+        return false;
+    }
+
+    // Returns the previous page key of the current page key
+    public function previousPageKey($currentKey)
+    {
+        if ($this->db[$currentKey]['type']=='published') {
+            $keys = array_keys($this->db);
+            $position = array_search($currentKey, $keys) + 1;
+            if (isset($keys[$position])) {
+                $prevKey = $keys[$position];
+                if ($this->db[$prevKey]['type']=='published') {
+                    return $prevKey;
+                }
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Get a list of pages' keys. === Bludit v4
+     * @param       int         $pageNumber     Page number for the paginator
+     * @param       int         $numberOfItems  Amount of items to return, if -1 returns all the items
+     * @return      array|bool                  Returns an array with the pages' keys or FALSE if it out of range
+     */
+    public function getList(int $pageNumber, int $numberOfItems, bool $published=true, bool $static=false, bool $sticky=false, bool $draft=false, bool $scheduled=false)
+    {
+        $list = array();
+        foreach ($this->db as $key=>$fields) {
+            if ($published && $fields['type']=='published') {
+                array_push($list, $key);
+            } elseif ($static && $fields['type']=='static') {
+                array_push($list, $key);
+            } elseif ($sticky && $fields['type']=='sticky') {
+                array_push($list, $key);
+            } elseif ($draft && $fields['type']=='draft') {
+                array_push($list, $key);
+            } elseif ($scheduled && $fields['type']=='scheduled') {
+                array_push($list, $key);
+            }
+        }
+
+        if ($numberOfItems==-1) {
+            return $list;
+        }
+
+        // The first page number is 1, so the real is 0
+        $realPageNumber = $pageNumber - 1;
+
+        $total = count($list);
+        $init = (int) $numberOfItems * $realPageNumber;
+        $end  = (int) min( ($init + $numberOfItems - 1), $total );
+        $outrange = $init<0 ? true : $init>$end;
+        if (!$outrange) {
+            return array_slice($list, $init, $numberOfItems, true);
+        }
+
+        return false;
+    }
+
+    // Returns the amount of pages
+    // (boolean) $onlyPublished, TRUE returns the total of published pages (without draft and scheduled)
+    // (boolean) $onlyPublished, FALSE returns the total of pages
+    public function count($onlyPublished=true)
+    {
+        if ($onlyPublished) {
+            $db = $this->getPublishedDB(false);
+            return count($db);
+        }
+
+        return count($this->db);
+    }
+
+    // Returns an array with all parents pages key. A parent page is not a child
+    public function getParents()
+    {
+        $db = $this->getPublishedDB();
+        foreach ($db as $key=>$pageKey) {
+            // if the key has slash then is a child
+            if (Text::stringContains($pageKey, '/')) {
+                unset($db[$key]);
+            }
+        }
+        return $db;
+    }
+
+    public function getChildren($parentKey)
+    {
+        $tmp = $this->db;
+        $list = array();
+        foreach ($tmp as $key=>$fields) {
+            if (Text::startsWith($key, $parentKey.'/')) {
+                array_push($list, $key);
+            }
+        }
+        return $list;
+    }
+
+    public function sortBy()
+    {
+        if (ORDER_BY=='date') {
+            return $this->sortByDate(true);
+        }
+        return $this->sortByPosition(false);
+    }
+
+    // Sort pages by position
+    public function sortByPosition($HighToLow=false)
+    {
+        if($HighToLow) {
+            uasort($this->db, array($this, 'sortByPositionHighToLow'));
+        } else {
+            uasort($this->db, array($this, 'sortByPositionLowToHigh'));
+        }
+        return true;
+    }
+
+    private function sortByPositionLowToHigh($a, $b)
+    {
+        return $a['position']>$b['position'];
+    }
+    private function sortByPositionHighToLow($a, $b)
+    {
+        return $a['position']<$b['position'];
+    }
+
+    // Sort pages by date
+    public function sortByDate($HighToLow=true)
+    {
+        if($HighToLow) {
+            uasort($this->db, array($this, 'sortByDateHighToLow'));
+        } else {
+            uasort($this->db, array($this, 'sortByDateLowToHigh'));
+        }
+        return true;
+    }
+
+    private function sortByDateLowToHigh($a, $b)
+    {
+        return $a['date']<=>$b['date'];
+    }
+    private function sortByDateHighToLow($a, $b)
+    {
+        return $b['date']<=>$a['date'];
+    }
+
+    function generateUUID() {
+        return md5( uniqid().time() );
+    }
+
+    // Returns the UUID of a page, by the page key
+    function getUUID($key)
+    {
+        if ($this->exists($key)) {
+            return $this->db[$key]['uuid'];
+        }
+        return false;
+    }
+
+    // Returns the page key by the uuid
+    // if the UUID doesn't exits returns FALSE
+    function getByUUID($uuid)
+    {
+        foreach ($this->db as $key=>$value) {
+            if ($value['uuid']==$uuid) {
+                return $key;
+            }
+        }
+        return false;
+    }
+
+
+    // Returns string without HTML tags and truncated
+    private function generateSlug($text, $truncateLength=60)
+    {
+        $tmpslug = Text::removeHTMLTags($text);
+        $tmpslug = Text::removeLineBreaks($tmpslug);
+        $tmpslug = Text::truncate($tmpslug, $truncateLength, '');
+        return $tmpslug;
+    }
+
+    // Returns TRUE if there are new pages published, FALSE otherwise
+    public function scheduler()
+    {
+        // Get current date
+        $currentDate = Date::current(DB_DATE_FORMAT);
+        $saveDatabase = false;
+
+        // The database need to be sorted by date
+        foreach($this->db as $pageKey=>$fields) {
+            if($fields['type']=='scheduled') {
+                if($fields['date']<=$currentDate) {
+                    $this->db[$pageKey]['type'] = 'published';
+                    $saveDatabase = true;
+                }
+            }
+            elseif( ($fields['type']=='published') && (ORDER_BY=='date') ) {
+                break;
+            }
+        }
+
+        if($saveDatabase) {
+            if( $this->save() === false ) {
+                Log::set(__METHOD__.LOG_SEP.'Error occurred when trying to save the database file.');
+                return false;
+            }
+
+            Log::set(__METHOD__.LOG_SEP.'New pages published from the scheduler.');
+            return true;
+        }
+
+        return false;
+    }
+
+    // Generate a valid Key/Slug
+    public function generateKey($text, $parent=false, $returnSlug=false, $oldKey='')
+    {
+        global $L;
+        global $site;
+
+        if (Text::isEmpty($text)) {
+            $text = $L->g('empty');
+        }
+
+        if (Text::isEmpty($parent)) {
+            $newKey = Text::cleanUrl($text);
+        } else {
+            $newKey = Text::cleanUrl($parent).'/'.Text::cleanUrl($text);
+        }
+
+        // cleanURL can return empty string
+        if (Text::isEmpty($newKey)) {
+            $newKey = $L->g('empty');
+        }
+
+        if ($newKey!==$oldKey) {
+            // Verify if the key is already been used
+            if (isset($this->db[$newKey])) {
+                $i = 0;
+                while (isset($this->db[$newKey.'-'.$i])) {
+                    $i++;
+                }
+                $newKey = $newKey.'-'.$i;
+            }
+        }
+
+        if ($returnSlug) {
+            $explode = explode('/', $newKey);
+            if (isset($explode[1])) {
+                return $explode[1];
+            }
+            return $explode[0];
+        }
+
+        return $newKey;
+    }
+
+    // Returns an Array, array('tagSlug'=>'tagName')
+    // (string) $tags, tag list separated by comma.
+    public function generateTags($tags)
+    {
+        $tmp = array();
+        $tags = trim($tags);
+        if (empty($tags)) {
+            return $tmp;
+        }
+
+        $tags = explode(',', $tags);
+        foreach ($tags as $tag) {
+            $tag = trim($tag);
+            $tagKey = Text::cleanUrl($tag);
+            $tmp[$tagKey] = $tag;
+        }
+        return $tmp;
+    }
+
+    /*	Change all pages linked to the old category key to the new category key === Bludit v4
+
+        @oldCategoryKey		string		The old category key
+        @newCategoryKey		string		The new category key
+        @return			boolean		Returns TRUE if the database was saved, FALSE otherwise
+    */
+    public function changeCategory($oldCategoryKey, $newCategoryKey)
+    {
+        foreach ($this->db as $key=>$value) {
+            if ($value['category']===$oldCategoryKey) {
+                $this->db[$key]['category'] = $newCategoryKey;
+            }
+        }
+        return $this->save();
+    }
+
+    // Insert custom fields to all the pages in the database
+    // The structure for the custom fields need to be a valid JSON format
+    // The custom fields are incremental, this means the custom fields are never deleted
+    // The pages only store the value of the custom field, the structure of the custom fields are in the database site.php
+    public function setCustomFields($fields)
+    {
+        $customFields = json_decode($fields, true);
+        if (json_last_error() != JSON_ERROR_NONE) {
+            return false;
+        }
+        foreach ($this->db as $pageKey=>$pageFields) {
+            foreach ($customFields as $customField=>$customValues) {
+                if (!isset($pageFields['custom'][$customField])) {
+                    $defaultValue = '';
+                    if (isset($customValues['default'])) {
+                        $defaultValue = $customValues['default'];
+                    }
+                    $this->db[$pageKey]['custom'][$customField]['value'] = $defaultValue;
+                }
+            }
+        }
+
+        return $this->save();
+    }
 
 
 }

+ 1 - 1
bl-kernel/parsedown.class.php

@@ -13,7 +13,7 @@
 #
 #
 
-class Parsedown
+final class Parsedown
 {
     # ~
 

+ 442 - 439
bl-kernel/site.class.php

@@ -1,441 +1,444 @@
 <?php defined('BLUDIT') or die('Bludit CMS.');
 
-class Site extends dbJSON {
-	public $dbFields = array(
-		'title'=>		'I am Guybrush Threepwood, mighty developer',
-		'slogan'=>		'',
-		'description'=>		'',
-		'footer'=>		'I wanna be a pirate!',
-		'itemsPerPage'=>	6,
-		'language'=>		'en',
-		'locale'=>		'en, en_US, en_AU, en_CA, en_GB, en_IE, en_NZ',
-		'timezone'=>		'America/Argentina/Buenos_Aires',
-		'theme'=>		'alternative',
-		'adminTheme'=>		'booty',
-		'homepage'=>		'',
-		'pageNotFound'=>	'',
-		'uriPage'=>		'/',
-		'uriTag'=>		'/tag/',
-		'uriCategory'=>		'/category/',
-		'uriBlog'=>		'/blog/',
-		'url'=>			'',
-		'emailFrom'=>		'',
-		'dateFormat'=>		'F j, Y',
-		'timeFormat'=>		'g:i a',
-		'currentBuild'=>	0,
-		'twitter'=>		'',
-		'facebook'=>		'',
-		'codepen'=>		'',
-		'instagram'=>		'',
-		'github'=>		'',
-		'gitlab'=>		'',
-		'linkedin'=>		'',
-		'xing'=>		'',
-		'mastodon'=>		'',
-		'dribbble'=>		'',
-		'vk'=>			'',
-		'discord'=>			'',
-		'youtube'=>			'',
-		'orderBy'=>		'date', // date or position
-		'extremeFriendly'=>	true,
-		'autosaveInterval'=>	2, // minutes
-		'titleFormatHomepage'=>	'{{site-slogan}} | {{site-title}}',
-		'titleFormatPages'=>	'{{page-title}} | {{site-title}}',
-		'titleFormatCategory'=> '{{category-name}} | {{site-title}}',
-		'titleFormatTag'=> 	'{{tag-name}} | {{site-title}}',
-		'imageRestrict'=>	true,
-		'imageRelativeToAbsolute'=> false,
-		'thumbnailWidth'=> 	400, // px
-		'thumbnailHeight'=> 	400, // px
-		'thumbnailQuality'=> 	100,
-		'logo'=>		'',
-		'markdownParser'=>	true,
-		'customFields'=>	'{}',
-		'darkModeAdmin'=> false
-	);
-
-	function __construct()
-	{
-		parent::__construct(DB_SITE);
-
-		// Set timezone
-		$this->setTimezone( $this->timezone() );
-
-		// Set locale
-		$this->setLocale( $this->locale() );
-	}
-
-	// Returns an array with site configuration.
-	function get()
-	{
-		return $this->db;
-	}
-
-	public function set($args)
-	{
-		// Check values on args or set default values
-		foreach ($this->dbFields as $field=>$value) {
-			if (isset($args[$field])) {
-				$finalValue = Sanitize::html($args[$field]);
-				if ($finalValue==='false') { $finalValue = false; }
-				elseif ($finalValue==='true') { $finalValue = true; }
-				settype($finalValue, gettype($value));
-				$this->db[$field] = $finalValue;
-			}
-		}
-		return $this->save();
-	}
-
-	// Returns an array with the URL filters
-	// Also, you can get the a particular filter
-	public function uriFilters($filter='')
-	{
-		$filters['admin'] = '/'.ADMIN_URI_FILTER.'/';
-		$filters['page'] = $this->getField('uriPage');
-		$filters['tag'] = $this->getField('uriTag');
-		$filters['category'] = $this->getField('uriCategory');
-
-		if ($this->getField('uriBlog')) {
-			$filters['blog'] = $this->getField('uriBlog');
-		}
-
-		if (empty($filter)) {
-			return $filters;
-		}
-
-		if (isset($filters[$filter])) {
-			return $filters[$filter];
-		}
-
-		return false;
-	}
-
-	// DEPRECATED in v3.0, use HTML::rssUrl()
-	public function rss()
-	{
-		return DOMAIN_BASE.'rss.xml';
-	}
-
-	// DEPRECATED in v3.0, use HTML::sitemapUrl()
-	public function sitemap()
-	{
-		return DOMAIN_BASE.'sitemap.xml';
-	}
-
-	public function thumbnailWidth()
-	{
-		return $this->getField('thumbnailWidth');
-	}
-
-	public function thumbnailHeight()
-	{
-		return $this->getField('thumbnailHeight');
-	}
-
-	public function thumbnailQuality()
-	{
-		return $this->getField('thumbnailQuality');
-	}
-
-	public function autosaveInterval()
-	{
-		return $this->getField('autosaveInterval');
-	}
-
-	public function extremeFriendly()
-	{
-		return $this->getField('extremeFriendly');
-	}
-
-	public function markdownParser()
-	{
-		return $this->getField('markdownParser');
-	}
-
-	public function darkModeAdmin()
-	{
-		return $this->getField('darkModeAdmin');
-	}
-
-	public function twitter()
-	{
-		return $this->getField('twitter');
-	}
-
-	public function facebook()
-	{
-		return $this->getField('facebook');
-	}
-
-	public function discord()
-	{
-		return $this->getField('discord');
-	}
-
-	public function youtube()
-	{
-		return $this->getField('youtube');
-	}
-
-	public function codepen()
-	{
-		return $this->getField('codepen');
-	}
-
-	public function instagram()
-	{
-		return $this->getField('instagram');
-	}
-
-	public function github()
-	{
-		return $this->getField('github');
-	}
-
-	public function gitlab()
-	{
-		return $this->getField('gitlab');
-	}
-
-	public function linkedin()
-	{
-		return $this->getField('linkedin');
-	}
-
-	public function xing()
-	{
-		return $this->getField('xing');
-	}
-
-	public function mastodon()
-	{
-		return $this->getField('mastodon');
-	}
-
-	public function dribbble()
-	{
-		return $this->getField('dribbble');
-	}
-
-	public function vk()
-	{
-		return $this->getField('vk');
-	}
-
-	public function orderBy()
-	{
-		return $this->getField('orderBy');
-	}
-
-	public function imageRestrict()
-	{
-		return $this->getField('imageRestrict');
-	}
-
-	public function imageRelativeToAbsolute()
-	{
-		return $this->getField('imageRelativeToAbsolute');
-	}
-
-	// Returns the site title
-	public function title()
-	{
-		return $this->getField('title');
-	}
-
-	// Returns the site slogan
-	public function slogan()
-	{
-		return $this->getField('slogan');
-	}
-
-	// Returns the site description
-	public function description()
-	{
-		return $this->getField('description');
-	}
-
-	public function emailFrom()
-	{
-		return $this->getField('emailFrom');
-	}
-
-	public function dateFormat()
-	{
-		return $this->getField('dateFormat');
-	}
-
-	public function timeFormat()
-	{
-		return $this->getField('timeFormat');
-	}
-
-	// Returns the site theme name
-	public function theme()
-	{
-		return $this->getField('theme');
-	}
-
-	// Returns the admin theme name
-	public function adminTheme()
-	{
-		return $this->getField('adminTheme');
-	}
-
-	// Returns the footer text
-	public function footer()
-	{
-		return $this->getField('footer');
-	}
-
-	public function titleFormatPages()
-	{
-		return $this->getField('titleFormatPages');
-	}
-
-	public function titleFormatHomepage()
-	{
-		return $this->getField('titleFormatHomepage');
-	}
-
-	public function titleFormatCategory()
-	{
-		return $this->getField('titleFormatCategory');
-	}
-
-	public function titleFormatTag()
-	{
-		return $this->getField('titleFormatTag');
-	}
-
-	// Returns the absolute URL of the site logo
-	// If you set $absolute=false returns only the filename
-	public function logo($absolute=true)
-	{
-		$logo = $this->getField('logo');
-		if ($absolute && $logo) {
-			return DOMAIN_UPLOADS.$logo;
-		}
-		return $logo;
-	}
-
-	// Returns the full domain and base url
-	// For example, https://www.domain.com/bludit
-	public function url()
-	{
-		return $this->getField('url');
-	}
-
-	// Returns the protocol and the domain, without the base url
-	// For example, http://www.domain.com
-	public function domain()
-	{
-		// If the URL field is not set, try detect the domain.
-		if(Text::isEmpty( $this->url() )) {
-			if(!empty($_SERVER['HTTPS'])) {
-				$protocol = 'https://';
-			}
-			else {
-				$protocol = 'http://';
-			}
-
-			$domain = trim($_SERVER['HTTP_HOST'], '/');
-			return $protocol.$domain;
-		}
-
-		// Parse the domain from the field url (Settings->Advanced)
-		$parse = parse_url($this->url());
-		$domain = rtrim($parse['host'], '/');
-		$port = !empty($parse['port']) ? ':'.$parse['port'] : '';
-		$scheme = !empty($parse['scheme']) ? $parse['scheme'].'://' : 'http://';
-
-		return $scheme.$domain.$port;
-	}
-
-	// Returns the timezone.
-	public function timezone()
-	{
-		return $this->getField('timezone');
-	}
-
-	// Returns the current build / version of Bludit.
-	public function currentBuild()
-	{
-		return $this->getField('currentBuild');
-	}
-
-	// Returns the amount of pages per page
-	public function itemsPerPage()
-	{
-		return $this->getField('itemsPerPage');
-	}
-
-	// Returns the current language.
-	public function language()
-	{
-		return $this->getField('language');
-	}
-
-	// Returns the sort version of the site's language
-	public function languageShortVersion()
-	{
-		$current = $this->language();
-		$explode = explode('_', $current);
-		return $explode[0];
-	}
-
-	// Returns the current locale.
-	public function locale()
-	{
-		return $this->getField('locale');
-	}
-
-	// Returns the current homepage, FALSE if not defined homepage
-	public function homepage()
-	{
-		$homepage = $this->getField('homepage');
-		if (empty($homepage)) {
-			return false;
-		}
-		return $homepage;
-	}
-
-	// Returns the page key for the page not found
-	public function pageNotFound()
-	{
-		$pageNotFound = $this->getField('pageNotFound');
-		return $pageNotFound;
-	}
-
-	// Set the locale, returns TRUE is success, FALSE otherwise
-	public function setLocale($locale)
-	{
-		$localeList = explode(',', $locale);
-		foreach ($localeList as $locale) {
-			$locale = trim($locale);
-			if (setlocale(LC_ALL, $locale.'.UTF-8')!==false) {
-				return true;
-			}
-			elseif (setlocale(LC_ALL, $locale)!==false) {
-				return true;
-			}
-		}
-
-		// Not was possible to set a locale, using default locale
-		return false;
-	}
-
-	// Set the timezone.
-	public function setTimezone($timezone)
-	{
-		return date_default_timezone_set($timezone);
-	}
-
-	// Returns the custom fields as array
-	public function customFields()
-	{
-		$customFields = Sanitize::htmlDecode($this->getField('customFields'));
-		return json_decode($customFields, true);
-	}
-
-}
+class Site extends dbJSON
+{
+    public $dbFields = array(
+        'title'                         => 'I am Guybrush Threepwood, mighty developer',
+        'slogan'                        => '',
+        'description'                   => '',
+        'footer'                        => 'I wanna be a pirate!',
+        'itemsPerPage'                  =>  6,
+        'language'                      => 'en',
+        'locale'                        => 'en, en_US, en_AU, en_CA, en_GB, en_IE, en_NZ',
+        'timezone'                      => 'America/Argentina/Buenos_Aires',
+        'theme'                         => 'alternative',
+        'adminTheme'                    => 'booty',
+        'homepage'                      => '',
+        'pageNotFound'                  => '',
+        'uriPage'                       => '/',
+        'uriTag'                        => '/tag/',
+        'uriCategory'                   => '/category/',
+        'uriBlog'                       => '/blog/',
+        'url'                           => '',
+        'emailFrom'                     => '',
+        'dateFormat'                    => 'F j, Y',
+        'timeFormat'                    => 'g:i a',
+        'currentBuild'                  => 0,
+        'twitter'                       => '',
+        'facebook'                      => '',
+        'codepen'                       => '',
+        'instagram'                     => '',
+        'github'                        => '',
+        'gitlab'                        => '',
+        'linkedin'                      => '',
+        'xing'                          => '',
+        'mastodon'                      => '',
+        'dribbble'                      => '',
+        'vk'                            => '',
+        'discord'                       => '',
+        'youtube'                       => '',
+        'orderBy'                       => 'date', // date or position
+        'extremeFriendly'               => true,
+        'autosaveInterval'              => 2, // minutes
+        'titleFormatHomepage'           => '{{site-slogan}} | {{site-title}}',
+        'titleFormatPages'              => '{{page-title}} | {{site-title}}',
+        'titleFormatCategory'           => '{{category-name}} | {{site-title}}',
+        'titleFormatTag'                => '{{tag-name}} | {{site-title}}',
+        'imageRestrict'                 => true,
+        'imageRelativeToAbsolute'       => false,
+        'thumbnailSmallWidth'           => 400, // px
+        'thumbnailSmallHeight'          => 400, // px
+        'thumbnailSmallQuality'         => 100, // %
+        'thumbnailMediumWidth'          => 800, // px
+        'thumbnailMediumHeight'         => 600, // px
+        'thumbnailMediumQuality'        => 100, // %
+        'logo'                          => '',
+        'markdownParser'                => true,
+        'customFields'                  => '{}',
+        'darkModeAdmin'                 => false
+    );
+
+    function __construct()
+    {
+        parent::__construct(DB_SITE);
+        $this->setTimezone($this->timezone());
+        $this->setLocale($this->locale());
+    }
+
+    // Returns an array with site configuration.
+    function get()
+    {
+        return $this->db;
+    }
+
+    public function set($args)
+    {
+        // Check values on args or set default values
+        foreach ($this->dbFields as $field => $value) {
+            if (isset($args[$field])) {
+                $finalValue = Sanitize::html($args[$field]);
+                if ($finalValue === 'false') {
+                    $finalValue = false;
+                } elseif ($finalValue === 'true') {
+                    $finalValue = true;
+                }
+                settype($finalValue, gettype($value));
+                $this->db[$field] = $finalValue;
+            }
+        }
+        return $this->save();
+    }
+
+    // Returns an array with the URL filters
+    // Also, you can get the a particular filter
+    public function uriFilters($filter = '')
+    {
+        $filters['admin'] = '/' . ADMIN_URI_FILTER . '/';
+        $filters['page'] = $this->getField('uriPage');
+        $filters['tag'] = $this->getField('uriTag');
+        $filters['category'] = $this->getField('uriCategory');
+
+        if ($this->getField('uriBlog')) {
+            $filters['blog'] = $this->getField('uriBlog');
+        }
+
+        if (empty($filter)) {
+            return $filters;
+        }
+
+        if (isset($filters[$filter])) {
+            return $filters[$filter];
+        }
+
+        return false;
+    }
+
+    public function thumbnailSmallWidth()
+    {
+        return $this->getField('thumbnailSmallWidth');
+    }
+
+    public function thumbnailSmallHeight()
+    {
+        return $this->getField('thumbnailSmallHeight');
+    }
+
+    public function thumbnailSmallQuality()
+    {
+        return $this->getField('thumbnailSmallQuality');
+    }
+
+    public function thumbnailMediumWidth()
+    {
+        return $this->getField('thumbnailMediumWidth');
+    }
+
+    public function thumbnailMediumHeight()
+    {
+        return $this->getField('thumbnailMediumHeight');
+    }
+
+    public function thumbnailMediumQuality()
+    {
+        return $this->getField('thumbnailMediumQuality');
+    }
+
+    public function autosaveInterval()
+    {
+        return $this->getField('autosaveInterval');
+    }
+
+    public function extremeFriendly()
+    {
+        return $this->getField('extremeFriendly');
+    }
+
+    public function markdownParser()
+    {
+        return $this->getField('markdownParser');
+    }
+
+    public function darkModeAdmin()
+    {
+        return $this->getField('darkModeAdmin');
+    }
+
+    public function twitter()
+    {
+        return $this->getField('twitter');
+    }
+
+    public function facebook()
+    {
+        return $this->getField('facebook');
+    }
+
+    public function discord()
+    {
+        return $this->getField('discord');
+    }
+
+    public function youtube()
+    {
+        return $this->getField('youtube');
+    }
+
+    public function codepen()
+    {
+        return $this->getField('codepen');
+    }
+
+    public function instagram()
+    {
+        return $this->getField('instagram');
+    }
+
+    public function github()
+    {
+        return $this->getField('github');
+    }
+
+    public function gitlab()
+    {
+        return $this->getField('gitlab');
+    }
+
+    public function linkedin()
+    {
+        return $this->getField('linkedin');
+    }
+
+    public function xing()
+    {
+        return $this->getField('xing');
+    }
+
+    public function mastodon()
+    {
+        return $this->getField('mastodon');
+    }
+
+    public function dribbble()
+    {
+        return $this->getField('dribbble');
+    }
+
+    public function vk()
+    {
+        return $this->getField('vk');
+    }
+
+    public function orderBy()
+    {
+        return $this->getField('orderBy');
+    }
+
+    public function imageRestrict()
+    {
+        return $this->getField('imageRestrict');
+    }
+
+    public function imageRelativeToAbsolute()
+    {
+        return $this->getField('imageRelativeToAbsolute');
+    }
+
+    // Returns the site title
+    public function title()
+    {
+        return $this->getField('title');
+    }
+
+    // Returns the site slogan
+    public function slogan()
+    {
+        return $this->getField('slogan');
+    }
+
+    // Returns the site description
+    public function description()
+    {
+        return $this->getField('description');
+    }
+
+    public function emailFrom()
+    {
+        return $this->getField('emailFrom');
+    }
+
+    public function dateFormat()
+    {
+        return $this->getField('dateFormat');
+    }
+
+    public function timeFormat()
+    {
+        return $this->getField('timeFormat');
+    }
+
+    // Returns the site theme name
+    public function theme()
+    {
+        return $this->getField('theme');
+    }
+
+    // Returns the admin theme name
+    public function adminTheme()
+    {
+        return $this->getField('adminTheme');
+    }
+
+    // Returns the footer text
+    public function footer()
+    {
+        return $this->getField('footer');
+    }
+
+    public function titleFormatPages()
+    {
+        return $this->getField('titleFormatPages');
+    }
+
+    public function titleFormatHomepage()
+    {
+        return $this->getField('titleFormatHomepage');
+    }
+
+    public function titleFormatCategory()
+    {
+        return $this->getField('titleFormatCategory');
+    }
+
+    public function titleFormatTag()
+    {
+        return $this->getField('titleFormatTag');
+    }
+
+    // Returns the absolute URL of the site logo
+    // If you set $absolute=false returns only the filename
+    public function logo($absolute = true)
+    {
+        $logo = $this->getField('logo');
+        if ($absolute && $logo) {
+            return DOMAIN_UPLOADS . $logo;
+        }
+        return $logo;
+    }
+
+    // Returns the full domain and base url
+    // For example, https://www.domain.com/bludit
+    public function url()
+    {
+        return $this->getField('url');
+    }
+
+    // Returns the protocol and the domain, without the base url
+    // For example, http://www.domain.com
+    public function domain()
+    {
+        // If the URL field is not set, try detect the domain.
+        if (Text::isEmpty($this->url())) {
+            if (!empty($_SERVER['HTTPS'])) {
+                $protocol = 'https://';
+            } else {
+                $protocol = 'http://';
+            }
+
+            $domain = trim($_SERVER['HTTP_HOST'], '/');
+            return $protocol . $domain;
+        }
+
+        // Parse the domain from the field url (Settings->Advanced)
+        $parse = parse_url($this->url());
+        $domain = rtrim($parse['host'], '/');
+        $port = !empty($parse['port']) ? ':' . $parse['port'] : '';
+        $scheme = !empty($parse['scheme']) ? $parse['scheme'] . '://' : 'http://';
+
+        return $scheme . $domain . $port;
+    }
+
+    // Returns the timezone.
+    public function timezone()
+    {
+        return $this->getField('timezone');
+    }
+
+    // Returns the current build / version of Bludit.
+    public function currentBuild()
+    {
+        return $this->getField('currentBuild');
+    }
+
+    // Returns the amount of pages per page
+    public function itemsPerPage()
+    {
+        return $this->getField('itemsPerPage');
+    }
+
+    // Returns the current language.
+    public function language()
+    {
+        return $this->getField('language');
+    }
+
+    // Returns the sort version of the site's language
+    public function languageShortVersion()
+    {
+        $current = $this->language();
+        $explode = explode('_', $current);
+        return $explode[0];
+    }
+
+    // Returns the current locale.
+    public function locale()
+    {
+        return $this->getField('locale');
+    }
+
+    // Returns the current homepage, FALSE if not defined homepage
+    public function homepage()
+    {
+        $homepage = $this->getField('homepage');
+        if (empty($homepage)) {
+            return false;
+        }
+        return $homepage;
+    }
+
+    // Returns the page key for the page not found
+    public function pageNotFound()
+    {
+        $pageNotFound = $this->getField('pageNotFound');
+        return $pageNotFound;
+    }
+
+    // Set the locale, returns TRUE is success, FALSE otherwise
+    public function setLocale($locale)
+    {
+        $localeList = explode(',', $locale);
+        foreach ($localeList as $locale) {
+            $locale = trim($locale);
+            if (setlocale(LC_ALL, $locale . '.UTF-8') !== false) {
+                return true;
+            } elseif (setlocale(LC_ALL, $locale) !== false) {
+                return true;
+            }
+        }
+
+        // Not was possible to set a locale, using default locale
+        return false;
+    }
+
+    // Set the timezone.
+    public function setTimezone($timezone)
+    {
+        return date_default_timezone_set($timezone);
+    }
+
+    // Returns the custom fields as array
+    public function customFields()
+    {
+        $customFields = Sanitize::htmlDecode($this->getField('customFields'));
+        return json_decode($customFields, true);
+    }
+}

+ 25 - 23
bl-kernel/users.class.php

@@ -33,7 +33,7 @@ class Users extends dbJSON {
 		parent::__construct(DB_USERS);
 	}
 
-	public function getDefaultFields()
+	public function getDefaultFields(): array
 	{
 		return $this->dbFields;
 	}
@@ -48,28 +48,26 @@ class Users extends dbJSON {
 	}
 
 	// Return TRUE if the user exists, FALSE otherwise
-	public function exists($username)
+	public function exists($username): bool
 	{
 		return isset($this->db[$username]);
 	}
 
-	/*	Disable an user === Bludit v4
-
-		@username		string			The username to be disabled
-		@return			string			Returns the username
-	*/
-	public function disableUser($username)
+	/**
+	 * Disable an username
+	 */
+	public function disableUser(string $username): string
 	{
 		$this->db[$username]['password'] = '!';
 		$this->save();
 		return $username;
 	}
 
-	/*	Create a new user === Bludit v4
-
-		@args			array			The array $args supports all the keys from the variable $dbFields. If you don't pass all the keys, the default values are used.
-		@return		string/bool	Returns the username if the user is successfully created, FALSE otherwise
-	*/
+	/**
+	 * Create a new user. === Bludit v4
+	 * @param array $args All supported parameters are defined in this class, variable $dbFields
+	 * @return string|bool Returns the username on successful create, FALSE otherwise
+	 */
 	public function add($args)
 	{
 		// The username is store as key and not as field
@@ -110,11 +108,11 @@ class Users extends dbJSON {
 		return $username;
 	}
 
-	/*	Edit an user === Bludit v4
-
-		@args			array			The array $args supports all the keys from the variable $dbFields. If you don't pass all the keys, the default values are used.
-		@return			string/bool		Returns the username if the user is successfully created, FALSE otherwise
-	*/
+	/**
+	 * Edit an user. === Bludit v4
+	 * @param		array		$args		All supported parameters are defined in this class, variable $dbFields
+	 * @return		string|bool				Returns the username on successful edit, FALSE otherwise
+	 */
 	public function edit($args)
 	{
 		// The username is store as key and not as field
@@ -157,7 +155,11 @@ class Users extends dbJSON {
 		return $username;
 	}
 
-	// Delete an user
+	/**
+	 * Delete an user. === Bludit v4
+	 * @param		string		$username	Username to be delete
+	 * @return		string|bool				Returns true or false
+	 */
 	public function delete($username)
 	{
 		unset($this->db[$username]);
@@ -186,16 +188,16 @@ class Users extends dbJSON {
 
 	public function setRememberToken($username, $token)
 	{
-		$args['username']	= $username;
-		$args['tokenRemember']	= $token;
-		return $this->set($args);
+		$args['username'] = $username;
+		$args['tokenRemember'] = $token;
+		return $this->edit($args);
 	}
 
 	// Change user password
 	// args => array( username, password )
 	public function setPassword($args)
 	{
-		return $this->set($args);
+		return $this->edit($args);
 	}
 
 	// Return the username associated to an email, FALSE otherwise

+ 24 - 13
bl-plugins/api/plugin.php

@@ -587,8 +587,8 @@ class pluginAPI extends Plugin {
 
 	/**
 	 * Edit settings
-	 * @param		array		$args		All supported keys are defined in the class site.class.php variable $dbFields
-	 * @return		array
+	 * @param			array			$args			All supported parameters are defined in the class site.class.php, variable $dbFields
+	 * @return			array
 	 */
 	private function editSettings($args)
 	{
@@ -956,24 +956,35 @@ class pluginAPI extends Plugin {
 		$files = array();
 		$listFiles = Filesystem::listFiles($path, '*', '*', $sortByDate, $chunk);
 		foreach ($listFiles as $file) {
-			$filename = basename($file);
-			$absoluteURL = DOMAIN_UPLOADS_PAGES.$pageKey.DS.$filename;
-			$absolutePath = $file;
+			if (Text::stringContains($file, '-thumbnail-')) {
+				continue;
+			}
+
+			$filename = Filesystem::filename($file);
+			$fileExtension = Filesystem::extension($file);
+			$absoluteURL = DOMAIN_UPLOADS_PAGES.$pageKey.DS.$filename.'.'.$fileExtension;
+			$absolutePath = PATH_UPLOADS_PAGES.$pageKey.DS.$filename.'.'.$fileExtension;
+
+			$thumbnailSmall = '';
+			if (Filesystem::fileExists(PATH_UPLOADS_PAGES.$pageKey.DS.$filename.'-thumbnail-s.'.$fileExtension)) {
+				$thumbnailSmall = DOMAIN_UPLOADS_PAGES.$pageKey.DS.$filename.'-thumbnail-s.'.$fileExtension;
+			}
+
+			$thumbnailMedium = '';
+			if (Filesystem::fileExists(PATH_UPLOADS_PAGES.$pageKey.DS.$filename.'-thumbnail-m.'.$fileExtension)) {
+				$thumbnailMedium = DOMAIN_UPLOADS_PAGES.$pageKey.DS.$filename.'-thumbnail-m.'.$fileExtension;
+			}
+
 			$data = array(
-				'filename'=>$filename,
+				'filename'=>$filename.'.'.$fileExtension,
 				'absolutePath'=>$absolutePath,
 				'absoluteURL'=>$absoluteURL,
 				'mime'=>Filesystem::mimeType($absolutePath),
 				'size'=>Filesystem::getSize($absolutePath),
-				'thumbnail'=>''
+				'thumbnailSmall'=>$thumbnailSmall,
+				'thumbnailMedium'=>$thumbnailMedium
 			);
 
-			// Check if thumbnail exists for the file
-			$thumbnail = $path.'thumbnails'.DS.$filename;
-			if (Filesystem::fileExists($thumbnail)) {
-				$data['thumbnail'] = $thumbnail;
-			}
-
 			array_push($files, $data);
 		}
 

+ 1 - 1
bl-plugins/latest-pages/plugin.php

@@ -13,7 +13,7 @@ class pluginLatestPages extends Plugin {
 		$tmp = $pages->getList(1, 5);
 		foreach ($tmp as $key) {
 			$page = buildPage($key);
-			$html .= '<a href="'.$page->permalink().'" class="list-group-item list-group-item-action d-flex gap-3 py-3" aria-current="true">';
+			$html .= '<a target="_blank" href="'.$page->permalink().'" class="list-group-item list-group-item-action d-flex gap-3 py-3" aria-current="true">';
 			$html .= '<div class="d-flex gap-2 w-100 justify-content-between">';
 			$html .= '<div>';
 			$html .= '<h6 class="mb-0">'.($page->title() ? $page->title() : '<span class="text-muted">' . $L->g('Empty title') . '</span> ').'</h6>';

+ 2 - 2
bl-plugins/search/vendors/fuzz.php

@@ -205,8 +205,8 @@ class Fuzz
      */
     public function getJaroWinkler($first, $second)
     {
-        $shorter;
-        $longer;
+        $shorter = '';
+        $longer = '';
 
         if (mb_strlen($first, CHARSET) > mb_strlen($second, CHARSET)) {
             $longer = mb_strtolower($first, CHARSET);

+ 18 - 7
install.php

@@ -62,7 +62,6 @@ define('PATH_WORKSPACES',		PATH_CONTENT . 'workspaces' . DS);
 define('PATH_DATABASES',		PATH_CONTENT . 'databases' . DS);
 define('PATH_PLUGINS_DATABASES', PATH_CONTENT . 'databases' . DS . 'plugins' . DS);
 define('PATH_UPLOADS_PROFILES',	PATH_UPLOADS . 'profiles' . DS);
-define('PATH_UPLOADS_THUMBNAILS', PATH_UPLOADS . 'thumbnails' . DS);
 define('PATH_UPLOADS_PAGES',	PATH_UPLOADS . 'pages' . DS);
 define('PATH_HELPERS',			PATH_KERNEL . 'helpers' . DS);
 define('PATH_ABSTRACT',			PATH_KERNEL . 'abstract' . DS);
@@ -275,7 +274,7 @@ function install($adminPassword, $timezone)
 	}
 
 	// Directories for initial plugins
-	$pluginsToInstall = array('tinymce', 'about', 'welcome', 'api', 'visits-stats', 'robots', 'canonical', 'popeye');
+	$pluginsToInstall = array('tinymce', 'about', 'welcome', 'api', 'visits-stats', 'robots', 'canonical', 'popeye', 'latest-pages');
 	foreach ($pluginsToInstall as $plugin) {
 		if (!mkdir(PATH_PLUGINS_DATABASES . $plugin, DIR_PERMISSIONS, true)) {
 			$errorText = 'Error when trying to created the directory=>' . PATH_PLUGINS_DATABASES . $plugin;
@@ -284,7 +283,7 @@ function install($adminPassword, $timezone)
 	}
 
 	// System directories
-	$systemDirectories = array(PATH_UPLOADS_PROFILES, PATH_UPLOADS_THUMBNAILS, PATH_TMP, PATH_WORKSPACES, PATH_UPLOADS_PAGES);
+	$systemDirectories = array(PATH_UPLOADS_PROFILES, PATH_TMP, PATH_WORKSPACES, PATH_UPLOADS_PAGES);
 	foreach ($systemDirectories as $directory) {
 		if (!mkdir($directory, DIR_PERMISSIONS, true)) {
 			$errorText = 'Error when trying to created the directory=>' . $directory;
@@ -388,9 +387,9 @@ function install($adminPassword, $timezone)
 		'titleFormatTag' => '{{tag-name}} | {{site-title}}',
 		'imageRestrict' => true,
 		'imageRelativeToAbsolute' => false,
-		'thumbnailWidth' => 400,
-		'thumbnailHeight' => 400,
-		'thumbnailQuality' => 100,
+		'thumbnailSmallWidth' => 400,
+		'thumbnailSmallHeight' => 400,
+		'thumbnailSmallQuality' => 100,
 		'logo' => '',
 		'markdownParser' => true,
 		'customFields' => '{}',
@@ -548,12 +547,24 @@ function install($adminPassword, $timezone)
 		LOCK_EX
 	);
 
+	// File plugins/latest-pages/db.php
+	file_put_contents(
+		PATH_PLUGINS_DATABASES . 'latest-pages' . DS . 'db.php',
+		$dataHead . json_encode(
+			array(
+				'position' => 2
+			),
+			JSON_PRETTY_PRINT
+		),
+		LOCK_EX
+	);
+
 	// File plugins/visits-stats/db.php
 	file_put_contents(
 		PATH_PLUGINS_DATABASES . 'visits-stats' . DS . 'db.php',
 		$dataHead . json_encode(
 			array(
-				'position' => 2,
+				'position' => 3,
 				'excludeAdmins' => false,
 				'label' => $L->get('Visits')
 			),

+ 2 - 2
phpstan.neon

@@ -1,9 +1,9 @@
 parameters:
-    phpVersion: 70100 # PHP 7.1 - https://github.com/phpstan/phpstan/blob/master/playground-api/handler.ts#L38
+    #phpVersion: 70100 # PHP 7.1 - https://github.com/phpstan/phpstan/blob/master/playground-api/handler.ts#L38
     #phpVersion: 70200 # PHP 7.2
     #phpVersion: 70300 # PHP 7.3
     #phpVersion: 70400 # PHP 7.4
-    #phpVersion: 80000 # PHP 8.0
+    phpVersion: 80000 # PHP 8.0
     level: 0
     excludePaths:
         analyse:

部分文件因为文件数量过多而无法显示