- new blog cms

This commit is contained in:
Ty Clifford
2026-07-04 19:41:44 -04:00
parent 64cb76a846
commit 782b983bf4
819 changed files with 3103 additions and 88735 deletions
Vendored
BIN
View File
Binary file not shown.
+6
View File
@@ -0,0 +1,6 @@
/storage/database/*.sqlite
/storage/database/*.sqlite-*
/storage/stats/*.csv
/storage/cache/*
!.gitkeep
.DS_Store
+20
View File
@@ -0,0 +1,20 @@
Options -Indexes
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /
RewriteRule ^(app|cli|config|storage)(/|$) - [F,L]
RewriteRule ^bl-content/(databases|pages|tmp)(/|$) - [F,L]
RewriteCond %{REQUEST_FILENAME} -f [OR]
RewriteCond %{REQUEST_FILENAME} -d
RewriteRule ^ - [L]
RewriteRule ^(.+)$ index.php?route=$1 [QSA,L]
RewriteRule ^$ index.php [L]
</IfModule>
<FilesMatch "\.(sqlite|db|csv|json|lock)$">
Require all denied
</FilesMatch>
-21
View File
@@ -1,21 +0,0 @@
The MIT License (MIT)
Copyright (c) 2015-2023 Diego Najar
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+35 -72
View File
@@ -1,92 +1,55 @@
# [Bludit](https://www.bludit.com/)
# Neon Notes CMS
[![English](https://img.shields.io/badge/Language-English-blue.svg)](README.md)
[![Español](https://img.shields.io/badge/Language-Español-green.svg)](README.es.md)
[![العربية](https://img.shields.io/badge/Language-العربية-yellow.svg)](README.ar.md)
[![中文](https://img.shields.io/badge/Language-中文-red.svg)](README.zh.md)
[![Français](https://img.shields.io/badge/Language-Français-purple.svg)](README.fr.md)
[![Deutsch](https://img.shields.io/badge/Language-Deutsch-orange.svg)](README.de.md)
[![हिंदी](https://img.shields.io/badge/Language-हिंदी-lightblue.svg)](README.hi.md)
[![日本語](https://img.shields.io/badge/Language-日本語-pink.svg)](README.ja.md)
[![Português](https://img.shields.io/badge/Language-Português-darkgreen.svg)](README.pt.md)
[![Русский](https://img.shields.io/badge/Language-Русский-cyan.svg)](README.ru.md)
Neon Notes is a small PHP blog CMS with a Bludit-style content layout:
Bludit the Simple, Fast, and Flexible CMS.
```text
bl-content/pages/<slug>/index.txt
```
With Bludit, you can build your own website or blog in just seconds. Its completely free, open-source, and easy to use. Bludit stores content in JSON format, eliminating the need for database installation or configuration. All you need is a web server with PHP support.
As a Flat-File CMS, Bludit offers unparalleled flexibility and speed. Plus, with support for both Markdown and HTML code, creating and managing content has never been easier.
## Resources
- [Plugins](https://plugins.bludit.com)
- [Themes](https://themes.bludit.com)
- [Documentation](https://docs.bludit.com)
- News and announcement on [Twitter](https://twitter.com/bludit), [Facebook](https://www.facebook.com/bluditcms), and [Reddit](https://www.reddit.com/r/bludit/)
- Talk & Chat on [Discord](https://discord.gg/CFaXEdZWds)
- Help and Support on [Forum](https://forum.bludit.org)
- Bugs reports on [Github Issues](https://github.com/bludit/bludit/issues)
Posts and static pages are Markdown `.txt` files with front matter. Comments are stored in SQLite, site configuration and generated indexes are JSON, and visit stats are CSV for easy Excel import.
## Requirements
- Webserver with PHP support.
- PHP v8.0 or higher version.
- PHP [mbstring](http://php.net/manual/en/book.mbstring.php) module for full UTF-8 support.
- PHP [gd](http://php.net/manual/en/book.image.php) module for image processing.
- PHP [dom](http://php.net/manual/en/book.dom.php) module for DOM manipulation.
- PHP [json](http://php.net/manual/en/book.json.php) module for JSON manipulation.
- PHP 8.1 or newer
- PDO SQLite enabled
- Apache with `mod_rewrite` for `.htaccess` pretty URLs, or the PHP built-in server for local testing
## Installation
1. Download the latest version from the official page: [Bludit.com](https://www.bludit.com)
2. Extract the zip file into a directory, such as `bludit`.
3. Upload the `bludit` directory to your web server or hosting.
4. Visit your domain (e.g., https://example.com/bludit/).
5. Follow the Bludit Installer to set up your website.
> **Note — running behind Cloudflare, nginx, Apache or another reverse proxy:** Bludit reads the client IP from `REMOTE_ADDR` only. Proxy headers like `X-Forwarded-For` or `CF-Connecting-IP` are **not** trusted at the PHP layer because any client can forge them. If your site sits behind a reverse proxy, configure the webserver to rewrite `REMOTE_ADDR` to the real client IP before PHP sees it — otherwise the login brute-force blocklist will block the proxy itself (locking out every visitor after enough failed logins) and visitor statistics will count all traffic as a single visitor.
>
> - **Apache** — enable [`mod_remoteip`](https://httpd.apache.org/docs/current/mod/mod_remoteip.html) and list your upstream's IP ranges with `RemoteIPTrustedProxy`. For Cloudflare, use the header `CF-Connecting-IP` and the IP ranges published at [cloudflare.com/ips-v4](https://www.cloudflare.com/ips-v4) and [cloudflare.com/ips-v6](https://www.cloudflare.com/ips-v6).
> - **nginx** — use the built-in [`ngx_http_realip_module`](https://nginx.org/en/docs/http/ngx_http_realip_module.html) with `set_real_ip_from` + `real_ip_header`.
>
> No configuration is needed for direct / localhost / intranet deployments.
## Quick installation for testing
You can use PHP Built-in web server (`php -S localhost:8000`) or Docker:
## Local Server
```bash
docker pull bludit/docker:latest
docker run -d --name bludit -p 8000:80 bludit/docker:latest
php -S 127.0.0.1:8080 router.php
```
Then open http://localhost:8000
## CLI
## Upgrading Bludit
```bash
php cli/blog.php help
php cli/blog.php list --all
php cli/blog.php create "New Post" --status=published --category=Notes --tags=php,markdown
php cli/blog.php update hello-neon --title="Hello Neon"
php cli/blog.php delete old-slug
php cli/blog.php rebuild
```
The following steps are valid for upgrading to any version of Bludit from the same major version. The major version is the first digit in the version number, for example, Bludit v3.x.
## Comments
1. **Make a full backup**, no discussion here, entire backup of the files and directories. That means copy ALL files into a new folder.
2. **Remember which version of Bludit you are using** for a possible roll-back.
3. **Download the latest version** from the [official page](https://www.bludit.com).
4. **Extract the zip file**.
5. **Replace existing files** with the new files.
6. **Clean your browser cache**, and please read the note below.
7. **Log into the admin area** and check your settings.
8. Done.
Comments are moderated by default and protected by captcha plus a honeypot field.
> **Note:** If your website is behind some kind of server cache system (for example, Cloudflare provides one by default) you need to purge the files there, too. It's also a good idea to clear your browser cache. Bludit tries to reload the files with the new ones, but some components like TinyMCE may not reload, and provoke some issues in the UI or throw out JavaScript errors.
```bash
php cli/blog.php comments:list --status=pending
php cli/blog.php comments:approve 1
php cli/blog.php comments:spam 2
php cli/blog.php comments:delete 3
```
## Support Bludit
## Stats
Bludit is open-source and free to use, but if you find the project useful and would like to support its development, you can contribute on [Patreon](https://www.patreon.com/join/bludit). As a token of our appreciation, supporters will receive Bludit PRO.
Visits are appended to `storage/stats/visits.csv` with a header row suitable for spreadsheet import.
If you prefer, you can also make a one-time donation to buy us a coffee or beer. Every contribution helps us continue to improve Bludit and provide the best possible experience for our users.
```bash
php cli/blog.php stats:summary
```
- [PayPal](https://www.paypal.me/bludit/10)
- BTC (Bitcoin): [bc1qtets5pdj73uyysjpegfh2gar4pfywra4rglcph](https://www.blockchain.com/explorer/addresses/btc/bc1qtets5pdj73uyysjpegfh2gar4pfywra4rglcph)
- ETH (Ethereum): [0x0d7D58D848aA5f175D75Ce4bC746bAC107f331b7](https://www.blockchain.com/explorer/addresses/eth/0x0d7D58D848aA5f175D75Ce4bC746bAC107f331b7)
## Apache
## License
Bludit is open source software licensed under the [MIT license](https://tldrlegal.com/license/mit-license).
The included `.htaccess` enables pretty URLs and blocks direct access to app code, configuration, SQLite, CSV, JSON databases, and Markdown content files.
-53
View File
@@ -1,53 +0,0 @@
# Security Policy
## Reporting a Vulnerability
If you believe you have found a security vulnerability in Bludit, please **do
not** open a public GitHub issue, post in discussions, or disclose it on social
media before it has been addressed.
Instead, report it privately through GitHub's Private Vulnerability Reporting:
**https://github.com/bludit/bludit/security/advisories/new**
This opens a private advisory visible only to the Bludit maintainers.
When reporting, please include:
- A clear description of the vulnerability and its impact.
- Steps to reproduce, including a proof of concept if possible.
- The Bludit version, PHP version, and webserver where you reproduced it.
- Any suggested mitigation or patch.
## What to Expect
- We aim to acknowledge new reports within **5 business days**.
- We will keep you informed as we investigate and work on a fix.
- Once a fix is released, we will publish a GitHub Security Advisory crediting
the reporter (unless you prefer to remain anonymous) and, where appropriate,
request a CVE.
## Supported Versions
Security fixes are provided for the **latest stable release** of Bludit on the
`master` branch. Older versions are not supported — please upgrade before
reporting issues against them.
## Scope
In scope:
- The Bludit core (`bl-kernel/`, `bl-plugins/` shipped with core, `bl-themes/`
shipped with core).
- The official admin interface.
Out of scope:
- Third-party plugins and themes not maintained in this repository.
- Issues that require an already-compromised admin account or server.
- Denial of service, rate-limiting, and brute-force concerns — these should be
handled at the infrastructure layer (reverse proxy / WAF / Cloudflare).
- Self-XSS, missing security headers without a demonstrated impact, and other
best-practice findings without a concrete exploit.
Thank you for helping keep Bludit and its users safe.
+360
View File
@@ -0,0 +1,360 @@
<?php
declare(strict_types=1);
namespace NeonBlog;
use Throwable;
final class App
{
private Config $config;
private ContentRepository $repository;
private CommentService $comments;
private Stats $stats;
public function __construct(Config $config, ContentRepository $repository, CommentService $comments, Stats $stats)
{
$this->config = $config;
$this->repository = $repository;
$this->comments = $comments;
$this->stats = $stats;
}
public function handle(): void
{
try {
$path = $this->path();
if (($_SERVER['REQUEST_METHOD'] ?? 'GET') === 'POST' && preg_match('#^comment/([a-z0-9-]+)$#', $path, $matches)) {
$this->handleComment($matches[1]);
return;
}
if (($_SERVER['REQUEST_METHOD'] ?? 'GET') !== 'GET') {
$this->render('error', ['title' => 'Method not allowed', 'message' => 'This route only accepts GET requests.'], 405, 'error');
return;
}
$this->route($path);
} catch (Throwable $error) {
$this->render('error', [
'title' => 'Server error',
'message' => 'Something went wrong while rendering this page.',
'debug' => $error->getMessage(),
], 500, 'error');
}
}
public function repository(): ContentRepository
{
return $this->repository;
}
public function comments(): CommentService
{
return $this->comments;
}
public function stats(): Stats
{
return $this->stats;
}
public function config(): Config
{
return $this->config;
}
private function route(string $path): void
{
if ($path === '' || $path === 'home') {
$this->home(1);
return;
}
if (preg_match('#^page/(\d+)$#', $path, $matches)) {
$this->home((int) $matches[1]);
return;
}
if ($path === 'search') {
$this->search();
return;
}
if ($path === 'feed.xml') {
$this->feed();
return;
}
if ($path === 'sitemap.xml') {
$this->sitemap();
return;
}
if (preg_match('#^post/([a-z0-9-]+)$#', $path, $matches)) {
$this->single($matches[1], 'post');
return;
}
if (preg_match('#^category/([a-z0-9-]+)(?:/page/(\d+))?$#', $path, $matches)) {
$this->taxonomy('category', $matches[1], isset($matches[2]) ? (int) $matches[2] : 1);
return;
}
if (preg_match('#^tag/([a-z0-9-]+)(?:/page/(\d+))?$#', $path, $matches)) {
$this->taxonomy('tag', $matches[1], isset($matches[2]) ? (int) $matches[2] : 1);
return;
}
if (preg_match('#^archive/(\d{4})(?:/(\d{2}))?(?:/page/(\d+))?$#', $path, $matches)) {
$this->archive((int) $matches[1], isset($matches[2]) ? (int) $matches[2] : null, isset($matches[3]) ? (int) $matches[3] : 1);
return;
}
$item = $this->repository->find($path);
if ($item !== null) {
$this->single($path, (string) $item['type']);
return;
}
$this->render('error', ['title' => 'Not found', 'message' => 'That page does not exist.'], 404, 'not_found');
}
private function home(int $page): void
{
$perPage = (int) $this->config->get('posts_per_page', 6);
$result = $this->repository->paginate($this->repository->posts(false), $page, $perPage);
$this->render('listing', [
'title' => (string) $this->config->get('site.title'),
'heading' => (string) $this->config->get('site.tagline'),
'items' => $result['items'],
'pagination' => $result['pagination'],
'pagination_url' => static fn (int $target): string => $target === 1 ? View::url('') : View::url('page/' . $target),
], 200, 'home');
}
private function search(): void
{
$query = trim((string) ($_GET['q'] ?? ''));
$page = max(1, (int) ($_GET['page'] ?? 1));
$perPage = (int) $this->config->get('posts_per_page', 6);
$result = $this->repository->search($query, $page, $perPage);
$this->render('listing', [
'title' => $query === '' ? 'Search' : 'Search: ' . $query,
'heading' => $query === '' ? 'Search the archive' : 'Search results for "' . $query . '"',
'items' => $result['items'],
'pagination' => $result['pagination'],
'search_query' => $query,
'empty_message' => $query === '' ? 'Enter a word or phrase to search posts and pages.' : 'No matching posts were found.',
'pagination_url' => static fn (int $target): string => View::url('search', ['q' => $query, 'page' => $target]),
], 200, 'search');
}
private function taxonomy(string $type, string $slug, int $page): void
{
$items = $type === 'category' ? $this->repository->byCategory($slug) : $this->repository->byTag($slug);
$label = $this->termLabel($items, $type, $slug);
$perPage = (int) $this->config->get('posts_per_page', 6);
$result = $this->repository->paginate($items, $page, $perPage);
$this->render('listing', [
'title' => ucfirst($type) . ': ' . $label,
'heading' => ucfirst($type) . ': ' . $label,
'items' => $result['items'],
'pagination' => $result['pagination'],
'empty_message' => 'No posts live here yet.',
'pagination_url' => static fn (int $target): string => $target === 1
? View::url($type . '/' . $slug)
: View::url($type . '/' . $slug . '/page/' . $target),
], 200, $type);
}
private function archive(int $year, ?int $month, int $page): void
{
$items = $this->repository->byArchive($year, $month);
$perPage = (int) $this->config->get('posts_per_page', 6);
$result = $this->repository->paginate($items, $page, $perPage);
$label = $month === null ? (string) $year : date('F Y', strtotime(sprintf('%04d-%02d-01', $year, $month)));
$base = $month === null ? 'archive/' . $year : sprintf('archive/%04d/%02d', $year, $month);
$this->render('listing', [
'title' => 'Archive: ' . $label,
'heading' => 'Archive: ' . $label,
'items' => $result['items'],
'pagination' => $result['pagination'],
'empty_message' => 'No posts were published in this window.',
'pagination_url' => static fn (int $target): string => $target === 1 ? View::url($base) : View::url($base . '/page/' . $target),
], 200, 'archive');
}
private function single(string $slug, string $type): void
{
$item = $this->repository->find($slug);
if ($item === null || $item['type'] !== $type) {
$this->render('error', ['title' => 'Not found', 'message' => 'That content is not published.'], 404, 'not_found');
return;
}
$approved = $this->comments->approvedFor((string) $item['slug']);
$captcha = $this->config->get('comments.captcha', true) ? Captcha::challenge() : ['token' => '', 'question' => ''];
$this->render('single', [
'title' => $item['title'],
'item' => $item,
'approved_comments' => $approved,
'captcha' => $captcha,
], 200, (string) $item['type'], (string) $item['slug']);
}
private function feed(): void
{
$base = $this->config->baseUrl();
$items = array_slice($this->repository->posts(false), 0, 20);
$xml = [
'<?xml version="1.0" encoding="UTF-8"?>',
'<rss version="2.0">',
'<channel>',
'<title>' . $this->xml((string) $this->config->get('site.title')) . '</title>',
'<link>' . $this->xml($base . View::url('')) . '</link>',
'<description>' . $this->xml((string) $this->config->get('site.description')) . '</description>',
];
foreach ($items as $item) {
$xml[] = '<item>';
$xml[] = '<title>' . $this->xml((string) $item['title']) . '</title>';
$xml[] = '<link>' . $this->xml($base . View::itemUrl($item)) . '</link>';
$xml[] = '<guid>' . $this->xml($base . View::itemUrl($item)) . '</guid>';
$xml[] = '<pubDate>' . gmdate(DATE_RSS, strtotime((string) $item['date']) ?: time()) . '</pubDate>';
$xml[] = '<description>' . $this->xml((string) $item['summary']) . '</description>';
$xml[] = '</item>';
}
$xml[] = '</channel>';
$xml[] = '</rss>';
$this->respond(implode("\n", $xml), 'application/rss+xml; charset=utf-8', 200, 'feed');
}
private function sitemap(): void
{
$base = $this->config->baseUrl();
$xml = [
'<?xml version="1.0" encoding="UTF-8"?>',
'<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">',
'<url><loc>' . $this->xml($base . View::url('')) . '</loc></url>',
];
foreach ($this->repository->all(false) as $item) {
$xml[] = '<url>';
$xml[] = '<loc>' . $this->xml($base . View::itemUrl($item)) . '</loc>';
$xml[] = '<lastmod>' . $this->xml(date('Y-m-d', strtotime((string) $item['modified']) ?: time())) . '</lastmod>';
$xml[] = '</url>';
}
$xml[] = '</urlset>';
$this->respond(implode("\n", $xml), 'application/xml; charset=utf-8', 200, 'sitemap');
}
private function handleComment(string $slug): void
{
$item = $this->repository->find($slug);
if ($item === null || !$item['allow_comments'] || !$this->config->get('comments.enabled', true)) {
$this->flash('error', 'Comments are closed for that post.');
$this->redirect(View::url(''));
return;
}
if ($this->config->get('comments.captcha', true)) {
$valid = Captcha::verify((string) ($_POST['captcha_token'] ?? ''), (string) ($_POST['captcha_answer'] ?? ''));
if (!$valid) {
$this->flash('error', 'The captcha answer was not correct.');
$this->redirect(View::itemUrl($item) . '#comments');
return;
}
}
$result = $this->comments->add((string) $item['slug'], $_POST);
$this->flash($result['ok'] ? 'success' : 'error', $result['message']);
$this->redirect(View::itemUrl($item) . '#comments');
}
/** @param array<string, mixed> $data */
private function render(string $template, array $data, int $statusCode, string $routeType, string $slug = ''): void
{
http_response_code($statusCode);
header('Content-Type: text/html; charset=utf-8');
$theme = preg_replace('/[^a-z0-9_-]/', '', (string) $this->config->get('theme', 'neon')) ?: 'neon';
$layout = BLOG_ROOT . '/themes/' . $theme . '/layout.php';
$config = $this->config;
$repository = $this->repository;
$facets = $this->repository->facets();
$navPages = $this->repository->pages(false);
usort($navPages, static fn (array $a, array $b): int => ((int) $a['menu_order']) <=> ((int) $b['menu_order']));
$flash = $this->consumeFlash();
extract($data, EXTR_SKIP);
ob_start();
require $layout;
$body = (string) ob_get_clean();
$this->stats->record($routeType, $slug, $statusCode);
echo $body;
}
private function respond(string $body, string $contentType, int $statusCode, string $routeType): void
{
http_response_code($statusCode);
header('Content-Type: ' . $contentType);
$this->stats->record($routeType, '', $statusCode);
echo $body;
}
private function path(): string
{
$route = (string) ($_GET['route'] ?? '');
if ($route === '') {
$route = (string) parse_url((string) ($_SERVER['REQUEST_URI'] ?? '/'), PHP_URL_PATH);
}
$scriptDir = str_replace('\\', '/', dirname((string) ($_SERVER['SCRIPT_NAME'] ?? '')));
$route = '/' . ltrim($route, '/');
if ($scriptDir !== '/' && str_starts_with($route, $scriptDir . '/')) {
$route = substr($route, strlen($scriptDir));
}
return trim($route, '/');
}
private function termLabel(array $items, string $type, string $slug): string
{
foreach ($items as $item) {
if ($type === 'category') {
return (string) $item['category'];
}
foreach ($item['tags'] as $tag) {
if (ContentRepository::slugify((string) $tag) === $slug) {
return (string) $tag;
}
}
}
return ucwords(str_replace('-', ' ', $slug));
}
private function flash(string $type, string $message): void
{
if (session_status() === PHP_SESSION_ACTIVE) {
$_SESSION['flash'] = ['type' => $type, 'message' => $message];
}
}
/** @return array{type: string, message: string}|null */
private function consumeFlash(): ?array
{
if (session_status() !== PHP_SESSION_ACTIVE || !isset($_SESSION['flash'])) {
return null;
}
$flash = $_SESSION['flash'];
unset($_SESSION['flash']);
return is_array($flash) ? $flash : null;
}
private function redirect(string $url): void
{
header('Location: ' . $url, true, 303);
}
private function xml(string $value): string
{
return htmlspecialchars($value, ENT_XML1 | ENT_COMPAT, 'UTF-8');
}
}
+43
View File
@@ -0,0 +1,43 @@
<?php
declare(strict_types=1);
namespace NeonBlog;
final class Captcha
{
/** @return array{token: string, question: string} */
public static function challenge(): array
{
if (session_status() !== PHP_SESSION_ACTIVE) {
return ['token' => '', 'question' => ''];
}
$a = random_int(2, 12);
$b = random_int(2, 12);
$token = bin2hex(random_bytes(12));
$_SESSION['captcha'][$token] = [
'answer' => (string) ($a + $b),
'expires' => time() + 900,
];
return [
'token' => $token,
'question' => 'What is ' . $a . ' + ' . $b . '?',
];
}
public static function verify(string $token, string $answer): bool
{
if (session_status() !== PHP_SESSION_ACTIVE || $token === '') {
return false;
}
$challenge = $_SESSION['captcha'][$token] ?? null;
unset($_SESSION['captcha'][$token]);
if (!is_array($challenge) || (int) ($challenge['expires'] ?? 0) < time()) {
return false;
}
return trim($answer) === (string) ($challenge['answer'] ?? '');
}
}
+147
View File
@@ -0,0 +1,147 @@
<?php
declare(strict_types=1);
namespace NeonBlog;
use PDO;
final class CommentService
{
private PDO $pdo;
private Config $config;
public function __construct(PDO $pdo, Config $config)
{
$this->pdo = $pdo;
$this->config = $config;
}
/** @return array<int, array<string, mixed>> */
public function approvedFor(string $slug): array
{
$statement = $this->pdo->prepare(
'SELECT * FROM comments WHERE slug = :slug AND status = "approved" ORDER BY created_at ASC'
);
$statement->execute(['slug' => $slug]);
return $statement->fetchAll();
}
/** @return array{ok: bool, stored: bool, message: string} */
public function add(string $slug, array $data): array
{
$honeypot = (string) $this->config->get('comments.honeypot_field', 'website_url');
if (trim((string) ($data[$honeypot] ?? '')) !== '') {
return [
'ok' => true,
'stored' => false,
'message' => 'Thanks. Your comment is awaiting review.',
];
}
$author = trim((string) ($data['author'] ?? ''));
$email = trim((string) ($data['email'] ?? ''));
$website = trim((string) ($data['site_url'] ?? ''));
$body = trim((string) ($data['body'] ?? ''));
if ($author === '' || $email === '' || $body === '') {
return ['ok' => false, 'stored' => false, 'message' => 'Name, email, and comment are required.'];
}
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
return ['ok' => false, 'stored' => false, 'message' => 'Please enter a valid email address.'];
}
if ($website !== '' && !filter_var($website, FILTER_VALIDATE_URL)) {
return ['ok' => false, 'stored' => false, 'message' => 'Please enter a valid website URL or leave it blank.'];
}
if (mb_strlen($body) > 5000) {
return ['ok' => false, 'stored' => false, 'message' => 'Comments must stay under 5,000 characters.'];
}
$now = date('c');
$status = $this->config->get('comments.moderate', true) ? 'pending' : 'approved';
$statement = $this->pdo->prepare(
'INSERT INTO comments
(slug, author, email, website, body, status, ip_hash, user_agent, created_at, updated_at)
VALUES
(:slug, :author, :email, :website, :body, :status, :ip_hash, :user_agent, :created_at, :updated_at)'
);
$statement->execute([
'slug' => $slug,
'author' => $author,
'email' => $email,
'website' => $website,
'body' => $body,
'status' => $status,
'ip_hash' => $this->ipHash(),
'user_agent' => substr((string) ($_SERVER['HTTP_USER_AGENT'] ?? 'cli'), 0, 300),
'created_at' => $now,
'updated_at' => $now,
]);
return [
'ok' => true,
'stored' => true,
'message' => $status === 'approved'
? 'Thanks. Your comment is live.'
: 'Thanks. Your comment is awaiting review.',
];
}
/** @return array<int, array<string, mixed>> */
public function list(?string $status = null, ?string $slug = null): array
{
$where = [];
$params = [];
if ($status !== null) {
$where[] = 'status = :status';
$params['status'] = $status;
}
if ($slug !== null) {
$where[] = 'slug = :slug';
$params['slug'] = $slug;
}
$sql = 'SELECT * FROM comments';
if ($where !== []) {
$sql .= ' WHERE ' . implode(' AND ', $where);
}
$sql .= ' ORDER BY created_at DESC';
$statement = $this->pdo->prepare($sql);
$statement->execute($params);
return $statement->fetchAll();
}
public function setStatus(int $id, string $status): bool
{
if (!in_array($status, ['pending', 'approved', 'spam'], true)) {
return false;
}
$statement = $this->pdo->prepare('UPDATE comments SET status = :status, updated_at = :updated_at WHERE id = :id');
$statement->execute([
'id' => $id,
'status' => $status,
'updated_at' => date('c'),
]);
return $statement->rowCount() > 0;
}
public function delete(int $id): bool
{
$statement = $this->pdo->prepare('DELETE FROM comments WHERE id = :id');
$statement->execute(['id' => $id]);
return $statement->rowCount() > 0;
}
private function ipHash(): string
{
$ip = (string) ($_SERVER['REMOTE_ADDR'] ?? 'cli');
$salt = (string) $this->config->get('stats.hash_salt', 'change-me');
return hash_hmac('sha256', $ip, $salt);
}
}
+127
View File
@@ -0,0 +1,127 @@
<?php
declare(strict_types=1);
namespace NeonBlog;
final class Config
{
/** @var array<string, mixed> */
private array $data;
private string $path;
/** @param array<string, mixed> $data */
private function __construct(string $path, array $data)
{
$this->path = $path;
$this->data = array_replace_recursive(self::defaults(), $data);
}
public static function load(string $path): self
{
$data = [];
if (is_file($path)) {
$decoded = json_decode((string) file_get_contents($path), true);
if (is_array($decoded)) {
$data = $decoded;
}
}
return new self($path, $data);
}
/** @return mixed */
public function get(string $key, mixed $default = null): mixed
{
$segments = explode('.', $key);
$value = $this->data;
foreach ($segments as $segment) {
if (!is_array($value) || !array_key_exists($segment, $value)) {
return $default;
}
$value = $value[$segment];
}
return $value;
}
public function set(string $key, mixed $value): void
{
$segments = explode('.', $key);
$cursor = &$this->data;
foreach ($segments as $segment) {
if (!isset($cursor[$segment]) || !is_array($cursor[$segment])) {
$cursor[$segment] = [];
}
$cursor = &$cursor[$segment];
}
$cursor = $value;
}
/** @return array<string, mixed> */
public function all(): array
{
return $this->data;
}
public function save(): void
{
$dir = dirname($this->path);
if (!is_dir($dir)) {
mkdir($dir, 0775, true);
}
file_put_contents(
$this->path,
json_encode($this->data, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES) . PHP_EOL
);
}
public function baseUrl(): string
{
$configured = trim((string) $this->get('base_url', ''));
if ($configured !== '') {
return rtrim($configured, '/');
}
if (PHP_SAPI === 'cli') {
return '';
}
$https = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off')
|| (($_SERVER['HTTP_X_FORWARDED_PROTO'] ?? '') === 'https');
$scheme = $https ? 'https' : 'http';
$host = $_SERVER['HTTP_HOST'] ?? 'localhost';
return $scheme . '://' . $host;
}
/** @return array<string, mixed> */
private static function defaults(): array
{
return [
'site' => [
'title' => 'Neon Notes',
'tagline' => 'A fast file-first blog CMS',
'description' => 'A Markdown blog powered by PHP, SQLite, JSON, and plain files.',
'author' => 'Editor',
'language' => 'en',
],
'base_url' => '',
'timezone' => 'UTC',
'theme' => 'neon',
'posts_per_page' => 6,
'comments' => [
'enabled' => true,
'moderate' => true,
'captcha' => true,
'honeypot_field' => 'website_url',
],
'stats' => [
'enabled' => true,
'hash_salt' => 'change-me',
],
'social' => [],
];
}
}
+521
View File
@@ -0,0 +1,521 @@
<?php
declare(strict_types=1);
namespace NeonBlog;
use RuntimeException;
final class ContentRepository
{
private string $pagesRoot;
private string $indexPath;
public function __construct(string $pagesRoot, string $indexPath)
{
$this->pagesRoot = $pagesRoot;
$this->indexPath = $indexPath;
}
/** @return array<int, array<string, mixed>> */
public function posts(bool $includeDrafts = false): array
{
return $this->filterByType('post', $includeDrafts);
}
/** @return array<int, array<string, mixed>> */
public function pages(bool $includeDrafts = false): array
{
return $this->filterByType('page', $includeDrafts);
}
/** @return array<int, array<string, mixed>> */
public function all(bool $includeDrafts = false): array
{
$items = [];
if (!is_dir($this->pagesRoot)) {
return $items;
}
$directories = glob($this->pagesRoot . '/*', GLOB_ONLYDIR) ?: [];
foreach ($directories as $directory) {
$slug = basename($directory);
$file = $directory . '/index.txt';
if (!is_file($file)) {
continue;
}
$item = $this->read($slug, $file);
if (!$includeDrafts && $item['status'] !== 'published') {
continue;
}
$items[] = $item;
}
usort($items, static function (array $a, array $b): int {
$dateCompare = strcmp((string) $b['date'], (string) $a['date']);
if ($dateCompare !== 0) {
return $dateCompare;
}
return strcmp((string) $a['title'], (string) $b['title']);
});
return $items;
}
public function find(string $slug, bool $includeDrafts = false): ?array
{
$slug = self::slugify($slug);
$file = $this->pagesRoot . '/' . $slug . '/index.txt';
if (!is_file($file)) {
return null;
}
$item = $this->read($slug, $file);
if (!$includeDrafts && $item['status'] !== 'published') {
return null;
}
return $item;
}
/** @return array{items: array<int, array<string, mixed>>, pagination: array<string, int|bool|null>} */
public function paginate(array $items, int $page, int $perPage): array
{
$total = count($items);
$pages = max(1, (int) ceil($total / max(1, $perPage)));
$page = max(1, min($page, $pages));
$offset = ($page - 1) * $perPage;
return [
'items' => array_slice($items, $offset, $perPage),
'pagination' => [
'page' => $page,
'per_page' => $perPage,
'total' => $total,
'pages' => $pages,
'has_previous' => $page > 1,
'has_next' => $page < $pages,
'previous' => $page > 1 ? $page - 1 : null,
'next' => $page < $pages ? $page + 1 : null,
],
];
}
/** @return array{items: array<int, array<string, mixed>>, pagination: array<string, int|bool|null>} */
public function search(string $query, int $page, int $perPage): array
{
$query = trim(mb_strtolower($query));
if ($query === '') {
return $this->paginate([], $page, $perPage);
}
$terms = preg_split('/\s+/', $query) ?: [];
$scored = [];
foreach ($this->all(false) as $item) {
$haystack = mb_strtolower(
$item['title'] . ' ' .
$item['summary'] . ' ' .
$item['category'] . ' ' .
implode(' ', $item['tags']) . ' ' .
strip_tags((string) $item['markdown'])
);
$score = 0;
foreach ($terms as $term) {
if ($term === '') {
continue;
}
$score += substr_count($haystack, $term);
if (str_contains(mb_strtolower((string) $item['title']), $term)) {
$score += 4;
}
}
if ($score > 0) {
$item['search_score'] = $score;
$scored[] = $item;
}
}
usort($scored, static function (array $a, array $b): int {
$scoreCompare = ((int) $b['search_score']) <=> ((int) $a['search_score']);
if ($scoreCompare !== 0) {
return $scoreCompare;
}
return strcmp((string) $b['date'], (string) $a['date']);
});
return $this->paginate($scored, $page, $perPage);
}
/** @return array<int, array<string, mixed>> */
public function byCategory(string $categorySlug): array
{
return array_values(array_filter($this->posts(false), static function (array $item) use ($categorySlug): bool {
return self::slugify((string) $item['category']) === $categorySlug;
}));
}
/** @return array<int, array<string, mixed>> */
public function byTag(string $tagSlug): array
{
return array_values(array_filter($this->posts(false), static function (array $item) use ($tagSlug): bool {
foreach ($item['tags'] as $tag) {
if (self::slugify((string) $tag) === $tagSlug) {
return true;
}
}
return false;
}));
}
/** @return array<int, array<string, mixed>> */
public function byArchive(int $year, ?int $month = null): array
{
return array_values(array_filter($this->posts(false), static function (array $item) use ($year, $month): bool {
$timestamp = strtotime((string) $item['date']) ?: 0;
if ((int) date('Y', $timestamp) !== $year) {
return false;
}
return $month === null || (int) date('m', $timestamp) === $month;
}));
}
/** @return array{categories: array<string, int>, tags: array<string, int>, archives: array<string, int>} */
public function facets(): array
{
$categories = [];
$tags = [];
$archives = [];
foreach ($this->posts(false) as $item) {
$category = (string) $item['category'];
$categories[$category] = ($categories[$category] ?? 0) + 1;
foreach ($item['tags'] as $tag) {
$tags[(string) $tag] = ($tags[(string) $tag] ?? 0) + 1;
}
$archive = date('Y-m', strtotime((string) $item['date']) ?: time());
$archives[$archive] = ($archives[$archive] ?? 0) + 1;
}
ksort($categories);
ksort($tags);
krsort($archives);
return [
'categories' => $categories,
'tags' => $tags,
'archives' => $archives,
];
}
/** @param array<string, mixed> $metadata */
public function save(string $slug, array $metadata, string $markdown): array
{
$slug = self::slugify($slug !== '' ? $slug : (string) ($metadata['title'] ?? 'untitled'));
if ($slug === '') {
throw new RuntimeException('A title or slug is required.');
}
$directory = $this->pagesRoot . '/' . $slug;
if (!is_dir($directory)) {
mkdir($directory, 0775, true);
}
$existing = $this->find($slug, true);
$now = date('c');
$metadata = array_replace([
'title' => self::titleFromSlug($slug),
'type' => 'post',
'status' => 'draft',
'date' => $existing['date'] ?? $now,
'modified' => $now,
'category' => 'Notes',
'tags' => [],
'summary' => '',
'author' => 'Editor',
'cover' => '',
'allow_comments' => true,
'menu_order' => 0,
], $metadata);
$metadata['slug'] = $slug;
$metadata['tags'] = self::normalizeList($metadata['tags'] ?? []);
$metadata['allow_comments'] = self::toBool($metadata['allow_comments'] ?? true);
$metadata['modified'] = $now;
file_put_contents($directory . '/index.txt', $this->serialize($metadata, $markdown));
$this->rebuildIndex();
return $this->find($slug, true) ?? [];
}
public function delete(string $slug): bool
{
$slug = self::slugify($slug);
$directory = $this->pagesRoot . '/' . $slug;
if (!is_dir($directory)) {
return false;
}
$files = new \RecursiveIteratorIterator(
new \RecursiveDirectoryIterator($directory, \FilesystemIterator::SKIP_DOTS),
\RecursiveIteratorIterator::CHILD_FIRST
);
foreach ($files as $file) {
$file->isDir() ? rmdir($file->getPathname()) : unlink($file->getPathname());
}
rmdir($directory);
$this->rebuildIndex();
return true;
}
public function rebuildIndex(): void
{
$dir = dirname($this->indexPath);
if (!is_dir($dir)) {
mkdir($dir, 0775, true);
}
$records = [];
foreach ($this->all(true) as $item) {
$copy = $item;
unset($copy['markdown'], $copy['html'], $copy['path']);
$records[] = $copy;
}
file_put_contents($this->indexPath, json_encode([
'generated_at' => date('c'),
'format' => 'neon-blog-pages-v1',
'items' => $records,
], JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES) . PHP_EOL);
}
public static function slugify(string $value): string
{
$value = trim(mb_strtolower($value));
$value = preg_replace('/[^a-z0-9]+/u', '-', $value) ?? '';
$value = trim($value, '-');
return $value;
}
/** @return array<int, string> */
public static function normalizeList(mixed $value): array
{
if (is_string($value)) {
$value = preg_split('/\s*,\s*/', $value) ?: [];
}
if (!is_array($value)) {
return [];
}
$clean = [];
foreach ($value as $item) {
$item = trim((string) $item);
if ($item !== '') {
$clean[] = $item;
}
}
return array_values(array_unique($clean));
}
public static function toBool(mixed $value): bool
{
if (is_bool($value)) {
return $value;
}
return in_array(mb_strtolower((string) $value), ['1', 'true', 'yes', 'on'], true);
}
/** @return array<int, array<string, mixed>> */
private function filterByType(string $type, bool $includeDrafts): array
{
return array_values(array_filter($this->all($includeDrafts), static function (array $item) use ($type): bool {
return $item['type'] === $type;
}));
}
/** @return array<string, mixed> */
private function read(string $slug, string $file): array
{
[$metadata, $markdown] = $this->parse((string) file_get_contents($file));
$modified = date('c', filemtime($file) ?: time());
$metadata = array_replace([
'slug' => $slug,
'title' => $this->firstHeading($markdown) ?: self::titleFromSlug($slug),
'type' => 'post',
'status' => 'published',
'date' => $modified,
'modified' => $modified,
'category' => 'Notes',
'tags' => [],
'summary' => '',
'author' => 'Editor',
'cover' => '',
'allow_comments' => true,
'menu_order' => 0,
], $metadata);
$metadata['slug'] = self::slugify((string) ($metadata['slug'] ?: $slug));
$metadata['tags'] = self::normalizeList($metadata['tags'] ?? []);
$metadata['allow_comments'] = self::toBool($metadata['allow_comments'] ?? true);
$metadata['menu_order'] = (int) ($metadata['menu_order'] ?? 0);
$metadata['date'] = date('c', strtotime((string) $metadata['date']) ?: time());
$metadata['modified'] = date('c', strtotime((string) $metadata['modified']) ?: filemtime($file) ?: time());
$metadata['summary'] = trim((string) ($metadata['summary'] ?: Markdown::excerpt($markdown)));
return $metadata + [
'path' => $file,
'markdown' => $markdown,
'html' => Markdown::toHtml($markdown),
'excerpt' => Markdown::excerpt($metadata['summary'] ?: $markdown, 34),
];
}
/** @return array{0: array<string, mixed>, 1: string} */
private function parse(string $raw): array
{
$raw = str_replace(["\r\n", "\r"], "\n", $raw);
if (str_starts_with($raw, "---\n")) {
$end = strpos($raw, "\n---\n", 4);
if ($end !== false) {
$frontMatter = substr($raw, 4, $end - 4);
$body = ltrim(substr($raw, $end + 5), "\n");
return [$this->parseKeyValueBlock($frontMatter), $body];
}
}
return $this->parseLegacyHeader($raw);
}
/** @return array<string, mixed> */
private function parseKeyValueBlock(string $block): array
{
$metadata = [];
foreach (explode("\n", $block) as $line) {
if (!str_contains($line, ':')) {
continue;
}
[$key, $value] = explode(':', $line, 2);
$metadata[trim($key)] = $this->parseValue(trim($value));
}
return $metadata;
}
/** @return array{0: array<string, mixed>, 1: string} */
private function parseLegacyHeader(string $raw): array
{
$known = [
'title', 'slug', 'date', 'modified', 'type', 'status', 'category',
'tags', 'summary', 'description', 'author', 'cover', 'allow_comments',
'menu_order',
];
$metadata = [];
$lines = explode("\n", $raw);
$cursor = 0;
while (isset($lines[$cursor])) {
$line = $lines[$cursor];
if (trim($line) === '') {
$cursor++;
break;
}
if (!preg_match('/^([A-Za-z_ -]+):\s*(.*)$/', $line, $matches)) {
break;
}
$key = mb_strtolower(str_replace([' ', '-'], '_', trim($matches[1])));
if (!in_array($key, $known, true)) {
break;
}
$metadata[$key] = $this->parseValue(trim($matches[2]));
$cursor++;
}
if ($metadata === []) {
return [[], $raw];
}
return [$metadata, implode("\n", array_slice($lines, $cursor))];
}
private function parseValue(string $value): mixed
{
$value = trim($value);
if ($value === '') {
return '';
}
if ($value[0] === '[' && str_ends_with($value, ']')) {
$inside = trim(substr($value, 1, -1));
return $inside === '' ? [] : self::normalizeList($inside);
}
if (
(str_starts_with($value, '"') && str_ends_with($value, '"')) ||
(str_starts_with($value, "'") && str_ends_with($value, "'"))
) {
return substr($value, 1, -1);
}
$lower = mb_strtolower($value);
if (in_array($lower, ['true', 'false'], true)) {
return $lower === 'true';
}
if (is_numeric($value) && preg_match('/^\d+$/', $value)) {
return (int) $value;
}
return $value;
}
/** @param array<string, mixed> $metadata */
private function serialize(array $metadata, string $markdown): string
{
$order = [
'title', 'slug', 'type', 'status', 'date', 'modified', 'category',
'tags', 'summary', 'author', 'cover', 'allow_comments', 'menu_order',
];
$lines = ['---'];
foreach ($order as $key) {
if (!array_key_exists($key, $metadata)) {
continue;
}
$value = $metadata[$key];
if (is_array($value)) {
$value = '[' . implode(', ', array_map('strval', $value)) . ']';
} elseif (is_bool($value)) {
$value = $value ? 'true' : 'false';
}
$lines[] = $key . ': ' . str_replace(["\r", "\n"], ' ', (string) $value);
}
$lines[] = '---';
$lines[] = '';
$lines[] = rtrim($markdown) . PHP_EOL;
return implode(PHP_EOL, $lines);
}
private function firstHeading(string $markdown): string
{
foreach (explode("\n", $markdown) as $line) {
if (preg_match('/^#\s+(.+)$/', trim($line), $matches)) {
return trim($matches[1]);
}
}
return '';
}
private static function titleFromSlug(string $slug): string
{
return ucwords(str_replace('-', ' ', $slug));
}
}
+49
View File
@@ -0,0 +1,49 @@
<?php
declare(strict_types=1);
namespace NeonBlog;
use PDO;
final class Database
{
public static function connect(string $path): PDO
{
$dir = dirname($path);
if (!is_dir($dir)) {
mkdir($dir, 0775, true);
}
$pdo = new PDO('sqlite:' . $path);
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$pdo->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC);
$pdo->exec('PRAGMA foreign_keys = ON');
$pdo->exec('PRAGMA journal_mode = WAL');
self::migrate($pdo);
return $pdo;
}
private static function migrate(PDO $pdo): void
{
$pdo->exec(
'CREATE TABLE IF NOT EXISTS comments (
id INTEGER PRIMARY KEY AUTOINCREMENT,
slug TEXT NOT NULL,
author TEXT NOT NULL,
email TEXT NOT NULL,
website TEXT DEFAULT "",
body TEXT NOT NULL,
status TEXT NOT NULL DEFAULT "pending",
ip_hash TEXT NOT NULL DEFAULT "",
user_agent TEXT NOT NULL DEFAULT "",
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL
)'
);
$pdo->exec('CREATE INDEX IF NOT EXISTS comments_slug_status_idx ON comments(slug, status)');
$pdo->exec('CREATE INDEX IF NOT EXISTS comments_status_created_idx ON comments(status, created_at)');
}
}
+143
View File
@@ -0,0 +1,143 @@
<?php
declare(strict_types=1);
namespace NeonBlog;
final class Markdown
{
public static function toHtml(string $markdown): string
{
$markdown = str_replace(["\r\n", "\r"], "\n", trim($markdown));
if ($markdown === '') {
return '';
}
$lines = explode("\n", $markdown);
$html = [];
$count = count($lines);
$i = 0;
while ($i < $count) {
$line = rtrim($lines[$i]);
if (trim($line) === '') {
$i++;
continue;
}
if (preg_match('/^```([A-Za-z0-9_-]*)\s*$/', $line, $matches)) {
$language = $matches[1] !== '' ? ' class="language-' . self::e($matches[1]) . '"' : '';
$code = [];
$i++;
while ($i < $count && !preg_match('/^```\s*$/', rtrim($lines[$i]))) {
$code[] = $lines[$i];
$i++;
}
$html[] = '<pre><code' . $language . '>' . self::e(implode("\n", $code)) . '</code></pre>';
$i++;
continue;
}
if (preg_match('/^(#{1,6})\s+(.+)$/', $line, $matches)) {
$level = strlen($matches[1]);
$html[] = '<h' . $level . '>' . self::inline($matches[2]) . '</h' . $level . '>';
$i++;
continue;
}
if (preg_match('/^(-{3,}|\*{3,}|_{3,})$/', trim($line))) {
$html[] = '<hr>';
$i++;
continue;
}
if (preg_match('/^\s*>\s?(.*)$/', $line)) {
$quote = [];
while ($i < $count && preg_match('/^\s*>\s?(.*)$/', rtrim($lines[$i]), $matches)) {
$quote[] = $matches[1];
$i++;
}
$html[] = '<blockquote>' . self::toHtml(implode("\n", $quote)) . '</blockquote>';
continue;
}
if (preg_match('/^\s*[-*+]\s+(.+)$/', $line)) {
$items = [];
while ($i < $count && preg_match('/^\s*[-*+]\s+(.+)$/', rtrim($lines[$i]), $matches)) {
$items[] = '<li>' . self::inline($matches[1]) . '</li>';
$i++;
}
$html[] = '<ul>' . implode('', $items) . '</ul>';
continue;
}
if (preg_match('/^\s*\d+\.\s+(.+)$/', $line)) {
$items = [];
while ($i < $count && preg_match('/^\s*\d+\.\s+(.+)$/', rtrim($lines[$i]), $matches)) {
$items[] = '<li>' . self::inline($matches[1]) . '</li>';
$i++;
}
$html[] = '<ol>' . implode('', $items) . '</ol>';
continue;
}
$paragraph = [$line];
$i++;
while ($i < $count && trim($lines[$i]) !== '' && !self::startsBlock(rtrim($lines[$i]))) {
$paragraph[] = trim($lines[$i]);
$i++;
}
$html[] = '<p>' . self::inline(implode(' ', $paragraph)) . '</p>';
}
return implode("\n", $html);
}
public static function excerpt(string $markdown, int $words = 28): string
{
$text = trim(preg_replace('/\s+/', ' ', strip_tags(self::toHtml($markdown))) ?? '');
if ($text === '') {
return '';
}
$parts = preg_split('/\s+/', $text) ?: [];
if (count($parts) <= $words) {
return $text;
}
return implode(' ', array_slice($parts, 0, $words)) . '...';
}
private static function startsBlock(string $line): bool
{
return (bool) preg_match('/^(#{1,6}\s+|```|^\s*[-*+]\s+|^\s*\d+\.\s+|^\s*>\s?|(-{3,}|\*{3,}|_{3,})$)/', $line);
}
private static function inline(string $text): string
{
$placeholders = [];
$text = preg_replace_callback('/`([^`]+)`/', static function (array $matches) use (&$placeholders): string {
$key = "\x1A" . count($placeholders) . "\x1A";
$placeholders[$key] = '<code>' . self::e($matches[1]) . '</code>';
return $key;
}, $text) ?? $text;
$text = self::e($text);
$text = preg_replace_callback('/!\[([^\]]*)\]\(([^)\s]+)(?:\s+"([^"]+)")?\)/', static function (array $matches): string {
$title = isset($matches[3]) ? ' title="' . self::e($matches[3]) . '"' : '';
return '<img src="' . self::e($matches[2]) . '" alt="' . self::e($matches[1]) . '"' . $title . '>';
}, $text) ?? $text;
$text = preg_replace_callback('/\[([^\]]+)\]\(([^)\s]+)(?:\s+"([^"]+)")?\)/', static function (array $matches): string {
$title = isset($matches[3]) ? ' title="' . self::e($matches[3]) . '"' : '';
return '<a href="' . self::e($matches[2]) . '"' . $title . '>' . $matches[1] . '</a>';
}, $text) ?? $text;
$text = preg_replace('/\*\*([^*]+)\*\*/', '<strong>$1</strong>', $text) ?? $text;
$text = preg_replace('/\*([^*]+)\*/', '<em>$1</em>', $text) ?? $text;
return strtr($text, $placeholders);
}
private static function e(string $value): string
{
return htmlspecialchars($value, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
}
}
+125
View File
@@ -0,0 +1,125 @@
<?php
declare(strict_types=1);
namespace NeonBlog;
final class Stats
{
private string $path;
private Config $config;
public function __construct(string $path, Config $config)
{
$this->path = $path;
$this->config = $config;
}
public function record(string $routeType, string $slug, int $statusCode): void
{
if (!$this->config->get('stats.enabled', true) || PHP_SAPI === 'cli') {
return;
}
if (($_SERVER['REQUEST_METHOD'] ?? 'GET') !== 'GET') {
return;
}
$dir = dirname($this->path);
if (!is_dir($dir)) {
mkdir($dir, 0775, true);
}
$handle = fopen($this->path, 'ab');
if (!$handle) {
return;
}
flock($handle, LOCK_EX);
clearstatcache(true, $this->path);
if ((filesize($this->path) ?: 0) === 0) {
fputcsv($handle, [
'date',
'time',
'request_id',
'ip_hash',
'method',
'path',
'route_type',
'slug',
'query',
'referrer',
'user_agent',
'status_code',
], ',', '"', '', "\n");
}
$query = parse_url((string) ($_SERVER['REQUEST_URI'] ?? ''), PHP_URL_QUERY) ?: '';
fputcsv($handle, [
date('Y-m-d'),
date('H:i:s'),
bin2hex(random_bytes(6)),
$this->ipHash(),
$_SERVER['REQUEST_METHOD'] ?? 'GET',
parse_url((string) ($_SERVER['REQUEST_URI'] ?? '/'), PHP_URL_PATH) ?: '/',
$routeType,
$slug,
$query,
$_SERVER['HTTP_REFERER'] ?? '',
substr((string) ($_SERVER['HTTP_USER_AGENT'] ?? ''), 0, 300),
$statusCode,
], ',', '"', '', "\n");
flock($handle, LOCK_UN);
fclose($handle);
}
/** @return array<string, mixed> */
public function summary(): array
{
if (!is_file($this->path)) {
return [
'visits' => 0,
'routes' => [],
'top_paths' => [],
];
}
$handle = fopen($this->path, 'rb');
if (!$handle) {
return ['visits' => 0, 'routes' => [], 'top_paths' => []];
}
$header = fgetcsv($handle, null, ',', '"', '') ?: [];
$visits = 0;
$routes = [];
$paths = [];
while (($row = fgetcsv($handle, null, ',', '"', '')) !== false) {
if ($row === $header) {
continue;
}
$record = array_combine($header, $row);
if (!is_array($record)) {
continue;
}
$visits++;
$routes[$record['route_type']] = ($routes[$record['route_type']] ?? 0) + 1;
$paths[$record['path']] = ($paths[$record['path']] ?? 0) + 1;
}
fclose($handle);
arsort($routes);
arsort($paths);
return [
'visits' => $visits,
'routes' => $routes,
'top_paths' => array_slice($paths, 0, 10, true),
];
}
private function ipHash(): string
{
$ip = (string) ($_SERVER['REMOTE_ADDR'] ?? 'cli');
$salt = (string) $this->config->get('stats.hash_salt', 'change-me');
return hash_hmac('sha256', $ip, $salt);
}
}
+54
View File
@@ -0,0 +1,54 @@
<?php
declare(strict_types=1);
namespace NeonBlog;
final class View
{
public static function e(mixed $value): string
{
return htmlspecialchars((string) $value, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
}
/** @param array<string, mixed> $query */
public static function url(string $path = '', array $query = []): string
{
$path = '/' . ltrim($path, '/');
if ($path !== '/' && str_ends_with($path, '/')) {
$path = rtrim($path, '/');
}
return $query === [] ? $path : $path . '?' . http_build_query($query);
}
/** @param array<string, mixed> $item */
public static function itemUrl(array $item): string
{
$slug = (string) ($item['slug'] ?? '');
return ($item['type'] ?? 'post') === 'page'
? self::url($slug)
: self::url('post/' . $slug);
}
public static function asset(string $path): string
{
return self::url('themes/neon/assets/' . ltrim($path, '/'));
}
public static function date(string $iso, string $format = 'M j, Y'): string
{
$time = strtotime($iso);
return $time ? date($format, $time) : $iso;
}
public static function month(string $yearMonth): string
{
$time = strtotime($yearMonth . '-01');
return $time ? date('F Y', $time) : $yearMonth;
}
public static function classIf(bool $condition, string $class): string
{
return $condition ? ' ' . $class : '';
}
}
+45
View File
@@ -0,0 +1,45 @@
<?php
declare(strict_types=1);
use NeonBlog\App;
use NeonBlog\CommentService;
use NeonBlog\Config;
use NeonBlog\ContentRepository;
use NeonBlog\Database;
use NeonBlog\Stats;
define('BLOG_ROOT', dirname(__DIR__));
spl_autoload_register(static function (string $class): void {
$prefix = 'NeonBlog\\';
if (strncmp($class, $prefix, strlen($prefix)) !== 0) {
return;
}
$relative = substr($class, strlen($prefix));
$path = BLOG_ROOT . '/app/' . str_replace('\\', '/', $relative) . '.php';
if (is_file($path)) {
require $path;
}
});
if (PHP_SAPI !== 'cli' && session_status() !== PHP_SESSION_ACTIVE) {
session_start([
'cookie_httponly' => true,
'cookie_samesite' => 'Lax',
]);
}
$config = Config::load(BLOG_ROOT . '/config/site.json');
date_default_timezone_set((string) $config->get('timezone', 'UTC'));
$repository = new ContentRepository(
BLOG_ROOT . '/bl-content/pages',
BLOG_ROOT . '/bl-content/databases/pages.json'
);
$pdo = Database::connect(BLOG_ROOT . '/storage/database/blog.sqlite');
$comments = new CommentService($pdo, $config);
$stats = new Stats(BLOG_ROOT . '/storage/stats/visits.csv', $config);
return new App($config, $repository, $comments, $stats);
View File
+102
View File
@@ -0,0 +1,102 @@
{
"generated_at": "2026-07-04T19:14:59-04:00",
"format": "neon-blog-pages-v1",
"items": [
{
"slug": "hello-neon",
"title": "Hello Neon",
"type": "post",
"status": "published",
"date": "2026-07-04T09:00:00-04:00",
"modified": "2026-07-04T09:00:00-04:00",
"category": "Announcements",
"tags": [
"cms",
"markdown",
"neon"
],
"summary": "Meet the new file-first blog system with Bludit-style folders, Markdown posts, and a neon theme.",
"author": "Editor",
"cover": "",
"allow_comments": true,
"menu_order": 0,
"excerpt": "Meet the new file-first blog system with Bludit-style folders, Markdown posts, and a neon theme."
},
{
"slug": "copy-friendly-publishing",
"title": "Copy-Friendly Publishing",
"type": "post",
"status": "published",
"date": "2026-07-03T16:20:00-04:00",
"modified": "2026-07-03T16:20:00-04:00",
"category": "Workflow",
"tags": [
"bludit",
"files",
"cli"
],
"summary": "The canonical content store is still plain folders and text files, with JSON indexes generated for quick lookup.",
"author": "Editor",
"cover": "",
"allow_comments": true,
"menu_order": 0,
"excerpt": "The canonical content store is still plain folders and text files, with JSON indexes generated for quick lookup."
},
{
"slug": "comments-captcha-stats",
"title": "Comments, Captcha, and CSV Stats",
"type": "post",
"status": "published",
"date": "2026-07-02T11:45:00-04:00",
"modified": "2026-07-02T11:45:00-04:00",
"category": "Features",
"tags": [
"comments",
"sqlite",
"stats"
],
"summary": "Comments are stored in SQLite, protected by a math captcha and a honeypot field, while visits stream into an Excel-friendly CSV.",
"author": "Editor",
"cover": "",
"allow_comments": true,
"menu_order": 0,
"excerpt": "Comments are stored in SQLite, protected by a math captcha and a honeypot field, while visits stream into an Excel-friendly CSV."
},
{
"slug": "uses",
"title": "Uses",
"type": "page",
"status": "published",
"date": "2026-07-01T10:10:00-04:00",
"modified": "2026-07-01T10:10:00-04:00",
"category": "Pages",
"tags": [
"uses"
],
"summary": "A second static page to show menu ordering and page routing.",
"author": "Editor",
"cover": "",
"allow_comments": false,
"menu_order": 2,
"excerpt": "A second static page to show menu ordering and page routing."
},
{
"slug": "about",
"title": "About",
"type": "page",
"status": "published",
"date": "2026-07-01T10:00:00-04:00",
"modified": "2026-07-01T10:00:00-04:00",
"category": "Pages",
"tags": [
"about"
],
"summary": "A static page powered by the same Markdown file structure as posts.",
"author": "Editor",
"cover": "",
"allow_comments": false,
"menu_order": 1,
"excerpt": "A static page powered by the same Markdown file structure as posts."
}
]
}
+26
View File
@@ -0,0 +1,26 @@
---
title: About
slug: about
type: page
status: published
date: 2026-07-01T10:00:00-04:00
modified: 2026-07-01T10:00:00-04:00
category: Pages
tags: [about]
summary: A static page powered by the same Markdown file structure as posts.
author: Editor
cover:
allow_comments: false
menu_order: 1
---
Neon Notes is a small blog CMS for people who like WordPress-style publishing features but want content they can move with a file copy.
Posts, pages, generated indexes, comments, configuration, and stats each have a clear storage home:
- Markdown content lives in `bl-content/pages`.
- Generated JSON indexes live in `bl-content/databases`.
- Comments live in `storage/database/blog.sqlite`.
- Visits live in `storage/stats/visits.csv`.
There is no admin dashboard. Publishing and moderation happen through `php cli/blog.php`.
@@ -0,0 +1,26 @@
---
title: Comments, Captcha, and CSV Stats
slug: comments-captcha-stats
type: post
status: published
date: 2026-07-02T11:45:00-04:00
modified: 2026-07-02T11:45:00-04:00
category: Features
tags: [comments, sqlite, stats]
summary: Comments are stored in SQLite, protected by a math captcha and a honeypot field, while visits stream into an Excel-friendly CSV.
author: Editor
cover:
allow_comments: true
menu_order: 0
---
Comments use SQLite with moderation enabled by default. Visitors answer a small math captcha, and a hidden honeypot field catches many automated submissions before they hit the database.
Approved comments render below each post. Pending, approved, spam, and deleted states are managed from the CLI:
```bash
php cli/blog.php comments:list --status=pending
php cli/blog.php comments:approve 1
```
Stats are written to `storage/stats/visits.csv` with date, time, path, route type, hashed IP, referrer, user agent, and status code. The file is intentionally human readable and importable into Excel.
@@ -0,0 +1,25 @@
---
title: Copy-Friendly Publishing
slug: copy-friendly-publishing
type: post
status: published
date: 2026-07-03T16:20:00-04:00
modified: 2026-07-03T16:20:00-04:00
category: Workflow
tags: [bludit, files, cli]
summary: The canonical content store is still plain folders and text files, with JSON indexes generated for quick lookup.
author: Editor
cover:
allow_comments: true
menu_order: 0
---
The CMS keeps posts and static pages in `bl-content/pages/<slug>/index.txt`. That makes import work simple:
1. Copy a post folder into `bl-content/pages`.
2. Check the metadata at the top of `index.txt`.
3. Run `php cli/blog.php rebuild`.
SQLite stores comments. JSON stores site configuration and a generated page index. CSV stores visit stats in a format Excel can open without ceremony.
The CLI handles normal publishing work, so there is no browser admin dashboard to harden, theme, or maintain.
+25
View File
@@ -0,0 +1,25 @@
---
title: Hello Neon
slug: hello-neon
type: post
status: published
date: 2026-07-04T09:00:00-04:00
modified: 2026-07-04T09:00:00-04:00
category: Announcements
tags: [cms, markdown, neon]
summary: Meet the new file-first blog system with Bludit-style folders, Markdown posts, and a neon theme.
author: Editor
cover:
allow_comments: true
menu_order: 0
---
This is the first post in the new CMS. Each post lives in a folder shaped like Bludit content:
```text
bl-content/pages/hello-neon/index.txt
```
The file is Markdown with a small metadata block at the top. You can copy a folder into `bl-content/pages`, run `php cli/blog.php rebuild`, and the post becomes part of the site.
The public theme defaults to dark neon and lets visitors switch to a light palette or choose green, blue, red, pink, purple, or orange accents.
+23
View File
@@ -0,0 +1,23 @@
---
title: Uses
slug: uses
type: page
status: published
date: 2026-07-01T10:10:00-04:00
modified: 2026-07-01T10:10:00-04:00
category: Pages
tags: [uses]
summary: A second static page to show menu ordering and page routing.
author: Editor
cover:
allow_comments: false
menu_order: 2
---
This page exists to demonstrate static pages, menu ordering, and pretty URLs. Edit it with:
```bash
php cli/blog.php update uses --title="Uses" --body-file=/path/to/body.md
```
Static pages use the same folder format as posts. The only difference is `type: page` in the metadata block.
+1
View File
@@ -0,0 +1 @@
keep
-123
View File
@@ -1,123 +0,0 @@
<?php defined('BLUDIT') or die('Bludit CMS.');
class dbJSON {
public $db;
public $dbBackup;
public $file;
public $firstLine;
// $file, the JSON file.
// $firstLine, TRUE if you want to remove the first line, FALSE otherwise
function __construct($file, $firstLine=true)
{
$this->file = $file;
$this->db = array();
$this->dbBackup = array();
$this->firstLine = $firstLine;
if (file_exists($file)) {
// Read JSON file
$lines = file($file);
// Remove the first line, the first line is for security reasons
if ($firstLine) {
unset($lines[0]);
}
// Regenerate the JSON file
$implode = implode('', $lines);
// Unserialize, JSON to Array
$array = $this->unserialize($implode);
if (empty($array)) {
$this->db = array();
$this->dbBackup = array();
} else {
$this->db = $array;
$this->dbBackup = $array;
}
}
}
public function restoreDB()
{
$this->db = $this->dbBackup;
return true;
}
// Returns the number of rows in the database
public function count()
{
return count($this->db);
}
// Returns the value from the field
public function getField($field)
{
if (isset($this->db[$field])) {
return $this->db[$field];
}
if (isset($this->dbFields[$field])) {
return $this->dbFields[$field];
}
return null;
}
// Save the JSON file
public function save()
{
$data = '';
if ($this->firstLine) {
$data = "<?php defined('BLUDIT') or die('Bludit CMS.'); ?>".PHP_EOL;
}
// Serialize database
$data .= $this->serialize($this->db);
// Backup the new database.
$this->dbBackup = $this->db;
// LOCK_EX flag to prevent anyone else writing to the file at the same time.
if (file_put_contents($this->file, $data, LOCK_EX)) {
return true;
} else {
Log::set(__METHOD__.LOG_SEP.'Error occurred when trying to save the database file.', LOG_TYPE_ERROR);
return false;
}
}
// Returns a JSON encoded string on success or FALSE on failure
private function serialize($data)
{
if (DEBUG_MODE) {
return json_encode($data, JSON_PRETTY_PRINT);
}
return json_encode($data);
}
// Returns the value encoded in json in appropriate PHP type
private function unserialize($data)
{
// NULL is returned if the json cannot be decoded
$decode = json_decode($data, true);
if ($decode===NULL) {
Log::set(__METHOD__.LOG_SEP.'Error trying to read the JSON file: '.$this->file, LOG_TYPE_ERROR);
return false;
}
return $decode;
}
public function getDB()
{
return $this->db;
}
// Truncate all the rows
public function truncate()
{
$this->db = array();
return $this->save();
}
}
-191
View File
@@ -1,191 +0,0 @@
<?php defined('BLUDIT') or die('Bludit CMS.');
/*
Database structure
{
"videos": {
"name": "Videos",
"template: "",
"description: "",
"list": [ "my-page", "second-page" ]
},
"pets": {
"name": "Pets",
"template: "",
"description: "",
"list": [ "cats-and-dogs" ]
}
}
*/
class dbList extends dbJSON
{
public $db = array();
function __construct($file)
{
parent::__construct($file);
}
public function keys()
{
return array_keys($this->db);
}
// Returns the list of keys filter by pageNumber
// $pageNumber start in 1
public function getList($key, $pageNumber, $numberOfItems)
{
if (!isset($this->db[$key])) {
Log::set(__METHOD__.LOG_SEP.'Error key does not exist '.$key);
return false;
}
// List of keys
$list = $this->db[$key]['list'];
// Returns all the items from the list
if ($numberOfItems==-1) {
return $list;
}
// The first page number is 1, so the real is 0
$realPageNumber = $pageNumber - 1;
$chunks = array_chunk($list, $numberOfItems);
if (isset($chunks[$realPageNumber])) {
return $chunks[$realPageNumber];
}
// Out of index,returns FALSE
return false;
}
public function generateKey($name)
{
global $L;
$key = Text::cleanUrl($name);
if (Text::isEmpty($key)) {
$key = $L->g('empty');
}
while (isset($this->db[$key])) {
$key++;
}
return $key;
}
// Add a new item to the dblist
// $args => 'name', 'template', 'description', 'list'
public function add($args)
{
$key = $this->generateKey($args['name']);
$this->db[$key]['name'] = Sanitize::removeTags($args['name']);
$this->db[$key]['template'] = isset($args['template'])?Sanitize::removeTags($args['template']):'';
$this->db[$key]['description'] = isset($args['description'])?Sanitize::removeTags($args['description']):'';
$this->db[$key]['list'] = isset($args['list'])?$args['list']:array();
$this->sortAlphanumeric();
$this->save();
return $key;
}
public function remove($key)
{
if (!isset($this->db[$key])) {
Log::set(__METHOD__.LOG_SEP.'The key does not exist, key: '.$key);
return false;
}
unset($this->db[$key]);
return $this->save();
}
// Edit an item to the dblist
// $args => 'name', 'oldkey', 'newKey', 'template', 'description'
public function edit($args)
{
if ( isset($this->db[$args['newKey']]) && ($args['newKey']!==$args['oldKey']) ) {
Log::set(__METHOD__.LOG_SEP.'The new key already exists. Key: '.$args['newKey'], LOG_TYPE_WARN);
return false;
}
$this->db[$args['newKey']]['name'] = Sanitize::removeTags($args['name']);
$this->db[$args['newKey']]['template'] = isset($args['template'])?Sanitize::removeTags($args['template']):'';
$this->db[$args['newKey']]['description'] = isset($args['description'])?Sanitize::removeTags($args['description']):'';
$this->db[$args['newKey']]['list'] = $this->db[$args['oldKey']]['list'];
// Remove the old category
if ($args['oldKey'] !== $args['newKey']) {
unset( $this->db[$args['oldKey']] );
}
$this->sortAlphanumeric();
$this->save();
return $args['newKey'];
}
// Sort the categories by "Natural order"
public function sortAlphanumeric()
{
// Sort key alphanumeric strings, a01, a10, b10, c02
return ksort($this->db);
}
// Returns the name associated to the key, FALSE if the key doesn't exist
public function getName($key)
{
if (isset($this->db[$key])) {
return $this->db[$key]['name'];
}
return false;
}
// Returns an array with key=>name of the list
public function getKeyNameArray()
{
$tmp = array();
foreach($this->db as $key=>$fields) {
$tmp[$key] = $fields['name'];
}
return $tmp;
}
// Returns the number of items in the list
public function countItems($key)
{
if (isset($this->db[$key])) {
return count($this->db[$key]['list']);
}
return 0;
}
public function exists($key)
{
return isset( $this->db[$key] );
}
public function existsName($name)
{
foreach ($this->db as $key=>$fields) {
if ($name==$fields['name']) {
return true;
}
}
return false;
}
// Returns an array with a portion of the database filtered by key
// Returns array( 'key'=>'', 'name'=>'', 'template'=>'', 'description'=>'', list'=>array() )
public function getMap($key)
{
if (isset($this->db[$key])) {
$tmp = $this->db[$key];
$tmp['key'] = $key;
return $tmp;
}
return false;
}
}
-354
View File
@@ -1,354 +0,0 @@
<?php defined('BLUDIT') or die('Bludit CMS.');
class Plugin
{
// (string) directory name, just the name
// Ex: sitemap
public $directoryName;
// (string) Absolute database filename and path
// Ex: /www/bludit/bl-content/plugins/sitemap/db.php
public $filenameDb;
// (string) Absolute metadata filename and path
// Ex: /www/bludit/bl-plugins/sitemap/metadata.json
public $filenameMetadata;
// (array) Plugin metadata
// Ex: array('author'=>'',...., 'notes'=>'')
public $metadata;
// (string) Class name
// Ex: pluginSitemap
public $className;
// (array) Database unserialized
public $db;
// (array) Database fields, only for initialize
public $dbFields;
// (boolean) Enable or disable default Save and Cancel button on plugin settings
public $formButtons;
// (array) List of custom hooks
public $customHooks;
function __construct()
{
$this->dbFields = array();
$this->customHooks = array();
$reflector = new ReflectionClass(get_class($this));
// Directory name
$this->directoryName = basename(dirname($reflector->getFileName()));
// Class Name
$this->className = $reflector->getName();
$this->formButtons = true;
// Call the method init() from the children
$this->init();
// Init empty database with default values
$this->db = $this->dbFields;
$this->filenameDb = PATH_PLUGINS_DATABASES . $this->directoryName . DS . 'db.php';
// --- Metadata ---
$this->filenameMetadata = PATH_PLUGINS . $this->directoryName() . DS . 'metadata.json';
$metadataString = file_get_contents($this->filenameMetadata);
$this->metadata = json_decode($metadataString, true);
// If the plugin is installed then get the database
if ($this->installed()) {
$Tmp = new dbJSON($this->filenameDb);
$this->db = $Tmp->db;
$this->prepare();
}
}
public function save()
{
$tmp = new dbJSON($this->filenameDb);
$tmp->db = $this->db;
return $tmp->save();
}
public function includeCSS($filename)
{
return '<link rel="stylesheet" type="text/css" href="' . $this->domainPath() . 'css/' . $filename . '?version=' . BLUDIT_VERSION . '">' . PHP_EOL;
}
public function includeJS($filename)
{
return '<script charset="utf-8" src="' . $this->domainPath() . 'js/' . $filename . '?version=' . BLUDIT_VERSION . '"></script>' . PHP_EOL;
}
// Returns absolute URL and path of the plugin directory
// This function helps to include CSS or Javascript files with absolute URL
public function domainPath()
{
return DOMAIN_PLUGINS . $this->directoryName . '/';
}
// Returns relative path of the plugin directory
// This function helps to include CSS or Javascript files with relative URL
public function htmlPath()
{
return HTML_PATH_PLUGINS . $this->directoryName . '/';
}
// Returns absolute path of the plugin directory
// This function helps to include PHP libraries or some file at server level
public function phpPath()
{
return PATH_PLUGINS . $this->directoryName . DS;
}
public function phpPathDB()
{
return PATH_PLUGINS_DATABASES . $this->directoryName . DS;
}
// Returns the value of the key from the metadata of the plugin, FALSE if the key doesn't exist
public function getMetadata($key)
{
if (isset($this->metadata[$key])) {
return $this->metadata[$key];
}
return false;
}
// Set a key / value on the metadata of the plugin
public function setMetadata($key, $value)
{
$this->metadata[$key] = $value;
return true;
}
// Returns the value of the field from the database
// (string) $field
// (boolean) $html, TRUE returns the value sanitized, FALSE unsanitized
public function getValue($field, $html = true)
{
if (isset($this->db[$field])) {
if ($html) {
return $this->db[$field];
} else {
return Sanitize::htmlDecode($this->db[$field]);
}
}
return $this->dbFields[$field];
}
public function label()
{
return $this->getMetadata('label');
}
public function name()
{
return $this->getMetadata('name');
}
public function description()
{
return $this->getMetadata('description');
}
public function author()
{
return $this->getMetadata('author');
}
public function email()
{
return $this->getMetadata('email');
}
public function website()
{
return $this->getMetadata('website');
}
public function position()
{
return $this->getValue('position');
}
public function version()
{
return $this->getMetadata('version');
}
public function releaseDate()
{
return $this->getMetadata('releaseDate');
}
public function className()
{
return $this->className;
}
public function formButtons()
{
return $this->formButtons;
}
public function isCompatible()
{
$bluditRoot = explode('.', BLUDIT_VERSION);
$compatible = explode(',', $this->getMetadata('compatible'));
foreach ($compatible as $version) {
$root = explode('.', $version);
if ($root[0] == $bluditRoot[0] && $root[1] == $bluditRoot[1]) {
return true;
}
}
return false;
}
public function directoryName()
{
return $this->directoryName;
}
// Return TRUE if the installation success, otherwise FALSE.
public function install($position = 1)
{
if ($this->installed()) {
return false;
}
// Create workspace
$workspace = $this->workspace();
mkdir($workspace, DIR_PERMISSIONS, true);
// Create plugin directory for the database
mkdir(PATH_PLUGINS_DATABASES . $this->directoryName, DIR_PERMISSIONS, true);
$this->dbFields['position'] = $position;
// Sanitize default values to store in the file
foreach ($this->dbFields as $key => $value) {
$value = Sanitize::html($value);
settype($value, gettype($this->dbFields[$key]));
$this->db[$key] = $value;
}
// Create the database
return $this->save();
}
public function uninstall()
{
// Delete database
$path = PATH_PLUGINS_DATABASES . $this->directoryName;
Filesystem::deleteRecursive($path);
// Delete workspace
$workspace = $this->workspace();
Filesystem::deleteRecursive($workspace);
return true;
}
// Returns TRUE if the plugin is installed
// This function just check if the database of the plugin is created
public function installed()
{
return file_exists($this->filenameDb);
}
public function workspace()
{
return PATH_WORKSPACES . $this->directoryName . DS;
}
public function init()
{
// This method is used on children classes
// The user can define his own field of the database
}
public function prepare()
{
// This method is used on children classes
// The user can prepare the plugin, when it is installed
}
public function post()
{
$args = $_POST;
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();
}
public function type()
{
return $this->getMetadata('type');
}
public function setField($field, $value)
{
$this->db[$field] = Sanitize::html($value);
return $this->save();
}
public function setPosition($position)
{
return $this->setField('position', $position);
}
// Returns the parameters after the URI, FALSE if the URI doesn't match with the webhook
// Example: https://www.mybludit.com/api/foo/bar
public function webhook($URI = false, $returnsAfterURI = false, $fixed = true)
{
global $url;
if (empty($URI)) {
return false;
}
// Check URI start with the webhook
$startString = HTML_PATH_ROOT . $URI;
$URI = $url->uri();
$length = mb_strlen($startString, CHARSET);
if (mb_substr($URI, 0, $length) != $startString) {
return false;
}
$afterURI = mb_substr($URI, $length);
if (!empty($afterURI)) {
if ($fixed) {
return false;
}
if ($afterURI[0] != '/') {
return false;
}
}
if ($returnsAfterURI) {
return $afterURI;
}
Log::set(__METHOD__ . LOG_SEP . 'Webhook requested.');
return true;
}
}
-4
View File
@@ -1,4 +0,0 @@
<?php defined('BLUDIT') or die('Bludit CMS.');
// Title of the page
$layout['title'] = $L->g('About') . ' - ' . $layout['title'];
-40
View File
@@ -1,40 +0,0 @@
<?php defined('BLUDIT') or die('Bludit CMS.');
// ============================================================================
// Check role
// ============================================================================
checkRole(array('admin'));
// ============================================================================
// Functions
// ============================================================================
// ============================================================================
// Main before POST
// ============================================================================
// ============================================================================
// POST Method
// ============================================================================
// ============================================================================
// Main after POST
// ============================================================================
activatePlugin('pluginAPI');
$apiURL = DOMAIN_BASE.'api/';
$pluginAPI = getPlugin('pluginAPI');
$apiToken = $pluginAPI->getToken();
$username = $login->username();
$admin = new User($username);
$authToken = $admin->tokenAuth();
$output = array(
'apiURL'=>$apiURL,
'username'=>$username,
'apiToken'=>$apiToken,
'authToken'=>$authToken
);
exit(json_encode($output));
?>
@@ -1,26 +0,0 @@
<?php defined('BLUDIT') or die('Bludit CMS.');
// ============================================================================
// Check role
// ============================================================================
checkRole(array('admin'));
// ============================================================================
// Functions
// ============================================================================
// ============================================================================
// Main before POST
// ============================================================================
// ============================================================================
// POST Method
// ============================================================================
// ============================================================================
// Main after POST
// ============================================================================
// Title of the page
$layout['title'] .= ' - '.$L->g('Categories');
@@ -1,53 +0,0 @@
<?php defined('BLUDIT') or die('Bludit CMS.');
// ============================================================================
// Check role
// ============================================================================
checkRole(array('admin'));
// ============================================================================
// Functions
// ============================================================================
// ============================================================================
// Main before POST
// ============================================================================
$plugin = false;
$pluginClassName = $layout['parameters'];
// Check if the plugin exists
if (isset($plugins['all'][$pluginClassName])) {
$plugin = $plugins['all'][$pluginClassName];
} else {
Redirect::page('plugins');
}
// Check if the plugin has the method form()
if (!method_exists($plugin, 'form')) {
Redirect::page('plugins');
}
// ============================================================================
// POST Method
// ============================================================================
if ($_SERVER['REQUEST_METHOD'] == 'POST') {
// Add to syslog
$syslog->add(array(
'dictionaryKey'=>'plugin-configured',
'notes'=>$plugin->name()
));
// Call the method post of the plugin
$plugin->post();
Alert::set( $L->g('The changes have been saved') );
Redirect::page('configure-plugin/'.$plugin->className());
}
// ============================================================================
// Main after POST
// ============================================================================
// Title of the page
$layout['title'] = $L->g('Plugin').' - '.$plugin->name().' - '.$layout['title'];
-60
View File
@@ -1,60 +0,0 @@
<?php defined('BLUDIT') or die('Bludit CMS.');
// ============================================================================
// Check role
// ============================================================================
checkRole(array('admin', 'editor', 'author'));
// ============================================================================
// Functions
// ============================================================================
// Returns the content belongs to the current user if the user has the role Editor
function filterContentOwner($list) {
global $login;
global $pages;
$tmp = array();
foreach ($list as $pageKey) {
if ($pages->db[$pageKey]['username']==$login->username()) {
array_push($tmp, $pageKey);
}
}
return $tmp;
}
// ============================================================================
// Main before POST
// ============================================================================
// ============================================================================
// POST Method
// ============================================================================
// ============================================================================
// Main after POST
// ============================================================================
$published = $pages->getList($url->pageNumber(), ITEMS_PER_PAGE_ADMIN);
$drafts = $pages->getDraftDB(true);
$scheduled = $pages->getScheduledDB(true);
$static = $pages->getStaticDB(true);
$sticky = $pages->getStickyDB(true);
$autosave = $pages->getAutosaveDB(true);
// If the user is an Author filter the content he/she can edit
if (checkRole(array('author'), false)) {
$published = filterContentOwner($published);
$drafts = filterContentOwner($drafts);
$scheduled = filterContentOwner($scheduled);
$static = filterContentOwner($static);
$sticky = filterContentOwner($sticky);
}
// Check if out of range the pageNumber
if (empty($published) && $url->pageNumber()>1) {
Redirect::page('content');
}
// Title of the page
$layout['title'] .= ' - '.$L->g('Manage content');
-66
View File
@@ -1,66 +0,0 @@
<?php defined('BLUDIT') or die('Bludit CMS.');
// ============================================================================
// Functions
// ============================================================================
function updateBludit() {
global $site;
global $syslog;
// New installation
if ($site->currentBuild()==0) {
$site->set(array('currentBuild'=>BLUDIT_BUILD));
}
// Check if Bludit need to be updated
if ( ($site->currentBuild() < BLUDIT_BUILD) || isset($_GET['update']) ) {
Log::set('UPDATE SYSTEM - Starting.');
// Updates only for version less than Bludit v3.0 rc-3
if ($site->currentBuild()<='20180910') {
@mkdir(PATH_WORKSPACES, DIR_PERMISSIONS, true);
$plugins = array('pluginVisitsStats', 'pluginRSS', 'pluginSitemap', 'pluginTimeMachineX', 'pluginBackup');
foreach ($plugins as $plugin) {
if (pluginActivated($plugin)) {
Log::set('UPDATE SYSTEM - Re-enable plugin: '.$plugin);
deactivatePlugin($plugin);
activatePlugin($plugin);
}
}
}
// Updates only for version less than Bludit v3.1
if ($site->currentBuild()<='20180921') {
@mkdir(PATH_UPLOADS_PAGES, DIR_PERMISSIONS, true);
$site->set(array('imageRelativeToAbsolute'=>true, 'imageRestrict'=>false));
}
// Set the current build number
$site->set(array('currentBuild'=>BLUDIT_BUILD));
Log::set('UPDATE SYSTEM - Finished.');
// Add to syslog
$syslog->add(array(
'dictionaryKey'=>'system-updated',
'notes'=>'Bludit v'.BLUDIT_VERSION
));
}
}
// ============================================================================
// Main before POST
// ============================================================================
// ============================================================================
// POST Method
// ============================================================================
// ============================================================================
// Main after POST
// ============================================================================
// Try update Bludit
updateBludit();
// Title of the page
$layout['title'] .= ' - '.$L->g('Dashboard');
@@ -1,51 +0,0 @@
<?php defined('BLUDIT') or die('Bludit CMS.');
// ============================================================================
// Check role
// ============================================================================
checkRole(array('admin'));
// ============================================================================
// Functions
// ============================================================================
// This function is used on the VIEW to show the tables
function printTable($title, $array) {
echo '<h2 class="mb-2 mt-4">'.$title.'</h2>';
echo '<table class="table table-striped mt-3">
<tbody>
';
foreach ($array as $key=>$value) {
if($value===false) { $value = 'false'; }
elseif($value===true) { $value = 'true'; }
echo '<tr>';
echo '<td>'.$key.'</td>';
if (is_array($value)) {
echo '<td>'.json_encode($value).'</td>';
} else {
echo '<td>'.Sanitize::html($value).'</td>';
}
echo '</tr>';
}
echo '
</tbody>
</table>
';
}
// ============================================================================
// Main before POST
// ============================================================================
// ============================================================================
// POST Method
// ============================================================================
// ============================================================================
// Main after POST
// ============================================================================
$layout['title'] .= ' - '.$L->g('Developers');
@@ -1,44 +0,0 @@
<?php defined('BLUDIT') or die('Bludit CMS.');
// ============================================================================
// Check role
// ============================================================================
checkRole(array('admin'));
// ============================================================================
// Functions
// ============================================================================
// ============================================================================
// Main before POST
// ============================================================================
// ============================================================================
// POST Method
// ============================================================================
if ($_SERVER['REQUEST_METHOD'] == 'POST') {
if ($_POST['action']=='delete') {
deleteCategory($_POST);
} elseif ($_POST['action']=='edit') {
editCategory($_POST);
}
Redirect::page('categories');
}
// ============================================================================
// Main after POST
// ============================================================================
$categoryKey = $layout['parameters'];
if (!$categories->exists($categoryKey)) {
Log::set(__METHOD__.LOG_SEP.'Error occurred when trying to get the category: '.$categoryKey);
Redirect::page('categories');
}
$categoryMap = $categories->getMap($categoryKey);
// Title of the page
$layout['title'] .= ' - '.$L->g('Edit Category').' [ '.$categoryMap['name'] . ' ] ';
@@ -1,111 +0,0 @@
<?php defined('BLUDIT') or die('Bludit CMS.');
// ============================================================================
// Check role
// ============================================================================
if (checkRole(array('author'), false)) {
try {
$pageKey = isset($_POST['key']) ? $_POST['key'] : $layout['parameters'];
$page = new Page($pageKey);
} catch (Exception $e) {
Alert::set($L->g('You do not have sufficient permissions'));
Redirect::page('dashboard');
}
if ($page->username()!==$login->username()) {
// Add to syslog
$syslog->add(array(
'dictionaryKey'=>'access-denied',
'notes'=>$login->username()
));
Alert::set($L->g('You do not have sufficient permissions'));
Redirect::page('dashboard');
}
}
// ============================================================================
// Functions
// ============================================================================
// ============================================================================
// Main before POST
// ============================================================================
// ============================================================================
// POST Method
// ============================================================================
if ($_SERVER['REQUEST_METHOD'] == 'POST') {
if ($_POST['type']==='delete') {
// Get the page type before deleting to redirect to the correct tab
if (empty($_POST['tab'])) {
try {
$pageToDelete = new Page($_POST['key']);
$pageType = $pageToDelete->type();
if ($pageType === 'autosave') {
$_POST['tab'] = 'autosave';
} elseif ($pageType === 'draft') {
$_POST['tab'] = 'draft';
} elseif ($pageType === 'scheduled') {
$_POST['tab'] = 'scheduled';
} elseif ($pageType === 'static') {
$_POST['tab'] = 'static';
} else {
$_POST['tab'] = 'pages';
}
} catch (Exception $e) {
$_POST['tab'] = 'pages';
}
}
if (deletePage($_POST['key'])) {
Alert::set( $L->g('The changes have been saved') );
}
} else {
$key = editPage($_POST);
if ($key!==false) {
Alert::set( $L->g('The changes have been saved') );
Redirect::page('edit-content/'.$key);
}
}
if (!empty($_POST['tab'])) {
$tab = preg_replace('/[^a-zA-Z0-9_-]/', '', $_POST['tab']);
Redirect::page('content#'.$tab);
}
Redirect::page('content');
}
// ============================================================================
// Main after POST
// ============================================================================
try {
$pageKey = $layout['parameters'];
$page = new Page($pageKey);
} catch (Exception $e) {
Log::set(__METHOD__.LOG_SEP.'Error occurred when trying to get the page: '.$pageKey, LOG_TYPE_ERROR);
Redirect::page('content');
}
// Images prefix directory
define('PAGE_IMAGES_KEY', $page->uuid());
// Images and thubmnails directories
if (IMAGE_RESTRICT) {
define('PAGE_IMAGES_DIRECTORY', (IMAGE_RELATIVE_TO_ABSOLUTE? '' : HTML_PATH_UPLOADS_PAGES.PAGE_IMAGES_KEY.'/'));
define('PAGE_IMAGES_URL', (IMAGE_RELATIVE_TO_ABSOLUTE? '' : DOMAIN_UPLOADS_PAGES.PAGE_IMAGES_KEY.'/'));
define('PAGE_THUMBNAILS_DIRECTORY', PATH_UPLOADS_PAGES.PAGE_IMAGES_KEY.DS.'thumbnails'.DS);
define('PAGE_THUMBNAILS_HTML', HTML_PATH_UPLOADS_PAGES.PAGE_IMAGES_KEY.'/thumbnails/');
define('PAGE_THUMBNAILS_URL', DOMAIN_UPLOADS_PAGES.PAGE_IMAGES_KEY.'/thumbnails/');
} else {
define('PAGE_IMAGES_DIRECTORY', (IMAGE_RELATIVE_TO_ABSOLUTE? '' : HTML_PATH_UPLOADS));
define('PAGE_IMAGES_URL', (IMAGE_RELATIVE_TO_ABSOLUTE? '' : DOMAIN_UPLOADS));
define('PAGE_THUMBNAILS_DIRECTORY', PATH_UPLOADS_THUMBNAILS);
define('PAGE_THUMBNAILS_HTML', HTML_PATH_UPLOADS_THUMBNAILS);
define('PAGE_THUMBNAILS_URL', DOMAIN_UPLOADS_THUMBNAILS);
}
// Title of the page
$layout['title'] .= ' - '.$L->g('Edit content').' - '.$page->title();
-60
View File
@@ -1,60 +0,0 @@
<?php defined('BLUDIT') or die('Bludit CMS.');
// ============================================================================
// Functions
// ============================================================================
// ============================================================================
// Main before POST
// ============================================================================
// ============================================================================
// POST Method
// ============================================================================
if ($_SERVER['REQUEST_METHOD'] == 'POST') {
// Prevent non-administrators to change other users
if ($login->role()!=='admin') {
$_POST['username'] = $login->username();
unset($_POST['role']);
}
if (isset($_POST['deleteUserAndDeleteContent']) && ($login->role()==='admin')) {
$_POST['deleteContent'] = true;
deleteUser($_POST);
} elseif (isset($_POST['deleteUserAndKeepContent']) && ($login->role()==='admin')) {
$_POST['deleteContent'] = false;
deleteUser($_POST);
} elseif (isset($_POST['disableUser']) && ($login->role()==='admin')) {
disableUser(array('username'=>$_POST['username']));
} else {
editUser($_POST);
}
Alert::set($L->g('The changes have been saved'));
if ($login->role()==='admin') {
Redirect::page('users');
}
Redirect::page('edit-user/'.$login->username());
}
// ============================================================================
// Main after POST
// ============================================================================
$username = $layout['parameters'];
// Prevent non-administrators to change other users
if ($login->role()!=='admin') {
$username = $login->username();
}
try {
$user = new User($username);
} catch (Exception $e) {
Redirect::page('users');
}
// Title of the page
$layout['title'] = $L->g('Edit user').' - '.$layout['title'];
@@ -1,39 +0,0 @@
<?php defined('BLUDIT') or die('Bludit CMS.');
// ============================================================================
// Check role
// ============================================================================
checkRole(array('admin'));
// ============================================================================
// Functions
// ============================================================================
// ============================================================================
// Main before POST
// ============================================================================
// ============================================================================
// POST Method
// ============================================================================
// ============================================================================
// Main after POST
// ============================================================================
$pluginClassName = $layout['parameters'];
if (!activatePlugin($pluginClassName)) {
Log::set('Fail when try to activate the plugin.', LOG_TYPE_ERROR);
}
if (isset($plugins['all'][$pluginClassName])) {
$plugin = $plugins['all'][$pluginClassName];
} else {
Redirect::page('plugins');
}
if (method_exists($plugin, 'form')) {
Redirect::page('configure-plugin/'.$pluginClassName);
}
Redirect::page('plugins#'.$pluginClassName);
@@ -1,30 +0,0 @@
<?php defined('BLUDIT') or die('Bludit CMS.');
// ============================================================================
// Check role
// ============================================================================
checkRole(array('admin'));
// ============================================================================
// Functions
// ============================================================================
// ============================================================================
// Main before POST
// ============================================================================
// ============================================================================
// POST Method
// ============================================================================
// ============================================================================
// Main after POST
// ============================================================================
$themeDirectory = $layout['parameters'];
// Activate theme
activateTheme($themeDirectory);
// Redirect
Redirect::page('themes');
-80
View File
@@ -1,80 +0,0 @@
<?php defined('BLUDIT') or die('Bludit CMS.');
// ============================================================================
// Check role
// ============================================================================
// ============================================================================
// Functions
// ============================================================================
function checkLogin($args)
{
global $security;
global $login;
global $L;
if ($security->isBlocked()) {
Alert::set($L->g('IP address has been blocked').'<br>'.$L->g('Try again in a few minutes'), ALERT_STATUS_FAIL);
return false;
}
if ($login->verifyUser($_POST['username'], $_POST['password'])) {
if (isset($_POST['remember'])) {
$login->setRememberMe($_POST['username']);
}
// Renew the token. This token will be the same inside the session for multiple forms.
$security->generateTokenCSRF();
if (isset($_GET['enableAPI'])) {
Redirect::page('api');
}
Redirect::page('dashboard');
return true;
}
// Bruteforce protection, add IP to the blacklist
$security->addToBlacklist();
// Create alert
Alert::set($L->g('Username or password incorrect'), ALERT_STATUS_FAIL);
return false;
}
function checkRememberMe()
{
global $security;
global $login;
if ($security->isBlocked()) {
return false;
}
if ($login->verifyUserByRemember()) {
$security->generateTokenCSRF();
Redirect::page('dashboard');
return true;
}
return false;
}
// ============================================================================
// Main before POST
// ============================================================================
if ($_SERVER['REQUEST_METHOD']!=='POST') {
checkRememberMe();
}
// ============================================================================
// POST Method
// ============================================================================
if ($_SERVER['REQUEST_METHOD']=='POST') {
checkLogin($_POST);
}
// ============================================================================
// Main after POST
// ============================================================================
-25
View File
@@ -1,25 +0,0 @@
<?php defined('BLUDIT') or die('Bludit CMS.');
// ============================================================================
// Check role
// ============================================================================
// ============================================================================
// Functions
// ============================================================================
// ============================================================================
// Main before POST
// ============================================================================
// ============================================================================
// POST Method
// ============================================================================
// ============================================================================
// Main after POST
// ============================================================================
if ($login->logout()) {
Redirect::admin();
}
@@ -1,32 +0,0 @@
<?php defined('BLUDIT') or die('Bludit CMS.');
// ============================================================================
// Check role
// ============================================================================
checkRole(array('admin'));
// ============================================================================
// Functions
// ============================================================================
// ============================================================================
// Main before POST
// ============================================================================
// ============================================================================
// POST Method
// ============================================================================
if ($_SERVER['REQUEST_METHOD'] == 'POST') {
if (createCategory($_POST)) {
Redirect::page('categories');
}
}
// ============================================================================
// Main after POST
// ============================================================================
// Title of the page
$layout['title'] .= ' - '.$L->g('New category');
@@ -1,52 +0,0 @@
<?php defined('BLUDIT') or die('Bludit CMS.');
// ============================================================================
// Check role
// ============================================================================
checkRole(array('admin', 'editor', 'author'));
// ============================================================================
// Functions
// ============================================================================
// ============================================================================
// Main before POST
// ============================================================================
// ============================================================================
// POST Method
// ============================================================================
if ($_SERVER['REQUEST_METHOD'] == 'POST') {
createPage($_POST);
Redirect::page('content');
}
// ============================================================================
// Main after POST
// ============================================================================
// UUID of the page is need it for autosave and media manager
$uuid = $pages->generateUUID();
// Images prefix directory
define('PAGE_IMAGES_KEY', $uuid);
// Images and thubmnails directories
if (IMAGE_RESTRICT) {
define('PAGE_IMAGES_DIRECTORY', (IMAGE_RELATIVE_TO_ABSOLUTE? '' : HTML_PATH_UPLOADS_PAGES.PAGE_IMAGES_KEY.'/'));
define('PAGE_IMAGES_URL', (IMAGE_RELATIVE_TO_ABSOLUTE? '' : DOMAIN_UPLOADS_PAGES.PAGE_IMAGES_KEY.'/'));
define('PAGE_THUMBNAILS_DIRECTORY', PATH_UPLOADS_PAGES.PAGE_IMAGES_KEY.DS.'thumbnails'.DS);
define('PAGE_THUMBNAILS_HTML', HTML_PATH_UPLOADS_PAGES.PAGE_IMAGES_KEY.'/thumbnails/');
define('PAGE_THUMBNAILS_URL', DOMAIN_UPLOADS_PAGES.PAGE_IMAGES_KEY.'/thumbnails/');
} else {
define('PAGE_IMAGES_DIRECTORY', (IMAGE_RELATIVE_TO_ABSOLUTE? '' : HTML_PATH_UPLOADS));
define('PAGE_IMAGES_URL', (IMAGE_RELATIVE_TO_ABSOLUTE? '' : DOMAIN_UPLOADS));
define('PAGE_THUMBNAILS_DIRECTORY', PATH_UPLOADS_THUMBNAILS);
define('PAGE_THUMBNAILS_HTML', HTML_PATH_UPLOADS_THUMBNAILS);
define('PAGE_THUMBNAILS_URL', DOMAIN_UPLOADS_THUMBNAILS);
}
// Title of the page
$layout['title'] = $L->g('New content').' - '.$layout['title'];
-32
View File
@@ -1,32 +0,0 @@
<?php defined('BLUDIT') or die('Bludit CMS.');
// ============================================================================
// Check role
// ============================================================================
checkRole(array('admin'));
// ============================================================================
// Functions
// ============================================================================
// ============================================================================
// Main before POST
// ============================================================================
// ============================================================================
// POST Method
// ============================================================================
if ($_SERVER['REQUEST_METHOD'] == 'POST') {
if (createUser($_POST)) {
Redirect::page('users');
}
}
// ============================================================================
// Main after POST
// ============================================================================
// Title of the page
$layout['title'] .= ' - '.$L->g('Add a new user');
@@ -1,29 +0,0 @@
<?php defined('BLUDIT') or die('Bludit CMS.');
// ============================================================================
// Check role
// ============================================================================
checkRole(array('admin'));
// ============================================================================
// Functions
// ============================================================================
// ============================================================================
// Main before POST
// ============================================================================
if ($_SERVER['REQUEST_METHOD'] == 'POST') {
changePluginsPosition(explode(',',$_POST['plugin-list']));
Redirect::page('plugins-position');
}
// ============================================================================
// POST Method
// ============================================================================
// ============================================================================
// Main after POST
// ============================================================================
// Title of the page
$layout['title'] .= ' - '.$L->g('Plugins');
-26
View File
@@ -1,26 +0,0 @@
<?php defined('BLUDIT') or die('Bludit CMS.');
// ============================================================================
// Check role
// ============================================================================
checkRole(array('admin'));
// ============================================================================
// Functions
// ============================================================================
// ============================================================================
// Main before POST
// ============================================================================
// ============================================================================
// POST Method
// ============================================================================
// ============================================================================
// Main after POST
// ============================================================================
// Title of the page
$layout['title'] .= ' - '.$L->g('Plugins');
-31
View File
@@ -1,31 +0,0 @@
<?php defined('BLUDIT') or die('Bludit CMS.');
// ============================================================================
// Check role
// ============================================================================
checkRole(array('admin'));
// ============================================================================
// Functions
// ============================================================================
// ============================================================================
// Main after POST
// ============================================================================
// ============================================================================
// POST Method
// ============================================================================
if ($_SERVER['REQUEST_METHOD'] == 'POST') {
editSettings($_POST);
Redirect::page('settings');
}
// ============================================================================
// Main after POST
// ============================================================================
// Title of the page
$layout['title'] .= ' - '.$L->g('Advanced Settings');
-24
View File
@@ -1,24 +0,0 @@
<?php defined('BLUDIT') or die('Bludit CMS.');
// ============================================================================
// Check role
// ============================================================================
checkRole(array('admin'));
// ============================================================================
// Main after POST
// ============================================================================
// ============================================================================
// POST Method
// ============================================================================
// ============================================================================
// Main after POST
// ============================================================================
$themes = buildThemes();
// Title of the page
$layout['title'] .= ' - '.$L->g('Themes');
@@ -1,26 +0,0 @@
<?php defined('BLUDIT') or die('Bludit CMS.');
// ============================================================================
// Check role
// ============================================================================
checkRole(array('admin'));
// ============================================================================
// Functions
// ============================================================================
// ============================================================================
// Main before POST
// ============================================================================
// ============================================================================
// POST Method
// ============================================================================
// ============================================================================
// Main after POST
// ============================================================================
$pluginClassName = $layout['parameters'];
deactivatePlugin($pluginClassName);
Redirect::page('plugins');
@@ -1,51 +0,0 @@
<?php defined('BLUDIT') or die('Bludit CMS.');
// ============================================================================
// Functions
// ============================================================================
// ============================================================================
// Main before POST
// ============================================================================
// ============================================================================
// POST Method
// ============================================================================
if ($_SERVER['REQUEST_METHOD'] == 'POST') {
// Prevent non-administrators to change other users
$username = $_POST['username'];
if ($login->role()!=='admin') {
$username = $login->username();
}
if (changeUserPassword(array(
'username'=>$username,
'newPassword'=>$_POST['newPassword'],
'confirmPassword'=>$_POST['confirmPassword']
))) {
if ($login->role()==='admin') {
Redirect::page('users');
}
Redirect::page('edit-user/'.$login->username());
}
}
// ============================================================================
// Main after POST
// ============================================================================
// Prevent non-administrators to change other users
if ($login->role()!=='admin') {
$layout['parameters'] = $login->username();
}
try {
$username = $layout['parameters'];
$user = new User($username);
} catch (Exception $e) {
Redirect::page('users');
}
// Title of the page
$layout['title'] = $L->g('Change password').' - '.$layout['title'];
-31
View File
@@ -1,31 +0,0 @@
<?php defined('BLUDIT') or die('Bludit CMS.');
// ============================================================================
// Check role
// ============================================================================
checkRole(array('admin'));
// ============================================================================
// Functions
// ============================================================================
// ============================================================================
// Main after POST
// ============================================================================
// ============================================================================
// POST Method
// ============================================================================
if( $_SERVER['REQUEST_METHOD'] == 'POST' )
{
$site->set($_POST);
}
// ============================================================================
// Main after POST
// ============================================================================
// Title of the page
$layout['title'] .= ' - '.$L->g('Users');
@@ -1,195 +0,0 @@
a {
color: var(--primary-blue, #1e88e5);
transition: color 0.2s ease;
}
a:hover {
color: var(--primary-blue-dark, #1565c0);
text-decoration: none;
}
.bg-success {
background-color: #8BC34A!important;
}
.text-primary {
color: var(--primary-blue, #1e88e5)!important;
}
.text-danger {
color: #D40000!important;
}
a.text-danger:focus,
a.text-danger:hover {
color: #790000!important;
}
/* Buttons */
.btn {
font-weight: var(--font-weight-medium, 550);
font-size: var(--font-size-base, 0.945rem);
padding: 12px 24px;
border-radius: var(--radius-sm, 4px);
transition: all 0.3s ease;
}
.btn-primary {
background: var(--primary-blue, #0078D4);
border: none;
font-weight: var(--font-weight-semibold, 650);
}
.btn-primary:hover {
background: var(--primary-blue-dark, #005A9E);
border: none;
transform: translateY(-1px);
}
.btn-secondary {
background-color: var(--color-secondary, #64748B);
border: none;
font-weight: var(--font-weight-semibold, 650);
}
.btn-secondary:hover {
background-color: var(--color-secondary-dark, #475569);
border: none;
transform: translateY(-1px);
}
.btn-light {
background-color: var(--bg-light, #f8f9fa);
border: 2px solid var(--border-color, #e2e8f0);
font-weight: var(--font-weight-medium, 550);
}
.btn-light:hover {
background-color: var(--bg-sidebar, #F1F5F9);
border-color: var(--border-light, #cbd5e1);
transform: translateY(-1px);
}
.btn-danger {
background: var(--color-danger, #DC2626);
border: none;
font-weight: var(--font-weight-semibold, 650);
}
.btn-danger:hover {
background: var(--color-danger-dark, #B91C1C);
border: none;
transform: translateY(-1px);
}
.btn-success {
background: var(--color-success, #16A34A);
border: none;
font-weight: var(--font-weight-semibold, 650);
}
.btn-success:hover {
background: var(--color-success-dark, #15803D);
border: none;
transform: translateY(-1px);
}
.btn-sm {
padding: 8px 16px;
font-size: var(--font-size-sm, 0.925rem);
}
.btn-lg {
padding: 16px 32px;
font-size: var(--font-size-lg, 1.08rem);
}
.btn-light.focus, .btn-light:focus {
box-shadow: none;
}
.btn.focus, .btn:focus {
box-shadow: none;
}
/* Form */
.form-control {
font-size: var(--font-size-base, 0.945rem);
font-weight: var(--font-weight-normal, 450);
padding: 12px 16px;
border: 2px solid var(--border-color, #e2e8f0);
border-radius: var(--radius-sm, 4px);
transition: all 0.2s ease;
}
label {
font-weight: var(--font-weight-semibold, 650);
color: var(--text-primary, #0F172A);
margin-bottom: 8px;
font-size: var(--font-size-base, 0.945rem);
}
/* Cards */
.card {
border: none;
border-radius: var(--radius-md, 8px);
}
.card:hover {
}
.card-header {
background: transparent;
border-bottom: 1px solid var(--border-color, #e2e8f0);
font-weight: var(--font-weight-bold, 750);
font-size: var(--font-size-lg, 1.08rem);
}
/* Tables */
.table-striped tbody tr:nth-of-type(odd) {
background-color: var(--bg-warm, #faf8f5);
}
.table-striped tbody tr:hover {
background-color: var(--bg-warm-card, #f7f5f2);
}
.table thead th {
font-size: var(--font-size-xs, 0.8rem);
text-transform: uppercase!important;
font-weight: var(--font-weight-bold, 750);
color: var(--text-secondary, #475569);
letter-spacing: 0.8px;
border-bottom: 2px solid var(--border-color, #e2e8f0);
}
.table td {
font-size: var(--font-size-base, 0.945rem);
font-weight: var(--font-weight-normal, 450);
vertical-align: middle;
}
/* Headings */
h1, h2, h3, h4, h5, h6 {
color: var(--text-primary, #0F172A);
font-weight: var(--font-weight-bold, 750);
letter-spacing: -0.02em;
}
h1 { font-size: var(--font-size-3xl, 2.25rem); }
h2 { font-size: var(--font-size-2xl, 1.75rem); }
h3 { font-size: var(--font-size-xl, 1.4rem); }
h4 { font-size: var(--font-size-lg, 1.08rem); }
h5 { font-size: var(--font-size-base, 0.945rem); }
h6 { font-size: var(--font-size-sm, 0.925rem); }
/* Badge */
.badge {
font-size: var(--font-size-xs, 0.8rem);
font-weight: var(--font-weight-semibold, 650);
padding: 6px 12px;
border-radius: var(--radius-sm, 4px);
}
.badge-primary {
background: var(--primary-blue, #0078D4);
}
-800
View File
@@ -1,800 +0,0 @@
:root {
/* Dynamic font sizing */
--font-size-xs: 0.72rem;
--font-size-sm: 0.833rem;
--font-size-base: 0.945rem;
--font-size-lg: 1.08rem;
--font-size-xl: 1.26rem;
--font-size-2xl: 1.575rem;
--font-size-3xl: 2.025rem;
/* Font weights */
--font-weight-normal: 450;
--font-weight-medium: 550;
--font-weight-semibold: 650;
--font-weight-bold: 750;
/* Royal Azure Solid Blue colors */
--primary-blue: #0078D4;
--primary-blue-dark: #005A9E;
--primary-blue-darker: #004578;
--primary-gradient: #0078D4;
/* UI colors */
--bg-main: #F8FAFC;
--bg-sidebar: #F1F5F9;
--text-primary: #1E293B;
--text-secondary: #475569;
--text-muted: #94A3B8;
--border-color: #E2E8F0;
--shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
--shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
/* Warm neutral accents */
--bg-warm: #faf8f5;
--bg-warm-card: #f7f5f2;
--border-warm: #e8e6e3;
/* Radius scale */
--radius-sm: 4px;
--radius-md: 8px;
/* Semantic colors */
--color-info: #0078D4;
--color-danger: #DC2626;
--color-danger-dark: #B91C1C;
--color-success: #16A34A;
--color-success-dark: #15803D;
--color-secondary: #64748B;
--color-secondary-dark: #475569;
--bg-card: #FFFFFF;
--bg-light: #f8f9fa;
--border-light: #cbd5e1;
--primary-blue-hover-bg: rgba(0, 120, 212, 0.06);
}
html {
height: 100%;
font-size: var(--font-size-base);
background: var(--bg-main);
}
body {
background: var(--bg-main);
color: var(--text-primary);
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
line-height: 1.6;
font-weight: var(--font-weight-normal);
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
/* Prevent events in iframes */
/* iframe{
pointer-events: none;
} */
/*
ICONS
*/
.fa {
margin-right: .5rem !important;
line-height: inherit;
}
/*
SIDEBAR
*/
div.sidebar {
background: var(--bg-sidebar);
border-right: 1px solid var(--border-color);
border-left: 1px solid var(--border-color);
padding: 10px 15px;
}
div.sidebar .nav-item a {
padding-left: 5px;
padding-right: 5px;
color: var(--text-primary);
padding-top: 6px;
padding-bottom: 6px;
font-size: var(--font-size-base);
font-weight: var(--font-weight-normal);
transition: color 0.2s ease, transform 0.2s ease;
}
div.sidebar .nav-item a:hover {
color: var(--primary-blue);
transform: translateX(3px);
}
div.sidebar .nav-item a.active,
div.sidebar .nav-item a:focus {
color: var(--primary-blue);
}
div.sidebar .nav-item h4 {
font-size: var(--font-size-xs);
text-transform: uppercase;
font-weight: var(--font-weight-bold);
margin-top: 15px;
padding-left: 5px;
color: var(--text-secondary);
letter-spacing: 0.8px;
}
/*
QUICK SEARCH - SPOTLIGHT STYLE
*/
/* Trigger Button */
.quick-search-trigger {
position: relative;
width: 100%;
padding: 16px;
background: white;
border: 2px solid var(--border-color);
border-radius: var(--radius-sm);
display: flex;
align-items: center;
gap: 12px;
cursor: pointer;
transition: all 0.2s ease;
}
.quick-search-trigger:hover {
border-color: var(--primary-blue);
}
.quick-search-trigger .quick-search-icon {
width: 40px;
height: 40px;
border-radius: var(--radius-sm);
background: var(--primary-gradient);
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
}
.quick-search-trigger .quick-search-icon .fa {
color: white;
font-size: 16px;
margin: 0 !important;
}
.quick-search-text {
flex: 1;
display: flex;
flex-direction: column;
gap: 2px;
}
#hello-text {
color: var(--text-primary);
font-size: var(--font-size-base);
font-weight: var(--font-weight-medium);
}
.quick-search-hint {
color: var(--text-secondary);
font-size: var(--font-size-sm);
font-weight: var(--font-weight-normal);
}
.quick-search-shortcut {
padding: 6px 12px;
background: var(--bg-main);
border: 1px solid var(--border-color);
border-radius: var(--radius-sm);
font-size: var(--font-size-sm);
color: var(--text-secondary);
font-weight: var(--font-weight-medium);
font-family: monospace;
}
/* Modal Overlay */
.quick-search-modal {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 9999;
display: none;
align-items: flex-start;
justify-content: center;
padding-top: 15vh;
}
.quick-search-modal.active {
display: flex;
}
.quick-search-overlay {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.6);
backdrop-filter: blur(8px);
-webkit-backdrop-filter: blur(8px);
animation: fadeIn 0.2s ease;
}
@keyframes fadeIn {
from { opacity: 0; }
to { opacity: 1; }
}
/* Modal Content */
.quick-search-content {
position: relative;
width: 90%;
max-width: 680px;
max-height: 80vh;
background: var(--bg-card);
border-radius: var(--radius-md);
box-shadow: var(--shadow-md);
animation: slideDown 0.3s cubic-bezier(0.16, 1, 0.3, 1);
overflow: hidden;
display: flex;
flex-direction: column;
}
@keyframes slideDown {
from {
opacity: 0;
transform: translateY(-20px) scale(0.96);
}
to {
opacity: 1;
transform: translateY(0) scale(1);
}
}
.quick-search-header {
display: flex;
align-items: center;
gap: 16px;
padding: 24px;
border-bottom: 1px solid var(--border-color);
flex-shrink: 0;
}
.quick-search-header .fa-search {
color: var(--text-secondary);
font-size: 22px;
margin: 0 !important;
flex-shrink: 0;
}
.quick-search-input {
flex: 1;
border: none !important;
background: transparent !important;
padding: 0 !important;
font-size: var(--font-size-xl) !important;
font-weight: var(--font-weight-normal) !important;
outline: none !important;
box-shadow: none !important;
line-height: 1.5 !important;
}
.quick-search-input::placeholder {
color: var(--text-muted) !important;
font-size: var(--font-size-xl) !important;
}
.quick-search-results {
flex: 1;
overflow-y: auto;
min-height: 150px;
max-height: calc(80vh - 120px);
}
.search-no-results {
padding: 32px 24px;
text-align: center;
color: var(--text-secondary);
font-size: var(--font-size-base);
}
.search-suggestion {
display: block;
padding: 12px 16px;
transition: background 0.15s ease;
cursor: pointer;
border-bottom: 1px solid rgba(0, 0, 0, 0.04);
text-decoration: none;
color: inherit;
}
.search-suggestion:last-child {
border-bottom: none;
}
.search-suggestion:hover {
background: var(--primary-blue-hover-bg);
}
.search-suggestion .fa {
color: var(--primary-blue);
margin-right: 14px !important;
width: 22px;
text-align: center;
font-size: 16px;
}
.search-suggestion-item {
font-weight: var(--font-weight-medium);
color: var(--text-primary);
margin-bottom: 6px;
font-size: var(--font-size-lg);
line-height: 1.4;
}
.search-suggestion-options {
font-size: var(--font-size-sm);
padding-top: 4px;
}
.search-suggestion-options a {
color: var(--primary-blue);
margin-right: 16px;
font-weight: var(--font-weight-medium);
text-decoration: none;
transition: color 0.2s ease;
}
.search-suggestion-options a:hover {
color: var(--primary-blue-dark);
text-decoration: underline;
}
/*
BOOTSTRAP Hacks
*/
@media (min-width: 1200px) {
.container {
max-width: 1600px;
}
}
/* for small devices */
@media (max-width: 575.98px) {
#jsmediaManagerButton,
#jscategoryButton,
#jsdescriptionButton {
width: 100%;
text-align: left;
}
}
.btn-light {
color: #212529;
background-color: #f3f3f3;
border-color: #ced4d9;
}
.btn-form {
background-color: #F3F3F3;
border-color: #DDD;
color: #000;
}
.btn-form:hover {
background-color: rgb(228, 228, 228);
border-color: #DDD;
color: #000;
}
code {
padding: 3px 5px 2px;
margin: 0 1px;
background: #eaeaea;
background: rgba(0, 0, 0, .07);
color: #444;
}
.list-group-sortable {
cursor: pointer;
}
.modal-body {
padding: 2rem;
}
.modal-footer {
background-color: rgb(247, 247, 247);
}
.modal-dialog .btn-link {
color: #000;
}
/*
LOGIN
*/
body.login {
background: rgb(255, 255, 255);
background: linear-gradient(0deg, rgba(255, 255, 255, 1) 0%, rgba(250, 250, 250, 1) 53%);
height: 100%;
}
/*
DASHBOARD
*/
#dashboard ul.list-group.list-group-striped {
border: 1px solid var(--border-color);
border-radius: var(--radius-md);
overflow: hidden;
background: var(--bg-card);
}
#dashboard ul.list-group.list-group-striped li {
border: none;
overflow-wrap: anywhere;
font-size: var(--font-size-sm);
font-weight: var(--font-weight-normal);
padding: 10px 18px;
color: var(--text-primary);
transition: background 0.2s ease;
}
#dashboard ul.list-group.list-group-striped li:first-child {
background: var(--bg-card);
color: var(--text-primary);
padding: 14px 18px;
border-bottom: 1px solid var(--border-color);
}
#dashboard ul.list-group.list-group-striped li:first-child h4 {
color: var(--text-primary);
font-size: var(--font-size-sm);
font-weight: var(--font-weight-bold);
text-transform: uppercase;
letter-spacing: 0.6px;
margin: 0;
}
#dashboard ul.list-group.list-group-striped li:nth-of-type(even):not(:first-child) {
background: var(--bg-light);
}
#dashboard ul.list-group.list-group-striped li:hover:not(:first-child) {
background: var(--primary-blue-hover-bg);
}
#dashboard ul.list-group.list-group-striped li .notification-date {
color: var(--text-secondary);
font-size: var(--font-size-xs);
margin-top: 2px;
display: block;
}
#dashboard div.quick-links-icons {
font-size: 2.5em;
width: 100%;
}
#dashboard a.quick-links {
color: var(--text-secondary);
transition: all 0.2s ease;
}
#dashboard a.quick-links:hover {
text-decoration: none;
color: var(--primary-blue);
transform: translateY(-2px);
}
/*
DASHBOARD METRIC CARDS
*/
.card.metric-card {
border: 1px solid var(--border-color);
border-radius: var(--radius-md);
height: 100%;
background: var(--bg-card);
box-shadow: none;
}
.card.metric-card .card-body > .d-flex {
padding-bottom: 12px;
border-bottom: 1px solid var(--border-color);
}
.metric-card .card-body {
padding: 22px;
}
.metric-card .card-title {
font-size: var(--font-size-sm);
font-weight: var(--font-weight-bold);
color: var(--text-secondary);
text-transform: uppercase;
letter-spacing: 0.6px;
margin: 0;
}
.metric-value {
font-size: var(--font-size-2xl);
font-weight: var(--font-weight-bold);
color: var(--text-primary);
line-height: 1.2;
margin-bottom: 4px;
}
.metric-label {
font-size: var(--font-size-xs);
color: var(--text-muted);
font-weight: var(--font-weight-medium);
text-transform: uppercase;
letter-spacing: 0.5px;
}
.metric-card-list {
max-height: 300px;
overflow-y: auto;
}
/*
DASHBOARD ANALYTICS SECTION
*/
.analytics-section {
padding: 22px;
border: 1px solid var(--border-color);
border-radius: var(--radius-md);
background: var(--bg-card);
}
#dashboard .analytics-section ul.list-group.list-group-striped {
border: none;
border-radius: 0;
background: transparent;
}
#dashboard .analytics-section ul.list-group.list-group-striped li:first-child {
padding: 0 0 12px 0;
background: transparent;
border-bottom: 1px solid var(--border-color);
}
/*
ALERT
*/
#alert {
display: none;
position: fixed;
text-align: center;
border-radius: var(--radius-md);
border: 0;
z-index: 1000;
bottom: 20px;
right: 20px;
margin: 0;
font-size: var(--font-size-base);
padding: 16px 24px;
}
.alert-success {
background: var(--primary-gradient);
border-left: none !important;
color: #ffffff;
}
.alert-danger {
background: var(--color-danger);
border-left: none !important;
color: #ffffff;
}
/*
PLUGINS
*/
.plugin-form label {
display: block;
margin-top: 1rem !important;
}
.plugin-form input[type="text"],
.plugin-form textarea,
.plugin-form select {
display: block;
width: 100%;
padding: 8px 12px;
font-size: var(--font-size-base);
line-height: 1.5;
color: var(--text-primary);
background-color: var(--bg-card);
background-clip: padding-box;
border: 1px solid var(--border-color);
border-radius: var(--radius-sm);
transition: border-color .15s ease-in-out;
}
.plugin-form textarea {
min-height: 120px;
}
.plugin-form span.tip {
display: block;
font-size: 80%;
font-weight: 400;
margin-top: .25rem;
color: #6c757d !important;
}
/*
Manage > Content
*/
td.child {
padding-left: 30px;
}
/* Truncate the URL column with ellipsis when it doesn't fit. */
td.contentURL a {
display: inline-block;
max-width: 30em;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
vertical-align: middle;
}
/* Kebab actions dropdown */
.actionsDropdownToggle {
line-height: 1;
font-size: 1.25rem;
}
.actionsDropdownToggle:focus,
.actionsDropdownToggle:hover {
text-decoration: none;
color: var(--text-primary, #1E293B);
}
.actionsDropdownToggle:focus-visible {
outline: 2px solid var(--primary-blue);
outline-offset: 2px;
}
.actionsDropdown .dropdown-item i.fa {
width: 1.25em;
text-align: center;
}
/*
Manage > New Content
*/
#jseditor {
background: #fff;
padding: 10px 5% !important;
font-size: 16px;
line-height: 1.5em;
border: 1px solid #ced4da;
}
#jseditorSidebar {
display: none;
height: calc(100% - 45px);
width: 50%;
max-width: 350px;
position: absolute;
z-index: 50;
top: 45px;
right: 15px;
background-color: #fff;
overflow-x: hidden;
transition: 0.5s;
border-left: 1px solid #ccc;
border-right: 1px solid #ccc;
border-top: 1px solid #ccc;
}
@media (max-width: 575.98px) {
#jseditorSidebar {
width: 100%;
max-width: 100%;
right: 0;
}
#jseditorToolbarRight button {
font-size: 0px !important;
}
#jseditorToolbarRight button span {
font-size: 16px !important;
}
.contentTools .btn {
font-size: 0px !important;
margin-right: 5px;
}
.contentTools .btn span {
font-size: 16px !important;
}
}
#jseditorSidebar nav {
background: #f3f3f3;
}
#jseditorSidebar nav a {
color: #000;
}
#jseditorSidebar .nav-tabs .nav-link {
border: none;
}
#jseditorSidebar .nav-link.active {
background: none;
border: none;
border-bottom: 3px solid #ccc;
}
#jsshadow {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
background-color: rgba(72, 72, 72, 0.7);
z-index: 10;
display: none;
}
img.profilePicture {
width: 30px;
height: 30px;
border-radius: 4px;
border: 1px solid #ccc;
}
/* Switch button */
.switch-button {
font-size: 0.9em;
text-transform: uppercase;
cursor: pointer;
}
.switch-icon-publish {
color: #1cb11c;
}
/* Subtle focus style for form elements */
input:focus,
textarea:focus,
select:focus,
.form-control:focus,
.custom-select:focus,
.btn:focus {
border-color: #b0bec5 !important;
box-shadow: none !important;
outline: none !important;
}
@@ -1,17 +0,0 @@
<script charset="utf-8">
function showAlert(text) {
console.log("[INFO] Function showAlert() called.");
$("#alert").html(text);
$("#alert").slideDown().delay(<?php echo ALERT_DISAPPEAR_IN*1000 ?>).slideUp();
}
<?php if (Alert::defined()): ?>
setTimeout(function(){ showAlert("<?php echo Alert::get() ?>") }, 500);
<?php endif; ?>
$(window).click(function() {
$("#alert").hide();
});
</script>
<div id="alert" class="alert <?php echo (Alert::status()==ALERT_STATUS_FAIL)?'alert-danger':'alert-success' ?>"></div>
-269
View File
@@ -1,269 +0,0 @@
<?php
// Preload the first chunk of files to avoid an AJAX round-trip the first time
// the Media Manager is opened. Scans originals and resolves each thumbnail,
// mirroring the shape returned by ajax/list-images.
if (IMAGE_RESTRICT) {
$mediaImagesPath = PATH_UPLOADS_PAGES.PAGE_IMAGES_KEY.DS;
$mediaThumbnailsPath = PATH_UPLOADS_PAGES.PAGE_IMAGES_KEY.DS.'thumbnails'.DS;
} else {
$mediaImagesPath = PATH_UPLOADS;
$mediaThumbnailsPath = PATH_UPLOADS_THUMBNAILS;
}
$listOfFilesByPage = mediaManagerListImages($mediaImagesPath, $mediaThumbnailsPath, MEDIA_MANAGER_NUMBER_OF_FILES);
$preLoadFiles = !empty($listOfFilesByPage[0]) ? $listOfFilesByPage[0] : array();
// Amount of pages for the paginator
$numberOfPages = count($listOfFilesByPage);
?>
<div id="jsmediaManagerModal" class="modal" tabindex="-1" role="dialog">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="container-fluid">
<div class="row">
<div class="col p-3">
<!--
UPLOAD INPUT
-->
<h3 class="mt-2 mb-3"><i class="fa fa-image"></i> <?php $L->p('Images'); ?></h3>
<div id="jsalertMedia" class="alert alert-warning d-none" role="alert"></div>
<!-- Form and Input file -->
<form name="bluditFormUpload" id="jsbluditFormUpload" enctype="multipart/form-data">
<div class="custom-file">
<input type="file" class="custom-file-input" id="jsimages" name="images[]" multiple>
<label class="custom-file-label" for="jsimages"><?php $L->p('Choose images to upload'); ?></label>
</div>
</form>
<!-- Progress bar -->
<div class="progress mt-3">
<div id="jsbluditProgressBar" class="progress-bar bg-primary" role="progressbar" style="width:0%"></div>
</div>
<!--
IMAGES LIST
-->
<!-- Table for list files -->
<table id="jsbluditMediaTable" class="table mt-3">
<tr>
<td><?php $L->p('There are no images'); ?></td>
</tr>
</table>
<!-- Paginator -->
<nav id="jsbluditMediaTablePagination"></nav>
</div>
</div>
</div>
</div>
</div>
</div>
<script>
<?php
echo 'var preLoadFiles = '.json_encode($preLoadFiles).';';
?>
function openMediaManager() {
$('#jsmediaManagerModal').modal('show');
}
function closeMediaManager() {
$('#jsmediaManagerModal').modal('hide');
}
// Remove all files from the table
function cleanTable() {
$('#jsbluditMediaTable').empty();
}
function showMediaAlert(message) {
$("#jsalertMedia").html(message).removeClass('d-none');
}
function hideMediaAlert() {
$("#jsalertMedia").addClass('d-none');
}
// Show the files in the table
function displayFiles(files, numberOfPages = <?= $numberOfPages ?>) {
if (!Array.isArray(files)) {
return false;
}
// Clean table
cleanTable();
// Regenerate the table
if (files.length > 0) {
$.each(files, function(key, item) {
var filename = item.filename;
var image = "<?php echo PAGE_IMAGES_URL; ?>"+filename;
// item.thumbnail is empty when no thumbnail file exists (thumbnails
// disabled, generation failed, format unsupported by GD). Fall back
// to the original image so the preview never 404s.
var thumbnail = item.thumbnail ? "<?php echo PAGE_THUMBNAILS_URL; ?>"+item.thumbnail : image;
tableRow = '<tr id="js'+filename+'">'+
'<td style="width:80px"><img class="img-thumbnail" alt="200x200" src="'+thumbnail+'" style="width: 50px; height: 50px;"><\/td>'+
'<td class="information">'+
'<div class="text-secondary pb-2">'+filename+'<\/div>'+
'<div>'+
'<a href="#" class="mr-3 text-primary" onClick="editorInsertMedia(\''+image+'\'); closeMediaManager();"><i class="fa fa-plus-circle"></i><?php $L->p('Insert') ?><\/a>'+
'<a href="#" class="mr-3 text-primary" onClick="editorInsertMedia(\''+thumbnail+'\'); closeMediaManager();"><i class="fa fa-image"></i><?php $L->p('Insert thumbnail') ?><\/a>'+
'<a href="#" class="mr-3 text-primary" onClick="editorInsertLinkedMedia(\''+thumbnail+'\',\''+image+'\'); closeMediaManager();"><i class="fa fa-link"></i><?php $L->p('Insert linked thumbnail') ?><\/a>'+
'<a href="#" class="text-primary" onClick="setCoverImage(\''+filename+'\'); closeMediaManager();"><i class="fa fa-desktop"></i><?php $L->p('Set as cover image') ?><\/a>'+
'<a href="#" class="float-right text-danger" onClick="deleteMedia(\''+filename+'\')"><i class="fa fa-trash-o"></i><?php $L->p('Delete') ?><\/a>'+
'<\/div>'+
'<\/td>'+
'<\/tr>';
$('#jsbluditMediaTable').append(tableRow);
});
mediaPagination = '<ul class="pagination justify-content-center flex-wrap">';
for (var i = 1; i <= numberOfPages; i++) {
mediaPagination += '<li class="page-item"><button type="button" class="btn btn-link page-link" onClick="getFiles('+i+')">'+i+'</button></li>';
}
mediaPagination += '</ul>';
$('#jsbluditMediaTablePagination').html(mediaPagination);
}
if (files.length == 0) {
$('#jsbluditMediaTable').html("<p><?php (IMAGE_RESTRICT ? $L->p('There are no images for the page') : $L->p('There are no images')) ?></p>");
$('#jsbluditMediaTablePagination').html('');
}
}
// Get the list of files via AJAX, filter by the page number
function getFiles(pageNumber) {
$.post(HTML_PATH_ADMIN_ROOT+"ajax/list-images",
{ tokenCSRF: tokenCSRF,
pageNumber: pageNumber,
uuid: "<?php echo PAGE_IMAGES_KEY ?>",
path: "thumbnails" // the paths are defined in ajax/list-images
},
function(data) { // success function
if (data.status==0) {
displayFiles(data.files, data.numberOfPages);
} else {
console.log(data.message);
}
}
);
}
// Delete the file and the thumbnail if exist
function deleteMedia(filename) {
$.post(HTML_PATH_ADMIN_ROOT+"ajax/delete-image",
{ tokenCSRF: tokenCSRF,
filename: filename,
uuid: "<?php echo PAGE_IMAGES_KEY; ?>"
},
function(data) { // success function
if (data.status==0) {
getFiles(1);
} else {
console.log(data.message);
}
}
);
}
function setCoverImage(filename) {
var image = "<?php echo PAGE_IMAGES_URL; ?>"+filename;
$("#jscoverImage").val(filename);
$("#jscoverImagePreview").attr("src", image);
}
function uploadImages() {
// Remove current alerts
hideMediaAlert();
var images = $("#jsimages")[0].files;
for (var i=0; i < images.length; i++) {
// Check file type/extension
const validImageTypes = ['image/gif', 'image/jpeg', 'image/png', 'image/svg+xml', 'image/webp'];
if (!validImageTypes.includes(images[i].type)) {
showMediaAlert("<?php echo $L->g('File type is not supported. Allowed types:').' '.implode(', ',$GLOBALS['ALLOWED_IMG_EXTENSION']) ?>");
return false;
}
// Check file size and compare with PHP upload_max_filesize
if (images[i].size > UPLOAD_MAX_FILESIZE) {
showMediaAlert("<?php echo $L->g('Maximum load file size allowed:').' '.ini_get('upload_max_filesize') ?>");
return false;
}
};
// Clean progress bar
$("#jsbluditProgressBar").removeClass().addClass("progress-bar bg-primary");
$("#jsbluditProgressBar").width("0");
// Data to send via AJAX
var formData = new FormData($("#jsbluditFormUpload")[0]);
formData.append("uuid", "<?php echo PAGE_IMAGES_KEY ?>");
formData.append("tokenCSRF", tokenCSRF);
$.ajax({
url: HTML_PATH_ADMIN_ROOT+"ajax/upload-images",
type: "POST",
data: formData,
cache: false,
contentType: false,
processData: false,
xhr: function() {
var xhr = $.ajaxSettings.xhr();
if (xhr.upload) {
xhr.upload.addEventListener("progress", function(e) {
if (e.lengthComputable) {
var percentComplete = (e.loaded / e.total)*100;
$("#jsbluditProgressBar").width(percentComplete+"%");
}
}, false);
}
return xhr;
}
}).done(function(data) {
if (data.status==0) {
$("#jsbluditProgressBar").removeClass("bg-primary").addClass("bg-success");
// Get the files for the first page, including uploaded files
getFiles(1);
} else {
$("#jsbluditProgressBar").removeClass("bg-primary").addClass("bg-danger");
showMediaAlert(data.message);
}
});
}
$(document).ready(function() {
// Display the files preloaded for the first time
displayFiles(preLoadFiles);
// Select image event
$("#jsimages").on("change", function(e) {
uploadImages();
});
// Drag and drop image
$(window).on("dragover dragenter", function(e) {
e.preventDefault();
e.stopPropagation();
openMediaManager();
});
// Drag and drop image
$(window).on("drop", function(e) {
e.preventDefault();
e.stopPropagation();
$("#jsimages").prop("files", e.originalEvent.dataTransfer.files);
uploadImages();
});
});
</script>
@@ -1,77 +0,0 @@
<nav class="navbar navbar-expand-lg navbar-dark bg-dark text-uppercase d-block d-lg-none">
<div class="container">
<span class="navbar-brand">
<?php echo (defined('BLUDIT_PRO'))?'BLUDIT PRO':'BLUDIT' ?></span>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false"
aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav">
<li class="nav-item">
<a class="nav-link" href="<?php echo HTML_PATH_ADMIN_ROOT.'dashboard' ?>">
<?php $L->p('Dashboard') ?></a>
</li>
<li class="nav-item">
<a class="nav-link" href="<?php echo HTML_PATH_ROOT ?>">
<?php $L->p('Website') ?></a>
</li>
<li class="nav-item">
<a class="nav-link" href="<?php echo HTML_PATH_ADMIN_ROOT.'new-content' ?>">
<?php $L->p('New content') ?></a>
</li>
<li class="nav-item">
<a class="nav-link" href="<?php echo HTML_PATH_ADMIN_ROOT.'content' ?>">
<?php $L->p('Content') ?></a>
</li>
<?php if (!checkRole(array('admin'),false)): ?>
<li class="nav-item">
<a class="nav-link" href="<?php echo HTML_PATH_ADMIN_ROOT.'edit-user/'.$login->username() ?>">
<?php $L->p('Profile') ?></a>
</li>
<?php endif; ?>
<?php if (checkRole(array('admin'),false)): ?>
<li class="nav-item">
<a class="nav-link" href="<?php echo HTML_PATH_ADMIN_ROOT.'categories' ?>">
<?php $L->p('Categories') ?></a>
</li>
<li class="nav-item">
<a class="nav-link" href="<?php echo HTML_PATH_ADMIN_ROOT.'users' ?>">
<?php $L->p('Users') ?></a>
</li>
<li class="nav-item">
<a class="nav-link" href="<?php echo HTML_PATH_ADMIN_ROOT.'settings' ?>">
<?php $L->p('Settings') ?></a>
</li>
<li class="nav-item">
<a class="nav-link" href="<?php echo HTML_PATH_ADMIN_ROOT.'plugins' ?>">
<?php $L->p('Plugins') ?></a>
</li>
<li class="nav-item">
<a class="nav-link" href="<?php echo HTML_PATH_ADMIN_ROOT.'themes' ?>">
<?php $L->p('Themes') ?></a>
</li>
<li class="nav-item">
<a class="nav-link" href="<?php echo HTML_PATH_ADMIN_ROOT.'about' ?>">
<?php $L->p('About') ?></a>
</li>
<?php endif; ?>
<?php if (checkRole(array('admin'),false)): ?>
<?php
if (!empty($plugins['adminSidebar'])) {
foreach ($plugins['adminSidebar'] as $pluginSidebar) {
echo '<li class="nav-item">';
echo $pluginSidebar->adminSidebar();
echo '</li>';
}
}
?>
<?php endif; ?>
<li class="nav-item">
<a class="nav-link" href="<?php echo HTML_PATH_ADMIN_ROOT.'logout' ?>">
<?php $L->p('Logout') ?></a>
</li>
</ul>
</div>
</div>
</nav>
@@ -1,83 +0,0 @@
<!-- Use .flex-column to set a vertical direction -->
<ul class="nav flex-column pt-4">
<li class="nav-item mb-4" style="display: flex; align-items: center;">
<div style="display: inline-flex; align-items: center; justify-content: center; width: 32px; height: 32px; background: var(--primary-gradient); border-radius: 8px; box-shadow: var(--shadow-sm); flex-shrink: 0;">
<img src="<?php echo HTML_PATH_CORE_IMG ?>logo.svg" width="18" height="18" alt="bludit-logo" style="filter: brightness(0) invert(1);">
</div>
<span style="margin-left: 10px; font-weight: 600; font-size: 1rem; color: var(--text-primary); line-height: 1;"><?php echo (defined('BLUDIT_PRO'))?'BLUDIT PRO':'BLUDIT' ?></span>
</li>
<li class="nav-item">
<a class="nav-link" href="<?php echo HTML_PATH_ADMIN_ROOT.'dashboard' ?>"><span class="fa fa-dashboard"></span><?php $L->p('Dashboard') ?></a>
</li>
<li class="nav-item">
<a class="nav-link" target="_blank" href="<?php echo HTML_PATH_ROOT ?>"><span class="fa fa-home"></span><?php $L->p('Website') ?></a>
</li>
<li class="nav-item mt-3">
<a class="nav-link" href="<?php echo HTML_PATH_ADMIN_ROOT.'new-content' ?>"><span class="fa fa-plus-circle text-primary"></span><?php $L->p('New content') ?></a>
</li>
<?php if (!checkRole(array('admin'),false)): ?>
<li class="nav-item">
<a class="nav-link" href="<?php echo HTML_PATH_ADMIN_ROOT.'content' ?>"><span class="fa fa-archive"></span><?php $L->p('Content') ?></a>
</li>
<li class="nav-item">
<a class="nav-link" href="<?php echo HTML_PATH_ADMIN_ROOT.'edit-user/'.$login->username() ?>"><span class="fa fa-user"></span><?php $L->p('Profile') ?></a>
</li>
<?php endif; ?>
<?php if (checkRole(array('admin'),false)): ?>
<li class="nav-item mt-3">
<h4><?php $L->p('Manage') ?></h4>
</li>
<li class="nav-item">
<a class="nav-link" href="<?php echo HTML_PATH_ADMIN_ROOT.'content' ?>"><span class="fa fa-folder"></span><?php $L->p('Content') ?></a>
</li>
<li class="nav-item">
<a class="nav-link" href="<?php echo HTML_PATH_ADMIN_ROOT.'categories' ?>"><span class="fa fa-bookmark"></span><?php $L->p('Categories') ?></a>
</li>
<li class="nav-item">
<a class="nav-link" href="<?php echo HTML_PATH_ADMIN_ROOT.'users' ?>"><span class="fa fa-users"></span><?php $L->p('Users') ?></a>
</li>
<li class="nav-item mt-3">
<h4><?php $L->p('Settings') ?></h4>
</li>
<li class="nav-item">
<a class="nav-link" href="<?php echo HTML_PATH_ADMIN_ROOT.'settings' ?>"><span class="fa fa-gear"></span><?php $L->p('General') ?></a>
</li>
<li class="nav-item">
<a class="nav-link" href="<?php echo HTML_PATH_ADMIN_ROOT.'plugins' ?>"><span class="fa fa-puzzle-piece"></span><?php $L->p('Plugins') ?></a>
</li>
<li class="nav-item">
<a class="nav-link" href="<?php echo HTML_PATH_ADMIN_ROOT.'themes' ?>"><span class="fa fa-desktop"></span><?php $L->p('Themes') ?></a>
</li>
<li class="nav-item">
<a class="nav-link" href="<?php echo HTML_PATH_ADMIN_ROOT.'about' ?>"><span class="fa fa-info"></span><?php $L->p('About') ?></a>
</li>
<?php endif; ?>
<?php if (checkRole(array('admin', 'editor'),false)): ?>
<?php
if (!empty($plugins['adminSidebar'])) {
echo '<li class="nav-item"><hr></li>';
foreach ($plugins['adminSidebar'] as $pluginSidebar) {
echo '<li class="nav-item">';
echo $pluginSidebar->adminSidebar();
echo '</li>';
}
}
?>
<?php endif; ?>
<li class="nav-item mt-5">
<a class="nav-link" href="<?php echo HTML_PATH_ADMIN_ROOT.'logout' ?>"><span class="fa fa-arrow-circle-right"></span><?php $L->p('Logout') ?></a>
</li>
</ul>
-98
View File
@@ -1,98 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<title><?php echo $layout['title'] ?></title>
<meta charset="<?php echo CHARSET ?>">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="robots" content="noindex,nofollow">
<meta name="generator" content="Bludit">
<!-- Favicon -->
<link rel="shortcut icon" type="image/x-icon" href="<?php echo HTML_PATH_CORE_IMG.'favicon.png?version='.BLUDIT_VERSION ?>">
<!-- CSS -->
<?php
echo Theme::cssBootstrap();
echo Theme::cssLineAwesome();
echo Theme::css(array(
'bludit.css',
'bludit.bootstrap.css'
), DOMAIN_ADMIN_THEME_CSS);
echo Theme::css(array(
'jquery.datetimepicker.min.css',
'select2.min.css',
'select2-bootstrap4.min.css'
), DOMAIN_CORE_CSS);
?>
<!-- Javascript -->
<?php
echo Theme::jquery();
echo Theme::jsBootstrap();
echo Theme::jsSortable();
echo Theme::js(array(
'jquery.datetimepicker.full.min.js',
'select2.full.min.js',
'functions.js'
), DOMAIN_CORE_JS, null);
?>
<!-- Plugins -->
<?php Theme::plugins('adminHead') ?>
</head>
<body class="h-100">
<!-- Plugins -->
<?php Theme::plugins('adminBodyBegin') ?>
<!-- Javascript dynamic generated by PHP -->
<?php
echo '<script charset="utf-8">'.PHP_EOL;
include(PATH_CORE_JS.'variables.php');
echo '</script>'.PHP_EOL;
echo '<script charset="utf-8">'.PHP_EOL;
include(PATH_CORE_JS.'bludit-ajax.php');
echo '</script>'.PHP_EOL;
?>
<!-- Overlay background -->
<div id="jsshadow"></div>
<!-- Alert -->
<?php include('html/alert.php'); ?>
<!-- Navbar, only for small devices -->
<?php include('html/navbar.php'); ?>
<div class="container min-vh-100">
<!-- 25%/75% split on large devices, small, medium devices hide -->
<div class="row min-vh-100 align-items-stretch">
<!-- LEFT SIDEBAR - Display only on large devices -->
<div class="sidebar col-lg-2 d-none d-lg-block">
<?php include('html/sidebar.php'); ?>
</div>
<!-- RIGHT MAIN -->
<div class="col-lg-10 pt-3 pb-1">
<?php
if (Sanitize::pathFile(PATH_ADMIN_VIEWS, $layout['view'].'.php')) {
include(PATH_ADMIN_VIEWS.$layout['view'].'.php');
} elseif ($layout['plugin'] && method_exists($layout['plugin'], 'adminView')) {
echo $layout['plugin']->adminView();
} else {
echo '<h1 class="text-center">'.$L->g('Page not found').'</h1>';
echo '<h2 class="text-center">'.$L->g('Choose a page from the sidebar.').'</h2>';
}
?>
</div>
</div>
</div>
<!-- Plugins -->
<?php Theme::plugins('adminBodyEnd') ?>
</body>
</html>
-403
View File
@@ -1,403 +0,0 @@
<?php
class Bootstrap
{
public static function modal($args)
{
$buttonSecondary = $args['buttonSecondary'];
$buttonSecondaryClass = $args['buttonSecondaryClass'];
$buttonPrimary = $args['buttonPrimary'];
$buttonPrimaryClass = $args['buttonPrimaryClass'];
$modalText = $args['modalText'];
$modalTitle = $args['modalTitle'];
$modalId = $args['modalId'];
return <<<EOF
<div id="$modalId" class="modal fade" tabindex="-1" role="dialog">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-body">
<h3>$modalTitle</h3>
<p>$modalText</p>
</div>
<div class="modal-footer">
<button type="button" class="btn $buttonSecondaryClass" data-dismiss="modal">$buttonSecondary</button>
<button type="button" class="btn $buttonPrimaryClass">$buttonPrimary</button>
</div>
</div>
</div>
</div>
EOF;
}
public static function link($args)
{
$options = 'href="' . $args['href'] . '"';
if (isset($args['class'])) {
$options .= ' class="' . $args['class'] . '"';
}
if (isset($args['target'])) {
$options .= ' target="' . $args['target'] . '"';
}
if (isset($args['icon'])) {
return '<a ' . $options . '><span class="fa fa-' . $args['icon'] . '"></span>' . $args['title'] . '</a>';
}
return '<a ' . $options . '>' . $args['title'] . '</a>';
}
public static function pageTitle($args)
{
$icon = $args['icon'];
$title = $args['title'];
return <<<EOF
<h2 class="mt-0 mb-3">
<span class="fa fa-$icon" style="font-size: 0.9em;"></span><span>$title</span>
</h2>
EOF;
}
public static function formOpen($args)
{
$class = empty($args['class']) ? '' : 'class="' . $args['class'] . '"';
$id = empty($args['id']) ? '' : 'id="' . $args['id'] . '"';
$enctype = empty($args['enctype']) ? '' : 'enctype="' . $args['enctype'] . '"';
$action = empty($args['action']) ? 'action=""' : 'action="' . $args['action'] . '"';
$method = empty($args['method']) ? 'method="post"' : 'method="' . $args['method'] . '"';
$style = empty($args['style']) ? '' : 'style="' . $args['style'] . '"';
return <<<EOF
<form $class $enctype $id $method $action $style autocomplete="off">
EOF;
}
public static function formClose()
{
return <<<EOF
</form>
<script>
$(document).ready(function() {
// Prevent the form submit when press enter key.
$("form").keypress(function(e) {
if ((e.which == 13) && (e.target.type !== "textarea")) {
return false;
}
});
});
</script>
EOF;
}
public static function formTitle($args)
{
$title = $args['title'];
return <<<EOF
<h6 class="mt-4 mb-2 pb-2 border-bottom text-uppercase">$title</h6>
EOF;
}
public static function formInputTextBlock($args)
{
$name = $args['name'];
$disabled = empty($args['disabled']) ? '' : 'disabled';
$placeholder = isset($args['placeholder']) ? $args['placeholder'] : '';
$value = isset($args['value']) ? $args['value'] : '';
$id = 'js' . $name;
if (isset($args['id'])) {
$id = $args['id'];
}
$tip = '';
if (!empty($args['tip'])) {
$tip = '<small class="form-text text-muted">' . $args['tip'] . '</small>';
}
$class = 'form-group m-0';
if (isset($args['class'])) {
$class = $args['class'];
}
$labelClass = 'mt-4 mb-2 pb-2 border-bottom text-uppercase w-100';
if (isset($args['labelClass'])) {
$labelClass = $args['labelClass'];
}
$label = '';
if (!empty($args['label'])) {
$label = '<label class="' . $labelClass . '" for="' . $id . '">' . $args['label'] . '</label>';
}
$type = 'text';
if (isset($args['type'])) {
$type = $args['type'];
}
return <<<EOF
<div class="$class">
$label
<input type="text" dir="auto" value="$value" class="form-control" id="$id" name="$name" placeholder="$placeholder" $disabled>
$tip
</div>
EOF;
}
public static function formInputFile($args)
{
$id = 'js' . $args['name'];
if (isset($args['id'])) {
$id = $args['id'];
}
$class = 'custom-file';
if (isset($args['class'])) {
$class = $class . ' ' . $args['class'];
}
$html = '<div class="' . $class . '">';
$html .= '<input type="file" class="custom-file-input" id="' . $id . '">';
$html .= '<label class="custom-file-label" for="' . $id . '">' . $args['label'] . '</label>';
$html .= '</div>';
return $html;
}
public static function formTextarea($args)
{
$id = 'js' . $args['name'];
if (isset($args['id'])) {
$id = $args['id'];
}
$class = 'form-control';
if (isset($args['class'])) {
$class = $class . ' ' . $args['class'];
}
$html = '<div class="form-group row">';
if (!empty($args['label'])) {
$html .= '<label for="' . $id . '" class="col-sm-2 col-form-label">' . $args['label'] . '</label>';
}
$html .= '<div class="col-sm-10">';
$html .= '<textarea class="' . $class . '" id="' . $id . '" name="' . $args['name'] . '" rows="' . $args['rows'] . '" placeholder="' . $args['placeholder'] . '">' . $args['value'] . '</textarea>';
if (isset($args['tip'])) {
$html .= '<small class="form-text text-muted">' . $args['tip'] . '</small>';
}
$html .= '</div>';
$html .= '</div>';
return $html;
}
public static function formTextareaBlock($args)
{
$id = 'js' . $args['name'];
if (isset($args['id'])) {
$id = $args['id'];
}
$class = 'form-control';
if (!empty($args['class'])) {
$class = $class . ' ' . $args['class'];
}
$html = '<div class="form-group m-0">';
if (!empty($args['label'])) {
$html .= '<label class="mt-4 mb-2 pb-2 border-bottom text-uppercase w-100" for="' . $id . '">' . $args['label'] . '</label>';
}
$html .= '<textarea class="' . $class . '" id="' . $id . '" name="' . $args['name'] . '" rows="' . $args['rows'] . '" placeholder="' . $args['placeholder'] . '">' . $args['value'] . '</textarea>';
if (!empty($args['tip'])) {
$html .= '<small class="form-text text-muted">' . $args['tip'] . '</small>';
}
$html .= '</div>';
return $html;
}
public static function formInputText($args)
{
$name = $args['name'];
$disabled = empty($args['disabled']) ? '' : 'disabled';
$readonly = empty($args['readonly']) ? '' : 'readonly';
$placeholder = isset($args['placeholder']) ? $args['placeholder'] : '';
$value = isset($args['value']) ? $args['value'] : '';
$id = 'js' . $name;
if (isset($args['id'])) {
$id = $args['id'];
}
$tip = '';
if (isset($args['tip'])) {
$tip = '<small class="form-text text-muted">' . $args['tip'] . '</small>';
}
$label = '';
if (isset($args['label'])) {
$label = '<label for="' . $id . '" class="col-sm-2 col-form-label">' . $args['label'] . '</label>';
}
$class = 'form-control';
if (isset($args['class'])) {
$class = $class . ' ' . $args['class'];
}
$type = 'text';
if (isset($args['type'])) {
$type = $args['type'];
}
return <<<EOF
<div class="form-group row">
$label
<div class="col-sm-10">
<input class="$class" id="$id" name="$name" value="$value" placeholder="$placeholder" type="$type" dir="auto" $disabled $readonly>
$tip
</div>
</div>
EOF;
}
public static function formCheckbox($args)
{
$labelForCheckbox = isset($args['labelForCheckbox']) ? $args['labelForCheckbox'] : '';
$placeholder = isset($args['placeholder']) ? $args['placeholder'] : '';
$tip = isset($args['tip']) ? '<small class="form-text text-muted">' . $args['tip'] . '</small>' : '';
$value = isset($args['value']) ? $args['value'] : '';
$name = $args['name'];
$id = 'js' . $name;
if (isset($args['id'])) {
$id = $args['id'];
}
$disabled = isset($args['disabled']) ? 'disabled' : '';
$class = 'form-group m-0';
if (isset($args['class'])) {
$class = $args['class'];
}
$labelClass = 'mt-4 mb-2 pb-2 border-bottom text-uppercase w-100';
if (isset($args['labelClass'])) {
$labelClass = $args['labelClass'];
}
$type = 'text';
if (isset($args['type'])) {
$type = $args['type'];
}
$label = '';
if (!empty($args['label'])) {
$label = '<label class="' . $labelClass . '">' . $args['label'] . '</label>';
}
$checked = $args['checked'] ? 'checked' : '';
$value = $checked ? '1' : '0';
return <<<EOF
<div class="$class">
$label
<div class="form-check">
<input type="hidden" name="$name" value="$value"><input id="$id" type="checkbox" class="form-check-input" onclick="this.previousSibling.value=1-this.previousSibling.value" $checked>
<label class="form-check-label" for="$id">$labelForCheckbox</label>
$tip
</div>
</div>
EOF;
}
public static function formSelect($args)
{
$id = 'js' . $args['name'];
if (isset($args['id'])) {
$id = $args['id'];
}
$class = 'custom-select';
if (isset($args['class'])) {
$class = $class . ' ' . $args['class'];
}
$html = '<div class="form-group row">';
if (isset($args['label'])) {
$html .= '<label for="' . $id . '" class="col-sm-2 col-form-label">' . $args['label'] . '</label>';
}
$html .= '<div class="col-sm-10">';
$html .= '<select id="' . $id . '" name="' . $args['name'] . '" class="' . $class . '">';
foreach ($args['options'] as $key => $value) {
$html .= '<option ' . (($key == $args['selected']) ? 'selected' : '') . ' value="' . $key . '">' . $value . '</option>';
}
$html .= '</select>';
if (isset($args['tip'])) {
$html .= '<small class="form-text text-muted">' . $args['tip'] . '</small>';
}
$html .= '</div>';
$html .= '</div>';
return $html;
}
public static function formSelectBlock($args)
{
$id = 'js' . $args['name'];
if (isset($args['id'])) {
$id = $args['id'];
}
$class = 'custom-select';
if (!empty($args['class'])) {
$class = $class . ' ' . $args['class'];
}
$html = '<div class="form-group m-0">';
if (!empty($args['label'])) {
$html .= '<label class="mt-4 mb-2 pb-2 border-bottom text-uppercase w-100" for="' . $id . '">' . $args['label'] . '</label>';
}
$html .= '<select id="' . $id . '" name="' . $args['name'] . '" class="' . $class . '">';
if (!empty($args['emptyOption'])) {
$html .= '<option value="">' . $args['emptyOption'] . '</option>';
}
foreach ($args['options'] as $key => $value) {
$html .= '<option ' . (($key == $args['selected']) ? 'selected' : '') . ' value="' . $key . '">' . $value . '</option>';
}
$html .= '</select>';
if (!empty($args['tip'])) {
$html .= '<small class="form-text text-muted">' . $args['tip'] . '</small>';
}
$html .= '</div>';
return $html;
}
public static function formInputHidden($args)
{
return '<input type="hidden" id="js' . $args['name'] . '" name="' . $args['name'] . '" value="' . $args['value'] . '">';
}
public static function alert($args)
{
$class = 'alert';
if (!empty($args['class'])) {
$class = $class . ' ' . $args['class'];
}
$text = $args['text'];
return <<<EOF
<div class="$class" role="alert">$text</div>
EOF;
}
}
-312
View File
@@ -1,312 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title><?php echo (defined('BLUDIT_PRO') ? $site->title() : 'BLUDIT') ?> - Login</title>
<meta charset="<?php echo CHARSET ?>">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="robots" content="noindex,nofollow">
<!-- Favicon -->
<link rel="shortcut icon" type="image/x-icon" href="<?php echo HTML_PATH_CORE_IMG . 'favicon.png?version=' . BLUDIT_VERSION ?>">
<!-- CSS -->
<?php
echo Theme::cssBootstrap();
echo Theme::css(array(
'bludit.css',
'bludit.bootstrap.css'
), DOMAIN_ADMIN_THEME_CSS);
?>
<style>
body.login {
min-height: 100vh;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
background: linear-gradient(135deg, #1e88e5 0%, #1565c0 50%, #0d47a1 100%);
padding: 20px;
}
.login-container {
width: 100%;
max-width: 420px;
}
.login-card {
background: #ffffff;
border-radius: 16px;
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.15);
padding: 40px;
animation: fadeInUp 0.5s ease-out;
}
@keyframes fadeInUp {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.login-logo {
text-align: center;
margin-bottom: 30px;
}
.login-logo .logo-icon {
width: 70px;
height: 70px;
background: linear-gradient(135deg, #1e88e5 0%, #1565c0 100%);
border-radius: 16px;
display: inline-flex;
align-items: center;
justify-content: center;
margin-bottom: 15px;
box-shadow: 0 8px 20px rgba(21, 101, 192, 0.4);
}
.login-logo .logo-icon img {
width: 36px;
height: 36px;
filter: brightness(0) invert(1);
}
.login-logo .logo-icon.custom-logo {
background: transparent;
box-shadow: none;
width: auto;
height: auto;
max-width: 150px;
max-height: 80px;
}
.login-logo .logo-icon.custom-logo img {
width: auto;
height: auto;
max-width: 150px;
max-height: 80px;
filter: none;
border-radius: 12px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
}
.login-logo h1 {
font-size: 1.5rem;
font-weight: 600;
color: #1a1a2e;
margin: 0;
}
.login-logo p {
color: #6c757d;
font-size: 0.9rem;
margin-top: 5px;
}
.login-card .form-control {
border: 2px solid #e9ecef;
border-radius: 10px;
padding: 12px 16px;
font-size: 0.95rem;
transition: all 0.3s ease;
background-color: #f8f9fa;
}
.login-card .form-control:focus {
border-color: #1e88e5;
box-shadow: 0 0 0 4px rgba(30, 136, 229, 0.15);
background-color: #fff;
}
.login-card .form-control::placeholder {
color: #adb5bd;
}
.login-card .form-group {
margin-bottom: 20px;
}
.login-card .form-group label {
font-weight: 500;
color: #495057;
margin-bottom: 10px;
font-size: 0.9rem;
}
.login-card .btn-login {
background: linear-gradient(135deg, #1e88e5 0%, #1565c0 100%);
border: none;
border-radius: 10px;
padding: 12px 18px;
font-size: 0.95rem;
font-weight: 600;
color: white;
width: 100%;
transition: all 0.3s ease;
box-shadow: 0 4px 15px rgba(21, 101, 192, 0.4);
}
.login-card .btn-login:hover {
transform: translateY(-2px);
box-shadow: 0 6px 20px rgba(21, 101, 192, 0.5);
}
.login-card .btn-login:active {
transform: translateY(0);
}
.login-card .form-check {
margin-bottom: 25px;
}
.login-card .form-check-input {
width: 18px;
height: 18px;
margin-top: 0;
border: 2px solid #dee2e6;
border-radius: 4px;
}
.login-card .form-check-input:checked {
background-color: #1e88e5;
border-color: #1e88e5;
}
.login-card .form-check-label {
color: #6c757d;
font-size: 0.9rem;
padding-left: 8px;
}
.login-footer {
text-align: center;
margin-top: 25px;
padding-top: 20px;
border-top: 1px solid #e9ecef;
}
.login-footer p {
color: #6c757d;
font-size: 0.85rem;
margin: 0;
}
.login-footer a {
color: #1e88e5;
text-decoration: none;
}
/* Alert styles for login page */
.login-alert {
position: fixed;
top: 20px;
left: 50%;
transform: translateX(-50%);
z-index: 1050;
min-width: 300px;
max-width: 90%;
border-radius: 10px;
padding: 12px 20px;
font-weight: 500;
font-size: 0.9rem;
animation: slideDown 0.4s ease-out;
}
@keyframes slideDown {
from {
opacity: 0;
transform: translateX(-50%) translateY(-20px);
}
to {
opacity: 1;
transform: translateX(-50%) translateY(0);
}
}
.login-alert.alert-danger {
background: linear-gradient(135deg, #ff6b6b 0%, #ee5a5a 100%);
color: white;
border: none;
box-shadow: 0 4px 15px rgba(238, 90, 90, 0.4);
}
.login-alert.alert-success {
background: linear-gradient(135deg, #51cf66 0%, #40c057 100%);
color: white;
border: none;
box-shadow: 0 4px 15px rgba(64, 192, 87, 0.4);
}
/* Input icons */
.input-icon-wrapper {
position: relative;
}
.input-icon-wrapper .form-control {
padding-left: 40px;
}
.input-icon-wrapper .input-icon {
position: absolute;
left: 18px;
top: 50%;
transform: translateY(-50%);
color: #adb5bd;
pointer-events: none;
}
.input-icon-wrapper .form-control:focus + .input-icon,
.input-icon-wrapper .form-control:not(:placeholder-shown) + .input-icon {
color: #1e88e5;
}
</style>
<!-- Javascript -->
<?php
echo Theme::jquery();
echo Theme::jsBootstrap();
?>
<!-- Plugins -->
<?php Theme::plugins('loginHead') ?>
</head>
<body class="login">
<!-- Plugins -->
<?php Theme::plugins('loginBodyBegin') ?>
<!-- Alert -->
<?php if (Alert::defined()): ?>
<div id="login-alert" class="login-alert alert <?php echo (Alert::status() == ALERT_STATUS_FAIL) ? 'alert-danger' : 'alert-success' ?>">
<?php echo Alert::get() ?>
</div>
<script>
setTimeout(function() {
document.getElementById('login-alert').style.display = 'none';
}, <?php echo ALERT_DISAPPEAR_IN * 1000 ?>);
</script>
<?php endif; ?>
<div class="login-container">
<div class="login-card">
<?php
if (Sanitize::pathFile(PATH_ADMIN_VIEWS, $layout['view'] . '.php')) {
include(PATH_ADMIN_VIEWS . $layout['view'] . '.php');
}
?>
</div>
</div>
<!-- Plugins -->
<?php Theme::plugins('loginBodyEnd') ?>
</body>
</html>
-13
View File
@@ -1,13 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<svg viewBox="0 0 500 500" xmlns="http://www.w3.org/2000/svg">
<defs>
<pattern id="pattern-0" x="0" y="0" width="20" height="20" patternUnits="userSpaceOnUse" viewBox="0 0 100 100">
<path d="M 0 0 L 50 0 L 50 100 L 0 100 Z" style="fill: black;"/>
</pattern>
</defs>
<path style="fill: rgb(255, 255, 255); stroke: rgb(0, 0, 0); stroke-miterlimit: 4; stroke-dasharray: none; stroke-width: 24.1239;" d="M 140.612 16.07 C 136.565 10.367 125.853 69.255 108.381 129.061 C 103.577 145.502 110.407 162.089 112.885 176.473 C 117.048 183.779 121.184 194.444 124.691 207.736 C 125.873 212.214 126.903 216.775 127.823 221.339 C 118.109 245.954 112.626 273.846 112.626 303.409 C 112.626 330.642 117.299 356.445 125.623 379.586 C 141.303 391.431 156.518 408.669 169.013 430.093 C 174.86 440.118 179.69 450.381 183.539 460.577 C 203.547 475.159 226.618 483.493 251.189 483.493 C 274.393 483.493 296.256 476.068 315.472 462.963 C 319.453 451.983 324.113 440.618 330.877 430.093 C 344.265 409.265 360.773 389.253 377.633 377.13 C 389.314 358.536 390.837 330.762 389.75 303.409 C 388.621 275.027 384.678 248.146 375.666 224.253 C 376.446 218.449 377.415 212.643 378.618 206.935 C 381.644 192.557 385.537 181.154 389.621 173.676 C 391.233 159.61 396.683 143.363 391.511 127.64 C 370.872 64.898 373.494 118.034 367.485 123.062 C 359.888 129.421 319.716 67.053 321.298 129.061 C 321.471 135.825 321.836 142.432 322.359 148.856 C 301.557 132.637 276.381 152.664 250.36 152.664 C 223.685 152.664 199.605 133.116 178.463 150.095 C 178.74 142.832 178.79 135.327 178.593 127.64 C 177.01 65.631 165.781 51.533 140.612 16.07 Z" id="path2987"/>
<path id="path3763" d="M 248.613 379.622 C 238.675 379.622 215.913 399.093 215.913 412.414 C 215.913 416.062 217.038 419.515 219.042 422.614 C 222.816 424.2 226.478 426.509 229.486 429.38 C 230.893 430.722 232.055 432.096 232.981 433.462 C 237.797 435.415 243.351 436.53 249.265 436.53 C 254.852 436.53 260.114 435.536 264.738 433.78 C 265.698 432.311 266.929 430.827 268.446 429.38 C 271.669 426.304 275.642 423.875 279.701 422.286 C 281.573 419.274 282.618 415.932 282.618 412.414 C 282.618 399.093 259.202 379.622 248.613 379.622 Z" style="fill: rgb(0, 0, 0); stroke: rgb(0, 0, 0); stroke-miterlimit: 4; stroke-dasharray: none; stroke-width: 24.1239;"/>
<path style="fill: rgb(0, 0, 0); stroke: rgb(0, 0, 0); stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 4; stroke-dasharray: none; stroke-width: 24.1239;" d="M 218.799 311.887 C 253.953 253.834 192.034 175.403 161.146 240.701 C 147.314 269.942 179.823 338.489 218.799 311.887 Z" id="path3779"/>
<path style="fill: rgb(0, 0, 0); stroke: rgb(0, 0, 0); stroke-miterlimit: 4; stroke-width: 18;" d="M 311.951 273.021 C 311.951 278.795 306.666 283.478 300.147 283.478 C 293.629 283.478 288.343 278.795 288.343 273.021 C 288.343 267.245 293.629 262.563 300.147 262.563 C 306.666 262.563 311.951 267.245 311.951 273.021 Z" id="path3785"/>
<path style="stroke: rgb(0, 0, 0); stroke-miterlimit: 4; fill: rgb(255, 255, 255); stroke-width: 24.1239;" d="M 229.289 269.988 C 229.289 285.489 216.409 298.058 200.518 298.058 C 184.631 298.058 171.749 285.489 171.749 269.988 C 171.749 254.478 184.631 241.912 200.518 241.912 C 216.409 241.912 229.289 254.478 229.289 269.988 Z" id="path-1"/>
</svg>

Before

Width:  |  Height:  |  Size: 3.3 KiB

-47
View File
@@ -1,47 +0,0 @@
<?php
echo Bootstrap::pageTitle(array('title'=>$L->g('About'), 'icon'=>'info-circle'));
echo '
<table class="table table-striped mt-3">
<tbody>
';
echo '<tr>';
echo '<td>Bludit Edition</td>';
if (defined('BLUDIT_PRO')) {
echo '<td>PRO - '.$L->g('Thanks for supporting Bludit').' <span class="fa fa-heart" style="color: #ffc107"></span></td>';
} else {
echo '<td>Standard - <a target="_blank" href="https://pro.bludit.com">'.$L->g('Upgrade to Bludit PRO').'</a></td>';
}
echo '</tr>';
echo '<tr>';
echo '<td>Bludit Version</td>';
echo '<td>'.BLUDIT_VERSION.'</td>';
echo '</tr>';
echo '<tr>';
echo '<td>Bludit Codename</td>';
echo '<td>'.BLUDIT_CODENAME.'</td>';
echo '</tr>';
echo '<tr>';
echo '<td>Bludit Build Number</td>';
echo '<td>'.BLUDIT_BUILD.'</td>';
echo '</tr>';
echo '<tr>';
echo '<td>Disk usage</td>';
echo '<td>'.Filesystem::bytesToHumanFileSize(Filesystem::getSize(PATH_ROOT)).'</td>';
echo '</tr>';
echo '<tr>';
echo '<td><a href="'.HTML_PATH_ADMIN_ROOT.'developers'.'">Bludit Developers</a></td>';
echo '<td></td>';
echo '</tr>';
echo '
</tbody>
</table>
';
-59
View File
@@ -1,59 +0,0 @@
<?php defined('BLUDIT') or die('Bludit CMS.'); ?>
<?php echo Bootstrap::formOpen(array('id'=>'jsform', 'class'=>'tab-content')); ?>
<div class="align-middle">
<div class="float-right mt-1">
<button type="submit" class="btn btn-primary btn-sm" name="save"><?php $L->p('Save') ?></button>
<a class="btn btn-secondary btn-sm" href="<?php echo HTML_PATH_ADMIN_ROOT.'themes' ?>" role="button"><?php $L->p('Cancel') ?></a>
</div>
<?php echo Bootstrap::pageTitle(array('title'=>$L->g('Blocks'), 'icon'=>'box')); ?>
</div>
<?php
// Token CSRF
echo Bootstrap::formInputHidden(array(
'name'=>'tokenCSRF',
'value'=>$security->getTokenCSRF()
));
foreach ($blocks->getAll() as $block) {
echo Bootstrap::formTitle(array('title'=>$block->title()));
if (Text::isNotEmpty( $block->description() )) {
echo Bootstrap::alert(array('class'=>'alert-primary', 'text'=>$block->description()));
}
echo Bootstrap::formInputText(array(
'name'=>'key[]',
'label'=>$L->g('Key'),
'value'=>$block->key(),
'class'=>'',
'placeholder'=>'',
'tip'=>'',
'readonly'=>true
));
echo Bootstrap::formInputText(array(
'name'=>'title[]',
'label'=>$L->g('title'),
'value'=>$block->title(),
'class'=>'',
'placeholder'=>'',
'tip'=>''
));
echo Bootstrap::formTextarea(array(
'name'=>'value[]',
'label'=>$L->g('Value'),
'value'=>$block->value(),
'class'=>'',
'placeholder'=>'',
'tip'=>'',
'rows'=>5
));
}
echo Bootstrap::formClose();
?>
-33
View File
@@ -1,33 +0,0 @@
<?php defined('BLUDIT') or die('Bludit CMS.');
echo Bootstrap::pageTitle(array('title'=>$L->g('Categories'), 'icon'=>'tags'));
echo Bootstrap::link(array(
'title'=>$L->g('Add a new category'),
'href'=>HTML_PATH_ADMIN_ROOT.'new-category',
'icon'=>'plus'
));
echo '
<table class="table table-striped mt-3">
<thead>
<tr>
<th class="border-bottom-0" scope="col">'.$L->g('Name').'</th>
<th class="border-bottom-0" scope="col">'.$L->g('URL').'</th>
</tr>
</thead>
<tbody>
';
foreach ($categories->keys() as $key) {
$category = new Category($key);
echo '<tr>';
echo '<td><a href="'.HTML_PATH_ADMIN_ROOT.'edit-category/'.$key.'">'.$category->name().'</a></td>';
echo '<td><a href="'.$category->permalink().'">'.$url->filters('category', false).$key.'</a></td>';
echo '</tr>';
}
echo '
</tbody>
</table>
';
@@ -1,31 +0,0 @@
<?php defined('BLUDIT') or die('Bludit CMS.'); ?>
<?php echo Bootstrap::formOpen(array('id'=>'jsform', 'class'=>'plugin-form')); ?>
<div class="align-middle">
<?php if ($plugin->formButtons()): ?>
<div class="float-right mt-1">
<button type="submit" class="btn btn-primary btn-sm" name="save"><?php $L->p('Save') ?></button>
<a class="btn btn-secondary btn-sm" href="<?php echo HTML_PATH_ADMIN_ROOT.'plugins' ?>" role="button"><?php $L->p('Cancel') ?></a>
</div>
<?php endif; ?>
<?php echo Bootstrap::pageTitle(array('title'=>$plugin->name(), 'icon'=>'cog')); ?>
</div>
<?php
// Token CSRF
echo Bootstrap::formInputHidden(array(
'name'=>'tokenCSRF',
'value'=>$security->getTokenCSRF()
));
// Compatibility warning
if (!$plugin->isCompatible()) {
echo '<div class="alert alert-warning mt-2">' . $L->g('This plugin may not be supported by this version of Bludit') . '</div>';
}
// Print the plugin form
echo $plugin->form();
?>
<?php echo Bootstrap::formClose(); ?>
-417
View File
@@ -1,417 +0,0 @@
<?php
echo Bootstrap::pageTitle(array('title'=>$L->g('Content'), 'icon'=>'archive'));
function moveTypeIcon($type) {
$icons = array(
'published' => 'fa-file-text-o',
'sticky' => 'fa-thumb-tack',
'static' => 'fa-file',
'draft' => 'fa-pencil',
);
return isset($icons[$type]) ? $icons[$type] : 'fa-file';
}
function moveTypeLabel($current, $target, $L) {
if ($target === 'sticky') {
return $L->g('Sticky');
}
if ($current === 'sticky' && $target === 'published') {
return $L->g('Unstick');
}
$labels = array(
'published' => $L->g('Move to Page'),
'static' => $L->g('Move to Static'),
'draft' => $L->g('Move to Draft'),
);
return isset($labels[$target]) ? $labels[$target] : $target;
}
// Render a single row (or row + nested children) for a page key.
// $type controls which columns/buttons are shown; $isSticky adds the Sticky badge
// and flips the toggle button into "Unstick" mode.
function tableRow($pageKey, $type, $isSticky = false, $renderChildren = false) {
global $url;
global $L;
try {
$page = new Page($pageKey);
} catch (Exception $e) {
return;
}
$showURL = ($type === 'published' || $type === 'static' || $type === 'sticky');
// Allowed "Move to" transitions per current type.
$moves = array(
'published' => array('sticky', 'static', 'draft'),
'sticky' => array('published', 'static', 'draft'),
'draft' => array('published', 'static'),
'static' => array('published', 'draft'),
);
$dateLabel = '';
if ($type === 'scheduled') {
$dateLabel = $L->g('Scheduled').': '.$page->date(SCHEDULED_DATE_FORMAT);
} elseif ((ORDER_BY === 'position') || ($type !== 'published' && $type !== 'sticky')) {
$dateLabel = $L->g('Position').': '.$page->position();
} else {
$dateLabel = $page->date(MANAGE_CONTENT_DATE_FORMAT);
}
echo '<tr>';
echo '<td class="pt-3">';
echo '<div>';
echo '<a style="font-size: 1.1em" href="'.HTML_PATH_ADMIN_ROOT.'edit-content/'.$page->key().'">';
echo ($page->title() ? $page->title() : '<span class="label-empty-title">'.$L->g('Empty title').'</span> ');
echo '</a>';
if ($isSticky) {
echo ' <span class="badge badge-warning align-middle ml-1" title="'.$L->g('Sticky').'"><i class="fa fa-thumb-tack"></i> '.$L->g('Sticky').'</span>';
}
echo '</div>';
echo '<div><p style="font-size: 0.8em" class="m-0 text-uppercase text-muted">'.$dateLabel.'</p></div>';
echo '</td>';
if ($showURL) {
$friendlyURL = Text::isEmpty($url->filters('page')) ? '/'.$page->key() : '/'.$url->filters('page').'/'.$page->key();
echo '<td class="pt-3 d-none d-xl-table-cell contentURL"><a target="_blank" href="'.$page->permalink().'" title="'.$friendlyURL.'">'.$friendlyURL.'</a></td>';
}
echo '<td class="contentTools pt-3 text-center align-middle">'.PHP_EOL;
echo '<div class="dropdown actionsDropdown">';
echo '<button class="btn btn-link text-secondary p-1 actionsDropdownToggle" type="button" data-toggle="dropdown" data-boundary="viewport" aria-haspopup="true" aria-expanded="false" title="'.$L->g('Actions').'"><i class="fa fa-bars"></i></button>';
echo '<div class="dropdown-menu dropdown-menu-right">';
// View / Edit
if ($showURL) {
echo '<a class="dropdown-item" target="_blank" href="'.$page->permalink().'"><i class="fa fa-desktop fa-fw mr-2"></i>'.$L->g('View').'</a>';
}
echo '<a class="dropdown-item" href="'.HTML_PATH_ADMIN_ROOT.'edit-content/'.$page->key().'"><i class="fa fa-edit fa-fw mr-2"></i>'.$L->g('Edit').'</a>';
// Sticky / Unstick toggle, between View/Edit and Move-to.
$stickyToggleTarget = false;
if ($type === 'published') {
$stickyToggleTarget = 'sticky';
} elseif ($type === 'sticky') {
$stickyToggleTarget = 'published';
}
if ($stickyToggleTarget) {
echo '<div class="dropdown-divider"></div>';
echo '<a href="#" class="dropdown-item changeTypeButton" data-key="'.$page->key().'" data-type="'.$stickyToggleTarget.'"><i class="fa '.moveTypeIcon($stickyToggleTarget).' fa-fw mr-2"></i>'.moveTypeLabel($type, $stickyToggleTarget, $L).'</a>';
}
// Move to ... (everything except the sticky toggle target rendered above).
if (isset($moves[$type])) {
$remaining = array();
foreach ($moves[$type] as $target) {
if ($target !== $stickyToggleTarget) {
$remaining[] = $target;
}
}
if (!empty($remaining)) {
echo '<div class="dropdown-divider"></div>';
foreach ($remaining as $target) {
echo '<a href="#" class="dropdown-item changeTypeButton" data-key="'.$page->key().'" data-type="'.$target.'"><i class="fa '.moveTypeIcon($target).' fa-fw mr-2"></i>'.moveTypeLabel($type, $target, $L).'</a>';
}
}
}
if (count($page->children()) == 0) {
echo '<div class="dropdown-divider"></div>';
echo '<a href="#" class="dropdown-item text-danger deletePageButton" data-toggle="modal" data-target="#jsdeletePageModal" data-key="'.$page->key().'"><i class="fa fa-trash fa-fw mr-2"></i>'.$L->g('Delete').'</a>';
}
echo '</div></div>';
echo '</td>';
echo '</tr>';
if ($renderChildren) {
foreach ($page->children() as $child) {
echo '<tr>';
echo '<td class="child">';
echo '<div>';
echo '<a style="font-size: 1.1em" href="'.HTML_PATH_ADMIN_ROOT.'edit-content/'.$child->key().'">';
echo ($child->title() ? $child->title() : '<span class="label-empty-title">'.$L->g('Empty title').'</span> ');
echo '</a>';
echo '</div>';
echo '<div><p style="font-size: 0.8em" class="m-0 text-uppercase text-muted">'.$L->g('Position').': '.$child->position().'</p></div>';
echo '</td>';
if ($showURL) {
$friendlyChildURL = Text::isEmpty($url->filters('page')) ? '/'.$child->key() : '/'.$url->filters('page').'/'.$child->key();
echo '<td class="d-none d-xl-table-cell contentURL"><a target="_blank" href="'.$child->permalink().'" title="'.$friendlyChildURL.'">'.$friendlyChildURL.'</a></td>';
}
echo '<td class="contentTools pt-3 text-center align-middle">'.PHP_EOL;
echo '<div class="dropdown actionsDropdown">';
echo '<button class="btn btn-link text-secondary p-1 actionsDropdownToggle" type="button" data-toggle="dropdown" data-boundary="viewport" aria-haspopup="true" aria-expanded="false" title="'.$L->g('Actions').'"><i class="fa fa-bars"></i></button>';
echo '<div class="dropdown-menu dropdown-menu-right">';
if ($showURL) {
echo '<a class="dropdown-item" target="_blank" href="'.$child->permalink().'"><i class="fa fa-desktop fa-fw mr-2"></i>'.$L->g('View').'</a>';
}
echo '<a class="dropdown-item" href="'.HTML_PATH_ADMIN_ROOT.'edit-content/'.$child->key().'"><i class="fa fa-edit fa-fw mr-2"></i>'.$L->g('Edit').'</a>';
echo '<div class="dropdown-divider"></div>';
echo '<a href="#" class="dropdown-item text-danger deletePageButton" data-toggle="modal" data-target="#jsdeletePageModal" data-key="'.$child->key().'"><i class="fa fa-trash fa-fw mr-2"></i>'.$L->g('Delete').'</a>';
echo '</div></div>';
echo '</td>';
echo '</tr>';
}
}
}
// Render rows for a list, applying the parent/child nesting rules used by the
// Static tab and by the Pages/Sticky lists when ORDER_BY is "position".
function tableRows($list, $type, $isSticky = false) {
$nestChildren = ($type === 'static') || (ORDER_BY === 'position');
foreach ($list as $pageKey) {
try {
$page = new Page($pageKey);
} catch (Exception $e) {
continue;
}
if ($nestChildren) {
if ($page->isChild()) {
continue;
}
tableRow($pageKey, $type, $isSticky, true);
} else {
tableRow($pageKey, $type, $isSticky, false);
}
}
}
// Render a full table for a single tab (Static, Scheduled, Draft, Autosave).
function table($type) {
global $L;
global $drafts;
global $scheduled;
global $static;
global $autosave;
if ($type === 'draft') {
$list = $drafts;
$emptyMessage = $L->g('There are no draft pages at this moment.');
} elseif ($type === 'scheduled') {
$list = $scheduled;
$emptyMessage = $L->g('There are no scheduled pages at this moment.');
} elseif ($type === 'static') {
$list = $static;
$emptyMessage = $L->g('There are no static pages at this moment.');
} elseif ($type === 'autosave') {
$list = $autosave;
$emptyMessage = '';
} else {
return;
}
if (empty($list) && $type !== 'autosave') {
echo '<p class="mt-4 text-muted">'.$emptyMessage.'</p>';
return;
}
echo '<table class="table mt-3"><thead><tr>';
echo '<th class="border-0" scope="col">'.$L->g('Title').'</th>';
if ($type === 'static') {
echo '<th class="border-0 d-none d-xl-table-cell" scope="col">'.$L->g('URL').'</th>';
}
echo '<th class="border-0 text-center d-sm-table-cell" scope="col">'.$L->g('Actions').'</th>';
echo '</tr></thead><tbody>';
tableRows($list, $type);
echo '</tbody></table>';
}
// Render the Pages tab: sticky rows first, then the paginated published list,
// in a single table.
function tablePages() {
global $L;
global $published;
global $sticky;
global $url;
$isFirstPage = ($url->pageNumber() <= 1);
if (empty($published) && (empty($sticky) || !$isFirstPage)) {
echo '<p class="mt-4 text-muted">'.$L->g('There are no pages at this moment.').'</p>';
return;
}
echo '<table class="table mt-3"><thead><tr>';
echo '<th class="border-0" scope="col">'.$L->g('Title').'</th>';
echo '<th class="border-0 d-none d-xl-table-cell" scope="col">'.$L->g('URL').'</th>';
echo '<th class="border-0 text-center d-sm-table-cell" scope="col">'.$L->g('Actions').'</th>';
echo '</tr></thead><tbody>';
if (!empty($sticky) && $isFirstPage) {
tableRows($sticky, 'sticky', true);
}
if (!empty($published)) {
tableRows($published, 'published', false);
}
echo '</tbody></table>';
}
?>
<!-- TABS -->
<ul class="nav nav-tabs" role="tablist">
<li class="nav-item">
<a class="nav-link active" id="pages-tab" data-toggle="tab" href="#pages" role="tab"><?php $L->p('Pages') ?></a>
</li>
<li class="nav-item">
<a class="nav-link" id="static-tab" data-toggle="tab" href="#static" role="tab"><?php $L->p('Static') ?></a>
</li>
<li class="nav-item">
<a class="nav-link" id="scheduled-tab" data-toggle="tab" href="#scheduled" role="tab"><?php $L->p('Scheduled') ?> <?php if (count($scheduled)>0) { echo '<span class="badge badge-danger">'.count($scheduled).'</span>'; } ?></a>
</li>
<li class="nav-item">
<a class="nav-link" id="draft-tab" data-toggle="tab" href="#draft" role="tab"><?php $L->p('Draft') ?></a>
</li>
<?php if (!empty($autosave)): ?>
<li class="nav-item">
<a class="nav-link" id="autosave-tab" data-toggle="tab" href="#autosave" role="tab"><?php $L->p('Autosave') ?></a>
</li>
<?php endif; ?>
</ul>
<div class="tab-content">
<!-- TABS PAGES (includes sticky on top) -->
<div class="tab-pane show active" id="pages" role="tabpanel">
<?php tablePages(); ?>
<?php if (Paginator::numberOfPages() > 1): ?>
<!-- Paginator -->
<nav class="paginator">
<ul class="pagination flex-wrap justify-content-center">
<!-- First button -->
<li class="page-item <?php if (!Paginator::showPrev()) echo 'disabled' ?>">
<a class="page-link" href="<?php echo Paginator::firstPageUrl() ?>"><span class="align-middle fa fa-media-skip-backward"></span> <?php echo $L->get('First'); ?></a>
</li>
<!-- Previous button -->
<li class="page-item <?php if (!Paginator::showPrev()) echo 'disabled' ?>">
<a class="page-link" href="<?php echo Paginator::previousPageUrl() ?>"><?php echo $L->get('Previous'); ?></a>
</li>
<!-- Next button -->
<li class="page-item <?php if (!Paginator::showNext()) echo 'disabled' ?>">
<a class="page-link" href="<?php echo Paginator::nextPageUrl() ?>"><?php echo $L->get('Next'); ?></a>
</li>
<!-- Last button -->
<li class="page-item <?php if (!Paginator::showNext()) echo 'disabled' ?>">
<a class="page-link" href="<?php echo Paginator::lastPageUrl() ?>"><?php echo $L->get('Last'); ?> <span class="align-middle fa fa-media-skip-forward"></span></a>
</li>
</ul>
</nav>
<?php endif; ?>
</div>
<!-- TABS STATIC -->
<div class="tab-pane" id="static" role="tabpanel">
<?php table('static'); ?>
</div>
<!-- TABS SCHEDULED -->
<div class="tab-pane" id="scheduled" role="tabpanel">
<?php table('scheduled'); ?>
</div>
<!-- TABS DRAFT -->
<div class="tab-pane" id="draft" role="tabpanel">
<?php table('draft'); ?>
</div>
<!-- TABS AUTOSAVE -->
<?php if (!empty($autosave)): ?>
<div class="tab-pane" id="autosave" role="tabpanel">
<?php table('autosave'); ?>
</div>
<?php endif; ?>
</div>
<!-- Modal for delete page -->
<?php
echo Bootstrap::modal(array(
'buttonPrimary'=>$L->g('Delete'),
'buttonPrimaryClass'=>'btn-danger deletePageModalAcceptButton',
'buttonSecondary'=>$L->g('Cancel'),
'buttonSecondaryClass'=>'btn-link',
'modalTitle'=>$L->g('Delete content'),
'modalText'=>$L->g('Are you sure you want to delete this page'),
'modalId'=>'jsdeletePageModal'
));
?>
<script>
$(document).ready(function() {
var key = false;
// Button for delete a page in the table
$(document).on("click", ".deletePageButton", function() {
key = $(this).data('key');
});
// Event from button accept from the modal
$(".deletePageModalAcceptButton").on("click", function() {
var currentTab = window.location.hash ? window.location.hash.substring(1) : 'pages';
var form = jQuery('<form>', {
'action': HTML_PATH_ADMIN_ROOT+'edit-content/'+key,
'method': 'post',
'target': '_top'
}).append(jQuery('<input>', {
'type': 'hidden',
'name': 'tokenCSRF',
'value': tokenCSRF
})).append(jQuery('<input>', {
'type': 'hidden',
'name': 'key',
'value': key
})).append(jQuery('<input>', {
'type': 'hidden',
'name': 'type',
'value': 'delete'
})).append(jQuery('<input>', {
'type': 'hidden',
'name': 'tab',
'value': currentTab
}));
form.hide().appendTo("body").submit();
});
// Move-to: change page type via AJAX
$(document).on("click", ".changeTypeButton", function(e) {
e.preventDefault();
var $btn = $(this);
if ($btn.data('busy')) { return; }
$btn.data('busy', true).css('opacity', 0.5);
$.ajax({
type: "POST",
url: HTML_PATH_ADMIN_ROOT + "ajax/change-type",
data: {
tokenCSRF: tokenCSRF,
key: $btn.data('key'),
type: $btn.data('type')
},
dataType: "json"
}).done(function(response) {
if (response && response.status === 0) {
window.location.reload();
} else {
$btn.data('busy', false).css('opacity', 1);
alert((response && response.message) ? response.message : <?php echo json_encode($L->g('Failed to change type.')); ?>);
}
}).fail(function() {
$btn.data('busy', false).css('opacity', 1);
alert(<?php echo json_encode($L->g('Failed to change type.')); ?>);
});
});
});
</script>
<script>
// Open the tab defined in the URL
const anchor = window.location.hash;
$(`a[href="${anchor}"]`).tab('show');
</script>
-352
View File
@@ -1,352 +0,0 @@
<div id="dashboard" class="container">
<!-- Search with welcome message -->
<?php
$username = $login->username();
$user = new User($username);
$name = '';
if ($user->nickname()) {
$name = $user->nickname();
} elseif ($user->firstName()) {
$name = $user->firstName();
}
?>
<div class="quick-search-trigger mb-4" id="searchTrigger">
<div class="quick-search-icon">
<span class="fa fa-hand-spock-o" id="hello-icon"></span>
</div>
<span class="quick-search-text">
<span id="hello-text"><?php echo $L->g('welcome') . ($name ? ', ' . htmlspecialchars($name, ENT_QUOTES, 'UTF-8') : '') ?></span>
<small class="quick-search-hint"><?php $L->p('click-here-for-quick-search') ?></small>
</span>
<span class="quick-search-shortcut">⌘K</span>
</div>
<script>
$(document).ready(function() {
var date = new Date()
var hours = date.getHours()
var icon, greeting
var suffix = <?php echo json_encode($name ? ', ' . $name : '', JSON_HEX_TAG | JSON_UNESCAPED_UNICODE) ?>
if (hours >= 6 && hours < 12) {
icon = 'fa-sun-o'; greeting = <?php echo json_encode($L->g('good-morning'), JSON_HEX_TAG | JSON_UNESCAPED_UNICODE) ?> + suffix
} else if (hours >= 12 && hours < 18) {
icon = 'fa-sun-o'; greeting = <?php echo json_encode($L->g('good-afternoon'), JSON_HEX_TAG | JSON_UNESCAPED_UNICODE) ?> + suffix
} else if (hours >= 18 && hours < 22) {
icon = 'fa-moon-o'; greeting = <?php echo json_encode($L->g('good-evening'), JSON_HEX_TAG | JSON_UNESCAPED_UNICODE) ?> + suffix
} else {
icon = 'fa-moon-o'; greeting = <?php echo json_encode($L->g('good-night'), JSON_HEX_TAG | JSON_UNESCAPED_UNICODE) ?> + suffix
}
$('#hello-icon').attr('class', 'fa ' + icon)
$('#hello-text').text(greeting)
});
</script>
<!-- Quick Search Modal -->
<div class="quick-search-modal" id="searchModal">
<div class="quick-search-overlay" id="searchOverlay"></div>
<div class="quick-search-content">
<div class="quick-search-header">
<span class="fa fa-search"></span>
<input type="text" id="jsclippy" class="quick-search-input" placeholder="<?php $L->p('search-placeholder') ?>">
</div>
<div id="searchResults" class="quick-search-results"></div>
</div>
</div>
<script>
$(document).ready(function() {
var searchInput = $('#jsclippy');
var searchResults = $('#searchResults');
var modal = $('#searchModal');
var trigger = $('#searchTrigger');
var overlay = $('#searchOverlay');
var searchTimeout;
function openSearch() {
modal.addClass('active');
$('body').css('overflow', 'hidden');
setTimeout(function() {
searchInput.focus();
}, 150);
}
function closeSearch() {
modal.removeClass('active');
$('body').css('overflow', '');
searchInput.val('');
searchResults.empty();
}
function performSearch(query) {
if (!query) {
searchResults.empty();
return;
}
$.ajax({
url: HTML_PATH_ADMIN_ROOT + "ajax/clippy",
data: { query: query },
success: function(data) {
searchResults.empty();
if (data.results && data.results.length > 0) {
data.results.forEach(function(item) {
var resultHtml = '';
if (item.type == 'menu') {
resultHtml = '<a href="' + item.url + '" class="search-suggestion">';
resultHtml += '<span class="fa fa-' + item.icon + '"></span>' + item.text + '</a>';
} else {
resultHtml = '<div class="search-suggestion">';
resultHtml += '<div class="search-suggestion-item">' + item.text + ' <span class="badge badge-pill badge-light">' + item.type + '</span></div>';
resultHtml += '<div class="search-suggestion-options">';
resultHtml += '<a target="_blank" href="' + DOMAIN_PAGES + item.id + '"><?php $L->p('view') ?></a>';
resultHtml += '<a class="ml-2" href="' + DOMAIN_ADMIN + 'edit-content/' + item.id + '"><?php $L->p('edit') ?></a>';
resultHtml += '</div></div>';
}
searchResults.append(resultHtml);
});
} else {
searchResults.html('<div class="search-no-results"><?php $L->p("no-results-found") ?></div>');
}
}
});
}
searchInput.on('input', function() {
clearTimeout(searchTimeout);
var query = $(this).val();
searchTimeout = setTimeout(function() {
performSearch(query);
}, 300);
});
trigger.on('click', openSearch);
overlay.on('click', closeSearch);
$(document).on('keydown', function(e) {
if (e.key === 'Escape' && modal.hasClass('active')) {
closeSearch();
}
if ((e.metaKey || e.ctrlKey) && e.key === 'k') {
e.preventDefault();
openSearch();
}
});
});
</script>
<!-- Dashboard Metric Cards -->
<div class="container px-0">
<div class="row">
<!-- Content Metrics Card -->
<div class="col-lg col-12 mb-4">
<div class="card metric-card">
<div class="card-body">
<div class="d-flex align-items-center mb-3">
<h5 class="card-title mb-0"><?php $L->p('Content') ?></h5>
</div>
<div class="mt-3 metric-card-list">
<div class="list-group list-group-flush">
<a href="<?php echo HTML_PATH_ADMIN_ROOT.'content' ?>" class="list-group-item d-flex justify-content-between align-items-center">
<span><?php $L->p('Published') ?></span>
<span class="badge badge-primary badge-pill"><?php echo count($pages->getPublishedDB()); ?></span>
</a>
<a href="<?php echo HTML_PATH_ADMIN_ROOT.'content#draft' ?>" class="list-group-item d-flex justify-content-between align-items-center">
<span><?php $L->p('Drafts') ?></span>
<span class="badge badge-primary badge-pill"><?php echo count($pages->getDraftDB()); ?></span>
</a>
<a href="<?php echo HTML_PATH_ADMIN_ROOT.'content#scheduled' ?>" class="list-group-item d-flex justify-content-between align-items-center">
<span><?php $L->p('Scheduled') ?></span>
<span class="badge badge-primary badge-pill"><?php echo count($pages->getScheduledDB()); ?></span>
</a>
</div>
</div>
</div>
</div>
</div>
<?php
// Categories Card - Only show if Categories plugin is active
if (pluginActivated('pluginCategories')) {
$categoryList = $categories->keys();
?>
<!-- Categories Card -->
<div class="col-lg col-12 mb-4">
<div class="card metric-card">
<div class="card-body">
<div class="d-flex align-items-center mb-3">
<h5 class="card-title mb-0"><?php $L->p('Categories') ?></h5>
</div>
<div class="mt-3 metric-card-list">
<?php if (!empty($categoryList)): ?>
<div class="list-group list-group-flush">
<?php foreach ($categoryList as $categoryKey):
$category = new Category($categoryKey);
$pageCount = count($category->pages());
?>
<a href="<?php echo HTML_PATH_ADMIN_ROOT . 'edit-category/' . $categoryKey ?>" class="list-group-item d-flex justify-content-between align-items-center">
<span><?php echo $category->name() ?></span>
<span class="badge badge-primary badge-pill"><?php echo $pageCount ?></span>
</a>
<?php endforeach; ?>
</div>
<?php else: ?>
<p class="text-muted text-center py-3"><?php $L->p('No categories') ?></p>
<?php endif; ?>
</div>
</div>
</div>
</div>
<?php } ?>
<?php
// Tags Card - Only show if Tags plugin is active
if (pluginActivated('pluginTags')) {
$tagList = $tags->keys();
?>
<!-- Tags Card -->
<div class="col-lg col-12 mb-4">
<div class="card metric-card">
<div class="card-body">
<div class="d-flex align-items-center mb-3">
<h5 class="card-title mb-0"><?php $L->p('Tags') ?></h5>
</div>
<div class="mt-3 metric-card-list">
<?php if (!empty($tagList)): ?>
<div class="list-group list-group-flush">
<?php foreach ($tagList as $tagKey):
$tag = new Tag($tagKey);
$pageCount = count($tag->pages());
?>
<a href="<?php echo HTML_PATH_ADMIN_ROOT . 'content/tag/' . $tagKey ?>" class="list-group-item d-flex justify-content-between align-items-center">
<span><?php echo $tag->name() ?></span>
<span class="badge badge-primary badge-pill"><?php echo $pageCount ?></span>
</a>
<?php endforeach; ?>
</div>
<?php else: ?>
<p class="text-muted text-center py-3"><?php $L->p('No tags') ?></p>
<?php endif; ?>
</div>
</div>
</div>
</div>
<?php } ?>
</div>
</div>
<?php
// Analytics Section - Only show if Visits Stats plugin is active
$visitsStats = getPlugin('pluginVisitsStats');
if ($visitsStats && $visitsStats->installed()):
$currentDate = Date::current('Y-m-d');
$visitsToday = $visitsStats->visits($currentDate);
$uniqueVisitors = $visitsStats->uniqueVisitors($currentDate);
$weekData = $visitsStats->getLastDaysData(7);
?>
<!-- Analytics Section -->
<div class="analytics-section mb-4">
<ul class="list-group list-group-striped b-0 mb-3">
<li class="list-group-item">
<h4 class="m-0"><?php $L->p('Analytics') ?></h4>
</li>
</ul>
<div class="row align-items-center">
<div class="col-lg-3 col-12 mb-3 mb-lg-0">
<div class="row text-center">
<div class="col-4 col-lg-12 mb-0 mb-lg-4">
<div class="metric-value"><?php echo $visitsToday; ?></div>
<div class="metric-label"><?php $L->p('Visits Today') ?></div>
</div>
<div class="col-4 col-lg-12 mb-0 mb-lg-4">
<div class="metric-value"><?php echo $uniqueVisitors; ?></div>
<div class="metric-label"><?php $L->p('Unique Visitors') ?></div>
</div>
<div class="col-4 col-lg-12">
<div class="metric-value"><?php echo $weekData['total']; ?></div>
<div class="metric-label"><?php $L->p('7-Day Total') ?></div>
</div>
</div>
</div>
<div class="col-lg-9 col-12">
<canvas id="analytics-chart"></canvas>
</div>
</div>
</div>
<script>
(function() {
var ctx = document.getElementById('analytics-chart');
if (!ctx || typeof Chart === 'undefined') { return; }
new Chart(ctx, {
type: 'bar',
data: {
labels: <?php echo json_encode($weekData['labels']); ?>,
datasets: [{
label: <?php echo json_encode($L->g('unique-visitors'), JSON_HEX_TAG | JSON_UNESCAPED_UNICODE) ?>,
backgroundColor: 'rgba(0,120,212,0.45)',
borderColor: 'rgba(0,120,212,0.75)',
borderWidth: 1,
data: <?php echo json_encode($weekData['unique']); ?>
}, {
label: <?php echo json_encode($L->g('visits-today'), JSON_HEX_TAG | JSON_UNESCAPED_UNICODE) ?>,
backgroundColor: 'rgba(148,163,184,0.5)',
borderColor: 'rgba(100,116,139,0.8)',
borderWidth: 1,
data: <?php echo json_encode($weekData['visits']); ?>
}]
},
options: {
responsive: true,
maintainAspectRatio: true,
aspectRatio: 4,
legend: {
display: true,
position: 'bottom',
labels: { fontSize: 11, boxWidth: 12, fontColor: '#475569' }
},
scales: {
yAxes: [{
ticks: { beginAtZero: true, stepSize: 1, fontColor: '#94A3B8', fontSize: 11 },
gridLines: { color: 'rgba(0,0,0,0.05)', zeroLineColor: 'rgba(0,0,0,0.1)' }
}],
xAxes: [{
ticks: { fontColor: '#94A3B8', fontSize: 11 },
gridLines: { display: false }
}]
},
tooltips: { mode: 'index', intersect: false }
}
});
})();
</script>
<?php endif; ?>
<!-- Notifications -->
<div class="mt-4">
<ul class="list-group list-group-striped b-0">
<li class="list-group-item">
<h4 class="m-0"><?php $L->p('Notifications') ?></h4>
</li>
<?php
$logs = array_slice($syslog->db, 0, NOTIFICATIONS_AMOUNT);
foreach ($logs as $log) {
$phrase = $L->g($log['dictionaryKey']);
echo '<li class="list-group-item">';
echo $phrase;
if (!empty($log['notes'])) {
echo ' « <b>' . htmlspecialchars($log['notes'], ENT_QUOTES, 'UTF-8') . '</b> »';
}
echo '<br><span class="notification-date"><small>';
echo Date::format($log['date'], DB_DATE_FORMAT, NOTIFICATIONS_DATE_FORMAT);
echo ' [ ' . htmlspecialchars($log['username'], ENT_QUOTES, 'UTF-8') . ' ]';
echo '</small></span>';
echo '</li>';
}
?>
</ul>
</div>
</div>
-38
View File
@@ -1,38 +0,0 @@
<?php
echo Bootstrap::pageTitle(array('title'=>$L->g('Developers'), 'icon'=>'gears'));
echo '<h2 class="mb-4 mt-4"><b>PHP version: '.phpversion().'</b></h2>';
// PHP Ini
$uploadOptions = array(
'upload_max_filesize'=>ini_get('upload_max_filesize'),
'post_max_size'=>ini_get('post_max_size'),
'upload_tmp_dir'=>ini_get('upload_tmp_dir')
);
printTable('File Uploads', $uploadOptions);
// Loaded extensions
printTable('Server information ( $_SERVER )', $_SERVER);
// PHP Ini
printTable('PHP Configuration options ( ini_get_all() )', ini_get_all());
// Loaded extensions
printTable('Loaded extensions',get_loaded_extensions());
// Locales installed
exec('locale -a', $locales);
printTable('Locales installed', $locales);
echo '<hr>';
echo '<h2>BLUDIT</h2>';
echo '<hr>';
// Constanst defined by Bludit
$constants = get_defined_constants(true);
printTable('Bludit Constants', $constants['user']);
// Site object
printTable('$site object database',$site->db);
-92
View File
@@ -1,92 +0,0 @@
<?php defined('BLUDIT') or die('Bludit CMS.'); ?>
<?php echo Bootstrap::formOpen(array('id'=>'jsform')); ?>
<div class="align-middle">
<div class="float-right mt-1">
<button type="submit" class="btn btn-primary btn-sm" name="save"><?php $L->p('Save') ?></button>
<button type="button" class="btn btn-danger btn-sm" data-toggle="modal" data-target="#jsdeleteModal"><?php $L->p('Delete') ?></button>
<a class="btn btn-secondary btn-sm" href="<?php echo HTML_PATH_ADMIN_ROOT.'dashboard' ?>" role="button"><?php $L->p('Cancel') ?></a>
</div>
<?php echo Bootstrap::pageTitle(array('title'=>$L->g('Edit Category'), 'icon'=>'cog')); ?>
</div>
<?php
// Token CSRF
echo Bootstrap::formInputHidden(array(
'name'=>'tokenCSRF',
'value'=>$security->getTokenCSRF()
));
echo Bootstrap::formInputHidden(array(
'name'=>'action',
'value'=>'edit'
));
echo Bootstrap::formInputHidden(array(
'name'=>'oldKey',
'value'=>$categoryMap['key']
));
echo Bootstrap::formInputText(array(
'name'=>'name',
'label'=>$L->g('Name'),
'value'=>$categoryMap['name'],
'class'=>'',
'placeholder'=>'',
'tip'=>''
));
echo Bootstrap::formTextarea(array(
'name'=>'description',
'label'=>$L->g('Description'),
'value'=>isset($categoryMap['description'])?$categoryMap['description']:'',
'class'=>'',
'placeholder'=>'',
'tip'=>'',
'rows'=>3
));
echo Bootstrap::formInputText(array(
'name'=>'template',
'label'=>$L->g('Template'),
'value'=>isset($categoryMap['template'])?$categoryMap['template']:'',
'class'=>'',
'placeholder'=>'',
'tip'=>''
));
echo Bootstrap::formInputText(array(
'name'=>'newKey',
'label'=>$L->g('Friendly URL'),
'value'=>$categoryMap['key'],
'class'=>'',
'placeholder'=>'',
'tip'=>DOMAIN_CATEGORIES.$categoryMap['key']
));
echo Bootstrap::formClose();
?>
<!-- Modal for delete category -->
<?php
echo Bootstrap::modal(array(
'buttonPrimary'=>$L->g('Delete'),
'buttonPrimaryClass'=>'btn-danger jsbuttonDeleteAccept',
'buttonSecondary'=>$L->g('Cancel'),
'buttonSecondaryClass'=>'btn-link',
'modalTitle'=>$L->g('Delete category'),
'modalText'=>$L->g('Are you sure you want to delete this category?'),
'modalId'=>'jsdeleteModal'
));
?>
<script>
$(document).ready(function() {
// Delete content
$(".jsbuttonDeleteAccept").on("click", function() {
$("#jsaction").val("delete");
$("#jsform").submit();
});
});
</script>
-564
View File
@@ -1,564 +0,0 @@
<?php defined('BLUDIT') or die('Bludit CMS.'); ?>
<?php
// FORM START
echo Bootstrap::formOpen(array(
'id' => 'jsform',
'class' => 'd-flex flex-column h-100'
));
// Token CSRF
echo Bootstrap::formInputHidden(array(
'name' => 'tokenCSRF',
'value' => $security->getTokenCSRF()
));
// UUID
// The UUID is generated in the controller
echo Bootstrap::formInputHidden(array(
'name' => 'uuid',
'value' => $page->uuid()
));
// Type = published, draft, sticky, static
echo Bootstrap::formInputHidden(array(
'name' => 'type',
'value' => $page->type()
));
// Cover image
echo Bootstrap::formInputHidden(array(
'name' => 'coverImage',
'value' => $page->coverImage(false)
));
// Content
echo Bootstrap::formInputHidden(array(
'name' => 'content',
'value' => ''
));
// Current page key
echo Bootstrap::formInputHidden(array(
'name' => 'key',
'value' => $page->key()
));
?>
<!-- TOOLBAR -->
<div id="jseditorToolbar" class="mb-1">
<div id="jseditorToolbarRight" class="btn-group btn-group-sm float-right" role="group" aria-label="Toolbar right">
<button type="button" class="btn btn-light" id="jsmediaManagerOpenModal" data-toggle="modal" data-target="#jsmediaManagerModal"><span class="fa fa-image"></span> <?php $L->p('Images') ?></button>
<?php Theme::plugins('editorToolbar') ?>
<button type="button" class="btn btn-light" id="jsoptionsSidebar" style="z-index:30"><span class="fa fa-cog"></span> <?php $L->p('Options') ?></button>
</div>
<div id="jseditorToolbarLeft">
<button type="button" class="btn btn-sm btn-primary" id="jsbuttonSave"><?php echo $L->g('Save') ?></button>
<button id="jsbuttonPreview" type="button" class="btn btn-sm btn-secondary"><?php $L->p('Preview') ?></button>
<span id="jsswitchButton" data-switch="<?php echo ($page->draft() ? 'draft' : 'publish') ?>" class="ml-2 text-secondary switch-button"><i class="fa fa-square switch-icon-<?php echo ($page->draft() ? 'draft' : 'publish') ?>"></i> <?php echo ($page->draft() ? $L->g('Draft') : $L->g('Publish')) ?></span>
</div>
<?php if ($page->scheduled()) : ?>
<div class="alert alert-warning p-1 mt-1 mb-0"><?php $L->p('scheduled') ?>: <?php echo $page->date(SCHEDULED_DATE_FORMAT) ?></div>
<?php endif; ?>
</div>
<script>
$(document).ready(function() {
$("#jsoptionsSidebar").on("click", function() {
$("#jseditorSidebar").toggle();
$("#jsshadow").toggle();
});
$("#jsshadow").on("click", function() {
$("#jseditorSidebar").toggle();
$("#jsshadow").toggle();
});
});
</script>
<!-- SIDEBAR OPTIONS -->
<div id="jseditorSidebar">
<nav>
<div class="nav nav-tabs" id="nav-tab" role="tablist">
<a class="nav-link active show" id="nav-general-tab" data-toggle="tab" href="#nav-general" role="tab" aria-controls="general"><?php $L->p('General') ?></a>
<a class="nav-link" id="nav-advanced-tab" data-toggle="tab" href="#nav-advanced" role="tab" aria-controls="advanced"><?php $L->p('Advanced') ?></a>
<?php if (!empty($site->customFields())) : ?>
<a class="nav-link" id="nav-custom-tab" data-toggle="tab" href="#nav-custom" role="tab" aria-controls="custom"><?php $L->p('Custom') ?></a>
<?php endif ?>
<a class="nav-link" id="nav-seo-tab" data-toggle="tab" href="#nav-seo" role="tab" aria-controls="seo"><?php $L->p('SEO') ?></a>
</div>
</nav>
<div class="tab-content pr-3 pl-3 pb-3">
<div id="nav-general" class="tab-pane fade show active" role="tabpanel" aria-labelledby="general-tab">
<?php
// Category
echo Bootstrap::formSelectBlock(array(
'name' => 'category',
'label' => $L->g('Category'),
'selected' => $page->categoryKey(),
'class' => '',
'emptyOption' => '- ' . $L->g('Uncategorized') . ' -',
'options' => $categories->getKeyNameArray()
));
// Description
echo Bootstrap::formTextareaBlock(array(
'name' => 'description',
'label' => $L->g('Description'),
'selected' => '',
'class' => '',
'value' => $page->description(),
'rows' => 5,
'placeholder' => $L->get('this-field-can-help-describe-the-content')
));
?>
<!-- Cover Image -->
<?php
$coverImage = $page->coverImage(false);
$externalCoverImage = '';
if (filter_var($coverImage, FILTER_VALIDATE_URL)) {
$coverImage = '';
$externalCoverImage = $page->coverImage(false);
}
?>
<label class="mt-4 mb-2 pb-2 border-bottom text-uppercase w-100"><?php $L->p('Cover Image') ?></label>
<div>
<img id="jscoverImagePreview" class="mx-auto d-block w-100" alt="Cover image preview" src="<?php echo (empty($coverImage) ? HTML_PATH_CORE_IMG . 'default.svg' : $page->coverImage()) ?>" />
</div>
<div class="mt-2 text-center">
<button type="button" id="jsbuttonSelectCoverImage" class="btn btn-primary btn-sm"><?php echo $L->g('Select cover image') ?></button>
<button type="button" id="jsbuttonRemoveCoverImage" class="btn btn-secondary btn-sm"><?php echo $L->g('Remove cover image') ?></button>
</div>
<script>
$(document).ready(function() {
$("#jscoverImagePreview").on("click", function() {
openMediaManager();
});
$("#jsbuttonSelectCoverImage").on("click", function() {
openMediaManager();
});
$("#jsbuttonRemoveCoverImage").on("click", function() {
$("#jscoverImage").val('');
$("#jscoverImagePreview").attr('src', HTML_PATH_CORE_IMG + 'default.svg');
});
});
</script>
</div>
<div id="nav-advanced" class="tab-pane fade" role="tabpanel" aria-labelledby="advanced-tab">
<?php
// Date
echo Bootstrap::formInputTextBlock(array(
'name' => 'date',
'label' => $L->g('Date'),
'placeholder' => '',
'value' => $page->dateRaw(),
'tip' => $L->g('date-format-format')
));
// Type
echo Bootstrap::formSelectBlock(array(
'name' => 'typeSelector',
'label' => $L->g('Type'),
'selected' => $page->type(),
'options' => array(
'published' => '- ' . $L->g('Default') . ' -',
'sticky' => $L->g('Sticky'),
'static' => $L->g('Static')
),
'tip' => ''
));
// Position
echo Bootstrap::formInputTextBlock(array(
'name' => 'position',
'label' => $L->g('Position'),
'tip' => $L->g('Field used when ordering content by position'),
'value' => $page->position()
));
// Tags
echo Bootstrap::formInputTextBlock(array(
'name' => 'tags',
'label' => $L->g('Tags'),
'placeholder' => '',
'tip' => $L->g('Write the tags separated by commas'),
'value' => $page->tags()
));
// Parent
try {
$options = array();
$parentKey = $page->parent();
if (!empty($parentKey)) {
$parent = new Page($parentKey);
$options = array($parentKey => $parent->title());
}
} catch (Exception $e) {
// continue
}
echo Bootstrap::formSelectBlock(array(
'name' => 'parent',
'label' => $L->g('Parent'),
'options' => $options,
'selected' => false,
'class' => '',
'tip' => $L->g('Start typing a page title to see a list of suggestions.'),
));
?>
<script>
$(document).ready(function() {
var parent = $("#jsparent").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>
<?php
// Template
echo Bootstrap::formInputTextBlock(array(
'name' => 'template',
'label' => $L->g('Template'),
'placeholder' => '',
'value' => $page->template(),
'tip' => $L->g('Write a template name to filter the page in the theme and change the style of the page.')
));
echo Bootstrap::formInputTextBlock(array(
'name' => 'externalCoverImage',
'label' => $L->g('External cover image'),
'placeholder' => "https://",
'value' => $externalCoverImage,
'tip' => $L->g('Set a cover image from external URL, such as a CDN or some server dedicated for images.')
));
// Username
echo Bootstrap::formInputTextBlock(array(
'name' => '',
'label' => $L->g('Author'),
'placeholder' => '',
'value' => $page->username(),
'tip' => '',
'disabled' => true
));
?>
<script>
$(document).ready(function() {
// Changes in External cover image input
$("#jsexternalCoverImage").change(function() {
$("#jscoverImage").val($(this).val());
});
// Datepicker
$("#jsdate").datetimepicker({
format: DB_DATE_FORMAT
});
});
</script>
</div>
<?php if (!empty($site->customFields())) : ?>
<div id="nav-custom" class="tab-pane fade" role="tabpanel" aria-labelledby="custom-tab">
<?php
$customFields = $site->customFields();
foreach ($customFields as $field => $options) {
if (!isset($options['position'])) {
if ($options['type'] == "string") {
echo Bootstrap::formInputTextBlock(array(
'name' => 'custom[' . $field . ']',
'value' => (isset($options['default']) ? $options['default'] : ''),
'tip' => (isset($options['tip']) ? $options['tip'] : ''),
'label' => (isset($options['label']) ? $options['label'] : ''),
'placeholder' => (isset($options['placeholder']) ? $options['placeholder'] : ''),
'value' => $page->custom($field)
));
} elseif ($options['type'] == "bool") {
echo Bootstrap::formCheckbox(array(
'name' => 'custom[' . $field . ']',
'label' => (isset($options['label']) ? $options['label'] : ''),
'placeholder' => (isset($options['placeholder']) ? $options['placeholder'] : ''),
'checked' => $page->custom($field),
'labelForCheckbox' => (isset($options['tip']) ? $options['tip'] : '')
));
}
}
}
?>
</div>
<?php endif ?>
<div id="nav-seo" class="tab-pane fade" role="tabpanel" aria-labelledby="seo-tab">
<?php
// Friendly URL
echo Bootstrap::formInputTextBlock(array(
'name' => 'slug',
'tip' => $L->g('URL associated with the content'),
'label' => $L->g('Friendly URL'),
'placeholder' => $L->g('Leave empty for autocomplete by Bludit.'),
'value' => $page->slug()
));
// Robots
echo Bootstrap::formCheckbox(array(
'name' => 'noindex',
'label' => 'Robots',
'labelForCheckbox' => $L->g('apply-code-noindex-code-to-this-page'),
'placeholder' => '',
'checked' => $page->noindex(),
'tip' => $L->g('This tells search engines not to show this page in their search results.')
));
// Robots
echo Bootstrap::formCheckbox(array(
'name' => 'nofollow',
'label' => '',
'labelForCheckbox' => $L->g('apply-code-nofollow-code-to-this-page'),
'placeholder' => '',
'checked' => $page->nofollow(),
'tip' => $L->g('This tells search engines not to follow links on this page.')
));
// Robots
echo Bootstrap::formCheckbox(array(
'name' => 'noarchive',
'label' => '',
'labelForCheckbox' => $L->g('apply-code-noarchive-code-to-this-page'),
'placeholder' => '',
'checked' => $page->noarchive(),
'tip' => $L->g('This tells search engines not to save a cached copy of this page.')
));
?>
</div>
</div>
</div>
<!-- Custom fields: TOP -->
<?php
$customFields = $site->customFields();
foreach ($customFields as $field => $options) {
if (isset($options['position']) && ($options['position'] == 'top')) {
if ($options['type'] == "string") {
echo Bootstrap::formInputTextBlock(array(
'name' => 'custom[' . $field . ']',
'label' => (isset($options['label']) ? $options['label'] : ''),
'value' => $page->custom($field),
'tip' => (isset($options['tip']) ? $options['tip'] : ''),
'placeholder' => (isset($options['placeholder']) ? $options['placeholder'] : ''),
'class' => 'mb-2',
'labelClass' => 'mb-2 pb-2 border-bottom text-uppercase w-100'
));
} elseif ($options['type'] == "bool") {
echo Bootstrap::formCheckbox(array(
'name' => 'custom[' . $field . ']',
'label' => (isset($options['label']) ? $options['label'] : ''),
'placeholder' => (isset($options['placeholder']) ? $options['placeholder'] : ''),
'checked' => $page->custom($field),
'labelForCheckbox' => (isset($options['tip']) ? $options['tip'] : ''),
'class' => 'mb-2',
'labelClass' => 'mb-2 pb-2 border-bottom text-uppercase w-100'
));
}
}
}
?>
<!-- Title -->
<div class="form-group mb-1">
<input id="jstitle" name="title" type="text" dir="auto" class="form-control form-control-lg rounded-0" value="<?php echo $page->title() ?>" placeholder="<?php $L->p('Enter title') ?>">
</div>
<!-- Editor -->
<textarea id="jseditor" class="editable h-100" style=""><?php echo $page->contentRaw(true) ?></textarea>
<!-- Custom fields: BOTTOM -->
<?php
$customFields = $site->customFields();
foreach ($customFields as $field => $options) {
if (isset($options['position']) && ($options['position'] == 'bottom')) {
if ($options['type'] == "string") {
echo Bootstrap::formInputTextBlock(array(
'name' => 'custom[' . $field . ']',
'label' => (isset($options['label']) ? $options['label'] : ''),
'value' => $page->custom($field),
'tip' => (isset($options['tip']) ? $options['tip'] : ''),
'placeholder' => (isset($options['placeholder']) ? $options['placeholder'] : ''),
'class' => 'mt-2',
'labelClass' => 'mb-2 pb-2 border-bottom text-uppercase w-100'
));
} elseif ($options['type'] == "bool") {
echo Bootstrap::formCheckbox(array(
'name' => 'custom[' . $field . ']',
'label' => (isset($options['label']) ? $options['label'] : ''),
'placeholder' => (isset($options['placeholder']) ? $options['placeholder'] : ''),
'checked' => $page->custom($field),
'labelForCheckbox' => (isset($options['tip']) ? $options['tip'] : ''),
'class' => 'mt-2',
'labelClass' => 'mb-2 pb-2 border-bottom text-uppercase w-100'
));
}
}
}
?>
</form>
<!-- Modal for Delete page -->
<div id="jsdeletePageModal" class="modal" tabindex="-1" role="dialog">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-body">
<h3><?php $L->p('Delete content') ?></h3>
<p><?php $L->p('Are you sure you want to delete this page') ?></p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-link" data-dismiss="modal"><?php $L->p('Cancel') ?></button>
<button type="button" class="btn btn-danger" data-dismiss="modal" id="jsbuttonDeleteAccept"><?php $L->p('Delete') ?></button>
</div>
</div>
</div>
<script>
$(document).ready(function() {
$("#jsbuttonDeleteAccept").on("click", function() {
$("#jstype").val("delete");
$("#jscontent").val("");
$("#jsform").submit();
});
});
</script>
</div>
<!-- Modal for Media Manager -->
<?php include(PATH_ADMIN_THEMES . 'booty/html/media.php'); ?>
<script>
$(document).ready(function() {
// Define function if they don't exist
// This helps if the user doesn't activate any plugin as editor
if (typeof editorGetContent != "function") {
window.editorGetContent = function() {
return $("#jseditor").val();
};
}
if (typeof editorInsertMedia != "function") {
window.editorInsertMedia = function(filename) {
$("#jseditor").val($('#jseditor').val() + '<img src="' + filename + '" alt="">');
};
}
if (typeof editorInsertLinkedMedia != "function") {
window.editorInsertLinkedMedia = function(filename, link) {
$("#jseditor").val($('#jseditor').val() + '<a href="' + link + '"><img src="' + filename + '" alt=""></a>');
};
}
// Button switch
$("#jsswitchButton").on("click", function() {
if ($(this).data("switch") == "publish") {
$(this).html('<i class="fa fa-square switch-icon-draft"></i> <?php $L->p('Draft') ?>');
$(this).data("switch", "draft");
} else {
$(this).html('<i class="fa fa-square switch-icon-publish"></i> <?php $L->p('Publish') ?>');
$(this).data("switch", "publish");
}
});
// Button preview
$("#jsbuttonPreview").on("click", function() {
var uuid = $("#jsuuid").val();
var title = $("#jstitle").val();
var content = editorGetContent();
var ajax = new bluditAjax();
bluditAjax.saveAsDraft(uuid, title, content).then(function(data) {
var preview = window.open("<?php echo DOMAIN_PAGES . 'autosave-' . $page->uuid() . '?preview=' . hash_hmac('sha256', 'autosave-' . $page->uuid(), DB_SITE) ?>", "bludit-preview");
preview.focus();
});
});
// Button Save
$("#jsbuttonSave").on("click", function() {
// If the switch is setted to "published", get the value from the selector
if ($("#jsswitchButton").data("switch") == "publish") {
var value = $("#jstypeSelector option:selected").val();
$("#jstype").val(value);
} else {
$("#jstype").val("draft");
}
// Get the content
$("#jscontent").val(editorGetContent());
// Submit the form
$("#jsform").submit();
});
// Button Save as draft
$("#jsbuttonDraft").on("click", function() {
// Set the type as draft
$("#jstype").val("draft");
// Get the content
$("#jscontent").val(editorGetContent());
// Submit the form
$("#jsform").submit();
});
// Autosave
var currentContent = editorGetContent();
setInterval(function() {
var uuid = $("#jsuuid").val();
var title = $("#jstitle").val() + "[<?php $L->p('Autosave') ?>]";
var content = editorGetContent();
// Autosave when content has at least 100 characters
if (content.length < 100) {
return false;
}
// Autosave only when the user change the content
if (currentContent != content) {
currentContent = content;
bluditAjax.saveAsDraft(uuid, title, content).then(function(data) {
if (data.status == 0) {
showAlert("<?php $L->p('Autosave') ?>");
}
});
}
}, 1000 * 60 * AUTOSAVE_INTERVAL);
});
</script>
-334
View File
@@ -1,334 +0,0 @@
<?php defined('BLUDIT') or die('Bludit CMS.'); ?>
<?php echo Bootstrap::formOpen(array('id' => 'jsform', 'class' => 'tab-content')); ?>
<div class="align-middle">
<div class="float-right mt-1">
<button type="submit" class="btn btn-primary btn-sm" name="save"><?php $L->p('Save') ?></button>
<a class="btn btn-secondary btn-sm" href="<?php echo HTML_PATH_ADMIN_ROOT . 'users' ?>" role="button"><?php $L->p('Cancel') ?></a>
</div>
<?php echo Bootstrap::pageTitle(array('title' => $L->g('Edit user'), 'icon' => 'user')); ?>
</div>
<!-- TABS -->
<nav class="mb-3">
<div class="nav nav-tabs" id="nav-tab" role="tablist">
<a class="nav-item nav-link active" id="nav-profile-tab" data-toggle="tab" href="#profile" role="tab" aria-controls="nav-profile" aria-selected="false"><?php $L->p('Profile') ?></a>
<a class="nav-item nav-link" id="nav-picture-tab" data-toggle="tab" href="#picture" role="tab" aria-controls="nav-picture" aria-selected="false"><?php $L->p('Profile picture') ?></a>
<a class="nav-item nav-link" id="nav-security-tab" data-toggle="tab" href="#security" role="tab" aria-controls="nav-security" aria-selected="false"><?php $L->p('Security') ?></a>
<a class="nav-item nav-link" id="nav-social-tab" data-toggle="tab" href="#social" role="tab" aria-controls="nav-social" aria-selected="false"><?php $L->p('Social Networks') ?></a>
</div>
</nav>
<?php
// Token CSRF
echo Bootstrap::formInputHidden(array(
'name' => 'tokenCSRF',
'value' => $security->getTokenCSRF()
));
// Username
echo Bootstrap::formInputHidden(array(
'name' => 'username',
'value' => $user->username()
));
?>
<div class="tab-content" id="nav-tabContent">
<!-- Profile tab -->
<div class="tab-pane fade show active" id="profile" role="tabpanel" aria-labelledby="nav-profile-tab">
<?php
// Display username but disable the field
echo Bootstrap::formInputText(array(
'name' => 'usernameDisabled',
'label' => $L->g('Username'),
'value' => $user->username(),
'class' => '',
'placeholder' => '',
'disabled' => true,
'tip' => ''
));
if ($login->role() === 'admin') {
echo Bootstrap::formSelect(array(
'name' => 'role',
'label' => $L->g('Role'),
'options' => array('author' => $L->g('Author'), 'editor' => $L->g('Editor'), 'admin' => $L->g('Administrator')),
'selected' => $user->role(),
'class' => '',
'tip' => $L->g('author-can-write-and-edit-their-own-content')
));
}
echo Bootstrap::formInputText(array(
'name' => 'email',
'label' => $L->g('Email'),
'value' => $user->email(),
'class' => '',
'placeholder' => '',
'tip' => ''
));
echo Bootstrap::formInputText(array(
'name' => 'nickname',
'label' => $L->g('Nickname'),
'value' => $user->nickname(),
'class' => '',
'placeholder' => '',
'tip' => $L->g('The nickname is almost used in the themes to display the author of the content')
));
echo Bootstrap::formInputText(array(
'name' => 'firstName',
'label' => $L->g('First Name'),
'value' => $user->firstName(),
'class' => '',
'placeholder' => '',
'tip' => ''
));
echo Bootstrap::formInputText(array(
'name' => 'lastName',
'label' => $L->g('Last Name'),
'value' => $user->lastName(),
'class' => '',
'placeholder' => '',
'tip' => ''
));
?>
</div>
<!-- Profile picture tab -->
<div class="tab-pane fade" id="picture" role="tabpanel" aria-labelledby="nav-picture-tab">
<div class="container">
<div class="row">
<div class="col-lg-4 col-sm-12 p-0 pr-2">
<div class="custom-file">
<input type="file" class="custom-file-input" id="jsprofilePictureInputFile" name="profilePictureInputFile">
<label class="custom-file-label" for="jsprofilePictureInputFile"><?php $L->p('Upload image'); ?></label>
</div>
<!-- <button id="jsbuttonRemovePicture" type="button" class="btn btn-primary w-100 mt-4 mb-4"><i class="fa fa-trash"></i> Remove picture</button> -->
</div>
<div class="col-lg-8 col-sm-12 p-0 text-center">
<img id="jsprofilePicturePreview" class="img-fluid img-thumbnail" alt="Profile picture preview" src="<?php echo (Sanitize::pathFile(PATH_UPLOADS_PROFILES . $user->username() . '.png') ? DOMAIN_UPLOADS_PROFILES . $user->username() . '.png?version=' . time() : HTML_PATH_CORE_IMG . 'default.svg') ?>" />
</div>
</div>
</div>
<script>
// $("#jsbuttonRemovePicture").on("click", function() {
// var username = $("#jsusername").val();
// bluditAjax.removeProfilePicture(username);
// $("#jsprofilePicturePreview").attr("src", "<?php echo HTML_PATH_CORE_IMG . 'default.svg' ?>");
// });
$("#jsprofilePictureInputFile").on("change", function() {
var formData = new FormData();
formData.append('tokenCSRF', tokenCSRF);
formData.append('profilePictureInputFile', $(this)[0].files[0]);
formData.append('username', $("#jsusername").val());
$.ajax({
url: HTML_PATH_ADMIN_ROOT + "ajax/profile-picture-upload",
type: "POST",
data: formData,
cache: false,
contentType: false,
processData: false
}).done(function(data) {
if (data.status == 0) {
$("#jsprofilePicturePreview").attr('src', data.absoluteURL + "?time=" + Math.random());
} else {
showAlert(data.message);
}
});
});
</script>
</div>
<!-- Security tab -->
<div class="tab-pane fade" id="security" role="tabpanel" aria-labelledby="nav-security-tab">
<?php
echo Bootstrap::formTitle(array('title' => $L->g('Password')));
echo '
<div class="form-group">
<a href="' . HTML_PATH_ADMIN_ROOT . 'user-password/' . $user->username() . '" class="btn btn-primary mr-2">' . $L->g('Change password') . '</a>
</div>
';
echo Bootstrap::formTitle(array('title' => $L->g('Authentication Token')));
echo Bootstrap::formInputText(array(
'name' => 'tokenAuth',
'label' => $L->g('Token'),
'value' => $user->tokenAuth(),
'class' => '',
'tip' => $L->g('this-token-is-similar-to-a-password-it-should-not-be-shared')
));
if (checkRole(array('admin'), false)) {
echo Bootstrap::formTitle(array('title' => $L->g('Status')));
echo Bootstrap::formInputText(array(
'name' => 'status',
'label' => $L->g('Current status'),
'value' => $user->enabled() ? $L->g('Enabled') : $L->g('Disabled'),
'class' => '',
'disabled' => true,
'tip' => $user->enabled() ? '' : $L->g('To enable the user you must set a new password')
));
if ($user->enabled()) {
echo '
<div class="form-group row">
<div class="col-sm-2"></div>
<div class="col-sm-10">
<button type="submit" class="btn btn-warning mr-2" id="jsdisableUser" name="disableUser">' . $L->g('Disable user') . '</button>
<button type="submit" class="btn btn-danger mr-2" id="jsdeleteUserAndKeepContent" name="deleteUserAndKeepContent">' . $L->g('Delete user and keep content') . '</button>
<button type="submit" class="btn btn-danger mr-2" id="jsdeleteUserAndDeleteContent" name="deleteUserAndDeleteContent">' . $L->g('Delete user and delete content') . '</button>
</div>
</div>
';
}
}
?>
</div>
<!-- Social Networks tab -->
<div class="tab-pane fade" id="social" role="tabpanel" aria-labelledby="nav-social-tab">
<?php
echo Bootstrap::formInputText(array(
'name' => 'twitter',
'label' => 'Twitter',
'value' => $user->twitter(),
'class' => '',
'placeholder' => '',
'tip' => ''
));
echo Bootstrap::formInputText(array(
'name' => 'facebook',
'label' => 'Facebook',
'value' => $user->facebook(),
'class' => '',
'placeholder' => '',
'tip' => ''
));
echo Bootstrap::formInputText(array(
'name' => 'codepen',
'label' => 'CodePen',
'value' => $user->codepen(),
'class' => '',
'placeholder' => '',
'tip' => ''
));
echo Bootstrap::formInputText(array(
'name' => 'instagram',
'label' => 'Instagram',
'value' => $user->instagram(),
'class' => '',
'placeholder' => '',
'tip' => ''
));
echo Bootstrap::formInputText(array(
'name' => 'gitlab',
'label' => 'GitLab',
'value' => $user->gitlab(),
'class' => '',
'placeholder' => '',
'tip' => ''
));
echo Bootstrap::formInputText(array(
'name' => 'github',
'label' => 'GitHub',
'value' => $user->github(),
'class' => '',
'placeholder' => '',
'tip' => ''
));
echo Bootstrap::formInputText(array(
'name' => 'linkedin',
'label' => 'LinkedIn',
'value' => $user->linkedin(),
'class' => '',
'placeholder' => '',
'tip' => ''
));
echo Bootstrap::formInputText(array(
'name' => 'xing',
'label' => 'Xing',
'value' => $user->xing(),
'class' => '',
'placeholder' => '',
'tip' => ''
));
echo Bootstrap::formInputText(array(
'name' => 'telegram',
'label' => 'Telegram',
'value' => $user->telegram(),
'class' => '',
'placeholder' => '',
'tip' => ''
));
echo Bootstrap::formInputText(array(
'name' => 'mastodon',
'label' => 'Mastodon',
'value' => $user->mastodon(),
'class' => '',
'placeholder' => '',
'tip' => ''
));
echo Bootstrap::formInputText(array(
'name' => 'vk',
'label' => 'VK',
'value' => $user->vk(),
'class' => '',
'placeholder' => '',
'tip' => ''
));
echo Bootstrap::formInputText(array(
'name' => 'youtube',
'label' => 'Youtube',
'value' => $user->youtube(),
'class' => '',
'placeholder' => '',
'tip' => ''
));
echo Bootstrap::formInputText(array(
'name' => 'bluesky',
'label' => 'Bluesky',
'value' => $user->bluesky(),
'class' => '',
'placeholder' => '',
'tip' => ''
));
?>
</div>
</div>
<?php echo Bootstrap::formClose(); ?>
<script>
// Open current tab after refresh page
$(function() {
$('a[data-toggle="tab"]').on('click', function(e) {
window.localStorage.setItem('activeTab', $(e.target).attr('href'));
console.log($(e.target).attr('href'));
});
var activeTab = window.localStorage.getItem('activeTab');
if (activeTab) {
$('#nav-tab a[href="' + activeTab + '"]').tab('show');
//window.localStorage.removeItem("activeTab");
}
});
</script>
-100
View File
@@ -1,100 +0,0 @@
<?php defined('BLUDIT') or die('Bludit CMS.');
// Logo and title
$logoPath = HTML_PATH_CORE_IMG . 'logo.svg';
$logoClass = 'logo-icon';
if (defined('BLUDIT_PRO') && $site->logo(false)) {
$logoPath = $site->logo(true);
$logoClass = 'logo-icon custom-logo';
}
echo '
<div class="login-logo">
<div class="' . $logoClass . '">
<img src="' . $logoPath . '" alt="Logo">
</div>
<h1>' . (defined('BLUDIT_PRO') ? Sanitize::html($site->title()) : 'BLUDIT') . '</h1>
</div>
';
echo Bootstrap::formOpen(array());
echo Bootstrap::formInputHidden(array(
'name' => 'tokenCSRF',
'value' => $security->getTokenCSRF()
));
// Username field with icon
echo '
<div class="form-group">
<label for="jsusername">' . $L->g('Username') . '</label>
<div class="input-icon-wrapper">
<input type="text"
dir="auto"
value="' . (isset($_POST['username']) ? Sanitize::html($_POST['username']) : '') . '"
class="form-control"
id="jsusername"
name="username"
placeholder="' . $L->g('Username') . '"
autocomplete="username"
autofocus>
<span class="input-icon">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"></path>
<circle cx="12" cy="7" r="4"></circle>
</svg>
</span>
</div>
</div>
';
// Password field with icon
echo '
<div class="form-group">
<label for="jspassword">' . $L->g('Password') . '</label>
<div class="input-icon-wrapper">
<input type="password"
class="form-control"
id="jspassword"
name="password"
placeholder="' . $L->g('Password') . '"
autocomplete="current-password">
<span class="input-icon">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<rect x="3" y="11" width="18" height="11" rx="2" ry="2"></rect>
<path d="M7 11V7a5 5 0 0 1 10 0v4"></path>
</svg>
</span>
</div>
</div>
';
// Remember me checkbox
echo '
<div class="form-check">
<input class="form-check-input" type="checkbox" value="true" id="jsremember" name="remember">
<label class="form-check-label" for="jsremember">' . $L->g('Remember me') . '</label>
</div>
';
// Submit button
echo '
<button type="submit" class="btn btn-login" name="save">
<span>' . $L->g('Login') . '</span>
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="margin-left: 8px; vertical-align: middle;">
<path d="M5 12h14"></path>
<path d="M12 5l7 7-7 7"></path>
</svg>
</button>
';
echo '</form>';
// Footer
if (!defined('BLUDIT_PRO')) {
echo '
<div class="login-footer">
<p>Powered by <a href="https://www.bludit.com" target="_blank" rel="noopener">Bludit</a></p>
</div>
';
}
-39
View File
@@ -1,39 +0,0 @@
<?php defined('BLUDIT') or die('Bludit CMS.'); ?>
<?php echo Bootstrap::formOpen(array('id'=>'jsform', 'class'=>'tab-content')); ?>
<div class="align-middle">
<div class="float-right mt-1">
<button type="submit" class="btn btn-primary btn-sm" name="save"><?php $L->p('Save') ?></button>
<a class="btn btn-secondary btn-sm" href="<?php echo HTML_PATH_ADMIN_ROOT.'categories' ?>" role="button"><?php $L->p('Cancel') ?></a>
</div>
<?php echo Bootstrap::pageTitle(array('title'=>$L->g('New category'), 'icon'=>'tag')); ?>
</div>
<?php
echo Bootstrap::formInputHidden(array(
'name'=>'tokenCSRF',
'value'=>$security->getTokenCSRF()
));
echo Bootstrap::formInputText(array(
'name'=>'name',
'label'=>$L->g('Name'),
'value'=>isset($_POST['category'])?$_POST['category']:'',
'class'=>'',
'placeholder'=>'',
'tip'=>''
));
echo Bootstrap::formTextarea(array(
'name'=>'description',
'label'=>$L->g('Description'),
'value'=>isset($_POST['description'])?$_POST['description']:'',
'class'=>'',
'placeholder'=>'',
'tip'=>'',
'rows'=>3
));
?>
<?php echo Bootstrap::formClose(); ?>
-516
View File
@@ -1,516 +0,0 @@
<?php defined('BLUDIT') or die('Bludit CMS.'); ?>
<?php
// Start form
echo Bootstrap::formOpen(array(
'id' => 'jsform',
'class' => 'd-flex flex-column h-100'
));
// Token CSRF
echo Bootstrap::formInputHidden(array(
'name' => 'tokenCSRF',
'value' => $security->getTokenCSRF()
));
// UUID
// The UUID is generated in the controller
echo Bootstrap::formInputHidden(array(
'name' => 'uuid',
'value' => $uuid
));
// Type = published, draft, sticky, static
echo Bootstrap::formInputHidden(array(
'name' => 'type',
'value' => $site->defaultContentStatus()
));
// Cover image
echo Bootstrap::formInputHidden(array(
'name' => 'coverImage',
'value' => ''
));
// Content
echo Bootstrap::formInputHidden(array(
'name' => 'content',
'value' => ''
));
?>
<!-- TOOLBAR -->
<div id="jseditorToolbar" class="mb-1">
<div id="jseditorToolbarRight" class="btn-group btn-group-sm float-right" role="group" aria-label="Toolbar right">
<button type="button" class="btn btn-light" id="jsmediaManagerOpenModal" data-toggle="modal" data-target="#jsmediaManagerModal"><span class="fa fa-image"></span> <?php $L->p('Images') ?></button>
<?php Theme::plugins('editorToolbar') ?>
<button type="button" class="btn btn-light" id="jsoptionsSidebar" style="z-index:30"><span class="fa fa-cog"></span> <?php $L->p('Options') ?></button>
</div>
<div id="jseditorToolbarLeft">
<button id="jsbuttonSave" type="button" class="btn btn-sm btn-primary"><?php $L->p('Save') ?></button>
<button id="jsbuttonPreview" type="button" class="btn btn-sm btn-secondary"><?php $L->p('Preview') ?></button>
<?php if ($site->defaultContentStatus() == 'draft'): ?>
<span id="jsbuttonSwitch" data-switch="draft" class="ml-2 text-secondary switch-button"><i class="fa fa-square switch-icon-draft"></i> <?php $L->p('Draft') ?></span>
<?php else: ?>
<span id="jsbuttonSwitch" data-switch="publish" class="ml-2 text-secondary switch-button"><i class="fa fa-square switch-icon-publish"></i> <?php $L->p('Publish') ?></span>
<?php endif; ?>
</div>
</div>
<script>
$(document).ready(function() {
$("#jsoptionsSidebar").on("click", function() {
$("#jseditorSidebar").toggle();
$("#jsshadow").toggle();
});
$("#jsshadow").on("click", function() {
$("#jseditorSidebar").toggle();
$("#jsshadow").toggle();
});
});
</script>
<!-- SIDEBAR OPTIONS -->
<div id="jseditorSidebar">
<nav>
<div class="nav nav-tabs" id="nav-tab" role="tablist">
<a class="nav-link active show" id="nav-general-tab" data-toggle="tab" href="#nav-general" role="tab" aria-controls="general"><?php $L->p('General') ?></a>
<a class="nav-link" id="nav-advanced-tab" data-toggle="tab" href="#nav-advanced" role="tab" aria-controls="advanced"><?php $L->p('Advanced') ?></a>
<?php if (!empty($site->customFields())) : ?>
<a class="nav-link" id="nav-custom-tab" data-toggle="tab" href="#nav-custom" role="tab" aria-controls="custom"><?php $L->p('Custom') ?></a>
<?php endif ?>
<a class="nav-link" id="nav-seo-tab" data-toggle="tab" href="#nav-seo" role="tab" aria-controls="seo"><?php $L->p('SEO') ?></a>
</div>
</nav>
<div class="tab-content pr-3 pl-3 pb-3">
<div id="nav-general" class="tab-pane fade show active" role="tabpanel" aria-labelledby="general-tab">
<?php
// Category
echo Bootstrap::formSelectBlock(array(
'name' => 'category',
'label' => $L->g('Category'),
'selected' => '',
'class' => '',
'emptyOption' => '- ' . $L->g('Uncategorized') . ' -',
'options' => $categories->getKeyNameArray()
));
// Description
echo Bootstrap::formTextareaBlock(array(
'name' => 'description',
'label' => $L->g('Description'),
'selected' => '',
'class' => '',
'value' => '',
'rows' => 5,
'placeholder' => $L->get('this-field-can-help-describe-the-content')
));
?>
<!-- Cover Image -->
<label class="mt-4 mb-2 pb-2 border-bottom text-uppercase w-100"><?php $L->p('Cover Image') ?></label>
<div>
<img id="jscoverImagePreview" class="mx-auto d-block w-100" alt="Cover image preview" src="<?php echo HTML_PATH_CORE_IMG ?>default.svg" />
</div>
<div class="mt-2 text-center">
<button type="button" id="jsbuttonSelectCoverImage" class="btn btn-primary btn-sm"><?php echo $L->g('Select cover image') ?></button>
<button type="button" id="jsbuttonRemoveCoverImage" class="btn btn-secondary btn-sm"><?php echo $L->g('Remove cover image') ?></button>
</div>
<script>
$(document).ready(function() {
$("#jscoverImagePreview").on("click", function() {
openMediaManager();
});
$("#jsbuttonSelectCoverImage").on("click", function() {
openMediaManager();
});
$("#jsbuttonRemoveCoverImage").on("click", function() {
$("#jscoverImage").val('');
$("#jscoverImagePreview").attr('src', HTML_PATH_CORE_IMG + 'default.svg');
});
});
</script>
</div>
<div id="nav-advanced" class="tab-pane fade" role="tabpanel" aria-labelledby="advanced-tab">
<?php
// Date
echo Bootstrap::formInputTextBlock(array(
'name' => 'date',
'label' => $L->g('Date'),
'placeholder' => '',
'value' => Date::current(DB_DATE_FORMAT),
'tip' => $L->g('date-format-format')
));
// Type
echo Bootstrap::formSelectBlock(array(
'name' => 'typeSelector',
'label' => $L->g('Type'),
'selected' => '',
'options' => array(
'published' => '- ' . $L->g('Default') . ' -',
'sticky' => $L->g('Sticky'),
'static' => $L->g('Static')
),
'tip' => ''
));
// Position
echo Bootstrap::formInputTextBlock(array(
'name' => 'position',
'label' => $L->g('Position'),
'tip' => $L->g('Field used when ordering content by position'),
'value' => $pages->nextPositionNumber()
));
// Tags
echo Bootstrap::formInputTextBlock(array(
'name' => 'tags',
'label' => $L->g('Tags'),
'placeholder' => '',
'tip' => $L->g('Write the tags separated by commas')
));
// Parent
echo Bootstrap::formSelectBlock(array(
'name' => 'parent',
'label' => $L->g('Parent'),
'options' => array(),
'selected' => false,
'class' => '',
'tip' => $L->g('Start typing a page title to see a list of suggestions.'),
));
?>
<script>
$(document).ready(function() {
var parent = $("#jsparent").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>
<?php
// Template
echo Bootstrap::formInputTextBlock(array(
'name' => 'template',
'label' => $L->g('Template'),
'placeholder' => '',
'value' => '',
'tip' => $L->g('Write a template name to filter the page in the theme and change the style of the page.')
));
echo Bootstrap::formInputTextBlock(array(
'name' => 'externalCoverImage',
'label' => $L->g('External cover image'),
'placeholder' => "https://",
'value' => '',
'tip' => $L->g('Set a cover image from external URL, such as a CDN or some server dedicated for images.')
));
// Username
echo Bootstrap::formInputTextBlock(array(
'name' => '',
'label' => $L->g('Author'),
'placeholder' => '',
'value' => $login->username(),
'tip' => '',
'disabled' => true
));
?>
<script>
$(document).ready(function() {
// Changes in External cover image input
$("#jsexternalCoverImage").change(function() {
$("#jscoverImage").val($(this).val());
});
// Generate slug when the user type the title
$("#jstitle").keyup(function() {
var text = $(this).val();
var parent = $("#jsparent").val();
var currentKey = "";
var ajax = new bluditAjax();
var callBack = $("#jsslug");
ajax.generateSlug(text, parent, currentKey, callBack);
});
// Datepicker
$("#jsdate").datetimepicker({
format: DB_DATE_FORMAT
});
});
</script>
</div>
<?php if (!empty($site->customFields())) : ?>
<div id="nav-custom" class="tab-pane fade" role="tabpanel" aria-labelledby="custom-tab">
<?php
$customFields = $site->customFields();
foreach ($customFields as $field => $options) {
if (!isset($options['position'])) {
if ($options['type'] == "string") {
echo Bootstrap::formInputTextBlock(array(
'name' => 'custom[' . $field . ']',
'label' => (isset($options['label']) ? $options['label'] : ''),
'value' => (isset($options['default']) ? $options['default'] : ''),
'tip' => (isset($options['tip']) ? $options['tip'] : ''),
'placeholder' => (isset($options['placeholder']) ? $options['placeholder'] : '')
));
} elseif ($options['type'] == "bool") {
echo Bootstrap::formCheckbox(array(
'name' => 'custom[' . $field . ']',
'label' => (isset($options['label']) ? $options['label'] : ''),
'placeholder' => (isset($options['placeholder']) ? $options['placeholder'] : ''),
'checked' => (isset($options['checked']) ? true : false),
'labelForCheckbox' => (isset($options['tip']) ? $options['tip'] : '')
));
}
}
}
?>
</div>
<?php endif ?>
<div id="nav-seo" class="tab-pane fade" role="tabpanel" aria-labelledby="seo-tab">
<?php
// Friendly URL
echo Bootstrap::formInputTextBlock(array(
'name' => 'slug',
'tip' => $L->g('URL associated with the content'),
'label' => $L->g('Friendly URL'),
'placeholder' => $L->g('Leave empty for autocomplete by Bludit.')
));
// Robots
echo Bootstrap::formCheckbox(array(
'name' => 'noindex',
'label' => 'Robots',
'labelForCheckbox' => $L->g('apply-code-noindex-code-to-this-page'),
'placeholder' => '',
'checked' => false,
'tip' => $L->g('This tells search engines not to show this page in their search results.')
));
// Robots
echo Bootstrap::formCheckbox(array(
'name' => 'nofollow',
'label' => '',
'labelForCheckbox' => $L->g('apply-code-nofollow-code-to-this-page'),
'placeholder' => '',
'checked' => false,
'tip' => $L->g('This tells search engines not to follow links on this page.')
));
// Robots
echo Bootstrap::formCheckbox(array(
'name' => 'noarchive',
'label' => '',
'labelForCheckbox' => $L->g('apply-code-noarchive-code-to-this-page'),
'placeholder' => '',
'checked' => false,
'tip' => $L->g('This tells search engines not to save a cached copy of this page.')
));
?>
</div>
</div>
</div>
<!-- Custom fields: TOP -->
<?php
$customFields = $site->customFields();
foreach ($customFields as $field => $options) {
if (isset($options['position']) && ($options['position'] == 'top')) {
if ($options['type'] == "string") {
echo Bootstrap::formInputTextBlock(array(
'name' => 'custom[' . $field . ']',
'label' => (isset($options['label']) ? $options['label'] : ''),
'value' => (isset($options['default']) ? $options['default'] : ''),
'tip' => (isset($options['tip']) ? $options['tip'] : ''),
'placeholder' => (isset($options['placeholder']) ? $options['placeholder'] : ''),
'class' => 'mb-2',
'labelClass' => 'mb-2 pb-2 border-bottom text-uppercase w-100'
));
} elseif ($options['type'] == "bool") {
echo Bootstrap::formCheckbox(array(
'name' => 'custom[' . $field . ']',
'label' => (isset($options['label']) ? $options['label'] : ''),
'placeholder' => (isset($options['placeholder']) ? $options['placeholder'] : ''),
'checked' => (isset($options['checked']) ? true : false),
'labelForCheckbox' => (isset($options['tip']) ? $options['tip'] : ''),
'class' => 'mb-2',
'labelClass' => 'mb-2 pb-2 border-bottom text-uppercase w-100'
));
}
}
}
?>
<!-- Title -->
<div id="jseditorTitle" class="form-group mb-1">
<input id="jstitle" name="title" type="text" dir="auto" class="form-control form-control-lg rounded-0" value="" placeholder="<?php $L->p('Enter title') ?>">
</div>
<!-- Editor -->
<textarea id="jseditor" class="editable h-100 mb-1"></textarea>
<!-- Custom fields: BOTTOM -->
<?php
$customFields = $site->customFields();
foreach ($customFields as $field => $options) {
if (isset($options['position']) && ($options['position'] == 'bottom')) {
if ($options['type'] == "string") {
echo Bootstrap::formInputTextBlock(array(
'name' => 'custom[' . $field . ']',
'label' => (isset($options['label']) ? $options['label'] : ''),
'value' => (isset($options['default']) ? $options['default'] : ''),
'tip' => (isset($options['tip']) ? $options['tip'] : ''),
'placeholder' => (isset($options['placeholder']) ? $options['placeholder'] : ''),
'class' => 'mt-2',
'labelClass' => 'mb-2 pb-2 border-bottom text-uppercase w-100'
));
} elseif ($options['type'] == "bool") {
echo Bootstrap::formCheckbox(array(
'name' => 'custom[' . $field . ']',
'label' => (isset($options['label']) ? $options['label'] : ''),
'placeholder' => (isset($options['placeholder']) ? $options['placeholder'] : ''),
'checked' => (isset($options['checked']) ? true : false),
'labelForCheckbox' => (isset($options['tip']) ? $options['tip'] : ''),
'class' => 'mt-2',
'labelClass' => 'mb-2 pb-2 border-bottom text-uppercase w-100'
));
}
}
}
?>
</form>
<!-- Modal for Media Manager -->
<?php include(PATH_ADMIN_THEMES . 'booty/html/media.php'); ?>
<script>
$(document).ready(function() {
// Define functions if they don't exist
// This helps if the user doesn't activate any plugin as editor
if (typeof editorGetContent != "function") {
window.editorGetContent = function() {
return $("#jseditor").val();
};
}
if (typeof editorInsertMedia != "function") {
window.editorInsertMedia = function(filename) {
$("#jseditor").val($('#jseditor').val() + '<img src="' + filename + '" alt="">');
};
}
if (typeof editorInsertLinkedMedia != "function") {
window.editorInsertLinkedMedia = function(filename, link) {
$("#jseditor").val($('#jseditor').val() + '<a href="' + link + '"><img src="' + filename + '" alt=""></a>');
};
}
// Button switch
$("#jsbuttonSwitch").on("click", function() {
if ($(this).data("switch") == "publish") {
$(this).html('<i class="fa fa-square switch-icon-draft"></i> <?php $L->p('Draft') ?>');
$(this).data("switch", "draft");
} else {
$(this).html('<i class="fa fa-square switch-icon-publish"></i> <?php $L->p('Publish') ?>');
$(this).data("switch", "publish");
}
});
// Button preview
$("#jsbuttonPreview").on("click", function() {
var uuid = $("#jsuuid").val();
var title = $("#jstitle").val();
var content = editorGetContent();
bluditAjax.saveAsDraft(uuid, title, content).then(function(data) {
var preview = window.open("<?php echo DOMAIN_PAGES . 'autosave-' . $uuid . '?preview=' . hash_hmac('sha256', 'autosave-' . $uuid, DB_SITE) ?>", "bludit-preview");
preview.focus();
});
});
// Button Save
$("#jsbuttonSave").on("click", function() {
let actionParameters = '';
// If the switch is setted to "published", get the value from the selector
if ($("#jsbuttonSwitch").data("switch") == "publish") {
var value = $("#jstypeSelector option:selected").val();
$("#jstype").val(value);
actionParameters = '#' + value;
} else {
$("#jstype").val("draft");
actionParameters = '#draft';
}
// Get the content
$("#jscontent").val(editorGetContent());
// Submit the form
$("#jsform").attr('action', actionParameters);
$("#jsform").submit();
});
// Autosave
var currentContent = editorGetContent();
setInterval(function() {
var uuid = $("#jsuuid").val();
var title = $("#jstitle").val() + "[<?php $L->p('Autosave') ?>]";
var content = editorGetContent();
// Autosave when content has at least 100 characters
if (content.length < 100) {
return false;
}
// Autosave only when the user change the content
if (currentContent != content) {
currentContent = content;
bluditAjax.saveAsDraft(uuid, title, content).then(function(data) {
if (data.status == 0) {
showAlert("<?php $L->p('Autosave') ?>");
}
});
}
}, 1000 * 60 * AUTOSAVE_INTERVAL);
});
</script>
-67
View File
@@ -1,67 +0,0 @@
<?php defined('BLUDIT') or die('Bludit CMS.'); ?>
<?php echo Bootstrap::formOpen(array('id'=>'jsform', 'class'=>'tab-content')); ?>
<div class="align-middle">
<div class="float-right mt-1">
<button type="submit" class="btn btn-primary btn-sm" name="save"><?php $L->p('Save') ?></button>
<a class="btn btn-secondary btn-sm" href="<?php echo HTML_PATH_ADMIN_ROOT.'users' ?>" role="button"><?php $L->p('Cancel') ?></a>
</div>
<?php echo Bootstrap::pageTitle(array('title'=>$L->g('Add a new user'), 'icon'=>'user')); ?>
</div>
<?php
echo Bootstrap::formInputHidden(array(
'name'=>'tokenCSRF',
'value'=>$security->getTokenCSRF()
));
echo Bootstrap::formInputText(array(
'name'=>'new_username',
'label'=>$L->g('Username'),
'value'=>(isset($_POST['new_username'])?$_POST['new_username']:''),
'class'=>'',
'placeholder'=>'',
'tip'=>''
));
echo Bootstrap::formInputText(array(
'name'=>'new_password',
'type'=>'password',
'label'=>$L->g('Password'),
'value'=>'',
'class'=>'',
'placeholder'=>'',
'tip'=>''
));
echo Bootstrap::formInputText(array(
'name'=>'confirm_password',
'type'=>'password',
'label'=>$L->g('Confirm Password'),
'value'=>'',
'class'=>'',
'placeholder'=>'',
'tip'=>''
));
echo Bootstrap::formSelect(array(
'name'=>'role',
'label'=>$L->g('Role'),
'options'=>array('author'=>$L->g('Author'), 'editor'=>$L->g('Editor'), 'admin'=>$L->g('Administrator')),
'selected'=>'Author',
'class'=>'',
'tip'=>$L->g('author-can-write-and-edit-their-own-content')
));
echo Bootstrap::formInputText(array(
'name'=>'email',
'label'=>$L->g('Email'),
'value'=>(isset($_POST['email'])?$_POST['email']:''),
'class'=>'',
'placeholder'=>'',
'tip'=>''
));
?>
<?php echo Bootstrap::formClose(); ?>
@@ -1,51 +0,0 @@
<?php defined('BLUDIT') or die('Bludit CMS.'); ?>
<?php echo Bootstrap::formOpen(array('id'=>'jsform', 'class'=>'tab-content')); ?>
<div class="align-middle">
<div class="float-right mt-1">
<button type="button" class="btn btn-primary btn-sm jsbuttonSave" name="save"><?php $L->p('Save') ?></button>
<a class="btn btn-secondary btn-sm" href="<?php echo HTML_PATH_ADMIN_ROOT.'plugins' ?>" role="button"><?php $L->p('Cancel') ?></a>
</div>
<?php echo Bootstrap::pageTitle(array('title'=>$L->g('Plugins position'), 'icon'=>'tags')); ?>
</div>
<div class="alert alert-primary"><?php $L->p('Drag and Drop to sort the plugins') ?></div>
<?php
// Token CSRF
echo Bootstrap::formInputHidden(array(
'name'=>'tokenCSRF',
'value'=>$security->getTokenCSRF()
));
echo Bootstrap::formInputHidden(array(
'name'=>'plugin-list',
'value'=>''
));
echo '<ul class="list-group list-group-sortable">';
foreach ($plugins['siteSidebar'] as $Plugin) {
echo '<li class="list-group-item" data-plugin="'.$Plugin->className().'"><span class="fa fa-arrows-v"></span> '.$Plugin->name().'</li>';
}
echo '</ul>';
?>
<?php echo Bootstrap::formClose(); ?>
<script>
$(document).ready(function() {
$('.list-group-sortable').sortable({
placeholderClass: 'list-group-item'
});
$(".jsbuttonSave").on("click", function() {
var tmp = [];
$("li.list-group-item").each(function() {
tmp.push( $(this).attr("data-plugin") );
});
$("#jsplugin-list").attr("value", tmp.join(",") );
$("#jsform").submit();
});
});
</script>
-128
View File
@@ -1,128 +0,0 @@
<?php
echo Bootstrap::pageTitle(array('title' => $L->g('Plugins'), 'icon' => 'puzzle-piece'));
echo Bootstrap::link(array(
'title' => $L->g('Change the position of the plugins'),
'href' => HTML_PATH_ADMIN_ROOT . 'plugins-position',
'icon' => 'arrows'
));
echo Bootstrap::formTitle(array('title' => $L->g('Search plugins')));
?>
<input type="text" dir="auto" class="form-control" id="search" placeholder="<?php $L->p('Search') ?>">
<script>
$(document).ready(function() {
$("#search").on("keyup", function() {
var textToSearch = $(this).val().toLowerCase();
$(".searchItem").each(function() {
var item = $(this);
item.hide();
item.find(".searchText").each(function() {
var element = $(this).text().toLowerCase();
if (element.indexOf(textToSearch) != -1) {
item.show();
}
});
});
});
});
</script>
<?php
echo Bootstrap::formTitle(array('title' => $L->g('Enabled plugins')));
echo '
<table class="table">
<tbody>
';
// Show installed plugins
foreach ($pluginsInstalled as $plugin) {
if ($plugin->type() == 'theme') {
// Do not display theme's plugins
continue;
}
echo '<tr id="' . $plugin->className() . '" class="bg-light searchItem">';
echo '<td class="align-middle pt-3 pb-3 w-25">
<div class="searchText">' . $plugin->name() . '</div>
<div class="mt-1">';
if (method_exists($plugin, 'form')) {
echo '<a class="mr-3" href="' . HTML_PATH_ADMIN_ROOT . 'configure-plugin/' . $plugin->className() . '">' . $L->g('Settings') . '</a>';
}
echo '<a href="' . HTML_PATH_ADMIN_ROOT . 'uninstall-plugin/' . $plugin->className() . '">' . $L->g('Deactivate') . '</a>';
echo '</div>';
echo '</td>';
echo '<td class="searchText align-middle d-none d-sm-table-cell">';
echo $plugin->description();
echo '</td>';
echo '<td class="text-center align-middle d-none d-lg-table-cell">';
echo '<span>' . $plugin->version() . '</span>';
if (!$plugin->isCompatible()) {
echo ' <span class="badge badge-pill badge-warning" title="' . $L->g('This plugin may not be supported by this version of Bludit') . '">' . $L->g('Update') . '</span>';
}
echo '</td>';
echo '<td class="text-center align-middle d-none d-lg-table-cell">
<a target="_blank" href="' . $plugin->website() . '">' . $plugin->author() . '</a>
</td>';
echo '</tr>';
}
echo '
</tbody>
</table>
';
echo Bootstrap::formTitle(array('title' => $L->g('Disabled plugins')));
echo '
<table class="table">
<tbody>
';
// Plugins not installed
$pluginsNotInstalled = array_diff_key($plugins['all'], $pluginsInstalled);
foreach ($pluginsNotInstalled as $plugin) {
if ($plugin->type() == 'theme') {
// Do not display theme's plugins
continue;
}
echo '<tr id="' . $plugin->className() . '" class="searchItem">';
echo '<td class="align-middle pt-3 pb-3 w-25">
<div class="searchText">' . $plugin->name() . '</div>
<div class="mt-1">
<a href="' . HTML_PATH_ADMIN_ROOT . 'install-plugin/' . $plugin->className() . '">' . $L->g('Activate') . '</a>
</div>
</td>';
echo '<td class="searchText align-middle d-none d-sm-table-cell">';
echo $plugin->description();
echo '</td>';
echo '<td class="text-center align-middle d-none d-lg-table-cell">';
echo '<span>' . $plugin->version() . '</span>';
echo '</td>';
echo '<td class="text-center align-middle d-none d-lg-table-cell">
<a target="_blank" href="' . $plugin->website() . '">' . $plugin->author() . '</a>
</td>';
echo '</tr>';
}
echo '
</tbody>
</table>
';
-650
View File
@@ -1,650 +0,0 @@
<?php defined('BLUDIT') or die('Bludit CMS.'); ?>
<?php echo Bootstrap::formOpen(array('id' => 'jsform', 'class' => 'tab-content')); ?>
<div class="align-middle">
<div class="float-right mt-1">
<button type="submit" class="btn btn-primary btn-sm" name="save"><?php $L->p('Save') ?></button>
<a class="btn btn-secondary btn-sm" href="<?php echo HTML_PATH_ADMIN_ROOT . 'dashboard' ?>" role="button"><?php $L->p('Cancel') ?></a>
</div>
<?php echo Bootstrap::pageTitle(array('title' => $L->g('Settings'), 'icon' => 'cog')); ?>
</div>
<!-- TABS -->
<nav class="mb-3">
<div class="nav nav-tabs" id="nav-tab" role="tablist">
<a class="nav-item nav-link active" id="nav-general-tab" data-toggle="tab" href="#general" role="tab" aria-controls="nav-general" aria-selected="false"><?php $L->p('General') ?></a>
<a class="nav-item nav-link" id="nav-advanced-tab" data-toggle="tab" href="#advanced" role="tab" aria-controls="nav-advanced" aria-selected="false"><?php $L->p('Advanced') ?></a>
<a class="nav-item nav-link" id="nav-seo-tab" data-toggle="tab" href="#seo" role="tab" aria-controls="nav-seo" aria-selected="false"><?php $L->p('SEO') ?></a>
<a class="nav-item nav-link" id="nav-social-tab" data-toggle="tab" href="#social" role="tab" aria-controls="nav-social" aria-selected="false"><?php $L->p('Social Networks') ?></a>
<a class="nav-item nav-link" id="nav-images-tab" data-toggle="tab" href="#images" role="tab" aria-controls="nav-images" aria-selected="false"><?php $L->p('Images') ?></a>
<a class="nav-item nav-link" id="nav-language-tab" data-toggle="tab" href="#language" role="tab" aria-controls="nav-language" aria-selected="false"><?php $L->p('Language') ?></a>
<a class="nav-item nav-link" id="nav-custom-fields-tab" data-toggle="tab" href="#custom-fields" role="tab" aria-controls="nav-custom-fields" aria-selected="false"><?php $L->p('Custom fields') ?></a>
<a class="nav-item nav-link" id="nav-logo-tab" data-toggle="tab" href="#logo" role="tab" aria-controls="nav-logo" aria-selected="false"><?php $L->p('Logo') ?></a>
</div>
</nav>
<?php
// Token CSRF
echo Bootstrap::formInputHidden(array(
'name' => 'tokenCSRF',
'value' => $security->getTokenCSRF()
));
?>
<!-- General tab -->
<div class="tab-pane fade show active" id="general" role="tabpanel" aria-labelledby="general-tab">
<?php
echo Bootstrap::formTitle(array('title' => $L->g('Site')));
echo Bootstrap::formInputText(array(
'name' => 'title',
'label' => $L->g('Site title'),
'value' => $site->title(),
'class' => '',
'placeholder' => '',
'tip' => $L->g('use-this-field-to-name-your-site')
));
echo Bootstrap::formInputText(array(
'name' => 'slogan',
'label' => $L->g('Site slogan'),
'value' => $site->slogan(),
'class' => '',
'placeholder' => '',
'tip' => $L->g('use-this-field-to-add-a-catchy-phrase')
));
echo Bootstrap::formInputText(array(
'name' => 'description',
'label' => $L->g('Site description'),
'value' => $site->description(),
'class' => '',
'placeholder' => '',
'tip' => $L->g('you-can-add-a-site-description-to-provide')
));
echo Bootstrap::formInputText(array(
'name' => 'footer',
'label' => $L->g('Footer text'),
'value' => $site->footer(),
'class' => '',
'placeholder' => '',
'tip' => $L->g('you-can-add-a-small-text-on-the-bottom')
));
?>
</div>
<!-- Advanced tab -->
<div class="tab-pane fade" id="advanced" role="tabpanel" aria-labelledby="advanced-tab">
<?php
echo Bootstrap::formTitle(array('title' => $L->g('Content')));
echo Bootstrap::formInputText(array(
'name' => 'itemsPerPage',
'label' => $L->g('Items per page'),
'value' => $site->itemsPerPage(),
'class' => '',
'placeholder' => '',
'tip' => $L->g('Number of items to show per page')
));
echo Bootstrap::formSelect(array(
'name' => 'orderBy',
'label' => $L->g('Order content by'),
'options' => array('date' => $L->g('Date'), 'position' => $L->g('Position')),
'selected' => $site->orderBy(),
'class' => '',
'tip' => $L->g('order-the-content-by-date-to-build-a-blog')
));
echo Bootstrap::formSelect(array(
'name' => 'defaultContentStatus',
'label' => $L->g('Default content status'),
'options' => array('published' => $L->g('Published'), 'draft' => $L->g('Draft')),
'selected' => $site->defaultContentStatus(),
'class' => '',
'tip' => $L->g('default-status-for-new-content')
));
echo Bootstrap::formTitle(array('title' => $L->g('Predefined pages')));
// Homepage
try {
$options = array();
$homeKey = $site->homepage();
if (!empty($homeKey)) {
$home = new Page($homeKey);
$options = array($homeKey => $home->title());
}
} catch (Exception $e) {
// continue
}
echo Bootstrap::formSelect(array(
'name' => 'homepage',
'label' => $L->g('Homepage'),
'options' => $options,
'selected' => false,
'class' => '',
'tip' => $L->g('Returning page for the main page')
));
?>
<script>
$(document).ready(function() {
var homepage = $("#jshomepage").select2({
placeholder: "<?php $L->p('Start typing to see a list of suggestions.') ?>",
allowClear: true,
theme: "bootstrap4",
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>
<?php
// Page not found 404
try {
$options = array();
$pageNotFoundKey = $site->pageNotFound();
if (!empty($pageNotFoundKey)) {
$pageNotFound = new Page($pageNotFoundKey);
$options = array($pageNotFoundKey => $pageNotFound->title());
}
} catch (Exception $e) {
// continue
}
echo Bootstrap::formSelect(array(
'name' => 'pageNotFound',
'label' => $L->g('Page not found'),
'options' => $options,
'selected' => false,
'class' => '',
'tip' => $L->g('Returning page when the page doesnt exist')
));
?>
<script>
$(document).ready(function() {
var homepage = $("#jspageNotFound").select2({
placeholder: "<?php $L->p('Start typing to see a list of suggestions.') ?>",
allowClear: true,
theme: "bootstrap4",
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>
<?php
echo Bootstrap::formTitle(array('title' => $L->g('Email account settings')));
echo Bootstrap::formInputText(array(
'name' => 'emailFrom',
'label' => $L->g('Sender email'),
'value' => $site->emailFrom(),
'class' => '',
'placeholder' => '',
'tip' => $L->g('Emails will be sent from this address')
));
echo Bootstrap::formTitle(array('title' => $L->g('Autosave')));
echo Bootstrap::formInputText(array(
'name' => 'autosaveInterval',
'label' => $L->g('Interval'),
'value' => $site->autosaveInterval(),
'class' => '',
'placeholder' => '',
'tip' => $L->g('Number in minutes for every execution of autosave')
));
echo Bootstrap::formTitle(array('title' => $L->g('Site URL')));
echo Bootstrap::formInputText(array(
'name' => 'url',
'label' => 'URL',
'value' => $site->url(),
'class' => '',
'placeholder' => '',
'tip' => $L->g('full-url-of-your-site'),
'placeholder' => 'https://'
));
echo Bootstrap::formTitle(array('title' => $L->g('Page content')));
echo Bootstrap::formSelect(array(
'name' => 'markdownParser',
'label' => $L->g('Markdown parser'),
'options' => array('true' => $L->g('Enabled'), 'false' => $L->g('Disabled')),
'selected' => ($site->markdownParser() ? 'true' : 'false'),
'class' => '',
'tip' => $L->g('Enable the markdown parser for the content of the page.')
));
echo Bootstrap::formTitle(array('title' => $L->g('URL Filters')));
echo Bootstrap::formInputText(array(
'name' => 'uriPage',
'label' => $L->g('Pages'),
'value' => $site->uriFilters('page'),
'class' => '',
'placeholder' => '',
'tip' => DOMAIN_PAGES
));
echo Bootstrap::formInputText(array(
'name' => 'uriTag',
'label' => $L->g('Tags'),
'value' => $site->uriFilters('tag'),
'class' => '',
'placeholder' => '',
'tip' => DOMAIN_TAGS
));
echo Bootstrap::formInputText(array(
'name' => 'uriCategory',
'label' => $L->g('Category'),
'value' => $site->uriFilters('category'),
'class' => '',
'placeholder' => '',
'tip' => DOMAIN_CATEGORIES
));
echo Bootstrap::formInputText(array(
'name' => 'uriBlog',
'label' => $L->g('Blog'),
'value' => $site->uriFilters('blog'),
'class' => '',
'placeholder' => '',
'tip' => DOMAIN . $site->uriFilters('blog'),
'disabled' => Text::isEmpty($site->uriFilters('blog'))
));
?>
</div>
<!-- SEO tab -->
<div class="tab-pane fade" id="seo" role="tabpanel" aria-labelledby="seo-tab">
<?php
echo Bootstrap::formTitle(array('title' => $L->g('Extreme friendly URL')));
echo Bootstrap::formSelect(array(
'name' => 'extremeFriendly',
'label' => $L->g('Allow Unicode'),
'options' => array('true' => $L->g('Enabled'), 'false' => $L->g('Disabled')),
'selected' => ($site->extremeFriendly() ? 'true' : 'false'),
'class' => '',
'tip' => $L->g('Allow unicode characters in the URL and some part of the system.')
));
echo Bootstrap::formTitle(array('title' => $L->g('Title formats')));
echo Bootstrap::formInputText(array(
'name' => 'titleFormatHomepage',
'label' => $L->g('Homepage'),
'value' => $site->titleFormatHomepage(),
'class' => '',
'placeholder' => '',
'tip' => $L->g('Variables allowed') . ' <code>{{site-title}}</code> <code>{{site-slogan}}</code> <code>{{site-description}}</code>',
'placeholder' => ''
));
echo Bootstrap::formInputText(array(
'name' => 'titleFormatPages',
'label' => $L->g('Pages'),
'value' => $site->titleFormatPages(),
'class' => '',
'placeholder' => '',
'tip' => $L->g('Variables allowed') . ' <code>{{page-title}}</code> <code>{{page-description}}</code> <code>{{site-title}}</code> <code>{{site-slogan}}</code> <code>{{site-description}}</code>',
'placeholder' => ''
));
echo Bootstrap::formInputText(array(
'name' => 'titleFormatCategory',
'label' => $L->g('Category'),
'value' => $site->titleFormatCategory(),
'class' => '',
'placeholder' => '',
'tip' => $L->g('Variables allowed') . ' <code>{{category-name}}</code> <code>{{site-title}}</code> <code>{{site-slogan}}</code> <code>{{site-description}}</code>',
'placeholder' => ''
));
echo Bootstrap::formInputText(array(
'name' => 'titleFormatTag',
'label' => $L->g('Tag'),
'value' => $site->titleFormatTag(),
'class' => '',
'placeholder' => '',
'tip' => $L->g('Variables allowed') . ' <code>{{tag-name}}</code> <code>{{site-title}}</code> <code>{{site-slogan}}</code> <code>{{site-description}}</code>',
'placeholder' => ''
));
?>
</div>
<!-- Social Network tab -->
<div class="tab-pane fade" id="social" role="tabpanel" aria-labelledby="social-tab">
<?php
echo Bootstrap::formInputText(array(
'name' => 'twitter',
'label' => 'Twitter',
'value' => $site->twitter(),
'class' => '',
'placeholder' => '',
'tip' => ''
));
echo Bootstrap::formInputText(array(
'name' => 'facebook',
'label' => 'Facebook',
'value' => $site->facebook(),
'class' => '',
'placeholder' => '',
'tip' => ''
));
echo Bootstrap::formInputText(array(
'name' => 'codepen',
'label' => 'CodePen',
'value' => $site->codepen(),
'class' => '',
'placeholder' => '',
'tip' => ''
));
echo Bootstrap::formInputText(array(
'name' => 'instagram',
'label' => 'Instagram',
'value' => $site->instagram(),
'class' => '',
'placeholder' => '',
'tip' => ''
));
echo Bootstrap::formInputText(array(
'name' => 'gitlab',
'label' => 'GitLab',
'value' => $site->gitlab(),
'class' => '',
'placeholder' => '',
'tip' => ''
));
echo Bootstrap::formInputText(array(
'name' => 'github',
'label' => 'GitHub',
'value' => $site->github(),
'class' => '',
'placeholder' => '',
'tip' => ''
));
echo Bootstrap::formInputText(array(
'name' => 'linkedin',
'label' => 'LinkedIn',
'value' => $site->linkedin(),
'class' => '',
'placeholder' => '',
'tip' => ''
));
echo Bootstrap::formInputText(array(
'name' => 'xing',
'label' => 'Xing',
'value' => $site->xing(),
'class' => '',
'placeholder' => '',
'tip' => ''
));
echo Bootstrap::formInputText(array(
'name' => 'telegram',
'label' => 'Telegram',
'value' => $site->telegram(),
'class' => '',
'placeholder' => '',
'tip' => ''
));
echo Bootstrap::formInputText(array(
'name' => 'mastodon',
'label' => 'Mastodon',
'value' => $site->mastodon(),
'class' => '',
'placeholder' => '',
'tip' => ''
));
echo Bootstrap::formInputText(array(
'name' => 'dribbble',
'label' => 'Dribbble',
'value' => $site->dribbble(),
'class' => '',
'placeholder' => '',
'tip' => ''
));
echo Bootstrap::formInputText(array(
'name' => 'vk',
'label' => 'VK',
'value' => $site->vk(),
'class' => '',
'placeholder' => '',
'tip' => ''
));
echo Bootstrap::formInputText(array(
'name' => 'youtube',
'label' => 'Youtube',
'value' => $site->youtube(),
'class' => '',
'placeholder' => '',
'tip' => ''
));
echo Bootstrap::formInputText(array(
'name' => 'bluesky',
'label' => 'Bluesky',
'value' => $site->bluesky(),
'class' => '',
'placeholder' => '',
'tip' => ''
));
?>
</div>
<!-- Images tab -->
<div class="tab-pane fade" id="images" role="tabpanel" aria-labelledby="images-tab">
<?php
echo Bootstrap::formTitle(array('title' => $L->g('Thumbnails')));
echo Bootstrap::formSelect(array(
'name' => 'thumbnailEnable',
'label' => $L->g('Thumbnail generation'),
'options' => array('true' => $L->g('Enabled'), 'false' => $L->g('Disabled')),
'selected' => ($site->thumbnailEnable() ? 'true' : 'false'),
'class' => '',
'tip' => $L->g('Enable or disable automatic thumbnail generation on image upload.')
));
echo Bootstrap::formInputText(array(
'name' => 'thumbnailWidth',
'label' => $L->g('Width'),
'value' => $site->thumbnailWidth(),
'class' => '',
'placeholder' => '',
'tip' => $L->g('Thumbnail width in pixels')
));
echo Bootstrap::formInputText(array(
'name' => 'thumbnailHeight',
'label' => $L->g('Height'),
'value' => $site->thumbnailHeight(),
'class' => '',
'placeholder' => '',
'tip' => $L->g('Thumbnail height in pixels')
));
echo Bootstrap::formInputText(array(
'name' => 'thumbnailQuality',
'label' => $L->g('Quality'),
'value' => $site->thumbnailQuality(),
'class' => '',
'placeholder' => '',
'tip' => $L->g('Thumbnail quality in percentage')
));
?>
</div>
<!-- Timezone and language tab -->
<div class="tab-pane fade" id="language" role="tabpanel" aria-labelledby="language-tab">
<?php
echo Bootstrap::formTitle(array('title' => $L->g('Language and timezone')));
echo Bootstrap::formSelect(array(
'name' => 'language',
'label' => $L->g('Language'),
'options' => $L->getLanguageList(),
'selected' => $site->language(),
'class' => '',
'tip' => $L->g('select-your-sites-language')
));
echo Bootstrap::formSelect(array(
'name' => 'timezone',
'label' => $L->g('Timezone'),
'options' => Date::timezoneList(),
'selected' => $site->timezone(),
'class' => '',
'tip' => $L->g('select-a-timezone-for-a-correct')
));
echo Bootstrap::formInputText(array(
'name' => 'locale',
'label' => $L->g('Locale'),
'value' => $site->locale(),
'class' => '',
'placeholder' => '',
'tip' => $L->g('with-the-locales-you-can-set-the-regional-user-interface')
));
echo Bootstrap::formTitle(array('title' => $L->g('Date and time formats')));
echo Bootstrap::formInputText(array(
'name' => 'dateFormat',
'label' => $L->g('Date format'),
'value' => $site->dateFormat(),
'class' => '',
'placeholder' => '',
'tip' => $L->g('Current format') . ': ' . Date::current($site->dateFormat())
));
?>
</div>
<!-- Custom fields -->
<div class="tab-pane fade" id="custom-fields" role="tabpanel" aria-labelledby="custom-fields-tab">
<?php
echo Bootstrap::formTitle(array('title' => $L->g('Custom fields')));
echo Bootstrap::formTextarea(array(
'name' => 'customFields',
'label' => 'JSON Format',
'value' => json_encode($site->customFields(), JSON_PRETTY_PRINT),
'class' => '',
'placeholder' => '',
'tip' => $L->g('define-custom-fields-for-the-content'),
'rows' => 15
));
?>
</div>
<!-- Site logo tab -->
<div class="tab-pane fade" id="logo" role="tabpanel" aria-labelledby="logo-tab">
<?php
echo Bootstrap::formTitle(array('title' => $L->g('Site logo')));
?>
<div class="container">
<div class="row">
<div class="col-lg-4 col-sm-12 p-0 pr-2">
<div class="custom-file">
<input id="jssiteLogoInputFile" class="custom-file-input" type="file" name="inputFile">
<label for="jssiteLogoInputFile" class="custom-file-label"><?php $L->p('Upload image'); ?></label>
</div>
<button id="jsbuttonRemoveLogo" type="button" class="btn btn-primary w-100 mt-4 mb-4"><i class="fa fa-trash"></i><?php $L->p('Remove logo') ?></button>
</div>
<div class="col-lg-8 col-sm-12 p-0 text-center">
<img id="jssiteLogoPreview" class="img-fluid img-thumbnail" alt="Site logo preview" src="<?php echo ($site->logo() ? DOMAIN_UPLOADS . $site->logo(false) . '?version=' . time() : HTML_PATH_CORE_IMG . 'default.svg') ?>" />
</div>
</div>
</div>
<script>
$("#jsbuttonRemoveLogo").on("click", function() {
bluditAjax.removeLogo();
$("#jssiteLogoPreview").attr("src", "<?php echo HTML_PATH_CORE_IMG . 'default.svg' ?>");
});
$("#jssiteLogoInputFile").on("change", function() {
var formData = new FormData();
formData.append('tokenCSRF', tokenCSRF);
formData.append('inputFile', $(this)[0].files[0]);
$.ajax({
url: HTML_PATH_ADMIN_ROOT + "ajax/logo-upload",
type: "POST",
data: formData,
cache: false,
contentType: false,
processData: false
}).done(function(data) {
if (data.status == 0) {
$("#jssiteLogoPreview").attr('src', data.absoluteURL + "?time=" + Math.random());
} else {
showAlert(data.message);
}
});
});
</script>
</div>
<?php echo Bootstrap::formClose(); ?>
<script>
// Open current tab after refresh page
$(function() {
$('a[data-toggle="tab"]').on('click', function(e) {
window.localStorage.setItem('activeTab', $(e.target).attr('href'));
});
var activeTab = window.localStorage.getItem('activeTab');
if (activeTab) {
$('#nav-tab a[href="' + activeTab + '"]').tab('show');
//window.localStorage.removeItem("activeTab");
}
});
</script>
-57
View File
@@ -1,57 +0,0 @@
<?php
echo Bootstrap::pageTitle(array('title' => $L->g('Themes'), 'icon' => 'desktop'));
echo '
<table class="table mt-3">
<thead>
<tr>
<th class="border-bottom-0 w-25" scope="col">' . $L->g('Name') . '</th>
<th class="border-bottom-0 d-none d-sm-table-cell" scope="col">' . $L->g('Description') . '</th>
<th class="text-center border-bottom-0 d-none d-lg-table-cell" scope="col">' . $L->g('Version') . '</th>
<th class="text-center border-bottom-0 d-none d-lg-table-cell" scope="col">' . $L->g('Author') . '</th>
</tr>
</thead>
<tbody>
';
foreach ($themes as $theme) {
echo '
<tr ' . ($theme['dirname'] == $site->theme() ? 'class="bg-light"' : '') . '>
<td class="align-middle pt-3 pb-3">
<div>'.$theme['name'].($theme['dirname']==$site->theme()?'<span class="badge badge-primary ml-2">'.$L->g('Active').'</span>':'').'</div>
<div class="mt-1">
';
if ($theme['dirname'] != $site->theme()) {
echo '<a href="' . HTML_PATH_ADMIN_ROOT . 'install-theme/' . $theme['dirname'] . '">' . $L->g('Activate') . '</a>';
} else {
if (isset($theme['plugin'])) {
echo '<a href="' . HTML_PATH_ADMIN_ROOT . 'configure-plugin/' . $theme['plugin'] . '">' . $L->g('Settings') . '</a>';
}
}
echo '
</div>
</td>
';
echo '<td class="align-middle d-none d-sm-table-cell">';
echo $theme['description'];
echo '</td>';
echo '<td class="text-center align-middle d-none d-lg-table-cell">';
echo '<span>' . $theme['version'] . '</span>';
echo '</td>';
echo '<td class="text-center align-middle d-none d-lg-table-cell">
<a target="_blank" href="' . $theme['website'] . '">' . $theme['author'] . '</a>
</td>';
echo '</tr>';
}
echo '
</tbody>
</table>
';
-60
View File
@@ -1,60 +0,0 @@
<?php defined('BLUDIT') or die('Bludit CMS.'); ?>
<?php echo Bootstrap::formOpen(array('id'=>'jsform', 'class'=>'tab-content')); ?>
<div class="align-middle">
<div class="float-right mt-1">
<button type="submit" class="btn btn-primary btn-sm" name="save"><?php $L->p('Save') ?></button>
<a class="btn btn-secondary btn-sm" href="<?php echo HTML_PATH_ADMIN_ROOT.'edit-user/'.$user->username() ?>" role="button"><?php $L->p('Cancel') ?></a>
</div>
<?php echo Bootstrap::pageTitle(array('title'=>$L->g('Change password'), 'icon'=>'user')); ?>
</div>
<?php
// Token CSRF
echo Bootstrap::formInputHidden(array(
'name'=>'tokenCSRF',
'value'=>$security->getTokenCSRF()
));
// Username
echo Bootstrap::formInputHidden(array(
'name'=>'username',
'value'=>$user->username()
));
// Username disabled
echo Bootstrap::formInputText(array(
'name'=>'usernameDisabled',
'label'=>$L->g('Username'),
'value'=>$user->username(),
'class'=>'',
'placeholder'=>'',
'disabled'=>true,
'tip'=>''
));
// New password
echo Bootstrap::formInputText(array(
'name'=>'newPassword',
'label'=>$L->g('New password'),
'type'=>'password',
'value'=>'',
'class'=>'',
'placeholder'=>'',
'tip'=>''
));
// Confirm password
echo Bootstrap::formInputText(array(
'name'=>'confirmPassword',
'label'=>$L->g('Confirm new password'),
'type'=>'password',
'value'=>'',
'class'=>'',
'placeholder'=>'',
'tip'=>''
));
?>
<?php echo Bootstrap::formClose(); ?>
-56
View File
@@ -1,56 +0,0 @@
<?php defined('BLUDIT') or die('Bludit CMS.'); ?>
<?php
echo Bootstrap::pageTitle(array('title'=>$L->g('Users'), 'icon'=>'users'));
echo Bootstrap::link(array(
'title'=>$L->g('add-a-new-user'),
'href'=>HTML_PATH_ADMIN_ROOT.'new-user',
'icon'=>'plus'
));
echo '
<table class="table table-striped mt-3">
<thead>
<tr>
<th class="border-bottom-0" scope="col">'.$L->g('Username').'</th>
<th class="border-bottom-0 d-none d-lg-table-cell" scope="col">'.$L->g('Nickname').'</th>
<th class="border-bottom-0" scope="col">'.$L->g('Email').'</th>
<th class="border-bottom-0" scope="col">'.$L->g('Status').'</th>
<th class="border-bottom-0" scope="col">'.$L->g('Role').'</th>
<th class="border-bottom-0 d-none d-lg-table-cell" scope="col">'.$L->g('Registered').'</th>
</tr>
</thead>
<tbody>
';
$list = $users->keys();
foreach ($list as $username) {
try {
$user = new User($username);
echo '<tr>';
echo '<td><img class="profilePicture mr-1" alt="" src="'.(Sanitize::pathFile(PATH_UPLOADS_PROFILES.$user->username().'.png')?DOMAIN_UPLOADS_PROFILES.$user->username().'.png':HTML_PATH_CORE_IMG.'default.svg').'" /><a href="'.HTML_PATH_ADMIN_ROOT.'edit-user/'.$username.'">'.$username.'</a></td>';
echo '<td class="d-none d-lg-table-cell">'.$user->nickname().'</td>';
echo '<td>'.$user->email().'</td>';
echo '<td>'.($user->enabled()?'<b>'.$L->g('Enabled').'</b>':$L->g('Disabled')).'</td>';
if ($user->role()=='admin') {
echo '<td>'.$L->g('Administrator').'</td>';
} elseif ($user->role()=='editor') {
echo '<td>'.$L->g('Editor').'</td>';
} elseif ($user->role()=='author') {
echo '<td>'.$L->g('Author').'</td>';
} else {
echo '<td>'.$L->g('Reader').'</td>';
}
echo '<td class="d-none d-lg-table-cell">'.Date::format($user->registered(), DB_DATE_FORMAT, ADMIN_PANEL_DATE_FORMAT).'</td>';
echo '</tr>';
} catch (Exception $e) {
// Continue
}
}
echo '
</tbody>
</table>
';
-69
View File
@@ -1,69 +0,0 @@
<?php defined('BLUDIT') or die('Bludit CMS.');
header('Content-Type: application/json');
/*
| Change a page's type. Allowed types: published, sticky, static, draft.
| Allowed transitions are restricted to the same set on both ends anything
| else (autosave, scheduled) goes through the normal edit flow.
|
| @_POST['key'] string Page key
| @_POST['type'] string Target type
|
| @return JSON { status, message, key, type }
*/
checkRole(array('admin', 'editor', 'author'));
$allowed = array('published', 'sticky', 'static', 'draft');
$key = isset($_POST['key']) ? Sanitize::html($_POST['key']) : false;
$newType = isset($_POST['type']) ? Sanitize::html($_POST['type']) : false;
if (empty($key) || !$pages->exists($key)) {
ajaxResponse(1, $L->g('Page not found.'));
}
if (!in_array($newType, $allowed, true)) {
ajaxResponse(1, $L->g('Target type is not allowed.'));
}
$current = $pages->db[$key]['type'];
if (!in_array($current, $allowed, true)) {
ajaxResponse(1, $L->g('Current page type cannot be changed from this menu.'));
}
if ($current === $newType) {
ajaxResponse(0, $L->g('Page type unchanged.'), array(
'key' => $key,
'type' => $newType
));
}
// Authors can only change their own pages
if (checkRole(array('author'), false)) {
if ($pages->db[$key]['username'] !== $login->username()) {
ajaxResponse(1, $L->g('Permission denied.'));
}
}
// Don't let a static parent be moved away from "static" while it has children;
// otherwise the descendants get orphaned in the static tree (getStaticDB filters by type).
if ($current === 'static' && $newType !== 'static') {
try {
$page = new Page($key);
if (count($page->children()) > 0) {
ajaxResponse(1, $L->g('Cannot change type while the page has children.'));
}
} catch (Exception $e) {
ajaxResponse(1, $L->g('Page not found.'));
}
}
if ($pages->setField($key, 'type', $newType) === false) {
ajaxResponse(1, $L->g('Failed to update page type.'));
}
ajaxResponse(0, $L->g('Page type updated.'), array(
'key' => $key,
'type' => $newType
));
-68
View File
@@ -1,68 +0,0 @@
<?php defined('BLUDIT') or die('Bludit CMS.');
header('Content-Type: application/json');
// $_GET
// ----------------------------------------------------------------------------
// (string) $_GET['query']
$query = isset($_GET['query']) ? Text::lowercase($_GET['query']) : false;
// ----------------------------------------------------------------------------
if ($query===false) {
ajaxResponse(1, 'Invalid query.');
}
$result = array();
// MENU
if (Text::stringContains(Text::lowercase($L->g('New content')), $query)) {
$tmp = array('disabled'=>true, 'icon'=>'plus-circle', 'type'=>'menu');
$tmp['text'] = $L->g('New content');
$tmp['url'] = HTML_PATH_ADMIN_ROOT.'new-content';
array_push($result, $tmp);
}
if (Text::stringContains(Text::lowercase($L->g('New category')), $query)) {
$tmp = array('disabled'=>true, 'icon'=>'tag', 'type'=>'menu');
$tmp['text'] = $L->g('New category');
$tmp['url'] = HTML_PATH_ADMIN_ROOT.'new-category';
array_push($result, $tmp);
}
if (Text::stringContains(Text::lowercase($L->g('New user')), $query)) {
$tmp = array('disabled'=>true, 'icon'=>'user', 'type'=>'menu');
$tmp['text'] = $L->g('New user');
$tmp['url'] = HTML_PATH_ADMIN_ROOT.'new-user';
array_push($result, $tmp);
}
if (Text::stringContains(Text::lowercase($L->g('Categories')), $query)) {
$tmp = array('disabled'=>true, 'icon'=>'tags', 'type'=>'menu');
$tmp['text'] = $L->g('Categories');
$tmp['url'] = HTML_PATH_ADMIN_ROOT.'categories';
array_push($result, $tmp);
}
if (Text::stringContains(Text::lowercase($L->g('Users')), $query)) {
$tmp = array('disabled'=>true, 'icon'=>'users', 'type'=>'menu');
$tmp['text'] = $L->g('Users');
$tmp['url'] = HTML_PATH_ADMIN_ROOT.'users';
array_push($result, $tmp);
}
// PAGES
$pagesKey = $pages->getDB();
foreach ($pagesKey as $pageKey) {
try {
$page = new Page($pageKey);
$lowerTitle = Text::lowercase($page->title());
if (Text::stringContains($lowerTitle, $query)) {
$tmp = array('disabled'=>true);
$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)));
?>
-49
View File
@@ -1,49 +0,0 @@
<?php defined('BLUDIT') or die('Bludit CMS.');
header('Content-Type: application/json');
/*
| Search for pages that have in the title the string $query and returns the array of pages
|
| @_GET['published'] boolean True to search in published database
| @_GET['static'] boolean True to search in static database
| @_GET['sticky'] boolean True to search in sticky database
| @_GET['scheduled'] boolean True to search in scheduled database
| @_GET['draft'] boolean True to search in draft database
| @_GET['query'] string Text to search in the title
|
| @return array
*/
// $_GET
// ----------------------------------------------------------------------------
$published = empty($_GET['published']) ? false:true;
$static = empty($_GET['static']) ? false:true;
$sticky = empty($_GET['sticky']) ? false:true;
$scheduled = empty($_GET['scheduled']) ? false:true;
$draft = empty($_GET['draft']) ? false:true;
$query = isset($_GET['query']) ? Text::lowercase($_GET['query']) : false;
// ----------------------------------------------------------------------------
if ($query===false) {
ajaxResponse(1, 'Invalid query.');
}
$pageNumber = 1;
$numberOfItems = -1;
$pagesKey = $pages->getList($pageNumber, $numberOfItems, $published, $static, $sticky, $draft, $scheduled);
$tmp = array();
foreach ($pagesKey as $pageKey) {
try {
$page = new Page($pageKey);
$lowerTitle = Text::lowercase($page->title());
if (Text::stringContains($lowerTitle, $query)) {
$tmp[$page->key()] = $page->json(true);
}
} catch (Exception $e) {
// continue
}
}
exit (json_encode($tmp));
?>
-65
View File
@@ -1,65 +0,0 @@
<?php defined('BLUDIT') or die('Bludit CMS.');
header('Content-Type: application/json');
/*
| Delete an image from a particular page
|
| @_POST['filename'] string Name of the file to delete
| @_POST['uuid'] string Page UUID
|
| @return array
*/
// $_POST
// ----------------------------------------------------------------------------
$filename = isset($_POST['filename']) ? $_POST['filename'] : false;
$uuid = empty($_POST['uuid']) ? false : $_POST['uuid'];
// ----------------------------------------------------------------------------
if ($filename===false) {
ajaxResponse(1, 'The filename is empty.');
}
if ($uuid && IMAGE_RESTRICT) {
if (Text::stringContains($uuid, DS, false)) {
ajaxResponse(1, 'Invalid uuid.');
}
$imagePath = PATH_UPLOADS_PAGES.$uuid.DS;
$thumbnailPath = PATH_UPLOADS_PAGES.$uuid.DS.'thumbnails'.DS;
} else {
$imagePath = PATH_UPLOADS;
$thumbnailPath = PATH_UPLOADS_THUMBNAILS;
}
// Delete the original
if (Sanitize::pathFile($imagePath.$filename)) {
Filesystem::rmfile($imagePath.$filename);
}
// Delete the thumbnail. Exact-name match is the fast path (new uploads have
// matching extensions). If no exact match, fall back to any allowed-extension
// match on the basename — this recovers legacy pairs where thumbnails were
// forced to .jpg while the original kept its real extension. Before deleting
// a mismatched candidate, verify no other original owns that extension, to
// avoid taking out an unrelated image's thumbnail.
if (Sanitize::pathFile($thumbnailPath.$filename) && is_file($thumbnailPath.$filename)) {
Filesystem::rmfile($thumbnailPath.$filename);
} else {
$base = pathinfo($filename, PATHINFO_FILENAME);
foreach ($GLOBALS['ALLOWED_IMG_EXTENSION'] as $ext) {
$candidate = $base.'.'.$ext;
if ($candidate === $filename) {
continue;
}
if (is_file($imagePath.$candidate)) {
continue;
}
if (Sanitize::pathFile($thumbnailPath.$candidate) && is_file($thumbnailPath.$candidate)) {
Filesystem::rmfile($thumbnailPath.$candidate);
}
}
}
ajaxResponse(0, 'Image deleted.');
?>
-27
View File
@@ -1,27 +0,0 @@
<?php defined('BLUDIT') or die('Bludit CMS.');
header('Content-Type: application/json');
/*
| Generate a slug text for the URL
|
| @_POST['text'] string The text from where is generated the slug
| @_POST['parentKey'] string The parent key if the page has one
| @_POST['currentKey'] string The current page key
|
| @return array
*/
// $_POST
// ----------------------------------------------------------------------------
$text = isset($_POST['text']) ? $_POST['text'] : '';
$parent = isset($_POST['parentKey']) ? $_POST['parentKey'] : '';
$oldKey = isset($_POST['currentKey']) ? $_POST['currentKey'] : '';
// ----------------------------------------------------------------------------
$slug = $pages->generateKey($text, $parent, $returnSlug=true, $oldKey);
ajaxResponse(0, 'Slug generated.', array(
'slug'=>$slug
));
?>
-50
View File
@@ -1,50 +0,0 @@
<?php defined('BLUDIT') or die('Bludit CMS.');
header('Content-Type: application/json');
/*
| Returns a list of pages and the title contains the query string
| The returned list have published, sticky and statics pages
|
| @_POST['query'] string The string to search in the title of the pages
|
| @return array
*/
// $_GET
// ----------------------------------------------------------------------------
// (string) $_GET['query']
$query = isset($_GET['query']) ? Text::lowercase($_GET['query']) : false;
// (boolean) $_GET['checkIsParent']
$checkIsParent = empty($_GET['checkIsParent']) ? false : true;
// ----------------------------------------------------------------------------
if ($query===false) {
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
}
}
exit (json_encode(array('results'=>$result)));
?>
-60
View File
@@ -1,60 +0,0 @@
<?php defined('BLUDIT') or die('Bludit CMS.');
header('Content-Type: application/json');
/*
| Returns a list of images from a particular page
|
| @_POST['pageNumber'] int Page number for the paginator
| @_POST['path'] string Pre-defined name for the directory to read, its pre-defined to avoid security issues
| @_POST['uuid'] string Page UUID
|
| @return array Each file is an object with 'filename' (original) and
| 'thumbnail' (resolved preview filename may differ from
| the original for legacy pairs or fall back to it when no
| thumbnail exists).
*/
// $_POST
// ----------------------------------------------------------------------------
// $_POST['pageNumber'] > 0
$pageNumber = empty($_POST['pageNumber']) ? 1 : (int)$_POST['pageNumber'];
$pageNumber = $pageNumber - 1;
$path = empty($_POST['path']) ? false : $_POST['path'];
$uuid = empty($_POST['uuid']) ? false : $_POST['uuid'];
// ----------------------------------------------------------------------------
// The only accepted value is kept for backward-compat with clients that
// preserve the old contract; the server now scans originals regardless.
if ($path !== 'thumbnails') {
ajaxResponse(1, 'Invalid path.');
}
// Resolve the originals and thumbnails directories
if ($uuid && IMAGE_RESTRICT) {
if (Text::stringContains($uuid, DS, false)) {
ajaxResponse(1, 'Invalid uuid.');
}
$imagePath = PATH_UPLOADS_PAGES.$uuid.DS;
$thumbnailPath = PATH_UPLOADS_PAGES.$uuid.DS.'thumbnails'.DS;
} else {
$imagePath = PATH_UPLOADS;
$thumbnailPath = PATH_UPLOADS_THUMBNAILS;
}
// Scan originals and pair each with its matching thumbnail
$listOfFilesByPage = mediaManagerListImages($imagePath, $thumbnailPath, MEDIA_MANAGER_NUMBER_OF_FILES);
if (isset($listOfFilesByPage[$pageNumber])) {
ajaxResponse(0, 'List of files and number of chunks.', array(
'numberOfPages'=>count($listOfFilesByPage),
'files'=>$listOfFilesByPage[$pageNumber]
));
}
ajaxResponse(0, 'List of files and number of chunks.', array(
'numberOfPages'=>0,
'files'=>array()
));
?>
-22
View File
@@ -1,22 +0,0 @@
<?php defined('BLUDIT') or die('Bludit CMS.');
header('Content-Type: application/json');
/*
| Delete the site logo
| This script delete the file and set and empty string in the database
|
| @return array
*/
// Delete the file
$logoFilename = $site->logo(false);
if ($logoFilename) {
Filesystem::rmfile(PATH_UPLOADS.$logoFilename);
}
// Remove the logo from the database
$site->set(array('logo'=>''));
ajaxResponse(0, 'Logo removed.');
?>
-81
View File
@@ -1,81 +0,0 @@
<?php defined('BLUDIT') or die('Bludit CMS.');
header('Content-Type: application/json');
/*
| Upload site logo
| The final filename is the site's name and the extension is the same as the file uploaded
|
| @_FILES['inputFile'] multipart/form-data File from form
|
| @return array
*/
if (!isset($_FILES['inputFile'])) {
ajaxResponse(1, 'Error trying to upload the site logo.');
}
// Check path traversal on $filename
if (Text::stringContains($_FILES['inputFile']['name'], DS, false)) {
$message = 'Path traversal detected.';
Log::set($message, LOG_TYPE_ERROR);
ajaxResponse(1, $message);
}
// Block dotfiles
if (strpos($_FILES['inputFile']['name'], '.') === 0) {
$message = 'File type not allowed.';
Log::set($message, LOG_TYPE_ERROR);
ajaxResponse(1, $message);
}
// File extension
$fileExtension = Filesystem::extension($_FILES['inputFile']['name']);
$fileExtension = Text::lowercase($fileExtension);
if (!in_array($fileExtension, $GLOBALS['ALLOWED_IMG_EXTENSION'])) {
$message = $L->g('File type is not supported. Allowed types:') . ' ' . implode(', ', $GLOBALS['ALLOWED_IMG_EXTENSION']);
Log::set($message, LOG_TYPE_ERROR);
ajaxResponse(1, $message);
}
// File MIME Type
$fileMimeType = Filesystem::mimeType($_FILES['inputFile']['tmp_name']);
if ($fileMimeType !== false) {
if (!in_array($fileMimeType, $GLOBALS['ALLOWED_IMG_MIMETYPES'])) {
$message = $L->g('File mime type is not supported. Allowed types:') . ' ' . implode(', ', $GLOBALS['ALLOWED_IMG_MIMETYPES']);
Log::set($message, LOG_TYPE_ERROR);
ajaxResponse(1, $message);
}
}
// Final filename
$filename = 'logo.' . $fileExtension;
if (Text::isNotEmpty($site->title())) {
$sanitizedTitle = Text::removeSpecialCharacters($site->title(), '-');
$sanitizedTitle = Text::removeQuotes($sanitizedTitle);
$sanitizedTitle = Text::removeSpaces($sanitizedTitle, '-');
$sanitizedTitle = trim($sanitizedTitle, '-');
if (Text::isNotEmpty($sanitizedTitle)) {
$filename = $sanitizedTitle . '.' . $fileExtension;
}
}
// Delete old image
$oldFilename = $site->logo(false);
if ($oldFilename) {
Filesystem::rmfile(PATH_UPLOADS . $oldFilename);
}
// Move from temporary directory to uploads
Filesystem::mv($_FILES['inputFile']['tmp_name'], PATH_UPLOADS . $filename);
// Permissions
chmod(PATH_UPLOADS . $filename, 0644);
// Store the filename in the database
$site->set(array('logo' => $filename));
ajaxResponse(0, 'Image uploaded.', array(
'filename' => $filename,
'absoluteURL' => DOMAIN_UPLOADS . $filename,
'absolutePath' => PATH_UPLOADS . $filename
));
-105
View File
@@ -1,105 +0,0 @@
<?php defined('BLUDIT') or die('Bludit CMS.');
header('Content-Type: application/json');
// $_POST
// ----------------------------------------------------------------------------
// (string) $_POST['username']
$username = empty($_POST['username']) ? false : $_POST['username'];
// ----------------------------------------------------------------------------
if ($username===false) {
ajaxResponse(1, 'Error in username.');
}
if ( ($login->role()!='admin') && ($login->username()!=$username) ) {
ajaxResponse(1, 'Error in username.');
}
if (!isset($_FILES['profilePictureInputFile'])) {
ajaxResponse(1, 'Error trying to upload the profile picture.');
}
// Check path traversal
if (Text::stringContains($username, DS, false)) {
$message = 'Path traversal detected.';
Log::set($message, LOG_TYPE_ERROR);
ajaxResponse(1, $message);
}
// Block dotfiles
if (strpos($_FILES['profilePictureInputFile']['name'], '.') === 0) {
$message = 'File type not allowed.';
Log::set($message, LOG_TYPE_ERROR);
ajaxResponse(1, $message);
}
// Sanitize username for filename to prevent issues with special characters
$sanitizedUsername = Text::removeSpecialCharacters($username, '-');
$sanitizedUsername = Text::removeQuotes($sanitizedUsername);
$sanitizedUsername = Text::removeSpaces($sanitizedUsername, '-');
// Check file extension
$fileExtension = Filesystem::extension($_FILES['profilePictureInputFile']['name']);
$fileExtension = Text::lowercase($fileExtension);
if (!in_array($fileExtension, $GLOBALS['ALLOWED_IMG_EXTENSION']) ) {
$message = $L->g('File type is not supported. Allowed types:').' '.implode(', ',$GLOBALS['ALLOWED_IMG_EXTENSION']);
Log::set($message, LOG_TYPE_ERROR);
ajaxResponse(1, $message);
}
// Check file MIME Type
$fileMimeType = Filesystem::mimeType($_FILES['profilePictureInputFile']['tmp_name']);
if ($fileMimeType!==false) {
if (!in_array($fileMimeType, $GLOBALS['ALLOWED_IMG_MIMETYPES'])) {
$message = $L->g('File mime type is not supported. Allowed types:').' '.implode(', ',$GLOBALS['ALLOWED_IMG_MIMETYPES']);
Log::set($message, LOG_TYPE_ERROR);
ajaxResponse(1, $message);
}
}
// Tmp filename
$tmpFilename = $sanitizedUsername.'.'.$fileExtension;
// Final filename
$filename = $sanitizedUsername.'.png';
// Ensure Bludit tmp directory exists
if (!Filesystem::directoryExists(PATH_TMP)) {
if (!Filesystem::mkdir(PATH_TMP, true)) {
$message = 'Temporary directory does not exist and cannot be created.';
Log::set($message, LOG_TYPE_ERROR);
ajaxResponse(1, $message);
}
}
// Move from temporary directory to uploads folder
$moved = rename($_FILES['profilePictureInputFile']['tmp_name'], PATH_TMP.$tmpFilename);
if (!$moved) {
$message = 'Error moving uploaded file to temporary directory.';
Log::set($message, LOG_TYPE_ERROR);
ajaxResponse(1, $message);
}
// Resize and convert to png
$image = new Image();
if ($image->setImage(PATH_TMP.$tmpFilename, PROFILE_IMG_WIDTH, PROFILE_IMG_HEIGHT, 'crop') === false) {
Filesystem::rmfile(PATH_TMP.$tmpFilename);
$message = 'Profile picture upload failed: GD cannot decode the image (unsupported format or corrupted file).';
Log::set($message, LOG_TYPE_ERROR);
ajaxResponse(1, $message);
}
$image->saveImage(PATH_UPLOADS_PROFILES.$filename, PROFILE_IMG_QUALITY, false, true);
// Delete temporary file
Filesystem::rmfile(PATH_TMP.$tmpFilename);
// Permissions
chmod(PATH_UPLOADS_PROFILES.$filename, 0644);
ajaxResponse(0, 'Image uploaded.', array(
'filename'=>$filename,
'absoluteURL'=>DOMAIN_UPLOADS_PROFILES.$filename,
'absolutePath'=>PATH_UPLOADS_PROFILES.$filename
));
?>
-52
View File
@@ -1,52 +0,0 @@
<?php defined('BLUDIT') or die('Bludit CMS.');
header('Content-Type: application/json');
/*
| Create/edit a page and save as draft
| If the UUID already exists the page is updated
|
| @_POST['title'] string Page title
| @_POST['content'] string Page content
| @_POST['uuid'] string Page uuid
| @_POST['uuid'] string Page type, by default is draft
|
| @return array
*/
// $_POST
// ----------------------------------------------------------------------------
$title = isset($_POST['title']) ? $_POST['title'] : false;
$content = isset($_POST['content']) ? $_POST['content'] : false;
$uuid = isset($_POST['uuid']) ? $_POST['uuid'] : false;
$type = isset($_POST['type']) ? $_POST['type'] : 'draft';
// ----------------------------------------------------------------------------
// Check UUID
if (empty($uuid)) {
ajaxResponse(1, 'Save as draft fail. UUID not defined.');
}
$page = array(
'uuid'=>$uuid,
'key'=>$uuid,
'slug'=>$uuid,
'title'=>$title,
'content'=>$content,
'type'=>$type
);
// Get the page key by the UUID
$pageKey = $pages->getByUUID($uuid);
// if pageKey is empty means the page doesn't exist
if (empty($pageKey)) {
createPage($page);
} else {
editPage($page);
}
ajaxResponse(0, 'Save as draft successfully.', array(
'uuid'=>$uuid
));
?>
-127
View File
@@ -1,127 +0,0 @@
<?php defined('BLUDIT') or die('Bludit CMS.');
header('Content-Type: application/json');
/*
| Upload an image to a particular page
|
| @_POST['uuid'] string Page uuid
|
| @return array
*/
// $_POST
// ----------------------------------------------------------------------------
$uuid = empty($_POST['uuid']) ? false : $_POST['uuid'];
// ----------------------------------------------------------------------------
// Check path traversal on $uuid
if ($uuid) {
if (Text::stringContains($uuid, DS, false)) {
$message = 'Path traversal detected.';
Log::set($message, LOG_TYPE_ERROR);
ajaxResponse(1, $message);
}
}
// Set upload directory
if ($uuid && IMAGE_RESTRICT) {
$imageDirectory = PATH_UPLOADS_PAGES . $uuid . DS;
$thumbnailDirectory = $imageDirectory . 'thumbnails' . DS;
if (!Filesystem::directoryExists($thumbnailDirectory)) {
Filesystem::mkdir($thumbnailDirectory, true);
}
} else {
$imageDirectory = PATH_UPLOADS;
$thumbnailDirectory = PATH_UPLOADS_THUMBNAILS;
}
$images = array();
foreach ($_FILES['images']['name'] as $uuid => $filename) {
// Check for errors
if ($_FILES['images']['error'][$uuid] != 0) {
$message = $L->g('Maximum load file size allowed:') . ' ' . ini_get('upload_max_filesize');
Log::set($message, LOG_TYPE_ERROR);
ajaxResponse(1, $message);
}
// Convert URL characters such as spaces or quotes to characters
$filename = urldecode($filename);
// Sanitize filename to prevent issues with special characters
$filenameWithoutExt = Filesystem::filename($filename);
$filenameWithoutExt = Text::removeSpecialCharacters($filenameWithoutExt, '-');
$filenameWithoutExt = Text::removeQuotes($filenameWithoutExt);
$filenameWithoutExt = Text::removeSpaces($filenameWithoutExt, '-');
$fileExtension = Filesystem::extension($filename);
$filename = $filenameWithoutExt . '.' . $fileExtension;
// Block dotfiles
if (strpos($filename, '.') === 0) {
$message = 'File type not allowed.';
Log::set($message, LOG_TYPE_ERROR);
ajaxResponse(1, $message);
}
// Check path traversal on $filename
if (Text::stringContains($filename, DS, false)) {
$message = 'Path traversal detected.';
Log::set($message, LOG_TYPE_ERROR);
ajaxResponse(1, $message);
}
// Check file extension
$fileExtension = Filesystem::extension($filename);
$fileExtension = Text::lowercase($fileExtension);
if (!in_array($fileExtension, $GLOBALS['ALLOWED_IMG_EXTENSION'])) {
$message = $L->g('File type is not supported. Allowed types:') . ' ' . implode(', ', $GLOBALS['ALLOWED_IMG_EXTENSION']);
Log::set($message, LOG_TYPE_ERROR);
ajaxResponse(1, $message);
}
// Check file MIME Type
$fileMimeType = Filesystem::mimeType($_FILES['images']['tmp_name'][$uuid]);
if ($fileMimeType === false) {
$message = $L->g('File mime type is not supported. Allowed types:') . ' ' . implode(', ', $GLOBALS['ALLOWED_IMG_MIMETYPES']);
Log::set($message, LOG_TYPE_ERROR);
ajaxResponse(1, $message);
}
if (!in_array($fileMimeType, $GLOBALS['ALLOWED_IMG_MIMETYPES'])) {
$message = $L->g('File mime type is not supported. Allowed types:') . ' ' . implode(', ', $GLOBALS['ALLOWED_IMG_MIMETYPES']);
Log::set($message, LOG_TYPE_ERROR);
ajaxResponse(1, $message);
}
// Ensure Bludit tmp directory exists
if (!Filesystem::directoryExists(PATH_TMP)) {
if (!Filesystem::mkdir(PATH_TMP, true)) {
$message = 'Temporary directory does not exist and cannot be created.';
Log::set($message, LOG_TYPE_ERROR);
ajaxResponse(1, $message);
}
}
// Move from PHP tmp file to Bludit tmp directory
$moved = Filesystem::mv($_FILES['images']['tmp_name'][$uuid], PATH_TMP . $filename);
if (!$moved) {
$message = 'Error moving uploaded file to temporary directory.';
Log::set($message, LOG_TYPE_ERROR);
ajaxResponse(1, $message);
}
// Transform the image and generate the thumbnail
$image = transformImage(PATH_TMP . $filename, $imageDirectory, $thumbnailDirectory);
if ($image) {
chmod($image, 0644);
$filename = Filesystem::filename($image);
array_push($images, $filename);
} else {
$message = 'Error after transformImage() function.';
Log::set($message, LOG_TYPE_ERROR);
ajaxResponse(1, $message);
}
}
ajaxResponse(0, 'Images uploaded.', array(
'images' => $images
));
-104
View File
@@ -1,104 +0,0 @@
<?php defined('BLUDIT') or die('Bludit CMS.');
// Start the session
// If the session is not possible to start the admin area is not available
Session::start($site->urlPath(), $site->isHTTPS());
if (Session::started()===false) {
exit('Bludit CMS. Session initialization failed.');
}
$login = new Login();
$layout = array(
'controller'=>null,
'view'=>null,
'template'=>'index.php',
'slug'=>null,
'plugin'=>false,
'parameters'=>null,
'title'=>'Bludit'
);
// Get the Controller
$explodeSlug = $url->explodeSlug();
$layout['controller'] = $layout['view'] = $layout['slug'] = empty($explodeSlug[0])?'dashboard':$explodeSlug[0];
unset($explodeSlug[0]);
// Get the Plugins
include(PATH_RULES.'60.plugins.php');
// Check if the user want to access to an admin controller or view from a plugin
if ($layout['controller'] === 'plugin' && !empty($explodeSlug)) {
// Lowercase plugins class name to search by case-insensitive
$pluginsLowerCases = array_change_key_case($pluginsInstalled);
$pluginName = Text::lowercase(array_shift($explodeSlug));
if (isset($pluginsLowerCases[$pluginName])) {
$layout['plugin'] = $pluginsLowerCases[$pluginName];
}
}
// Get the URL parameters
$layout['parameters'] = implode('/', $explodeSlug);
// --- AJAX ---
if ($layout['slug']==='ajax') {
if ($login->isLogged()) {
// Rules: Security check CSRF
include(PATH_RULES.'99.security.php');
// Load the ajax file
if (Sanitize::pathFile(PATH_AJAX, $layout['parameters'].'.php')) {
include(PATH_AJAX.$layout['parameters'].'.php');
}
}
header('HTTP/1.1 401 User not logged.');
exit(0);
}
// --- ADMIN AREA ---
else
{
// Boot rules
include(PATH_RULES.'69.pages.php');
include(PATH_RULES.'99.header.php');
include(PATH_RULES.'99.paginator.php');
include(PATH_RULES.'99.themes.php');
include(PATH_RULES.'99.security.php');
// Page not found.
// User not logged.
// Slug is login.
if ($url->notFound() || !$login->isLogged() || ($url->slug()==='login') ) {
$layout['controller'] = 'login';
$layout['view'] = 'login';
$layout['template'] = 'login.php';
// Generate the tokenCSRF for the user not logged, when the user log-in the token will be changed.
$security->generateTokenCSRF();
}
// Define variables
$ADMIN_CONTROLLER = $layout['controller'];
$ADMIN_VIEW = $layout['view'];
// Load plugins before the admin area will be load.
Theme::plugins('beforeAdminLoad');
// Load init.php if the theme has one.
if (Sanitize::pathFile(PATH_ADMIN_THEMES, $site->adminTheme().DS.'init.php')) {
include(PATH_ADMIN_THEMES.$site->adminTheme().DS.'init.php');
}
// Load controller.
if (Sanitize::pathFile(PATH_ADMIN_CONTROLLERS, $layout['controller'].'.php')) {
include(PATH_ADMIN_CONTROLLERS.$layout['controller'].'.php');
} elseif ($layout['plugin'] && method_exists($layout['plugin'], 'adminController')) {
$layout['plugin']->adminController();
}
// Load view and theme.
if (Sanitize::pathFile(PATH_ADMIN_THEMES, $site->adminTheme().DS.$layout['template'])) {
include(PATH_ADMIN_THEMES.$site->adminTheme().DS.$layout['template']);
}
// Load plugins after the admin area is loaded.
Theme::plugins('afterAdminLoad');
}
-273
View File
@@ -1,273 +0,0 @@
<?php defined('BLUDIT') or die('Bludit CMS.');
// Bludit version
define('BLUDIT_VERSION', '3.22.0');
define('BLUDIT_CODENAME', 'BrownBear');
define('BLUDIT_RELEASE_DATE', '2026-05-10');
define('BLUDIT_BUILD', '20260510');
// Change to TRUE for debugging
define('DEBUG_MODE', FALSE);
define('DEBUG_TYPE', 'INFO'); // INFO, TRACE
// This determines whether errors should be printed to the screen as part of the output or if they should be hidden from the user.
ini_set("display_errors", 0);
// Even when display_errors is on, errors that occur during PHP's startup sequence are not displayed.
// It's strongly recommended to keep display_startup_errors off, except for debugging.
ini_set('display_startup_errors', 0);
// If disabled, error message will be solely plain text instead HTML code.
ini_set("html_errors", 0);
// Tells whether script error messages should be logged to the server's error log or error_log.
ini_set('log_errors', 1);
if (DEBUG_MODE) {
error_reporting(E_ERROR | E_WARNING | E_PARSE | E_NOTICE);
} else {
error_reporting(E_ERROR);
}
// PHP paths
// PATH_ROOT and PATH_BOOT are defined in index.php
define('PATH_LANGUAGES', PATH_ROOT . 'bl-languages' . DS);
define('PATH_THEMES', PATH_ROOT . 'bl-themes' . DS);
define('PATH_PLUGINS', PATH_ROOT . 'bl-plugins' . DS);
define('PATH_KERNEL', PATH_ROOT . 'bl-kernel' . DS);
define('PATH_CONTENT', PATH_ROOT . 'bl-content' . DS);
define('PATH_ABSTRACT', PATH_KERNEL . 'abstract' . DS);
define('PATH_RULES', PATH_KERNEL . 'boot' . DS . 'rules' . DS);
define('PATH_HELPERS', PATH_KERNEL . 'helpers' . DS);
define('PATH_AJAX', PATH_KERNEL . 'ajax' . DS);
define('PATH_CORE_JS', PATH_KERNEL . 'js' . DS);
define('PATH_PAGES', PATH_CONTENT . 'pages' . DS);
define('PATH_DATABASES', PATH_CONTENT . 'databases' . DS);
define('PATH_PLUGINS_DATABASES', PATH_CONTENT . 'databases' . DS . 'plugins' . DS);
define('PATH_TMP', PATH_CONTENT . 'tmp' . DS);
define('PATH_UPLOADS', PATH_CONTENT . 'uploads' . DS);
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);
define('PATH_ADMIN_CONTROLLERS', PATH_ADMIN . 'controllers' . DS);
define('PATH_ADMIN_VIEWS', PATH_ADMIN . 'views' . DS);
define('DEBUG_FILE', PATH_CONTENT . 'debug.txt');
// PAGES DATABASE
define('DB_PAGES', PATH_DATABASES . 'pages.php');
define('DB_SITE', PATH_DATABASES . 'site.php');
define('DB_CATEGORIES', PATH_DATABASES . 'categories.php');
define('DB_TAGS', PATH_DATABASES . 'tags.php');
define('DB_SYSLOG', PATH_DATABASES . 'syslog.php');
define('DB_USERS', PATH_DATABASES . 'users.php');
define('DB_SECURITY', PATH_DATABASES . 'security.php');
// User environment variables
include(PATH_KERNEL . 'boot' . DS . 'variables.php');
// Set internal character encoding
mb_internal_encoding(CHARSET);
// Set HTTP output character encoding
mb_http_output(CHARSET);
// Inclde Abstract Classes
include(PATH_ABSTRACT . 'dbjson.class.php');
include(PATH_ABSTRACT . 'dblist.class.php');
include(PATH_ABSTRACT . 'plugin.class.php');
// Inclde Classes
include(PATH_KERNEL . 'pages.class.php');
include(PATH_KERNEL . 'users.class.php');
include(PATH_KERNEL . 'tags.class.php');
include(PATH_KERNEL . 'language.class.php');
include(PATH_KERNEL . 'site.class.php');
include(PATH_KERNEL . 'categories.class.php');
include(PATH_KERNEL . 'syslog.class.php');
include(PATH_KERNEL . 'pagex.class.php');
include(PATH_KERNEL . 'category.class.php');
include(PATH_KERNEL . 'tag.class.php');
include(PATH_KERNEL . 'user.class.php');
include(PATH_KERNEL . 'url.class.php');
include(PATH_KERNEL . 'login.class.php');
include(PATH_KERNEL . 'parsedown.class.php');
include(PATH_KERNEL . 'security.class.php');
// Include functions
include(PATH_KERNEL . 'functions.php');
// Include Helpers Classes
include(PATH_HELPERS . 'text.class.php');
include(PATH_HELPERS . 'log.class.php');
include(PATH_HELPERS . 'date.class.php');
include(PATH_HELPERS . 'theme.class.php');
include(PATH_HELPERS . 'session.class.php');
include(PATH_HELPERS . 'redirect.class.php');
include(PATH_HELPERS . 'sanitize.class.php');
include(PATH_HELPERS . 'valid.class.php');
include(PATH_HELPERS . 'email.class.php');
include(PATH_HELPERS . 'filesystem.class.php');
include(PATH_HELPERS . 'alert.class.php');
include(PATH_HELPERS . 'paginator.class.php');
include(PATH_HELPERS . 'image.class.php');
include(PATH_HELPERS . 'tcp.class.php');
include(PATH_HELPERS . 'dom.class.php');
include(PATH_HELPERS . 'cookie.class.php');
/**
* ---------------------------------------------------------------------------
* If you have bypassed the license check, I understand.
* But please consider supporting the project on Patreon if you use this
* commercially. It helps me keep the core free for everyone.
* ---------------------------------------------------------------------------
*/
define('BLUDIT_PRO_HASH', substr(md5(BLUDIT_BUILD), 0, 8));
$_bluditProFile = PATH_KERNEL . 'bludit.pro.' . BLUDIT_PRO_HASH . '.php';
if (file_exists($_bluditProFile)) {
include($_bluditProFile);
}
unset($_bluditProFile);
// Objects
$pages = new Pages();
$users = new Users();
$tags = new Tags();
$categories = new Categories();
$site = new Site();
$url = new Url();
$security = new Security();
$syslog = new Syslog();
// --- Relative paths ---
// These paths are relative for the user / web browsing.
// Base URL
// The user can define the base URL.
// Left empty if you want to Bludit try to detect the base URL.
$base = '';
if (!empty($_SERVER['DOCUMENT_ROOT']) && !empty($_SERVER['SCRIPT_NAME']) && empty($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);
}
if (strpos($_SERVER['REQUEST_URI'], $base) !== 0) {
$base = '/';
} elseif ($base != DS) {
$base = trim($base, '/');
$base = '/' . $base . '/';
} else {
// 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_THEME_CSS', HTML_PATH_THEME . 'css/');
define('HTML_PATH_THEME_JS', HTML_PATH_THEME . 'js/');
define('HTML_PATH_THEME_IMG', HTML_PATH_THEME . 'img/');
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_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_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/');
// --- Objects with dependency ---
$language = new Language($site->language());
$url->checkFilters($site->uriFilters());
// --- CONSTANTS with dependency ---
// Tag URI filter
define('TAG_URI_FILTER', $url->filters('tag'));
// Category URI filter
define('CATEGORY_URI_FILTER', $url->filters('category'));
// Page URI filter
define('PAGE_URI_FILTER', $url->filters('page'));
// Content order by: date / position
define('ORDER_BY', $site->orderBy());
// Allow unicode characters in the URL
define('EXTREME_FRIENDLY_URL', $site->extremeFriendly());
// Minutes to execute the autosave function
define('AUTOSAVE_INTERVAL', $site->autosaveInterval());
// TRUE for upload images restric to a pages, FALSE to upload images in common
define('IMAGE_RESTRICT', $site->imageRestrict());
// TRUE to convert relatives images to absoultes, FALSE No changes apply
define('IMAGE_RELATIVE_TO_ABSOLUTE', $site->imageRelativeToAbsolute());
// TRUE if the markdown parser is enabled
define('MARKDOWN_PARSER', $site->markdownParser());
// --- PHP paths with dependency ---
// These paths are absolutes for the OS
define('THEME_DIR', PATH_ROOT . 'bl-themes' . DS . $site->theme() . DS);
define('THEME_DIR_PHP', THEME_DIR . 'php' . DS);
define('THEME_DIR_CSS', THEME_DIR . 'css' . DS);
define('THEME_DIR_JS', THEME_DIR . 'js' . DS);
define('THEME_DIR_IMG', THEME_DIR . 'img' . DS);
define('THEME_DIR_LANG', THEME_DIR . 'languages' . DS);
// --- Absolute paths with domain ---
// These paths are absolutes for the user / web browsing.
define('DOMAIN', $site->domain());
define('DOMAIN_BASE', DOMAIN . HTML_PATH_ROOT);
define('DOMAIN_CORE_JS', DOMAIN . HTML_PATH_CORE_JS);
define('DOMAIN_CORE_CSS', DOMAIN . HTML_PATH_CORE_CSS);
define('DOMAIN_THEME', DOMAIN . HTML_PATH_THEME);
define('DOMAIN_THEME_CSS', DOMAIN . HTML_PATH_THEME_CSS);
define('DOMAIN_THEME_JS', DOMAIN . HTML_PATH_THEME_JS);
define('DOMAIN_THEME_IMG', DOMAIN . HTML_PATH_THEME_IMG);
define('DOMAIN_ADMIN_THEME', DOMAIN . HTML_PATH_ADMIN_THEME);
define('DOMAIN_ADMIN_THEME_CSS', DOMAIN . HTML_PATH_ADMIN_THEME_CSS);
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);
define('DOMAIN_ADMIN', DOMAIN_BASE . ADMIN_URI_FILTER . '/');
define('DOMAIN_TAGS', Text::addSlashes(DOMAIN_BASE . TAG_URI_FILTER, false, true));
define('DOMAIN_CATEGORIES', Text::addSlashes(DOMAIN_BASE . CATEGORY_URI_FILTER, false, true));
define('DOMAIN_PAGES', Text::addSlashes(DOMAIN_BASE . PAGE_URI_FILTER, false, true));
$ADMIN_CONTROLLER = '';
$ADMIN_VIEW = '';
$ID_EXECUTION = uniqid(); // string 13 characters long
$WHERE_AM_I = $url->whereAmI();
// --- Objects shortcuts ---
$L = $language;
// DEBUG: Print constants
// $arr = array_filter(get_defined_constants(), 'is_string');
// echo json_encode($arr);
// exit;
-135
View File
@@ -1,135 +0,0 @@
<?php defined('BLUDIT') or die('Bludit CMS.');
// ============================================================================
// Variables
// ============================================================================
$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(),
'editorToolbar'=>array(),
'beforeAll'=>array(),
'afterAll'=>array(),
'paginator'=>array(),
'afterPageCreate'=>array(),
'afterPageModify'=>array(),
'afterPageDelete'=>array(),
'loginHead'=>array(),
'loginBodyBegin'=>array(),
'loginBodyEnd'=>array(),
'all'=>array()
);
$pluginsEvents = $plugins;
unset($pluginsEvents['all']);
$pluginsInstalled = array();
// ============================================================================
// Functions
// ============================================================================
function buildPlugins()
{
global $plugins;
global $pluginsEvents;
global $pluginsInstalled;
global $L;
global $site;
// Get declared clasess BEFORE load plugins clasess
$currentDeclaredClasess = get_declared_classes();
// List plugins directories
$list = Filesystem::listDirectories(PATH_PLUGINS);
// Load each plugin clasess
foreach ($list as $pluginPath) {
// Check if the directory has the plugin.php
if (file_exists($pluginPath.DS.'plugin.php')) {
include_once($pluginPath.DS.'plugin.php');
}
}
// Get plugins clasess loaded
$pluginsDeclaredClasess = array_diff(get_declared_classes(), $currentDeclaredClasess);
foreach ($pluginsDeclaredClasess as $pluginClass) {
$Plugin = new $pluginClass;
// Check if the plugin is translated
$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;
}
$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']);
// Remove name and description from the language file loaded and add new words if there are
// This function overwrite the key=>value
unset($database['plugin-data']);
if (!empty($database)) {
$L->add($database);
}
// $plugins['all'] Array with all plugins, installed and not installed
$plugins['all'][$pluginClass] = $Plugin;
// If the plugin is installed insert on the hooks
if ($Plugin->installed()) {
// Include custom hooks
if (!empty($Plugin->customHooks)) {
foreach ($Plugin->customHooks as $customHook) {
if (!isset($plugins[$customHook])) {
$plugins[$customHook] = array();
$pluginsEvents[$customHook] = array();
}
}
}
$pluginsInstalled[$pluginClass] = $Plugin;
foreach ($pluginsEvents as $event=>$value) {
if (method_exists($Plugin, $event)) {
array_push($plugins[$event], $Plugin);
}
}
}
// Sort the plugins by the position for the site sidebar
uasort($plugins['siteSidebar'], function ($a, $b) {
return $a->position() <=> $b->position();
}
);
}
}
// ============================================================================
// Main
// ============================================================================
buildPlugins();
-23
View File
@@ -1,23 +0,0 @@
<?php defined('BLUDIT') or die('Bludit CMS.');
// Redirect admin, from /admin to /admin/
if ($url->uri()==HTML_PATH_ROOT.ADMIN_URI_FILTER) {
Redirect::url(DOMAIN_ADMIN);
}
// Redirect blog, from /blog to /blog/
// This rule only works when the user set a page as homepage
if ($url->uri()==HTML_PATH_ROOT.'blog' && $site->homepage()) {
$filter = $url->filters('blog');
$finalURL = Text::addSlashes(DOMAIN_BASE.$filter, false, true);
Redirect::url($finalURL);
}
// Redirect pages, from /my-page/ to /my-page
if ($url->whereAmI()=='page' && !$url->notFound()) {
$pageKey = $url->slug();
if (Text::endsWith($pageKey, '/')) {
$pageKey = rtrim($pageKey, '/');
Redirect::url(DOMAIN_PAGES.$pageKey);
}
}
-86
View File
@@ -1,86 +0,0 @@
<?php defined('BLUDIT') or die('Bludit CMS.');
// ============================================================================
// Variables
// ============================================================================
// Array with pages, each page is a Page Object
// Filtered by pagenumber, number of items per page and sorted by date/position
/*
array(
0 => Page Object,
1 => Page Object,
...
N => Page Object
)
*/
$content = array();
// Page filtered by the user, is a Page Object
$page = false;
// Array with static content, each item is a Page Object
// Order by position
/*
array(
0 => Page Object,
1 => Page Object,
...
N => Page Object
)
*/
$staticContent = $staticPages = buildStaticPages();
// ============================================================================
// Main
// ============================================================================
// Execute the scheduler
if ($pages->scheduler()) {
// Execute plugins with the hook afterPageCreate
Theme::plugins('afterPageCreate');
reindexTags();
reindexCategories();
// Add to syslog
$syslog->add(array(
'dictionaryKey'=>'content-published-from-scheduler',
'notes'=>''
));
}
// Set home page if the user defined one
if ($site->homepage() && $url->whereAmI()==='home') {
$pageKey = $site->homepage();
if ($pages->exists($pageKey)) {
$url->setSlug($pageKey);
$content[0] = $page = buildThePage();
}
}
// Build specific page
elseif ($url->whereAmI()==='page') {
$content[0] = $page = buildThePage();
}
// Build content by tag
elseif ($url->whereAmI()==='tag') {
$content = buildPagesByTag();
}
// Build content by category
elseif ($url->whereAmI()==='category') {
$content = buildPagesByCategory();
}
// Build content for the homepage
elseif ( ($url->whereAmI()==='home') || ($url->whereAmI()==='blog') ) {
$content = buildPagesForHome();
}
if (isset($content[0])) {
$page = $content[0];
}
// If set notFound, create the page 404
if ($url->notFound()) {
$content[0] = $page = buildErrorPage();
}
-4
View File
@@ -1,4 +0,0 @@
<?php defined('BLUDIT') or die('Bludit CMS.');
header('HTTP/1.0 '.$url->httpCode().' '.$url->httpMessage());
header('X-Powered-By: Bludit');
-54
View File
@@ -1,54 +0,0 @@
<?php defined('BLUDIT') or die('Bludit CMS.');
// Current page number
$currentPage = $url->pageNumber();
Paginator::set('currentPage', $currentPage);
if ($url->whereAmI()=='admin') {
$itemsPerPage = ITEMS_PER_PAGE_ADMIN;
$numberOfItems = $pages->count(true);
} elseif ($url->whereAmI()=='tag') {
$itemsPerPage = $site->itemsPerPage();
$tagKey = $url->slug();
$numberOfItems = $tags->numberOfPages($tagKey);
} elseif ($url->whereAmI()=='category') {
$itemsPerPage = $site->itemsPerPage();
$categoryKey = $url->slug();
$numberOfItems = $categories->numberOfPages($categoryKey);
} else {
$itemsPerPage = $site->itemsPerPage();
$numberOfItems = $pages->count(true);
}
// Execute hook from plugins
Theme::plugins('paginator');
// Items per page
Paginator::set('itemsPerPage', $itemsPerPage);
// Amount of items
Paginator::set('numberOfItems', $numberOfItems);
// Amount of pages
$numberOfPages = (int) max(ceil($numberOfItems / $itemsPerPage), 1);
Paginator::set('numberOfPages', $numberOfPages);
// TRUE if exists a next page to show
$showNext = $numberOfPages > $currentPage;
Paginator::set('showNext', $showNext);
// TRUE if exists a previous page to show
$showPrev = $currentPage > Paginator::firstPage();
Paginator::set('showPrev', $showPrev);
// TRUE if exists a next and previous page to show
$showNextPrev = $showNext && $showPrev;
Paginator::set('showNextPrev', $showNextPrev);
// Integer with the next page
$nextPage = max(0, $currentPage+1);
Paginator::set('nextPage', $nextPage);
// Integer with the previous page
$prevPage = min($numberOfPages, $currentPage-1);
Paginator::set('prevPage', $prevPage);

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