Compare commits
	
		
			No commits in common. "master" and "1.0.0" have entirely different histories.
		
	
	
		
	
		
							
								
								
									
										29
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										29
									
								
								README.md
									
									
									
									
									
								
							@ -1,4 +1,4 @@
 | 
			
		||||

 | 
			
		||||

 | 
			
		||||
 | 
			
		||||
# Novaconium PHP:  A PHP Framework Built from the Past
 | 
			
		||||
 | 
			
		||||
@ -6,30 +6,15 @@ NovaconiumPHP is a high-performance PHP framework designed with inspiration from
 | 
			
		||||
 | 
			
		||||
Pronounced: Noh-vah-koh-nee-um
 | 
			
		||||
 | 
			
		||||
Packagist: https://packagist.org/packages/4lt/novaconium 
 | 
			
		||||
Master Repo: https://git.4lt.ca/4lt/novaconium
 | 
			
		||||
 | 
			
		||||
## Getting Started
 | 
			
		||||
 | 
			
		||||
Novaconium is heavly influenced by docker, but you can use composer outside of docker.  
 | 
			
		||||
You can [learn more about how novaconium works with composer](https://git.4lt.ca/4lt/novaconium/src/branch/master/docs/Install-Composer-On-Debian.md).
 | 
			
		||||
### Installation 
 | 
			
		||||
 | 
			
		||||
```bash
 | 
			
		||||
PROJECTNAME=novaproject;
 | 
			
		||||
mkdir -p $PROJECTNAME/novaconium;
 | 
			
		||||
cd $PROJECTNAME;
 | 
			
		||||
mkdir project_name;
 | 
			
		||||
cd project_name;
 | 
			
		||||
mkdir -p App/controllers App/templates App/views public
 | 
			
		||||
touch App/controllers/404.php App/controllers/index.php App/views/index.html.twig public/index.php public/.htaccess App/routes.php
 | 
			
		||||
 | 
			
		||||
docker run --rm --interactive --tty --volume ./novaconium/:/app composer:latest require 4lt/novaconium;
 | 
			
		||||
 | 
			
		||||
cp -R novaconium/vendor/4lt/novaconium/skeleton/. .;
 | 
			
		||||
 | 
			
		||||
# Edit .env
 | 
			
		||||
# Edit novaconium/App/config.php
 | 
			
		||||
 | 
			
		||||
docker compose up -d
 | 
			
		||||
composer require 4lt/novaconium
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
## Documentation
 | 
			
		||||
 | 
			
		||||
* [Novaconiumm Official Repo](https://git.4lt.ca/4lt/novaconium)
 | 
			
		||||
* [CORXN Apache and PHP Container for Novaconium](https://git.4lt.ca/4lt/CORXN)
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										39
									
								
								_assets/header.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								_assets/header.svg
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,39 @@
 | 
			
		||||
<svg width="100%" height="200" xmlns="http://www.w3.org/2000/svg">
 | 
			
		||||
  <rect width="100%" height="100%" fill="black"/>
 | 
			
		||||
  <defs>
 | 
			
		||||
    <radialGradient id="star-gradient" cx="50%" cy="50%" r="50%" fx="50%" fy="50%">
 | 
			
		||||
      <stop offset="0%" style="stop-color: white; stop-opacity: 1" />
 | 
			
		||||
      <stop offset="100%" style="stop-color: white; stop-opacity: 0" />
 | 
			
		||||
    </radialGradient>
 | 
			
		||||
    <g id="star">
 | 
			
		||||
      <circle cx="0" cy="0" r="2" fill="white" />
 | 
			
		||||
    </g>
 | 
			
		||||
  </defs>
 | 
			
		||||
 | 
			
		||||
  <!-- Lots of stars moving outward slower -->
 | 
			
		||||
  <g>
 | 
			
		||||
    <use href="#star" x="50%" y="50%" transform="translate(-300,-150) scale(1)" />
 | 
			
		||||
    <use href="#star" x="50%" y="50%" transform="translate(300,-150) scale(1)" />
 | 
			
		||||
    <use href="#star" x="50%" y="50%" transform="translate(-300,150) scale(1)" />
 | 
			
		||||
    <use href="#star" x="50%" y="50%" transform="translate(300,150) scale(1)" />
 | 
			
		||||
    <use href="#star" x="50%" y="50%" transform="translate(-150,-75) scale(0.5)" />
 | 
			
		||||
    <use href="#star" x="50%" y="50%" transform="translate(150,-75) scale(0.5)" />
 | 
			
		||||
    <use href="#star" x="50%" y="50%" transform="translate(-150,75) scale(0.5)" />
 | 
			
		||||
    <use href="#star" x="50%" y="50%" transform="translate(150,75) scale(0.5)" />
 | 
			
		||||
    <use href="#star" x="50%" y="50%" transform="translate(-75,-37) scale(0.7)" />
 | 
			
		||||
    <use href="#star" x="50%" y="50%" transform="translate(75,-37) scale(0.7)" />
 | 
			
		||||
    <use href="#star" x="50%" y="50%" transform="translate(-75,37) scale(0.7)" />
 | 
			
		||||
    <use href="#star" x="50%" y="50%" transform="translate(75,37) scale(0.7)" />
 | 
			
		||||
    <use href="#star" x="50%" y="50%" transform="translate(-350,-175) scale(0.4)" />
 | 
			
		||||
    <use href="#star" x="50%" y="50%" transform="translate(350,-175) scale(0.4)" />
 | 
			
		||||
    <use href="#star" x="50%" y="50%" transform="translate(-350,175) scale(0.4)" />
 | 
			
		||||
    <use href="#star" x="50%" y="50%" transform="translate(350,175) scale(0.4)" />
 | 
			
		||||
    <animateTransform attributeName="transform" type="scale" from="1" to="10" dur="2s" repeatCount="indefinite"/>
 | 
			
		||||
    <animate attributeName="opacity" from="1" to="0" dur="2s" repeatCount="indefinite" />
 | 
			
		||||
  </g>
 | 
			
		||||
 | 
			
		||||
  <!-- Centered Title -->
 | 
			
		||||
  <text x="50%" y="50%" fill="white" font-size="40" text-anchor="middle" font-family="Arial" dy=".3em">
 | 
			
		||||
    Novaconium PHP
 | 
			
		||||
  </text>
 | 
			
		||||
</svg>
 | 
			
		||||
| 
		 After Width: | Height: | Size: 2.2 KiB  | 
										
											Binary file not shown.
										
									
								
							| 
		 Before Width: | Height: | Size: 49 KiB  | 
@ -1,6 +1,7 @@
 | 
			
		||||
{
 | 
			
		||||
    "name": "4lt/novaconium", 
 | 
			
		||||
    "description": "A high-performance PHP framework built from the past.",
 | 
			
		||||
    "version": "1.0.0",
 | 
			
		||||
    "license": "MIT",
 | 
			
		||||
    "authors": [
 | 
			
		||||
        {
 | 
			
		||||
@ -16,8 +17,7 @@
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "require": {
 | 
			
		||||
      "twig/twig": "*",
 | 
			
		||||
      "nickyeoman/php-validation-class": "^5.0"
 | 
			
		||||
      "twig/twig": "*"
 | 
			
		||||
    },
 | 
			
		||||
    "minimum-stability": "stable", 
 | 
			
		||||
    "extra": {
 | 
			
		||||
 | 
			
		||||
@ -1,44 +0,0 @@
 | 
			
		||||
<?php
 | 
			
		||||
$framework_routes = [
 | 
			
		||||
    '/novaconium' => [ 
 | 
			
		||||
        'get' => 'NOVACONIUM/init'
 | 
			
		||||
    ],
 | 
			
		||||
    '/novaconium/create_admin' => [ 
 | 
			
		||||
        'post' => 'NOVACONIUM/create_admin'
 | 
			
		||||
    ],
 | 
			
		||||
    '/novaconium/login' => [
 | 
			
		||||
        'post' => 'NOVACONIUM/authenticate',
 | 
			
		||||
        'get' => 'NOVACONIUM/login'
 | 
			
		||||
    ],
 | 
			
		||||
    '/novaconium/dashboard' => [
 | 
			
		||||
        'get' => 'NOVACONIUM/dashboard'
 | 
			
		||||
    ],
 | 
			
		||||
    '/novaconium/pages' => [
 | 
			
		||||
        'get' => 'NOVACONIUM/pages'
 | 
			
		||||
    ],
 | 
			
		||||
    '/novaconium/page/edit/{id}' => [
 | 
			
		||||
        'get' => 'NOVACONIUM/editpage'
 | 
			
		||||
    ],
 | 
			
		||||
    '/novaconium/page/create' => [
 | 
			
		||||
        'get' => 'NOVACONIUM/editpage'
 | 
			
		||||
    ],
 | 
			
		||||
    '/novaconium/savePage' => [
 | 
			
		||||
        'post' => 'NOVACONIUM/savepage'
 | 
			
		||||
    ],
 | 
			
		||||
    '/novaconium/messages' => [
 | 
			
		||||
        'get' => 'NOVACONIUM/messages'
 | 
			
		||||
    ],
 | 
			
		||||
    '/novaconium/messages/delete/{id}' => [
 | 
			
		||||
        'get' => 'NOVACONIUM/message_delete'
 | 
			
		||||
    ],
 | 
			
		||||
    '/novaconium/messages/edit/{id}' => [
 | 
			
		||||
        'get' => 'NOVACONIUM/message_edit'
 | 
			
		||||
    ],
 | 
			
		||||
    '/novaconium/message_save' => [
 | 
			
		||||
        'post' => 'NOVACONIUM/message_save'
 | 
			
		||||
    ],
 | 
			
		||||
    '/novaconium/logout' => [
 | 
			
		||||
        'post' => 'NOVACONIUM/logout',
 | 
			
		||||
        'get' => 'NOVACONIUM/logout'
 | 
			
		||||
    ]
 | 
			
		||||
];
 | 
			
		||||
@ -1,70 +0,0 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
use Nickyeoman\Validation;
 | 
			
		||||
$v = new Nickyeoman\Validation\Validate();
 | 
			
		||||
 | 
			
		||||
$url_success = '/novaconium/dashboard';
 | 
			
		||||
$url_fail = '/novaconium/login';
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
// Don't go  further if already logged in
 | 
			
		||||
if ( !empty($session->get('username')) ) {
 | 
			
		||||
    $redirect->url($url_success);
 | 
			
		||||
    makeitso();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Make sure Session Token is correct
 | 
			
		||||
if ($session->get('token') != $post->get('token')) {
 | 
			
		||||
    $messages->addMessage('error', "Invalid Session.");
 | 
			
		||||
    $log->error("Login Authentication - Invalid Session Token");
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Handle Username
 | 
			
		||||
$rawUsername = $post->get('username', null);
 | 
			
		||||
$cleanUsername = $v->clean($rawUsername); // Clean the input
 | 
			
		||||
$username = strtolower($cleanUsername); // Convert to lowercase
 | 
			
		||||
if (!$username) {
 | 
			
		||||
    $messages->addMessage('error', "No Username given.");
 | 
			
		||||
} 
 | 
			
		||||
 | 
			
		||||
// Handle Password
 | 
			
		||||
$password = $v->clean($post->get('password', null));
 | 
			
		||||
if ( empty($password) ) {
 | 
			
		||||
    $messages->addMessage('error', "Password Empty.");
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*************************************************************************************************************
 | 
			
		||||
 * Query Database
 | 
			
		||||
 ************************************************************************************************************/
 | 
			
		||||
 | 
			
		||||
if ($messages->count('error') === 0) {
 | 
			
		||||
    $query = "SELECT id, username, email, password, blocked FROM users WHERE username = ? OR email = ?";
 | 
			
		||||
    $matched = $db->getRow($query, [$username, $username]);  
 | 
			
		||||
    if (empty($matched)) {
 | 
			
		||||
        $messages->addMessage('error', "User or Password incorrect.");
 | 
			
		||||
        $log->warning("Login Authentication - Login Error, user doesn't exist");
 | 
			
		||||
    }    
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
if ($messages->count('error') === 0) {
 | 
			
		||||
    // Re-apply pepper
 | 
			
		||||
    $peppered = hash_hmac('sha3-512', $password, $config['secure_key']);
 | 
			
		||||
 | 
			
		||||
    // Verify hashed password
 | 
			
		||||
    if (!password_verify($peppered, $matched['password'])) {
 | 
			
		||||
        $messages->addMessage('error', "User or Password incorrect.");
 | 
			
		||||
        $log->warning("Login Authentication - Login Error, password wrong");
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Process Login or Redirect
 | 
			
		||||
if ($messages->count('error') === 0) {
 | 
			
		||||
    $query = "SELECT groupName FROM user_groups WHERE user_id = ?";
 | 
			
		||||
    $groups = $db->getRow($query, [$matched['id']]);
 | 
			
		||||
    $session->set('username', $cleanUsername);
 | 
			
		||||
    $session->set('group', $groups['groupName']);
 | 
			
		||||
    $redirect->url($url_success);
 | 
			
		||||
    $log->info("Login Authentication - Login Success");
 | 
			
		||||
} else {
 | 
			
		||||
    $redirect->url($url_fail);
 | 
			
		||||
}
 | 
			
		||||
@ -1,58 +0,0 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
use Nickyeoman\Validation;
 | 
			
		||||
 | 
			
		||||
$validate = new Validation\Validate();
 | 
			
		||||
$valid = true;
 | 
			
		||||
$p = $post->all();
 | 
			
		||||
 | 
			
		||||
// Check secure key
 | 
			
		||||
if (empty($p['secure_key']) || $p['secure_key'] !== $config['secure_key']) {
 | 
			
		||||
    $valid = false;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Username
 | 
			
		||||
$name = $validate->clean($p['username']);
 | 
			
		||||
if (!$validate->minLength($name, 1)) {
 | 
			
		||||
    $valid = false;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Email
 | 
			
		||||
if (empty($p['email'])) {
 | 
			
		||||
    $valid = false;
 | 
			
		||||
} elseif (!$validate->isEmail($p['email'])) {
 | 
			
		||||
    $valid = false;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Password
 | 
			
		||||
if (empty($p['password'])) {
 | 
			
		||||
    $valid = false;
 | 
			
		||||
} else {
 | 
			
		||||
    // Use pepper + Argon2id
 | 
			
		||||
    $peppered = hash_hmac('sha3-512', $p['password'], $config['secure_key']);
 | 
			
		||||
    $hashed_password = password_hash($peppered, PASSWORD_ARGON2ID);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
if ($valid) {
 | 
			
		||||
    // Insert user
 | 
			
		||||
    $query = <<<EOSQL
 | 
			
		||||
    INSERT INTO `users`
 | 
			
		||||
        (`username`, `password`, `email`, `validate`, `confirmationToken`, `reset`, `created`, `updated`, `confirmed`, `blocked`) 
 | 
			
		||||
    VALUES 
 | 
			
		||||
        (?, ?, ?, NULL, NULL, NULL, NOW(), NOW(), 1, 0);
 | 
			
		||||
EOSQL;
 | 
			
		||||
 | 
			
		||||
    $params = [$name, $hashed_password, $p['email']];
 | 
			
		||||
    $db->query($query, $params);
 | 
			
		||||
    $userid = $db->lastid();
 | 
			
		||||
 | 
			
		||||
    // Assign admin group
 | 
			
		||||
    $groupInsertQuery = <<<EOSQL
 | 
			
		||||
    INSERT INTO `user_groups` (`user_id`, `groupName`) VALUES (?, ?);
 | 
			
		||||
EOSQL;
 | 
			
		||||
 | 
			
		||||
    $db->query($groupInsertQuery, [$userid, 'admin']);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Always redirect at end
 | 
			
		||||
$redirect->url('/novaconium');
 | 
			
		||||
@ -1,13 +0,0 @@
 | 
			
		||||
<?php
 | 
			
		||||
$data = array_merge($data, [
 | 
			
		||||
    'title' => 'Novaconium Dashboard Page',
 | 
			
		||||
    'pageclass' => 'novaconium'
 | 
			
		||||
]);
 | 
			
		||||
 | 
			
		||||
if ( empty($session->get('username'))) {
 | 
			
		||||
    $redirect->url('/novaconium/login');
 | 
			
		||||
    $messages->error('You are not loggedin');
 | 
			
		||||
    makeitso();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
view('@novacore/dashboard', $data);
 | 
			
		||||
@ -1,73 +0,0 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
$data = array_merge($data, [
 | 
			
		||||
    'title' => 'Novaconium Edit Page',
 | 
			
		||||
    'pageclass' => 'novaconium'
 | 
			
		||||
]);
 | 
			
		||||
 | 
			
		||||
// Check if logged in
 | 
			
		||||
if (empty($session->get('username'))) {
 | 
			
		||||
    $messages->error('You are not logged in');
 | 
			
		||||
    $redirect->url('/novaconium/login');
 | 
			
		||||
    makeitso();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Get page ID from router parameters
 | 
			
		||||
$pageid = $router->parameters['id'] ?? null;
 | 
			
		||||
 | 
			
		||||
if (!empty($pageid)) {
 | 
			
		||||
    // Existing page: fetch from database
 | 
			
		||||
    $query = <<<EOSQL
 | 
			
		||||
        SELECT 
 | 
			
		||||
            id,
 | 
			
		||||
            title,
 | 
			
		||||
            heading,
 | 
			
		||||
            description,
 | 
			
		||||
            keywords,
 | 
			
		||||
            author,
 | 
			
		||||
            slug,
 | 
			
		||||
            path,
 | 
			
		||||
            intro,
 | 
			
		||||
            body,
 | 
			
		||||
            notes,
 | 
			
		||||
            draft,
 | 
			
		||||
            changefreq,
 | 
			
		||||
            priority,
 | 
			
		||||
            created,
 | 
			
		||||
            updated
 | 
			
		||||
        FROM pages 
 | 
			
		||||
        WHERE id = ?
 | 
			
		||||
EOSQL;
 | 
			
		||||
 | 
			
		||||
    $data['rows'] = $db->getRow($query, [$pageid]);
 | 
			
		||||
 | 
			
		||||
    // If no row is found, treat as new page
 | 
			
		||||
    if (!$data['rows']) {
 | 
			
		||||
        $pageid = null;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
if (empty($pageid)) {
 | 
			
		||||
    // New page: set default values for all fields
 | 
			
		||||
    $data['rows'] = [
 | 
			
		||||
        'id'          => '',
 | 
			
		||||
        'title'       => '',
 | 
			
		||||
        'heading'     => '',
 | 
			
		||||
        'description' => '',
 | 
			
		||||
        'keywords'    => '',
 | 
			
		||||
        'author'      => $session->get('username') ?? '',
 | 
			
		||||
        'slug'        => '',
 | 
			
		||||
        'path'        => '',
 | 
			
		||||
        'intro'       => '',
 | 
			
		||||
        'body'        => '',
 | 
			
		||||
        'notes'       => '',
 | 
			
		||||
        'draft'       => 0,
 | 
			
		||||
        'changefreq'  => 'monthly',
 | 
			
		||||
        'priority'    => 0.0,
 | 
			
		||||
        'created'     => date('Y-m-d H:i:s'),
 | 
			
		||||
        'updated'     => date('Y-m-d H:i:s')
 | 
			
		||||
    ];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Render the edit page view
 | 
			
		||||
view('@novacore/editpage', $data);
 | 
			
		||||
@ -1,154 +0,0 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
$data = [
 | 
			
		||||
    'secure_key' => false,
 | 
			
		||||
    'gen_key' => NULL,
 | 
			
		||||
    'users_created' => false,
 | 
			
		||||
    'empty_users' => false,
 | 
			
		||||
    'show_login' => false,
 | 
			
		||||
    'token' => $session->get('token'),
 | 
			
		||||
    'title' => 'Novaconium Admin'
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
// Check if SECURE KEY is Set in 
 | 
			
		||||
if ($config['secure_key'] !== null && strlen($config['secure_key']) === 64) {
 | 
			
		||||
    $data['secure_key'] = true;
 | 
			
		||||
} else {
 | 
			
		||||
    $data['gen_key'] = substr(bin2hex(random_bytes(32)), 0, 64);
 | 
			
		||||
    $log->warn('secure_key not detected');
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Check if user table exists
 | 
			
		||||
$query = <<<EOSQL
 | 
			
		||||
SELECT TABLE_NAME
 | 
			
		||||
FROM information_schema.tables
 | 
			
		||||
WHERE table_schema = DATABASE()
 | 
			
		||||
  AND TABLE_NAME = 'users';
 | 
			
		||||
EOSQL;
 | 
			
		||||
$result = $db->query($query);
 | 
			
		||||
 | 
			
		||||
if ($result->num_rows === 0) {
 | 
			
		||||
    $query = <<<EOSQL
 | 
			
		||||
    CREATE TABLE `users` (
 | 
			
		||||
        `id` int(11) NOT NULL AUTO_INCREMENT,
 | 
			
		||||
        `username` varchar(30) NOT NULL,
 | 
			
		||||
        `password` varchar(255) NOT NULL,
 | 
			
		||||
        `email` varchar(255) NOT NULL,
 | 
			
		||||
        `validate` varchar(32) DEFAULT NULL,
 | 
			
		||||
        `confirmationToken` varchar(255) DEFAULT NULL,
 | 
			
		||||
        `reset` varchar(32) DEFAULT NULL,
 | 
			
		||||
        `created` datetime NOT NULL,
 | 
			
		||||
        `updated` datetime DEFAULT NULL,
 | 
			
		||||
        `confirmed` tinyint(1) NOT NULL DEFAULT 0,
 | 
			
		||||
        `blocked` tinyint(1) NOT NULL DEFAULT 0,
 | 
			
		||||
        PRIMARY KEY (`id`)
 | 
			
		||||
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci;
 | 
			
		||||
EOSQL;
 | 
			
		||||
 | 
			
		||||
    $db->query($query);
 | 
			
		||||
    $data['users_created'] = true;
 | 
			
		||||
    $log->info('Users Table Created');
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Check Usergroup
 | 
			
		||||
$query = <<<EOSQL
 | 
			
		||||
SELECT TABLE_NAME
 | 
			
		||||
FROM information_schema.tables
 | 
			
		||||
WHERE table_schema = DATABASE()
 | 
			
		||||
  AND TABLE_NAME = 'user_groups';
 | 
			
		||||
EOSQL;
 | 
			
		||||
$result = $db->query($query);
 | 
			
		||||
 | 
			
		||||
if ($result->num_rows === 0) {
 | 
			
		||||
    $query = <<<EOSQL
 | 
			
		||||
        CREATE TABLE `user_groups` (
 | 
			
		||||
            `id` INT(11) NOT NULL AUTO_INCREMENT,
 | 
			
		||||
            `user_id` INT(11) UNSIGNED NOT NULL,
 | 
			
		||||
            `groupName` VARCHAR(40) NOT NULL,
 | 
			
		||||
            PRIMARY KEY (`id`)
 | 
			
		||||
        ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
 | 
			
		||||
EOSQL;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    $db->query($query);
 | 
			
		||||
    $log->info('User_groups Table Created');
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Check Pages Table
 | 
			
		||||
$query = <<<EOSQL
 | 
			
		||||
SELECT TABLE_NAME
 | 
			
		||||
FROM information_schema.tables
 | 
			
		||||
WHERE table_schema = DATABASE()
 | 
			
		||||
  AND TABLE_NAME = 'pages';
 | 
			
		||||
EOSQL;
 | 
			
		||||
$result = $db->query($query);
 | 
			
		||||
 | 
			
		||||
if ($result->num_rows === 0) {
 | 
			
		||||
    $query = <<<EOSQL
 | 
			
		||||
    CREATE TABLE `pages` (
 | 
			
		||||
        `id` int(11) NOT NULL AUTO_INCREMENT,
 | 
			
		||||
        `title` varchar(255) NOT NULL,
 | 
			
		||||
        `heading` varchar(255) NOT NULL,
 | 
			
		||||
        `description` varchar(255) NOT NULL,
 | 
			
		||||
        `keywords` varchar(255) NOT NULL,
 | 
			
		||||
        `author` varchar(255) NOT NULL,
 | 
			
		||||
        `slug` varchar(255) NOT NULL,
 | 
			
		||||
        `path` varchar(255) DEFAULT NULL,
 | 
			
		||||
        `intro` text DEFAULT NULL,
 | 
			
		||||
        `body` text DEFAULT NULL,
 | 
			
		||||
        `notes` text DEFAULT NULL,
 | 
			
		||||
        `created` datetime NOT NULL,
 | 
			
		||||
        `updated` datetime DEFAULT NULL,
 | 
			
		||||
        `draft` tinyint(1) NOT NULL DEFAULT 1,
 | 
			
		||||
        `changefreq` varchar(7) NOT NULL DEFAULT 'monthly',
 | 
			
		||||
        `priority` float(4,1) NOT NULL DEFAULT 0.0,
 | 
			
		||||
        PRIMARY KEY (`id`)
 | 
			
		||||
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci;
 | 
			
		||||
EOSQL;
 | 
			
		||||
 | 
			
		||||
    $db->query($query);
 | 
			
		||||
    $log->info('Pages Table Created');
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Check ContactForm Table
 | 
			
		||||
$query = <<<EOSQL
 | 
			
		||||
SELECT TABLE_NAME
 | 
			
		||||
FROM information_schema.tables
 | 
			
		||||
WHERE table_schema = DATABASE()
 | 
			
		||||
  AND TABLE_NAME = 'contactForm';
 | 
			
		||||
EOSQL;
 | 
			
		||||
$result = $db->query($query);
 | 
			
		||||
 | 
			
		||||
if ($result->num_rows === 0) {
 | 
			
		||||
    $query = <<<EOSQL
 | 
			
		||||
    CREATE TABLE `contactForm` (
 | 
			
		||||
        `id` int(11) NOT NULL AUTO_INCREMENT,
 | 
			
		||||
        `name` varchar(255) NOT NULL,
 | 
			
		||||
        `email` varchar(255) NOT NULL,
 | 
			
		||||
        `message` text DEFAULT NULL,
 | 
			
		||||
        `created` datetime NOT NULL DEFAULT current_timestamp(),
 | 
			
		||||
        `unread` tinyint(1) NOT NULL DEFAULT 1 COMMENT 'Unread is true by default',
 | 
			
		||||
        PRIMARY KEY (`id`)
 | 
			
		||||
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci;
 | 
			
		||||
EOSQL;
 | 
			
		||||
 | 
			
		||||
    $db->query($query);
 | 
			
		||||
    $log->info('ContactForm Table Created');
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Check if a user exists
 | 
			
		||||
$result = $db->query("SELECT COUNT(*) as total FROM users");
 | 
			
		||||
$row = $result->fetch_assoc();
 | 
			
		||||
 | 
			
		||||
if ($row['total'] < 1) {
 | 
			
		||||
  $data['empty_users'] = true;
 | 
			
		||||
} else {
 | 
			
		||||
    $log->info('Init Run complete, all sql tables exist with a user.');
 | 
			
		||||
    // Everything is working, send them to login page
 | 
			
		||||
    $redirect->url('/novaconium/login');
 | 
			
		||||
    makeitso();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
view('@novacore/init', $data);
 | 
			
		||||
@ -1,9 +0,0 @@
 | 
			
		||||
<?php
 | 
			
		||||
$data['title'] = 'Novaconium Login Page';
 | 
			
		||||
 | 
			
		||||
// Don't come here if logged in
 | 
			
		||||
if ($session->get('username')) {
 | 
			
		||||
    $redirect->url('/novaconium/dashboard');
 | 
			
		||||
    makeitso();
 | 
			
		||||
}
 | 
			
		||||
view('@novacore/login');
 | 
			
		||||
@ -1,5 +0,0 @@
 | 
			
		||||
<?php
 | 
			
		||||
$session->kill();
 | 
			
		||||
$log->info("Logout - Logout Success - " . $_SERVER['REMOTE_ADDR']);
 | 
			
		||||
$redirect->url('/');
 | 
			
		||||
makeitso();
 | 
			
		||||
@ -1,15 +0,0 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
if ( empty($session->get('username'))) {
 | 
			
		||||
    $redirect->url('/novaconium/login');
 | 
			
		||||
    $messages->error('You are not loggedin');
 | 
			
		||||
    makeitso();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
$messageid = $router->parameters['id'];
 | 
			
		||||
$query="DELETE FROM contactForm WHERE `contactForm`.`id` = ?";
 | 
			
		||||
$db->query($query, [$messageid]);
 | 
			
		||||
 | 
			
		||||
$redirect->url('/novaconium/messages');
 | 
			
		||||
$messages->notice("Removed Message $messageid");
 | 
			
		||||
makeitso();
 | 
			
		||||
@ -1,19 +0,0 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
$data = array_merge($data, [
 | 
			
		||||
    'title' => 'Novaconium Message Page',
 | 
			
		||||
    'pageclass' => 'novaconium'
 | 
			
		||||
]);
 | 
			
		||||
 | 
			
		||||
if ( empty($session->get('username'))) {
 | 
			
		||||
    $redirect->url('/novaconium/login');
 | 
			
		||||
    $messages->error('You are not loggedin');
 | 
			
		||||
    makeitso();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
$messageid = $router->parameters['id'];
 | 
			
		||||
$query = "SELECT id, name, email, message, created, unread FROM contactForm WHERE id = '$messageid'";
 | 
			
		||||
 | 
			
		||||
$data['themessage'] = $db->getRow($query);
 | 
			
		||||
 | 
			
		||||
view('@novacore/editmessage', $data);
 | 
			
		||||
@ -1,57 +0,0 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
use Nickyeoman\Validation;
 | 
			
		||||
 | 
			
		||||
$v = new Nickyeoman\Validation\Validate();
 | 
			
		||||
 | 
			
		||||
$url_success = '/novaconium/messages';
 | 
			
		||||
$url_error = '/novaconium/messages/edit/' . $post->get('id'); // Redirect back to the message edit form on error
 | 
			
		||||
 | 
			
		||||
// Check if logged in
 | 
			
		||||
if (empty($session->get('username'))) {
 | 
			
		||||
    $messages->error('You are not logged in');
 | 
			
		||||
    $redirect->url('/novaconium/login');
 | 
			
		||||
    makeitso();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Check CSRF token
 | 
			
		||||
if ($session->get('token') != $post->get('token')) {
 | 
			
		||||
    $messages->error('Invalid token');
 | 
			
		||||
    $redirect->url($url_success);
 | 
			
		||||
    makeitso();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Get POST data
 | 
			
		||||
$id = $post->get('id');
 | 
			
		||||
$name = $post->get('name');
 | 
			
		||||
$email = $post->get('email');
 | 
			
		||||
$message = $post->get('message');
 | 
			
		||||
$unread = !empty($post->get('unread')) ? 1 : 0;
 | 
			
		||||
 | 
			
		||||
// Validate required fields
 | 
			
		||||
if (empty($id) || empty($message) || empty($email)) {
 | 
			
		||||
    $messages->error('One of the required fields was empty.');
 | 
			
		||||
    $redirect->url($url_error);
 | 
			
		||||
    makeitso();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
try {
 | 
			
		||||
    // Prepare update query
 | 
			
		||||
    $query = "UPDATE `contactForm` 
 | 
			
		||||
              SET `name` = ?, `email` = ?, `message` = ?, `unread` = ? 
 | 
			
		||||
              WHERE `id` = ?";
 | 
			
		||||
 | 
			
		||||
    $params = [$name, $email, $message, $unread, $id];
 | 
			
		||||
 | 
			
		||||
    $db->query($query, $params);
 | 
			
		||||
 | 
			
		||||
    $messages->notice('Message updated successfully');
 | 
			
		||||
 | 
			
		||||
} catch (Exception $e) {
 | 
			
		||||
    $messages->error('Error updating message: ' . $e->getMessage());
 | 
			
		||||
    $redirect->url($url_error);
 | 
			
		||||
    makeitso();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Redirect to success page
 | 
			
		||||
$redirect->url($url_success);
 | 
			
		||||
@ -1,21 +0,0 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
$data = array_merge($data, [
 | 
			
		||||
    'title' => 'Novaconium Messages',
 | 
			
		||||
    'pageclass' => 'novaconium'
 | 
			
		||||
]);
 | 
			
		||||
 | 
			
		||||
if ( empty($session->get('username'))) {
 | 
			
		||||
    $redirect->url('/novaconium/login');
 | 
			
		||||
    $messages->error('You are not loggedin');
 | 
			
		||||
    makeitso();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Get the pages
 | 
			
		||||
$query = "SELECT id, name, email, LEFT(message, 40) AS message, created, unread FROM contactForm";
 | 
			
		||||
 | 
			
		||||
$matched = $db->getRows($query);  
 | 
			
		||||
 | 
			
		||||
$data['messages'] = $matched;
 | 
			
		||||
 | 
			
		||||
view('@novacore/messages', $data);
 | 
			
		||||
@ -1,20 +0,0 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
$data = array_merge($data, [
 | 
			
		||||
    'title' => 'Novaconium Pages',
 | 
			
		||||
    'pageclass' => 'novaconium'
 | 
			
		||||
]);
 | 
			
		||||
 | 
			
		||||
if ( empty($session->get('username'))) {
 | 
			
		||||
    $redirect->url('/novaconium/login');
 | 
			
		||||
    $messages->error('You are not loggedin');
 | 
			
		||||
    makeitso();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Get the pages
 | 
			
		||||
$query = "SELECT id, title, created, updated, draft FROM pages";
 | 
			
		||||
$matched = $db->getRows($query);  
 | 
			
		||||
 | 
			
		||||
$data['pages'] = $matched;
 | 
			
		||||
 | 
			
		||||
view('@novacore/pages', $data);
 | 
			
		||||
@ -1,83 +0,0 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
use Nickyeoman\Validation;
 | 
			
		||||
$v = new Nickyeoman\Validation\Validate();
 | 
			
		||||
 | 
			
		||||
$url_error = '/novaconium/page/edit/' . $post->get('id'); // fallback for errors
 | 
			
		||||
 | 
			
		||||
// Check login
 | 
			
		||||
if (empty($session->get('username'))) {
 | 
			
		||||
    $messages->error('You are not logged in');
 | 
			
		||||
    $redirect->url('/novaconium/login');
 | 
			
		||||
    makeitso();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Check token
 | 
			
		||||
if ($session->get('token') != $post->get('token')) {
 | 
			
		||||
    $messages->error('Invalid Token');
 | 
			
		||||
    $redirect->url('/novaconium/pages');
 | 
			
		||||
    makeitso();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Gather POST data
 | 
			
		||||
$id          = $post->get('id');
 | 
			
		||||
$title       = $_POST['title'] ?? '';
 | 
			
		||||
$heading     = $_POST['heading'] ?? '';
 | 
			
		||||
$description = $_POST['description'] ?? '';
 | 
			
		||||
$keywords    = $_POST['keywords'] ?? '';
 | 
			
		||||
$author      = $_POST['author'] ?? '';
 | 
			
		||||
$slug        = $_POST['slug'] ?? '';
 | 
			
		||||
$path        = $_POST['path'] ?? null;
 | 
			
		||||
$intro       = $_POST['intro'] ?? '';
 | 
			
		||||
$body        = $_POST['body'] ?? '';
 | 
			
		||||
$notes       = $_POST['notes'] ?? '';
 | 
			
		||||
$draft       = !empty($post->get('draft')) ? 1 : 0;
 | 
			
		||||
$changefreq  = $_POST['changefreq'] ?? 'monthly';
 | 
			
		||||
$priority    = $_POST['priority'] ?? 0.0;
 | 
			
		||||
 | 
			
		||||
// Validate required fields
 | 
			
		||||
if (empty($title) || empty($slug) || empty($body)) {
 | 
			
		||||
    $messages->error('Title, Slug, and Body are required.');
 | 
			
		||||
    $redirect->url($url_error);
 | 
			
		||||
    makeitso();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
try {
 | 
			
		||||
    if (!empty($id)) {
 | 
			
		||||
        // Update existing page
 | 
			
		||||
        $query = "UPDATE `pages` SET
 | 
			
		||||
                    `title` = ?, `heading` = ?, `description` = ?, `keywords` = ?, `author` = ?,
 | 
			
		||||
                    `slug` = ?, `path` = ?, `intro` = ?, `body` = ?, `notes` = ?,
 | 
			
		||||
                    `draft` = ?, `changefreq` = ?, `priority` = ?, `updated` = NOW()
 | 
			
		||||
                  WHERE `id` = ?";
 | 
			
		||||
        $params = [
 | 
			
		||||
            $title, $heading, $description, $keywords, $author,
 | 
			
		||||
            $slug, $path, $intro, $body, $notes,
 | 
			
		||||
            $draft, $changefreq, $priority, $id
 | 
			
		||||
        ];
 | 
			
		||||
        $db->query($query, $params);
 | 
			
		||||
        $messages->notice('Page Updated');
 | 
			
		||||
    } else {
 | 
			
		||||
        // Create new page
 | 
			
		||||
        $query = "INSERT INTO `pages`
 | 
			
		||||
                    (`title`, `heading`, `description`, `keywords`, `author`,
 | 
			
		||||
                     `slug`, `path`, `intro`, `body`, `notes`,
 | 
			
		||||
                     `draft`, `changefreq`, `priority`, `created`)
 | 
			
		||||
                  VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, NOW())";
 | 
			
		||||
        $params = [
 | 
			
		||||
            $title, $heading, $description, $keywords, $author,
 | 
			
		||||
            $slug, $path, $intro, $body, $notes,
 | 
			
		||||
            $draft, $changefreq, $priority
 | 
			
		||||
        ];
 | 
			
		||||
        $db->query($query, $params);
 | 
			
		||||
        $id = $db->lastid; // Get new page ID
 | 
			
		||||
        $messages->notice('Page Created');
 | 
			
		||||
    }
 | 
			
		||||
} catch (Exception $e) {
 | 
			
		||||
    $messages->error($e->getMessage());
 | 
			
		||||
    $redirect->url($url_error);
 | 
			
		||||
    makeitso();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Redirect to edit page
 | 
			
		||||
$redirect->url('/novaconium/page/edit/' . $id);
 | 
			
		||||
@ -1,18 +0,0 @@
 | 
			
		||||
# PHP Composer Cheatsheet
 | 
			
		||||
 | 
			
		||||
Install novaconium with composer: ```composer require 4lt/novaconium```
 | 
			
		||||
 | 
			
		||||
Install novaconium with composer in docker: ```docker run --rm --interactive --tty --volume $PWD:/app composer:latest require 4lt/novaconium```
 | 
			
		||||
 | 
			
		||||
Update novaconium with composer in docker: ```docker run --rm --interactive --tty --volume $PWD:/app composer:latest update```
 | 
			
		||||
 | 
			
		||||
## Install Composer natively on Debian
 | 
			
		||||
 | 
			
		||||
Assuming you have nala installed:
 | 
			
		||||
 | 
			
		||||
```bash
 | 
			
		||||
sudo nala install curl php-cli php-mbstring git unzip
 | 
			
		||||
curl -sS https://getcomposer.org/installer -o composer-setup.php
 | 
			
		||||
sudo php composer-setup.php --install-dir=/usr/local/bin --filename=composer
 | 
			
		||||
rm composer-setup.php
 | 
			
		||||
```
 | 
			
		||||
							
								
								
									
										19
									
								
								docs/Logs.md
									
									
									
									
									
								
							
							
						
						
									
										19
									
								
								docs/Logs.md
									
									
									
									
									
								
							@ -1,19 +0,0 @@
 | 
			
		||||
# Logging
 | 
			
		||||
 | 
			
		||||
You can use the logging class to output to a file.
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
use ```$log->info(The Message');```1
 | 
			
		||||
 | 
			
		||||
Logging levels are:
 | 
			
		||||
```
 | 
			
		||||
'DEBUG' => 0,
 | 
			
		||||
        'INFO' => 1,
 | 
			
		||||
        'WARNING' => 2,
 | 
			
		||||
        'ERROR' => 3,
 | 
			
		||||
    ];
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
It's recommended that production is set to ERROR. 
 | 
			
		||||
You set the log level in /App/config.php under 'loglevel' => 'ERROR'
 | 
			
		||||
If you are using CORXN a health check is run every 30 seconds which would fill the log file with info.
 | 
			
		||||
@ -1,3 +0,0 @@
 | 
			
		||||
# Messages
 | 
			
		||||
 | 
			
		||||
Messages is $messages.
 | 
			
		||||
@ -1,5 +0,0 @@
 | 
			
		||||
# Post
 | 
			
		||||
 | 
			
		||||
There is a post class.
 | 
			
		||||
It cleans the post.
 | 
			
		||||
You can access it with $post.
 | 
			
		||||
@ -1,6 +0,0 @@
 | 
			
		||||
# Redirect
 | 
			
		||||
 | 
			
		||||
How to use redirect class.
 | 
			
		||||
 | 
			
		||||
$redirect->url;
 | 
			
		||||
it's called on every page, if you set it more than once the last one is used.
 | 
			
		||||
@ -1,5 +0,0 @@
 | 
			
		||||
# Sessions
 | 
			
		||||
 | 
			
		||||
There is a sessions handler built into Novaconium.
 | 
			
		||||
 | 
			
		||||
$session
 | 
			
		||||
@ -1,7 +0,0 @@
 | 
			
		||||
# Style Sheets
 | 
			
		||||
 | 
			
		||||
The idea is to use sass to generate only what you need for style sheets.
 | 
			
		||||
 | 
			
		||||
```bash
 | 
			
		||||
sudo docker run --rm -v $(pwd):/usr/src/app sass-container sass sass/project.sass public/css/main.css
 | 
			
		||||
```
 | 
			
		||||
@ -1,21 +0,0 @@
 | 
			
		||||
# Twig 
 | 
			
		||||
 | 
			
		||||
## Overrides
 | 
			
		||||
 | 
			
		||||
You can override twig templates by creating the same file in the templates directory.
 | 
			
		||||
 | 
			
		||||
## Calling View
 | 
			
		||||
 | 
			
		||||
There is a $data that the system uses to store arrays for twig you can save to this array:
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
$data['newinfo'] = 'stuff';
 | 
			
		||||
view('templatename');
 | 
			
		||||
```
 | 
			
		||||
and that will automotically go to twig.
 | 
			
		||||
or you can create a new array and pass it in:
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
$anotherArr['newinfo'] = 'stuff';
 | 
			
		||||
view('templatename',$anotherArr);
 | 
			
		||||
```
 | 
			
		||||
@ -1,9 +0,0 @@
 | 
			
		||||
# Docker Cheatsheet (for Novaconium)
 | 
			
		||||
 | 
			
		||||
## Sample Docker Compose File
 | 
			
		||||
 | 
			
		||||
See the skeleton directory for an example docker setup.
 | 
			
		||||
 | 
			
		||||
## Start Docker
 | 
			
		||||
 | 
			
		||||
```docker compose up -d```
 | 
			
		||||
							
								
								
									
										1
									
								
								examples/App/controllers/404.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								examples/App/controllers/404.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1 @@
 | 
			
		||||
<h1>This is 404</h1>
 | 
			
		||||
							
								
								
									
										2
									
								
								examples/App/controllers/index.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								examples/App/controllers/index.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,2 @@
 | 
			
		||||
<?php
 | 
			
		||||
echo $twig->render('index.html.twig');
 | 
			
		||||
@ -1,4 +1,6 @@
 | 
			
		||||
RewriteEngine On
 | 
			
		||||
 | 
			
		||||
RewriteCond %{REQUEST_FILENAME} !-f
 | 
			
		||||
RewriteCond %{REQUEST_FILENAME} !-d
 | 
			
		||||
 | 
			
		||||
RewriteRule ^(.*)$ index.php?_uri=$1 [QSA,L]
 | 
			
		||||
							
								
								
									
										6
									
								
								examples/App/public/index.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								examples/App/public/index.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,6 @@
 | 
			
		||||
<?php
 | 
			
		||||
error_reporting(E_ALL);
 | 
			
		||||
ini_set('display_errors', 1);
 | 
			
		||||
define('BASEPATH', dirname(__DIR__, 1));
 | 
			
		||||
require_once(BASEPATH . '/vendor/4lt/novaconium/src/bootstrap.php');
 | 
			
		||||
?>
 | 
			
		||||
							
								
								
									
										9
									
								
								examples/App/routes.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								examples/App/routes.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,9 @@
 | 
			
		||||
<?php
 | 
			
		||||
$routes = [
 | 
			
		||||
    '/about' => [ 
 | 
			
		||||
        'file' => 'about'
 | 
			
		||||
    ],
 | 
			
		||||
    '/' => [ 
 | 
			
		||||
        'file' => 'index'
 | 
			
		||||
    ]
 | 
			
		||||
];
 | 
			
		||||
							
								
								
									
										7
									
								
								examples/App/views/index.html.twig
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								examples/App/views/index.html.twig
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,7 @@
 | 
			
		||||
{% extends '@nytwig/master.html.twig' %}
 | 
			
		||||
 | 
			
		||||
{% block content %}
 | 
			
		||||
<h1>This is twig</h1>
 | 
			
		||||
<p>Content Here</p>
 | 
			
		||||
{% endblock %}
 | 
			
		||||
 | 
			
		||||
@ -1,9 +0,0 @@
 | 
			
		||||
<?php
 | 
			
		||||
class Auth
 | 
			
		||||
{
 | 
			
		||||
 | 
			
		||||
    public function __construct()
 | 
			
		||||
    {
 | 
			
		||||
        
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -1,2 +0,0 @@
 | 
			
		||||
MYSQL_ROOT_PASSWORD=random
 | 
			
		||||
MYSQL_PASSWORD=random
 | 
			
		||||
							
								
								
									
										3
									
								
								skeleton/.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										3
									
								
								skeleton/.gitignore
									
									
									
									
										vendored
									
									
								
							@ -1,3 +0,0 @@
 | 
			
		||||
data/
 | 
			
		||||
novaconium/vendor/
 | 
			
		||||
novaconium/logs/
 | 
			
		||||
@ -1,55 +0,0 @@
 | 
			
		||||
# Sample Docker Compose
 | 
			
		||||
services:
 | 
			
		||||
  corxn:
 | 
			
		||||
    image: 4lights/corxn:6.0.0
 | 
			
		||||
    ports:
 | 
			
		||||
      - "8000:80"
 | 
			
		||||
    volumes:
 | 
			
		||||
      - ./novaconium:/data
 | 
			
		||||
      - ./data/logs:/var/log/apache2  # Optional Logs
 | 
			
		||||
    restart: unless-stopped
 | 
			
		||||
    networks:
 | 
			
		||||
      - internal
 | 
			
		||||
      - proxy
 | 
			
		||||
 | 
			
		||||
  redis:
 | 
			
		||||
    image: redis:latest
 | 
			
		||||
    networks:
 | 
			
		||||
      - internal
 | 
			
		||||
    restart: unless-stopped
 | 
			
		||||
 | 
			
		||||
  mariadb:
 | 
			
		||||
    image: mariadb:latest
 | 
			
		||||
    environment:
 | 
			
		||||
      MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
 | 
			
		||||
      MYSQL_DATABASE: novadb
 | 
			
		||||
      MYSQL_USER: novaconium
 | 
			
		||||
      MYSQL_PASSWORD: ${MYSQL_PASSWORD}
 | 
			
		||||
    volumes:
 | 
			
		||||
      - ./data/db:/var/lib/mysql
 | 
			
		||||
    networks:
 | 
			
		||||
      - internal
 | 
			
		||||
    restart: unless-stopped
 | 
			
		||||
 | 
			
		||||
  phpmyadmin:
 | 
			
		||||
    image: phpmyadmin/phpmyadmin:latest
 | 
			
		||||
    restart: unless-stopped
 | 
			
		||||
    ports:
 | 
			
		||||
      - "8001:80"
 | 
			
		||||
    networks:
 | 
			
		||||
      - internal
 | 
			
		||||
    environment:
 | 
			
		||||
      - PMA_ARBITRARY=-1
 | 
			
		||||
      - PMA_HOST=mariadb
 | 
			
		||||
      - PMA_USER=root
 | 
			
		||||
      - PMA_PASSWORD=${MYSQL_ROOT_PASSWORD}
 | 
			
		||||
      - UPLOAD_LIMIT=200M
 | 
			
		||||
    volumes:
 | 
			
		||||
      - "/etc/timezone:/etc/timezone:ro"
 | 
			
		||||
      - "/etc/localtime:/etc/localtime:ro"
 | 
			
		||||
 | 
			
		||||
networks:
 | 
			
		||||
  proxy:
 | 
			
		||||
    external: true
 | 
			
		||||
  internal:
 | 
			
		||||
    driver: bridge
 | 
			
		||||
@ -1,14 +0,0 @@
 | 
			
		||||
<?php
 | 
			
		||||
$config = [
 | 
			
		||||
    'database' => [ 
 | 
			
		||||
        'host' => 'mariadb',
 | 
			
		||||
        'name' => 'novadb',
 | 
			
		||||
        'user' => 'novaconium',
 | 
			
		||||
        'pass' => '',
 | 
			
		||||
        'port' => 3306
 | 
			
		||||
    ],
 | 
			
		||||
    'base_url' => 'http://localhost:8000',
 | 
			
		||||
    'secure_key' => '', //64 alphanumeric characters
 | 
			
		||||
    'logfile' => '/logs/novaconium.log',
 | 
			
		||||
    'loglevel' => 'ERROR' // 'DEBUG', 'INFO', 'WARNING', 'ERROR', 'NONE'
 | 
			
		||||
];
 | 
			
		||||
@ -1,15 +0,0 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
// Define our status code and message
 | 
			
		||||
$status_code = 404;
 | 
			
		||||
$status_message = 'The requested resource could not be found.';
 | 
			
		||||
 | 
			
		||||
// Set the HTTP response code and message
 | 
			
		||||
http_response_code($status_code);
 | 
			
		||||
header("Content-Type: text/html");
 | 
			
		||||
?>
 | 
			
		||||
 | 
			
		||||
<h1>Error 404 Resource Not found</h1>
 | 
			
		||||
<p><?php echo $status_message; ?></p>
 | 
			
		||||
<p style="font-size:10px; margin-top:60px">Novaconium Default 404 page.</p>
 | 
			
		||||
 | 
			
		||||
@ -1,2 +0,0 @@
 | 
			
		||||
<?php
 | 
			
		||||
view('index');
 | 
			
		||||
@ -1,6 +0,0 @@
 | 
			
		||||
<?php
 | 
			
		||||
$routes = [
 | 
			
		||||
    '/' => [ 
 | 
			
		||||
        'get' => 'index'
 | 
			
		||||
    ]
 | 
			
		||||
];
 | 
			
		||||
@ -1 +0,0 @@
 | 
			
		||||
{# Overrides go here #}
 | 
			
		||||
@ -1,21 +0,0 @@
 | 
			
		||||
{% extends '@novaconium/master.html.twig' %}
 | 
			
		||||
 | 
			
		||||
{% block content %}
 | 
			
		||||
    <img src="https://git.4lt.ca/4lt/novaconium/media/branch/master/_assets/novaconium-logo.png" aalt="Novaconium framework logo" />
 | 
			
		||||
    <h2>Minimalist PHP framework</h2>
 | 
			
		||||
    <p>
 | 
			
		||||
        Edit <code>App/routes.php</code> and <code>App/controllers/index.php</code><br>
 | 
			
		||||
        to customize this page.
 | 
			
		||||
    </p>
 | 
			
		||||
    <p>Sign in to the <a href="/novaconium">Administration</a></p>
 | 
			
		||||
 | 
			
		||||
    <h2>Documentation</h2>
 | 
			
		||||
    <ul>
 | 
			
		||||
        <li><a href="https://git.4lt.ca/4lt/novaconium/src/branch/master/docs/StyleSheets-sass.md">Style Sheets</a></li>
 | 
			
		||||
        <li><a href="https://git.4lt.ca/4lt/novaconium/src/branch/master/docs/Twig-Views.md">Twig overrides</a></li>
 | 
			
		||||
    </ul>
 | 
			
		||||
 | 
			
		||||
    <h2>Repository</h2>
 | 
			
		||||
    <p class="small">Visit Source Control Repository for <a href="https://git.4lt.ca/4lt/novaconium">Novaconium</a></p>
 | 
			
		||||
 | 
			
		||||
{% endblock %}
 | 
			
		||||
@ -1,114 +0,0 @@
 | 
			
		||||
body {
 | 
			
		||||
    background-color: #1b1f23;
 | 
			
		||||
    width: 100%;
 | 
			
		||||
    font-family: 'Fira Code', 'Source Code Pro', monospace;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#page .container {
 | 
			
		||||
    display: flex;
 | 
			
		||||
    padding: 0;
 | 
			
		||||
    justify-content: center;
 | 
			
		||||
    align-items: flex-start;
 | 
			
		||||
    margin-top: 20px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
article {
 | 
			
		||||
    width: 900px;
 | 
			
		||||
    flex-shrink: 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#leftnav {
 | 
			
		||||
    width: 320px;
 | 
			
		||||
    flex-shrink: 0;
 | 
			
		||||
    margin: 0;
 | 
			
		||||
    margin-right: 50px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
article, #leftnav {
 | 
			
		||||
    border: 1px solid #3b444c;
 | 
			
		||||
    background-color: #14171a;
 | 
			
		||||
    color: #fff;
 | 
			
		||||
    padding: 20px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ul#leftnav {
 | 
			
		||||
    list-style: none;
 | 
			
		||||
    padding: 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#leftnav li {
 | 
			
		||||
    border-bottom: 1px solid #3b444c;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#leftnav a {
 | 
			
		||||
    display: block;
 | 
			
		||||
    padding: 10px 15px;
 | 
			
		||||
    text-decoration: none;
 | 
			
		||||
    color: #fff;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#leftnav a:hover {
 | 
			
		||||
    background: #333;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#leftnav li:last-child {
 | 
			
		||||
    border-bottom: none;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
code {
 | 
			
		||||
    font-family: 'Fira Code', monospace;
 | 
			
		||||
    font-size: 13px;
 | 
			
		||||
    background-color: #0d1117;
 | 
			
		||||
    color: #c9d1d9;
 | 
			
		||||
    padding: 0.2em 0.4em;
 | 
			
		||||
    border-radius: 6px;
 | 
			
		||||
    border: 1px solid #30363d;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.small {
 | 
			
		||||
    font-size: 10px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
h2 {
 | 
			
		||||
    margin-top: 40px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
div.error, div#debug {
 | 
			
		||||
    border: 1px solid red;
 | 
			
		||||
    padding: 30px;
 | 
			
		||||
    background-color: pink;
 | 
			
		||||
    color: darkred;
 | 
			
		||||
    margin: 0 auto;
 | 
			
		||||
    width: 900px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
div.notice {
 | 
			
		||||
    border: 1px solid rgb(31, 119, 13);
 | 
			
		||||
    padding: 30px;
 | 
			
		||||
    background-color: rgb(169, 218, 163);
 | 
			
		||||
    color: rgb(20, 56, 13);
 | 
			
		||||
    margin: 0 auto;
 | 
			
		||||
    width: 900px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
div#debug {
 | 
			
		||||
    margin-top: 100px;
 | 
			
		||||
    margin-bottom: 100px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.pages-table {
 | 
			
		||||
    width: 100%;
 | 
			
		||||
    border-collapse: collapse;
 | 
			
		||||
    border: 1px solid #333;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.pages-table th,
 | 
			
		||||
.pages-table td {
 | 
			
		||||
    padding: 10px;
 | 
			
		||||
    border: 1px solid #ddd;
 | 
			
		||||
    text-align: left;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.pages-table th {
 | 
			
		||||
    font-weight: bold;
 | 
			
		||||
}
 | 
			
		||||
@ -1,6 +0,0 @@
 | 
			
		||||
<?php
 | 
			
		||||
// error_reporting(E_ALL);
 | 
			
		||||
// ini_set('display_errors', 1);
 | 
			
		||||
define('BASEPATH', dirname(__DIR__, 1));
 | 
			
		||||
require_once(BASEPATH . '/vendor/4lt/novaconium/src/novaconium.php');
 | 
			
		||||
?>
 | 
			
		||||
							
								
								
									
										126
									
								
								src/Database.php
									
									
									
									
									
								
							
							
						
						
									
										126
									
								
								src/Database.php
									
									
									
									
									
								
							@ -1,126 +0,0 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
class Database {
 | 
			
		||||
 | 
			
		||||
    private $conn;
 | 
			
		||||
    public $lastid;
 | 
			
		||||
 | 
			
		||||
    public function __construct($dbinfo) {
 | 
			
		||||
        $this->conn = new mysqli($dbinfo['host'], $dbinfo['user'], $dbinfo['pass'], $dbinfo['name']);
 | 
			
		||||
        if ($this->conn->connect_error) {
 | 
			
		||||
            die("Connection failed: " . $this->conn->connect_error);
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function query($query, $params = []) {
 | 
			
		||||
        // Clean up pending results to avoid "commands out of sync"
 | 
			
		||||
        while ($this->conn->more_results() && $this->conn->next_result()) {
 | 
			
		||||
            if ($res = $this->conn->use_result()) {
 | 
			
		||||
                $res->free();
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Prepare the SQL statement
 | 
			
		||||
        $stmt = $this->conn->prepare($query);
 | 
			
		||||
        if (!$stmt) {
 | 
			
		||||
            throw new Exception("Query preparation failed: " . $this->conn->error);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Bind parameters if needed
 | 
			
		||||
        if (!empty($params)) {
 | 
			
		||||
            $types = str_repeat('s', count($params)); // Use 's' for all types, or detect types dynamically
 | 
			
		||||
            $stmt->bind_param($types, ...$params);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Execute the statement
 | 
			
		||||
        if (!$stmt->execute()) {
 | 
			
		||||
            $stmt->close();
 | 
			
		||||
            throw new Exception("Query execution failed: " . $stmt->error);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Save last insert id if it's an INSERT query
 | 
			
		||||
        if (preg_match('/^\s*INSERT/i', $query)) {
 | 
			
		||||
            $this->lastid = $this->conn->insert_id;
 | 
			
		||||
        } else {
 | 
			
		||||
            $this->lastid = 0;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Decide what to return
 | 
			
		||||
        // For SELECT/SHOW etc., return result set
 | 
			
		||||
        if (preg_match('/^\s*(SELECT|SHOW|DESCRIBE|EXPLAIN)/i', $query)) {
 | 
			
		||||
            $result = $stmt->get_result();
 | 
			
		||||
            $stmt->close();
 | 
			
		||||
            return $result;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // For INSERT/UPDATE/DELETE, return success status
 | 
			
		||||
        $success = $stmt->affected_rows;
 | 
			
		||||
        $stmt->close();
 | 
			
		||||
        return $success;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function lastid() {
 | 
			
		||||
        return $this->lastid;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function getRow($query, $params = []) {
 | 
			
		||||
        try {
 | 
			
		||||
            // Prepare the SQL statement
 | 
			
		||||
            $stmt = $this->conn->prepare($query);
 | 
			
		||||
            if (!$stmt) {
 | 
			
		||||
                throw new Exception("Query preparation failed: " . $this->conn->error);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // Bind parameters
 | 
			
		||||
            if (!empty($params)) {
 | 
			
		||||
                $types = str_repeat('s', count($params)); // You may improve this with actual type detection
 | 
			
		||||
                $stmt->bind_param($types, ...$params);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // Execute the statement
 | 
			
		||||
            if (!$stmt->execute()) {
 | 
			
		||||
                $stmt->close();
 | 
			
		||||
                throw new Exception("Query execution failed: " . $stmt->error);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // Get result
 | 
			
		||||
            $result = $stmt->get_result();
 | 
			
		||||
            $row = $result->fetch_assoc();
 | 
			
		||||
 | 
			
		||||
            $stmt->close();
 | 
			
		||||
            return $row;
 | 
			
		||||
 | 
			
		||||
        } catch (Exception $e) {
 | 
			
		||||
            echo "An error occurred: " . $e->getMessage();
 | 
			
		||||
            return null;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    public function getRows($query, $params = []) {
 | 
			
		||||
        $stmt = $this->conn->prepare($query);
 | 
			
		||||
        if (!$stmt) {
 | 
			
		||||
            die("Query preparation failed: " . $this->conn->error);
 | 
			
		||||
        }
 | 
			
		||||
    
 | 
			
		||||
        // Bind parameters if provided
 | 
			
		||||
        if (!empty($params)) {
 | 
			
		||||
            $types = str_repeat('s', count($params)); // Assuming all are strings, adjust as needed
 | 
			
		||||
            $stmt->bind_param($types, ...$params);
 | 
			
		||||
        }
 | 
			
		||||
    
 | 
			
		||||
        $stmt->execute();
 | 
			
		||||
        $result = $stmt->get_result(); // Requires MySQL Native Driver (mysqlnd)
 | 
			
		||||
    
 | 
			
		||||
        if ($result) {
 | 
			
		||||
            return $result->fetch_all(MYSQLI_ASSOC);
 | 
			
		||||
        } else {
 | 
			
		||||
            return [];
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function close() {
 | 
			
		||||
        $this->conn->close();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -1,53 +0,0 @@
 | 
			
		||||
<?php
 | 
			
		||||
class Logger {
 | 
			
		||||
    protected string $logFile;
 | 
			
		||||
    protected int $logLevelThreshold;
 | 
			
		||||
 | 
			
		||||
    const LEVELS = [
 | 
			
		||||
        'DEBUG' => 0,
 | 
			
		||||
        'INFO' => 1,
 | 
			
		||||
        'WARNING' => 2,
 | 
			
		||||
        'ERROR' => 3,
 | 
			
		||||
        'NONE' => 999
 | 
			
		||||
    ];
 | 
			
		||||
 | 
			
		||||
    public function __construct(string $logFile, string $minLevel = 'DEBUG') {
 | 
			
		||||
        $this->logFile = $logFile;
 | 
			
		||||
        $minLevel = strtoupper($minLevel);
 | 
			
		||||
        $this->logLevelThreshold = self::LEVELS[$minLevel] ?? 0;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    protected function log(string $level, string $message): void {
 | 
			
		||||
        $level = strtoupper($level);
 | 
			
		||||
        if (!isset(self::LEVELS[$level]) || self::LEVELS[$level] < $this->logLevelThreshold) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $time = date('Y-m-d H:i:s');
 | 
			
		||||
        $ip = $_SERVER['REMOTE_ADDR'];
 | 
			
		||||
        $logEntry = "[$time] [$ip] [$level] $message" . PHP_EOL;
 | 
			
		||||
 | 
			
		||||
        file_put_contents($this->logFile, $logEntry, FILE_APPEND | LOCK_EX);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function debug(string $msg): void {
 | 
			
		||||
        $this->log('DEBUG', $msg);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function info(string $msg): void {
 | 
			
		||||
        $this->log('INFO', $msg);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function warning(string $msg): void {
 | 
			
		||||
        $this->log('WARNING', $msg);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Alias
 | 
			
		||||
    public function warn(string $msg): void {
 | 
			
		||||
        $this->log('WARNING', $msg);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function error(string $msg): void {
 | 
			
		||||
        $this->log('ERROR', $msg);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -1,87 +0,0 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
class MessageHandler {
 | 
			
		||||
    private $messages = [
 | 
			
		||||
        'error' => [],
 | 
			
		||||
        'warning' => [],
 | 
			
		||||
        'notice' => [],
 | 
			
		||||
        'success' => []
 | 
			
		||||
    ];
 | 
			
		||||
 | 
			
		||||
    public function __construct(array $sessionMessages = [])
 | 
			
		||||
    {
 | 
			
		||||
        // Merge existing session messages into the default structure
 | 
			
		||||
        foreach ($this->messages as $type => $_) {
 | 
			
		||||
            if (isset($sessionMessages[$type]) && is_array($sessionMessages[$type])) {
 | 
			
		||||
                $this->messages[$type] = $sessionMessages[$type];
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Add a message of a specific type
 | 
			
		||||
    public function addMessage($type, $message) {
 | 
			
		||||
        if (!isset($this->messages[$type])) {
 | 
			
		||||
            throw new Exception("Invalid message type: $type");
 | 
			
		||||
        }
 | 
			
		||||
        $this->messages[$type][] = $message;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function error($message){
 | 
			
		||||
        $this->addMessage('error', $message);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function notice($message){
 | 
			
		||||
        $this->addMessage('notice', $message);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Get all messages of a specific type
 | 
			
		||||
    public function getMessages($type) {
 | 
			
		||||
        return $this->messages[$type] ?? [];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Get all messages of a specific type
 | 
			
		||||
    public function showMessages($type) {
 | 
			
		||||
        $result = $this->messages[$type] ?? [];
 | 
			
		||||
        $this->messages[$type] = []; // Clear messages after showing
 | 
			
		||||
        return $result;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Get all messages of all types
 | 
			
		||||
    public function getAllMessages() {
 | 
			
		||||
        return $this->messages;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Get the count of messages for a specific type
 | 
			
		||||
    public function count($type) {
 | 
			
		||||
        return isset($this->messages[$type]) ? count($this->messages[$type]) : 0;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Get the total count of all messages
 | 
			
		||||
    public function totalCount() {
 | 
			
		||||
        return array_sum(array_map('count', $this->messages));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Check if there are any messages of a specific type
 | 
			
		||||
    public function hasMessages($type) {
 | 
			
		||||
        return !empty($this->messages[$type]);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Check if there are any messages at all
 | 
			
		||||
    public function hasAnyMessages() {
 | 
			
		||||
        return $this->totalCount() > 0;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Clear messages of a specific type
 | 
			
		||||
    public function clear($type) {
 | 
			
		||||
        if (isset($this->messages[$type])) {
 | 
			
		||||
            $this->messages[$type] = [];
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Clear all messages
 | 
			
		||||
    public function clearAll() {
 | 
			
		||||
        foreach ($this->messages as $type => $list) {
 | 
			
		||||
            $this->messages[$type] = [];
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										25
									
								
								src/Post.php
									
									
									
									
									
								
							
							
						
						
									
										25
									
								
								src/Post.php
									
									
									
									
									
								
							@ -1,25 +0,0 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
class Post {
 | 
			
		||||
    private $data = [];
 | 
			
		||||
 | 
			
		||||
    public function __construct($post) {
 | 
			
		||||
        $this->sanitize($post);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private function sanitize($post) {
 | 
			
		||||
        foreach ($post as $key => $value) {
 | 
			
		||||
            $this->data[$key] = is_array($value) 
 | 
			
		||||
                ? filter_var_array($value, FILTER_SANITIZE_FULL_SPECIAL_CHARS) 
 | 
			
		||||
                : filter_var($value, FILTER_SANITIZE_FULL_SPECIAL_CHARS);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function get($key, $default = null) {
 | 
			
		||||
        return $this->data[$key] ?? $default;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function all() {
 | 
			
		||||
        return $this->data;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -1,40 +0,0 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Use 
 | 
			
		||||
 * $redirect->url('/login');
 | 
			
		||||
 * to trigger a redirect
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Redirect {
 | 
			
		||||
    private ?string $url = null;
 | 
			
		||||
    private int $statusCode = 303;
 | 
			
		||||
 | 
			
		||||
    public function url(string $relativeUrl, int $statusCode = 303): void {
 | 
			
		||||
        $this->statusCode = $statusCode;
 | 
			
		||||
 | 
			
		||||
        // Detect HTTPS
 | 
			
		||||
        $protocol = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') ? "https" : "http";
 | 
			
		||||
 | 
			
		||||
        // Get Hostname
 | 
			
		||||
        $host = $_SERVER['HTTP_HOST'];
 | 
			
		||||
 | 
			
		||||
        // Get Base Directory
 | 
			
		||||
        $basePath = rtrim(dirname($_SERVER['SCRIPT_NAME']), '/\\');
 | 
			
		||||
 | 
			
		||||
        // Construct Absolute URL
 | 
			
		||||
        $this->url = "$protocol://$host$basePath/" . ltrim($relativeUrl, '/');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function isset(): bool {
 | 
			
		||||
        return !is_null($this->url);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function execute(): void {
 | 
			
		||||
        if ($this->url) {
 | 
			
		||||
            header("Location: " . $this->url, true, $this->statusCode);
 | 
			
		||||
            exit();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										125
									
								
								src/Router.php
									
									
									
									
									
								
							
							
						
						
									
										125
									
								
								src/Router.php
									
									
									
									
									
								
							@ -4,46 +4,22 @@ class Router {
 | 
			
		||||
    public $routes = [];
 | 
			
		||||
    public $query = [];
 | 
			
		||||
    public $path;
 | 
			
		||||
    public $controller;
 | 
			
		||||
    public $controllerPath;
 | 
			
		||||
    public $parameters = [];
 | 
			
		||||
    public $requestType = 'get';
 | 
			
		||||
    public $controllerPath = BASEPATH . '/App/controllers/404.php';
 | 
			
		||||
 | 
			
		||||
    public function __construct() {
 | 
			
		||||
        $this->routes           = $this->loadRoutes();
 | 
			
		||||
        $this->path             = $this->preparePath();
 | 
			
		||||
        $this->query            = $this->prepareQuery();
 | 
			
		||||
        $this->requestType      = $this->getRequestType();
 | 
			
		||||
        $this->controller       = $this->findController();
 | 
			
		||||
        $this->controllerPath   = $this->setRouteFile();
 | 
			
		||||
        $this->loadRoutes();
 | 
			
		||||
        $this->preparePath();
 | 
			
		||||
        $this->prepareQuery();
 | 
			
		||||
        $this->setRouteFile();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private function loadRoutes() {
 | 
			
		||||
        require_once(FRAMEWORKPATH . '/config/routes.php');
 | 
			
		||||
        // Check if Path exists
 | 
			
		||||
        if (file_exists(BASEPATH . '/App/routes.php')) {
 | 
			
		||||
            require_once( BASEPATH . '/App/routes.php');
 | 
			
		||||
        }
 | 
			
		||||
        $routes = array_merge((array)$routes, (array)$framework_routes);
 | 
			
		||||
 | 
			
		||||
        return $routes;
 | 
			
		||||
        require_once( BASEPATH . '/App/routes.php');
 | 
			
		||||
        $this->routes = $routes;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private function preparePath() {
 | 
			
		||||
        $path = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
 | 
			
		||||
 | 
			
		||||
        //homepage
 | 
			
		||||
        if ($path === '/') {
 | 
			
		||||
            return $path;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // remove empty directory path
 | 
			
		||||
        $path = rtrim($path, '/'); // remove trailing slash
 | 
			
		||||
 | 
			
		||||
        //remove anything after and including ampersand
 | 
			
		||||
        $path = preg_replace('/&.+$/', '', $path);
 | 
			
		||||
        
 | 
			
		||||
        return $path;
 | 
			
		||||
        $this->path = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private function prepareQuery() {
 | 
			
		||||
@ -52,91 +28,16 @@ class Router {
 | 
			
		||||
        if (isset($parsedUri['query'])) {
 | 
			
		||||
            parse_str($parsedUri['query'], $queryArray);
 | 
			
		||||
        }
 | 
			
		||||
        return $queryArray;
 | 
			
		||||
        $this->query = $queryArray;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private function getRequestType() {
 | 
			
		||||
        // is the requewst a get or post?
 | 
			
		||||
        if (empty($_POST)) {
 | 
			
		||||
            return 'get';
 | 
			
		||||
        } else {
 | 
			
		||||
            return 'post';
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private function findController() {
 | 
			
		||||
 | 
			
		||||
        // one to one match
 | 
			
		||||
        if (array_key_exists($this->path, $this->routes)) {
 | 
			
		||||
            if (!empty($this->routes[$this->path][$this->requestType])) {
 | 
			
		||||
                return $this->routes[$this->path][$this->requestType];
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        foreach ($this->routes as $key => $value) {
 | 
			
		||||
            // Check if key contains a curly bracket, if not continue.  We already checked above.
 | 
			
		||||
            if (!strpos($key, '{')) continue;
 | 
			
		||||
 | 
			
		||||
            // Remove everything after the curly bracket, from key
 | 
			
		||||
            $keyPath = substr($key, 0, strpos($key, '{'));
 | 
			
		||||
 | 
			
		||||
            //see if keyPath matches the first characters of $this->path, only the first characters have to match
 | 
			
		||||
            if (strpos($this->path, $keyPath) === 0) {
 | 
			
		||||
                
 | 
			
		||||
                // We have a potential match. Now check if the parameter count is equal
 | 
			
		||||
                $keyParams = explode('/', $key);
 | 
			
		||||
                $pathParams = explode('/', $this->path);
 | 
			
		||||
                $keyParamCount = count($keyParams);
 | 
			
		||||
                $pathParamCount = count($pathParams);
 | 
			
		||||
                if ($keyParamCount === $pathParamCount) {
 | 
			
		||||
                    for ($i=0; $i < $pathParamCount; $i++) {
 | 
			
		||||
                        if (strpos($keyParams[$i], '{') !== false) {
 | 
			
		||||
                            $keyParams[$i] = substr($keyParams[$i], 1, -1);
 | 
			
		||||
                            $this->parameters[$keyParams[$i]] = $pathParams[$i];
 | 
			
		||||
                            return $this->routes[$key][$this->requestType];
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        return '404';
 | 
			
		||||
        
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // checks if the file exists, sets file path
 | 
			
		||||
    private function setRouteFile() {
 | 
			
		||||
        
 | 
			
		||||
        if (str_starts_with($this->controller, 'NOVACONIUM')) {
 | 
			
		||||
            $trimmed = substr($this->controller, strlen('NOVACONIUM/'));
 | 
			
		||||
            $cp = FRAMEWORKPATH . '/controllers/' . $trimmed . '.php';
 | 
			
		||||
        } else {
 | 
			
		||||
            $cp = BASEPATH . '/App/controllers/' . $this->controller . '.php';
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (file_exists($cp)) {
 | 
			
		||||
            return $cp;
 | 
			
		||||
        } else {
 | 
			
		||||
            //Check if 404 exits
 | 
			
		||||
            if (file_exists(BASEPATH . '/App/controllers/404.php')) {
 | 
			
		||||
                return BASEPATH . '/App/controllers/404.php';
 | 
			
		||||
            } else {
 | 
			
		||||
                return FRAMEWORKPATH . '/defaults/App/controllers/404.php';
 | 
			
		||||
        foreach ($this->routes as $key => $value) {
 | 
			
		||||
            if ( $this->path == $key) {
 | 
			
		||||
                $this->controllerPath = BASEPATH . '/App/controllers/' . $value['file'] . '.php';
 | 
			
		||||
                break;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function debug() {
 | 
			
		||||
        echo '<div id="router-debug-container" class="debug">';
 | 
			
		||||
        echo '<table border="1" cellpadding="10" cellspacing="0">';
 | 
			
		||||
        echo '<tr><th>Url Path</th><td>' . htmlspecialchars($this->path) . '</td></tr>';
 | 
			
		||||
        echo '<tr><th>Controller Path</th><td>' . htmlspecialchars($this->controllerPath) . '</td></tr>';
 | 
			
		||||
        echo '<tr><th>Parameters</th><td><pre>' . print_r($this->parameters, true) . '</pre></td></tr>';
 | 
			
		||||
        echo '<tr><th>Routes</th><td><pre>' . print_r($this->routes, true) . '</pre></td></tr>';
 | 
			
		||||
        echo '</table></div>';
 | 
			
		||||
    
 | 
			
		||||
        die();
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -1,63 +0,0 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
class Session {
 | 
			
		||||
    private $session;
 | 
			
		||||
 | 
			
		||||
    public function __construct() {
 | 
			
		||||
        if (session_status() === PHP_SESSION_NONE) {
 | 
			
		||||
            session_start();
 | 
			
		||||
        }
 | 
			
		||||
        $this->session = &$_SESSION; // Reference $_SESSION to keep them in sync
 | 
			
		||||
        if (!isset($this->session['token'])) {
 | 
			
		||||
            $this->setToken();
 | 
			
		||||
        }
 | 
			
		||||
        if (!isset($this->session['messages'])) {
 | 
			
		||||
            $this->session['messages'] = []; // Always ensure messages is an array
 | 
			
		||||
        }
 | 
			
		||||
        if (!isset($this->session['formData'])) {
 | 
			
		||||
            $this->session['formData'] = []; // Initialize formData
 | 
			
		||||
        }
 | 
			
		||||
        if (!isset($this->session['errors'])) {
 | 
			
		||||
            $this->session['errors'] = []; // Initialize errors
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function setToken() {
 | 
			
		||||
        $this->session['token'] = bin2hex(random_bytes(32));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function set($key, $value) {
 | 
			
		||||
        $this->session[$key] = $value;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function get($key) {
 | 
			
		||||
        return isset($this->session[$key]) ? $this->session[$key] : null;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function flash($key) {
 | 
			
		||||
        $return = $this->get($key);
 | 
			
		||||
        $this->delete($key);
 | 
			
		||||
        return $return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function debug() {
 | 
			
		||||
        return $this->session;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function delete($key) {
 | 
			
		||||
        if (isset($this->session[$key])) {
 | 
			
		||||
            unset($this->session[$key]);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function write() {
 | 
			
		||||
        // No need to assign to $_SESSION since $this->session is a reference
 | 
			
		||||
        session_write_close();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function kill() {
 | 
			
		||||
        $this->session = [];
 | 
			
		||||
        $_SESSION = [];
 | 
			
		||||
        session_destroy();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										13
									
								
								src/bootstrap.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								src/bootstrap.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,13 @@
 | 
			
		||||
<?php
 | 
			
		||||
require_once(BASEPATH . '/vendor/autoload.php');
 | 
			
		||||
 | 
			
		||||
//Twig
 | 
			
		||||
$loader = new Twig\Loader\FilesystemLoader(BASEPATH . '/App/views/');
 | 
			
		||||
$loader->addPath(BASEPATH . '/vendor/4lt/novaconium/twig', 'novaconium');
 | 
			
		||||
$loader->addPath(BASEPATH . '/App/templates', 'override');
 | 
			
		||||
$twig = new Twig\Environment($loader);
 | 
			
		||||
 | 
			
		||||
// Load a controller
 | 
			
		||||
require_once('Router.php');
 | 
			
		||||
$router = new Router();
 | 
			
		||||
require_once($router->controllerPath);
 | 
			
		||||
@ -1,29 +0,0 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Dump and Die
 | 
			
		||||
 */
 | 
			
		||||
function dd(...$vars) {
 | 
			
		||||
    echo "<pre style='background:#222;color:#0f0;padding:10px;border-radius:5px;'>";
 | 
			
		||||
    foreach ($vars as $var) {
 | 
			
		||||
        var_dump($var);
 | 
			
		||||
        echo "\n";
 | 
			
		||||
    }
 | 
			
		||||
    echo "</pre>";
 | 
			
		||||
    die();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function makeitso() {
 | 
			
		||||
    global $session, $db, $redirect, $config, $messages, $log;
 | 
			
		||||
 | 
			
		||||
    if (!empty($config['database']['host'])) {
 | 
			
		||||
        $db->close();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    $session->set('messages', $messages->getAllMessages());
 | 
			
		||||
    $session->write();
 | 
			
		||||
 | 
			
		||||
    $redirect->execute();
 | 
			
		||||
 | 
			
		||||
    exit();
 | 
			
		||||
}
 | 
			
		||||
@ -1,63 +0,0 @@
 | 
			
		||||
<?php
 | 
			
		||||
define('FRAMEWORKPATH', BASEPATH . '/vendor/4lt/novaconium');
 | 
			
		||||
 | 
			
		||||
require_once(BASEPATH . '/vendor/autoload.php');
 | 
			
		||||
 | 
			
		||||
//Check if config file exists
 | 
			
		||||
if (file_exists(BASEPATH . '/App/config.php')) {
 | 
			
		||||
    require_once(BASEPATH . '/App/config.php');
 | 
			
		||||
} else {
 | 
			
		||||
    require_once(FRAMEWORKPATH . '/defaults/App/config.php');
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Logging
 | 
			
		||||
require_once(FRAMEWORKPATH . '/src/Logger.php');
 | 
			
		||||
$log = new Logger(BASEPATH . $config['logfile'], $config['loglevel']);
 | 
			
		||||
 | 
			
		||||
// Global Functions
 | 
			
		||||
require_once(FRAMEWORKPATH . '/src/functions.php');
 | 
			
		||||
 | 
			
		||||
// Creates the view() function using twig
 | 
			
		||||
$data = array();
 | 
			
		||||
require_once(FRAMEWORKPATH . '/src/twig.php');
 | 
			
		||||
 | 
			
		||||
// Start a Session
 | 
			
		||||
require_once(FRAMEWORKPATH . '/src/Session.php');
 | 
			
		||||
$session = new Session();
 | 
			
		||||
$data['token'] = $session->get('token');
 | 
			
		||||
$data['username'] = $session->get('username');
 | 
			
		||||
if ($config['loglevel'] == 'DEBUG') {
 | 
			
		||||
    $data['debug'] = nl2br(print_r($session->debug(), true));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Messages
 | 
			
		||||
require_once(FRAMEWORKPATH . '/src/MessageHandler.php');
 | 
			
		||||
$messages = new MessageHandler($session->flash('messages'));
 | 
			
		||||
 | 
			
		||||
foreach (['error','notice'] as $key){
 | 
			
		||||
   $data[$key] = $messages->showMessages($key);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Load Database Class
 | 
			
		||||
if (!empty($config['database']['host'])) {
 | 
			
		||||
    require_once(FRAMEWORKPATH . '/src/Database.php');
 | 
			
		||||
    $db = new Database($config['database']);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Sanatize POST Data
 | 
			
		||||
if (!empty($_POST)) {
 | 
			
		||||
    require_once(FRAMEWORKPATH . '/src/Post.php');
 | 
			
		||||
    $post = new POST($_POST);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Start a Redirect
 | 
			
		||||
require_once(FRAMEWORKPATH . '/src/Redirect.php');
 | 
			
		||||
$redirect = new Redirect();
 | 
			
		||||
 | 
			
		||||
// Load a controller
 | 
			
		||||
require_once(FRAMEWORKPATH . '/src/Router.php');
 | 
			
		||||
$router = new Router();
 | 
			
		||||
//$router->debug();
 | 
			
		||||
require_once($router->controllerPath);
 | 
			
		||||
 | 
			
		||||
makeitso();
 | 
			
		||||
							
								
								
									
										32
									
								
								src/twig.php
									
									
									
									
									
								
							
							
						
						
									
										32
									
								
								src/twig.php
									
									
									
									
									
								
							@ -1,32 +0,0 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
//Twig
 | 
			
		||||
function view($name = '', $moreData = []) {
 | 
			
		||||
    global $config, $data; // Use the globally included $config
 | 
			
		||||
 | 
			
		||||
    if (!empty($moreData)){
 | 
			
		||||
        $data = array_merge($data, $moreData);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    $loader = new Twig\Loader\FilesystemLoader(BASEPATH . '/App/views/');
 | 
			
		||||
    $loader->addPath(FRAMEWORKPATH . '/twig', 'novaconium');
 | 
			
		||||
    $loader->addPath(FRAMEWORKPATH . '/views', 'novacore');
 | 
			
		||||
    $loader->addPath(BASEPATH . '/App/templates', 'override');
 | 
			
		||||
 | 
			
		||||
    $twig = new Twig\Environment($loader);
 | 
			
		||||
 | 
			
		||||
    // Add config to Twig globally
 | 
			
		||||
    $twig->addGlobal('config', $config);
 | 
			
		||||
 | 
			
		||||
    // Check if the template exists
 | 
			
		||||
    if (file_exists(BASEPATH . '/App/views/' . $name . '.html.twig')) {
 | 
			
		||||
        echo $twig->render($name . '.html.twig', $data);
 | 
			
		||||
        return true;
 | 
			
		||||
    } elseif (str_starts_with($name, '@')) { // Check if using framework
 | 
			
		||||
        echo $twig->render($name . '.html.twig', $data);
 | 
			
		||||
        return true;
 | 
			
		||||
    } else {
 | 
			
		||||
        echo "Error: Twig Template ($name) Not Found.";
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -1,6 +0,0 @@
 | 
			
		||||
<!--
 | 
			
		||||
    What goes very last on the page.
 | 
			
		||||
    right before the /body
 | 
			
		||||
    like javascript
 | 
			
		||||
    or analytics
 | 
			
		||||
-->
 | 
			
		||||
@ -1,3 +0,0 @@
 | 
			
		||||
<!-- 
 | 
			
		||||
    What goes in the footer html tag
 | 
			
		||||
-->
 | 
			
		||||
@ -1,6 +1,6 @@
 | 
			
		||||
<meta charset="utf-8">
 | 
			
		||||
<title>{{ title | default('Welcome To Novaconium') }}</title>
 | 
			
		||||
<meta name="generator" content="Novaconium" />
 | 
			
		||||
<title>{{ title | default('Welcome') }}</title>
 | 
			
		||||
<meta name="generator" content="nickyeoman/phpframework" />
 | 
			
		||||
 | 
			
		||||
<meta name="description" content="{{ description | default('No description given') }}">
 | 
			
		||||
<meta name="keywords" content="{{ keywords | default('website') }}">
 | 
			
		||||
@ -19,8 +19,8 @@
 | 
			
		||||
<link rel="icon" type="image/x-icon" href="/favicon.ico">
 | 
			
		||||
 | 
			
		||||
{# https://developers.google.com/fonts/docs/getting_started #}
 | 
			
		||||
<link href="https://fonts.googleapis.com/css2?family=Fira+Code&family=Source+Code+Pro&display=swap&family=Material+Icons&family=Material+Icons+Outlined" rel="stylesheet">
 | 
			
		||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Source+Code+Pro|Material+Icons|Material+Icons+Outlined">
 | 
			
		||||
 | 
			
		||||
<link rel="stylesheet" href="/css/novaconium.css">
 | 
			
		||||
<link rel="stylesheet" href="/css/main.css">
 | 
			
		||||
 | 
			
		||||
<meta name="theme-color" content="#000000">
 | 
			
		||||
@ -1,11 +0,0 @@
 | 
			
		||||
{% if username is not empty %}
 | 
			
		||||
<div class="left">
 | 
			
		||||
    <ul id="leftnav">
 | 
			
		||||
        <li><a href="/">Home</a></li>
 | 
			
		||||
        <li><a href="/novaconium/dashboard">Dashboard</a></li>
 | 
			
		||||
        <li><a href="/novaconium/pages">Pages</a></li>
 | 
			
		||||
        <li><a href="/novaconium/messages">Messages</a></li>
 | 
			
		||||
        <li><a href="/novaconium/logout">Logout</a></li>
 | 
			
		||||
    </ul>
 | 
			
		||||
</div>
 | 
			
		||||
{% endif %}
 | 
			
		||||
@ -1,63 +1,54 @@
 | 
			
		||||
<!doctype html>
 | 
			
		||||
<html class="no-js" lang="en">
 | 
			
		||||
 | 
			
		||||
{% include '@override/above_head.html.twig' ignore missing %}
 | 
			
		||||
{% include '@override/mod_above_head.html.twig' ignore missing %}
 | 
			
		||||
 | 
			
		||||
<head>
 | 
			
		||||
  {% include ['@override/head.html.twig', '@novaconium/head.html.twig'] %}
 | 
			
		||||
  {% include ['@override/mod_head.html.twig', '@novaconium/head.html.twig'] %}
 | 
			
		||||
</head>
 | 
			
		||||
 | 
			
		||||
<body id="{{ pageid | default('pageid') }}" class="{{ pageclass | default('pageclass') }}" >
 | 
			
		||||
<body id="{{ pageid | default('pageid') }}">
 | 
			
		||||
 | 
			
		||||
    {# Page Header #}
 | 
			
		||||
    <header> 
 | 
			
		||||
      {% block headerbefore %}{% endblock %}
 | 
			
		||||
      {% include ['@override/nav.html.twig', '@novaconium/nav.html.twig'] %}
 | 
			
		||||
      {% block headerafter %}{% endblock %}
 | 
			
		||||
    </header>
 | 
			
		||||
  {# Page Header #}
 | 
			
		||||
  <header> 
 | 
			
		||||
    {% block headerbefore %}{% endblock %}
 | 
			
		||||
    {% include ['@override/mod_nav.html.twig', '@novaconium/nav.html.twig'] %}
 | 
			
		||||
    {% block headerafter %}{% endblock %}
 | 
			
		||||
  </header>
 | 
			
		||||
 | 
			
		||||
    <!-- Main Content Of The Page -->
 | 
			
		||||
    <div id="page">
 | 
			
		||||
      <div class="container">
 | 
			
		||||
  <!-- Main Content Of The Page -->
 | 
			
		||||
  <div id="page">
 | 
			
		||||
    <div class="container">
 | 
			
		||||
 | 
			
		||||
        {% include ['@override/left.html.twig','@novaconium/left.html.twig'] %}
 | 
			
		||||
      <div class="middle">
 | 
			
		||||
        {% if error|default is not empty %}
 | 
			
		||||
          {% for key, val in error %}
 | 
			
		||||
            <div class="error">{{ val }}</div>
 | 
			
		||||
          {% endfor %}
 | 
			
		||||
        {% endif %}
 | 
			
		||||
 | 
			
		||||
        <div class="middle">
 | 
			
		||||
          {% if error|default is not empty %}
 | 
			
		||||
            {% for key, val in error %}
 | 
			
		||||
              <div class="error">{{ val }}</div>
 | 
			
		||||
            {% endfor %}
 | 
			
		||||
          {% endif %}
 | 
			
		||||
 | 
			
		||||
          {% if notice|default is not empty %}
 | 
			
		||||
            {% for key, val in notice %}
 | 
			
		||||
              <div class="notice">{{ val }}</div>
 | 
			
		||||
            {% endfor %}
 | 
			
		||||
          {% endif %}
 | 
			
		||||
 | 
			
		||||
          <article>
 | 
			
		||||
            {% block content %}{% endblock %}
 | 
			
		||||
          </article>
 | 
			
		||||
 | 
			
		||||
          {% include ['@override/right.html.twig','@novaconium/right.html.twig'] %}
 | 
			
		||||
        </div>
 | 
			
		||||
        {% if notice|default is not empty %}
 | 
			
		||||
          {% for key, val in notice %}
 | 
			
		||||
            <div class="notice">{{ val }}</div>
 | 
			
		||||
          {% endfor %}
 | 
			
		||||
        {% endif %}
 | 
			
		||||
 | 
			
		||||
        <article>
 | 
			
		||||
          {% include 'cms/mod_alex.html.twig' ignore missing %}
 | 
			
		||||
          {% block content %}{% endblock %}
 | 
			
		||||
          {% include 'cms/mod_simon.html.twig' ignore missing %}
 | 
			
		||||
        </article>
 | 
			
		||||
      </div>
 | 
			
		||||
 | 
			
		||||
    </div>
 | 
			
		||||
  </div>
 | 
			
		||||
 | 
			
		||||
    {# Page Footer #}
 | 
			
		||||
    <footer>
 | 
			
		||||
      {% block footerbefore %}{% endblock %}
 | 
			
		||||
      {% include ['@override/footer.html.twig', '@novaconium/footer.html.twig'] %}
 | 
			
		||||
      {% block footerafter %}{% endblock %}
 | 
			
		||||
    </footer>
 | 
			
		||||
  {# Page Footer #}
 | 
			
		||||
  <footer>
 | 
			
		||||
    {% block footerbefore %}{% endblock %}
 | 
			
		||||
    {% include ['@override/mod_footer.html.twig', '@novaconium/footer.html.twig'] %}
 | 
			
		||||
    {% block footerafter %}{% endblock %}
 | 
			
		||||
  </footer>
 | 
			
		||||
 | 
			
		||||
    {% if debug is not empty %}
 | 
			
		||||
      <div id="debug">
 | 
			
		||||
          <h2>Debugging Information</h2>
 | 
			
		||||
          {{ debug|raw }}
 | 
			
		||||
      </div>
 | 
			
		||||
    {% endif %}
 | 
			
		||||
  
 | 
			
		||||
{% include ['@override/foot.html.twig', '@novaconium/foot.html.twig'] %}
 | 
			
		||||
{% include ['@override/mod_foot.html.twig', '@novaconium/foot.html.twig'] %}
 | 
			
		||||
</body></html>
 | 
			
		||||
 | 
			
		||||
@ -1,5 +1,21 @@
 | 
			
		||||
<div id="topnav">
 | 
			
		||||
<div class="container">
 | 
			
		||||
  <div id="logo"><a href="/">Logo Goes Here</a></div>
 | 
			
		||||
 | 
			
		||||
  <nav>
 | 
			
		||||
    <! -- Navigation Goes Here -->
 | 
			
		||||
 | 
			
		||||
    <ul>
 | 
			
		||||
 | 
			
		||||
      <li><a href="/">Home</a></li>
 | 
			
		||||
      <li><a href="/contact">Contact Us</a></li>
 | 
			
		||||
      {% if loggedin|default(false) %}
 | 
			
		||||
        <li><a href="/logout/">Logout</a></li>
 | 
			
		||||
      {% else %}
 | 
			
		||||
        <li><a href="/login/">Login</a></li>
 | 
			
		||||
      {% endif %}
 | 
			
		||||
      {% if admin|default(false) == 'admin' %}
 | 
			
		||||
        <li><a href="/admin">Admin</a></li>
 | 
			
		||||
      {% endif %}
 | 
			
		||||
    </ul>
 | 
			
		||||
 | 
			
		||||
  </nav>
 | 
			
		||||
</div>
 | 
			
		||||
 | 
			
		||||
@ -1 +0,0 @@
 | 
			
		||||
<!-- Right Col -->
 | 
			
		||||
@ -1,8 +0,0 @@
 | 
			
		||||
{% extends '@novaconium/master.html.twig' %}
 | 
			
		||||
 | 
			
		||||
{% block content %}
 | 
			
		||||
    <h1>{{title}}</h1>
 | 
			
		||||
    <p>Dashboard page</p>
 | 
			
		||||
    <p><a href="/">Homepage</a></p>
 | 
			
		||||
    <p><a href="/novaconium/logout">logout</p>
 | 
			
		||||
{% endblock %}
 | 
			
		||||
@ -1,30 +0,0 @@
 | 
			
		||||
{% extends '@novaconium/master.html.twig' %}
 | 
			
		||||
 | 
			
		||||
{% block content %}
 | 
			
		||||
    <h2>Edit Message - {{ title }}</h2>
 | 
			
		||||
 | 
			
		||||
    <p><a href="/novaconium/messages/delete/{{ themessage.id }}">Delete</a></p>
 | 
			
		||||
 | 
			
		||||
    <form method="post" action="/novaconium/message_save">
 | 
			
		||||
        <input type="hidden" name="id" value="{{ themessage.id }}">
 | 
			
		||||
        <input type="hidden" name="token" value="{{ token }}">
 | 
			
		||||
 | 
			
		||||
        <label for="name">Name:</label>
 | 
			
		||||
        <input type="text" id="name" name="name" value="{{ themessage.name }}" required>
 | 
			
		||||
 | 
			
		||||
        <label for="email">Email:</label>
 | 
			
		||||
        <input type="email" id="email" name="email" value="{{ themessage.email }}" required>
 | 
			
		||||
 | 
			
		||||
        <label for="message">Message:</label>
 | 
			
		||||
        <textarea id="message" name="message" rows="10" required>{{ themessage.message }}</textarea>
 | 
			
		||||
 | 
			
		||||
        <label for="unread">
 | 
			
		||||
            <input type="checkbox" id="unread" name="unread" value="1" {% if themessage.unread %}checked{% endif %}>
 | 
			
		||||
            Unread
 | 
			
		||||
        </label>
 | 
			
		||||
 | 
			
		||||
        <p><strong>Created:</strong> {{ themessage.created|date("Y-m-d H:i:s") }}</p>
 | 
			
		||||
 | 
			
		||||
        <button type="submit">Save Changes</button>
 | 
			
		||||
    </form>
 | 
			
		||||
{% endblock %}
 | 
			
		||||
@ -1,61 +0,0 @@
 | 
			
		||||
{% extends '@novaconium/master.html.twig' %}
 | 
			
		||||
 | 
			
		||||
{% block content %}
 | 
			
		||||
<h2>Edit Page - {{ title }}</h2>
 | 
			
		||||
 | 
			
		||||
<form method="post" action="/novaconium/savePage">
 | 
			
		||||
    <input type="hidden" name="id" value="{{ rows.id }}">
 | 
			
		||||
    <input type="hidden" name="token" value="{{ token }}">
 | 
			
		||||
 | 
			
		||||
    <label for="title">Title:</label>
 | 
			
		||||
    <input type="text" id="title" name="title" value="{{ rows.title }}" required>
 | 
			
		||||
 | 
			
		||||
    <label for="heading">Heading:</label>
 | 
			
		||||
    <input type="text" id="heading" name="heading" value="{{ rows.heading }}">
 | 
			
		||||
 | 
			
		||||
    <label for="description">Description:</label>
 | 
			
		||||
    <input type="text" id="description" name="description" value="{{ rows.description }}">
 | 
			
		||||
 | 
			
		||||
    <label for="keywords">Keywords:</label>
 | 
			
		||||
    <input type="text" id="keywords" name="keywords" value="{{ rows.keywords }}">
 | 
			
		||||
 | 
			
		||||
    <label for="author">Author:</label>
 | 
			
		||||
    <input type="text" id="author" name="author" value="{{ rows.author }}">
 | 
			
		||||
 | 
			
		||||
    <label for="slug">Slug: (<a href="/page/{{ rows.slug }}" target="_new">/page/{{ rows.slug }}</a>)</label>
 | 
			
		||||
    <input type="text" id="slug" name="slug" value="{{ rows.slug }}" required>
 | 
			
		||||
 | 
			
		||||
    <label for="path">Path:</label>
 | 
			
		||||
    <input type="text" id="path" name="path" value="{{ rows.path }}">
 | 
			
		||||
 | 
			
		||||
    <label for="intro">Intro:</label>
 | 
			
		||||
    <textarea id="intro" name="intro" rows="5">{{ rows.intro }}</textarea>
 | 
			
		||||
 | 
			
		||||
    <label for="body">Body:</label>
 | 
			
		||||
    <textarea id="body" name="body" rows="10">{{ rows.body }}</textarea>
 | 
			
		||||
 | 
			
		||||
    <label for="notes">Notes:</label>
 | 
			
		||||
    <textarea id="notes" name="notes" rows="5">{{ rows.notes }}</textarea>
 | 
			
		||||
 | 
			
		||||
    <label for="draft">
 | 
			
		||||
        <input type="checkbox" id="draft" name="draft" value="1" {% if rows.draft %}checked{% endif %}>
 | 
			
		||||
        Save as Draft
 | 
			
		||||
    </label>
 | 
			
		||||
 | 
			
		||||
    <label for="changefreq">Change Frequency:</label>
 | 
			
		||||
    <select id="changefreq" name="changefreq">
 | 
			
		||||
        {% set freqs = ['always', 'hourly', 'daily', 'weekly', 'monthly', 'yearly', 'never'] %}
 | 
			
		||||
        {% for freq in freqs %}
 | 
			
		||||
            <option value="{{ freq }}" {% if rows.changefreq == freq %}selected{% endif %}>{{ freq|capitalize }}</option>
 | 
			
		||||
        {% endfor %}
 | 
			
		||||
    </select>
 | 
			
		||||
 | 
			
		||||
    <label for="priority">Priority (0.0 - 1.0):</label>
 | 
			
		||||
    <input type="number" id="priority" name="priority" value="{{ rows.priority }}" step="0.1" min="0" max="1">
 | 
			
		||||
 | 
			
		||||
    <p><strong>Created:</strong> {{ rows.created|date("Y-m-d H:i:s") }}</p>
 | 
			
		||||
    <p><strong>Last Updated:</strong> {{ rows.updated|date("Y-m-d H:i:s") }}</p>
 | 
			
		||||
 | 
			
		||||
    <button type="submit">Save Changes</button>
 | 
			
		||||
</form>
 | 
			
		||||
{% endblock %}
 | 
			
		||||
@ -1,67 +0,0 @@
 | 
			
		||||
{% extends '@novaconium/master.html.twig' %}
 | 
			
		||||
 | 
			
		||||
{% block content %}
 | 
			
		||||
    <h1>{{title}}</h1>
 | 
			
		||||
 | 
			
		||||
    {% if not secure_key %}
 | 
			
		||||
        <div id="secure_key">
 | 
			
		||||
            <h2>Secure Key</h2>
 | 
			
		||||
            <p>Please set the <code>secure_key</code> in <code>App/config.php</code> to an alphanumeric code that is 64 characters in length.</p>
 | 
			
		||||
            <p>You can generate a secure key like this:</p>
 | 
			
		||||
            <pre><code>pwgen -sB 64 1</code></pre>
 | 
			
		||||
            <p>Or use this one:</p>
 | 
			
		||||
            <pre><code>{{gen_key}}</code></pre>
 | 
			
		||||
        </div>
 | 
			
		||||
    {% endif %}
 | 
			
		||||
 | 
			
		||||
    {% if users_created %}
 | 
			
		||||
        <div id="users_created">
 | 
			
		||||
            <h2>Users Table Created</h2>
 | 
			
		||||
            <p>There was no users table in the database.  One was created.</p>
 | 
			
		||||
        </div>
 | 
			
		||||
    {% endif %}
 | 
			
		||||
 | 
			
		||||
    {% if empty_users and secure_key %}
 | 
			
		||||
        <div id="users_created">
 | 
			
		||||
            <h2>Create Admin</h2>
 | 
			
		||||
            <p>No admin users exist, make an admin user now.</p>
 | 
			
		||||
 | 
			
		||||
            <form method="post" action="/novaconium/create_admin">
 | 
			
		||||
                <input type="hidden" name="token" value="{{ token }}" />
 | 
			
		||||
                
 | 
			
		||||
                <label for="username">Username:</label><br>
 | 
			
		||||
                <input type="text" id="username" name="username" required><br><br>
 | 
			
		||||
 | 
			
		||||
                <label for="email">Email:</label><br>
 | 
			
		||||
                <input type="text" id="email" name="email" required><br><br>
 | 
			
		||||
 | 
			
		||||
                <label for="password">Password:</label><br>
 | 
			
		||||
                <input type="password" id="password" name="password" required><br><br>
 | 
			
		||||
 | 
			
		||||
                <label for="secure_key">Secure Key <i>The <code>secure_key</code> from your config.php file</i>:</label><br>
 | 
			
		||||
                <input type="text" id="secure_key" name="secure_key" required><br><br>
 | 
			
		||||
 | 
			
		||||
                <button type="submit">Create User</button>
 | 
			
		||||
            </form>
 | 
			
		||||
        </div>
 | 
			
		||||
    {% endif %}
 | 
			
		||||
 | 
			
		||||
    {% if show_login %}
 | 
			
		||||
        <div id="users_created">
 | 
			
		||||
            <h2>Administrator Login</h2>
 | 
			
		||||
            
 | 
			
		||||
            <form method="post" action="/novaconium/login">
 | 
			
		||||
                <input type="hidden" name="token" value="{{ token }}" />
 | 
			
		||||
                
 | 
			
		||||
                <label for="username">Username:</label><br>
 | 
			
		||||
                <input type="text" id="username" name="username" required><br><br>
 | 
			
		||||
 | 
			
		||||
                <label for="password">Password:</label><br>
 | 
			
		||||
                <input type="password" id="password" name="password" required><br><br>
 | 
			
		||||
 | 
			
		||||
                <button type="submit">Login</button>
 | 
			
		||||
            </form>
 | 
			
		||||
        </div>
 | 
			
		||||
    {% endif %}
 | 
			
		||||
 | 
			
		||||
{% endblock %}
 | 
			
		||||
@ -1,23 +0,0 @@
 | 
			
		||||
{% extends '@novaconium/master.html.twig' %}
 | 
			
		||||
 | 
			
		||||
{% block content %}
 | 
			
		||||
    <h1>{{title}}</h1>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    <div id="login_form">
 | 
			
		||||
        
 | 
			
		||||
        <form method="post" action="/novaconium/login">
 | 
			
		||||
            <input type="hidden" name="token" value="{{ token }}" />
 | 
			
		||||
            
 | 
			
		||||
            <label for="username">Username:</label><br>
 | 
			
		||||
            <input type="text" id="username" name="username" required><br><br>
 | 
			
		||||
 | 
			
		||||
            <label for="password">Password:</label><br>
 | 
			
		||||
            <input type="password" id="password" name="password" required><br><br>
 | 
			
		||||
 | 
			
		||||
            <button type="submit">Login</button>
 | 
			
		||||
        </form>
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
{% endblock %}
 | 
			
		||||
@ -1,43 +0,0 @@
 | 
			
		||||
{% extends '@novaconium/master.html.twig' %}
 | 
			
		||||
 | 
			
		||||
{% block content %}
 | 
			
		||||
    <h1>{{title}}</h1>
 | 
			
		||||
    <table class="messages-table">
 | 
			
		||||
  <thead>
 | 
			
		||||
    <tr>
 | 
			
		||||
      <th>Email</th>
 | 
			
		||||
      <th>Name</th>
 | 
			
		||||
      <th>Message Preview</th>
 | 
			
		||||
      <th>Created</th>
 | 
			
		||||
      <th>Status</th>
 | 
			
		||||
      <th>Actions</th>
 | 
			
		||||
    </tr>
 | 
			
		||||
  </thead>
 | 
			
		||||
  <tbody>
 | 
			
		||||
    {% for msg in messages %}
 | 
			
		||||
      <tr class="{{ msg.unread ? 'unread' : 'read' }}">
 | 
			
		||||
        <td><a href="mailto:{{ msg.email }}">{{ msg.email }}</a></td>
 | 
			
		||||
        <td>{{ msg.name }}</td>
 | 
			
		||||
        <td>{{ msg.message }}</td>
 | 
			
		||||
        <td>{{ msg.created|date('Y-m-d H:i') }}</td>
 | 
			
		||||
        <td>
 | 
			
		||||
          {% if msg.unread %}
 | 
			
		||||
            <strong>Unread</strong>
 | 
			
		||||
          {% else %}
 | 
			
		||||
            Read
 | 
			
		||||
          {% endif %}
 | 
			
		||||
        </td>
 | 
			
		||||
        <td>
 | 
			
		||||
          <a href="/novaconium/messages/edit/{{ msg.id }}" class="btn btn-edit">Edit</a>
 | 
			
		||||
          <a href="/novaconium/messages/delete/{{ msg.id }}">Delete</a>
 | 
			
		||||
        </td>
 | 
			
		||||
      </tr>
 | 
			
		||||
    {% else %}
 | 
			
		||||
      <tr>
 | 
			
		||||
        <td colspan="7" style="text-align:center;">No messages found</td>
 | 
			
		||||
      </tr>
 | 
			
		||||
    {% endfor %}
 | 
			
		||||
  </tbody>
 | 
			
		||||
</table>
 | 
			
		||||
 | 
			
		||||
{% endblock %}
 | 
			
		||||
@ -1,30 +0,0 @@
 | 
			
		||||
{% extends '@novaconium/master.html.twig' %}
 | 
			
		||||
 | 
			
		||||
{% block content %}
 | 
			
		||||
    <h1>{{title}}</h1>
 | 
			
		||||
    <p><a href="/novaconium/page/create">Create Page</a></p>
 | 
			
		||||
    <table class="pages-table">
 | 
			
		||||
      <thead>
 | 
			
		||||
        <tr>
 | 
			
		||||
          <th>Title</th>
 | 
			
		||||
          <th>Created</th>
 | 
			
		||||
          <th>Updated</th>
 | 
			
		||||
          <th>Draft</th>
 | 
			
		||||
        </tr>
 | 
			
		||||
      </thead>
 | 
			
		||||
      <tbody>
 | 
			
		||||
        {% for page in pages %}
 | 
			
		||||
          <tr>
 | 
			
		||||
            <td><a href="/novaconium/page/edit/{{ page.id }}">{{ page.title }}</a></td>
 | 
			
		||||
            <td>{{ page.created }}</td>
 | 
			
		||||
            <td>{{ page.updated|default('Not updated') }}</td>
 | 
			
		||||
            <td>{{ page.draft ? 'Draft' : 'Published' }}</td>
 | 
			
		||||
          </tr>
 | 
			
		||||
        {% else %}
 | 
			
		||||
          <tr>
 | 
			
		||||
            <td colspan="6">No pages found.</td>
 | 
			
		||||
          </tr>
 | 
			
		||||
        {% endfor %}
 | 
			
		||||
      </tbody>
 | 
			
		||||
    </table>
 | 
			
		||||
{% endblock %}
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user