Compare commits

...

3 Commits
4.1.1 ... 5.0

Author SHA1 Message Date
2aed4ec2ed composer bump 2025-02-04 21:34:36 -05:00
bd939cc078 composer bump 2025-02-03 12:47:23 -05:00
1fb4d2eb57 wip 2025-02-03 10:29:09 -05:00
32 changed files with 646 additions and 158 deletions

View File

@ -73,7 +73,7 @@ class Config extends BedrockConfig {
$html .= '<div class="mb-3 row">';
$html .= '<h4 class="col-lg-3 col-form-label text-end">Current Image</h4>';
$html .= '<div class="col-lg-6">';
$html .= '<img alt="User Avatar" src="{ROOT_URL}' . $node['value'] . '" class="img-circle img-fluid p-2 avatar-125">';
$html .= '<img alt="configured image" src="{ROOT_URL}' . $node['value'] . '" class="img-circle img-fluid p-2 avatar-125">';
$html .= '</div>';
}
$html .= '</div>';

View File

@ -121,7 +121,6 @@ class DatabaseModel extends BedrockDatabaseModel {
$errors = [];
foreach ( self::$installFlags as $flag_name ) {
if ( empty( $options[$flag_name] ) ) {
$module_data[ $flag_name ] = INSTALL_STATUS_SKIPPED;
continue;
}

View File

@ -115,6 +115,8 @@ class Forms extends Check {
self::addHandler( 'adminCreateToken', __CLASS__, 'adminCreateToken' );
self::addHandler( 'apiLogin', __CLASS__, 'apiLogin' );
self::addHandler( 'updatePreference', __CLASS__, 'updatePreference' );
self::addHandler( 'renameIImage', __CLASS__, 'renameIImage' );
self::addHandler( 'addImage', __CLASS__, 'addImage' );
self::addHandler( 'installStart', __CLASS__, 'install', [ 'start' ] );
self::addHandler( 'installAgreement', __CLASS__, 'install', [ 'agreement' ] );
self::addHandler( 'installCheck', __CLASS__, 'install', [ 'check' ] );
@ -663,4 +665,28 @@ class Forms extends Check {
}
return true;
}
public static function renameIImage() {
if ( !Input::exists( 'filelocation' ) ) {
self::addUserError( 'You must specify a location' );
return false;
}
if ( !Input::exists( 'newname' ) ) {
self::addUserError( 'You must specify a new name' );
return false;
}
return true;
}
public static function addImage() {
if ( !Input::exists( 'folderSelect' ) ) {
self::addUserError( 'You must specify a location' );
return false;
}
if ( !Input::exists( 'uploadImage' ) ) {
self::addUserError( 'You must include a file.' );
return false;
}
return true;
}
}

View File

@ -158,6 +158,10 @@ class Installer {
} else {
self::$installJson['modules'][$name]['enabled_txt'] = '<span class="text-danger">No</span>';
}
// in this case only, we save an array to remove the objects later, so an array stored is a success.
if ( ! empty( self::$installJson['modules'][$name]['resources_installed'] ) && is_array( self::$installJson['modules'][$name]['resources_installed'] ) ) {
self::$installJson['modules'][$name]['resources_installed'] = INSTALL_STATUS_SUCCESS;
}
}
return self::$installJson['modules'][$name];
}
@ -530,7 +534,7 @@ class Installer {
}
foreach ( $flags as $flag_type ) {
if ( ! in_array( $modelInfo[ $flag_type ], [ INSTALL_STATUS_SUCCESS, INSTALL_STATUS_NOT_REQUIRED ] ) ) {
if ( empty( $modelInfo[ $flag_type ] ) || ! in_array( $modelInfo[ $flag_type ], [ INSTALL_STATUS_SUCCESS, INSTALL_STATUS_NOT_REQUIRED ] ) ) {
$modelInfo['installStatus'] = INSTALL_STATUS_PARTIALLY_INSTALLED;
break;
}

View File

@ -245,7 +245,7 @@ class Permissions {
public static function getFieldEditHtml( $name, $default, $pretty ) {
$fieldname = str_ireplace( '/', '-', $name );
$fieldHtml = Forms::getSwitchHtml( $fieldname, [ 'true', 'false' ], $default );
$fieldHtml = Forms::getSwitchHtml( $fieldname, $default );
$html = '';
$html .= '<div class="mb-3 row">';
$html .= '<label for="' . $fieldname . '" class="col-lg-6 col-form-label text-end">' . $pretty . '</label>';

View File

@ -89,7 +89,7 @@ class Plugin {
foreach ( self::PLUGIN_FLAG_MAP as $flag_name => $function_name ) {
if ( empty( $options[$flag_name] ) ) {
$module_data[ $flag_name ] = INSTALL_STATUS_SKIPPED;
// $module_data[ $flag_name ] = INSTALL_STATUS_SKIPPED;
continue;
}
@ -280,7 +280,7 @@ class Plugin {
public function installResources( $options = '' ) {
if ( empty( $this->resourceMatrix ) ) {
Debug::log( 'resourceMatrix is empty' );
return true;
return INSTALL_STATUS_NOT_REQUIRED;
}
$ids = [];
foreach( $this->resourceMatrix as $tableName => $entries ) {

View File

@ -276,7 +276,7 @@ class Preferences {
$html .= '<div class="mb-3 row">';
$html .= '<h4 class="col-lg-6 col-form-label text-start text-lg-end">Current Image</h4>';
$html .= '<div class="col-lg-6">';
$html .= '<img alt="User Avatar" src="{ROOT_URL}' . $defaultValue . '" class="img-circle img-fluid p-2 avatar-125">';
$html .= '<img alt="preferred image" src="{ROOT_URL}' . $defaultValue . '" class="img-circle img-fluid p-2">';
$html .= '</div>';
}
$html .= '</div>';

View File

@ -39,7 +39,7 @@ if ( ! defined( 'CONFIG_DIRECTORY' ) ) {
define( 'CANARY_SECURE_HASH', 'd73ed7591a30f0ca7d686a0e780f0d05' );
# Tempus Project Core
define( 'APP_NAME', 'The Tempus Project');
define( 'TP_DEFAULT_LOGO', 'images/logo.png');
define( 'TP_DEFAULT_LOGO', 'images/logoWhite.png');
// Check
define( 'MINIMUM_PHP_VERSION', 8.1);
// Cookies

View File

@ -23,8 +23,154 @@ use TheTempusProject\Bedrock\Functions\Input;
use TheTempusProject\Bedrock\Functions\Check;
use TheTempusProject\Hermes\Functions\Redirect;
use TheTempusProject\Bedrock\Functions\Session;
use TheTempusProject\Hermes\Functions\Route as Routes;
use TheTempusProject\Bedrock\Functions\Upload;
use RecursiveIteratorIterator;
use RecursiveDirectoryIterator;
use FilesystemIterator;
class Images extends AdminController {
private $directories = [
APP_ROOT_DIRECTORY . 'images',
APP_ROOT_DIRECTORY . 'app/images',
APP_ROOT_DIRECTORY . 'app/plugins'
];
private $excludedDirectories = [
'.',
'..',
'vendor',
'docker',
'logs',
'gitlab',
'uploads',
'config',
];
public function upload() {
if ( Input::exists( 'submit' ) ) {
$route = '';
$destination = '';
if ( !TTPForms::check( 'addImage' ) ) {
Issues::add( 'error', [ 'There was an error with your image upload.' => Check::userErrors() ] );
} else {
$folder = Input::post( 'folderSelect' ) . DIRECTORY_SEPARATOR;
// dv( $folder );
$upload = Upload::image( 'uploadImage', $folder );
if ( $upload ) {
$route = str_replace( APP_ROOT_DIRECTORY, '', $folder );
$destination = $route . Upload::last();
Issues::add( 'success', 'Image uploaded.' );
} else {
Issues::add( 'error', [ 'There was an error with your image upload.' => Check::userErrors() ] );
}
}
}
$folders = $this->getDirectoriesRecursive( APP_ROOT_DIRECTORY );
$folderHtml = $this->generateFolderHtml( $folders );
Components::set( 'FOLDER_SELECT_ROOT', APP_ROOT_DIRECTORY );
Components::set( 'FOLDER_SELECT', Views::simpleView( 'forms.folderSelect', $folderHtml ) );
Views::view( 'admin.images.upload' );
}
private function getFolderObject( $folder, $subdirs = '' ) {
$names = explode( DIRECTORY_SEPARATOR, $folder );
$folderName = array_pop( $names );
$out = [
'folderName' => $folderName,
'location' => $folder,
'subdirs' => $subdirs,
];
if ( ! empty( $subdirs ) ) {
$out['folderexpand'] = '<i class="fa-solid fa-caret-down justify-content-end"></i>';
} else {
$out['folderexpand'] = '';
}
return (object) $out;
}
private function generateFolderHtml( $folders ) {
$rows = [];
foreach ( $folders as $top => $sub ) {
$object = $this->getFolderObject( $top );
if ( $top == $sub ) {
$html = '';
} else {
$children = $this->generateFolderHtml( $sub );
Components::set( 'parentfolderName', $object->folderName );
$html = Views::simpleView( 'forms.folderSelectParent', $children );
Components::set( 'parentfolderName', '' );
}
$rows[] = $this->getFolderObject( $top, $html );
}
return $rows;
}
private function getDirectoriesRecursive( $directory ) {
$dirs = [];
$directory = rtrim( $directory, DIRECTORY_SEPARATOR );
$directory = $directory. DIRECTORY_SEPARATOR;
$files = scandir( $directory );
$filteredFiles = array_values( array_diff( $files, $this->excludedDirectories ) );
foreach ( $filteredFiles as $key => $filename ) {
$long_name = $directory . $filename;
$is_dir = ( ( strpos( $filename, '.' ) === false ) && ( is_dir( $long_name ) === true ) );
if ( $is_dir ) {
$recursive_dirs = $this->getDirectoriesRecursive( $long_name );
if ( empty( $recursive_dirs ) ) {
$recursive_dirs = $long_name;
}
$dirs[$long_name] = $recursive_dirs;
}
}
return $dirs;
}
public function __construct() {
parent::__construct();
@ -76,39 +222,147 @@ class Images extends AdminController {
Views::view( 'admin.images.create' );
}
public function delete( $id = null ) {
public function delete() {
if ( self::$token->delete( [ $id ] ) ) {
Session::flash( 'success', 'Token deleted.' );
}
Redirect::to( 'admin/images' );
}
public function edit( $id = null ) {
$token = self::$token->findById( $id );
if ( Input::exists( 'submit' ) ) {
if ( !TTPForms::check( 'adminEditToken' ) ) {
Issues::add( 'error', [ 'There was an error with your token.' => Check::userErrors() ] );
} else {
if ( self::$token->update(
$id,
Input::post( 'name' ),
Input::post( 'notes' ),
Input::post( 'token_type' )
) ) {
Session::flash( 'success', 'Token Updated' );
public function rename() {
if ( ! Input::exists( 'fileLocation' ) ) {
Session::flash( 'warning', 'Unknown image.' );
Redirect::to( 'admin/images' );
}
Components::set( 'filelocation', Input::get( 'fileLocation' ) );
if ( Input::exists( 'submit' ) ) {
if ( !TTPForms::check( 'renameIImage' ) ) {
Issues::add( 'error', [ 'There was an error renaming the image.' => Check::userErrors() ] );
} else {
$result = $this->renameFile( Input::post( 'filelocation' ), Input::post( 'newname' ) );
if ( ! empty( $result ) ) {
Session::flash( 'success', 'Image has been renamed.' );
Redirect::to( 'admin/images' );
} else {
Issues::add( 'error', [ 'There was an error with the install.' => $this->installer->getErrors() ] );
}
}
Forms::selectOption( $token->token_type );
return Views::view( 'admin.images.edit', $token );
}
return Views::view( 'admin.images.rename' );
}
public function index() {
return Views::view( 'admin.images.list', self::$token->listPaginated() );
return Views::view( 'admin.images.list.combined', $this->getAllImageDetails() );
}
public function view( $id = null ) {
return Views::view( 'admin.images.view', self::$token->findById( $id ) );
public function view() {
if ( Input::exists( 'fileLocation' ) ) {
return Views::view( 'admin.images.view', $this->getImageByLocation( Input::get( 'fileLocation' ) ) );
}
return $this->index();
}
private function getAllImages() {
$files = [];
foreach ($this->directories as $dir) {
if ($dir === 'app/plugins') {
$pluginDirs = glob($dir . '/*', GLOB_ONLYDIR);
foreach ($pluginDirs as $pluginDir) {
$imageDir = $pluginDir . '/images';
if (is_dir($imageDir)) {
$files = array_merge($files, $this->scanDirectoryRecursively($imageDir));
}
}
} else {
$files = array_merge($files, $this->scanDirectory($dir));
}
}
return $files;
}
private function scanDirectory($path) {
return glob($path . '/*.{jpg,jpeg,png,gif,webp}', GLOB_BRACE) ?: [];
}
private function scanDirectoryRecursively($path) {
$files = [];
$iterator = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($path, FilesystemIterator::SKIP_DOTS));
foreach ($iterator as $file) {
if (preg_match('/\.(jpg|jpeg|png|gif|webp)$/i', $file->getFilename())) {
$files[] = $file->getPathname();
}
}
return $files;
}
private function getAllImageDetails() {
$images = [];
$files = $this->getAllImages();
foreach ( $files as $file ) {
$images[] = $this->getImageByLocation( $file );
}
return $images;
}
private function getImageByLocation( $location ) {
$realPath = realpath( $location );
return (object) [
'filename' => basename( $location ),
'extension' => pathinfo( $location , PATHINFO_EXTENSION),
'fileSize' => $this->formatFileSize(filesize( $location )),
'location' => $realPath,
'locationSafe' => urlencode( $realPath ),
'url' => Routes::getAddress() . str_replace( APP_ROOT_DIRECTORY, '', $realPath ),
'folder' => dirname( $location )
];
}
private function formatFileSize($size) {
$units = ['B', 'KB', 'MB', 'GB', 'TB'];
$i = 0;
while ($size >= 1024 && $i < count($units) - 1) {
$size /= 1024;
$i++;
}
return round($size, 2) . ' ' . $units[$i];
}
private function renameFile( $currentLocation, $newFilename ) {
// Ensure the file exists
if (!file_exists($currentLocation)) {
throw new \Exception("File does not exist: $currentLocation");
}
// Extract directory and current extension
$directory = dirname($currentLocation);
$currentExtension = pathinfo($currentLocation, PATHINFO_EXTENSION);
$newExtension = pathinfo($newFilename, PATHINFO_EXTENSION);
// Ensure the file extension has not changed
if (strcasecmp($currentExtension, $newExtension) !== 0) {
throw new \Exception("File extension cannot be changed.");
}
// Construct the new file path
$newLocation = $directory . DIRECTORY_SEPARATOR . $newFilename;
// Ensure the new file name does not already exist
if (file_exists($newLocation)) {
throw new \Exception("A file with the new name already exists: $newFilename");
}
// Attempt to rename the file
if (!rename($currentLocation, $newLocation)) {
throw new \Exception("Failed to rename file.");
}
return true;
}
}

View File

@ -46,6 +46,14 @@ class Group extends DatabaseModel {
'pretty' => 'Access Administrator Areas',
'default' => false,
],
'uploadImages' => [
'pretty' => 'Upload images (such as avatars)',
'default' => false,
],
'maintenanceAccess' => [
'pretty' => 'Upload images (such as avatars)',
'default' => false,
],
];
public $resourceMatrix = [
[

View File

@ -47,12 +47,6 @@ class User extends DatabaseModel {
public $searchFields = [
'username',
];
public $permissionMatrix = [
'uploadImages' => [
'pretty' => 'Upload images (such as avatars)',
'default' => false,
],
];
public $preferenceMatrix = [
'gender' => [
'pretty' => 'Gender',

View File

@ -45,19 +45,19 @@
<!-- Navbar Toggler (Left) -->
<!-- Centered Logo (Now inside normal document flow) -->
<a href="/" class="d-flex align-items-center text-white text-decoration-none d-md-none mx-auto">
<a href="/" class="align-items-center text-white text-decoration-none d-flex d-md-none">
<img src="{ROOT_URL}{LOGO}" width="40" height="32" alt="{SITENAME} Logo" class="bi">
</a>
<!-- Logo (Normal Position for Large Screens) -->
<a href="/" class="d-flex align-items-center text-white text-decoration-none d-none d-md-flex">
<a href="/" class="align-items-center text-white text-decoration-none d-none d-md-flex">
<img src="{ROOT_URL}{LOGO}" width="40" height="32" alt="{SITENAME} Logo" class="bi">
</a>
<div class="navbar-expand-md flex-grow-1">
<div class="collapse navbar-collapse d-md-flex" id="mainMenu">
<!-- Centered Navigation -->
<div class="d-none d-md-block d-flex justify-content-center w-100 position-absolute start-50 translate-middle-x">
<div class="d-none d-md-block d-flex justify-content-center position-absolute start-50 translate-middle-x">
{topNavLeft}
</div>
<div class="d-flex justify-content-center flex-grow-1 d-md-none">
@ -77,6 +77,7 @@
</div>
</div>
</header>
<div class="d-flex flex-column min-vh-100">
<div class="flex-container flex-grow-1">
{ISSUES}

View File

@ -44,7 +44,7 @@ class Bugreport extends Plugin {
],
];
public $permissionMatrix = [
'bugReport' => [
'canSendBugReports' => [
'pretty' => 'Can Submit Bug Reports',
'default' => false,
],

View File

@ -40,7 +40,7 @@ class Contact extends Plugin {
],
];
public $permissionMatrix = [
'contact' => [
'canUseContactForm' => [
'pretty' => 'Can Submit Contact',
'default' => true,
],

View File

@ -0,0 +1,30 @@
<div class="container py-5 context-main-bg">
<div class="d-flex justify-content-between align-items-center">
{ADMIN_BREADCRUMBS}
<a href="{ROOT_URL}admin/images/upload" class="btn btn-sm btn-primary">Create</a>
</div>
<div class="row row-cols-1 row-cols-sm-2 row-cols-md-3 g-3">
{LOOP}
<div class="col">
<div class="card h-100 shadow-sm context-other-bg">
<div class="d-flex justify-content-center align-items-center context-other-bg" style="height: 250px;">
<img src="{url}" class="img-fluid p-2" style="max-width: 100%; max-height: 100%; object-fit: contain;">
</div>
<div class="card-body context-third-bg d-flex flex-column">
<div class="flex-grow-1">
<div class="d-flex justify-content-between align-items-center">
<div class="">
<a href="{ROOT_URL}admin/images/view?fileLocation={locationSafe}" class="btn btn-sm btn-outline-primary">View</a>
<a href="{url}" class="btn btn-sm btn-outline-primary" target="_blank">Open</a>
<a href="{ROOT_URL}admin/images/rename?fileLocation={locationSafe}" class="btn btn-sm btn-outline-warning">Rename</a>
<a href="{ROOT_URL}admin/images/delete?fileLocation={locationSafe}" class="btn btn-sm btn-outline-danger">Delete</a>
</div>
<small class="text-muted">{filename}</small>
</div>
</div>
</div>
</div>
</div>
{/LOOP}
</div>
</div>

View File

@ -0,0 +1,35 @@
<div class="context-main-bg context-main p-3">
<legend class="text-center">Rename Image</legend>
<hr>
{ADMIN_BREADCRUMBS}
<form method="post">
<fieldset>
<!-- Name -->
<div class="mb-3 row">
<label for="nickname" class="col-lg-6 col-form-label text-end">Location:</label>
<div class="col-lg-2">
<input type="hidden" class="form-control" name="filelocation" id="filelocation" value="{filelocation}">
<strong>
{filelocation}
</strong>
</div>
</div>
<!-- Forward URL -->
<div class="mb-3 row">
<label for="newname" class="col-lg-6 col-form-label text-end">New filename ( Extensions cannot be modified ):</label>
<div class="col-lg-2">
<input type="text" class="form-control" name="newname" id="newname" required>
</div>
</div>
<!-- Hidden Token -->
<input type="hidden" name="token" value="{TOKEN}">
<!-- Submit Button -->
<div class="text-center">
<button type="submit" name="submit" value="submit" class="btn btn-primary btn-lg">Save</button>
</div>
</fieldset>
</form>
</div>

View File

@ -0,0 +1,29 @@
<div class="container p-4 context-main-bg mb-4 text-center">
<h3 class="mb-4">Image Upload</h3>
<hr>
<div class="row justify-content-center">
<div class="col-md-6">
<form method="post" enctype="multipart/form-data">
<fieldset>
<div class="mb-3 row">
<label for="avatar" class="h4 col-lg-6 col-form-label text-start text-lg-end">Image</label>
<div class="col-lg-6">
<input type="file" class="form-control" name="uploadImage" id="uploadImage">
</div>
</div>
<div class="mb-3 row">
<span class="h4 col-lg-6 col-form-label text-start text-lg-end">Destination Folder</span>
<div class="col-lg-6">
{FOLDER_SELECT}
</div>
</div>
</fieldset>
<input type="hidden" name="token" value="{TOKEN}">
<button name="submit" value="submit" type="submit" class="btn btn-lg btn-primary center-block">Update</button><br>
</form>
</div>
</div>
</div>

View File

@ -0,0 +1,66 @@
<div class="container py-4">
<div class="row justify-content-center">
<div class="col-md-8">
{ADMIN_BREADCRUMBS}
<div class="card shadow">
<!-- Card Header -->
<div class="card-header text-center bg-dark text-white">
<h3 class="card-title mb-0">{filename}</h3>
</div>
<!-- Card Body -->
<div class="card-body">
<div class="row align-items-center">
<!-- User Image -->
<div class="col-md-4 text-center">
<img src="{url}" alt="User Pic" class="img-fluid" style="max-width: 150px;">
</div>
<!-- User Details -->
<div class="col-md-8">
<table class="table table-borderless">
<tbody>
<tr>
<th scope="row">filename</th>
<td>{filename}</td>
</tr>
<tr>
<th scope="row">extension</th>
<td>{extension}</td>
</tr>
<tr>
<th scope="row">fileSize</th>
<td>{fileSize}</td>
</tr>
<tr>
<th scope="row">location</th>
<td>{location}</td>
</tr>
<tr>
<th scope="row">url</th>
<td>{url}</td>
</tr>
<tr>
<th scope="row">folder</th>
<td>{folder}</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<!-- Admin Controls -->
<div class="card-footer text-center">
{ADMIN}
<a href="{ROOT_URL}admin/images/rename?fileLocation={locationSafe}" class="btn btn-warning btn-sm me-2" data-bs-toggle="tooltip" title="Rename image">
<i class="fa fa-fw fa-pencil"></i>
</a>
<a href="{ROOT_URL}admin/images/delete?fileLocation={locationSafe}" class="btn btn-danger btn-sm" data-bs-toggle="tooltip" title="Delete image">
<i class="fa fa-fw fa-trash"></i>
</a>
{/ADMIN}
</div>
</div>
</div>
</div>
</div>

View File

@ -1,12 +1,3 @@
<div class="container py-4 context-main-bg my-2 my-lg-4 mx-2 mx-sm-auto">
<h2 class="text-center mb-4">Create an Account</h2>
<form method="post">

View File

@ -1,65 +1,65 @@
<div class="container p-4 context-main-bg my-4">
<h1 class="mb-4">Frequently Asked Questions</h1>
<div class="m-2 m-lg-4">
<div class="context-main-bg container py-2 my-2 py-lg-4 my-lg-4">
<h2 class="text-center mb-4">Frequently Asked Questions</h2>
<hr>
<!-- Table of Contents -->
<div class="mb-5">
<h2 class="h4">Table of Contents</h2>
<div class="mb-4">
<h2 class="h4 text-primary">General Questions</h2>
<ul class="list-unstyled">
<li><a href="#question1" class="text-decoration-none context-main">Does this work on mobile?</a></li>
<li><a href="#question2" class="text-decoration-none context-main">Does this work on Mac/PC/Linux?</a></li>
<li><a href="#question3" class="text-decoration-none context-main">How do I use the extension on my mobile device?</a></li>
<li><a href="#question4" class="text-decoration-none context-main">How do I add this to my phone like an app?</a></li>
<li><a href="#generalHeading1" class="text-decoration-none context-main" data-bs-toggle="collapse" data-bs-target="#collapse1">Does {SITENAME} work on mobile?</a></li>
<li><a href="#generalHeading2" class="text-decoration-none context-main" data-bs-toggle="collapse" data-bs-target="#collapse2">Does {SITENAME} work on Mac/PC/Linux?</a></li>
<li><a href="#generalHeading3" class="text-decoration-none context-main" data-bs-toggle="collapse" data-bs-target="#collapse3">How much does {SITENAME} cost?</a></li>
</ul>
</div>
<!-- Accordion for Questions -->
<div class="accordion" id="faqAccordion">
<!-- Accordion for General Questions -->
<div class="accordion mb-5" id="generalAccordion">
<div class="accordion-item">
<h2 class="accordion-header context-second-bg" id="heading1">
<button class="accordion-button context-main context-main-bg" type="button" data-bs-toggle="collapse" data-bs-target="#collapse1" aria-expanded="true" aria-controls="collapse1">
Does this work on mobile?
<h2 class="accordion-header context-second-bg" id="generalHeading1">
<button class="accordion-button context-main context-main-bg collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#collapse1" aria-expanded="false" aria-controls="collapse1">
Does {SITENAME} work on mobile?
</button>
</h2>
<div id="collapse1" class="accordion-collapse collapse show" aria-labelledby="heading1" data-bs-parent="#faqAccordion">
<div class="ml-5 accordion-body context-main context-main-bg" id="question1">
Yes, this works seamlessly on mobile devices.
<div id="collapse1" class="accordion-collapse collapse" aria-labelledby="generalHeading1" data-bs-parent="#generalAccordion">
<div class="ml-5 accordion-body context-main context-other-bg" id="general1">
<span class="text-lead">
Yes, {SITENAME} is a responsive web-app and it works seamlessly on mobile devices.
The front-end is powered by Bootstrap 5.2.3 the original design system used by twitter.
In addition to the powerful tools behind the scenes, every page on {SITENAME} has been tested to work on devices of all sizes.
</span>
</div>
</div>
</div>
<div class="accordion-item">
<h2 class="accordion-header context-main context-second-bg" id="heading2">
<h2 class="accordion-header context-main context-second-bg" id="generalHeading2">
<button class="accordion-button context-main context-main-bg collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#collapse2" aria-expanded="false" aria-controls="collapse2">
Does this work on Mac/PC/Linux?
Does {SITENAME} work on Mac/PC/Linux?
</button>
</h2>
<div id="collapse2" class="accordion-collapse collapse" aria-labelledby="heading2" data-bs-parent="#faqAccordion">
<div class="accordion-body context-main context-main-bg" id="question2">
Yes, it is compatible with Mac, PC, and Linux platforms.
<div id="collapse2" class="accordion-collapse collapse" aria-labelledby="generalHeading2" data-bs-parent="#generalAccordion">
<div class="accordion-body context-main context-other-bg" id="general2">
<span class="text-lead">
Yes, in more ways than one.
Since {SITENAME} is designed as a responsive web-app, its accessible from any browser connected to the internet.
Behind the scenes, the app can be installed on any server hardware and has been tested with both nginx and Apache web-servers.
</span>
</div>
</div>
</div>
<div class="accordion-item">
<h2 class="accordion-header context-main context-second-bg" id="heading3">
<h2 class="accordion-header context-main context-second-bg" id="generalHeading3">
<button class="accordion-button context-main context-main-bg collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#collapse3" aria-expanded="false" aria-controls="collapse3">
How do I use the extension on my mobile device?
How much does {SITENAME} cost?
</button>
</h2>
<div id="collapse3" class="accordion-collapse collapse" aria-labelledby="heading3" data-bs-parent="#faqAccordion">
<div class="accordion-body context-main context-main-bg" id="question3">
To use the extension on mobile, open your browser, install the extension, and follow the setup instructions.
</div>
</div>
</div>
<div class="accordion-item">
<h2 class="accordion-header context-main context-second-bg" id="heading4">
<button class="accordion-button context-main context-main-bg collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#collapse4" aria-expanded="false" aria-controls="collapse4">
How do I add this to my phone like an app?
</button>
</h2>
<div id="collapse4" class="accordion-collapse collapse" aria-labelledby="heading4" data-bs-parent="#faqAccordion">
<div class="accordion-body context-main context-main-bg" id="question4">
You can add it to your phone by installing the app or creating a shortcut from your browser to your home screen.
<div id="collapse3" class="accordion-collapse collapse" aria-labelledby="generalHeading3" data-bs-parent="#generalAccordion">
<div class="accordion-body context-main context-other-bg" id="general3">
<span class="text-lead">
{SITENAME} is open source and available free of charge through <a href="{ROOT_URL}libraries/ttp/git" class="text-decoration-none">GitLab</a> and <a href="{ROOT_URL}libraries/ttp/packagist" class="text-decoration-none">Packagist</a>.
The developer behind the project is <a href="https://joeykimsey.com/" class="text-decoration-none">Joey Kimsey</a> and he can be contacted through his website for development services.
</span>
</div>
</div>
</div>
</div>

View File

@ -0,0 +1,17 @@
Folder: <strong>{FOLDER_SELECT_ROOT}</strong>
<div class="list-group mx-0 mx-auto">
{LOOP}
<label class="list-group-item d-flex justify-content-between align-items-center">
<div class="d-flex align-items-center">
<input class="form-check-input me-2" type="radio" name="folderSelect" value="{location}" data-bs-toggle="collapse" data-bs-target="#top-{folderName}">
<span>
{folderName}
</span>
</div>
{folderexpand}
</label>
<div id="top-{folderName}" class="collapse">
{subdirs}
</div>
{/LOOP}
</div>

View File

@ -0,0 +1,6 @@
<label class="list-group-item">
<input class="form-check-input me-2" type="radio" name="folderSelect" value="{location}">
<span>
{folderName}
</span>
</label>

View File

@ -0,0 +1,14 @@
{LOOP}
<label class="list-group-item d-flex justify-content-between align-items-center">
<div class="d-flex align-items-center">
<input class="form-check-input me-2" type="radio" name="folderSelect" value="{location}" data-bs-toggle="collapse" data-bs-target="#{parentfolderName}-{folderName}">
<span>
{folderName}
</span>
</div>
{folderexpand}
</label>
<div id="{parentfolderName}-{folderName}" class="collapse">
{subdirs}
</div>
{/LOOP}

View File

@ -10,7 +10,7 @@
<form method="post">
<input type="hidden" name="token" value="{TOKEN}">
<div class="text-center">
<button class="btn btn-lg btn-primary center-block" type="submit" name="submit" value="submit">Generate</button><br>
<button class="btn btn-lg btn-primary center-block mb-3" type="submit" name="submit" value="submit">Generate</button><br>
</div>
</form>
</div>

View File

@ -8,7 +8,7 @@
<div class="text-center my-4">
<form method="post">
<input type="hidden" name="token" value="{TOKEN}">
<button class="btn btn-lg btn-primary center-block" type="submit" name="submit" value="submit">I Agree</button><br>
<button class="btn btn-lg btn-primary center-block mb-3" type="submit" name="submit" value="submit">I Agree</button><br>
</form>
</div>
</div>

View File

@ -15,7 +15,7 @@
<div class="text-center">
<form method="post">
<input type="hidden" name="token" value="{TOKEN}">
<button class="btn btn-lg btn-primary center-block" type="submit" name="submit" value="submit">Check</button><br>
<button class="btn btn-lg btn-primary center-block mb-3" type="submit" name="submit" value="submit">Check</button><br>
</form>
</div>
</div>

View File

@ -3,13 +3,13 @@
<div class="col-md-10 text-center">
<div class="context-main-bg my-3 pb-3 rounded">
{installer-nav}
<h1 class="mt-4">Welcome to The Tempus Project Installer.</h1>
<p>
<h1 class="mt-5 mb-4">Welcome to The Tempus Project Installer.</h1>
<p class="lead p-3">
This installer will guide you through the process of installing and configuring The Tempus Project. Do not forget to delete this file once you have completed installation.
</p>
<form method="post">
<input type="hidden" name="token" value="{TOKEN}">
<button class="btn btn-lg btn-primary center-block" type="submit" name="submit" id="submit" value="submit">Begin Installation</button><br>
<button class="btn btn-lg btn-primary center-block mb-3" type="submit" name="submit" id="submit" value="submit">Begin Installation</button><br>
</form>
</div>
</div>

View File

@ -1,23 +1,26 @@
<div class="col-8 mx-auto p-4 rounded shadow-sm context-main-bg my-4">
<div class="m-2 m-lg-4">
<div class="col-12 mx-5 col-sm-10 col-lg-8 mx-auto p-4 rounded shadow-sm context-main-bg">
<h2 class="text-center mb-4">Getting Started with {SITENAME}</h2>
<p class="lead">
<p class="lead text-center text-lg-start">
{SITENAME} has been open source for many years now. The hopes and intentions for it were always to give others a leg-up to get started building web-apps like i wish i had as a kid.
There were so many tutorials and ideas, expansions and plans for the project.
Unfortunately no person is given unlimited time to accomplish their dreams and over the years the idea for a huge repository for learning and education has taken a back seat.
</p>
<p>
<p class="text-center text-lg-start">
At this time, the best recommendation available is to contact us for more information.
The site here is actively maintained so feel free to utilize any of our available resources for contact.
In addition to the site here, you can contact the lead developer (me) directly through <a href="https://joeykimsey.com">JoeyKimsey.com</a>.
</p>
<p class="text-muted">
<p class="text-muted text-center text-lg-start">
Right now, this entire system was built and managed by myself. As stated, I have used my own version of this for years, but translating it to a publicly available product is not a 1-to-1 job. There may be bugs or issues encountered while you use the product. I can't guarantee a fix for every need in every case immediately, but I do actively keep track of bugs and work hard to ensure everyone has a great experience using the app.
</p>
<p>
<p class="text-center text-lg-start">
If you encounter any bugs, feel free to report them <a href="/bugreport" class="text-decoration-none">here</a>. Likewise, there are forms for feedback, reviews, suggestions, and a general contact form. Thanks for taking the time to check out the product!
</p>
<div class="text-center mt-4 pb-4">
{loggedin}<a href="/bugreport" class="btn btn-primary btn-lg px-5">Report a Bug</a>{/loggedin}
<a href="/contact" class="btn btn-outline-secondary btn-lg px-5 ms-3">Contact Us</a>
{loggedin}
<a href="/bugreport" class="btn btn-primary px-3 btn-lg m-2">Report a Bug</a>
{/loggedin}
<a href="/contact" class="btn btn-outline-primary px-3 m-2 btn-lg">Contact Us</a>
</div>
</div>

View File

@ -274,14 +274,14 @@ class TheTempusProject extends Bedrock {
"logo" => [
"type" => "file",
"pretty" => "Site Logo (Used mostly in emails)",
"default" => "images/logo.png",
"value" => "images/logo.png",
"default" => "images/logoWhite.png",
"value" => "images/logoWhite.png",
],
"logoLarge" => [
"type" => "file",
"pretty" => "Large Site Logo (Used mostly when sharing images on social media)",
"default" => "images/logoLarge.png",
"value" => "images/logoLarge.png",
"default" => "images/logoLarge.jpg",
"value" => "images/logoLarge.jpg",
],
"name" => [
"type" => "text",
@ -585,6 +585,26 @@ class TheTempusProject extends Bedrock {
Redirect::external($route->forwarded_url);
}
}
if ( 'home/login' !== $url ) {
if ( Config::getValue( 'maintenance/enabled' ) ) {
if ( ! self::$isLoggedIn ) {
Session::flash( 'notice', Config::getValue( 'maintenance/maintenanceMessage' ) );
Redirect::to( 'home/login' );
}
if ( self::$activeGroup->ID != 1 ) {
if ( empty( self::$activeGroup->perms['maintenanceAccess'] ) ) {
Session::flash( 'notice', Config::getValue( 'maintenance/maintenanceMessage' ) );
Redirect::to( 'home/login' );
}
}
}
} elseif ( Config::getValue( 'maintenance/enabled' ) ) {
if ( ! self::$isLoggedIn ) {
Issues::add( 'notice', Config::getValue( 'maintenance/maintenanceMessage' ) );
}
}
parent::load();
}

View File

@ -25,9 +25,9 @@
{
"components/jquery": "1.9.*",
"fortawesome/font-awesome": "4.7",
"thetempusproject/bedrock": "1.1.2",
"thetempusproject/canary": "1.0.7",
"thetempusproject/houdini": "2.0.3",
"thetempusproject/bedrock": "1.1.5",
"thetempusproject/canary": "1.0.9",
"thetempusproject/houdini": "2.0.5",
"twbs/bootstrap": "5.2.3"
},
"autoload":

48
composer.lock generated
View File

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "d85438494def4dfe5294034363151ee2",
"content-hash": "63640223834a7eeb9b4a211cd45cc6df",
"packages": [
{
"name": "components/jquery",
@ -303,28 +303,28 @@
},
{
"name": "thetempusproject/bedrock",
"version": "1.1.2",
"version": "1.1.5",
"source": {
"type": "git",
"url": "https://git.thetempusproject.com/the-tempus-project/bedrock",
"reference": "394e7520941a706fadc19d0c6606a78306289b74"
"reference": "39d350df061b4c69266bbbe152976cf7254e4c08"
},
"require": {
"php": ">=8.1.0",
"thetempusproject/canary": "1.0.7",
"thetempusproject/canary": "1.0.9",
"thetempusproject/hermes": "1.0.5",
"thetempusproject/houdini": "2.0.3"
"thetempusproject/houdini": "2.0.5"
},
"type": "library",
"autoload": {
"files": [
"Config/constants.php",
"Bin/Bedrock.php"
"config/constants.php",
"bin/bedrock.php"
],
"psr-4": {
"TheTempusProject\\Bedroock\\Classes\\": "Classes",
"TheTempusProject\\Bedroock\\Functions\\": "Functions"
}
"classmap": [
"classes",
"functions"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
@ -346,15 +346,15 @@
"php",
"thetempusproject"
],
"time": "2025-02-02T23:42:29+00:00"
"time": "2025-02-04T12:20:56+00:00"
},
{
"name": "thetempusproject/canary",
"version": "1.0.7",
"version": "1.0.9",
"source": {
"type": "git",
"url": "https://git.thetempusproject.com/the-tempus-project/canary",
"reference": "9c48e66bf54e63ba5ad2d4af90306c87b69f7048"
"reference": "77cef522e9919573836901eb82b59b20f453fb61"
},
"require": {
"php": ">=8.1.0"
@ -362,12 +362,12 @@
"type": "library",
"autoload": {
"files": [
"Config/constants.php",
"Bin/Canary.php"
"config/constants.php",
"bin/canary.php"
],
"psr-4": {
"TheTempusProject\\Canary\\Classes\\": "Classes"
}
"classmap": [
"classes"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
@ -390,7 +390,7 @@
"thetempusproject",
"tools"
],
"time": "2025-02-02T23:02:51+00:00"
"time": "2025-02-04T12:16:29+00:00"
},
{
"name": "thetempusproject/hermes",
@ -438,15 +438,15 @@
},
{
"name": "thetempusproject/houdini",
"version": "2.0.3",
"version": "2.0.5",
"source": {
"type": "git",
"url": "https://git.thetempusproject.com/the-tempus-project/houdini",
"reference": "4c9c9b60233c4dd7a366758c8436560098761eb5"
"reference": "2c7538471ab1f900048ccdb2c71d6bf2bee975e0"
},
"require": {
"php": ">=8.1.0",
"thetempusproject/canary": "1.0.7",
"thetempusproject/canary": "1.0.9",
"thetempusproject/hermes": "1.0.5"
},
"type": "library",
@ -478,7 +478,7 @@
"thetempusproject",
"tools"
],
"time": "2025-02-02T23:31:11+00:00"
"time": "2025-02-04T12:19:25+00:00"
},
{
"name": "twbs/bootstrap",

View File

@ -170,7 +170,7 @@ class Install extends Controller {
if ( Input::exists( 'submit' ) ) {
Issues::add( 'error', [ 'There was an error with the Installation.' => Check::userErrors() ] );
}
Components::set( 'TERMS', Views::simpleView( 'terms' ) );
Components::set( 'TERMS', Views::simpleView( 'auth.terms' ) );
Views::view( 'install.terms' );
}
@ -194,23 +194,14 @@ class Install extends Controller {
*/
public function configure() {
if ( Forms::Check( 'installConfigure' ) ) {
$logo = 'images/logo.png';
$logoLarge = 'images/logoLarge.png';
$logo = $baseConfig['main']['template']['default'];
if ( Input::exists( 'logo' ) && Upload::image( 'logo', 'System' ) ) {
$logo = 'Uploads/Images/System/' . Upload::last();
}
TheTempusProject::$activeConfig->load( BEDROCK_CONFIG_JSON );
$baseConfig = TheTempusProject::$configMatrix;
$baseConfig['main']['logo']['value'] = $logo;
$baseConfig['main']['logoLarge']['value'] = $logoLarge;
$baseConfig['main']['name']['value'] = Input::postNull( 'siteName' );
$baseConfig['main']['template']['value'] = $baseConfig['main']['template']['default'];
$baseConfig['main']['tokenEnabled']['value'] = $baseConfig['main']['tokenEnabled']['default'];
$baseConfig['main']['registrationEnabled']['value'] = $baseConfig['main']['registrationEnabled']['default'];
$baseConfig['main']['loginLimit']['value'] = $baseConfig['main']['loginLimit']['default'];
$baseConfig['main']['loginTimer']['value'] = $baseConfig['main']['loginTimer']['default'];
$baseConfig['uploads']['images']['value'] = $baseConfig['uploads']['images']['default'];
$baseConfig['uploads']['maxImageSize']['value'] = $baseConfig['uploads']['maxImageSize']['default'];
$baseConfig['database']['dbEnabled']['value'] = $baseConfig['database']['dbEnabled']['default'];
$baseConfig['database']['dbHost']['value'] = Input::postNull( 'dbHost' );
$baseConfig['database']['dbMaxQuery']['value'] = $baseConfig['database']['dbMaxQuery']['default'];