hfkfhkfhgjkuhgfkjfghkj

This commit is contained in:
Local Dev
2025-02-03 12:03:51 -05:00
commit fd36f0f4bf
302 changed files with 22625 additions and 0 deletions

16
.gitattribute Normal file
View File

@ -0,0 +1,16 @@
# Force LF for all text files
* text=auto eol=lf
# Ensure specific file types always use LF
*.sh text eol=lf
*.js text eol=lf
*.php text eol=lf
*.html text eol=lf
*.css text eol=lf
*.json text eol=lf
*.md text eol=lf
# Keep binary files untouched
*.png binary
*.jpg binary
*.gif binary

67
.gitignore vendored Executable file
View File

@ -0,0 +1,67 @@
# Windows image file caches
Thumbs.db
ehthumbs.db
# Folder config file
Desktop.ini
# Recycle Bin used on file shares
$RECYCLE.BIN/
# Windows Installer files
*.cab
*.msi
*.msm
*.msp
# Windows shortcuts
*.lnk
# OSX
.DS_Store
.AppleDouble
.LSOverride
# Thumbnails
._*
# Files that might appear in the root of a volume
.DocumentRevisions-V100
.fseventsd
.Spotlight-V100
.TemporaryItems
.Trashes
.VolumeIcon.icns
# Directories potentially created on remote AFP share
.AppleDB
.AppleDesktop
Network Trash Folder
Temporary Items
.apdisk
# keep specific directories
!uploads/images/.gitignore
!bin/cli/.gitignore
# keep main directories
!css/.gitignore
!vendor/.gitignore
# SublimeText
*.sublime-project
*.sublime-workspace
# TheTempusProject Specific
.htaccess
app/config/*
!app/config/constants.php
uploads/images/*
logs/*
.vscode/
mail.log
vendor/canary/logs/*
.env
components/*
mailhog.log
uploads/*

74
.gitlab-ci.yml Executable file
View File

@ -0,0 +1,74 @@
stages:
- prepare
- build
- test
- update
- deploy
variables:
TIMEZONE: "America/New_York" # For the system in general
DATE_TIMEZONE: ${TIMEZONE} # For PHP
GIT_DEPTH: 1
GITLAB_API_URL: ${CI_API_V4_URL}
TARGET_BRANCH: ${CI_COMMIT_REF_NAME} # This is the branch chosen in the `Pipeline Schedule`
TARGET_REMOTE: "https://${GITLAB_USERNAME}:${GITLAB_ACCESS_TOKEN}@${CI_SERVER_HOST}/${CI_PROJECT_NAMESPACE}/${CI_PROJECT_NAME}.git"
# These could/should be overridden in an extending job:
UPDATE_BRANCH_PREFIX: "update_PHP_deps_" # Used for the update branch name, it will be followed by the datetime
GIT_USER: "DependBot" # Used for the update commit
GIT_EMAIL: "webmaster@thetempusproject.com" # Used for the update commit
GITLAB_USERNAME: "root" # Used for pushing the new branch and opening the MR
GITLAB_ACCESS_TOKEN: "glpat-PKEmivGtBfbz4DVPdhzk" # Used for pushing the new branch and opening the MR
MERGE_IF_SUCCESSFUL: "true" # Set to true, to merge automatically if the pipeline succeeds
SECONDS_BETWEEN_POOLING: 10 # Nbr of seconds between checking if the MR pipeline is successful, so then it will merge
JOB_GIT_FLAGS: ""
JOB_CURL_FLAGS: ""
JOB_COMPOSER_FLAGS: ""
composer_update:
stage: update
rules:
- if: '$CI_COMMIT_BRANCH == "main"'
image: composer:latest
interruptible: true # allows to stop the job if a newer pipeline starts, saving resources and allowing new jobs to start because job concurrency is limited
script:
- git ${JOB_GIT_FLAGS} fetch origin ${TARGET_BRANCH}
- git ${JOB_GIT_FLAGS} checkout ${TARGET_BRANCH}
- git reset --hard origin/main
- git pull --allow-unrelated-histories
- export DATE_TIME="$(date '+%Y%m%d%H%M%S')"
- export MR_BRANCH="${UPDATE_BRANCH_PREFIX}${DATE_TIME}"
- git ${JOB_GIT_FLAGS} checkout -b "${MR_BRANCH}"
- composer update ${JOB_COMPOSER_FLAGS}
- if [ "$(git diff)" == "" ]; then echo "No updates needed!"; exit 0; fi
- export TITLE="Update PHP dependencies [${DATE_TIME}]"
- git ${JOB_GIT_FLAGS} commit -a -m "${TITLE}"
- git ${JOB_GIT_FLAGS} push "${TARGET_REMOTE}" "${MR_BRANCH}"
artifacts:
paths:
- vendor/
cache:
key: ${CI_COMMIT_REF_SLUG}
paths:
- vendor/
prepare:
stage: prepare
script:
- echo "Preparing environment..."
build:
stage: build
script:
- echo "Building the project..."
test:
stage: test
script:
- echo "Running tests..."
deploy:
stage: deploy
script:
- echo "Deploying the project..."

View File

@ -0,0 +1,34 @@
<?php
/**
* app/classes/admin_controller.php
*
* This is the base admin controller. Every other admin controller should
* extend this class.
*
* @version 3.0
* @author Joey Kimsey <Joey@thetempusproject.com>
* @link https://TheTempusProject.com
* @license https://opensource.org/licenses/MIT [MIT LICENSE]
*/
namespace TheTempusProject\Classes;
use TheTempusProject\Houdini\Classes\Template;
use TheTempusProject\Houdini\Classes\Filters;
use TheTempusProject\Houdini\Classes\Issues;
use TheTempusProject\TheTempusProject as App;
use TheTempusProject\Hermes\Functions\Redirect;
use TheTempusProject\Bedrock\Functions\Session;
class AdminController extends Controller {
public function __construct() {
parent::__construct();
if ( !App::$isAdmin ) {
Session::flash( 'error', 'You do not have permission to view this page.' );
return Redirect::home();
}
Template::noFollow();
Template::noIndex();
Template::setTemplate( 'admin' );
Filters::add( 'logMenu', '#<ul id="log-menu" class="collapse">#is', '<ul id="log-menu" class="">', true );
}
}

137
app/classes/api_controller.php Executable file
View File

@ -0,0 +1,137 @@
<?php
/**
* app/classes/api_controller.php
*
* This is the base api controller. Every other api controller should
* extend this class.
*
* @version 3.0
* @author Joey Kimsey <Joey@thetempusproject.com>
* @link https://TheTempusProject.com
* @license https://opensource.org/licenses/MIT [MIT LICENSE]
*/
namespace TheTempusProject\Classes;
use TheTempusProject\Houdini\Classes\Template;
use TheTempusProject\TheTempusProject as App;
use TheTempusProject\Hermes\Functions\Redirect;
use TheTempusProject\Bedrock\Functions\Session;
use TheTempusProject\Bedrock\Classes\Config;
use TheTempusProject\Models\Token;
use TheTempusProject\Canary\Bin\Canary as Debug;
use TheTempusProject\Houdini\Classes\Views;
class ApiController extends Controller {
protected static $canAccessApplicationApi = false;
protected static $canAccessUserApi = false;
protected static $canAccessAuthenticationApi = false;
protected static $authToken;
public function __construct( $secure = true ) {
header('Content-Type: application/json; charset=utf-8');
parent::__construct();
Template::setTemplate( 'api' );
Template::noFollow();
Template::noIndex();
$res = $this->verifyApiRequest();
if ( $secure && ! $this->canUseApi() ) {
exit( $res );
}
}
protected function canUseApi() {
return ( $this->canUseUserApi() || $this->canUseAppApi() || $this->canUseAuthApi() );
}
protected function canUseUserApi() {
$apiEnabled = Config::getValue( 'api/apiAccessApp' );
if ( empty( $apiEnabled ) ) {
return false;
}
return self::$canAccessUserApi;
}
protected function canUseAppApi() {
$apiEnabled = Config::getValue( 'api/apiAccessPersonal' );
if ( empty( $apiEnabled ) ) {
return false;
}
return self::$canAccessApplicationApi;
}
protected function canUseAuthApi() {
return self::$canAccessAuthenticationApi;
}
public function verifyApiRequest() {
$tokens = new Token;
$secret = null;
$bearer_token = $this->getBearerToken();
if ( ! empty( $bearer_token ) ) {
$token = $tokens->findByToken( $bearer_token );
} else {
$secret = $this->getSecretToken();
if ( empty( $secret ) ) {
return Views::simpleView( 'api.response', ['response' => json_encode( [ 'error' => 'invalid secret' ], true )]);
}
$token = $tokens->findBySecret( $secret );
}
if ( empty( $token ) ) {
return Views::simpleView( 'api.response', ['response' => json_encode( [ 'error' => 'invalid token' ], true )]);
}
self::$authToken = $token;
if ( $token->expiresAt <= time() && empty( $secret ) ) {
return Views::simpleView( 'api.response', ['response' => json_encode( [ 'error' => 'token expired' ], true )]);
}
if ( $token->expiresAt <= time() ) {
self::$canAccessAuthenticationApi = true;
return;
}
if ( $token->token_type == 'app' ) {
self::$canAccessApplicationApi = true;
return;
}
if ( $token->token_type == 'user' ) {
self::$canAccessUserApi = true;
return;
}
return $result;
}
public function getSecretToken() {
$headers = $this->getAuthorizationHeader();
if ( ! empty( $headers ) ) {
if ( preg_match( '/Secret\s(\S+)/', $headers, $matches ) ) {
return $matches[1];
}
}
return null;
}
public function getBearerToken() {
$headers = $this->getAuthorizationHeader();
if ( ! empty( $headers ) ) {
if ( preg_match( '/Bearer\s(\S+)/', $headers, $matches ) ) {
return $matches[1];
}
}
return null;
}
public function getAuthorizationHeader(){
$headers = null;
if ( isset( $_SERVER['Authorization'] ) ) {
$headers = trim( $_SERVER["Authorization"] );
} elseif ( isset( $_SERVER['HTTP_AUTHORIZATION'] ) ) {
$headers = trim( $_SERVER["HTTP_AUTHORIZATION"] );
} elseif ( function_exists( 'apache_request_headers' ) ) {
$requestHeaders = apache_request_headers();
$requestHeaders = array_combine(array_map('ucwords', array_keys($requestHeaders)), array_values($requestHeaders));
if ( isset( $requestHeaders['Authorization'] ) ) {
$headers = trim( $requestHeaders['Authorization'] );
}
}
return $headers;
}
}

115
app/classes/config.php Executable file
View File

@ -0,0 +1,115 @@
<?php
/**
* app/classes/config.php
*
* This class handles all the hard-coded configurations.
*
* @version 3.0
* @author Joey Kimsey <Joey@thetempusproject.com>
* @link https://TheTempusProject.com
* @license https://opensource.org/licenses/MIT [MIT LICENSE]
*/
namespace TheTempusProject\Classes;
use TheTempusProject\Houdini\Classes\Forms;
use TheTempusProject\Houdini\Classes\Template;
use TheTempusProject\Canary\Bin\Canary as Debug;
use TheTempusProject\Bedrock\Functions\Check;
use TheTempusProject\Bedrock\Functions\Input;
use TheTempusProject\Bedrock\Classes\Config as BedrockConfig;
class Config extends BedrockConfig {
public static function getFieldEditHtml( $name, $includeProtected = false ) {
$node = self::get( $name );
if ( empty( $node ) ) {
return;
}
if ( true === $node['protected'] ) {
return;
}
$fieldname = str_ireplace( '/', '-', $name );
$html = '';
$fieldHtml = '';
switch ( $node['type'] ) {
case 'radio':
case 'bool':
case 'boolean':
$fieldHtml = Forms::getSwitchHtml( $fieldname, $node['value'] );
break;
case 'select':
$fieldHtml = Forms::getSelectHtml( $fieldname, $options, $node['value'] );
break;
case 'block':
$fieldHtml = Forms::getTextBlockHtml( $fieldname, $node['value'] );
break;
case 'text':
case 'url':
$fieldHtml = Forms::getTextHtml( $fieldname, $node['value'] );
break;
case 'checkbox':
$fieldHtml = Forms::getCheckboxHtml( $fieldname, $node['value'] );
break;
case 'timezone':
$fieldHtml = Forms::getTimezoneHtml( $node['value'] );
break;
case 'file':
$fieldHtml = Forms::getFileHtml( $fieldname );
break;
case 'customSelect':
if ( empty( $options ) ) {
$options = '{' . $fieldname . '-options}';
}
$fieldHtml = Forms::getSelectHtml( $fieldname, $options, $node['value'] );
break;
}
$html .= '<div class="mb-3 row">';
$html .= '<label for="' . $fieldname . '" class="col-lg-3 col-form-label text-end">' . $node['pretty'] . '</label>';
$html .= '<div class="col-lg-6">';
$html .= $fieldHtml;
$html .= '</div>';
if ( 'file' === $node['type'] ) {
$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="configured image" src="{ROOT_URL}' . $node['value'] . '" class="img-circle img-fluid p-2 avatar-125">';
$html .= '</div>';
}
$html .= '</div>';
return Template::parse( $html );
}
public static function getCategoryEditHtml( $category ) {
$html = '';
if ( self::$config === false ) {
Debug::warn( 'Config not loaded.' );
return;
}
if ( empty( self::$config[$category] ) ) {
Debug::warn( "Config category not found: $category" );
return;
}
$categoryHeader = '<div class=""><h3 class="text-center">' . ucfirst( $category ) . ':</h3><hr>';
foreach ( self::$config[$category] as $field => $node ) {
$html .= self::getFieldEditHtml( $category . '/' . $field );
}
if ( !empty( $html ) ) {
$html = $categoryHeader . $html . '</div>';
}
return $html;
}
public static function getEditHtml() {
if ( self::$config === false ) {
Debug::warn( 'Config not loaded.' );
return;
}
$html = '';
foreach ( self::$config as $category => $fields ) {
$html .= self::getCategoryEditHtml( $category );
}
return $html;
}
}

43
app/classes/controller.php Executable file
View File

@ -0,0 +1,43 @@
<?php
/**
* app/classes/controller.php
*
* This is the main controller class.
*
* @version 3.0
* @author Joey Kimsey <Joey@thetempusproject.com>
* @link https://TheTempusProject.com
* @license https://opensource.org/licenses/MIT [MIT LICENSE]
*/
namespace TheTempusProject\Classes;
use TheTempusProject\Bedrock\Classes\Controller as BedrockController;
use TheTempusProject\Houdini\Classes\Template;
use TheTempusProject\Bedrock\Classes\Pagination;
use TheTempusProject\TheTempusProject as App;
use TheTempusProject\Models\User;
use TheTempusProject\Models\Sessions;
use TheTempusProject\Bedrock\Functions\Token;
use TheTempusProject\Houdini\Classes\Components;
class Controller extends BedrockController {
public static $user;
public static $session;
public static $pagination;
public function __construct() {
parent::__construct();
self::$session = new Sessions;
self::$user = new User;
self::$pagination = Pagination::generate();
if ( ! empty( App::$activePrefs ) ) {
self::$pagination::updatePrefs( App::$activePrefs['pageLimit'] );
}
new Template;
Template::setTemplate( 'default' );
}
public function __destruct() {
parent::__destruct();
}
}

144
app/classes/database_model.php Executable file
View File

@ -0,0 +1,144 @@
<?php
/**
* app/classes/database_model.php
*
* This is the main TempusProject database model.
*
* @version 3.0
* @author Joey Kimsey <Joey@thetempusproject.com>
* @link https://TheTempusProject.com
* @license https://opensource.org/licenses/MIT [MIT LICENSE]
*/
namespace TheTempusProject\Classes;
use TheTempusProject\Bedrock\Classes\DatabaseModel as BedrockDatabaseModel;
use TheTempusProject\Bedrock\Functions\Check;
use TheTempusProject\Canary\Bin\Canary as Debug;
use TheTempusProject\Models\Log;
class DatabaseModel extends BedrockDatabaseModel {
public $preferenceMatrix;
public $permissionMatrix;
public static $installFlags = MODEL_INSTALL_FLAGS;
protected static $user;
protected static $log;
protected static $group;
protected static $session;
protected static $message;
protected static $routes;
public function __construct() {
parent::__construct();
}
public function uninstall() {
Debug::log( 'Uninstalling Model: ' . get_class($this) );
parent::uninstall();
$this->uninstallPreferences();
$this->uninstallPermissions();
return true;
}
public function installPreferences() {
if ( empty( $this->preferenceMatrix ) ) {
Debug::log( 'preferenceMatrix is empty' );
return true;
}
$prefs = new Preferences();
foreach ( $this->preferenceMatrix as $name => $details ) {
$prefs->add( $name, $details );
}
return $prefs->save( true );
}
public function uninstallPreferences() {
if ( empty( $this->preferenceMatrix ) ) {
Debug::log( 'preferenceMatrix is empty' );
return true;
}
$prefs = new Preferences();
foreach ( $this->preferenceMatrix as $name => $details ) {
$prefs->remove( $name, true );
}
}
public function installPermissions() {
if ( empty( $this->permissionMatrix ) ) {
Debug::log( 'permissionMatrix is empty' );
return true;
}
$perms = new Permissions();
foreach ( $this->permissionMatrix as $name => $details ) {
$perms->add( $name, $details );
}
return $perms->save( true );
}
public function uninstallPermissions() {
$perms = new Permissions();
if ( empty( $this->permissionMatrix ) ) {
Debug::log( 'permissionMatrix is empty' );
return true;
}
foreach ( $this->permissionMatrix as $name => $details ) {
$perms->remove( $name, true );
}
}
public function delete( $idArray ) {
if ( empty( self::$log ) ) {
self::$log = new Log;
}
if ( !is_array( $idArray ) ) {
$idArray = [ $idArray ];
}
foreach ( $idArray as $id ) {
if ( !Check::id( $id ) ) {
Debug::info( "invalid ID: $id." );
$error = true;
continue;
}
$result = parent::delete( $id );
if ( true !== $result ) {
Debug::info( ucfirst( $this->tableName ) . " did not delete properly: $id" );
$error = true;
continue;
}
self::$log->admin( 'Deleted ' . ucfirst( $this->tableName ) . ": $id" );
Debug::info( ucfirst( $this->tableName ) . " successfully deleted: $id" );
}
if ( !empty( $error ) ) {
Debug::error( 'One or more rows were not deleted.' );
return false;
}
return true;
}
public function install( $options ) {
Debug::log( 'Installing Database Model');
$module_data = [];
$errors = [];
foreach ( self::$installFlags as $flag_name ) {
if ( empty( $options[$flag_name] ) ) {
continue;
}
$result = $this->$flag_name();
if ( empty( $result ) ) {
$errors[] = ['errorInfo' => get_class($this) . " Failed to execute $flag_name properly."];
$module_data[ $flag_name ] = INSTALL_STATUS_FAIL;
continue;
}
if ( 'installResources' === $flag_name ) {
$module_data['installedResources'] = $result;
}
$module_data[ $flag_name ] = INSTALL_STATUS_SUCCESS;
continue;
}
return [ $module_data, $errors ];
}
}

212
app/classes/email.php Executable file
View File

@ -0,0 +1,212 @@
<?php
/**
* app/classes/email.php
*
* This is our class for constructing and sending various kinds of emails.
*
* @version 3.0
* @author Joey Kimsey <Joey@thetempusproject.com>
* @link https://TheTempusProject.com
* @license https://opensource.org/licenses/MIT [MIT LICENSE]
*/
namespace TheTempusProject\Classes;
use TheTempusProject\Houdini\Classes\Template;
use TheTempusProject\Houdini\Classes\Views;
use TheTempusProject\Hermes\Functions\Route as Routes;
use TheTempusProject\Canary\Bin\Canary as Debug;
class Email {
private static $header = null;
private static $subject = null;
private static $title = null;
private static $message = null;
private static $unsub = false;
private static $useTemplate = false;
private static $footer = null;
private static $debug = false;
/**
* Sends pre-constructed email templates. Useful for modifying the
* entire theme or layout of the system generated emails.
*
* @param string $email - The email you are sending to.
* @param string $type - The template you wish to send.
* @param string|array $params - Any special parameters that may be required from your individual email template.
*
* @return bool
*/
public static function send( $email, $type, $params = null, $flags = null ) {
if ( !empty( $flags ) ) {
if ( is_array( $flags ) ) {
foreach ( $flags as $key => $value ) {
switch ( $key ) {
case 'template':
if ( $value == true ) {
self::$useTemplate = true;
}
break;
case 'unsubscribe':
if ( $value == true ) {
self::$unsub = true;
}
break;
case 'debug':
if ( $value == true ) {
self::$debug = false;
}
break;
}
}
}
}
self::build();
switch ( $type ) {
case 'debug':
self::$subject = 'Please Confirm your email at {SITENAME}';
self::$title = 'Almost Done';
self::$message = 'Please click or copy-paste this link to confirm your registration: <a href="{BASE}register/confirm/{PARAMS}">Confirm Your Email</a>';
break;
case 'confirmation':
self::$subject = 'Please Confirm your email at {SITENAME}';
self::$title = 'Almost Done';
self::$message = 'Please click or copy-paste this link to confirm your registration: <a href="{BASE}register/confirm/{PARAMS}">Confirm Your Email</a>';
break;
case 'install':
self::$subject = 'Notification from {SITENAME}';
self::$title = 'Installation Success';
self::$message = 'This is just a simple email to notify you that you have successfully installed The Tempus Project framework!';
break;
case 'passwordChange':
self::$subject = 'Security Notice from {SITENAME}';
self::$title = 'Password Successfully Changed';
self::$message = 'Recently your password on {SITENAME} was changed. If you are the one who changed the password, please ignore this email.';
break;
case 'emailChangeNotice':
self::$subject = 'Account Update from {SITENAME}';
self::$title = 'Email Updated';
self::$message = 'This is a simple notification to let you know your email has been changed at {SITENAME}.';
break;
case 'emailChange':
self::$subject = 'Account Update from {SITENAME}';
self::$title = 'Confirm your E-mail';
self::$message = 'Please click or copy-paste this link to confirm your new Email: <a href="{BASE}register/confirm/{PARAMS}">Confirm Your Email</a>';
break;
case 'emailNotify':
self::$subject = 'Account Update from {SITENAME}';
self::$title = 'Email Updated';
self::$message = 'You recently changed your email address on {SITENAME}.';
break;
case 'forgotPassword':
self::$subject = 'Reset Instructions for {SITENAME}';
self::$title = 'Reset your Password';
self::$message = 'You recently requested information to change your password at {SITENAME}.<br>Your password reset code is: {PARAMS}<br> Please click or copy-paste this link to reset your password: <a href="{BASE}register/reset/{PARAMS}">Password Reset</a>';
break;
case 'forgotUsername':
self::$subject = 'Account Update from {SITENAME}';
self::$title = 'Account Details';
self::$message = 'Your username for {SITENAME} is {PARAMS}.';
break;
case 'subscribe':
self::$subject = 'Thanks for Subscribing';
self::$title = 'Thanks for Subscribing!';
self::$message = 'Thank you for subscribing to updates from {SITENAME}. If you no longer wish to receive these emails, you can un-subscribe using the link below.';
self::$unsub = true;
break;
case 'unsubInstructions':
self::$subject = 'Unsubscribe Instructions';
self::$title = 'We are sad to see you go';
self::$message = 'If you would like to be un-subscribed from future emails from {SITENAME} simply click the link below.<br><br><a href="{BASE}home/unsubscribe/{EMAIL}/{PARAMS}">Click here to unsubscribe</a>';
self::$unsub = true;
break;
case 'unsubscribe':
self::$subject = 'Unsubscribed';
self::$title = 'We are sad to see you go';
self::$message = 'This is just a notification that you have successfully been unsubscribed from future emails from {SITENAME}.';
break;
case 'contact':
self::$subject = $params['subject'];
self::$title = $params['title'];
self::$message = $params['message'];
break;
default:
return false;
break;
}
if ( self::$useTemplate ) {
$data = new \stdClass();
if ( self::$unsub ) {
$data->UNSUB = Views::simpleView( 'email.unsubscribe' );
} else {
$data->UNSUB = '';
}
$data->LOGO = Config::getValue( 'main/logo' );
$data->SITENAME = Config::getValue( 'main/name' );
$data->EMAIL = $email;
if ( !is_array( $params ) ) {
$data->PARAMS = $params;
} else {
foreach ( $params as $key => $value ) {
$data->$key = $value;
}
}
$data->MAIL_FOOT = Views::simpleView( 'email.foot' );
$data->MAIL_TITLE = self::$title;
$data->MAIL_BODY = Template::parse( self::$message, $data );
$subject = Template::parse( self::$subject, $data );
$body = Views::simpleView( 'email.template', $data );
} else {
$subject = self::$subject;
$body = '<h1>' . self::$title . '</h1>' . self::$message;
}
if ( is_object( $email ) ) {
foreach ( $email as $data ) {
if ( !@mail( $data->email, $subject, $body, self::$header ) ) {
Debug::error( 'Failed to send email. Subject: ' . $subject . ' Email: ' . $data->email );
}
}
} else {
if ( !@mail( $email, $subject, $body, self::$header ) ) {
Debug::error( 'Failed to send email. Subject: ' . $subject . ' Email: ' . $email );
}
}
Debug::info( "Email sent: $type." );
return true;
}
/**
* Constructor for the header.
*/
public static function build() {
if ( !self::$header ) {
self::$header = 'From: ' . Config::getValue( 'main/name' ) . ' <noreply@' . $_SERVER['HTTP_HOST'] . ">\r\n";
self::$header .= "MIME-Version: 1.0\r\n";
self::$header .= "Content-Type: text/html; charset=ISO-8859-1\r\n";
$url = parse_url( Routes::getAddress(), PHP_URL_HOST );
$parts = explode( '.', $url );
$count = count( $parts );
if ( $count > 2 ) {
$host = $parts[ $count - 2 ] . '.' . $parts[ $count - 1 ];
} else {
$host = $url;
}
if ( self::$debug ) {
self::$header .= "CC: webmaster@localhost.com\r\n";
}
}
}
}

692
app/classes/forms.php Executable file
View File

@ -0,0 +1,692 @@
<?php
/**
* app/classes/forms.php
*
* This class is used in conjunction with TheTempusProject\Bedrock\Classes\Check
* to house complete form verification. You can utilize the
* error reporting to easily define exactly what feedback you
* would like to give.
*
* @version 3.0
* @author Joey Kimsey <Joey@thetempusproject.com>
* @link https://TheTempusProject.com
* @license https://opensource.org/licenses/MIT [MIT LICENSE]
*/
namespace TheTempusProject\Classes;
use TheTempusProject\Bedrock\Functions\Check;
use TheTempusProject\Canary\Bin\Canary as Debug;
use TheTempusProject\Bedrock\Functions\Input;
use TheTempusProject\Models\User;
use TheTempusProject\Classes\Forms;
use TheTempusProject\Bedrock\Classes\Database;
class Forms extends Check {
private static $formHandlers = [];
private static $initialized = false;
public static function check( $formName ) {
if ( self::$initialized !== true ) {
self::initHandlers();
}
if ( empty( self::$formHandlers[ $formName ] ) ) {
Debug::error( "Form not found: $formName" );
return false;
}
$handler = self::$formHandlers[ $formName ];
return call_user_func_array( [ $handler['class'], $handler['method'] ], $handler['params'] );
}
public static function addHandler( $formName, $class, $method, $params = [] ) {
if ( !empty( self::$formHandlers[ $formName ] ) ) {
return false;
}
self::$formHandlers[$formName] = [
'class' => $class,
'method' => $method,
'params' => $params,
];
}
/**
* Checks username formatting.
*
* Requirements:
* - 4 - 16 characters long
* - must only contain numbers and letters: [A - Z] , [a - z], [0 - 9]
*
* @param string $data - The string being tested.
*
* @return boolean
*/
public static function checkUsername( $data ) {
if ( strlen( $data ) > 16 ) {
self::addError( 'Username must be be 4 to 16 numbers or letters.', $data );
return false;
}
if ( strlen( $data ) < 4 ) {
self::addError( 'Username must be be 4 to 16 numbers or letters.', $data );
return false;
}
if ( !ctype_alnum( $data ) ) {
self::addError( 'Username must be be 4 to 16 numbers or letters.', $data );
return false;
}
return true;
}
public static function date( $data ) {
if ( strtotime( $data ) == false ) {
self::addError( 'Username must be be 4 to 16 numbers or letters.', $data );
return false;
}
return true;
}
/**
* Adds these functions to the form list.
*/
public function __construct() {
if ( self::$initialized === true ) {
return;
}
self::initHandlers();
}
private static function initHandlers() {
self::addHandler( 'passwordResetCode', __CLASS__, 'passwordResetCode' );
self::addHandler( 'createRoute', __CLASS__, 'createRoute' );
self::addHandler( 'editRoute', __CLASS__, 'editRoute' );
self::addHandler( 'register', __CLASS__, 'register' );
self::addHandler( 'createUser', __CLASS__, 'createUser' );
self::addHandler( 'editUser', __CLASS__, 'editUser' );
self::addHandler( 'login', __CLASS__, 'login' );
self::addHandler( 'changeEmail', __CLASS__, 'changeEmail' );
self::addHandler( 'changePassword', __CLASS__, 'changePassword' );
self::addHandler( 'passwordReset', __CLASS__, 'passwordReset' );
self::addHandler( 'emailConfirmation', __CLASS__, 'emailConfirmation' );
self::addHandler( 'confirmationResend', __CLASS__, 'confirmationResend' );
self::addHandler( 'replyMessage', __CLASS__, 'replyMessage' );
self::addHandler( 'newMessage', __CLASS__, 'newMessage' );
self::addHandler( 'userPrefs', __CLASS__, 'userPrefs' );
self::addHandler( 'newGroup', __CLASS__, 'newGroup' );
self::addHandler( 'editGroup', __CLASS__, 'editGroup' );
self::addHandler( 'install', __CLASS__, 'install' );
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' ] );
self::addHandler( 'installConfigure', __CLASS__, 'install', [ 'configure' ] );
self::addHandler( 'installRouting', __CLASS__, 'install', [ 'routing' ] );
self::addHandler( 'installModels', __CLASS__, 'install', [ 'models' ] );
self::addHandler( 'installPlugins', __CLASS__, 'install', [ 'plugins' ] );
self::addHandler( 'installResources', __CLASS__, 'install', [ 'resources' ] );
self::addHandler( 'installAdminUser', __CLASS__, 'install', [ 'adminUser' ] );
self::$initialized = true;
}
/**
* Validates the installer forms.
*
* @return {bool}
*/
public static function install( $page = '' ) {
// if ( !self::token() ) {
// return false;
// }
switch ( $page ) {
case 'configure':
if ( ! Input::exists( 'submit' ) ) {
return false;
}
if ( !Database::check( Input::post( 'dbHost' ), Input::post( 'dbName' ), Input::post( 'dbUsername' ), Input::post( 'dbPassword' ) ) ) {
self::addUserError( 'DB connection error.' );
return false;
}
return true;
case 'adminUser':
if ( !self::checkUsername( Input::post( 'newUsername' ) ) ) {
self::addUserError( 'Invalid username.' );
return false;
}
if ( !self::password( Input::post( 'userPassword' ) ) ) {
self::addUserError( 'Invalid password.' );
return false;
}
if ( Input::post( 'userPassword' ) !== Input::post( 'userPassword2' ) ) {
self::addUserError( 'Passwords do not match.' );
return false;
}
if ( Input::post( 'userEmail' ) !== Input::post( 'userEmail2' ) ) {
self::addUserError( 'Emails do not match.' );
return false;
}
return true;
case 'check':
if ( !self::uploads() ) {
self::addUserError( 'Uploads are disabled.' );
return false;
}
if ( !self::php() ) {
self::addUserError( 'PHP version is too old.' );
return false;
}
if ( !self::phpExtensions() ) {
self::addUserError( 'PHP extensions are missing.' );
return false;
}
if ( !self::sessions() ) {
self::addUserError( 'There is an error with Sessions.' );
return false;
}
if ( !self::mail() ) {
self::addUserError( 'PHP mail is not enabled.' );
return false;
}
if ( !self::safe() ) {
self::addUserError( 'Safe mode is enabled.' );
return false;
}
if ( ! Input::exists( 'submit' ) ) {
return false;
}
return true;
case 'start':
case 'agreement':
case 'routing':
case 'models':
case 'plugins':
case 'resources':
if ( ! Input::exists( 'submit' ) ) {
return false;
}
return true;
default:
return false;
}
return false;
}
/**
* Validates the password re-send form.
*
* @return {bool}
*/
public static function passwordResetCode() {
if ( !Input::exists( 'resetCode' ) ) {
self::addUserError( 'Invalid resetCode.' );
return false;
}
if ( !self::token() ) {
return false;
}
return true;
}
/**
* Validates the route creation form.
*
* @return {bool}
*/
public static function createRoute() {
if ( !Input::exists( 'redirect_type' ) ) {
return false;
}
if ( 'external' == Input::post( 'redirect_type' ) && !self::url( Input::post( 'forwarded_url' ) ) ) {
return false;
}
if ( !self::token() ) {
return false;
}
return true;
}
/**
* Validates the route edit form.
*
* @return {bool}
*/
public static function editRoute() {
if ( !Input::exists( 'redirect_type' ) ) {
return false;
}
if ( 'external' == Input::post( 'redirect_type' ) && !self::url( Input::post( 'forwarded_url' ) ) ) {
return false;
}
if ( !self::token() ) {
return false;
}
return true;
}
/**
* Validates the user creation form.
*
* @return {bool}
*/
public static function createUser() {
$user = new User;
if ( !$user->checkUsername( Input::post( 'username' ) ) ) {
self::addUserError( 'Invalid username.' );
return false;
}
if ( !self::password( Input::post( 'password' ) ) ) {
self::addUserError( 'Invalid password.' );
return false;
}
if ( !self::email( Input::post( 'email' ) ) ) {
self::addUserError( 'Invalid Email.' );
return false;
}
if ( !$user->noEmailExists( Input::post( 'email' ) ) ) {
self::addUserError( 'A user with that email is already registered.' );
return false;
}
if ( Input::post( 'password' ) !== Input::post( 'password2' ) ) {
self::addUserError( 'Passwords do not match.' );
return false;
}
if ( Input::post( 'email' ) !== Input::post( 'email2' ) ) {
self::addUserError( 'Emails do not match.' );
return false;
}
if ( !Input::post( 'groupSelect' ) ) {
self::addUserError( 'You must select a group for the new user.' );
return false;
}
if ( !self::token() ) {
return false;
}
return true;
}
/**
* Validates the user edit form.
*
* @return {bool}
*/
public static function editUser() {
$user = new User;
if ( !$user->checkUsername( Input::post( 'username' ) ) ) {
self::addUserError( 'Invalid username.' );
return false;
}
if ( Input::exists( 'password' ) ) {
if ( !self::password( Input::post( 'password' ) ) ) {
self::addUserError( 'Invalid password.' );
return false;
}
if ( Input::post( 'password' ) !== Input::post( 'password2' ) ) {
self::addUserError( 'Passwords do not match.' );
return false;
}
}
if ( !self::email( Input::post( 'email' ) ) ) {
self::addUserError( 'Invalid Email.' );
return false;
}
if ( !Input::post( 'groupSelect' ) ) {
self::addUserError( 'You must select a group for the new user.' );
return false;
}
if ( !self::token() ) {
return false;
}
return true;
}
/**
* Validates the user registration form.
*
* @return {bool}
*/
public static function register() {
$user = new User;
if ( !self::checkUsername( Input::post( 'username' ) ) ) {
self::addUserError( 'Invalid username.' );
return false;
}
if ( !self::password( Input::post( 'password' ) ) ) {
self::addUserError( 'Invalid password.' );
return false;
}
if ( !self::email( Input::post( 'email' ) ) ) {
self::addUserError( 'Invalid Email.' );
return false;
}
if ( !$user->noEmailExists( Input::post( 'email' ) ) ) {
self::addUserError( 'A user with that email is already registered.' );
return false;
}
if ( Input::post( 'password' ) !== Input::post( 'password2' ) ) {
self::addUserError( 'Passwords do not match.' );
return false;
}
if ( Input::post( 'email' ) !== Input::post( 'email2' ) ) {
self::addUserError( 'Emails do not match.' );
return false;
}
if ( Input::post( 'terms' ) != '1' ) {
self::addUserError( 'You must agree to the terms of service.' );
return false;
}
if ( !self::token() ) {
return false;
}
return true;
}
/**
* Validates the user login form.
*
* @return {bool}
*/
public static function login() {
if ( !self::checkUsername( Input::post( 'username' ) ) ) {
self::addUserError( 'Invalid username.' );
return false;
}
if ( !self::password( Input::post( 'password' ) ) ) {
self::addUserError( 'Invalid password.' );
return false;
}
if ( !self::token() ) {
return false;
}
return true;
}
/**
* Validates the email change form.
*
* @return {bool}
*/
public static function changeEmail() {
if ( !self::email( Input::post( 'email' ) ) ) {
self::addUserError( 'Invalid Email.' );
return false;
}
if ( Input::post( 'email' ) !== Input::post( 'email2' ) ) {
self::addUserError( 'Emails do not match.' );
return false;
}
if ( !self::token() ) {
return false;
}
return true;
}
/**
* Validates the password change form.
*
* @return {bool}
*/
public static function changePassword() {
if ( !self::password( Input::post( 'password' ) ) ) {
self::addUserError( 'Invalid password.' );
return false;
}
if ( Input::post( 'password' ) !== Input::post( 'password2' ) ) {
self::addUserError( 'Passwords do not match.' );
return false;
}
if ( !self::token() ) {
return false;
}
return true;
}
/**
* Validates the password reset form.
*
* @return {bool}
*/
public static function passwordReset() {
if ( !self::password( Input::post( 'password' ) ) ) {
self::addUserError( 'Invalid password.' );
return false;
}
if ( Input::post( 'password' ) !== Input::post( 'password2' ) ) {
self::addUserError( 'Passwords do not match.' );
return false;
}
if ( !self::token() ) {
return false;
}
return true;
}
/**
* Validates the email confirmation re-send form.
*
* @return {bool}
*/
public static function emailConfirmation() {
if ( !Input::exists( 'confirmationCode' ) ) {
self::addUserError( 'No confirmation code provided.' );
return false;
}
if ( !self::token() ) {
return false;
}
return true;
}
/**
* Validates the email confirmation re-send form.
*
* @return {bool}
*/
public static function confirmationResend() {
if ( !Input::exists( 'resendConfirmation' ) ) {
self::addUserError( 'Confirmation not provided.' );
return false;
}
if ( !self::token() ) {
return false;
}
return true;
}
/**
* Validates the reply message form.
*
* @return {bool}
*/
public static function replyMessage() {
if ( !Input::exists( 'message' ) ) {
self::addUserError( 'Reply cannot be empty.' );
return false;
}
if ( !Input::exists( 'messageID' ) ) {
return false;
}
if ( !self::token() ) {
return false;
}
return true;
}
/**
* Validates the new message form.
*
* @return {bool}
*/
public static function newMessage() {
if ( !Input::exists( 'toUser' ) ) {
self::addUserError( 'You must specify a user to send the message to.' );
return false;
}
if ( !Input::exists( 'subject' ) ) {
self::addUserError( 'You must have a subject for your message.' );
return false;
}
if ( !Input::exists( 'message' ) ) {
self::addUserError( 'No message entered.' );
return false;
}
if ( !self::token() ) {
return false;
}
return true;
}
/**
* Validates the user preferences form.
*
* @return {bool}
*/
public static function userPrefs() {
// @todo make this a real check
if ( !Input::exists( 'timeFormat' ) ) {
self::addUserError( 'You must specify timeFormat' );
return false;
}
if ( !Input::exists( 'pageLimit' ) ) {
self::addUserError( 'You must specify pageLimit' );
return false;
}
if ( !Input::exists( 'gender' ) ) {
self::addUserError( 'You must specify gender' );
return false;
}
if ( !Input::exists( 'dateFormat' ) ) {
self::addUserError( 'You must specify dateFormat' );
return false;
}
if ( !Input::exists( 'timezone' ) ) {
self::addUserError( 'You must specify timezone' );
return false;
}
if ( !Input::exists( 'updates' ) ) {
self::addUserError( 'You must specify updates' );
return false;
}
if ( !Input::exists( 'newsletter' ) ) {
self::addUserError( 'You must specify newsletter' );
return false;
}
if ( !self::token() ) {
return false;
}
return true;
}
/**
* Validates the group creation form.
*
* @return {bool}
*/
public static function newGroup() {
if ( !Input::exists( 'name' ) ) {
self::addUserError( 'You must specify a name' );
return false;
}
if ( !self::dataTitle( Input::exists( 'name' ) ) ) {
self::addUserError( 'invalid group name' );
return false;
}
if ( !self::token() ) {
return false;
}
return true;
}
/**
* Validates the group edit form.
*
* @return {bool}
*/
public static function editGroup() {
if ( !Input::exists( 'name' ) ) {
self::addUserError( 'You must specify a name' );
return false;
}
if ( !self::dataTitle( Input::exists( 'name' ) ) ) {
self::addUserError( 'invalid group name' );
return false;
}
if ( !self::token() ) {
return false;
}
return true;
}
public static function adminCreateToken() {
if ( !Input::exists( 'name' ) ) {
self::addUserError( 'You must specify a name' );
return false;
}
if ( !Input::exists( 'token_type' ) ) {
self::addUserError( 'You must specify a token_type' );
return false;
}
return true;
}
public static function adminEditToken() {
if ( !Input::exists( 'name' ) ) {
self::addUserError( 'You must specify a name' );
return false;
}
if ( !Input::exists( 'token_type' ) ) {
self::addUserError( 'You must specify a token_type' );
return false;
}
return true;
}
public static function apiLogin() {
if ( !self::checkUsername( Input::post( 'username' ) ) ) {
self::addUserError( 'Invalid username.' );
return false;
}
if ( !self::password( Input::post( 'password' ) ) ) {
self::addUserError( 'Invalid password.' );
return false;
}
return true;
}
public static function updatePreference() {
if ( !Input::exists( 'prefName' ) ) {
self::addUserError( 'You must specify a name' );
return false;
}
if ( !Input::exists( 'prefValue' ) ) {
self::addUserError( 'You must specify a value' );
return false;
}
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;
}
}

645
app/classes/installer.php Executable file
View File

@ -0,0 +1,645 @@
<?php
/**
* app/classes/installer.php
*
* This class is used for the installation, regulation, tracking, and updating of
* the application. It handles installing the application, installing and updating
* models as well as the database, and generating and checking the htaccess file.
*
* @version 3.0
* @author Joey Kimsey <Joey@thetempusproject.com>
* @link https://TheTempusProject.com
* @license https://opensource.org/licenses/MIT [MIT LICENSE]
*/
namespace TheTempusProject\Classes;
use TheTempusProject\Bedrock\Functions\Check;
use TheTempusProject\Bedrock\Functions\Code;
use TheTempusProject\Bedrock\Functions\Cookie;
use TheTempusProject\Canary\Bin\Canary as Debug;
use TheTempusProject\Hermes\Functions\Redirect;
use TheTempusProject\Hermes\Functions\Route as Routes;
use TheTempusProject\Bedrock\Functions\Session;
use TheTempusProject\Classes\DatabaseModel;
use TheTempusProject\Classes\Plugin;
class Installer {
const MATRIX_MAP = [
'installPreferences' => 'preferenceMatrix',
'installPermissions' => 'permissionMatrix',
'installConfigs' => 'configMatrix',
'installTable' => 'databaseMatrix',
'installResources' => 'resourceMatrix',
];
private $override = false;
private $status = null;
private static $installJson = null;
private static $errors = [];
public function __construct() {
Debug::log( 'Installer initialized.' );
if ( self::$installJson === null ) {
self::$installJson = self::getJson();
}
}
public function getComposerJson() {
if ( !file_exists( COMPOSER_JSON_LOCATION ) ) {
Debug::error( 'No install json found.' );
return false;
}
return json_decode( file_get_contents( COMPOSER_JSON_LOCATION ), true );
}
public function getComposerLock() {
if ( !file_exists( COMPOSER_LOCK_LOCATION ) ) {
Debug::error( 'No install json found.' );
return false;
}
return json_decode( file_get_contents( COMPOSER_LOCK_LOCATION ), true );
}
public static function emptyModule( $type, $folder, $filename = '' ) {
switch ( $type ) {
case 'model':
if ( empty( $filename ) ) {
$class = '';
$class_name = '';
break;
}
$class = convertFileNameToModelClass( $filename );
$class_name = convertFileNameToClassName( $filename );
break;
case 'plugin':
if ( empty( $folder ) ) {
$class = '';
$class_name = '';
break;
}
$class = convertFileNameToPluginClass( $folder );
$class_name = convertFolderToClassName( $folder );
break;
}
if ( empty( $folder ) ) {
$folder = '';
}
$object = (object) [
'name' => $class_name,
'class' => $class,
'version' => '0.0',
'installedVersion' => '',
'folder' => $folder,
'type' => $type,
'installDate' => '',
'lastUpdate' => '',
'installStatus' => INSTALL_STATUS_NOT_INSTALLED,
'enabled' => false,
'enabled_txt' => 'no',
];
return $object;
}
/**
* This function automatically attempts to install all models in the
* specified directory.
* NOTE: The 'Models/ folder is used by default.
*
* @param string $directory - The directory you wish to install all
* models from.
* @return boolean
*/
public function getErrors( $array = true ) {
if ( $array ) {
$out = [];
foreach (self::$errors as $error) {
if ( ! is_array($error) ) {
exit(var_export($error,true));
}
$out[] = $error['errorInfo'];
}
} else {
$out = self::$errors;
}
return $out;
}
private static function getJson() {
if ( file_exists( INSTALL_JSON_LOCATION ) ) {
$content = file_get_contents( INSTALL_JSON_LOCATION );
$json = json_decode( $content, true );
} else {
touch( INSTALL_JSON_LOCATION );
$json = [];
}
return $json;
}
public static function saveJson() {
$encodedJson = json_encode( self::$installJson );
if ( !file_exists( INSTALL_JSON_LOCATION ) ) {
$content = file_get_contents( $location );
$json = json_decode( $content, true );
$fh = fopen( INSTALL_JSON_LOCATION, 'w' );
}
$writeSuccess = file_put_contents( INSTALL_JSON_LOCATION, $encodedJson );
if ( $writeSuccess ) {
return true;
}
return false;
}
public function getModule( $name ) {
$name = ucfirst( $name );
if ( isset( self::$installJson['modules'][$name] ) ) {
if ( isset( self::$installJson['modules'][$name]['enabled'] ) ) {
if ( self::$installJson['modules'][$name]['enabled'] == true ) {
self::$installJson['modules'][$name]['enabled_txt'] = '<span class="text-success">Yes</span>';
} 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];
}
Debug::info( "install module not found: $name" );
return false;
}
public function getModules( $includeObjects ) {
if ( isset( self::$installJson['modules'] ) ) {
if ( empty( $includeObjects ) ) {
return self::$installJson['modules'];
} else {
$data = self::$installJson['modules'];
foreach ( $data as $name => $module) {
$class_object = new $module['class'];
$data[$name]['class_object'] = $class_object;
}
return $data;
}
}
Debug::error( "install modules not found" );
return false;
}
public function getNode( $name) {
if ( isset( self::$installJson[$name] ) ) {
return self::$installJson[$name];
}
}
public function setNode( $name, $value, $save = false ) {
self::$installJson[$name] = $value;
if ( $save !== false ) {
return self::saveJson();
}
return true;
}
public function setModule( $name, $value, $save = false ) {
if ( !isset( self::$installJson['modules'] ) ) {
self::$installJson['modules'] = [];
}
self::$installJson['modules'][$name] = $value;
if ( $save !== false ) {
return self::saveJson();
}
return true;
}
private function removeModule( $name, $save = false ) {
if ( !isset( self::$installJson['modules'] ) ) {
Debug::error( 'No modules installed' );
return false;
}
if ( !isset( self::$installJson['modules'][$name] ) ) {
Debug::error( 'Module not installed' );
return false;
}
self::$installJson['modules'][$name] = null;
if ( $save !== false ) {
return self::saveJson();
}
return true;
}
public function findModelFlags( $classObject ) {
$install_flags = [];
foreach ( self::MATRIX_MAP as $install_flag => $matrix_name ) {
if ( !empty( $classObject->$matrix_name ) ) {
$install_flags[$install_flag] = true;
} else {
$install_flags[$install_flag] = false;
}
}
return $install_flags;
}
public function getPluginInfo( $folder) {
$object = self::emptyModule( 'plugin', $folder );
$location = $folder . 'plugin.php';
if ( file_exists( $location ) ) {
include_once $location;
} else {
self::$errors[] = ['errorInfo' => "Could not find the requested plugin file: $location"];
return $object;
}
if ( ! class_exists( $object->class ) ) {
Debug::warn( 'Cannot get plugin version from class: ' . $object->class . ', class does NOT exist.');
return $object;
}
$class_object = new $object->class;
$object->version = $class_object->pluginVersion;
$module = $class_object->module;
if ( false !== $module ) {
$objectArray = (array) $object;
$object = (object) array_replace( $objectArray, $module );
}
$object->class_object = $class_object;
return $object;
}
public function getModelInfo( $filename, $folder = '' ) {
Debug::debug( 'getModelInfo filename: ' . $filename . ', folder: ' . $folder);
$object = self::emptyModule( 'model', $folder, $filename );
if ( ! class_exists( $object->class ) ) {
Debug::warn( 'Cannot get model version from class: ' . $object->class . ', class does NOT exist.');
return $object;
}
$class_object = new $object->class;
$object->version = $class_object->modelVersion;
$module = $this->getModule( $object->name );
if ( false !== $module ) {
$objectArray = (array) $object;
$object = (object) array_replace( $objectArray, $module );
}
$object->class_object = $class_object;
return $object;
}
public function getAvailablePlugins() {
$plugins = Plugin::getPluginDirectories();
$list = [];
foreach ( $plugins as $pluginName => $locations ) {
foreach ( $locations as $location ) {
foreach ( $location as $currentFolder => $file ) {
if ( 'plugin.php' == $file ) {
$list[ $pluginName ] = $this->getPluginInfo( str_ireplace( 'plugin.php', '', $currentFolder ) );
}
}
}
}
return $list;
}
public function getModelList( $folder ) {
$files = scandir( $folder );
$models = [];
array_shift( $files );
array_shift( $files );
foreach ( $files as $index => $filename ) {
if ( stripos( $filename, '.php' ) ) {
$list[] = $this->getModelInfo( $filename, $folder );
}
}
return $list;
}
public function installPlugin( $module_data, $flags = [], $defaultFlagValue = true ) {
Debug::log( 'Installing Plugin: ' . $module_data->name );
$errors = [];
if ( INSTALL_STATUS_INSTALLED === $module_data->installStatus ) {
Debug::warn( "$name has already been successfully installed" );
return true;
}
if ( empty( $module_data->class_object ) ) {
self::$errors[] = [ 'errorInfo' => 'installPlugin: class_object not found: ' . $module_data->class ];
return false;
}
// normalize install flags
foreach ( PLUGIN_INSTALL_FLAGS as $flag_type ) {
// add any missing flags
if ( ! isset( $flags[ $flag_type ] ) ) {
$flags[ $flag_type ] = $defaultFlagValue;
}
// exclude any flags that have already been successfully installed
if ( !empty( $module_data->$flag_type ) && $module_data->$flag_type == INSTALL_STATUS_SUCCESS ) {
Debug::warn( "$flag_type has already been successfully executed" );
$flags[ $flag_type ] = false;
}
}
list( $install_data, $errors ) = $module_data->class_object->install( $flags );
$objectArray = (array) $module_data;
$module_data = array_replace( $objectArray, $install_data );
$module_data['installedVersion'] = $module_data['version'];
$module_data['lastUpdate'] = time();
if ( empty( $module_data['installDate'] ) ) {
$module_data['installDate'] = time();
}
unset($module_data['class_object']); // we don't want the class object though
$this->setModule( $module_data['name'], $module_data, true );
$this->updateInstallStatus( $module_data['name'] );
if ( !empty( $errors ) ) {
self::$errors = array_merge( self::$errors, $errors );
return false;
}
$errors[] = [ 'errorInfo' => $module_data['name'] . " Plugin has been installed." ];
self::$errors = array_merge( self::$errors, $errors );
return true;
}
public function uninstallPlugin( $module_data, $flags = [], $defaultFlagValue = true ) {
Debug::log( 'Uninstalling Plugin: ' . $module_data->name );
$errors = [];
if ( empty( $module_data->class_object ) ) {
self::$errors[] = [ 'errorInfo' => 'uninstallPlugin: class_object not found: ' . $module_data->class ];
return false;
}
// normalize install flags
foreach ( PLUGIN_INSTALL_FLAGS as $flag_type ) {
// add any missing flags
if ( ! isset( $flags[ $flag_type ] ) ) {
$flags[ $flag_type ] = $defaultFlagValue;
}
}
$errors = $module_data->class_object->uninstall( $flags );
if ( !empty( $errors ) ) {
self::$errors = array_merge( self::$errors, $errors );
return false;
}
$this->removeModule( $module_data->name, true );
$errors[] = [ 'errorInfo' => $module_data->name . " Plugin has been uninstalled." ];
self::$errors = array_merge( self::$errors, $errors );
return true;
}
public function installModel( $module_data, $flags = [], $defaultFlagValue = true, $updateModule = true ) {
Debug::log( 'Installing Model: ' . $module_data->name );
$errors = [];
if ( INSTALL_STATUS_INSTALLED === $module_data->installStatus ) {
Debug::warn( "$module_data->name has already been successfully installed" );
return true;
}
if ( empty( $module_data->class_object ) ) {
self::$errors[] = [ 'errorInfo' => 'installModel class_object not found: ' . $module_data->class ];
return false;
}
// normalize install flags
$model_flags = $this->findModelFlags( $module_data->class_object );
foreach ( MODEL_INSTALL_FLAGS as $flag_type ) {
// add any missing flags
if ( ! isset( $flags[ $flag_type ] ) ) {
$flags[ $flag_type ] = $defaultFlagValue;
}
// exclude any flags that have already been successfully installed
if ( ! empty( $module_data->$flag_type ) && $module_data->$flag_type == INSTALL_STATUS_SUCCESS ) {
Debug::warn( "$flag_type has already been successfully installed" );
$flags[ $flag_type ] = false;
}
if ( $flags[ $flag_type ] === true ) {
// exclude any flags we can't do anything with
if ( ! isset( $model_flags[ $flag_type ] ) ) {
Debug::warn( "$flag_type cannot be installed due to installFlags on the model." );
$flags[ $flag_type ] = false;
}
// check to make sure we have the proper mapping
$matrix = self::MATRIX_MAP[ $flag_type ];
// exclude any flags we don't have a matric map for
if ( empty( $module_data->class_object->$matrix ) ) {
Debug::warn( "$flag_type does not have a proper matrix map and cannot be installed." );
$module_data->$flag_type = INSTALL_STATUS_NOT_FOUND;
}
}
}
list( $install_data, $errors ) = $module_data->class_object->install( $flags );
$objectArray = (array) $module_data;
$module_data = array_replace( $objectArray, $install_data );
$module_data['installedVersion'] = $module_data['version'];
$module_data['lastUpdate'] = time();
if ( empty( $module_data['installDate'] ) ) {
$module_data['installDate'] = time();
}
if ($updateModule) {
unset($module_data['class_object']); // we don't want the class object though
$this->setModule( $module_data['name'], $module_data, true );
$this->updateInstallStatus( $module_data['name'] );
}
if ( !empty( $errors ) ) {
self::$errors = array_merge( self::$errors, $errors );
return false;
}
$errors[] = [ 'errorInfo' => $module_data['name'] . " model has been installed." ];
self::$errors = array_merge( self::$errors, $errors );
return true;
}
public function uninstallModel( $module_data, $flags = [], $defaultFlagValue = true, $updateModule = true ) {
Debug::log( 'Uninstalling Model: ' . $module_data->name );
$errors = [];
if ( empty( $module_data->class_object ) ) {
self::$errors[] = [ 'errorInfo' => 'uninstallModel: class_object not found: ' . $module_data->class ];
return false;
}
// normalize install flags
$model_flags = $this->findModelFlags( $module_data->class_object );
foreach ( MODEL_INSTALL_FLAGS as $flag_type ) {
// add any missing flags
if ( ! isset( $flags[ $flag_type ] ) ) {
$flags[ $flag_type ] = $defaultFlagValue;
}
if ( $flags[ $flag_type ] === true ) {
// exclude any flags we can't do anything with
if ( ! isset( $model_flags[ $flag_type ] ) ) {
Debug::warn( "$flag_type cannot be installed due to installFlags on the model." );
$flags[ $flag_type ] = false;
}
// check to make sure we have the proper mapping
$matrix = self::MATRIX_MAP[ $flag_type ];
// exclude any flags we don't have a matric map for
if ( empty( $module_data->class_object->$matrix ) ) {
Debug::warn( "$flag_type does not have a proper matrix map and cannot be uninstalled." );
$module_data->$flag_type = INSTALL_STATUS_NOT_FOUND;
}
}
}
list( $install_data, $errors ) = $module_data->class_object->uninstall( $flags );
if ( !empty( $errors ) ) {
self::$errors = array_merge( self::$errors, $errors );
return false;
}
$errors[] = [ 'errorInfo' => $module_data->name . " model has been uninstalled." ];
self::$errors = array_merge( self::$errors, $errors );
return true;
}
private function updateInstallStatus( $name ) {
$modelInfo = $this->getModule( $name );
if ( $modelInfo === false ) {
return;
}
if ( $modelInfo['type'] == 'plugin' ) {
$flags = PLUGIN_INSTALL_FLAGS;
} else {
$flags = MODEL_INSTALL_FLAGS;
}
foreach ( $flags as $flag_type ) {
if ( empty( $modelInfo[ $flag_type ] ) || ! in_array( $modelInfo[ $flag_type ], [ INSTALL_STATUS_SUCCESS, INSTALL_STATUS_NOT_REQUIRED ] ) ) {
$modelInfo['installStatus'] = INSTALL_STATUS_PARTIALLY_INSTALLED;
break;
}
$modelInfo['installStatus'] = INSTALL_STATUS_INSTALLED;
}
$this->setModule( $name, $modelInfo, true );
}
/**
* Checks the root directory for a .htaccess file and compares it with
* the .htaccess file the application generates by default.
*
* NOTE: The $override flag will cause this function to automatically generate a
* new htaccess file if the .htaccess found in the root directory does not match
* the default generated version.
*
* @param boolean $create - Optional flag to generate and save a new htaccess
* if none is found.
*
* @return boolean - Returns true if the htaccess file was found or
* created, false otherwise.
*/
public function saveHtaccess( $text = false ) {
if ( false === $text ) {
$text = $this->generateHtaccess();
}
if ( file_exists( HTACCESS_LOCATION ) ) {
Debug::error( "Can't overwrite existing htaccess file" );
return false;
}
return file_put_contents( HTACCESS_LOCATION, $text );
}
public function checkHtaccess( $create = false ) {
$check = 0;
$findRewrite1 = "RewriteEngine On\n";
$findRewrite2 = 'RewriteBase ' . Routes::getRoot() . "\n\n";
if ( file_exists( HTACCESS_LOCATION ) ) {
$htaccess = file_get_contents( HTACCESS_LOCATION );
if ( $htaccess === $this->generateHtaccess() ) {
return true;
}
if ( stripos( $htaccess, $findRewrite1 ) ) {
$check++;
}
if ( stripos( $htaccess, $findRewrite2 ) ) {
$check++;
}
if ( $check === 2 ) {
return true;
}
}
return false;
}
public function baseHtaccess() {
$out = "# Intercepts for images not found
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^images/(.*)$ index.php?error=image404&url=$1 [L,NC,QSA]
# Intercepts for uploads not found
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^uploads/(.*)$ index.php?error=upload404&url=$1 [L,NC,QSA]
# Intercepts other errors
RewriteRule ^errors/(.*)$ index.php?error=$1 [L,NC,QSA]
# Intercept all traffic not originating locally and not going to images or uploads
RewriteCond %{REMOTE_ADDR} !^127\.0\.0\.1
RewriteCond %{REMOTE_ADDR} !^\:\:1
RewriteCond %{REQUEST_URI} !^(.*)/images/(.*)$ [NC]
RewriteCond %{REQUEST_URI} !^(.*)/uploads/(.*)$ [NC]
RewriteCond %{REQUEST_URI} !^(.*).js$ [NC]
RewriteCond %{REQUEST_URI} !^(.*).css$ [NC]
RewriteCond %{REQUEST_URI} !^(.*).ico$ [NC]
RewriteRule ^(.+)$ index.php?url=$1 [QSA,L]
# Catchall for any non existent files or folders
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.+)$ index.php?url=$1 [QSA,L]";
return str_ireplace( ' ', '', $out );
}
/**
* Generates the default htaccess file for the application. This will funnel
* all traffic that comes into the application directory to index.php where we
* use that data to construct the desired page using the controller.
*
* @param string $docroot - A custom document root to use instead of the default.
*
* @return string - The generated contents of the htaccess file.
*/
protected function generateHtaccess( $docroot = null, $rewrite = true ) {
$out = '';
if ( empty( $docroot ) ) {
$docroot = Routes::getRoot();
}
if ( $rewrite === true ) {
$out .= "RewriteEngine On\n\n";
}
$out .= "RewriteBase $docroot\n\n";
$out .= $this->baseHtaccess();
return $out;
}
}

258
app/classes/permissions.php Executable file
View File

@ -0,0 +1,258 @@
<?php
/**
* app/classes/permissions.php
*
* This class handles all the hard-coded permissions.
*
* @version 3.0
* @author Joey Kimsey <Joey@thetempusproject.com>
* @link https://TheTempusProject.com
* @license https://opensource.org/licenses/MIT [MIT LICENSE]
*/
namespace TheTempusProject\Classes;
use TheTempusProject\Canary\Bin\Canary as Debug;
use TheTempusProject\Bedrock\Functions\Check;
use TheTempusProject\Houdini\Classes\Forms;
use TheTempusProject\Bedrock\Functions\Input;
use TheTempusProject\Houdini\Classes\Template;
class Permissions {
public static $permissions = false;
private static $location = false;
private static $initialized = false;
/**
* Default constructor which will attempt to load the permissions from the location specified.
*
* @param {string} [$location]
* @return {null|object}
*/
public function __construct( $location = '' ) {
if ( self::$initialized !== false ) {
Debug::log( 'Permissions already initialized.' );
return $this;
}
if ( empty( $location ) ) {
$location = PERMISSIONS_JSON;
}
self::$initialized = $this->load( $location );
if ( self::$initialized !== false ) {
Debug::log( 'Permissions initialization succeeded.' );
return $this;
}
Debug::warn( 'Permissions initialization failed.' );
}
/**
* Attempts to retrieve then set the configuration from a file.
* @note This function will reset the permissions every time it is used.
*
* @param {string} [$location]
* @return {bool}
*/
public function load( $location ) {
self::$permissions = $this->getPermsFile( $location );
self::$location = $location;
if ( self::$permissions === false || empty( self::$permissions ) ) {
Debug::warn( 'Permissions load failed.' );
return false;
}
Debug::log( 'Permissions load succeeded.' );
return true;
}
/**
* Opens and decodes the permissions json from the location provided.
*
* @param {string} [$location]
* @return {bool|array}
*/
public function getPermsFile( $location ) {
if ( file_exists( $location ) ) {
Debug::debug( "Permissions json found: $location" );
return json_decode( file_get_contents( $location ), true );
} else {
Debug::warn( "Permissions json not found: $location" );
return false;
}
}
/**
* Retrieves the permissions option for $name.
*
* @param {string} [$name]
* @return {WILD}
*/
public function get( $name ) {
if ( self::$permissions === false ) {
Debug::warn( 'Permissions not loaded.' );
return;
}
if ( isset( self::$permissions[$name] ) ) {
return self::$permissions[$name];
}
Debug::warn( "Permission not found: $name" );
return;
}
/**
* Saves the current permissions.
*
* @param {bool} [$default] - Whether or not to save a default copy.
* @return {bool}
*/
public function save( $save_backup = true ) {
if ( self::$permissions === false ) {
Debug::warn( 'Permissions not loaded.' );
return false;
}
if ( self::$location === false ) {
Debug::warn( 'Permissions location not set.' );
return false;
}
if ( $save_backup ) {
$locationArray = explode( '.', self::$location );
$locationArray[] = 'bak';
$backupLoction = implode( '.', $locationArray );
if ( !file_put_contents( $backupLoction, json_encode( self::$permissions ) ) ) {
return false;
}
}
if ( file_put_contents( self::$location, json_encode( self::$permissions ) ) ) {
return true;
}
return false;
}
/**
* Adds a new permission to the $permissions array.
*
* @param {string} [$name]
* @param {string} [$value]
* @return {bool}
*/
public function add( $permName, $details ) {
if ( !Check::simpleName( $permName ) ) {
Debug::error( "Permission name invalid: $permName" );
return false;
}
if ( isset( self::$permissions[$permName] ) ) {
Debug::warn( "Permission already exists: $permName" );
return false;
}
if ( self::$permissions === false ) {
self::$permissions = [];
}
self::$permissions[$permName] = $details;
return true;
}
/**
* Adds many new permissions to the $permissions array.
*
* @param {array} [$data]
* @return {bool}
*/
public function addMany( $data ) {
if ( !is_array( $data ) ) {
Debug::error( 'Permissions must be an array.' );
return false;
}
foreach ( $data as $name => $value ) {
$this->add( $name, $value );
}
return true;
}
/**
* Removes an existing permission from the $permissions array.
*
* @param {string} [$name]
* @param {string} [$save]
* @return {bool}
*/
public function remove( $name, $save = false ) {
if ( self::$permissions === false ) {
Debug::warn( 'Permissions not loaded.' );
return false;
}
if ( !isset( self::$permissions[$name] ) ) {
Debug::error( "Permission does not exist: $name" );
return false;
}
unset( self::$permissions[$name] );
if ( $save === true ) {
return $this->save();
}
return true;
}
public function getDefaultPermissionsArray() {
if ( self::$permissions === false ) {
Debug::warn( 'Permissions not loaded.' );
return false;
}
$permsArray = [];
foreach ( self::$permissions as $name => $details ) {
$permsArray[$name] = $details['default'];
}
return $permsArray;
}
public function convertFormToArray() {
$permsArray = [];
foreach ( self::$permissions as $name => $details ) {
if ( Input::exists( $name ) ) {
$permsArray[$name] = true;
} else {
$permsArray[$name] = false;
}
}
return $permsArray;
}
public function getDefault( $name ) {
$perm = $this->get( $name );
if ( empty( $perm ) || empty( $perm['default'] ) ) {
Debug::warn( "Permission Default not found: $name" );
return;
}
}
public function getPrettyName( $name ) {
$pref = $this->get( $name );
if ( empty( $pref ) || empty( $pref['pretty'] ) ) {
Debug::warn( "Permission Pretty Name not found: $name" );
return;
}
return $pref['pretty'];
}
public function getFormHtml( $populated = [] ) {
$form = '';
foreach ( self::$permissions as $name => $details ) {
if ( isset( $populated[$name] ) && $populated[$name] !== false ) {
$checked = true;
} else {
$checked = false;
}
$form .= self::getFieldEditHtml( $name, $checked, $details['pretty'] );
}
return $form;
}
public static function getFieldEditHtml( $name, $default, $pretty ) {
$fieldname = str_ireplace( '/', '-', $name );
$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>';
$html .= '<div class="col-lg-6">';
$html .= $fieldHtml;
$html .= '</div>';
$html .= '</div>';
return Template::parse( $html );
}
}

498
app/classes/plugin.php Executable file
View File

@ -0,0 +1,498 @@
<?php
/**
* app/classes/plugin.php
*
* This class is used as a foundation for all plugins to build from.
*
* @version 3.0
* @author Joey Kimsey <Joey@thetempusproject.com>
* @link https://TheTempusProject.com
* @license https://opensource.org/licenses/MIT [MIT LICENSE]
*/
namespace TheTempusProject\Classes;
use TheTempusProject\Houdini\Classes\Navigation;
use TheTempusProject\Houdini\Classes\Filters;
use TheTempusProject\TheTempusProject as App;
use TheTempusProject\Canary\Bin\Canary as Debug;
use TheTempusProject\Bedrock\Classes\Database;
class Plugin {
public $required_models = [];
public $models = [];
public $errors = [];
// Global Properties
public $module;
public $initialized = false;
public static $installer;
public static $db;
public static $installFlags = PLUGIN_INSTALL_FLAGS;
public static $pluginFolders = [];
public static $pluginsAvailable = [];
public static $pluginsActive = [];
// Basic Required Info
public $pluginName = 'Default Plugin Name';
public $pluginAuthor = 'TheTempusProject';
public $pluginWebsite = 'https://TheTempusProject.com';
public $pluginVersion = 0.0;
public $pluginDescription = 'The Default Plugin Description';
// Front-end Properties
public $admin_links = [];
public $main_links = [];
public $footer_links = [];
public $filters = [];
// Install Related
public $configName = '';
public $configMatrix = [];
public $resourceMatrix = [];
public $preferenceMatrix = [];
public $permissionMatrix = [];
const PLUGIN_FLAG_MAP = [
'preferences_installed' => 'installPreferences',
'permissions_installed' => 'installPermissions',
'configs_installed' => 'installConfigs',
'models_installed' => 'installModels',
'resources_installed' => 'installResources',
];
public function __construct( $load = false ) {
if ( true === $this->initialized && false == $load ) {
return;
}
self::$db = Database::getInstance();
if ( ! isset( self::$installer ) ) {
self::$installer = new Installer();
}
if ( ! isset( $this->module ) ) {
$this->module = self::$installer->getModule( getClassName( $this ) );
}
if ( true == $load ) {
if ( $this->checkEnabled() ) {
$this->loadAdminNav();
$this->loadMainNav();
$this->loadFooterNav();
$this->loadFilters();
}
$this->initialized = true;
}
}
public function install( $options ) {
Debug::log( 'Installing Plugin: ' . $this->pluginName );
$module_data = [];
$errors = [];
foreach ( self::PLUGIN_FLAG_MAP as $flag_name => $function_name ) {
if ( empty( $options[$flag_name] ) ) {
// $module_data[ $flag_name ] = INSTALL_STATUS_SKIPPED;
continue;
}
if ( 'installModels' != $function_name ) {
$result = $this->$function_name( $options );
} else {
$model_options = $this->convertPluginOptionsToModelOptions( $options );
$result = $this->$function_name( $model_options );
}
if ( empty( $result ) ) {
$errors[] = ['errorInfo' => get_class($this) . " Failed to execute $flag_name properly."];
$module_data[ $flag_name ] = INSTALL_STATUS_FAIL;
continue;
}
if ( 'installResources' === $function_name ) {
$module_data[ $flag_name ] = $result;
continue;
}
$module_data[ $flag_name ] = INSTALL_STATUS_SUCCESS;
continue;
}
return [ $module_data, $errors ];
}
public function uninstall( $options ) {
Debug::log( 'Uninstalling Plugin: ' . $this->pluginName );
$module_data = [];
$errors = [];
foreach ( self::PLUGIN_FLAG_MAP as $flag_name => $function_name ) {
$function_name = 'un' . $function_name;
if ( empty( $options[$flag_name] ) ) {
$module_data[ $flag_name ] = INSTALL_STATUS_SKIPPED;
continue;
}
if ( 'installModels' != $function_name ) {
$result = $this->$function_name( $options );
} else {
$model_options = $this->convertPluginOptionsToModelOptions( $options );
$result = $this->$function_name( $model_options );
}
if ( empty( $result ) ) {
$errors[] = ['errorInfo' => get_class($this) . " Failed to execute $flag_name properly."];
$module_data[ $flag_name ] = INSTALL_STATUS_FAIL;
continue;
}
if ( 'uninstallResources' === $function_name ) {
$module_data[ $flag_name ] = $result;
continue;
}
$module_data[ $flag_name ] = INSTALL_STATUS_UNINSTALLED;
continue;
}
return $errors;
}
public function installModels( $options ) {
$class = get_class($this);
$nameArray = explode( '\\', $class );
$name = array_pop( $nameArray );
$directory = PLUGIN_DIRECTORY . lcfirst($name) . DIRECTORY_SEPARATOR . 'models' . DIRECTORY_SEPARATOR;
if ( ! file_exists( $directory ) ) {
Debug::log( 'models directory is empty' );
return true;
}
$models = self::$installer->getModelList( $directory );
$error = false;
foreach ( $models as $model ) {
$result = self::$installer->installModel( $model, $options, true, false );
if ( $result === false ) {
$error = true;
continue;
}
}
if ( $error ) {
return false;
} else {
return true;
}
}
public function uninstallModels( $options ) {
$class = get_class($this);
$nameArray = explode( '\\', $class );
$name = array_pop( $nameArray );
$directory = PLUGIN_DIRECTORY . lcfirst($name) . DIRECTORY_SEPARATOR . 'models' . DIRECTORY_SEPARATOR;
if ( ! file_exists( $directory ) ) {
Debug::log( 'models directory is empty' );
return true;
}
$models = self::$installer->getModelList( $directory );
$error = false;
foreach ( $models as $model ) {
$result = self::$installer->uninstallModel( $model, $options, true, false );
if ( $result === false ) {
$error = true;
continue;
}
}
if ( $error ) {
return false;
} else {
return true;
}
}
public function installPermissions( $options = '' ) {
if ( empty( $this->permissionMatrix ) ) {
Debug::log( 'permissionMatrix is empty' );
return true;
}
$perms = new Permissions();
foreach ( $this->permissionMatrix as $name => $details ) {
$perms->add( $name, $details );
}
return $perms->save( true );
}
public function uninstallPermissions( $options = '' ) {
if ( empty( $this->permissionMatrix ) ) {
Debug::log( 'permissionMatrix is empty' );
return true;
}
$perms = new Permissions();
foreach ( $this->permissionMatrix as $name => $details ) {
$perms->remove( $name, true );
}
return true;
}
public function installConfigs( $options = '' ) {
if ( empty( $this->configMatrix ) || empty( $this->configName )) {
Debug::log( 'configMatrix is empty' );
return true;
}
$config = new Config( CONFIG_JSON );
// should have some sort of DELTA functionality and safeguards
$config->addCategory( $this->configName );
foreach ( $this->configMatrix as $name => $details ) {
$config->add( $this->configName, $name, $details );
}
return $config->save();
}
public function uninstallConfigs( $options = '' ) {
if ( empty( $this->configName ) ) {
return true;
}
$config = new Config( CONFIG_JSON );
return $config->removeCategory( $this->configName, true, true );
}
public function installPreferences( $options = '' ) {
$prefs = new Preferences();
if ( empty( $this->preferenceMatrix ) ) {
Debug::log( 'preferenceMatrix is empty' );
return true;
}
foreach ( $this->preferenceMatrix as $name => $details ) {
$prefs->add( $name, $details );
}
return $prefs->save( true );
}
public function uninstallPreferences( $options = '' ) {
if ( empty( $this->preferenceMatrix ) ) {
Debug::log( 'preferenceMatrix is empty' );
return true;
}
$prefs = new Preferences();
foreach ( $this->preferenceMatrix as $name => $details ) {
$prefs->remove( $name, true );
}
return $prefs->save( true );
}
public function installResources( $options = '' ) {
if ( empty( $this->resourceMatrix ) ) {
Debug::log( 'resourceMatrix is empty' );
return INSTALL_STATUS_NOT_REQUIRED;
}
$ids = [];
foreach( $this->resourceMatrix as $tableName => $entries ) {
foreach ( $entries as $entry ) {
foreach ( $entry as $key => $value ) {
if ( '{time}' == $value ) {
$entry[$key] = time();
}
}
self::$db->insert( $tableName, $entry );
$id = self::$db->lastId();
if ( $id ) {
$ids[] = $id;
}
}
}
return $ids;
}
public function uninstallResources( $options = '' ) {
if ( empty( $this->resourceMatrix ) ) {
Debug::log( 'resourceMatrix is empty' );
return true;
}
$ids = $this->module['resources_installed'];
$data = [];
foreach( $this->resourceMatrix as $tableName => $entries ) {
foreach ($ids as $id) {
$data[] = self::$db->delete( $tableName, [ 'ID', '=', $id ] );
}
}
return $data;
}
/**
* Loaders
*/
public function loadAdminNav() {
if ( !empty( $this->admin_links ) ) {
foreach( $this->admin_links as $key => $link ) {
Navigation::addLink( App::ADMIN_MENU_NAME, $link );
}
}
}
public function loadMainNav() {
if ( !empty( $this->main_links ) ) {
foreach( $this->main_links as $key => $link ) {
Navigation::addLink( App::MAIN_MENU_NAME, $link );
}
}
}
public function loadFooterNav() {
if ( !empty( $this->contact_footer_links ) ) {
foreach( $this->contact_footer_links as $key => $link ) {
Navigation::addLink( App::CONTACT_FOOTER_MENU_NAME, $link );
}
}
if ( !empty( $this->info_footer_links ) ) {
foreach( $this->info_footer_links as $key => $link ) {
Navigation::addLink( App::INFO_FOOTER_MENU_NAME, $link );
}
}
}
public function loadFilters() {
if ( ! empty( $this->filters ) ) {
foreach( $this->filters as $filter ) {
Filters::add( $filter['name'], $filter['find'], $filter['replace'], $filter['enabled'] );
}
}
}
public function convertPluginOptionsToModelOptions( $options ) {
$data = [];
foreach (self::PLUGIN_FLAG_MAP as $pluginValue => $modelValue) {
if ( isset( $options[$pluginValue] ) ) {
$data[$modelValue] = $options[$pluginValue];
}
}
return $data;
}
public static function getPluginDirectories( $forceRefresh = false ) {
if ( !empty( self::$pluginFolders ) && true !== $forceRefresh ) {
return self::$pluginFolders;
}
$pluginFolders = [];
if ( ! PLUGINS_ENABLED && true !== $forceRefresh ) {
Debug::warn('Plugins disabled');
return $pluginFolders;
}
if ( ! file_exists( PLUGIN_DIRECTORY ) ) {
Debug::warn("Plugins folder is missing: $dir");
return $pluginFolders;
}
// get a list of all plugins in the plugin directory
$pluginDirectories = scandir( PLUGIN_DIRECTORY );
array_shift( $pluginDirectories ); // remove the .
array_shift( $pluginDirectories ); // remove the ..
foreach ( $pluginDirectories as $key => $pluginName ) {
$pluginDirectory = PLUGIN_DIRECTORY . $pluginName;
if ( is_file( $pluginDirectory ) ) {
continue; // skip any files in the main plugin directory if they exist
}
// get a list of all directories in this plugin directory
$pluginFolders[ $pluginName ] = [];
$pluginDirectory .= DIRECTORY_SEPARATOR;
$pluginDirectoryArray = scandir( $pluginDirectory );
array_shift( $pluginDirectoryArray ); // remove the .
array_shift( $pluginDirectoryArray ); // remove the ..
// loop over each sub-directory insider plugin directory
foreach ( $pluginDirectoryArray as $key => $file ) {
$currentFolder = $pluginDirectory . $file . DIRECTORY_SEPARATOR;
switch ( $file ) {
case 'controllers':
case 'config':
case 'models':
case 'views':
case 'templates':
break;
case 'forms.php':
$currentFolder = rtrim( $currentFolder, DIRECTORY_SEPARATOR );
break;
case 'plugin.php':
$currentFolder = rtrim( $currentFolder, DIRECTORY_SEPARATOR );
break;
default:
continue 2; // break if we aren't looking for whatever we found
}
$pluginFolders[ $pluginName ][] = [ $currentFolder => $file ];
}
}
self::$pluginFolders = $pluginFolders;
return self::$pluginFolders;
}
public static function getActivePlugins( $forceRefresh = false ) {
if ( ! empty( self::$pluginsActive ) && true !== $forceRefresh ) {
return self::$pluginsActive;
}
if ( ! isset( self::$installer ) ) {
self::$installer = new Installer();
}
$out = [];
$plugins = self::$installer->getAvailablePlugins( $forceRefresh );
if ( ! empty( $plugins ) ) {
foreach ( $plugins as $plugin ) {
if ( !isset( $plugin->class_object )) {
continue;
}
$installedPlugin = $plugin->class_object->module;
if ( !isset( $installedPlugin['enabled'] ) || !$installedPlugin['enabled'] ) {
continue;
}
$out[] = [
$plugin->name => $installedPlugin['class'],
];
}
}
self::$pluginsActive = $out;
return self::$pluginsActive;
}
public static function enable( $name, $save = true ) {
if ( ! isset( self::$installer ) ) {
self::$installer = new Installer();
}
$module = self::$installer->getModule( $name );
if ( empty( $module ) ) {
Debug::warn( "plugin not found: $name" );
return false;
}
if ( ! isset( $module['enabled'] ) ) {
Debug::error( "plugin enabled not set: $name" );
return false;
}
$module['enabled'] = true;
return self::$installer->setModule( $name, $module, $save );
}
public static function disable( $name, $save = true ) {
if ( ! isset( self::$installer ) ) {
self::$installer = new Installer();
}
$module = self::$installer->getModule( $name );
if ( empty($module) ) {
Debug::warn( "plugin not found: $name" );
return false;
}
if ( ! isset( $module['enabled'] ) ) {
Debug::error( "plugin not enabled: $name" );
return false;
}
$module['enabled'] = false;
return self::$installer->setModule( $name, $module, $save );
}
public function checkEnabled() {
$name = ucfirst( strtolower( $this->pluginName ) );
if ( isset( $this->module['enabled'] ) ) {
return $this->module['enabled'];
}
Debug::warn( "install not found: {$this->pluginName}" );
return false;
}
}

385
app/classes/preferences.php Executable file
View File

@ -0,0 +1,385 @@
<?php
/**
* app/classes/preferences.php
*
* This class handles all the hard-coded preferences.
*
* @version 3.0
* @author Joey Kimsey <Joey@thetempusproject.com>
* @link https://TheTempusProject.com
* @license https://opensource.org/licenses/MIT [MIT LICENSE]
*/
namespace TheTempusProject\Classes;
use TheTempusProject\Houdini\Classes\Issues;
use TheTempusProject\Houdini\Classes\Forms;
use TheTempusProject\Houdini\Classes\Template;
use TheTempusProject\Canary\Bin\Canary as Debug;
use TheTempusProject\Bedrock\Functions\Check;
use TheTempusProject\Bedrock\Functions\Upload;
use TheTempusProject\Bedrock\Functions\Input;
use TheTempusProject\TheTempusProject as App;
use TheTempusProject\Bedrock\Classes\Config;
class Preferences {
public static $preferences = false;
private static $location = false;
private static $initialized = false;
/**
* Default constructor which will attempt to load the preferences from the location specified.
*
* @param {string} [$location]
* @return {null|object}
*/
public function __construct( $location = '' ) {
if ( self::$initialized !== false ) {
Debug::log( 'Preferences already initialized.' );
return $this;
}
if ( empty( $location ) ) {
$location = PREFERENCES_JSON;
}
self::$initialized = $this->load( $location );
if ( self::$initialized !== false ) {
Debug::log( 'Preferences initialization succeeded.' );
return $this;
}
Debug::warn( 'Preferences initialization failed.' );
}
/**
* Attempts to retrieve then set the preferences from a file.
* @note This function will reset the preferences every time it is used.
*
* @param {string} [$location]
* @return {bool}
*/
public function load( $location ) {
self::$preferences = $this->getPrefsFile( $location );
self::$location = $location;
if ( self::$preferences === false || empty( self::$preferences ) ) {
Debug::warn( 'Preferences load failed.' );
return false;
}
Debug::log( 'Preferences load succeeded.' );
return true;
}
/**
* Opens and decodes the preferences json from the location provided.
*
* @param {string} [$location]
* @return {bool|array}
*/
public function getPrefsFile( $location ) {
if ( file_exists( $location ) ) {
Debug::debug( "Preferences json found: $location" );
return json_decode( file_get_contents( $location ), true );
} else {
Debug::warn( "Preferences json not found: $location" );
return false;
}
}
/**
* Retrieves the preference option for $name.
*
* @param {string} [$name]
* @return {WILD}
*/
public static function get( $name ) {
if ( self::$preferences === false ) {
return Debug::warn( 'Preferences not loaded.' );
}
if ( isset( self::$preferences[$name] ) ) {
return self::$preferences[$name];
}
return Debug::warn( "Preference not found: $name" );
}
public function getType( $name ) {
$pref = $this->get( $name );
if ( empty( $pref ) || empty( $pref['type'] ) ) {
return Debug::warn( "Preference Type not found: $name" );
}
return $pref['type'];
}
public function getDefault( $name ) {
$pref = $this->get( $name );
if ( empty( $pref ) || empty( $pref['default'] ) ) {
return Debug::warn( "Preference Default not found: $name" );
}
return $pref['default'];
}
public function getOptions( $name ) {
$pref = $this->get( $name );
if ( empty( $pref ) || empty( $pref['options'] ) ) {
return Debug::warn( "Preference Options not found: $name" );
}
return $pref['options'];
}
public function getPrettyName( $name ) {
$pref = $this->get( $name );
if ( empty( $pref ) || empty( $pref['pretty'] ) ) {
return Debug::warn( "Preference Pretty Name not found: $name" );
}
return $pref['pretty'];
}
/**
* Saves the current preferences.
*
* @param {bool} [$default] - Whether or not to save a default copy.
* @return {bool}
*/
public function save( $backup = true ) {
if ( self::$preferences === false ) {
Debug::warn( 'Preferences not loaded.' );
return false;
}
if ( self::$location === false ) {
Debug::warn( 'Preferences location not set.' );
return false;
}
if ( $backup ) {
$locationArray = explode( '.', self::$location );
$locationArray[] = 'bak';
$backupLoction = implode( '.', $locationArray );
if ( !file_put_contents( $backupLoction, json_encode( self::$preferences ) ) ) {
return false;
}
}
if ( file_put_contents( self::$location, json_encode( self::$preferences ) ) ) {
return true;
}
return false;
}
/**
* Adds a new preference to the $preferences array.
*
* @param {string} [$name]
* @param {string} [$value]
* @return {bool}
*/
public function add( $name, $details ) {
if ( !Check::simpleName( $name ) ) {
Debug::error( "Preference name invalid: $name" );
return false;
}
if ( isset( self::$preferences[$name] ) ) {
Debug::warn( "Preference already exists: $name" );
return false;
}
if ( self::$preferences === false ) {
self::$preferences = [];
}
$prefsArray = $this->normalizePreferenceArray( $name, $details );
if ( false === $prefsArray ) {
Debug::warn( 'Preference array failed to load properly.' );
return false;
}
self::$preferences[$name] = $prefsArray;
return true;
}
public function getFormHtml( $populated = [] ) {
// dv( self::$preferences );
$form = '';
// Added so i can force some sort of ordering
$inputTypes = [
'file' => [],
'select' => [],
'timezone' => [],
'checkbox' => [],
'switch' => [],
];
foreach ( self::$preferences as $name => $details ) {
$tempPrefsArray = $this->normalizePreferenceArray( $name, $details );
if ( isset( $populated[ $name ] ) ) {
$tempPrefsArray['value'] = $populated[$name];
} else {
$tempPrefsArray['value'] = $tempPrefsArray['default'];
}
// $form .= Forms::getFormFieldHtml( $name, $tempPrefsArray['pretty'], $tempPrefsArray['type'], $tempPrefsArray['default'], $tempPrefsArray['options'] );
if ( $tempPrefsArray['type'] == 'checkbox' ) {
$tempPrefsArray['type'] = 'switch';
}
if ( 'file' === $tempPrefsArray['type'] ) {
// dv( Config::getValue( 'uploads/images' ) );
if ( ! Config::getValue( 'uploads/images' ) ) {
Debug::info( 'Preference hidden because uploads are disabled.' );
continue;
}
}
$inputTypes[ $tempPrefsArray['type'] ][] = self::getFormFieldHtml( $name, $tempPrefsArray['pretty'], $tempPrefsArray['type'], $tempPrefsArray['value'], $tempPrefsArray['options'] );
}
foreach ( $inputTypes as $skip => $items ) {
$form .= implode( ' ', $items );
}
return $form;
}
public static function getFormFieldHtml( $fieldname, $fieldTitle, $type, $defaultValue = '', $options = null ) {
$html = '';
switch ( $type ) {
case 'radio':
case 'bool':
case 'boolean':
$fieldHtml = Forms::getRadioHtml( $fieldname, [ 'true', 'false' ], $defaultValue );
break;
case 'select':
$fieldHtml = Forms::getSelectHtml( $fieldname, $options, $defaultValue );
break;
case 'customSelect':
if ( empty( $options ) ) {
$options = '{' . $fieldname . '-options}';
}
$fieldHtml = Forms::getSelectHtml( $fieldname, $options, $defaultValue );
break;
case 'block':
$fieldHtml = Forms::getTextBlockHtml( $fieldname, $defaultValue );
break;
case 'text':
case 'url':
$fieldHtml = Forms::getTextHtml( $fieldname, $defaultValue );
break;
case 'checkbox':
$fieldHtml = Forms::getCheckboxHtml( $fieldname, $defaultValue );
break;
case 'switch':
$fieldHtml = Forms::getSwitchHtml( $fieldname, $defaultValue );
break;
case 'timezone':
$fieldHtml = Forms::getTimezoneHtml( $defaultValue );
break;
case 'file':
$fieldHtml = Forms::getFileHtml( $fieldname );
break;
default:
Debug::error( "unknown field type: $type" );
break;
}
$html .= '<div class="mb-3 row">';
$html .= '<label for="' . $fieldname . '" class="col-lg-6 col-form-label text-start text-lg-end">' . $fieldTitle . '</label>';
$html .= '<div class="col-lg-6">';
$html .= $fieldHtml;
$html .= '</div>';
if ( 'file' === $type ) {
$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="preferred image" src="{ROOT_URL}' . $defaultValue . '" class="img-circle img-fluid p-2">';
$html .= '</div>';
}
$html .= '</div>';
return Template::parse( $html );
}
public function convertFormToArray( $fillMissing = true, $defaultsOnly = true ) {
$prefsArray = [];
foreach ( self::$preferences as $name => $details ) {
if ( true === $fillMissing ) {
if ( true !== $defaultsOnly && !empty( App::$activePrefs[$name] ) ) {
$prefsArray[$name] = App::$activePrefs[$name];
} else {
$prefsArray[$name] = $details['default'];
}
}
if ( Input::exists( $name ) ) {
$prefsArray[$name] = Input::post( $name );
}
if ( 'file' == $details['type'] ) {
if ( Input::exists( $name ) ) {
$folder = UPLOAD_DIRECTORY . App::$activeUser->username . DIRECTORY_SEPARATOR . 'images' . DIRECTORY_SEPARATOR;
$upload = Upload::image( $name, $folder );
if ( $upload ) {
$route = str_replace( APP_ROOT_DIRECTORY, '', $folder );
$prefsArray[$name] = $route . Upload::last();
} else {
Issues::add( 'error', [ 'There was an error with your upload.' => Check::userErrors() ] );
unset( $prefsArray[$name] );
}
}
}
if ( 'checkbox' == $details['type'] ) {
if ( Input::exists( $name ) ) {
$prefsArray[$name] = true;
} else {
$prefsArray[$name] = false;
}
}
}
return $prefsArray;
}
public function getDefaultPreferencesArray() {
if ( self::$preferences === false ) {
Debug::warn( 'Preferences not loaded.' );
return false;
}
$prefsArray = [];
foreach ( self::$preferences as $name => $details ) {
$tempPrefsArray = $this->normalizePreferenceArray( $name, $details );
$prefsArray[$name] = $tempPrefsArray['default'];
}
return $prefsArray;
}
public function normalizePreferenceArray( $name, $prefsArray ) {
if ( !is_array( $prefsArray ) ) {
Debug::warn( 'Preference array was not an array.' );
return false;
}
if ( !isset( $prefsArray['type'] ) ) {
if ( isset( $prefsArray['options'] ) ) {
$prefsArray['type'] = 'select';
} else {
$prefsArray['type'] = 'text';
}
}
if ( !isset( $prefsArray['pretty'] ) ) {
$prefsArray['pretty'] = ucfirst( $name );
}
if ( !isset( $prefsArray['default'] ) ) {
$prefsArray['default'] = '';
}
if ( ( empty( $prefsArray['avatar'] ) ) || ( $prefsArray['avatar'] == 'defaultAvatar.png' ) ) {
$prefsArray['avatar'] = IMAGE_DIRECTORY . 'defaultAvatar.png';
}
if ( !isset( $prefsArray['options'] ) ) {
$prefsArray['options'] = null;
}
return $prefsArray;
}
/**
* Removes an existing preference from the $preferences array.
*
* @param {string} [$name]
* @param {bool} [$save]
* @return {bool}
*/
public function remove( $name, $save = false ) {
if ( self::$preferences === false ) {
Debug::warn( 'Preferences not loaded.' );
return false;
}
if ( !isset( self::$preferences[$name] ) ) {
Debug::error( "Preference does not exist: $name" );
return false;
}
unset( self::$preferences[$name] );
if ( $save === true ) {
return $this->save( true );
}
return true;
}
}

139
app/config/constants.php Executable file
View File

@ -0,0 +1,139 @@
<?php
define( 'APP_SPACE', 'TheTempusProject' );
if ( ! defined( 'APP_ROOT_DIRECTORY' ) ) {
define( 'APP_ROOT_DIRECTORY', dirname( __DIR__ ) . DIRECTORY_SEPARATOR ); // need to verify
}
define( 'APP_DIRECTORY', APP_ROOT_DIRECTORY . 'app' . DIRECTORY_SEPARATOR );
// Directories
define( 'CSS_DIRECTORY', APP_ROOT_DIRECTORY . 'css' . DIRECTORY_SEPARATOR );
define( 'IMAGE_DIRECTORY', APP_ROOT_DIRECTORY . 'images' . DIRECTORY_SEPARATOR );
define( 'JAVASCRIPT_DIRECTORY', APP_ROOT_DIRECTORY . 'js' . DIRECTORY_SEPARATOR );
define( 'HTACCESS_LOCATION', APP_ROOT_DIRECTORY . '.htaccess' );
if ( ! defined( 'CONFIG_DIRECTORY' ) ) {
define( 'CONFIG_DIRECTORY', APP_DIRECTORY . 'config' . DIRECTORY_SEPARATOR );
}
define( 'PLUGIN_DIRECTORY', APP_DIRECTORY . 'plugins' . DIRECTORY_SEPARATOR );
define( 'MODEL_DIRECTORY', APP_DIRECTORY . 'models' . DIRECTORY_SEPARATOR );
define( 'CONTROLLER_DIRECTORY', APP_DIRECTORY . 'controllers' . DIRECTORY_SEPARATOR );
define( 'ADMIN_CONTROLLER_DIRECTORY', CONTROLLER_DIRECTORY. 'admin' . DIRECTORY_SEPARATOR );
define( 'API_CONTROLLER_DIRECTORY', CONTROLLER_DIRECTORY. 'api' . DIRECTORY_SEPARATOR );
// Files
define( 'PERMISSIONS_JSON', CONFIG_DIRECTORY . 'permissions.json' );
define( 'PREFERENCES_JSON', CONFIG_DIRECTORY . 'preferences.json' );
define( 'INSTALL_JSON_LOCATION', CONFIG_DIRECTORY . 'install.json' );
define( 'INSTALLER_LOCATION', APP_ROOT_DIRECTORY . 'install.php' );
// Other
define( 'PLUGINS_ENABLED', true );
define( 'INSTALL_STATUS_NOT_REQUIRED', 'Not Required' );
define( 'INSTALL_STATUS_NOT_FOUND', 'Not Found' );
define( 'INSTALL_STATUS_PARTIALLY_INSTALLED', 'Partially Installed' );
define( 'INSTALL_STATUS_NOT_INSTALLED', 'Not Installed' );
define( 'INSTALL_STATUS_INSTALLED', 'Installed' );
define( 'INSTALL_STATUS_UNINSTALLED', 'Uninstalled' );
define( 'INSTALL_STATUS_SUCCESS', 'Success' );
define( 'INSTALL_STATUS_SKIPPED', 'Skipped' );
define( 'INSTALL_STATUS_FAIL', 'Failed' );
define( 'MODEL_INSTALL_FLAGS', [ 'installTable', 'installPermissions', 'installConfigs', 'installResources', 'installPreferences' ] );
define( 'PLUGIN_INSTALL_FLAGS', [ 'models_installed', 'permissions_installed', 'configs_installed', 'resources_installed', 'preferences_installed' ] );
# Tempus Debugger
define( 'CANARY_SECURE_HASH', 'd73ed7591a30f0ca7d686a0e780f0d05' );
# Tempus Project Core
define( 'APP_NAME', 'The Tempus Project');
define( 'TP_DEFAULT_LOGO', 'images/logo.png');
// Check
define( 'MINIMUM_PHP_VERSION', 8.1);
// Cookies
define( 'DEFAULT_COOKIE_PREFIX', 'TP_');
// Debug
define( 'CANARY_DEBUG_DIRECTORY', APP_ROOT_DIRECTORY . 'logs' . DIRECTORY_SEPARATOR );
define( 'CANARY_DEBUG_LEVEL_ERROR', 'error' );
define( 'CANARY_DEBUG_LEVEL_WARN', 'warn' );
define( 'CANARY_DEBUG_LEVEL_INFO', 'info' );
define( 'CANARY_DEBUG_LEVEL_LOG', 'log' );
define( 'CANARY_DEBUG_LEVEL_DEBUG', 'debug' );
define( 'CANARY_DEBUG_TO_FILE_LEVEL', CANARY_DEBUG_LEVEL_INFO );
define( 'CANARY_ENABLED', true );
define( 'DEBUG_EMAIL', 'webmaster@' . $_SERVER['HTTP_HOST'] );
define( 'HERMES_REDIRECTS_ENABLED', true );
define( 'RENDERING_ENABLED', true );
define( 'CANARY_TRACE_ENABLED', false );
define( 'CANARY_DEBUG_TO_CONSOLE', false );
define( 'CANARY_DEBUG_TO_FILE', true );
// Directories
if ( ! defined( 'VENDOR_DIRECTORY' ) ) {
define( 'VENDOR_DIRECTORY', APP_ROOT_DIRECTORY . 'vendor' . DIRECTORY_SEPARATOR );
}
if ( is_dir( VENDOR_DIRECTORY . 'thetempusproject' )) {
define( 'TP_VENDOR_DIRECTORY', VENDOR_DIRECTORY . 'thetempusproject' . DIRECTORY_SEPARATOR );
} elseif ( is_dir( VENDOR_DIRECTORY . 'TheTempusProject' )) {
define( 'TP_VENDOR_DIRECTORY', VENDOR_DIRECTORY . 'TheTempusProject' . DIRECTORY_SEPARATOR );
} else {
define( 'TP_VENDOR_DIRECTORY', VENDOR_DIRECTORY);
}
# Bedrock
if ( is_dir( TP_VENDOR_DIRECTORY . 'tempusprojectcore' )) {
define( 'BEDROCK_ROOT_DIRECTORY', TP_VENDOR_DIRECTORY . 'tempusprojectcore' . DIRECTORY_SEPARATOR );
} elseif ( is_dir( TP_VENDOR_DIRECTORY . 'TempusProjectCore' )) {
define( 'BEDROCK_ROOT_DIRECTORY', TP_VENDOR_DIRECTORY . 'TempusProjectCore' . DIRECTORY_SEPARATOR );
} elseif ( is_dir( TP_VENDOR_DIRECTORY . 'bedrock' )) {
define( 'BEDROCK_ROOT_DIRECTORY', TP_VENDOR_DIRECTORY . 'bedrock' . DIRECTORY_SEPARATOR );
} elseif ( is_dir( TP_VENDOR_DIRECTORY . 'Bedrock' )) {
define( 'BEDROCK_ROOT_DIRECTORY', TP_VENDOR_DIRECTORY . 'Bedrock' . DIRECTORY_SEPARATOR );
}
if ( is_dir( BEDROCK_ROOT_DIRECTORY . 'config' )) {
define( 'BEDROCK_CONFIG_DIRECTORY', BEDROCK_ROOT_DIRECTORY . 'config' . DIRECTORY_SEPARATOR );
}
# Canary
if ( is_dir( TP_VENDOR_DIRECTORY . 'tempusdebugger' )) {
define( 'CANARY_ROOT_DIRECTORY', TP_VENDOR_DIRECTORY . 'tempusdebugger' . DIRECTORY_SEPARATOR );
} elseif ( is_dir( TP_VENDOR_DIRECTORY . 'TempusDebugger' )) {
define( 'CANARY_ROOT_DIRECTORY', TP_VENDOR_DIRECTORY . 'TempusDebugger' . DIRECTORY_SEPARATOR );
} elseif ( is_dir( TP_VENDOR_DIRECTORY . 'canary' )) {
define( 'CANARY_ROOT_DIRECTORY', TP_VENDOR_DIRECTORY . 'canary' . DIRECTORY_SEPARATOR );
} elseif ( is_dir( TP_VENDOR_DIRECTORY . 'Canary' )) {
define( 'CANARY_ROOT_DIRECTORY', TP_VENDOR_DIRECTORY . 'Canary' . DIRECTORY_SEPARATOR );
}
if ( is_dir( CANARY_ROOT_DIRECTORY . 'config' )) {
define( 'CANARY_CONFIG_DIRECTORY', CANARY_ROOT_DIRECTORY . 'config' . DIRECTORY_SEPARATOR );
}
# Hermes
if ( is_dir( TP_VENDOR_DIRECTORY . 'hermes' )) {
define( 'HERMES_ROOT_DIRECTORY', TP_VENDOR_DIRECTORY . 'hermes' . DIRECTORY_SEPARATOR );
} elseif ( is_dir( TP_VENDOR_DIRECTORY . 'Hermes' )) {
define( 'HERMES_ROOT_DIRECTORY', TP_VENDOR_DIRECTORY . 'Hermes' . DIRECTORY_SEPARATOR );
}
if ( is_dir( HERMES_ROOT_DIRECTORY . 'config' )) {
define( 'HERMES_CONFIG_DIRECTORY', HERMES_ROOT_DIRECTORY . 'config' . DIRECTORY_SEPARATOR );
}
# Houdini
if ( is_dir( TP_VENDOR_DIRECTORY . 'houdini' )) {
define( 'HOUDINI_ROOT_DIRECTORY', TP_VENDOR_DIRECTORY . 'houdini' . DIRECTORY_SEPARATOR );
} elseif ( is_dir( TP_VENDOR_DIRECTORY . 'Houdini' )) {
define( 'HOUDINI_ROOT_DIRECTORY', TP_VENDOR_DIRECTORY . 'Houdini' . DIRECTORY_SEPARATOR );
}
if ( is_dir( HOUDINI_ROOT_DIRECTORY . 'config' )) {
define( 'HOUDINI_CONFIG_DIRECTORY', HOUDINI_ROOT_DIRECTORY . 'config' . DIRECTORY_SEPARATOR );
}
// Shared Directories
define( 'BIN_DIRECTORY', APP_ROOT_DIRECTORY . 'bin' . DIRECTORY_SEPARATOR );
define( 'VIEW_DIRECTORY', APP_DIRECTORY . 'views' . DIRECTORY_SEPARATOR );
define( 'ERRORS_DIRECTORY', VIEW_DIRECTORY . 'errors' . DIRECTORY_SEPARATOR );
define( 'CLASSES_DIRECTORY', APP_DIRECTORY . 'classes' . DIRECTORY_SEPARATOR );
define( 'FUNCTIONS_DIRECTORY', APP_DIRECTORY . 'functions' . DIRECTORY_SEPARATOR );
define( 'RESOURCES_DIRECTORY', APP_DIRECTORY . 'resources' . DIRECTORY_SEPARATOR );
define( 'TEMPLATE_DIRECTORY', APP_DIRECTORY . 'templates' . DIRECTORY_SEPARATOR );
define( 'UPLOAD_DIRECTORY', APP_ROOT_DIRECTORY . 'uploads' . DIRECTORY_SEPARATOR );
define( 'IMAGE_UPLOAD_DIRECTORY', UPLOAD_DIRECTORY . 'images' . DIRECTORY_SEPARATOR );
// Files
define( 'COMPOSER_JSON_LOCATION', APP_ROOT_DIRECTORY . 'composer.json' );
define( 'COMPOSER_LOCK_LOCATION', APP_ROOT_DIRECTORY . 'composer.lock' );
define( 'CONFIG_JSON', CONFIG_DIRECTORY . 'config.json' );
// Other
define( 'EMAIL_FROM_EMAIL', 'noreply@localohost.com' );
// Sessions
define( 'DEFAULT_SESSION_PREFIX', 'TP_' );
// Token
define( 'DEFAULT_TOKEN_NAME', 'TP_SESSION_TOKEN' );
# Tell the app; all constants have been loaded
define( 'TEMPUS_PROJECT_CONSTANTS_LOADED', true );

48
app/controllers/admin/admin.php Executable file
View File

@ -0,0 +1,48 @@
<?php
/**
* app/controllers/admin/admin.php
*
* This is the admin log controller.
*
* @version 3.0
* @author Joey Kimsey <Joey@thetempusproject.com>
* @link https://TheTempusProject.com
* @license https://opensource.org/licenses/MIT [MIT LICENSE]
*/
namespace TheTempusProject\Controllers\Admin;
use TheTempusProject\Houdini\Classes\Issues;
use TheTempusProject\Houdini\Classes\Views;
use TheTempusProject\Bedrock\Functions\Input;
use TheTempusProject\Classes\AdminController;
use TheTempusProject\Models\Log;
class Admin extends AdminController {
public static $log;
public function __construct() {
parent::__construct();
self::$title = 'Admin - Admin Logs';
self::$log = new Log;
}
public function delete( $id = null ) {
if ( Input::exists( 'submit' ) ) {
$id = Input::post( 'A_' );
}
if ( self::$log->delete( $id ) ) {
Issues::add( 'success', 'Admin-log deleted' );
} else {
Issues::add( 'error', 'There was an error deleting log(s)' );
}
$this->index();
}
public function index() {
return Views::view( 'admin.logs.admin_list', self::$log->list( 'admin' ) );
}
public function view( $id = null ) {
return Views::view( 'admin.logs.admin', self::$log->findById( $id ) );
}
}

View File

@ -0,0 +1,64 @@
<?php
/**
* app/controllers/admin/composer.php
*
* This is the composer controller. Its only very effective when using composer for autoloading.
*
* @version 3.0
* @author Joey Kimsey <Joey@thetempusproject.com>
* @link https://TheTempusProject.com
* @license https://opensource.org/licenses/MIT [MIT LICENSE]
*/
namespace TheTempusProject\Controllers\Admin;
use TheTempusProject\Houdini\Classes\Views;
use TheTempusProject\Houdini\Classes\Issues;
use TheTempusProject\Classes\AdminController;
use TheTempusProject\Classes\Installer;
class Composer extends AdminController {
public function __construct() {
parent::__construct();
self::$title = 'Admin - Composer Dependencies';
}
public function index() {
$installer = new Installer;
// Files
$composerJson = $installer->getComposerJson();
if ( empty( $composerJson ) ) {
return Issues::add( 'error', 'Composer json is missing.' );
}
$composerLock = $installer->getComposerLock();
if ( empty( $composerLock ) ) {
return Issues::add( 'error', 'Composer lock file is missing.' );
}
// Required Packages
$requiredPackages = $composerJson[ 'require' ];
foreach ( $requiredPackages as $name => $version ) {
$versionsRequired[ strtolower( $name ) ] = $version;
}
// Installed Packages
$installedPackages = $composerLock[ 'packages' ];
foreach ( $installedPackages as $package ) {
$name = strtolower( $package[ 'name' ] );
$versionsInstalled[ $name ] = $package;
}
// Versioning
foreach ( $versionsInstalled as $package ) {
$name = strtolower( $package[ 'name' ] );
if ( !empty( $versionsRequired[ $name ] ) ) {
$versionsInstalled[ $name ][ 'requiredVersion' ] = $versionsRequired[ $name ];
} else {
$versionsInstalled[ $name ][ 'requiredVersion' ] = 'sub-dependency';
}
$out[] = (object) $versionsInstalled[ $name ];
}
Views::view( 'admin.modules.dependencies', $out );
}
}

View File

@ -0,0 +1,54 @@
<?php
/**
* app/controllers/admin/errors.php
*
* This is the error logs controller.
*
* @version 3.0
* @author Joey Kimsey <Joey@thetempusproject.com>
* @link https://TheTempusProject.com
* @license https://opensource.org/licenses/MIT [MIT LICENSE]
*/
namespace TheTempusProject\Controllers\Admin;
use TheTempusProject\Houdini\Classes\Issues;
use TheTempusProject\Houdini\Classes\Views;
use TheTempusProject\Bedrock\Functions\Input;
use TheTempusProject\Classes\AdminController;
use TheTempusProject\Models\Log;
class Errors extends AdminController {
public static $log;
public function __construct() {
parent::__construct();
self::$title = 'Admin - Error Logs';
self::$log = new Log;
}
public function delete( $id = null ) {
if ( Input::exists( 'submit' ) ) {
$id = Input::post( 'E_' );
}
if ( self::$log->delete( $id ) ) {
Issues::add( 'success', 'Error-log deleted' );
} else {
Issues::add( 'error', 'There was an error deleting log(s)' );
}
$this->index();
}
public function index() {
return Views::view( 'admin.logs.error_list', self::$log->list( 'error' ) );
}
public function view( $id = null ) {
return Views::view( 'admin.logs.error', self::$log->findById( $id ) );
}
public function clear() {
self::$log->clear( 'error' );
Issues::add( 'success', 'Error Logs Cleared' );
$this->index();
}
}

126
app/controllers/admin/groups.php Executable file
View File

@ -0,0 +1,126 @@
<?php
/**
* app/controllers/admin/groups.php
*
* This is the groups admin controller.
*
* @version 3.0
* @author Joey Kimsey <Joey@thetempusproject.com>
* @link https://TheTempusProject.com
* @license https://opensource.org/licenses/MIT [MIT LICENSE]
*/
namespace TheTempusProject\Controllers\Admin;
use TheTempusProject\Bedrock\Functions\Check;
use TheTempusProject\Bedrock\Functions\Input;
use TheTempusProject\Houdini\Classes\Navigation;
use TheTempusProject\Houdini\Classes\Components;
use TheTempusProject\Houdini\Classes\Issues;
use TheTempusProject\Houdini\Classes\Views;
use TheTempusProject\Houdini\Classes\Forms;
use TheTempusProject\Classes\AdminController;
use TheTempusProject\Classes\Permissions;
use TheTempusProject\Models\Group;
use TheTempusProject\TheTempusProject as App;
class Groups extends AdminController {
public static $group;
public static $permissions;
public function __construct() {
parent::__construct();
self::$title = 'Admin - Groups';
self::$group = new Group;
self::$permissions = new Permissions;
}
public function create( $data = null ) {
$perms = self::$group->getDefaultPermissions();
if ( Input::exists( 'name' ) ) {
$perms = self::$permissions->convertFormToArray();
if ( self::$group->create( Input::post( 'name' ), $perms ) ) {
Issues::add( 'success', 'Group created' );
return $this->index();
} else {
Issues::add( 'error', 'There was an error creating your group.' );
}
}
Components::set( 'PERMISSIONS_FORM', self::$permissions->getFormHtml( $perms ) );
Views::view( 'admin.groups.create' );
}
public function delete( $id = null ) {
if ( Input::exists( 'submit' ) ) {
$id = Input::post( 'G_' );
}
if ( self::$group->delete( $id ) ) {
Issues::add( 'success', 'Group deleted' );
} else {
Issues::add( 'error', 'There was an error deleting group(s)' );
}
$this->index();
}
public function edit( $data = null ) {
$group = self::$group->findById( $data );
if ( in_array( $group->name, self::$group::$protectedGroups ) ) {
switch ( $group->name ) {
case 'Super':
if ( 'Super' !== App::$activeGroup->name ) {
Issues::add( 'error', 'You do not have permission to do that.' );
return $this->index();
}
case 'Admin':
if ( 'Moderator' === App::$activeGroup->name ) {
Issues::add( 'error', 'You do not have permission to do that.' );
return $this->index();
}
}
}
$perms = $group->perms;
if ( Input::exists( 'name' ) ) {
$perms = self::$permissions->convertFormToArray();
// @ todo need to come up with a way to check these forms....
if ( self::$group->update( $data, Input::post( 'name' ), $perms ) ) {
Issues::add( 'success', 'Group updated' );
return $this->index();
} else {
Issues::add( 'error', 'There was an error with your request.' );
}
}
Components::set( 'PERMISSIONS_FORM', self::$permissions->getFormHtml( $perms ) );
Views::view( 'admin.groups.edit', $group );
}
public function index( $data = null ) {
Views::view( 'admin.groups.list', self::$group->listPaginated() );
}
public function listmembers( $data = null ) {
$groupData = self::$group->findById( $data );
if ( $groupData !== false ) {
Components::set( 'groupName', $groupData->name );
return Views::view( 'admin.groups.list_members', self::$group->listMembers( $groupData->ID ) );
}
Issues::add( 'error', 'Group not found' );
$this->index();
}
public function view( $data = null ) {
$groupData = self::$group->findById( $data );
if ( $groupData == false ) {
Issues::add( 'error', 'Group not found' );
return $this->index();
}
$out = '';
foreach ( self::$group->getDefaultPermissions() as $name => $default ) {
$node_name = $name . '_pretty';
$pretty_name = $groupData->$node_name;
$node_name2 = $name . '_text';
$pretty_value = $groupData->$node_name2;
$out .= '<tr><td>' . $pretty_name . '</td><td>' . $pretty_value . '</td></tr>';
}
Components::set( 'PERMISSIONS_ROWS', $out );
Views::view( 'admin.groups.view', $groupData );
}
}

88
app/controllers/admin/home.php Executable file
View File

@ -0,0 +1,88 @@
<?php
/**
* app/controllers/admin/home.php
*
* This is the admin dashboard controller.
*
* @version 3.0
* @author Joey Kimsey <Joey@thetempusproject.com>
* @link https://TheTempusProject.com
* @license https://opensource.org/licenses/MIT [MIT LICENSE]
*/
namespace TheTempusProject\Controllers\Admin;
use TheTempusProject\Houdini\Classes\Views;
use TheTempusProject\Houdini\Classes\Components;
use TheTempusProject\Classes\AdminController;
use TheTempusProject\Models\User;
use TheTempusProject\Models\Comments;
use TheTempusProject\Models\Posts;
use TheTempusProject\Models\Contact;
use TheTempusProject\Plugins\Comments as CommentPlugin;
use TheTempusProject\Plugins\Blog as BlogPlugin;
use TheTempusProject\Plugins\Contact as ContactPlugin;
use TheTempusProject\Canary\Bin\Canary as Debug;
use TheTempusProject\Bedrock\Functions\Input;
class Home extends AdminController {
public static $user;
public static $comments;
public static $posts;
public function __construct() {
parent::__construct();
self::$title = 'Admin - Home';
}
public function index() {
Components::set( 'commentDash', '' );
if ( class_exists( 'TheTempusProject\Plugins\Comments' ) ) {
$plugin = new CommentPlugin;
if ( ! $plugin->checkEnabled() ) {
Debug::info( 'Comments Plugin is disabled in the control panel.' );
} else {
$comments = new Comments;
$commentList = Views::simpleView( 'comments.admin.dashboard', $comments->recent( 'all', 5 ) );
Components::set( 'commentDash', $commentList );
}
}
if ( class_exists( 'TheTempusProject\Plugins\Blog' ) ) {
$plugin = new BlogPlugin;
if ( ! $plugin->checkEnabled() ) {
Debug::info( 'Blog Plugin is disabled in the control panel.' );
Components::set( 'blogDash', '' );
} else {
$posts = new Posts;
$postsList = Views::simpleView( 'blog.admin.dashboard', $posts->recent( 5 ) );
Components::set( 'blogDash', $postsList );
}
}
if ( class_exists( 'TheTempusProject\Plugins\Contact' ) ) {
$plugin = new ContactPlugin;
if ( ! $plugin->checkEnabled() ) {
Debug::info( 'Contact Plugin is disabled in the control panel.' );
Components::set( 'contactDash', '' );
} else {
$posts = new Contact;
$postsList = Views::simpleView( 'contact.admin.dashboard', $posts->listPaginated( 5 ) );
Components::set( 'contactDash', $postsList );
}
}
self::$user = new User;
$users = Views::simpleView( 'admin.dashboard.users', self::$user->recent( 5 ) );
Components::set( 'userDash', $users );
if ( Input::exists( 'submit' ) ) {
$results = Views::simpleView( 'admin.dashboard.users', self::$user->search( Input::post('searchTerm') ) );
Components::set( 'searchResults', $results );
}
Views::view( 'admin.dashboard.dash' );
}
}

View File

@ -0,0 +1,368 @@
<?php
/**
* app/controllers/admin/tokens.php
*
* This is the admin app/user tokens controller.
*
* @version 3.0
* @author Joey Kimsey <Joey@thetempusproject.com>
* @link https://TheTempusProject.com
* @license https://opensource.org/licenses/MIT [MIT LICENSE]
*/
namespace TheTempusProject\Controllers\Admin;
use TheTempusProject\Classes\Forms as TTPForms;
use TheTempusProject\Houdini\Classes\Views;
use TheTempusProject\Houdini\Classes\Issues;
use TheTempusProject\Houdini\Classes\Navigation;
use TheTempusProject\Houdini\Classes\Components;
use TheTempusProject\Houdini\Classes\Forms;
use TheTempusProject\Classes\AdminController;
use TheTempusProject\Models\Token;
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();
self::$title = 'Admin - Images';
}
public function create() {
if ( Input::exists( 'submit' ) ) {
if ( !TTPForms::check( 'addImage' ) ) {
Issues::add( 'error', [ 'There was an error with your image.' => Check::userErrors() ] );
}
if ( Input::exists( 'folder' ) ) {
$folder = Input::post('folder');
} else {
// IMAGE_DIRECTORY
$folder = UPLOAD_DIRECTORY . App::$activeUser->username . DIRECTORY_SEPARATOR . 'images' . DIRECTORY_SEPARATOR;
}
$upload = Upload::image( 'upload', $folder );
if ( $upload ) {
$route = str_replace( APP_ROOT_DIRECTORY, '', $folder );
$out = $route . Upload::last();
} else {
Debug::error( 'There was an error with your upload.');
Issues::add( 'error', [ 'There was an error with your upload.' => Check::userErrors() ] );
}
// if ( self::$token->create(
// Input::post( 'name' ),
// Input::post( 'notes' ),
// Input::post( 'token_type' )
// ) ) {
// Session::flash( 'success', 'Token Created' );
// Redirect::to( 'admin/images' );
// }
}
Views::view( 'admin.images.create' );
}
public function delete() {
if ( self::$token->delete( [ $id ] ) ) {
Session::flash( 'success', 'Token deleted.' );
}
Redirect::to( 'admin/images' );
}
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() ] );
}
}
}
return Views::view( 'admin.images.rename' );
}
public function index() {
return Views::view( 'admin.images.list.combined', $this->getAllImageDetails() );
}
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

@ -0,0 +1,54 @@
<?php
/**
* app/controllers/admin/logins.php
*
* This is the login logs controller.
*
* @version 3.0
* @author Joey Kimsey <Joey@thetempusproject.com>
* @link https://TheTempusProject.com
* @license https://opensource.org/licenses/MIT [MIT LICENSE]
*/
namespace TheTempusProject\Controllers\Admin;
use TheTempusProject\Houdini\Classes\Issues;
use TheTempusProject\Houdini\Classes\Views;
use TheTempusProject\Bedrock\Functions\Input;
use TheTempusProject\Classes\AdminController;
use TheTempusProject\Models\Log;
class Logins extends AdminController {
public static $log;
public function __construct() {
parent::__construct();
self::$title = 'Admin - Login Logs';
self::$log = new Log;
}
public function delete( $id = null ) {
if ( Input::exists( 'submit' ) ) {
$id = Input::post( 'L_' );
}
if ( self::$log->delete( $id ) ) {
Issues::add( 'success', 'Login-log deleted' );
} else {
Issues::add( 'error', 'There was an error deleting log(s)' );
}
$this->index();
}
public function index() {
return Views::view( 'admin.logs.login_list', self::$log->list( 'login' ) );
}
public function view( $id = null ) {
return Views::view( 'admin.logs.login', self::$log->findById( $id ) );
}
public function clear() {
self::$log->clear( 'login' );
Issues::add( 'success', 'Login Logs Cleared' );
$this->index();
}
}

33
app/controllers/admin/logs.php Executable file
View File

@ -0,0 +1,33 @@
<?php
/**
* app/controllers/admin/logs.php
*
* This is the generic logs controller.
*
* @version 3.0
* @author Joey Kimsey <Joey@thetempusproject.com>
* @link https://TheTempusProject.com
* @license https://opensource.org/licenses/MIT [MIT LICENSE]
*/
namespace TheTempusProject\Controllers\Admin;
use TheTempusProject\Houdini\Classes\Issues;
use TheTempusProject\Houdini\Classes\Views;
use TheTempusProject\Classes\AdminController;
use TheTempusProject\Models\Log;
class Logs extends AdminController {
public static $log;
public function __construct() {
parent::__construct();
self::$title = 'Admin - Logs';
self::$log = new Log;
}
public function index( $data = null ) {
Views::view( 'admin.logs.error_list', self::$log->list( 'error' ) );
Views::view( 'admin.logs.admin_list', self::$log->list( 'admin' ) );
Views::view( 'admin.logs.login_list', self::$log->list( 'login' ) );
}
}

136
app/controllers/admin/plugins.php Executable file
View File

@ -0,0 +1,136 @@
<?php
/**
* app/controllers/admin/installed.php
*
* This is the installed plugins controller.
*
* @version 3.0
* @author Joey Kimsey <Joey@thetempusproject.com>
* @link https://TheTempusProject.com
* @license https://opensource.org/licenses/MIT [MIT LICENSE]
*/
namespace TheTempusProject\Controllers\Admin;
use TheTempusProject\Bedrock\Functions\Input;
use TheTempusProject\Houdini\Classes\Views;
use TheTempusProject\Houdini\Classes\Components;
use TheTempusProject\Houdini\Classes\Navigation;
use TheTempusProject\Classes\AdminController;
use TheTempusProject\Classes\Installer;
use TheTempusProject\Classes\Plugin;
use TheTempusProject\Hermes\Functions\Redirect;
use TheTempusProject\Bedrock\Functions\Session;
class Plugins extends AdminController {
public $installer;
public $plugins;
public function __construct() {
parent::__construct();
self::$title = 'Admin - Installed Plugins';
$this->installer = new Installer;
$this->plugins = $this->installer->getAvailablePlugins();
}
public function index() {
Views::view( 'admin.modules.plugins.list', $this->plugins );
}
public function disable( $name = null ) {
if ( empty( $name ) ) {
Session::flash( 'error', 'Unknown Plugin.' );
Redirect::to( 'admin/plugins' );
}
Components::set( 'PLUGIN', $name );
self::$title = 'Admin - Disable ' . $name;
if ( !Input::exists( 'installHash' ) ) {
return Views::view( 'admin.modules.plugins.disable' );
}
if ( !Plugin::disable( $name ) ) {
Session::flash( 'error', 'There was an error disabling the plugin.' );
} else {
Session::flash( 'success', 'Plugin has been disabled.' );
}
Redirect::to( 'admin/plugins' );
}
public function enable( $name = null ) {
if ( empty( $name ) ) {
Session::flash( 'error', 'Unknown Plugin.' );
Redirect::to( 'admin/plugins' );
}
Components::set( 'PLUGIN', $name );
self::$title = 'Admin - Enable ' . $name;
if ( !Input::exists( 'installHash' ) ) {
return Views::view( 'admin.modules.plugins.enable' );
}
if ( ! Plugin::enable( $name ) ) {
Session::flash( 'error', 'There was an error enabling the plugin.' );
} else {
Session::flash( 'success', 'Plugin has been enabled.' );
}
Redirect::to( 'admin/plugins' );
}
public function install( $name = null ) {
if ( empty( $name ) ) {
Session::flash( 'error', 'Unknown Plugin.' );
Redirect::to( 'admin/plugins' );
}
$name = strtolower( $name );
Components::set( 'PLUGIN', $name );
self::$title = 'Admin - Install ' . $name;
if ( ! Input::exists( 'installHash' ) ) {
return Views::view( 'admin.modules.plugins.install' );
}
if ( empty( $this->plugins[$name] ) ) {
Session::flash( 'error', 'Unknown Plugin.' );
} else {
$result = $this->installer->installPlugin( $this->plugins[$name] );
if ( empty( $result ) ) {
Session::flash( 'error', [ 'There was an error with the install.' => $this->installer->getErrors() ] );
} else {
Session::flash( 'success', 'Plugin has been installed.' );
}
}
Redirect::to( 'admin/plugins' );
}
public function uninstall( $name = null ) {
if ( empty($name)) {
Session::flash( 'error', 'Unknown Plugin.' );
Redirect::to( 'admin/plugins' );
}
$name = strtolower($name);
Components::set( 'PLUGIN', $name );
self::$title = 'Admin - Uninstall ' . $name;
if ( !Input::exists( 'uninstallHash' ) ) {
return Views::view( 'admin.modules.plugins.uninstall' );
}
if ( empty( $this->plugins[$name] ) ) {
Session::flash( 'error', 'Unknown Plugin.' );
} else {
$result = $this->installer->uninstallPlugin( $this->plugins[$name] );
if ( empty($result) ) {
Session::flash( 'error', [ 'There was an error with the uninstall.' => $this->installer->getErrors() ] );
} else {
Session::flash( 'success', 'Plugin has been uninstalled.' );
}
}
Redirect::to( 'admin/plugins' );
}
public function view( $name = null ) {
$name = strtolower($name);
if ( empty( $this->plugins[$name] ) ) {
Session::flash( 'error', 'Unknown Plugin.' );
Redirect::to( 'admin/plugins' );
} else {
Views::view( 'admin.modules.plugins.view', $this->plugins[$name] );
}
}
}

101
app/controllers/admin/routes.php Executable file
View File

@ -0,0 +1,101 @@
<?php
/**
* app/controllers/admin/routes.php
*
* This is the admin routes/redirects controller.
*
* @version 3.0
* @author Joey Kimsey <Joey@thetempusproject.com>
* @link https://TheTempusProject.com
* @license https://opensource.org/licenses/MIT [MIT LICENSE]
*/
namespace TheTempusProject\Controllers\Admin;
use TheTempusProject\Classes\Forms as TTPForms;
use TheTempusProject\Houdini\Classes\Views;
use TheTempusProject\Houdini\Classes\Issues;
use TheTempusProject\Houdini\Classes\Navigation;
use TheTempusProject\Houdini\Classes\Components;
use TheTempusProject\Houdini\Classes\Forms;
use TheTempusProject\Classes\AdminController;
use TheTempusProject\Models\Routes as RoutesClass;
use TheTempusProject\Bedrock\Functions\Input;
use TheTempusProject\Bedrock\Functions\Check;
use TheTempusProject\Hermes\Functions\Redirect;
use TheTempusProject\Bedrock\Functions\Session;
class Routes extends AdminController {
public static $routes;
public function __construct() {
parent::__construct();
self::$title = 'Admin - Redirects';
self::$routes = new RoutesClass;
}
public function create() {
if ( ! Input::exists( 'redirect_type' ) ) {
return Views::view( 'admin.routes.create' );
}
if ( !TTPForms::check( 'createRoute' ) ) {
Issues::add( 'error', [ 'There was an error with your route.' => Check::userErrors() ] );
return Views::view( 'admin.routes.create' );
}
if ( self::$routes->create(
Input::post( 'original_url' ),
Input::post( 'forwarded_url' ),
Input::post( 'nickname' ),
Input::post( 'redirect_type' )
) ) {
Session::flash( 'success', 'Route Created' );
Redirect::to( 'admin/routes' );
}
Issues::add( 'error', 'There was an unknown error saving your redirect.' );
Views::view( 'admin.routes.create' );
}
public function delete( $id = null ) {
if ( Input::exists( 'submit' ) ) {
$id = Input::post( 'R_' );
}
if ( self::$routes->delete( [ $id ] ) ) {
Session::flash( 'success', 'Route(s) deleted.' );
} else {
Session::flash( 'error', 'There was an error with your request.' );
}
Redirect::to( 'admin/routes' );
}
public function edit( $id = null ) {
$route = self::$routes->findById( $id );
if ( Input::exists( 'redirect_type' ) ) {
if ( !TTPForms::check( 'editRoute' ) ) {
Issues::add( 'error', [ 'There was an error with your route.' => Check::userErrors() ] );
} else {
if ( self::$routes->update(
$id,
Input::post( 'original_url' ),
Input::post( 'forwarded_url' ),
Input::post( 'nickname' ),
Input::post( 'redirect_type' )
) ) {
Session::flash( 'success', 'Route Updated' );
Redirect::to( 'admin/routes' );
}
}
}
Forms::selectOption( $route->redirect_type );
return Views::view( 'admin.routes.edit', $route );
}
public function index() {
return Views::view( 'admin.routes.list', self::$routes->listPaginated() );
}
public function view( $id = null ) {
return Views::view( 'admin.routes.view', self::$routes->findById( $id ) );
}
}

View File

@ -0,0 +1,109 @@
<?php
/**
* app/controllers/admin/send_mail.php
*
* This is the admin email controller. The only real use is to send out emails to the various lists.
*
* @version 3.0
* @author Joey Kimsey <Joey@thetempusproject.com>
* @link https://TheTempusProject.com
* @license https://opensource.org/licenses/MIT [MIT LICENSE]
*/
namespace TheTempusProject\Controllers\Admin;
use TheTempusProject\Classes\AdminController;
use TheTempusProject\Classes\Email;
use TheTempusProject\Bedrock\Functions\Input;
use TheTempusProject\Houdini\Classes\Issues;
use TheTempusProject\Houdini\Classes\Views;
use TheTempusProject\Models\User;
use TheTempusProject\Models\Subscribe;
use TheTempusProject\Plugins\Subscribe as Plugin;
class SendMail extends AdminController {
public static $user;
public static $subscribe;
public function __construct() {
parent::__construct();
self::$title = 'Admin - Send Mail';
self::$user = new User;
if ( class_exists( 'TheTempusProject\Plugins\Subscribe' ) ) {
$plugin = new Plugin;
if ( ! $plugin->checkEnabled() ) {
Issues::add( 'notice', 'Subscriptions are disabled so those feature will be unavailable.' );
} else {
self::$subscribe = new Subscribe;
}
} else {
Issues::add( 'notice', 'Subscriptions plugin is not installed so those feature will be unavailable.' );
}
}
private function emailSubscribers( $params ) {
if ( empty( self::$subscribe ) ) {
Issues::add( 'error', 'Subscriptions plugin is unavailable' );
return;
}
$list = self::$subscribe->list();
if ( empty( $list ) ) {
Issues::add( 'error', 'No subscribers found' );
return;
}
foreach ( $list as $recipient ) {
$params[ 'confirmationCode' ] = $recipient->confirmationCode;
Email::send( $recipient->email, 'contact', $params, [ 'template' => true, 'unsubscribe' => true ] );
}
}
private function emailUsers( $params, $limit = null ) {
$list = self::$user->userList( $limit );
foreach ( $list as $recipient ) {
Email::send( $recipient->email, 'contact', $params, [ 'template' => true ] );
}
}
public function index() {
if ( Input::exists( 'mailType' ) ) {
$params = [
'subject' => Input::post( 'mailSubject' ),
'title' => Input::post( 'mailTitle' ),
'message' => Input::post( 'mailMessage' ),
];
switch ( Input::post( 'mailType' ) ) {
case 'registered':
$this->emailUsers( $params );
Issues::add( 'success', 'Email(s) Sent' );
break;
case 'newsletter':
$this->emailUsers( $params, 'newsletter' );
Issues::add( 'success', 'Email(s) Sent' );
break;
case 'all':
$this->emailUsers( $params );
$this->emailSubscribers( $params );
Issues::add( 'success', 'Email(s) Sent' );
break;
case 'opt':
$this->emailUsers( $params, 'newsletter' );
$this->emailSubscribers( $params );
Issues::add( 'success', 'Email(s) Sent' );
break;
case 'subscribers':
$this->emailSubscribers( $params );
Issues::add( 'success', 'Email(s) Sent' );
break;
default:
Issues::add( 'error', 'Invalid Request' );
break;
}
}
Views::view( 'admin.contact' );
}
}

View File

@ -0,0 +1,48 @@
<?php
/**
* app/controllers/admin/settings.php
*
* This is the configuration and settings controller.
*
* @version 3.0
* @author Joey Kimsey <Joey@thetempusproject.com>
* @link https://TheTempusProject.com
* @license https://opensource.org/licenses/MIT [MIT LICENSE]
*/
namespace TheTempusProject\Controllers\Admin;
use TheTempusProject\Bedrock\Functions\Input;
use TheTempusProject\Houdini\Classes\Components;
use TheTempusProject\Houdini\Classes\Views;
use TheTempusProject\Houdini\Classes\Issues;
use TheTempusProject\Houdini\Classes\Forms;
use TheTempusProject\Classes\AdminController;
use TheTempusProject\Models\Group;
use TheTempusProject\Classes\Config;
use TheTempusProject\TheTempusProject as App;
class Settings extends AdminController {
public static $group;
public function __construct() {
parent::__construct();
self::$title = 'Admin - Settings';
self::$group = new Group;
}
public function index() {
if ( Input::exists( 'submit' ) ) {
if ( !App::$activeConfig->updateFromForm( true ) ) {
Issues::add( 'error', [ 'There was an error with your request.' => Check::userErrors() ] );
} else {
Issues::add( 'success', 'Settings Updated' );
}
}
Components::set( 'configForm', Config::getEditHtml() );
Components::set(
'group-defaultGroup-options',
Forms::getOptionsHtml( self::$group->listGroupsSimple(), Config::getValue( 'group/defaultGroup' ) )
);
Views::view( 'admin.settings' );
}
}

View File

@ -0,0 +1,88 @@
<?php
/**
* app/controllers/admin/tokens.php
*
* This is the admin app/user tokens controller.
*
* @version 3.0
* @author Joey Kimsey <Joey@thetempusproject.com>
* @link https://TheTempusProject.com
* @license https://opensource.org/licenses/MIT [MIT LICENSE]
*/
namespace TheTempusProject\Controllers\Admin;
use TheTempusProject\Classes\Forms as TTPForms;
use TheTempusProject\Houdini\Classes\Views;
use TheTempusProject\Houdini\Classes\Issues;
use TheTempusProject\Houdini\Classes\Navigation;
use TheTempusProject\Houdini\Classes\Components;
use TheTempusProject\Houdini\Classes\Forms;
use TheTempusProject\Classes\AdminController;
use TheTempusProject\Models\Token;
use TheTempusProject\Bedrock\Functions\Input;
use TheTempusProject\Bedrock\Functions\Check;
use TheTempusProject\Hermes\Functions\Redirect;
use TheTempusProject\Bedrock\Functions\Session;
class Tokens extends AdminController {
public static $token;
public function __construct() {
parent::__construct();
self::$title = 'Admin - Tokens';
self::$token = new Token;
}
public function create() {
if ( Input::exists( 'submit' ) ) {
if ( !TTPForms::check( 'adminCreateToken' ) ) {
Issues::add( 'error', [ 'There was an error with your token.' => Check::userErrors() ] );
}
if ( self::$token->create(
Input::post( 'name' ),
Input::post( 'notes' ),
Input::post( 'token_type' )
) ) {
Session::flash( 'success', 'Token Created' );
Redirect::to( 'admin/tokens' );
}
}
Views::view( 'admin.tokens.create' );
}
public function delete( $id = null ) {
if ( self::$token->delete( [ $id ] ) ) {
Session::flash( 'success', 'Token deleted.' );
}
Redirect::to( 'admin/tokens' );
}
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' );
Redirect::to( 'admin/tokens' );
}
}
}
Forms::selectOption( $token->token_type );
return Views::view( 'admin.tokens.edit', $token );
}
public function index() {
return Views::view( 'admin.tokens.list', self::$token->listPaginated() );
}
public function view( $id = null ) {
return Views::view( 'admin.tokens.view', self::$token->findById( $id ) );
}
}

209
app/controllers/admin/users.php Executable file
View File

@ -0,0 +1,209 @@
<?php
/**
* app/controllers/admin/users.php
*
* This is the users admin controller.
*
* @version 3.0
* @author Joey Kimsey <Joey@thetempusproject.com>
* @link https://TheTempusProject.com
* @license https://opensource.org/licenses/MIT [MIT LICENSE]
*/
namespace TheTempusProject\Controllers\Admin;
use TheTempusProject\Bedrock\Functions\Check;
use TheTempusProject\Bedrock\Functions\Input;
use TheTempusProject\Bedrock\Functions\Code;
use TheTempusProject\Bedrock\Functions\Hash;
use TheTempusProject\Houdini\Classes\Navigation;
use TheTempusProject\Houdini\Classes\Components;
use TheTempusProject\Houdini\Classes\Forms;
use TheTempusProject\Houdini\Classes\Issues;
use TheTempusProject\Houdini\Classes\Views;
use TheTempusProject\Bedrock\Classes\Config;
use TheTempusProject\Classes\Forms as FormChecker;
use TheTempusProject\Classes\AdminController;
use TheTempusProject\Models\User;
use TheTempusProject\Models\Group;
use TheTempusProject\TheTempusProject as App;
use TheTempusProject\Houdini\Classes\Template;
use TheTempusProject\Bedrock\Functions\Upload;
class Users extends AdminController {
public static $user;
public static $group;
public function __construct() {
parent::__construct();
self::$title = 'Admin - Users';
self::$user = new User;
self::$group = new Group;
}
public function create() {
if ( Input::exists( 'submit' ) ) {
if ( !FormChecker::check( 'createUser' ) ) {
Issues::add( 'error', [ 'There was an error with your request.' => Check::userErrors() ] );
} else {
$fields = [
'username' => Input::post( 'username' ),
'password' => Hash::make( Input::post( 'password' ) ),
'email' => Input::post( 'email' ),
'userGroup' => Input::post( 'groupSelect' ),
'terms' => 0,
];
if ( !Input::exists( 'confirmation' ) ) {
$fields['confirmed'] = 1;
}
if ( self::$user->create( $fields ) ) {
Issues::add( 'success', 'User Created' );
return $this->index();
} else {
Issues::add( 'error', 'There was an error creating the user' );
}
}
}
$select = Forms::getSelectHtml(
'groupSelect',
self::$group->listGroupsSimple(),
Config::getValue( 'group/defaultGroup' ),
);
Components::set( 'groupSelect', $select );
Views::view( 'admin.users.create' );
}
public function delete( $id = null ) {
if ( Input::exists( 'submit' ) ) {
$id = Input::post( 'U_' );
}
if ( self::$user->delete( $id ) ) {
Issues::add( 'success', 'User deleted' );
} else {
Issues::add( 'error', 'There was an error deleting user(s)' );
}
$this->index();
}
public function edit( $id = null ) {
if ( !Check::id( $id ) ) {
return Issues::add( 'error', 'Invalid user' );
}
$userData = self::$user->findById( $id );
if ( in_array( $userData->groupName, self::$group::$protectedGroups ) ) {
switch ( $userData->groupName ) {
case 'Super':
if ( 'Super' !== App::$activeGroup->name ) {
Issues::add( 'error', 'You do not have permission to do that.' );
return $this->index();
}
case 'Admin':
if ( 'Super' !== App::$activeGroup->name ) {
Issues::add( 'error', 'You do not have permission to do that.' );
return $this->index();
}
}
}
if ( Input::exists( 'submit' ) ) {
if ( ! FormChecker::check( 'editUser' ) ) {
Issues::add( 'error', [ 'There was an error with your request.' => Check::userErrors() ] );
} else {
$fields = [
'username' => Input::post( 'username' ),
'email' => Input::post( 'email' ),
'userGroup' => Input::post( 'groupSelect' ),
];
if ( Input::exists( 'avatar' ) ) {
$folder = UPLOAD_DIRECTORY . $userData->username . DIRECTORY_SEPARATOR . 'images' . DIRECTORY_SEPARATOR;
$upload = Upload::image( 'avatar', $folder );
if ( $upload ) {
$route = str_replace( APP_ROOT_DIRECTORY, '', $folder );
$prefs = [];
$prefs['avatar'] = $route . Upload::last();
self::$user->updatePrefs( $prefs, $userData->ID );
} else {
Issues::add( 'error', [ 'There was an error with your avatar.' => Check::userErrors() ] );
}
}
if ( Input::exists( 'password' ) ) {
$fields['password'] = Hash::make( Input::post( 'password' ) );
}
if ( Input::exists( 'confirmed' ) ) {
$fields['confirmed'] = 1;
} else {
if ( Input::exists( 'confirmation' ) ) {
$fields['confirmationCode'] = Code::genConfirmation();
}
}
if ( self::$user->update( $userData->ID, $fields ) ) {
Issues::add( 'success', 'User Updated.' );
return $this->index();
} else {
Issues::add( 'notice', 'There was an error with your request, please try again.' );
}
}
}
if ( empty( $avatarLocation ) ) {
$avatarLocation = $userData->prefs['avatar'];
}
if ( empty( $userGroup ) ) {
$userGroup = $userData->userGroup;
}
Forms::selectRadio( 'confirmed', $userData->confirmed );
$avatar = $this->getAvatar( 'avatar', $avatarLocation );
Components::set( 'AvatarSettings', $avatar );
$select = Forms::getSelectHtml(
'groupSelect',
self::$group->listGroupsSimple(),
$userGroup,
);
Components::set( 'groupSelect', $select );
Views::view( 'admin.users.edit', $userData );
}
public function index() {
Views::view( 'admin.users.list', self::$user->listPaginated() );
}
public function view( $id = null ) {
if ( !empty( $id ) ) {
$userData = self::$user->findById( $id );
if ( $userData !== false ) {
return Views::view( 'admin.users.view', $userData );
}
Issues::add( 'error', 'User not found.' );
}
$this->index();
}
private function getAvatar( $name, $value ) {
$fieldname = str_ireplace( '/', '-', $name );
$html = '';
$fieldHtml = '';
$fieldHtml = Forms::getFileHtml( $fieldname );
$html .= '<div class="mb-3 row">';
$html .= ' <label for="' . $fieldname . '" class="col-lg-6 col-form-label text-end">' . ucfirst( $fieldname ) . '</label>';
$html .= ' <div class="col-lg-2">';
$html .= ' ' . $fieldHtml;
$html .= ' </div>';
$html .= '</div>';
$html .= '<div class="mb-3 row">';
$html .= ' <h4 class="col-lg-6 col-form-label text-end">Current Image</h4>';
$html .= ' <div class="col-lg-2">';
$html .= ' <img alt="User Avatar" src="{ROOT_URL}' . $value . '" class="img-circle img-fluid p-2 avatar-125">';
$html .= ' </div>';
$html .= '</div>';
return Template::parse( $html );
}
}

40
app/controllers/api/auth.php Executable file
View File

@ -0,0 +1,40 @@
<?php
/**
* app/controllers/api/auth.php
*
* This is the api authentication controller.
*
* @version 3.0
* @author Joey Kimsey <Joey@thetempusproject.com>
* @link https://TheTempusProject.com
* @license https://opensource.org/licenses/MIT [MIT LICENSE]
*/
namespace TheTempusProject\Controllers\Api;
use TheTempusProject\Models\User;
use TheTempusProject\Classes\ApiController;
use TheTempusProject\Houdini\Classes\Views;
use TheTempusProject\Models\Token;
class Auth extends ApiController {
public static $tokens;
public function __construct() {
parent::__construct();
self::$tokens = new Token;
}
/**
public function refresh() {
$token = self::$tokens->refresh( self::$authToken->ID );
if ( empty( $token ) ) {
$responseType = 'error';
$response = 'IRDK';
} else {
$responseType = 'token';
$response = $token;
}
Views::view( 'api.response', ['response' => json_encode( [ $responseType => $response ], true )]);
}
*/
}

52
app/controllers/api/login.php Executable file
View File

@ -0,0 +1,52 @@
<?php
/**
* app/controllers/api/auth.php
*
* This is the api authentication controller.
*
* @version 3.0
* @author Joey Kimsey <Joey@thetempusproject.com>
* @link https://TheTempusProject.com
* @license https://opensource.org/licenses/MIT [MIT LICENSE]
*/
namespace TheTempusProject\Controllers\Api;
use TheTempusProject\Classes\ApiController;
use TheTempusProject\Houdini\Classes\Views;
use TheTempusProject\Models\Token;
use TheTempusProject\Models\User;
use TheTempusProject\Houdini\Classes\Template;
use TheTempusProject\Classes\Forms;
use TheTempusProject\Bedrock\Functions\Input;
class Login extends ApiController {
public static $tokens;
public static $user;
public function __construct() {
parent::__construct( false );
self::$tokens = new Token;
self::$user = new User;
Template::addHeader( 'Access-Control-Allow-Origin: *' );
Template::addHeader( 'Content-Type: application/json; charset=utf-8' );
}
/**
public function index() {
if ( ! Forms::check( 'apiLogin' ) ) {
$responseType = 'error';
$response = 'malformed input';
return Views::view( 'api.response', ['response' => json_encode( [ $responseType => $response ], true )]);
}
$user = self::$user->authorize( Input::post( 'username' ), Input::post( 'password' ) );
if ( ! $user ) {
$responseType = 'error';
$response = 'bad credentials';
return Views::view( 'api.response', ['response' => json_encode( [ $responseType => $response ], true )]);
}
$responseType = 'token';
$token = self::$tokens->findOrCreateUserToken( $user->ID, true );
return Views::view( 'api.response', ['response' => json_encode( [ $responseType => $token ], true )]);
}
*/
}

45
app/controllers/api/users.php Executable file
View File

@ -0,0 +1,45 @@
<?php
/**
* app/controllers/api/users.php
*
* This is the users' api controller.
*
* @version 3.0
* @author Joey Kimsey <Joey@thetempusproject.com>
* @link https://TheTempusProject.com
* @license https://opensource.org/licenses/MIT [MIT LICENSE]
*/
namespace TheTempusProject\Controllers\Api;
use TheTempusProject\Models\User;
use TheTempusProject\Classes\ApiController;
use TheTempusProject\Houdini\Classes\Views;
class Users extends ApiController {
public static $user;
public function __construct() {
parent::__construct();
self::$user = new User;
}
/**
* This is actually just for testing. It can provide attack information in the way of user count if not disabled.
*
* @param [type] $id
* @return void
*/
/**
public function find( $id = null ) {
$user = self::$user->get( $id );
if ( ! $user ) {
$responseType = 'error';
$response = 'No user found.';
} else {
$responseType = 'data';
$response = $user->ID;
}
Views::view( 'api.response', ['response' => json_encode( [ $responseType => $response ], true )]);
}
*/
}

27
app/controllers/error.php Executable file
View File

@ -0,0 +1,27 @@
<?php
/**
* app/controllers/error.php
*
* This is the error controller.
*
* @version 3.0
* @author Joey Kimsey <Joey@thetempusproject.com>
* @link https://TheTempusProject.com
* @license https://opensource.org/licenses/MIT [MIT LICENSE]
*/
namespace TheTempusProject\Controllers;
use TheTempusProject\Classes\Controller;
use TheTempusProject\Houdini\Classes\Views;
class Error extends Controller {
public function index() {
self::$title = 'Error';
self::$pageDescription = 'The application has encountered an error.';
Views::view( 'errors.generic' );
}
public function upload404() {
Views::view( 'errors.upload404' );
}
}

115
app/controllers/home.php Executable file
View File

@ -0,0 +1,115 @@
<?php
/**
* app/controllers/home.php
*
* This is the home or 'index' controller.
*
* @version 3.0
* @author Joey Kimsey <Joey@thetempusproject.com>
* @link https://TheTempusProject.com
* @license https://opensource.org/licenses/MIT [MIT LICENSE]
*/
namespace TheTempusProject\Controllers;
use TheTempusProject\Hermes\Functions\Redirect;
use TheTempusProject\Bedrock\Functions\Session;
use TheTempusProject\Bedrock\Functions\Check;
use TheTempusProject\Bedrock\Functions\Input;
use TheTempusProject\Hermes\Functions\Route as Routes;
use TheTempusProject\Houdini\Classes\Issues;
use TheTempusProject\Houdini\Classes\Views;
use TheTempusProject\Houdini\Classes\Components;
use TheTempusProject\Houdini\Classes\Template;
use TheTempusProject\Classes\Controller;
use TheTempusProject\Classes\Forms;
use TheTempusProject\TheTempusProject as App;
class Home extends Controller {
public function index() {
self::$title = '{SITENAME}';
self::$pageDescription = '{SITENAME} is here to provide you a better, faster, and easier - way to create and manage your own web applications.';
Views::view( 'index' );
}
public function login() {
self::$title = 'Portal - {SITENAME}';
self::$pageDescription = 'Please log in to access all of the great features {SITENAME} has to offer.';
if ( App::$isLoggedIn ) {
return Issues::add( 'notice', 'You are already logged in. Please <a href="' . Routes::getAddress() . 'home/logout">click here</a> to log out.' );
}
if ( !Input::exists() ) {
return Views::view( 'auth.login' );
}
if ( !Forms::check( 'login' ) ) {
Issues::add( 'error', [ 'There was an error with your login.' => Check::userErrors() ] );
return Views::view( 'auth.login' );
}
if ( !self::$user->logIn( Input::post( 'username' ), Input::post( 'password' ), Input::post( 'remember' ) ) ) {
Issues::add( 'error', 'Username or password was incorrect.' );
return Views::view( 'auth.login' );
}
Session::flash( 'success', 'You have been logged in.' );
if ( Input::exists( 'rurl' ) ) {
Redirect::to( Input::post( 'rurl' ) );
} else {
Redirect::to( 'home/index' );
}
}
public function logout() {
self::$title = 'Log Out - {SITENAME}';
Template::noIndex();
if ( !App::$isLoggedIn ) {
return Issues::add( 'notice', 'You are not logged in.' );
}
self::$user->logOut();
Session::flash( 'success', 'You have been logged out.' );
Redirect::to( 'home/index' );
}
public function profile( $id = null ) {
self::$title = 'User Profile - {SITENAME}';
self::$pageDescription = 'User Profile - {SITENAME}';
if ( !App::$isLoggedIn ) {
return Issues::add( 'notice', 'You must be logged in to view this page.' );
}
$user = self::$user->get( $id );
if ( !$user ) {
return Issues::add( 'notice', 'No user found.' );
}
self::$title = $user->username . '\'s Profile - {SITENAME}';
self::$pageDescription = 'User Profile for ' . $user->username . ' - {SITENAME}';
Views::view( 'profilePage', $user );
}
public function terms() {
self::$title = 'Terms and Conditions - {SITENAME}';
self::$pageDescription = '{SITENAME} Terms and Conditions of use. Please use {SITENAME} safely.';
Components::set( 'TERMS', Views::simpleView( 'auth.terms' ) );
Views::view( 'termsPage' );
}
public function about() {
self::$title = 'About - {SITENAME}';
self::$pageDescription = '{SITENAME} was started by a developer with years of industry experience which has lead to a refined no-nonsense tool for everyone. Find out more about us here.';
Views::view( 'about' );
}
public function privacy() {
self::$title = 'Privacy Policy - {SITENAME}';
self::$pageDescription = 'At {SITENAME} you privacy is very important to us. On this page you can find a detailed outline of all the information we collect and how its used.';
Views::view( 'privacy' );
}
public function faq() {
self::$title = 'Frequently Asked Questions - {SITENAME}';
self::$pageDescription = 'Many times, we aren\'t the first to ask why or how something works. Here you will find a list of {SITENAME} commonly asked questions and our best answers.' ;
Views::view( 'faq' );
}
public function getstarted() {
self::$title = 'Get Started - {SITENAME}';
self::$pageDescription = '{SITENAME} is a great tool to bring your ideas to reality. On this page, you can find out how to get started today.' ;
Views::view( 'start' );
}
}

View File

@ -0,0 +1,125 @@
<?php
/**
* app/controllers/houdini.php
*
* This is the houdini controller.
*
* @version 3.0
* @author Joey Kimsey <Joey@thetempusproject.com>
* @link https://TheTempusProject.com
* @license https://opensource.org/licenses/MIT [MIT LICENSE]
*/
namespace TheTempusProject\Controllers;
use TheTempusProject\Hermes\Functions\Redirect;
use TheTempusProject\Houdini\Classes\Views;
use TheTempusProject\Classes\Controller;
class Libraries extends Controller {
public function index() {
self::$title = 'Libraries - {SITENAME}';
self::$pageDescription = '{SITENAME} depends on several very important libraries, some of which are developed exclusively to support the project. Here you can find a list with more information.' ;
Views::view( 'deps.index' );
}
public function ttp( $method = null ) {
self::$title = '{SITENAME} - TheTempusProject';
self::$pageDescription = 'TheTempusProject is the primary repo of {SITENAME} which houses the main application.';
if ( empty( $method ) ) {
return Views::view( 'deps.ttp' );
}
switch ( $method ) {
case 'git':
return Redirect::external( 'https://git.thetempusproject.com/the-tempus-project/thetempusproject' );
case 'packagist':
return Redirect::external( 'https://packagist.org/packages/thetempusproject/thetempusproject' );
case 'changes':
self::$title .= ' Changes';
self::$pageDescription = 'This pages lists the most recent changes to TheTempusProject with some details on those changes.';
return Views::view( 'changes.ttp' );
default:
return Views::view( 'deps.ttp' );
}
}
public function hermes( $method = null ) {
self::$title = '{SITENAME} - Hermes';
self::$pageDescription = 'Hermes is a dependency of {SITENAME} that provides many common helper functions for navigating url components and file systems; designed to work seamlessly regardless of architecture.';
if ( empty( $method ) ) {
return Views::view( 'deps.hermes' );
}
switch ( $method ) {
case 'git':
return Redirect::external( 'https://git.thetempusproject.com/the-tempus-project/hermes' );
case 'packagist':
return Redirect::external( 'https://packagist.org/packages/thetempusproject/hermes' );
case 'changes':
self::$title .= ' Changes';
self::$pageDescription = 'Hermes is a dependency of {SITENAME} and this pages lists the most recent changes with some details on those changes.';
return Views::view( 'changes.hermes' );
default:
return Views::view( 'deps.hermes' );
}
}
public function canary( $method = null ) {
self::$title = '{SITENAME} - Canary';
self::$pageDescription = 'Canary is a dependency of {SITENAME} that both records and reports logs from various PHP applications.';
if ( empty( $method ) ) {
return Views::view( 'deps.canary' );
}
switch ( $method ) {
case 'git':
return Redirect::external( 'https://git.thetempusproject.com/the-tempus-project/canary' );
case 'packagist':
return Redirect::external( 'https://packagist.org/packages/thetempusproject/canary' );
case 'changes':
self::$title .= ' Changes';
self::$pageDescription = 'Canary is a dependency of {SITENAME} and this pages lists the most recent changes with some details on those changes.';
return Views::view( 'changes.canary' );
default:
return Views::view( 'deps.canary' );
}
}
public function bedrock( $method = null ) {
self::$title = '{SITENAME} - Bedrock';
self::$pageDescription = 'Bedrock is a dependency of {SITENAME} that provides many components used to manipulate database data and many helper functions vital for running the entire application.';
if ( empty( $method ) ) {
return Views::view( 'deps.bedrock' );
}
switch ( $method ) {
case 'git':
return Redirect::external( 'https://git.thetempusproject.com/the-tempus-project/bedrock' );
case 'packagist':
return Redirect::external( 'https://packagist.org/packages/thetempusproject/bedrock' );
case 'changes':
self::$title .= ' Changes';
self::$pageDescription = 'Bedrock is a dependency of {SITENAME} and this pages lists the most recent changes with some details on those changes.';
return Views::view( 'changes.bedrock' );
default:
return Views::view( 'deps.bedrock' );
}
}
public function houdini( $method = null ) {
self::$title = '{SITENAME} - Houdini';
self::$pageDescription = 'Houdini is a dependency of {SITENAME} that allows for the creation and manipulation of objects used in html page creation.';
if ( empty( $method ) ) {
return Views::view( 'deps.houdini' );
}
switch ( $method ) {
case 'git':
return Redirect::external( 'https://git.thetempusproject.com/the-tempus-project/houdini' );
case 'packagist':
return Redirect::external( 'https://packagist.org/packages/thetempusproject/houdini' );
case 'changes':
self::$title .= ' Changes';
self::$pageDescription = 'Houdini is a dependency of {SITENAME} and this pages lists the most recent changes with some details on those changes.';
return Views::view( 'changes.houdini' );
default:
return Views::view( 'deps.houdini' );
}
}
}

148
app/controllers/register.php Executable file
View File

@ -0,0 +1,148 @@
<?php
/**
* app/controllers/register.php
*
* This is the user registration controller.
*
* @version 3.0
* @author Joey Kimsey <Joey@thetempusproject.com>
* @link https://TheTempusProject.com
* @license https://opensource.org/licenses/MIT [MIT LICENSE]
*/
namespace TheTempusProject\Controllers;
use TheTempusProject\Houdini\Classes\Template;
use TheTempusProject\Classes\Email;
use TheTempusProject\Bedrock\Functions\Check;
use TheTempusProject\Bedrock\Functions\Session;
use TheTempusProject\Bedrock\Functions\Input;
use TheTempusProject\Bedrock\Functions\Hash;
use TheTempusProject\Hermes\Functions\Redirect;
use TheTempusProject\Houdini\Classes\Issues;
use TheTempusProject\Houdini\Classes\Components;
use TheTempusProject\Houdini\Classes\Views;
use TheTempusProject\TheTempusProject as App;
use TheTempusProject\Classes\Controller;
use TheTempusProject\Classes\Forms;
use TheTempusProject\Bedrock\Classes\Config;
class Register extends Controller {
public function confirm( $code = null ) {
Template::noIndex();
self::$title = 'Confirm Email';
if ( !isset( $code ) && !Input::exists( 'confirmationCode' ) ) {
return Views::view( 'auth.confirmation' );
}
if ( Forms::check( 'emailConfirmation' ) ) {
$code = Input::post( 'confirmationCode' );
}
if ( !self::$user->confirm( $code ) ) {
Issues::add( 'error', 'There was an error confirming your account, please try again.' );
return Views::view( 'auth.confirmation' );
}
Session::flash( 'success', 'You have successfully confirmed your email address.' );
Redirect::to( 'home/index' );
}
public function index() {
self::$title = '{SITENAME} Sign Up';
self::$pageDescription = 'Many features of {SITENAME} are disabled or hidden from unregistered users. On this page you can sign up for an account to access all the app has to offer.';
if ( ! Config::getValue( 'main/registrationEnabled' ) ) {
return Issues::add( 'notice', 'The site administrator has disable the ability to register a new account.' );
}
Components::set( 'TERMS', Views::simpleView( 'auth.terms' ) );
if ( App::$isLoggedIn ) {
return Issues::add( 'notice', 'You are currently logged in.' );
}
if ( !Input::exists() ) {
return Views::view( 'auth.register' );
}
if ( !Forms::check( 'register' ) ) {
Issues::add( 'error', [ 'There was an error with your registration.' => Check::userErrors() ] );
return Views::view( 'auth.register' );
}
self::$user->create( [
'username' => Input::post( 'username' ),
'password' => Hash::make( Input::post( 'password' ) ),
'email' => Input::post( 'email' ),
'terms' => 1,
] );
Session::flash( 'success', 'Thank you for registering! Please check your email to confirm your account.' );
Redirect::to( 'home/index' );
}
/**
* @todo Come back and separate this into multiple forms because this is gross.
*/
public function recover() {
self::$title = 'Recover Account - {SITENAME}';
Template::noIndex();
if ( !Input::exists() ) {
return Views::view( 'auth.forgot' );
}
if ( Check::email( Input::post( 'entry' ) ) && self::$user->findByEmail( Input::post( 'entry' ) ) ) {
$userData = self::$user->data();
Email::send( $userData->email, 'forgotUsername', $userData->username, [ 'template' => true ] );
Session::flash( 'notice', 'Your Username has been sent to your registered email address.' );
Redirect::to( 'home/login' );
} elseif ( self::$user->get( Input::post( 'entry' ) ) ) {
self::$user->newCode( self::$user->data()->ID );
self::$user->get( Input::post( 'entry' ) );
$userData = self::$user->data();
Email::send( $userData->email, 'forgotPassword', $userData->confirmationCode, [ 'template' => true ] );
Session::flash( 'notice', 'Details for resetting your password have been sent to your registered email address' );
Redirect::to( 'home/login' );
}
Issues::add( 'error', 'User not found.' );
Views::view( 'auth.forgot' );
}
public function resend() {
self::$title = 'Resend Confirmation';
Template::noIndex();
if ( !App::$isLoggedIn ) {
return Issues::add( 'notice', 'Please log in to resend your confirmation email.' );
}
if ( App::$activeUser->confirmed == '1' ) {
return Issues::add( 'notice', 'Your account has already been confirmed.' );
}
if ( !Forms::check( 'confirmationResend' ) ) {
return Views::view( 'auth.confirmation_resend' );
}
Email::send( App::$activeUser->email, 'confirmation', App::$activeUser->confirmationCode, [ 'template' => true ] );
Session::flash( 'success', 'Your confirmation email has been sent to the email for your account.' );
Redirect::to( 'home/index' );
}
public function reset( $code = null ) {
self::$title = 'Password Reset';
Template::noIndex();
if ( !isset( $code ) && !Input::exists( 'resetCode' ) ) {
Issues::add( 'info', 'Please provide a reset code.' );
return Views::view( 'auth.password_reset_code' );
}
if ( Input::exists( 'resetCode' ) ) {
if ( Forms::check( 'passwordResetCode' ) ) {
$code = Input::post( 'resetCode' );
}
}
if ( ! self::$user->checkCode( $code ) ) {
Issues::add( 'error', 'There was an error with your reset code. Please try again.' );
return Views::view( 'auth.password_reset_code' );
}
Components::set( 'resetCode', $code );
if ( ! Input::exists('password') ) {
return Views::view( 'auth.password_reset' );
}
if ( ! Forms::check( 'passwordReset' ) ) {
Issues::add( 'error', [ 'There was an error with your request.' => Check::userErrors() ] );
return Views::view( 'auth.password_reset' );
}
self::$user->changePassword( $code, Input::post( 'password' ) );
Email::send( self::$user->data()->email, 'passwordChange', null, [ 'template' => true ] );
Session::flash( 'success', 'Your Password has been changed, please use your new password to log in.' );
Redirect::to( 'home/login' );
}
}

157
app/controllers/usercp.php Executable file
View File

@ -0,0 +1,157 @@
<?php
/**
* app/controllers/usercp.php
*
* This is the user control panel controller.
*
* @version 3.0
* @author Joey Kimsey <Joey@thetempusproject.com>
* @link https://TheTempusProject.com
* @license https://opensource.org/licenses/MIT [MIT LICENSE]
*/
namespace TheTempusProject\Controllers;
use TheTempusProject\Houdini\Classes\Template;
use TheTempusProject\Classes\Email;
use TheTempusProject\Bedrock\Functions\Code;
use TheTempusProject\Bedrock\Functions\Check;
use TheTempusProject\Bedrock\Functions\Input;
use TheTempusProject\Bedrock\Functions\Hash;
use TheTempusProject\Houdini\Classes\Components;
use TheTempusProject\Houdini\Classes\Views;
use TheTempusProject\Houdini\Classes\Issues;
use TheTempusProject\Houdini\Classes\Navigation;
use TheTempusProject\TheTempusProject as App;
use TheTempusProject\Classes\Controller;
use TheTempusProject\Classes\Preferences;
use TheTempusProject\Classes\Forms;
use TheTempusProject\Hermes\Functions\Redirect;
use TheTempusProject\Bedrock\Functions\Session;
class Usercp extends Controller {
public function __construct() {
parent::__construct();
if ( !App::$isLoggedIn ) {
Session::flash( 'notice', 'You must be logged in to view this page!' );
Redirect::home();
}
Template::noIndex();
}
public function email() {
self::$title = 'Email Settings';
$menu = Views::simpleView( 'nav.usercp', App::$userCPlinks );
Navigation::activePageSelect( $menu, null, true, true );
if ( App::$activeUser->confirmed != '1' ) {
return Issues::add( 'notice', 'You need to confirm your email address before you can make modifications. If you would like to resend that confirmation link, please <a href="/register/resend">click here</a>', true );
}
if ( !Input::exists() ) {
return Views::view( 'user_cp.email_change' );
}
if ( !Forms::check( 'changeEmail' ) ) {
Issues::add( 'error', [ 'There was an error with your request.' => Check::userErrors() ] );
return Views::view( 'user_cp.email_change' );
}
$code = Code::genConfirmation();
self::$user->update(
App::$activeUser->ID,
[
'confirmed' => 0,
'email' => Input::post( 'email' ),
'confirmationCode' => $code,
],
);
Email::send( App::$activeUser->email, 'emailChangeNotice', $code, [ 'template' => true ] );
Email::send( Input::post( 'email' ), 'emailChange', $code, [ 'template' => true ] );
Issues::add( 'notice', 'Email has been changed, please check your email to confirm it.' );
}
public function index() {
self::$title = 'User Control Panel';
$menu = Views::simpleView( 'nav.usercp', App::$userCPlinks );
Navigation::activePageSelect( $menu, null, true, true );
Views::view( 'user_cp.profile', App::$activeUser );
}
public function password() {
self::$title = 'Password Settings';
$menu = Views::simpleView( 'nav.usercp', App::$userCPlinks );
Navigation::activePageSelect( $menu, null, true, true );
if ( !Input::exists() ) {
return Views::view( 'user_cp.password_change' );
}
if ( !Hash::check( Input::post( 'curpass' ), App::$activeUser->password ) ) {
Issues::add( 'error', 'Current password was incorrect.' );
return Views::view( 'user_cp.password_change' );
}
if ( !Forms::check( 'changePassword' ) ) {
Issues::add( 'error', [ 'There was an error with your request.' => Check::userErrors() ] );
return Views::view( 'user_cp.password_change' );
}
self::$user->update(
App::$activeUser->ID,
[ 'password' => Hash::make( Input::post( 'password' ) ) ],
);
Email::send( App::$activeUser->email, 'passwordChange', null, [ 'template' => true ] );
Issues::add( 'notice', 'Your Password has been changed!' );
}
public function settings() {
self::$title = 'Preferences';
$menu = Views::simpleView( 'nav.usercp', App::$userCPlinks );
Navigation::activePageSelect( $menu, null, true, true );
$prefs = new Preferences;
$userPrefs = App::$activePrefs;
if ( Input::exists( 'submit' ) ) {
$fields = $prefs->convertFormToArray( true, false );
// @TODO now i may need to rework the form checker to work with this....
// if (!Forms::check('userPrefs')) {
// Issues::add( 'error', [ 'There was an error with your request.' => Check::userErrors() ] );
// }
self::$user->updatePrefs( $fields, App::$activeUser->ID );
Issues::add( 'success', 'Your preferences have been updated.' );
// if the image upload fails, need to fall back on original
if ( empty( $fields['avatar'] ) ) {
$fields['avatar'] = $userPrefs['avatar'];
}
} else {
$fields = $userPrefs;
}
Components::set( 'AVATAR_SETTINGS', $fields['avatar'] );
Components::set( 'PREFERENCES_FORM', $prefs->getFormHtml( $fields ) );
Views::view( 'user_cp.settings', App::$activeUser );
}
public function updatePref() {
Template::setTemplate( 'api' );
if ( ! App::$isLoggedIn ) {
return Views::view( 'api.response', ['response' => json_encode( [ 'error' => 'Not Logged In' ], true )]);
}
if ( ! Forms::check( 'updatePreference' ) ) {
return Views::view( 'api.response', ['response' => json_encode( [ 'error' => Check::userErrors() ], true )]);
}
$name = Input::post( 'prefName' );
$value = Input::post('prefValue' );
if ( 'false' === $value ) {
$value = false;
} elseif ( 'true' === $value ) {
$value = true;
}
if ( empty( Preferences::get( $name ) ) ) {
return Views::view( 'api.response', ['response' => json_encode( [ 'error' => 'Unknown Preference' ], true )]);
}
$prefs = new Preferences;
$fields1 = $prefs->convertFormToArray( true, false );
$fields3 = $fields1;
if ( isset( $fields1[ $name ] ) ) {
$fields3[ $name ] = $value;
}
$result = self::$user->updatePrefs( $fields3, App::$activeUser->ID );
return Views::view( 'api.response', ['response' => json_encode( $result, true )]);
}
}

74
app/css/debug.css Executable file
View File

@ -0,0 +1,74 @@
/**
* app/css/debug.css
*
* This is css used in the debuging console.
*
* @version 3.0
* @author Joey Kimsey <Joey@thetempusproject.com>
* @link https://TheTempusProject.com
* @license https://opensource.org/licenses/MIT [MIT LICENSE]
*/
a {
color: #0000ff;
text-decoration: none;
}
a:hover {
text-decoration: underline;
}
#debug-wrapper {
margin: 0 auto;
margin-bottom: 20px;
background: #eee;
width: 1000px;
max-width: 100%;
border: 2px solid #212121;
border-radius: 4px;
}
#debug-log {
text-align: left;
margin: 0 auto;
margin-bottom: 25px;
margin-left: 25px;
margin-right: 25px;
padding: 10px;
background: #fff;
height: 300px;
border: 1px solid #a7a7a7;
overflow: auto;
border-radius: 4px;
border-bottom: 4px solid #a7a7a7;
}
.debug-log {
flex: 1;
}
.debug-log-error {
border-radius: 4px;
border-left: 5px solid #ff0000;
border-bottom: 2px solid #ff0000;
}
.debug-log-info {
border-radius: 4px;
border-left: 5px solid #0800ff;
border-bottom: 2px solid #0800ff;
}
.debug-log-warn {
border-radius: 4px;
border-left: 5px solid #eaff00;
border-bottom: 2px solid #eaff00;
}
#debug-header {
padding: 15px 25px;
display: flex;
}
#debug-header p {
flex: 1;
}

158
app/css/main-dark.css Executable file
View File

@ -0,0 +1,158 @@
/**
* app/css/main-dark.css
*
* This file provides dark mode styles to override existing Bootstrap 5 base styles.
*
* @version 3.0-dark
* @author Joey Kimsey <Joey@thetempusproject.com>
* @link https://TheTempusProject.com
* @license https://opensource.org/licenses/MIT [MIT LICENSE]
*/
.context-main-border {
border-color: #f5f5f5!important;
}
.context-main-bg {
background-color: #2c2c2c;
}
.context-second-bg {
background-color: #383838;
}
.context-third-bg {
background-color: #3a3a3a;
}
.context-other-bg {
background-color: #1e1e1e;
}
.context-main {
color: #fff;
}
.bg-default {
background-color: #2c2c2c;
}
hr {
color: #f5f5f5;
}
.bg-none,.bg-warning {
color: #000 !important;
}
.accordion-button:not(.collapsed) {
color: #f5f5f5;
background-color: var(--bs-accordion-dark-active-bg);
}
body {
background-image: linear-gradient(180deg, #2c2c2c, #1e1e1e 100px, #1e1e1e);
color: #f5f5f5;
}
/**
* Install Terms
*/
.install-terms {
border: 1px solid #555;
background: #3a3a3a;
}
.install-terms p,
.install-terms li {
color: #dcdcdc;
}
.install-terms h3 {
color: #ffffff;
}
.install-terms h4 {
color: #eaeaea;
}
.install-terms strong {
color: #ffffff;
}
/**
* Terms Page
*/
.terms-page {
border: 1px solid #555;
background: #3a3a3a;
}
.terms-page p,
.terms-page li {
color: #dcdcdc;
}
.terms-page h3 {
color: #ffffff;
}
.terms-page h4 {
color: #eaeaea;
}
.terms-page strong {
color: #ffffff;
}
/**
* Terms
*/
.terms {
border: 1px solid #555;
background: #3a3a3a;
}
.terms p,
.terms li {
color: #dcdcdc;
}
.terms h3 {
color: #ffffff;
}
.terms h4 {
color: #eaeaea;
}
.terms strong {
color: #ffffff;
}
/**
* Form Control
*/
.form-control-dark:focus {
border-color: #1e90ff;
box-shadow: 0 0 0 .25rem rgba(30, 144, 255, .5);
}
/**
* Example Divider
*/
.b-example-divider {
background-color: rgba(255, 255, 255, .1);
}
/**
* Text Shadows
*/
.text-shadow-1 {
text-shadow: 0 .125rem .25rem rgba(255, 255, 255, .25);
}
.text-shadow-2 {
text-shadow: 0 .25rem .5rem rgba(255, 255, 255, .25);
}
.text-shadow-3 {
text-shadow: 0 .5rem 1.5rem rgba(255, 255, 255, .25);
}
.form-control {
background-color: #1f1f1f;
color: #e0e0e0;
}
.form-control:focus {
color: #e0e0e0;
border-color: #1e90ff;
background-color: #1f1f1f;
box-shadow: 0 0 0 .25rem rgba(30, 144, 255, .5);
}

322
app/css/main.css Executable file
View File

@ -0,0 +1,322 @@
/**
* app/css/main.css
*
* This file is for any css that should be applied site wide.
*
* @version 3.0
* @author Joey Kimsey <Joey@thetempusproject.com>
* @link https://TheTempusProject.com
* @license https://opensource.org/licenses/MIT [MIT LICENSE]
*/
.context-main-border {
border-color: #1e1e1e!important;
}
.context-main-bg {
background-color: #f7f7f7;
/* background-color: #b1b; */
}
.context-second-bg {
background-color: #eaeaea;
/* background-color: #b1b; */
}
.context-third-bg {
background-color: #ccc;
/* background-color: #b1b; */
}
.context-main {
color: #000;
}
.nav-link.active {
font-weight: bold; /* Make the text bold */
}
hr {
color: #000;
}
/* Base styles for the switch container */
.material-switch {
position: relative;
display: inline-block;
width: 50px;
height: 25px;
}
/* Hide the default checkbox */
.material-switch input {
opacity: 0;
width: 0;
height: 0;
}
/* Style the label as the switch */
.material-switch .label-default {
position: absolute;
cursor: pointer;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: var(--switch-off-bg, #ccc);
border-radius: 25px;
transition: background-color 0.3s ease-in-out;
}
/* Style the toggle circle (slider) */
.material-switch .label-default::before {
content: '';
position: absolute;
height: 20px;
width: 20px;
border-radius: 50%;
background-color: var(--switch-slider-bg, #fff);
bottom: 2.5px;
left: 5px;
transition: transform 0.3s ease-in-out;
box-shadow: 0 2px 4px #00000033;
}
/* Change background color when checked */
.material-switch input:checked + .label-default {
background-color: var(--switch-on-bg, #555); /* Bootstrap primary color */
}
/* Move the slider when checked */
.material-switch input:checked + .label-default::before {
transform: translateX(25px); /* Adjust based on switch width */
}
html {
font-family: 'Open Sans', sans-serif;
position: relative;
min-height: 100%;
}
pre {
white-space: pre-wrap;
}
body {
background-color: #e4e4e4;
/* background-image: linear-gradient(180deg, #eee, #fff 100px, #fff); */
}
@media ( min-width: 768px ) {
.main {
padding-right: 40px;
padding-left: 40px;
}
#wrapper {
padding-right: 225px;
padding-left: 0;
}
.bd-placeholder-img-lg {
font-size: 3.5rem;
}
}
/**
* Install Terms
*/
.install-terms {
text-align: left;
width: 800px;
height: 600px;
border: 1px solid #ccc;
background: #f2f2f2;
padding: 6px;
overflow: auto;
}
.install-terms p,
.install-terms li {
font: normal 11px/15px arial;
color: #333;
}
.install-terms h3 {
font: bold 14px/19px arial;
color: #000;
}
.install-terms h4 {
font: bold 12px/17px arial;
color: #000;
}
.install-terms strong {
color: #000;
}
/**
* Terms Page
*/
.terms-page {
text-align: left;
border: 1px solid #ccc;
background: #f2f2f2;
padding: 6px;
overflow: auto;
margin-bottom: 70px;
}
.terms-page p,
.terms-page li {
font: normal 11px/15px arial;
color: #333;
}
.terms-page h3 {
font: bold 14px/19px arial;
color: #000;
}
.terms-page h4 {
font: bold 12px/17px arial;
color: #000;
}
.terms-page strong {
color: #000;
}
/**
* Terms
*/
.terms {
text-align: left;
width: 450px;
height: 150px;
border: 1px solid #ccc;
background: #f2f2f2;
padding: 6px;
overflow: auto;
}
.terms p,
.terms li {
font: normal 11px/15px arial;
color: #333;
}
.terms h3 {
font: bold 14px/19px arial;
color: #000;
}
.terms h4 {
font: bold 12px/17px arial;
color: #000;
}
.terms strong {
color: #000;
}
.pricing-header {
max-width: 700px;
}
.pricing-container {
max-width: 960px;
}
.bd-placeholder-img {
font-size: 1.125rem;
text-anchor: middle;
-webkit-user-select: none;
-moz-user-select: none;
user-select: none;
}
.b-example-vr {
flex-shrink: 0;
width: 1.5rem;
height: 100vh;
}
.bi {
vertical-align: -.125em;
fill: currentColor;
}
.form-control-dark {
border-color: var(--bs-gray);
}
.form-control-dark:focus {
border-color: #fff;
box-shadow: 0 0 0 .25rem rgba(255, 255, 255, .25);
}
.text-small {
font-size: 85%;
}
.dropdown-toggle {
outline: 0;
}
.b-example-divider {
height: 3rem;
background-color: rgba(0, 0, 0, .1);
border: solid rgba(0, 0, 0, .15);
border-width: 1px 0;
box-shadow: inset 0 .5em 1.5em rgba(0, 0, 0, .1), inset 0 .125em .5em rgba(0, 0, 0, .15);
}
.b-example-vr {
flex-shrink: 0;
width: 1.5rem;
height: 100vh;
}
.nav-scroller {
position: relative;
z-index: 2;
height: 2.75rem;
overflow-y: hidden;
}
.nav-scroller .nav {
display: flex;
flex-wrap: nowrap;
padding-bottom: 1rem;
margin-top: -1px;
overflow-x: auto;
text-align: center;
white-space: nowrap;
-webkit-overflow-scrolling: touch;
}
.b-example-vr {
flex-shrink: 0;
width: 1.5rem;
height: 100vh;
}
.feature-icon {
width: 4rem;
height: 4rem;
border-radius: .75rem;
}
.icon-link > .bi {
margin-top: .125rem;
margin-left: .125rem;
fill: currentcolor;
transition: transform .25s ease-in-out;
}
.icon-link:hover > .bi {
transform: translate(.25rem);
}
.icon-square {
width: 3rem;
height: 3rem;
border-radius: .75rem;
}
.text-shadow-1 {
text-shadow: 0 .125rem .25rem rgba(0, 0, 0, .25);
}
.text-shadow-2 {
text-shadow: 0 .25rem .5rem rgba(0, 0, 0, .25);
}
.text-shadow-3 {
text-shadow: 0 .5rem 1.5rem rgba(0, 0, 0, .25);
}
.card-cover {
background-repeat: no-repeat;
background-position: center center;
background-size: cover;
}
.feature-icon-small {
width: 3rem;
height: 3rem;
}
.gradient-custom-2 {
/* fallback for old browsers */
background: #fccb90;
/* Chrome 10-25, Safari 5.1-6 */
background: -webkit-linear-gradient(to right, #2c2c2c, #1e1e1e, #1e1e1e);
/* W3C, IE 10+/ Edge, Firefox 16+, Chrome 26+, Opera 12+, Safari 7+ */
background: linear-gradient(to right, #2c2c2c, #1e1e1e, #1e1e1e);
}

218
app/css/wysiwyg.css Executable file
View File

@ -0,0 +1,218 @@
/**
* app/css/wysiwyg.css
*
* This file is for the wysiwyg editor's css.
*
* @version 3.0
* @author Joey Kimsey <Joey@thetempusproject.com>
* @link https://TheTempusProject.com
* @license https://opensource.org/licenses/MIT [MIT LICENSE]
*/
body {
margin: 0;
height: 100vh;
display: flex;
justify-content: center;
align-items: center;
font-family: 'Helvetica Neue', 'Helvetica', arial, sans-serif;
}
/* WYSIWYG Editor */
.wp-webdeasy-comment-editor {
width: 40rem;
min-height: 18rem;
box-shadow: 0 0 4px 1px rgba(0, 0, 0, 0.3);
border-top: 6px solid #4a4a4a;
border-radius: 3px;
margin: 2rem 0;
.toolbar {
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.2);
.line {
display: flex;
border-bottom: 1px solid #e2e2e2;
&:last-child {
border-bottom: none;
}
.box {
display: flex;
border-left: 1px solid #e2e2e2;
.editor-btn {
display: block;
display: flex;
align-items: center;
justify-content: center;
position: relative;
transition: .2s ease all;
&:hover, &.active {
background-color: #e1e1e1;
cursor: pointer;
}
&.icon img {
width: 15px;
padding: 9px;
box-sizing: content-box;
}
&.icon.smaller img {
width: 16px;
}
&.has-submenu {
width: 20px;
padding: 0 10px;
&::after {
content: '';
width: 6px;
height: 6px;
position: absolute;
background-image: url(https://img.icons8.com/ios-glyphs/30/000000/chevron-down.png);
background-repeat: no-repeat;
background-size: cover;
background-position: center;
right: 4px;
}
.submenu {
display: none;
position: absolute;
top: 34px;
left: -1px;
z-index: 10;
background-color: #FFF;
border: 1px solid #b5b5b5;
border-top: none;
.btn {
width: 39px;
}
&:hover {
display: block;
}
}
&:hover .submenu {
display: block;
}
}
}
}
}
}
.content-area {
padding: 15px 12px;
line-height: 1.5;
.visuell-view {
outline: none;
min-height: 12rem;
p {
margin: 12px 0;
}
}
.html-view {
outline: none;
display: none;
width: 100%;
height: 200px;
border: none;
resize: none;
}
}
}
/* Modal */
.modal {
z-index: 40;
display: none;
.modal-wrapper {
background-color: #FFF;
padding: 1rem;
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 20rem;
min-height: 10rem;
z-index: 41;
.close {
position: absolute;
top: 1rem;
right: 1rem;
cursor: pointer;
}
.modal-content {
flex-direction: column;
h3 {
margin-top: 0;
}
input {
margin: 1rem 0;
padding: .5rem;
}
input[type="text"] {
width: calc(100% - 1rem);
}
.row {
label {
margin-left: .5rem;
}
}
button {
background-color: #D2434F;
border: 0;
color: #FFF;
padding: .5rem 1.2rem;
cursor: pointer;
}
}
}
.modal-bg {
position: fixed;
background-color: rgba(0, 0, 0, .3);
width: 100vw;
height: 100vh;
top: 0;
left: 0;
}
}
/* Codepen Footer */
footer {
position: fixed;
bottom: 0;
display: flex;
p {
margin: 0.5rem 1rem;
font-size: 12px;
}
a {
text-decoration: none;
font-weight: bold;
color: #000;
}
}

119
app/functions/common.php Executable file
View File

@ -0,0 +1,119 @@
<?php
/**
* app/functions/common.php
*
* @version 3.0
* @author Joey Kimsey <Joey@thetempusproject.com>
* @link https://TheTempusProject.com
* @license https://opensource.org/licenses/MIT [MIT LICENSE]
*/
function getClassName($class) {
$className = get_class($class);
$classNameParts = explode('\\', $className);
return end($classNameParts);
}
function convertClassNameToFileName( $class_name ) {
$lower = lcfirst( $class_name ) . '.php';
$upper_split = preg_split( '/(?=[A-Z])/', $lower );
$file_name = strtolower( implode( '_', $upper_split ) );
return $file_name;
}
function convertFileNameToClassName( $file_name ) {
$file_name = str_ireplace( '.php', '', $file_name );
$file_name = rtrim( $file_name, DIRECTORY_SEPARATOR );
$class_name = '';
if ( stripos( $file_name, '_' ) ) {
$exploded = explode( '_', $file_name );
foreach ( $exploded as $key => $value ) {
$class_name .= ucfirst( $value );
}
} else {
$class_name .= ucfirst( $file_name );
}
return $class_name;
}
function convertFolderToClassName( $folder ) {
$file_name = rtrim( $folder, DIRECTORY_SEPARATOR );
$parts_array = explode(DIRECTORY_SEPARATOR, $file_name);
$file_name = array_pop($parts_array);
$class_name = '';
if ( stripos( $file_name, '_' ) ) {
$exploded = explode( '_', $file_name );
foreach ( $exploded as $key => $value ) {
$class_name .= ucfirst( $value );
}
} else {
$class_name .= ucfirst( $file_name );
}
return $class_name;
}
function convertFileNameToPluginClass( $file_name ) {
$class_name = convertFolderToClassName( $file_name );
$class = (string) APP_SPACE . '\\Plugins\\' . $class_name;
return $class;
}
function convertFileNameToModelClass( $file_name ) {
$class_name = convertFileNameToClassName( $file_name );
$class = (string) APP_SPACE . '\\Models\\' . $class_name;
return $class;
}
function getFileList( $folder = '' ) {
if ( empty( $folder ) ) {
$folder = PLUGIN_DIRECTORY;
}
if ( !file_exists( $folder ) ) {
return false;
}
$pluginFolders = scandir( $folder );
array_shift( $pluginFolders ); // remove the .
array_shift( $pluginFolders ); // remove the ..
return $pluginFolders;
}
function dv( $variable ) {
echo '<pre>';
echo var_export( $variable, true );
echo '</pre>';
exit;
}
function iv( $variable ) {
echo '<pre>';
echo var_export( $variable, true );
echo '</pre>';
}
function generateToken(): string {
return bin2hex(random_bytes(32)); // Generates a 64-character hexadecimal token
}
function generateRandomString( $length = 10 ) {
$characters = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
$charactersLength = strlen( $characters );
$randomString = '';
for ($i = 0; $i < $length; $i++) {
$randomString .= $characters[random_int(0, $charactersLength - 1)];
}
return $randomString;
}
function generateUuidV4(): string {
// Generate 16 random bytes
$data = random_bytes(16);
// Set the version to 4 -> random (bits 12-15 of time_hi_and_version)
$data[6] = chr((ord($data[6]) & 0x0f) | 0x40);
// Set the variant to RFC 4122 -> (bits 6-7 of clock_seq_hi_and_reserved)
$data[8] = chr((ord($data[8]) & 0x3f) | 0x80);
// Convert to hexadecimal and format as a UUID
return vsprintf('%s%s-%s-%s-%s-%s%s%s', str_split(bin2hex($data), 4));
}

BIN
app/images/ttp-github.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

BIN
app/images/ttp-install.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

BIN
app/images/ttp.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

189
app/js/main.js Executable file
View File

@ -0,0 +1,189 @@
/**
* app/js/main.js
*
* This file is for 'access anywhere' javascript.
*
* @version 3.0
* @author Joey Kimsey <Joey@thetempusproject.com>
* @link https://TheTempusProject.com
* @license https://opensource.org/licenses/MIT [MIT LICENSE]
*/
/**
* Automatically selects/de-selects all check boxes associated with that field
**/
function checkAll(ele) {
var checkboxes = document.getElementsByTagName( 'input' );
if (ele.checked) {
test = true;
} else {
test = false;
}
for ( var i = 0; i < checkboxes.length; i++ ) {
if ( checkboxes[i].type == 'checkbox' ) {
if ( checkboxes[i].name == ele.value ) {
checkboxes[i].checked = test;
}
}
}
}
function insertTag( box, tag ) {
var Field = document.getElementById( box );
var currentPos = cursorPos( Field );
var val = Field.value;
var before = val.substring( 0, currentPos );
var after = val.substring( currentPos, val.length );
Field.value = before + '(' + tag + ')' + after;
}
function cursorPos( el ) {
if ( el.selectionStart ) {
return el.selectionStart;
} else if ( document.selection ) {
el.focus();
var r = document.selection.createRange();
if ( r == null ) {
return 0;
}
var re = el.createTextRange(),
rc = re.duplicate();
re.moveToBookmark( r.getBookmark() );
rc.setEndPoint( 'EndToStart', re );
return rc.text.length;
}
return 0;
}
function getRandomInt(min, max) {
const minCeiled = Math.ceil(min);
const maxFloored = Math.floor(max);
return Math.floor(Math.random() * (maxFloored - minCeiled) + minCeiled);
}
function copyElementText( id ) {
const inputElement = document.getElementById( id );
const textToCopy = inputElement.value;
if (navigator.clipboard && navigator.clipboard.writeText) {
navigator.clipboard.writeText(textToCopy)
.then(() => alert('Copied to clipboard!'))
.catch((err) => console.error('Failed to copy: ', err));
} else {
// Fallback for older browsers
inputElement.select();
try {
document.execCommand('copy');
alert('Copied to clipboard!');
} catch (err) {
console.error('Failed to copy: ', err);
}
}
}
$(document).ready(function() {
$('select').each(function() {
var selectedValue = $(this).attr('value');
if (selectedValue) {
$(this).removeAttr('value');
$(this).find('option').each(function() {
if ($(this).attr('value') === selectedValue) {
$(this).prop('selected', true);
}
});
}
});
});
document.addEventListener('DOMContentLoaded', function () {
const ttpDarkmode = document.getElementById('dark-mode-pref');
const toggleButton = document.getElementById('dark-mode-toggle');
const enableButton = document.getElementById('dark-mode-toggle-button');
const darkModeStylesheet = document.getElementById('dark-mode-stylesheet');
let currentState = '';
// Check if dark mode is set by ttp
if ( ttpDarkmode ) {
if ( 'true' == ttpDarkmode.value ) {
currentState = 'enabled';
}
if ( 'false' == ttpDarkmode.value ) {
currentState = 'disabled';
}
}
// Check if dark mode is set in localStorage
if ( '' == currentState ) {
if ( localStorage.getItem('darkMode') === 'enabled' ) {
currentState = 'enabled';
}
}
// Update current button states
if ( 'enabled' == currentState ) {
darkModeStylesheet.disabled = false;
if ( toggleButton ) {
toggleButton.checked = true;
}
if ( enableButton ) {
enableButton.innerText = 'Disable Now';
}
}
// Style striped table elements
document.querySelectorAll('.table-striped').forEach((table) => {
if ( 'enabled' == currentState ) {
table.classList.add('table-dark');
} else {
table.classList.add('table-light')
}
});
if ( enableButton ) {
enableButton.addEventListener('click', function () {
if ( darkModeStylesheet.disabled ) {
darkModeStylesheet.disabled = false;
localStorage.setItem('darkMode', 'enabled');
enableButton.innerText = 'Disable Now';
} else {
darkModeStylesheet.disabled = true;
localStorage.setItem('darkMode', 'disabled');
enableButton.innerText = 'Enable Now';
}
});
}
if ( toggleButton ) {
toggleButton.addEventListener('click', function () {
if (darkModeStylesheet.disabled) {
toggleDarkModePref( true );
darkModeStylesheet.disabled = false;
localStorage.setItem('darkMode', 'enabled');
} else {
toggleDarkModePref( false );
darkModeStylesheet.disabled = true;
localStorage.setItem('darkMode', 'disabled');
}
document.querySelectorAll('.table-striped').forEach((table) => {
if (localStorage.getItem('darkMode') === 'enabled') {
table.classList.add('table-dark');
table.classList.remove('table-light');
} else {
table.classList.add('table-light');
table.classList.remove('table-dark');
}
});
});
}
function toggleDarkModePref( value ) {
var fields = {};
fields.prefName = 'darkMode';
fields.prefValue = value;
$.post( '/usercp/updatePref', fields ).done(function(response) {
// alert('Timer updated successfully!');
});
}
});

233
app/js/wysiwyg.js Executable file
View File

@ -0,0 +1,233 @@
/**
* app/js/wysiwyg.js
*
* This is css used in the debuging console.
*
* @version 3.0
* @author Joey Kimsey <Joey@thetempusproject.com>
* @link https://TheTempusProject.com
* @license https://opensource.org/licenses/MIT [MIT LICENSE]
*/
// define vars
const editor = document.getElementsByClassName('wp-webdeasy-comment-editor')[0];
const toolbar = editor.getElementsByClassName('toolbar')[0];
const buttons = toolbar.querySelectorAll('.editor-btn:not(.has-submenu)');
const contentArea = editor.getElementsByClassName('content-area')[0];
const visuellView = contentArea.getElementsByClassName('visuell-view')[0];
const htmlView = contentArea.getElementsByClassName('html-view')[0];
const modal = document.getElementsByClassName('modal')[0];
// add active tag event
document.addEventListener('selectionchange', selectionChange);
// add paste event
visuellView.addEventListener('paste', pasteEvent);
// add paragraph tag on new line
contentArea.addEventListener('keypress', addParagraphTag);
// add toolbar button actions
for(let i = 0; i < buttons.length; i++) {
let button = buttons[i];
button.addEventListener('click', function(e) {
let action = this.dataset.action;
switch(action) {
case 'toggle-view':
execCodeAction(this, editor);
break;
case 'createLink':
execLinkAction();
break;
default:
execDefaultAction(action);
}
});
}
/**
* This function toggles between visual and html view
*/
function execCodeAction(button, editor) {
if(button.classList.contains('active')) { // show visuell view
visuellView.innerHTML = htmlView.value;
htmlView.style.display = 'none';
visuellView.style.display = 'block';
button.classList.remove('active');
} else { // show html view
htmlView.innerText = visuellView.innerHTML;
visuellView.style.display = 'none';
htmlView.style.display = 'block';
button.classList.add('active');
}
}
/**
* This function adds a link to the current selection
*/
function execLinkAction() {
modal.style.display = 'block';
let selection = saveSelection();
let submit = modal.querySelectorAll('button.done')[0];
let close = modal.querySelectorAll('.close')[0];
// done button active => add link
submit.addEventListener('click', function(e) {
e.preventDefault();
let newTabCheckbox = modal.querySelectorAll('#new-tab')[0];
let linkInput = modal.querySelectorAll('#linkValue')[0];
let linkValue = linkInput.value;
let newTab = newTabCheckbox.checked;
restoreSelection(selection);
if(window.getSelection().toString()) {
let a = document.createElement('a');
a.href = linkValue;
if(newTab) a.target = '_blank';
window.getSelection().getRangeAt(0).surroundContents(a);
}
modal.style.display = 'none';
linkInput.value = '';
// deregister modal events
submit.removeEventListener('click', arguments.callee);
close.removeEventListener('click', arguments.callee);
});
// close modal on X click
close.addEventListener('click', function(e) {
e.preventDefault();
let linkInput = modal.querySelectorAll('#linkValue')[0];
modal.style.display = 'none';
linkInput.value = '';
// deregister modal events
submit.removeEventListener('click', arguments.callee);
close.removeEventListener('click', arguments.callee);
});
}
/**
* This function executes all 'normal' actions
*/
function execDefaultAction(action) {
document.execCommand(action, false);
}
/**
* Saves the current selection
*/
function saveSelection() {
if(window.getSelection) {
sel = window.getSelection();
if(sel.getRangeAt && sel.rangeCount) {
let ranges = [];
for(var i = 0, len = sel.rangeCount; i < len; ++i) {
ranges.push(sel.getRangeAt(i));
}
return ranges;
}
} else if (document.selection && document.selection.createRange) {
return document.selection.createRange();
}
return null;
}
/**
* Loads a saved selection
*/
function restoreSelection(savedSel) {
if(savedSel) {
if(window.getSelection) {
sel = window.getSelection();
sel.removeAllRanges();
for(var i = 0, len = savedSel.length; i < len; ++i) {
sel.addRange(savedSel[i]);
}
} else if(document.selection && savedSel.select) {
savedSel.select();
}
}
}
/**
* Sets the current selected format buttons active/inactive
*/
function selectionChange(e) {
for(let i = 0; i < buttons.length; i++) {
let button = buttons[i];
// don't remove active class on code toggle button
if(button.dataset.action === 'toggle-view') continue;
button.classList.remove('active');
}
if(!childOf(window.getSelection().anchorNode.parentNode, editor)) return false;
parentTagActive(window.getSelection().anchorNode.parentNode);
}
/**
* Checks if the passed child has the passed parent
*/
function childOf(child, parent) {
return parent.contains(child);
}
/**
* Sets the tag active that is responsible for the current element
*/
function parentTagActive(elem) {
if(!elem ||!elem.classList || elem.classList.contains('visuell-view')) return false;
let toolbarButton;
// active by tag names
let tagName = elem.tagName.toLowerCase();
toolbarButton = document.querySelectorAll(`.toolbar .editor-btn[data-tag-name="${tagName}"]`)[0];
if(toolbarButton) {
toolbarButton.classList.add('active');
}
// active by text-align
let textAlign = elem.style.textAlign;
toolbarButton = document.querySelectorAll(`.toolbar .editor-btn[data-style="textAlign:${textAlign}"]`)[0];
if(toolbarButton) {
toolbarButton.classList.add('active');
}
return parentTagActive(elem.parentNode);
}
/**
* Handles the paste event and removes all HTML tags
*/
function pasteEvent(e) {
e.preventDefault();
let text = (e.originalEvent || e).clipboardData.getData('text/plain');
document.execCommand('insertHTML', false, text);
}
/**
* This functions adds a paragraph tag when the enter key is pressed
*/
function addParagraphTag(evt) {
if (evt.keyCode == '13') {
// don't add a p tag on list item
if(window.getSelection().anchorNode.parentNode.tagName === 'LI') return;
document.execCommand('formatBlock', false, 'p');
}
}

297
app/models/group.php Executable file
View File

@ -0,0 +1,297 @@
<?php
/**
* app/models/group.php
*
* This class is used for the manipulation of the groups database table.
*
* @version 3.0
* @author Joey Kimsey <Joey@thetempusproject.com>
* @link https://TheTempusProject.com
* @license https://opensource.org/licenses/MIT [MIT LICENSE]
*/
namespace TheTempusProject\Models;
use TheTempusProject\Bedrock\Functions\Check;
use TheTempusProject\Canary\Bin\Canary as Debug;
use TheTempusProject\Classes\Permissions;
use TheTempusProject\Bedrock\Classes\Config;
use TheTempusProject\Bedrock\Functions\Input;
use TheTempusProject\Classes\DatabaseModel;
class Group extends DatabaseModel {
protected static $user;
protected static $permissions;
public $tableName = 'groups';
public $configName = 'group';
public $modelVersion = '1.0';
public static $protectedGroups = [
'Super', 'Admin', 'Moderator'
];
public $configMatrix = [
'defaultGroup' => [
'type' => 'customSelect',
'pretty' => 'The Default Group for new registrations.',
'default' => 4,
],
];
public $databaseMatrix = [
[ 'name', 'varchar', '32' ],
[ 'permissions', 'text', '' ],
];
public $searchFields = [
'name',
];
public $permissionMatrix = [
'adminAccess' => [
'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 = [
[
'name' => 'Super',
'permissions' => '{"adminAccess":true}',
],
[
'name' => 'Admin',
'permissions' => '{"adminAccess":true}',
],
[
'name' => 'User',
'permissions' => '{"adminAccess":false}',
],
[
'name' => 'Guest',
'permissions' => '{"adminAccess":false}',
],
];
/**
* The model constructor.
*/
public function __construct() {
parent::__construct();
self::$group = $this;
self::$permissions = new Permissions;
}
public function isEmpty( $id ) {
if ( !Check::ID( $id ) ) {
return false;
}
$userData = self::$db->get( 'users', [ 'userGroup', '=', $id ] );
if ( !$userData->count() ) {
return true;
}
return false;
}
/**
* Function to delete the specified group.
*
* @param int|array $ID the log ID or array of ID's to be deleted
* @return bool
*/
public function delete( $data ) {
if ( empty( self::$log ) ) {
self::$log = new Log;
}
if ( !is_array( $data ) ) {
$data = [ $data ];
}
foreach ( $data as $instance ) {
if ( !Check::id( $instance ) ) {
$error = true;
}
if ( $this->countMembers( $instance ) !== 0 ) {
Debug::info( 'Group is not empty.' );
return false;
}
if ( $instance == Config::getValue( 'group/defaultGroup' ) ) {
Debug::info( 'Cannot delete the default group.' );
return false;
}
if ( $instance == '1' ) {
Debug::info( 'Cannot delete the super group.' );
return false;
}
self::$db->delete( $this->tableName, [ 'ID', '=', $instance ] );
self::$log->admin( "Deleted group: $instance" );
Debug::info( "Group deleted: $instance" );
if ( !empty( $end ) ) {
break;
}
}
if ( !empty( $error ) ) {
Debug::info( 'One or more invalid ID\'s.' );
return false;
}
return true;
}
public function hasPermission( $permission ) {
}
// update($data, Input::post('name'), ) {
public function getPermissionsDelta( $id, $permissions ) {
}
public function create( $name, $permissions ) {
if ( empty( self::$log ) ) {
self::$log = new Log;
}
if ( !Check::dataTitle( $name ) ) {
Debug::info( 'modelGroup: illegal group name.' );
return false;
}
$fields = [
'name' => $name,
'permissions' => json_encode( $permissions ),
];
if ( self::$db->insert( $this->tableName, $fields ) ) {
self::$log->admin( "Created Group: $name" );
return true;
}
return false;
}
public function update( $id, $name, $permissions ) {
if ( empty( self::$log ) ) {
self::$log = new Log;
}
if ( !Check::id( $id ) ) {
return false;
}
if ( !Check::dataTitle( $name ) ) {
Debug::info( 'modelGroup: illegal group name.' );
return false;
}
$fields = [
'name' => $name,
'permissions' => json_encode( $permissions ),
];
if ( self::$db->update( $this->tableName, $id, $fields ) ) {
self::$log->admin( "Updated Group: $id" );
return true;
}
return false;
}
public function getDefaultPermissions() {
return self::$permissions->getDefaultPermissionsArray();
}
public function filter( $data, $params = [] ) {
$defaults = $this->getDefaultPermissions();
foreach ( $data as $instance ) {
if ( !is_object( $instance ) ) {
$instance = $data;
$end = true;
}
$toArray = (array) $instance;
$instance->perms = json_decode( $instance->permissions, true );
$instance->userCount = $this->countMembers( $instance->ID );
foreach ( $defaults as $name => $default ) {
$string_name = $name . '_string';
$text_name = $name . '_text';
$pretty_name = $name . '_pretty';
if ( isset( $instance->perms[ $name ] ) ) {
$default = $instance->perms[ $name ];
}
$instance->$name = $default;
$instance->$pretty_name = self::$permissions->getPrettyName( $name );
if ( $default === true ) {
$instance->$string_name = 'true';
$instance->$text_name = 'yes';
} else {
$instance->$string_name = 'false';
$instance->$text_name = 'no';
}
}
$out[] = $instance;
if ( !empty( $end ) ) {
$out = $out[0];
break;
}
}
return $out;
}
public function findByName( $name ) {
if ( !Check::dataString( $name ) ) {
Debug::warn( "$this->tableName findByName: illegal name: $name" );
return false;
}
$groupData = self::$db->get( $this->tableName, [ 'name', '=', $name ] );
if ( !$groupData->count() ) {
Debug::warn( 'Could not find a group named: ' . $name );
return false;
}
return $this->filter( $groupData->first() );
}
public function listGroupsSimple( $include_all = false, $include_none = false ) {
$db = self::$db->get( $this->tableName, '*' );
if ( !$db->count() ) {
Debug::warn( 'Could not find any groups' );
return false;
}
$groups = $db->results();
$out = [];
if ( $include_all ) {
$out[ 'All Groups' ] = 0;
}
if ( $include_none ) {
$out[ 'No Group' ] = 0;
}
foreach ( $groups as &$group ) {
$out[ $group->name ] = $group->ID;
}
return $out;
}
public function listMembers( $id ) {
if ( !Check::id( $id ) ) {
return false;
}
$group = $this->findById( $id );
if ( $group === false ) {
return false;
}
$members = self::$db->get( 'users', [ 'userGroup', '=', $id ] );
if ( !$members->count() ) {
Debug::info( "list members: Could not find anyone in group: $id" );
return false;
}
$out = $members->results();
return $out;
}
/**
* Retrieves a count of the members in a specific group.
*
* @param integer $id - The group ID to count the members of
* @return boolean|integer
*/
public function countMembers( $id ) {
if ( !Check::id( $id ) ) {
return false;
}
$userData = self::$db->get( 'users', [ 'userGroup', '=', $id ] );
if ( !$userData->count() ) {
Debug::info( "count members: Could not find anyone in group: $id" );
return 0;
}
return $userData->count();
}
}

200
app/models/log.php Executable file
View File

@ -0,0 +1,200 @@
<?php
/**
* app/models/log.php
*
* Model for handling all logging.
*
* @version 3.0
* @author Joey Kimsey <Joey@thetempusproject.com>
* @link https://TheTempusProject.com
* @license https://opensource.org/licenses/MIT [MIT LICENSE]
*/
namespace TheTempusProject\Models;
use TheTempusProject\Bedrock\Functions\Check;
use TheTempusProject\Bedrock\Classes\Config;
use TheTempusProject\Canary\Classes\CustomException;
use TheTempusProject\TheTempusProject as App;
use TheTempusProject\Classes\DatabaseModel;
use TheTempusProject\Canary\Bin\Canary as Debug;
class Log extends DatabaseModel {
public $tableName = 'logs';
public $configName = 'logging';
public $modelVersion = '1.0';
public $configMatrix = [
'admin' => [
'type' => 'radio',
'pretty' => 'Enable Admin Action Logging.',
'default' => true,
],
'errors' => [
'type' => 'radio',
'pretty' => 'Enable Error Logging',
'default' => true,
],
'logins' => [
'type' => 'radio',
'pretty' => 'Enable Login Logging',
'default' => true,
],
];
public $databaseMatrix = [
[ 'userID', 'int', '11' ],
[ 'time', 'int', '10' ],
[ 'ip', 'varchar', '15' ],
[ 'source', 'varchar', '64' ],
[ 'action', 'text', '' ],
];
public $searchFields = [
'source',
];
/**
* The model constructor.
*/
public function __construct() {
parent::__construct();
self::$log = $this;
}
public function enabled( $type = '' ) {
if ( true === parent::enabled() ) {
return Config::getValue( 'logging/' . $type ) === true;
}
return false;
}
public function filter( $data, $params = [] ) {
foreach ( $data as $instance ) {
if ( !is_object( $instance ) ) {
$instance = $data;
$end = true;
}
$toArray = (array) $instance;
switch ($instance->source) {
case 'error':
$out[] = (object) array_merge( json_decode( $instance->action, true ), $toArray );
break;
default:
$instance->logUser = self::$user->getUsername( $instance->userID );
$out[] = $instance;
break;
}
if ( !empty( $end ) ) {
$out = $out[0];
break;
}
}
return $out;
}
public function list( $filter = null ) {
$logData = self::$db->get( $this->tableName, [ 'source', '=', $filter ] );
if ( !$logData->count() ) {
return false;
}
return $this->filter( $logData->results() );
}
/**
* Function to clear logs of a specific type.
*
* @param {string} $data - The log type to be cleared
* @return boolean
*/
public function clear( $data ) {
switch ( $data ) {
case 'admin':
Debug::error( 'You cannot delete admin logs' );
return false;
case 'login':
self::$db->delete( $this->tableName, [ 'source', '=', $data ] );
$this->admin( "Cleared Logs: $data" );
return true;
case 'error':
self::$db->delete( $this->tableName, [ 'source', '=', $data ] );
$this->admin( "Cleared Logs: $data" );
return true;
default:
return false;
}
}
/**
* logs an error to the DB.
*
* @param {int} [$errorID] - An associated error ID
* @param {string} [$class] - Class where the error occurred
* @param {string} [$function] - method in which the error occurred
* @param {string} [$error] - What was the error
* @param {string} [$data] - Any additional info
*/
public function error( $errorID = 500, $class = null, $function = null, $error = null, $data = null ) {
if ( !$this->enabled( 'errors' ) ) {
Debug::info( 'Error logging is disabled in the config.' );
return false;
}
$data = [
'class' => $class,
'function' => $function,
'error' => $error,
'description' => $data,
];
$output = json_encode( $data );
$fields = [
'userID' => $errorID,
'action' => $output,
'time' => time(),
'source' => 'error',
];
if ( !self::$db->insert( $this->tableName, $fields ) ) {
new CustomException( 'logError', $data );
}
}
/**
* Logs a login to the DB.
*
* @param {int} [$userID] - The User ID being logged in.
* @param {string} [$action] - Must be 'pass' or 'fail'.
*/
public function login( $userID, $action = 'fail' ) {
if ( !$this->enabled( 'logins' ) ) {
Debug::info( 'Login logging is disabled in the config.' );
return false;
}
$fields = [
'userID' => $userID,
'action' => $action,
'time' => time(),
'source' => 'login',
'ip' => $_SERVER['REMOTE_ADDR'],
];
if ( !self::$db->insert( $this->tableName, $fields ) ) {
new CustomException( 'logLogin' );
}
}
/**
* Logs an admin action to the DB.
*
* @param {string} [$action] - Must be 'pass' or 'fail'.
*/
public function admin( $action ) {
if ( !$this->enabled( 'admin' ) ) {
Debug::info( 'Admin logging is disabled in the config.' );
return false;
}
$fields = [
'userID' => App::$activeUser->ID,
'action' => $action,
'time' => time(),
'source' => 'admin',
'ip' => $_SERVER['REMOTE_ADDR'],
];
if ( !self::$db->insert( $this->tableName, $fields ) ) {
new CustomException( 'logAdmin' );
}
}
}

161
app/models/routes.php Executable file
View File

@ -0,0 +1,161 @@
<?php
/**
* app/models/routes.php
*
* This class is used for the manipulation of the routes database table.
*
* @version 3.0
* @author Joey Kimsey <Joey@thetempusproject.com>
* @link https://TheTempusProject.com
* @license https://opensource.org/licenses/MIT [MIT LICENSE]
*/
namespace TheTempusProject\Models;
use TheTempusProject\Bedrock\Functions\Check;
use TheTempusProject\Canary\Bin\Canary as Debug;
use TheTempusProject\Classes\DatabaseModel;
class Routes extends DatabaseModel {
public $tableName = 'routes';
public $modelVersion = '1.0';
public $databaseMatrix = [
[ 'nickname', 'varchar', '32' ],
[ 'redirect_type', 'varchar', '32' ],
[ 'original_url', 'varchar', '32' ],
[ 'forwarded_url', 'text', '' ],
];
public $searchFields = [
'nickname',
];
public $resourceMatrix = [
[
'original_url' => 'fb',
'redirect_type' => 'external',
'nickname' => 'Facebook',
'forwarded_url' => 'https://www.facebook.com/thetempusproject',
],
[
'original_url' => 'twitter',
'redirect_type' => 'external',
'nickname' => 'Twitter',
'forwarded_url' => 'https://twitter.com/ProjectTempus',
],
[
'original_url' => 'in',
'redirect_type' => 'external',
'nickname' => 'LinkedIn',
'forwarded_url' => 'https://www.linkedin.com/company/the-tempus-project/',
],
[
'original_url' => 'youtube',
'redirect_type' => 'external',
'nickname' => 'YouTube',
'forwarded_url' => 'https://www.youtube.com/channel/UCWy5mgBdvp8-nLJrvhnzC4w',
],
[
'original_url' => 'git',
'redirect_type' => 'external',
'nickname' => 'GitHub',
'forwarded_url' => 'https://github.com/TheTempusProject',
],
];
public $permissionMatrix = [
'addRoute' => [
'pretty' => 'Add Custom Routes',
'default' => false,
],
];
public function create( $original_url, $forwarded_url, $nickname = '', $type = 'external' ) {
if ( empty( self::$log ) ) {
self::$log = new Log;
}
if ( empty( $forwarded_url ) || empty( $original_url ) ) {
Debug::info( 'Missing some parts' );
return false;
}
if ( !Check::simpleName( $nickname ) ) {
Debug::warn( 'Invalid route nickname: ' . $nickname );
return false;
}
if ( 'external' == $type && !Check::url( $forwarded_url ) ) {
Debug::info( 'Routes: illegal forwarded_url.' );
return false;
}
$fields = [
'nickname' => $nickname,
'redirect_type' => $type,
'original_url' => $original_url,
'forwarded_url' => $forwarded_url,
];
if ( self::$db->insert( $this->tableName, $fields ) ) {
self::$log->admin( "Created Route: $nickname" );
return true;
}
return false;
}
public function update( $id, $original_url, $forwarded_url, $nickname, $type ) {
if ( empty( self::$log ) ) {
self::$log = new Log;
}
if ( !Check::simpleName( $nickname ) ) {
Debug::warn( "Invalid route nickname: $name" );
return false;
}
if ( empty( $forwarded_url ) || empty( $original_url ) ) {
Debug::info( 'Missing some parts' );
return false;
}
if ( 'external' == $type && !Check::url( $forwarded_url ) ) {
Debug::info( 'Routes: illegal forwarded_url.' );
return false;
}
$fields = [
'nickname' => $nickname,
'redirect_type' => $type,
'original_url' => $original_url,
'forwarded_url' => $forwarded_url,
];
if ( self::$db->update( $this->tableName, $id, $fields ) ) {
self::$log->admin( "Updated Route: $id" );
return true;
}
return false;
}
public function findByName( $name ) {
if ( !Check::simpleName( $name ) ) {
Debug::warn( "Invalid route nickname: $name" );
return false;
}
$routeData = self::$db->get( $this->tableName, [ 'nickname', '=', $name ] );
if ( !$routeData->count() ) {
Debug::info( "Routes:findByName: Could not find a route named: $name" );
return false;
}
return $this->filter( $routeData->first() );
}
public function findByOriginalUrl( $url ) {
$routeData = self::$db->get( $this->tableName, [ 'original_url', '=', $url ] );
if ( !$routeData->count() ) {
Debug::info( "Routes:findByOriginalUrl: Could not find route by original url: $url" );
return false;
}
return $this->filter( $routeData->first() );
}
public function findByforwardedUrl( $url ) {
if ( !Check::url( $url ) ) {
Debug::warn( "Routes:findByforwardedUrl: Invalid forwarded_url: $url" );
return false;
}
$routeData = self::$db->get( $this->tableName, [ 'forwarded_url', '=', $url ] );
if ( !$routeData->count() ) {
Debug::info( "Routes:findByforwardedUrl: Could not find route by forwarded url: $url" );
return false;
}
return $this->filter( $routeData->first() );
}
}

274
app/models/sessions.php Executable file
View File

@ -0,0 +1,274 @@
<?php
/**
* app/models/sessions.php
*
* This model is used for the modification and management of the session data.
*
* Notes: After refactor, the sessions will use ID's for short term, and Cookies
* will use the token for long term storage
*
* @version 3.0
* @author Joey Kimsey <Joey@thetempusproject.com>
* @link https://TheTempusProject.com
* @license https://opensource.org/licenses/MIT [MIT LICENSE]
*/
namespace TheTempusProject\Models;
use TheTempusProject\Bedrock\Functions\Check;
use TheTempusProject\Bedrock\Functions\Code;
use TheTempusProject\Canary\Bin\Canary as Debug;
use TheTempusProject\Bedrock\Functions\Session;
use TheTempusProject\Bedrock\Functions\Cookie;
use TheTempusProject\Classes\DatabaseModel;
use TheTempusProject\Classes\Config;
use TheTempusProject\TheTempusProject as App;
class Sessions extends DatabaseModel {
public $tableName = 'sessions';
public $modelVersion = '1.0';
public $databaseMatrix = [
[ 'userID', 'int', '5' ],
[ 'userGroup', 'int', '5' ],
[ 'expire', 'int', '10' ],
[ 'ip', 'varchar', '15' ],
[ 'hash', 'varchar', '80' ],
[ 'lastPage', 'varchar', '64' ],
[ 'username', 'varchar', '20' ],
[ 'token', 'varchar', '120' ],
];
public $searchFields = [
'username',
];
public static $activeSession = false;
/**
* The model constructor.
*/
public function __construct() {
parent::__construct();
self::$session = $this;
}
/**
* Check if a session exists, verifies the username,
* password, and IP address to prevent forgeries.
*
* @param {int} [$id] - The id of the session being checked.
* @return {bool}
*/
public function checkSession( $sessionID ) {
$user = new User;
// @todo lets put this on some sort of realistic checking regime other than check everything every time
if ( $sessionID == false ) {
Debug::log( 'sessionID false' );
return false;
}
if ( !Check::id( $sessionID ) ) {
Debug::log( 'sessionID not id' );
return false;
}
$data = self::$db->get( $this->tableName, [ 'ID', '=', $sessionID ] );
if ( $data->count() == 0 ) {
Debug::info( 'Session token not found.' );
return false;
}
$session = $data->first();
$user = $user->findById( $session->userID );
if ( $user === false ) {
Debug::info( 'User not found in DB.' );
$this->destroy( $session->ID );
return false;
}
if ( $user->username != $session->username ) {
Debug::info( 'Usernames do not match.' );
$this->destroy( $session->ID );
return false;
}
if ( $user->password != $session->hash ) {
Debug::info( 'Session Password does not match.' );
$this->destroy( $session->ID );
return false;
}
if ( time() > $session->expire ) {
Debug::info( 'Session Expired.' );
$this->destroy( $session->ID );
return false;
}
if ( $user->userGroup !== $session->userGroup ) {
Debug::info( 'Groups do not match.' );
$this->destroy( $session->ID );
return false;
}
if ( $_SERVER['REMOTE_ADDR'] != $session->ip ) {
Debug::info( 'IP addresses do not match.' );
$this->destroy( $session->ID );
return false;
}
self::$activeSession = $session;
return true;
}
/**
* Checks the "remember me" cookie we use to identify
* unique sessions across multiple visits. Checks that
* the tokens match, checks the username as well as the
* password from the database to ensure it hasn't been
* modified elsewhere between visits.
*
* @param {string} [$token] - The unique token saved as a cookie that is being checked.
* @return {bool}
*/
public function checkCookie( $cookieToken, $create = false ) {
$user = new User;
if ( $cookieToken === false ) {
Debug::info( 'cookieToken false' );
return false;
}
$data = self::$db->get( $this->tableName, [ 'token', '=', $cookieToken ] );
if ( !$data->count() ) {
Debug::info( 'sessions->checkCookie - Session token not found.' );
return false;
}
$session = $data->first();
$user = self::$user->findById( $session->userID );
if ( $user === false ) {
Debug::info( 'sessions->checkCookie - could not find user by ID.' );
return false;
}
if ( $user->username != $session->username ) {
Debug::info( 'sessions->checkCookie - Usernames do not match.' );
$this->destroy( $session->ID );
return false;
}
if ( $user->password != $session->hash ) {
Debug::info( 'sessions->checkCookie - Session Password does not match.' );
$this->destroy( $session->ID );
return false;
}
if ( $create ) {
return $this->newSession( null, false, false, $session->userID );
}
return true;
}
/**
* Creates a new session from the data provided. The
* expiration time is optional and will be set to the
* system default if not provided.
*
* @param {int} [$ID] - The User ID of the new session holder.
* @param {int} [$expire] - The expiration time (in seconds).
* @return {bool}
*/
public function newSession( $expire = null, $override = false, $remember = false, $userID = null ) {
if ( empty( $expire ) ) {
// default Session Expiration is 24 hours
$expireLimit = Config::getValue( 'main/loginTimer' );
$expire = ( time() + $expireLimit );
Debug::log( 'Using default expiration time' );
}
$lastPage = App::getUrl();
if ( self::$activeSession != false ) {
// there is already an active session
if ( $override === false ) {
Debug::error( 'No need for a new session.' );
return false;
}
// We can override the active session
$data = self::$db->get( $this->tableName, [ 'userID', '=', self::$activeSession->ID ] );
if ( $data->count() ) {
Debug::log( 'Deleting old session from db' );
$session = self::$db->first();
$this->destroy( $session->ID );
}
}
if ( empty( $userID ) ) {
if ( App::$activeUser === null ) {
Debug::info( 'Must provide user details to create a new session.' );
return false;
}
$userID = App::$activeUser->ID;
}
$userObject = self::$user->findById( $userID );
if ( $userObject === false ) {
Debug::info( 'User not found.' );
return false;
}
$token = Code::genToken();
$result = self::$db->insert(
$this->tableName,
[
'username' => $userObject->username,
'hash' => $userObject->password,
'userGroup' => $userObject->userGroup,
'userID' => $userObject->ID,
'lastPage' => $lastPage,
'expire' => $expire,
'ip' => $_SERVER['REMOTE_ADDR'],
'token' => $token,
]
);
$sessionID = self::$db->lastId();
$sessionData = self::$db->get( $this->tableName, [ 'ID', '=', $sessionID ] )->first();
Session::put( 'SessionID', $sessionID );
if ( $remember ) {
Cookie::put( 'RememberToken', $token, ( time() + ( 3600 * 24 * 30 ) ) );
}
self::$activeSession = $sessionData;
return true;
}
/**
* Function to update the users' current active page.
* NOTE: Current session assumed if no $id is provided.
*
* @param {string} [$page] - The name of the page you are updating to.
* @param {int|null} [$id] - The ID of the session you are updating.
* @return {bool}
*/
public function updatePage( $page, $id = null ) {
if ( empty( $id ) ) {
if ( self::$activeSession === false ) {
Debug::info( 'Session::updatePage - Must provide session ID or have active session' );
return false;
}
$id = self::$activeSession->ID;
}
if ( !Check::id( $id ) ) {
Debug::info( 'Session::updatePage - Invalid ID' );
return false;
}
if ( !self::$db->update( $this->tableName, $id, [ 'lastPage' => $page ] ) ) {
Debug::info( 'Session::updatePage - Failed to update database' );
return false;
}
return true;
}
/**
* Destroy a session.
*
* @param {int} [$id] - The ID of the session you wish to destroy.
* @return {bool}
*/
public function destroy( $id ) {
Session::delete( 'SessionID' );
Cookie::delete( 'RememberToken' );
if ( !Check::id( $id ) ) {
Debug::info( 'Session::destroy - Invalid ID' );
return false;
}
$data = self::$db->get( $this->tableName, [ 'ID', '=', $id ] );
if ( !$data->count() ) {
Debug::info( 'Session::destroy - Session not found in DB' );
return false;
}
self::$db->delete( $this->tableName, [ 'ID', '=', $id ] );
self::$activeSession = false;
return true;
}
}

207
app/models/token.php Executable file
View File

@ -0,0 +1,207 @@
<?php
/**
* app/models/token.php
*
* This class is used for the manipulation of the tokens database table.
*
* @version 3.0
* @author Joey Kimsey <Joey@thetempusproject.com>
* @link https://TheTempusProject.com
* @license https://opensource.org/licenses/MIT [MIT LICENSE]
*/
namespace TheTempusProject\Models;
use TheTempusProject\Bedrock\Functions\Check;
use TheTempusProject\Canary\Bin\Canary as Debug;
use TheTempusProject\Classes\DatabaseModel;
use TheTempusProject\Bedrock\Classes\Config;
use TheTempusProject\TheTempusProject as App;
class Token extends DatabaseModel {
public $tableName = 'tokens';
public $modelVersion = '1.0';
public $configName = 'api';
public $databaseMatrix = [
[ 'name', 'varchar', '128' ],
[ 'token_type', 'varchar', '8' ],
[ 'notes', 'text', '' ],
[ 'token', 'varchar', '64' ],
[ 'secret', 'varchar', '256' ],
[ 'createdAt', 'int', '10' ],
[ 'createdBy', 'int', '10' ],
[ 'expiresAt', 'int', '10' ],
];
public $searchFields = [
'name',
'token',
];
public $permissionMatrix = [
'addAppToken' => [
'pretty' => 'Add Application Tokens',
'default' => false,
],
'addAppToken' => [
'pretty' => 'Add Personal Tokens',
'default' => false,
],
];
public $configMatrix = [
'apiAccessApp' => [
'type' => 'radio',
'pretty' => 'Enable Api Access for Personal Tokens.',
'default' => true,
],
'apiAccessPersonal' => [
'type' => 'radio',
'pretty' => 'Enable Api Access for Personal Tokens.',
'default' => true,
],
'AppAccessTokenExpiration' => [
'type' => 'text',
'pretty' => 'How long before app tokens expire (in seconds)',
'default' => 2592000,
],
'UserAccessTokenExpiration' => [
'type' => 'text',
'pretty' => 'How long before user tokens expire (in seconds)',
'default' => 604800,
],
];
public function create( $name, $note, $token_type = 'app' ) {
if ( 'app' == $token_type ) {
$expiration = Config::getValue( 'api/AppAccessTokenExpiration' );
if ( empty( $expiration ) ) {
$expiration = $this->configMatrix['AppAccessTokenExpiration']['default'];
}
} else {
$expiration = Config::getValue( 'api/UserAccessTokenExpiration' );
if ( empty( $expiration ) ) {
$expiration = $this->configMatrix['UserAccessTokenExpiration']['default'];
}
}
$expireTime = time() + $expiration;
$fields = [
'name' => $name,
'notes' => $note,
'token_type' => $token_type,
'createdBy' => App::$activeUser->ID,
'createdAt' => time(),
'expiresAt' => $expireTime,
'token' => generateToken(),
'secret' => generateRandomString(256),
];
if ( self::$db->insert( $this->tableName, $fields ) ) {
return true;
}
return false;
}
public function findOrCreateUserToken( $user_id, $refresh = false ) {
$test = $this->findUserToken( $user_id );
if ( ! empty( $test ) ) {
if ( ! empty( $refresh ) ) {
$token = $this->refresh( $test->ID, 'user' );
} else {
$token = $test->token;
}
return $token;
}
$expiration = Config::getValue( 'api/UserAccessTokenExpiration' );
if ( empty( $expiration ) ) {
$expiration = $this->configMatrix['UserAccessTokenExpiration']['default'];
}
$expireTime = time() + $expiration;
$token = generateToken();
$fields = [
'name' => 'Browser Token',
'notes' => 'findOrCreateUserToken',
'token_type' => 'user',
'createdBy' => $user_id,
'createdAt' => time(),
'expiresAt' => $expireTime,
'token' => $token,
'secret' => generateRandomString(256),
];
if ( self::$db->insert( $this->tableName, $fields ) ) {
return $token;
}
return false;
}
public function update( $id, $name, $note, $token_type = 'app' ) {
$fields = [
'name' => $name,
'notes' => $note,
'token_type' => $token_type,
];
if ( self::$db->update( $this->tableName, $id, $fields ) ) {
return true;
}
return false;
}
public function refresh( $id, $token_type = 'app' ) {
if ( 'app' == $token_type ) {
$expiration = Config::getValue( 'api/AppAccessTokenExpiration' );
if ( empty( $expiration ) ) {
$expiration = $this->configMatrix['AppAccessTokenExpiration']['default'];
}
} else {
$expiration = Config::getValue( 'api/UserAccessTokenExpiration' );
if ( empty( $expiration ) ) {
$expiration = $this->configMatrix['UserAccessTokenExpiration']['default'];
}
}
$expireTime = time() + $expiration;
$token = generateToken();
$fields = [
'expiresAt' => $expireTime,
'token' => $token,
];
if ( self::$db->update( $this->tableName, $id, $fields ) ) {
return $token;
}
return false;
}
public function findByforwardedUrl( $url ) {
if ( !Check::url( $url ) ) {
Debug::warn( "Invalid forwarded_url: $url" );
return false;
}
$routeData = self::$db->get( $this->tableName, [ 'forwarded_url', '=', $url ] );
if ( !$routeData->count() ) {
Debug::warn( "Could not find route by forwarded url: $url" );
return false;
}
return $this->filter( $routeData->first() );
}
public function findByToken( $token ) {
$data = self::$db->get( $this->tableName, [ 'token', '=', $token ] );
if ( ! $data->count() ) {
return false;
}
return $data->first();
}
public function findBySecret( $secret ) {
$data = self::$db->get( $this->tableName, [ 'secret', '=', $secret ] );
if ( ! $data->count() ) {
return false;
}
return $data->first();
}
public function findUserToken( $user_id ) {
$data = self::$db->get( $this->tableName, [ 'createdBy', '=', $user_id, 'AND', 'token_type', '=', 'user' ] );
if ( ! $data->count() ) {
return false;
}
return $data->first();
}
}

744
app/models/user.php Executable file
View File

@ -0,0 +1,744 @@
<?php
/**
* app/models/user.php
*
* This class is used for the manipulation of the user database table.
*
* @todo needs a re-build
* @todo finish fixing the check functions that were migrated here
* These could go in the Forms class?
*
* @version 3.0
* @author Joey Kimsey <Joey@thetempusproject.com>
* @link https://TheTempusProject.com
* @license https://opensource.org/licenses/MIT [MIT LICENSE]
*/
namespace TheTempusProject\Models;
use TheTempusProject\Bedrock\Functions\Check;
use TheTempusProject\Canary\Bin\Canary as Debug;
use TheTempusProject\Bedrock\Functions\Hash;
use TheTempusProject\Bedrock\Functions\Session;
use TheTempusProject\Bedrock\Functions\Code;
use TheTempusProject\Bedrock\Classes\Config;
use TheTempusProject\Canary\Classes\CustomException;
use TheTempusProject\Classes\Email;
use TheTempusProject\Classes\DatabaseModel;
use TheTempusProject\Classes\Preferences;
use TheTempusProject\Classes\Forms;
use TheTempusProject\TheTempusProject as App;
class User extends DatabaseModel {
public $tableName = 'users';
public $modelVersion = '1.0';
public $databaseMatrix = [
[ 'registered', 'int', '10' ],
[ 'terms', 'int', '1' ],
[ 'confirmed', 'int', '1' ],
[ 'userGroup', 'int', '11' ],
[ 'lastLogin', 'int', '10' ],
[ 'username', 'varchar', '16' ],
[ 'password', 'varchar', '80' ],
[ 'email', 'varchar', '75' ],
[ 'name', 'varchar', '20' ],
[ 'confirmationCode', 'varchar', '80' ],
[ 'prefs', 'text', '' ],
];
public $searchFields = [
'username',
];
public $preferenceMatrix = [
'gender' => [
'pretty' => 'Gender',
'type' => 'select',
'default' => 'unspecified',
'options' => [
'male',
'female',
'other',
'unspecified',
],
],
'newsletter' => [
'pretty' => 'Receive our Newsletter?',
'type' => 'checkbox',
'default' => 'true',
],
'avatar' => [
'pretty' => 'Avatar',
'type' => 'file',
'default' => 'images/defaultAvatar.png',
],
'timezone' => [
'pretty' => 'Timezone',
'type' => 'timezone',
'default' => 'America/New_York',
],
'dateFormat' => [
'pretty' => 'Date Format',
'type' => 'select',
'default' => 'F j, Y',
'options' => [
'1-8-1991' => 'n-j-Y',
'8-1-1991' => 'j-n-Y',
'01-08-1991' => 'm-d-Y',
'08-01-1991' => 'd-m-Y',
'January 8, 1991' => 'F-j-Y',
'8 January, 1991' => 'j-F-Y',
'January 08, 1991' => 'F-d-Y',
'08 January, 1991' => 'd-F-Y',
'Jan 8, 1991' => 'M-j-Y',
'8 Jan 1991' => 'j-M-Y',
'Jan 08, 1991' => 'M-d-Y',
'08 Jan 1991' => 'd-M-Y',
],
],
'timeFormat' => [
'pretty' => 'Time Format',
'type' => 'select',
'default' => 'g:i:s A',
'options' => [
'3:33:33 AM' => 'g:i:s A',
'03:33:33 AM' => 'h:i:s A',
'3:33:33 am' => 'g:i:s a',
'03:33:33 am' => 'h:i:s a',
'3:33:33 (military)' => 'G:i:s',
'03:33:33 (military)' => 'H:i:s',
],
],
'pageLimit' => [
'pretty' => 'Items Displayed Per Page',
'type' => 'select',
'default' => '10',
'options' => [
'10',
'15',
'20',
'25',
'50',
],
],
'darkMode' => [
'pretty' => 'Enable Dark-Mode viewing',
'type' => 'checkbox',
'default' => 'false',
],
];
protected static $avatars;
protected static $preferences;
protected static $group;
protected static $usernames;
protected $data;
public function __construct() {
parent::__construct();
self::$user = $this;
self::$preferences = new Preferences;
self::$group = new Group;
}
public function getPreferences( $id ) {
if ( !Check::id( $id ) ) {
return false;
}
$userData = $this->get( $id );
$prefs = json_decode( $userData->prefs, true );
return $prefs;
}
public function getPreferencesDelta() {
$defaults = $this->getDefaultPreferences();
foreach ( $defaults as $key => $value ) {
if ( isset( self::$preferences[ $key ] ) ) {
$defaults[ $key ] = self::$preferences[ $key ];
}
}
return $defaults;
}
public function getDefaultPreferences() {
return self::$preferences->getDefaultPreferencesArray();
}
/**
* Check the database for a user with the same email.
*
* @param {string} [$email] - The email being tested.
* @return {bool}
*/
public function noEmailExists( $email ) {
if ( Check::email( $email ) ) {
$emailQuery = self::$db->get( $this->tableName, [ 'email', '=', $email ] );
if ( $emailQuery->count() == 0 ) {
return true;
}
}
// self::addError("Email is already in use.", $email);
return false;
}
/**
* Check the database for a user with the same username.
*
* @param {string} [$data] - The string being tested.
* @return {bool}
*/
public function usernameExists( $data ) {
if ( Forms::checkUsername( $data ) ) {
$usernameResults = self::$db->get( $this->tableName, [ 'username', '=', $data ] );
if ( $usernameResults->count() ) {
return true;
}
}
// self::addError("No user exists in the DB.", $data);
return false;
}
/**
* Checks username formatting.
*
* Requirements:
* - 4 - 16 characters long
* - must only contain numbers and letters: [A - Z] , [a - z], [0 - 9]
*
* @param {string} [$data] - The string being tested.
* @return {bool}
*/
public function checkUsername( $data ) {
if ( strlen( $data ) > 16 ) {
// self::addError("Username must be be 4 to 16 numbers or letters.", $data);
return false;
}
if ( strlen( $data ) < 4 ) {
// self::addError("Username must be be 4 to 16 numbers or letters.", $data);
return false;
}
if ( !ctype_alnum( $data ) ) {
// self::addError("Username must be be 4 to 16 numbers or letters.", $data);
return false;
}
return true;
}
/**
* Find and define usernames by user ID.
*
* @param {int} [$id] - The ID of the user you are looking for.
* @return {string} - Either the username or 'unknown' will be returned.
*/
public function getUsername( $id ) {
if ( !Check::id( $id ) ) {
return false;
}
if ( !isset( self::$usernames[ $id ] ) ) {
$user = $this->get( $id );
if ( $user !== false ) {
self::$usernames[ $id ] = $user->username;
} else {
self::$usernames[ $id ] = 'Unknown';
}
}
return self::$usernames[ $id ];
}
/**
* Since we need a cache of the usernames, we use this function
* to find/return all usernames based on ID.
*
* @param {int} [$username] - The username of the user you are looking for.
* @return {int}
*/
public function getID( $username ) {
if ( !Forms::checkUsername( $username ) ) {
return false;
}
$user = $this->get( $username );
if ( $user !== false ) {
return $user->ID;
} else {
return 0;
}
}
/**
* Find and define user avatar image urls.
*
* @param {int} [$id] - The ID of the user you are looking for.
* @return {string} - Either the username or 'unknown' will be returned.
*/
public function getAvatar( $id ) {
if ( !Check::id( $id ) ) {
return false;
}
if ( !isset( self::$avatars[ $id ] ) ) {
if ( $this->get( $id ) ) {
self::$avatars[ $id ] = self::data()->avatar;
} else {
self::$avatars[ $id ] = '{BASE}images/defaultAvatar.png';
}
}
return self::$avatars[ $id ];
}
/**
* Delete the specified user(s).
*
* @param {int|array} [$data] - The log ID or array of ID's to be deleted.
* @return {bool}
*/
public function delete( $idArray ) {
if ( !is_array( $idArray ) ) {
$idArray = [ $idArray ];
}
foreach ( $idArray as $id ) {
if ( App::$activeUser->ID == $id ) {
Debug::info( 'Attempting to delete own account.' );
return false;
}
$user = $this->get( $id );
if (
'Super' == $user->groupName
&& 'Super' !== App::$activeGroup->name
) {
Debug::info( 'Attempting to delete superior account.' );
return false;
}
}
return parent::delete( $idArray );
}
/**
* Attempt to authenticate a user login and set them as the active user.
*
* @param {string} [$username] - The username being used to login.
* @param {string} [$password] - The un-hashed password.
* @param {bool} [$remember] - Whether the user wishes to be remembered or not.
* @return {bool}
*/
public function logIn( $username, $password, $remember = false ) {
if ( !isset( self::$session ) ) {
self::$session = new Sessions;
}
if ( !isset( self::$log ) ) {
self::$log = new Log;
}
Debug::group( 'login', 1 );
if ( !Forms::checkUsername( $username ) ) {
Debug::warn( 'Invalid Username.' );
return false;
}
if ( !$this->get( $username ) ) {
self::$log->login( 0, "User not found: $username" );
Debug::warn( "User not found: $username" );
return false;
}
// login attempts protection.
$timeLimit = ( time() - 3600 );
$limit = Config::getValue( 'main/loginLimit' );
$user = $this->data();
if ( $limit > 0 ) {
$limitCheck = self::$db->get(
'logs',
[
'source', '=', 'login',
'AND',
'userID', '=', $user->ID,
'AND',
'time', '>=', $timeLimit,
'AND',
'action', '!=', 'pass',
]
);
if ( $limitCheck->count() >= $limit ) {
Debug::info( 'login: Limit reached.', 1 );
self::$log->login( $user->ID, 'Too many failed attempts.' );
Debug::warn( 'Too many failed login attempts, please try again later.' );
return false;
}
}
if ( !Check::password( $password ) ) {
Debug::warn( 'Invalid password.' );
self::$log->login( $user->ID, 'Invalid Password.' );
return false;
}
if ( !Hash::check( $password, $user->password ) ) {
Debug::warn( 'Pass hash does not match.' );
self::$log->login( $user->ID, 'Wrong Password.' );
return false;
}
self::$session->newSession( null, true, $remember, $user->ID );
self::$log->login( $this->data()->ID, 'pass' );
$this->update( $this->data()->ID, [ 'lastLogin' => time() ] );
Debug::gend();
return true;
}
/**
* Log out the currently active user.
*/
public function logOut() {
if ( !isset( self::$session ) ) {
self::$session = new Sessions;
}
Debug::group( 'Logout', 1 );
self::$session->destroy( Session::get( 'SessionToken' ) );
App::$isLoggedIn = false;
App::$isMember = false;
App::$isMod = false;
App::$isAdmin = false;
App::$activeUser = null;
Debug::info( 'User has been logged out.' );
Debug::gend();
return null;
}
/**
* Change a user password.
*
* @param {string} [$code] - The confirmation code required from the password email.
* @param {string} [$password] - The new password for the user's account.
* @return {bool}
*/
public function changePassword( $code, $password ) {
if ( !Check::password( $password ) ) {
return false;
}
$data = self::$db->get( $this->tableName, [ 'confirmationCode', '=', $code ] );
if ( $data->count() ) {
$this->data = $data->first();
$this->update(
$this->data->ID,
[ 'password' => Hash::make( $password ), 'confirmationCode' => '', ],
);
return true;
}
return false;
}
/**
* Create a list of registered users.
*
* @param {array} [$filter] - A filter to be applied to the users list.
* @return {bool|object}
*/
public function userList( $filter = null ) {
if ( ! empty( $filter ) ) {
switch ( $filter ) {
case 'newsletter':
$data = self::$db->searchColumn( $this->tableName, 'prefs', 'newsletter":"true' );
break;
default:
$data = self::$db->get( $this->tableName, '*' );
break;
}
} else {
$data = self::$db->get( $this->tableName, '*' );
}
if ( ! $data->count() ) {
return false;
}
return (object) $data->results();
}
/**
* Create a list of recently registered users.
*
* @param {int} [$limit] - How many posts you would like returned.
* @return {bool|object}
*/
public function recent( $limit = null ) {
if ( empty( $limit ) ) {
$data = self::$db->get( $this->tableName, '*' );
} else {
$data = self::$db->get( $this->tableName, [ 'ID', '>', '0' ], 'ID', 'DESC', [ 0, $limit ] );
}
if ( !$data->count() ) {
return false;
}
return (object) $data->results();
}
/**
* Check the database for a user with the same confirmation code.
*
* @param {string} [$code] - The confirmation code being checked.
* @return {bool}
*/
public function checkCode( $code ) {
$data = self::$db->get( $this->tableName, [ 'confirmationCode', '=', $code ] );
if ( $data->count() > 0 ) {
return true;
}
Debug::error( 'User confirmation code not found.' );
return false;
}
/**
* Generate and save a new confirmation code for the user.
*
* @param {int} [$id] - The user ID to update the confirmation code for.
* @return {bool}
*/
public function newCode( $id ) {
$data = self::$db->get( $this->tableName, [ 'ID', '=', $id ] );
if ( $data->count() == 0 ) {
return false;
}
$this->data = $data->first();
$Ccode = md5( uniqid() );
$this->update(
$this->data->ID,
[ 'confirmationCode' => $Ccode ],
);
return true;
}
/**
* Finds and confirms a user by their confirmation code.
*
* @param {string} [$code] - The confirmation code sent to the user.
* @return {bool}
*/
public function confirm( $code ) {
$data = self::$db->get( $this->tableName, [ 'confirmationCode', '=', $code ] );
if ( $data->count() ) {
$this->data = $data->first();
$this->update(
$this->data->ID,
[ 'confirmed' => 1, 'confirmationCode' => '', ],
);
return true;
}
return false;
}
/**
* Check if the specified user exists or not.
*
* @return {bool}
* @todo this function should actually check for a user
*/
public function exists() {
return ( !empty( $this->data ) ) ? true : false;
}
public function filter( $data, $params = [] ) {
foreach ( $data as $instance ) {
if ( !is_object( $instance ) ) {
$instance = (object) $data;
$end = true;
}
if ( $instance->confirmed == 1 ) {
$instance->confirmedText = 'Yes';
} else {
$instance->confirmedText = 'No';
}
$group = self::$group->findById( $instance->userGroup );
if ( !empty( $group ) ) {
$instance->groupName = $group->name;
} else {
$instance->groupName = 'Unknown';
}
$instance->prefs = json_decode( $instance->prefs, true );
$instance->gender = $instance->prefs['gender'];
$instance->avatar = $instance->prefs['avatar'];
$instance->usernamePretty = \ucfirst( $instance->username );
$out[] = $instance;
if ( !empty( $end ) ) {
$out = $out[0];
break;
}
}
return $out;
}
/**
* Get user data from an ID or username.
*
* @param {int|string} [$user] - Either the username or user ID being searched for.
* @return {bool|array}
*/
public function get( $user ) {
if ( empty( self::$group ) ) {
self::$group = new Group;
}
$user = (string) $user;
$field = ( ctype_digit( $user ) ) ? 'ID' : 'username';
if ( $field == 'username' ) {
if ( !Forms::checkUsername( $user ) ) {
Debug::info( 'modelUser->get Username improperly formatted.' );
return false;
}
} else {
if ( !Check::id( $user ) ) {
Debug::info( 'modelUser->get Invalid ID.' );
return false;
}
}
$data = self::$db->get( $this->tableName, [ $field, '=', $user ] );
if ( !$data->count() ) {
Debug::info( "modelUser->get User not found: $user" );
return false;
}
$this->data = $this->filter( $data->first() );
return $this->data;
}
/**
* Find a user by email address.
*
* @param {string} [$email] - The email being searched for.
* @return {bool}
*/
public function findByEmail( $email ) {
if ( Check::email( $email ) ) {
$data = self::$db->get( $this->tableName, [ 'email', '=', $email ] );
if ( $data->count() ) {
$this->data = $data->first();
return true;
}
}
Debug::error( "modelUser->findByEmail - User not found by email: $email" );
return false;
}
/**
* Create a new user.
*
* @param {array} [$fields] - The New User's data.
* @return {bool}
*/
public function create( $fields = [] ) {
if ( empty( $fields ) ) {
return false;
}
if ( !isset( $fields['email' ] ) ) {
return false;
}
if ( !isset( $fields['prefs' ] ) ) {
$fields['prefs'] = json_encode( $this->getDefaultPreferences() );
}
if ( !isset( $fields['userGroup' ] ) ) {
$fields['userGroup'] = Config::getValue( 'group/defaultGroup' );
} else {
if ( in_array( $fields['userGroup'], [ '1', 1 ] ) ) {
if ( App::$activeGroup && 'Super' !== App::$activeGroup->name ) {
Debug::error( 'You do not have permission to do this.' );
}
}
}
if ( !isset( $fields['registered' ] ) ) {
$fields['registered'] = time();
}
if ( !isset( $fields['confirmed' ] ) ) {
$code = Code::genConfirmation();
$fields['confirmed'] = 0;
$fields['confirmationCode'] = $code;
Email::send( $fields['email'], 'confirmation', $code, [ 'template' => true ] );
}
if ( !self::$db->insert( $this->tableName, $fields ) ) {
Debug::error( 'User not created.' );
return false;
}
return true;
}
/**
* Update a user database entry.
*
* @param {array} [$fields] - The fields to be updated.
* @param {int} [$id] - The user ID being updated.
* @return {bool}
*/
public function update( $id, $fields = [] ) {
if ( !Check::id( $id ) ) {
return false;
}
if ( !self::$db->update( $this->tableName, $id, $fields ) ) {
new CustomException( 'userUpdate' );
Debug::error( "User: $id not updated: " . var_export( $fields, true ) );
return false;
}
return true;
}
/**
* Update a user's preferences.
*
* @param {array} [$fields] - The fields to be updated.
* @param {int} [$id] - The user ID being updated.
* @return {bool}
*/
public function updatePrefs( $fields, $id ) {
if ( !Check::id( $id ) ) {
return false;
}
$userData = $this->get( $id );
$prefsInput = $userData->prefs;
foreach ( $fields as $name => $value ) {
$prefsInput[$name] = $value;
}
$fields = [ 'prefs' => json_encode( $prefsInput ) ];
if ( !self::$db->update( $this->tableName, $id, $fields ) ) {
Debug::error( "User: $id not updated." );
return false;
}
if ( $id === App::$activeUser->ID ) {
$userData = $this->get( $id );
App::$activeUser = $userData;
}
return true;
}
/**
* Return the most recent database data.
*
* @return {array} - An array of the user data.
*/
public function data() {
return $this->data;
}
public function authorize( $username, $password ) {
if ( !isset( self::$log ) ) {
self::$log = new Log;
}
if ( !$this->get( $username ) ) {
self::$log->login( 0, "API: User not found: $username" );
return false;
}
// login attempts protection.
$timeLimit = ( time() - 3600 );
$limit = Config::getValue( 'main/loginLimit' );
$user = $this->data();
if ( $limit > 0 ) {
$limitCheck = self::$db->get(
'logs',
[
'source', '=', 'login',
'AND',
'userID', '=', $user->ID,
'AND',
'time', '>=', $timeLimit,
'AND',
'action', '!=', 'pass',
]
);
if ( $limitCheck->count() >= $limit ) {
self::$log->login( $user->ID, 'API: Too many failed attempts.' );
return false;
}
}
if ( !Check::password( $password ) ) {
self::$log->login( $user->ID, 'API: Invalid Password.' );
return false;
}
if ( !Hash::check( $password, $user->password ) ) {
self::$log->login( $user->ID, 'API: Wrong Password.' );
return false;
}
self::$log->login( $this->data()->ID, 'API: pass' );
return $user;
}
}

View File

@ -0,0 +1,102 @@
<?php
/**
* app/plugins/blog/controllers/admin/blog.php
*
* This is the Blog admin controller.
*
* @package TP Blog
* @version 3.0
* @author Joey Kimsey <Joey@thetempusproject.com>
* @link https://TheTempusProject.com
* @license https://opensource.org/licenses/MIT [MIT LICENSE]
*/
namespace TheTempusProject\Controllers\Admin;
use TheTempusProject\Bedrock\Functions\Check;
use TheTempusProject\Bedrock\Functions\Input;
use TheTempusProject\Houdini\Classes\Issues;
use TheTempusProject\Houdini\Classes\Views;
use TheTempusProject\Houdini\Classes\Navigation;
use TheTempusProject\Houdini\Classes\Components;
use TheTempusProject\Classes\AdminController;
use TheTempusProject\Classes\Forms;
use TheTempusProject\Models\Posts;
class Blog extends AdminController {
public static $posts;
public function __construct() {
parent::__construct();
self::$posts = new Posts;
self::$title = 'Admin - Blog';
}
public function index( $data = null ) {
Views::view( 'blog.admin.list', self::$posts->listPosts( ['includeDrafts' => true] ) );
}
public function create( $data = null ) {
if ( !Input::exists( 'submit' ) ) {
return Views::view( 'blog.admin.create' );
}
if ( !Forms::check( 'newBlogPost' ) ) {
Issues::add( 'error', [ 'There was an error with your request.' => Check::userErrors() ] );
return $this->index();
}
$result = self::$posts->newPost( Input::post( 'title' ), Input::post( 'blogPost' ), Input::post( 'slug' ), Input::post( 'submit' ) );
if ( $result ) {
Issues::add( 'success', 'Your post has been created.' );
return $this->index();
} else {
Issues::add( 'error', [ 'There was an unknown error submitting your data.' => Check::userErrors() ] );
return $this->index();
}
}
public function edit( $data = null ) {
if ( !Input::exists( 'submit' ) ) {
return Views::view( 'blog.admin.edit', self::$posts->findById( $data ) );
}
if ( Input::post( 'submit' ) == 'preview' ) {
return Views::view( 'blog.admin.preview', self::$posts->preview( Input::post( 'title' ), Input::post( 'blogPost' ) ) );
}
if ( !Forms::check( 'editBlogPost' ) ) {
Issues::add( 'error', [ 'There was an error with your form.' => Check::userErrors() ] );
return $this->index();
}
if ( self::$posts->updatePost( $data, Input::post( 'title' ), Input::post( 'blogPost' ), Input::post( 'slug' ), Input::post( 'submit' ) ) === true ) {
Issues::add( 'success', 'Post Updated.' );
return $this->index();
}
Issues::add( 'error', 'There was an error with your request.' );
$this->index();
}
public function view( $data = null ) {
$blogData = self::$posts->findById( $data );
if ( $blogData !== false ) {
return Views::view( 'blog.admin.view', $blogData );
}
Issues::add( 'error', 'Post not found.' );
$this->index();
}
public function delete( $data = null ) {
if ( $data == null ) {
if ( Input::exists( 'B_' ) ) {
$data = Input::post( 'B_' );
}
}
if ( !self::$posts->delete( (array) $data ) ) {
Issues::add( 'error', 'There was an error with your request.' );
} else {
Issues::add( 'success', 'Post has been deleted' );
}
$this->index();
}
public function preview( $data = null ) {
Views::view( 'blog.admin.preview', self::$posts->preview( Input::post( 'title' ), Input::post( 'blogPost' ) ) );
}
}

View File

@ -0,0 +1,186 @@
<?php
/**
* app/plugins/blog/controllers/blog.php
*
* This is the blog controller.
*
* @package TP Blog
* @version 3.0
* @author Joey Kimsey <Joey@thetempusproject.com>
* @link https://TheTempusProject.com
* @license https://opensource.org/licenses/MIT [MIT LICENSE]
*/
namespace TheTempusProject\Controllers;
use TheTempusProject\Bedrock\Functions\Check;
use TheTempusProject\Canary\Bin\Canary as Debug;
use TheTempusProject\Bedrock\Functions\Input;
use TheTempusProject\Houdini\Classes\Components;
use TheTempusProject\Houdini\Classes\Issues;
use TheTempusProject\Houdini\Classes\Views;
use TheTempusProject\Houdini\Classes\Template;
use TheTempusProject\Classes\Controller;
use TheTempusProject\Classes\Forms;
use TheTempusProject\Hermes\Functions\Redirect;
use TheTempusProject\Bedrock\Functions\Session;
use TheTempusProject\Plugins\Blog as BlogPlugin;
use TheTempusProject\TheTempusProject as App;
use TheTempusProject\Plugins\Comments;
use TheTempusProject\Models\Comments as CommentsModel;
use TheTempusProject\Models\Posts as PostsModel;
class Blog extends Controller {
protected static $blog;
protected static $posts;
public function __construct() {
parent::__construct();
Template::setTemplate( 'blog' );
self::$posts = new PostsModel;
}
public function index() {
self::$title = '{SITENAME} Blog';
self::$pageDescription = 'The {SITENAME} blog is where you can find various posts containing information ranging from current projects and general information to editorial and opinion based content.';
Views::view( 'blog.list', self::$posts->listPosts() );
}
public function rss() {
Debug::log( 'Controller initiated: ' . __METHOD__ . '.' );
self::$title = '{SITENAME} RSS Feed';
self::$pageDescription = '{SITENAME} blog RSS feed.';
Template::setTemplate( 'rss' );
header( 'Content-Type: text/xml' );
return Views::view( 'blog.rss', self::$posts->listPosts( ['stripHtml' => true] ) );
}
public function comments( $sub = null, $data = null ) {
Debug::log( 'Controller initiated: ' . __METHOD__ . '.' );
if ( empty( $sub ) || empty( $data ) ) {
Issues::add( 'error', 'There was an issue with your request. Please check the url and try again.' );
return $this->index();
}
if ( class_exists( 'TheTempusProject\Plugins\Comments' ) ) {
$plugin = new Comments;
if ( ! $plugin->checkEnabled() ) {
Issues::add( 'error', 'Comments are disabled.' );
return $this->index();
}
$comments = new CommentsModel;
} else {
Debug::info( 'error', 'Comments plugin missing.' );
return $this->index();
}
switch ( $sub ) {
case 'post':
$content = self::$posts->findById( (int) $data );
if ( empty( $content ) ) {
Issues::add( 'error', 'Unknown Content.' );
return $this->index();
}
return $plugin->formPost( self::$posts->tableName, $content, 'blog/post/' );
case 'edit':
$content = $comments->findById( $data );
if ( empty( $content ) ) {
Issues::add( 'error', 'Unknown Comment.' );
return $this->index();
}
return $plugin->formEdit( self::$posts->tableName, $content, 'blog/post/' );
case 'delete':
$content = $comments->findById( $data );
if ( empty( $content ) ) {
Issues::add( 'error', 'Unknown Comment.' );
return $this->index();
}
return $plugin->formDelete( self::$posts->tableName, $content, 'blog/post/' );
}
}
public function post( $id = null ) {
if ( empty( $id ) ) {
return $this->index();
}
$post = self::$posts->findById( $id );
if ( empty( $post ) ) {
$post = self::$posts->findBySlug( $id );
if ( empty( $post ) ) {
return $this->index();
}
}
Debug::log( 'Controller initiated: ' . __METHOD__ . '.' );
self::$title = 'Blog Post';
if ( Input::exists( 'contentId' ) ) {
$this->comments( 'post', Input::post( 'contentId' ) );
}
Components::set( 'CONTENT_ID', $id );
Components::set( 'COMMENT_TYPE', self::$posts->tableName );
Components::set( 'NEWCOMMENT', '' );
Components::set( 'count', '0' );
Components::set( 'COMMENTS', '' );
if ( class_exists( 'TheTempusProject\Plugins\Comments' ) ) {
$plugin = new Comments;
if ( $plugin->checkEnabled() ) {
$comments = new CommentsModel;
if ( App::$isLoggedIn ) {
Components::set( 'NEWCOMMENT', Views::simpleView( 'comments.create' ) );
} else {
Components::set( 'NEWCOMMENT', '' );
}
Components::set( 'count', $comments->count( self::$posts->tableName, $post->ID ) );
Components::set( 'COMMENTS', Views::simpleView( 'comments.list', $comments->display( 10, self::$posts->tableName, $post->ID ) ) );
}
}
self::$title .= ' - ' . $post->title;
self::$pageDescription = strip_tags( $post->contentSummaryNoLink );
Views::view( 'blog.post', $post );
}
public function author( $data = null ) {
if ( empty( $data ) ) {
return $this->index();
}
Debug::log( 'Controller initiated: ' . __METHOD__ . '.' );
self::$title = 'Posts by author - {SITENAME}';
self::$pageDescription = '{SITENAME} blog posts easily and conveniently sorted by author.';
Views::view( 'blog.list', self::$posts->byAuthor( $data ) );
}
public function month( $month = null, $year = 0 ) {
if ( empty( $month ) ) {
return $this->index();
}
Debug::log( 'Controller initiated: ' . __METHOD__ . '.' );
self::$title = 'Posts By Month - {SITENAME}';
self::$pageDescription = '{SITENAME} blog posts easily and conveniently sorted by month.';
Views::view( 'blog.list', self::$posts->byMonth( $month, $year ) );
}
public function year( $year = null ) {
if ( empty( $year ) ) {
return $this->index();
}
Debug::log( 'Controller initiated: ' . __METHOD__ . '.' );
self::$title = 'Posts by Year - {SITENAME}';
self::$pageDescription = '{SITENAME} blog posts easily and conveniently sorted by years.';
Views::view( 'blog.list', self::$posts->byYear( $year ) );
}
public function search() {
$results = [];
if ( Input::exists( 'submit' ) ) {
$dbResults = self::$posts->search( Input::post('searchTerm') );
if ( ! empty( $dbResults ) ) {
$results = $dbResults;
}
}
Components::set( 'searchResults', Views::simpleView( 'blog.list', $results ) );
Views::view( 'blog.searchResults' );
}
}

81
app/plugins/blog/forms.php Executable file
View File

@ -0,0 +1,81 @@
<?php
/**
* app/plugins/blog/forms.php
*
* This houses all of the form checking functions for this plugin.
*
* @package TP Blog
* @version 3.0
* @author Joey Kimsey <Joey@thetempusproject.com>
* @link https://TheTempusProject.com
* @license https://opensource.org/licenses/MIT [MIT LICENSE]
*/
namespace TheTempusProject\Plugins\Blog;
use TheTempusProject\Bedrock\Functions\Input;
use TheTempusProject\Bedrock\Functions\Check;
use TheTempusProject\Classes\Forms;
class BlogForms extends Forms {
/**
* Adds these functions to the form list.
*/
public function __construct() {
self::addHandler( 'newBlogPost', __CLASS__, 'newBlogPost' );
self::addHandler( 'editBlogPost', __CLASS__, 'editBlogPost' );
}
/**
* Validates the new blog post form.
*
* @return {bool}
*/
public static function newBlogPost() {
if ( !Input::exists( 'title' ) ) {
self::addUserError( 'You must specify title' );
return false;
}
if ( !self::dataTitle( Input::post( 'title' ) ) ) {
self::addUserError( 'Invalid title' );
return false;
}
if ( !Input::exists( 'blogPost' ) ) {
self::addUserError( 'You must specify a post' );
return false;
}
/** You cannot use the token check due to how tinymce reloads the page
if (!self::token()) {
return false;
}
*/
return true;
}
/**
* Validates the edit blog post form.
*
* @return {bool}
*/
public static function editBlogPost() {
if ( !Input::exists( 'title' ) ) {
self::addUserError( 'You must specify title' );
return false;
}
if ( !self::dataTitle( Input::post( 'title' ) ) ) {
self::addUserError( 'Invalid title' );
return false;
}
if ( !Input::exists( 'blogPost' ) ) {
self::addUserError( 'You must specify a post' );
return false;
}
/** You cannot use the token check due to how tinymce reloads the page
if (!self::token()) {
return false;
}
*/
return true;
}
}
new BlogForms;

356
app/plugins/blog/models/posts.php Executable file
View File

@ -0,0 +1,356 @@
<?php
/**
* app/plugins/blog/models/blog.php
*
* This class is used for the manipulation of the blog database table.
*
* @package TP Blog
* @version 3.0
* @author Joey Kimsey <Joey@thetempusproject.com>
* @link https://TheTempusProject.com
* @license https://opensource.org/licenses/MIT [MIT LICENSE]
*/
namespace TheTempusProject\Models;
use TheTempusProject\Canary\Bin\Canary as Debug;
use TheTempusProject\Bedrock\Functions\Check;
use TheTempusProject\Bedrock\Functions\Sanitize;
use TheTempusProject\Classes\DatabaseModel;
use TheTempusProject\TheTempusProject as App;
use TheTempusProject\Canary\Classes\CustomException;
use TheTempusProject\Houdini\Classes\Filters;
use TheTempusProject\Plugins\Comments as CommentPlugin;
use TheTempusProject\Models\Comments;
class Posts extends DatabaseModel {
public $tableName = 'posts';
public $searchFields = [
'title',
'slug',
'content',
];
public static $comments = false;
public $databaseMatrix = [
[ 'author', 'int', '11' ],
[ 'created', 'int', '10' ],
[ 'edited', 'int', '10' ],
[ 'draft', 'int', '1' ],
[ 'title', 'varchar', '86' ],
[ 'slug', 'varchar', '64' ],
[ 'content', 'text', '' ],
];
public function __construct() {
parent::__construct();
if ( class_exists( 'TheTempusProject\Plugins\Comments' ) ) {
$comments = new CommentPlugin;
if ( $comments->checkEnabled() ) {
self::$comments = new Comments;
}
}
}
public function newPost( $title, $post, $slug, $draft ) {
if ( !Check::dataTitle( $title ) ) {
Debug::info( 'modelBlog: illegal title.' );
return false;
}
if ( $draft === 'saveDraft' ) {
$draft = 1;
} else {
$draft = 0;
}
$fields = [
'author' => App::$activeUser->ID,
'draft' => $draft,
'slug' => $slug,
'created' => time(),
'edited' => time(),
'content' => Sanitize::rich( $post ),
'title' => $title,
];
if ( !self::$db->insert( $this->tableName, $fields ) ) {
Debug::error( "Blog Post: $data not updated: $fields" );
new customException( 'blogCreate' );
return false;
}
return true;
}
public function updatePost( $id, $title, $content, $slug, $draft ) {
if ( empty( self::$log ) ) {
self::$log = new Log;
}
if ( !Check::id( $id ) ) {
Debug::info( 'modelBlog: illegal ID.' );
return false;
}
if ( !Check::dataTitle( $title ) ) {
Debug::info( 'modelBlog: illegal title.' );
return false;
}
if ( $draft === 'saveDraft' ) {
$draft = 1;
} else {
$draft = 0;
}
$fields = [
'draft' => $draft,
'slug' => $slug,
'edited' => time(),
'content' => Sanitize::rich( $content ),
'title' => $title,
];
if ( !self::$db->update( $this->tableName, $id, $fields ) ) {
new CustomException( 'blogUpdate' );
Debug::error( "Blog Post: $id not updated: $fields" );
return false;
}
self::$log->admin( "Updated Blog Post: $id" );
return true;
}
public function preview( $title, $content ) {
if ( !Check::dataTitle( $title ) ) {
Debug::info( 'modelBlog: illegal characters.' );
return false;
}
$fields = [
'title' => $title,
'content' => $content,
'authorName' => App::$activeUser->username,
'created' => time(),
];
return (object) $fields;
}
public function filter( $postArray, $params = [] ) {
foreach ( $postArray as $instance ) {
if ( !is_object( $instance ) ) {
$instance = $postArray;
$end = true;
}
$draft = '';
$authorName = self::$user->getUsername( $instance->author );
// Summarize
if ( ! empty( $instance->slug ) ) {
$identifier = $instance->slug;
} else {
$identifier = $instance->ID;
}
$cleanPost = Sanitize::contentShort( $instance->content );
// By Word
$wordsArray = explode( ' ', $cleanPost );
$wordSummary = implode( ' ', array_splice( $wordsArray, 0, 100 ) );
// By Line
$linesArray = explode( "\n", $cleanPost );
$lineSummary = implode( "\n", array_splice( $linesArray, 0, 5 ) );
if ( strlen( $wordSummary ) < strlen( $lineSummary ) ) {
$contentSummaryNoLink = $wordSummary;
$contentSummary = $wordSummary . '... <a href="{ROOT_URL}blog/post/' . $identifier . '" class="text-decoration-none">Read More</a>';
} else {
$contentSummaryNoLink = $lineSummary;
$contentSummary = $lineSummary . '... <a href="{ROOT_URL}blog/post/' . $identifier . '" class="text-decoration-none">Read More</a>';
}
$instance->contentSummaryNoLink = $contentSummaryNoLink;
$instance->contentSummary = $contentSummary;
if ( isset( $params['stripHtml'] ) && $params['stripHtml'] === true ) {
$instance->contentSummary = strip_tags( $instance->content );
}
if ( $instance->draft != '0' ) {
$draft = ' <b>Draft</b>';
}
$instance->isDraft = $draft;
$instance->authorName = \ucfirst( $authorName );
if ( self::$comments !== false ) {
$instance->commentCount = self::$comments->count( 'blog', $instance->ID );
} else {
$instance->commentCount = 0;
}
$instance->content = Filters::applyOne( 'mentions.0', $instance->content, true );
$instance->content = Filters::applyOne( 'hashtags.0', $instance->content, true );
$out[] = $instance;
if ( !empty( $end ) ) {
$out = $out[0];
break;
}
}
return $out;
}
public function archive( $includeDraft = false ) {
$whereClause = [];
$currentTimeUnix = time();
$x = 0;
$dataOut = [];
$month = date( 'F', $currentTimeUnix );
$year = date( 'Y', $currentTimeUnix );
$previous = date( 'U', strtotime( "$month 1st $year" ) );
if ( $includeDraft !== true ) {
$whereClause = ['draft', '=', '0', 'AND'];
}
while ( $x <= 5 ) {
$where = array_merge( $whereClause, ['created', '<=', $currentTimeUnix, 'AND', 'created', '>=', $previous] );
$data = self::$db->get( $this->tableName, $where );
$x++;
$month = date( 'm', $previous );
$montht = date( 'F', $previous );
$year = date( 'Y', $previous );
if ( !$data ) {
$count = 0;
} else {
$count = $data->count();
}
$dataOut[] = (object) [
'count' => $count,
'month' => $month,
'year' => $year,
'monthText' => $montht,
];
$currentTimeUnix = $previous;
$previous = date( 'U', strtotime( '-1 months', $currentTimeUnix ) );
}
if ( !$data ) {
Debug::info( 'No Blog posts found.' );
return false;
}
return (object) $dataOut;
}
public function recent( $limit = null, $includeDraft = false ) {
$whereClause = [];
if ( $includeDraft !== true ) {
$whereClause = ['draft', '=', '0'];
} else {
$whereClause = '*';
}
if ( empty( $limit ) ) {
$postData = self::$db->get( $this->tableName, $whereClause );
} else {
$postData = self::$db->get( $this->tableName, $whereClause, 'ID', 'DESC', [0, $limit] );
}
if ( !$postData->count() ) {
Debug::info( 'No Blog posts found.' );
return false;
}
return $this->filter( $postData->results() );
}
public function listPosts( $params = [] ) {
if ( isset( $params['includeDrafts'] ) && $params['includeDrafts'] === true ) {
$whereClause = '*';
} else {
$whereClause = ['draft', '=', '0'];
}
$postData = self::$db->get( $this->tableName, $whereClause );
if ( !$postData->count() ) {
Debug::info( 'No Blog posts found.' );
return false;
}
if ( isset( $params['stripHtml'] ) && $params['stripHtml'] === true ) {
return $this->filter( $postData->results(), ['stripHtml' => true] );
}
return $this->filter( $postData->results() );
}
public function byYear( $year, $includeDraft = false ) {
if ( !Check::id( $year ) ) {
Debug::info( 'Invalid Year' );
return false;
}
$whereClause = [];
if ( $includeDraft !== true ) {
$whereClause = ['draft', '=', '0', 'AND'];
}
$firstDayUnix = date( 'U', strtotime( "first day of $year" ) );
$lastDayUnix = date( 'U', strtotime( "last day of $year" ) );
$whereClause = array_merge( $whereClause, ['created', '<=', $lastDayUnix, 'AND', 'created', '>=', $firstDayUnix] );
$postData = self::$db->get( $this->tableName, $whereClause );
if ( !$postData->count() ) {
Debug::info( 'No Blog posts found.' );
return false;
}
return $this->filter( $postData->results() );
}
public function byAuthor( $ID, $includeDraft = false ) {
if ( !Check::id( $ID ) ) {
Debug::info( 'Invalid Author' );
return false;
}
$whereClause = [];
if ( $includeDraft !== true ) {
$whereClause = ['draft', '=', '0', 'AND'];
}
$whereClause = array_merge( $whereClause, ['author' => $ID] );
$postData = self::$db->get( $this->tableName, $whereClause );
if ( !$postData->count() ) {
Debug::info( 'No Blog posts found.' );
return false;
}
return $this->filter( $postData->results() );
}
public function findBySlug( $slug, $includeDraft = false ) {
$whereClause = [];
if ( $includeDraft !== true ) {
$whereClause = ['draft', '=', '0', 'AND'];
}
$whereClause = array_merge( $whereClause, ['slug', '=', $slug] );
$postData = self::$db->get( $this->tableName, $whereClause );
if ( !$postData->count() ) {
Debug::info( 'No Blog posts found.' );
return false;
}
return $this->filter( $postData->first() );
}
public function byMonth( $month, $year = 0, $includeDraft = false ) {
if ( 0 === $year ) {
$year = date( 'Y' );
}
if ( !Check::id( $month ) ) {
Debug::info( 'Invalid Month' );
return false;
}
if ( !Check::id( $year ) ) {
Debug::info( 'Invalid Year' );
return false;
}
$whereClause = [];
if ( $includeDraft !== true ) {
$whereClause = ['draft', '=', '0', 'AND'];
}
$firstDayUnix = date( 'U', strtotime( "$month/01/$year" ) );
$month = date( 'F', $firstDayUnix );
$lastDayUnix = date( 'U', strtotime( "last day of $month $year" ) );
$whereClause = array_merge( $whereClause, ['created', '<=', $lastDayUnix, 'AND', 'created', '>=', $firstDayUnix] );
$postData = self::$db->get( $this->tableName, $whereClause );
if ( !$postData->count() ) {
Debug::info( 'No Blog posts found.' );
return false;
}
return $this->filter( $postData->results() );
}
}

49
app/plugins/blog/plugin.php Executable file
View File

@ -0,0 +1,49 @@
<?php
/**
* app/plugins/blog/plugin.php
*
* This houses all of the main plugin info and functionality.
*
* @package TP Blog
* @version 3.0
* @author Joey Kimsey <Joey@thetempusproject.com>
* @link https://TheTempusProject.com
* @license https://opensource.org/licenses/MIT [MIT LICENSE]
*/
namespace TheTempusProject\Plugins;
use TheTempusProject\Classes\Plugin;
class Blog extends Plugin {
public $pluginName = 'TP Blog';
public $pluginAuthor = 'JoeyK';
public $pluginWebsite = 'https://TheTempusProject.com';
public $modelVersion = '1.0';
public $pluginVersion = '3.0';
public $pluginDescription = 'A simple plugin to add a blog to your installation.';
public $admin_links = [
[
'text' => '<i class="fa fa-fw fa-font"></i> Blog',
'url' => '{ROOT_URL}admin/blog',
],
];
public $info_footer_links = [
[
'text' => 'Blog',
'url' => '{ROOT_URL}blog/index',
],
];
public $resourceMatrix = [
'posts' => [
[
'title' => 'Welcome',
'slug' => 'welcome',
'content' => '<p>This is just a simple message to say thank you for installing The Tempus Project. If you have any questions you can find everything through our website <a href="https://TheTempusProject.com">here</a>.</p>',
'author' => 1,
'created' => '{time}',
'edited' => '{time}',
'draft' => 0,
],
],
];
}

View File

@ -0,0 +1,39 @@
<?php
/**
* app/plugins/blog/templates/blog.inc.php
*
* This is the loader for the blog template.
*
* @package TP Blog
* @version 3.0
* @author Joey Kimsey <Joey@thetempusproject.com>
* @link https://TheTempusProject.com
* @license https://opensource.org/licenses/MIT [MIT LICENSE]
*/
namespace TheTempusProject\Templates;
use TheTempusProject\Models\Posts;
use TheTempusProject\Houdini\Classes\Components;
use TheTempusProject\Houdini\Classes\Navigation;
use TheTempusProject\Houdini\Classes\Views;
use TheTempusProject\Houdini\Classes\Template;
use TheTempusProject\Bedrock\Functions\Input;
class BlogLoader extends DefaultLoader {
/**
* This is the function used to generate any components that may be
* needed by this template.
*/
public function __construct() {
$posts = new Posts;
Components::set('SIDEBAR', Views::simpleView('blog.widgets.recent', $posts->recent(5)));
Components::set('SIDEBAR2', Views::simpleView('blog.widgets.archive', $posts->archive()));
Components::set('SIDEBARABOUT', Views::simpleView('blog.widgets.about'));
Components::set('SIDEBARSEARCH', Views::simpleView('blog.widgets.search'));
Components::set('BLOGFEATURES', '');
Navigation::setCrumbComponent( 'BLOG_BREADCRUMBS', Input::get( 'url' ) );
Components::set( 'BLOG_TEMPLATE_URL', Template::parse( '{ROOT_URL}app/plugins/comments/' ) );
$this->addCss( '<link rel="stylesheet" href="{BLOG_TEMPLATE_URL}css/comments.css">' );
parent::__construct();
}
}

View File

@ -0,0 +1,139 @@
<!DOCTYPE html>
<html lang="en">
<!--
* app/plugins/blog/templates/blog.tpl
*
* @package TP Blog
* @version 3.0
* @author Joey Kimsey <Joey@thetempusproject.com>
* @link https://TheTempusProject.com
* @license https://opensource.org/licenses/MIT [MIT LICENSE]
-->
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta property="og:url" content="{CURRENT_URL}">
<meta name='twitter:card' content='summary_large_image'>
<title>{TITLE}</title>
<meta itemprop="name" content="{TITLE}">
<meta name="twitter:title" content="{TITLE}">
<meta property="og:title" content="{TITLE}">
<meta name="description" content="{PAGE_DESCRIPTION}">
<meta itemprop="description" content="{PAGE_DESCRIPTION}">
<meta name="twitter:description" content="{PAGE_DESCRIPTION}">
<meta property="og:description" content="{PAGE_DESCRIPTION}">
<meta itemprop="image" content="{META_IMAGE}">
<meta name="twitter:image" content="{META_IMAGE}">
<meta property="og:image" content="{META_IMAGE}">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="author" content="The Tempus Project">
{ROBOT}
<link rel="icon" href="{ROOT_URL}images/favicon.ico">
<!-- Required CSS -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.7.1/css/all.min.css" crossorigin="anonymous" referrerpolicy="no-referrer">
<link rel="stylesheet" href="{BOOTSTRAP_CDN}css/bootstrap.min.css" crossorigin="anonymous">
<!-- RSS -->
<link rel="alternate" href="{ROOT_URL}blog/rss" title="{TITLE} Feed" type="application/rss+xml">
<!-- Custom styles for this template -->
{TEMPLATE_CSS_INCLUDES}
</head>
<body>
<!-- Navigation -->
<header class="p-3 text-bg-dark">
<div class="container">
<div class="d-flex align-items-center position-relative">
<!-- Navbar Toggler (Left) -->
<!-- Centered Logo (Now inside normal document flow) -->
<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="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 position-absolute start-50 translate-middle-x">
{topNavLeft}
</div>
<div class="d-flex justify-content-center flex-grow-1 d-md-none">
{topNavLeft}
</div>
<!-- Right-Side Content (Push to End) -->
<div class="d-flex flex-row justify-content-center align-items-center mt-3 mt-md-0 ms-md-auto">
{topNavRight}
</div>
</div> <!-- End Collapse -->
</div> <!-- End Navbar Expand -->
<button class="me-3 d-md-none btn btn-md btn-outline-light" type="button" data-bs-toggle="collapse" data-bs-target="#mainMenu" aria-controls="mainMenu" aria-expanded="false" aria-label="Toggle navigation">
<i class="fa fa-bars"></i>
</button>
</div>
</div>
</header>
<div class="d-flex flex-column min-vh-100">
<div class="flex-container flex-grow-1">
{ISSUES}
<div class="container pt-4">
<div class="row">
{ERROR}
{NOTICE}
{SUCCESS}
{INFO}
</div>
</div>
{/ISSUES}
<!-- Leading Content -->
<div class="container">
{BLOGFEATURES}
</div>
<div class="pt-4">
<div class="container">
<h3 class="pb-4 mb-4 fst-italic border-bottom context-main-border">
{SITENAME} Blog
</h3>
<div class="d-md-flex g-5">
<!-- Main Content -->
<div class="col-12 col-md-8">
{CONTENT}
</div>
<!-- Sidebar Content -->
<div class="col-12 col-md-4">
<div class="position-sticky" style="top: 2rem;">
<div class="ps-md-2 ps-lg-5">
{SIDEBARABOUT}
</div>
<div class="ps-md-2 ps-lg-5">
{SIDEBARSEARCH}
</div>
<div class="ps-md-2 ps-lg-5">
{SIDEBAR}
</div>
<div class="ps-md-2 ps-lg-5">
{SIDEBAR2}
</div>
</div>
</div>
</div>
</div>
</div>
</div>
{FOOT}
</div>
<!-- Bootstrap core JavaScript and jquery -->
<script language="JavaScript" crossorigin="anonymous" type="text/javascript" src="{JQUERY_CDN}jquery.min.js"></script>
<script language="JavaScript" crossorigin="anonymous" type="text/javascript" src="https://cdn.jsdelivr.net/npm/@popperjs/core@2.11.6/dist/umd/popper.min.js"></script>
<script language="JavaScript" crossorigin="anonymous" type="text/javascript" src="{BOOTSTRAP_CDN}js/bootstrap.min.js"></script>
<!-- Custom javascript for this template -->
{TEMPLATE_JS_INCLUDES}
</body>
</html>

View File

@ -0,0 +1,19 @@
<?php
/**
* app/templates/rss/rss.inc.php
*
* This is the loader for the rss template.
*
* @package TP Blog
* @version 3.0
* @author Joey Kimsey <Joey@thetempusproject.com>
* @link https://TheTempusProject.com
* @license https://opensource.org/licenses/MIT [MIT LICENSE]
*/
namespace TheTempusProject\Templates;
class RssLoader extends DefaultLoader {
public function __construct() {
parent::__construct();
}
}

View File

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
<channel>
<title>{TITLE}</title>
<link>{ROOT_URL}blog</link>
<description>{PAGE_DESCRIPTION}</description>
<language>en-us</language>
<copyright>Copyright (C) 2023 {SITENAME}</copyright>
{CONTENT}
</channel>
</rss>

View File

@ -0,0 +1,55 @@
<div class="context-main-bg context-main p-3">
<legend class="text-center">Add Blog Post</legend>
<hr>
{ADMIN_BREADCRUMBS}
<form method="post">
<fieldset>
<!-- Title -->
<div class="mb-3 row">
<label for="title" class="col-lg-3 col-form-label text-end">Title:</label>
<div class="col-lg-6">
<input type="text" class="form-control" name="title" id="title" required>
</div>
</div>
<!-- Slug -->
<div class="mb-3 row">
<label for="slug" class="col-lg-3 col-form-label text-end">URL Slug (for pretty linking):</label>
<div class="col-lg-6">
<input type="text" class="form-control" name="slug" id="slug" required>
</div>
</div>
<!-- form buttons -->
<div class="mb-3 row">
<div class="offset-3 col-lg-6">
<div class="btn-group w-100">
<button type="button" class="btn btn-sm btn-outline-primary" onclick="insertTag ('blogPost', 'c');">&#10004;</button>
<button type="button" class="btn btn-sm btn-outline-primary" onclick="insertTag ('blogPost', 'x');">&#10006;</button>
<button type="button" class="btn btn-sm btn-outline-primary" onclick="insertTag ('blogPost', '!');">&#10069;</button>
<button type="button" class="btn btn-sm btn-outline-primary" onclick="insertTag ('blogPost', '?');">&#10068;</button>
</div>
</div>
</div>
<!-- Message -->
<div class="mb-3 row">
<label for="blogPost" class="col-lg-3 col-form-label text-end">Post:</label>
<div class="col-lg-6">
<textarea class="form-control" name="blogPost" id="blogPost" rows="6" maxlength="2000" required></textarea>
<small class="form-text text-muted">Max: 2000 characters</small>
</div>
</div>
<!-- Hidden Token -->
<input type="hidden" name="token" value="{TOKEN}">
</fieldset>
<!-- Submit Button -->
<div class="text-center">
<button name="submit" value="publish" type="submit" class="mx-2 btn btn-lg btn-primary">Publish</button>
<button name="submit" value="saveDraft" type="submit" class="mx-2 btn btn-lg btn-primary">Save as Draft</button>
<button name="submit" value="preview" type="submit" class="mx-2 btn btn-lg btn-primary">Preview</button>
</div>
</form>
</div>

View File

@ -0,0 +1,30 @@
<legend>New Posts</legend>
<table class="table context-main">
<thead>
<tr>
<th style="width: 20%"></th>
<th style="width: 65%"></th>
<th style="width: 5%"></th>
<th style="width: 5%"></th>
<th style="width: 5%"></th>
</tr>
</thead>
<tbody>
{LOOP}
<tr>
<td>{title}</td>
<td>{contentSummary}</td>
<td><a href="{ROOT_URL}admin/blog/view/{ID}" class="btn btn-sm btn-primary"><i class="fa fa-fw fa-upload"></i></a></td>
<td><a href="{ROOT_URL}admin/blog/edit/{ID}" class="btn btn-sm btn-warning"><i class="fa fa-fw fa-pencil"></i></a></td>
<td width="30px"><a href="{ROOT_URL}admin/blog/delete/{ID}" class="btn btn-sm btn-danger"><i class="fa fa-fw fa-trash"></i></a></td>
</tr>
{/LOOP}
{ALT}
<tr>
<td align="center" colspan="5">
No results to show.
</td>
</tr>
{/ALT}
</tbody>
</table>

View File

@ -0,0 +1,55 @@
<div class="context-main-bg context-main p-3">
<legend class="text-center">Edit Blog Post</legend>
<hr>
{ADMIN_BREADCRUMBS}
<form method="post">
<fieldset>
<!-- Title -->
<div class="mb-3 row">
<label for="title" class="col-lg-3 col-form-label text-end">Title:</label>
<div class="col-lg-6">
<input type="text" class="form-control" name="title" id="title" value="{title}" required>
</div>
</div>
<!-- Slug -->
<div class="mb-3 row">
<label for="slug" class="col-lg-3 col-form-label text-end">URL Slug (for pretty linking):</label>
<div class="col-lg-6">
<input type="text" class="form-control" name="slug" id="slug" value="{slug}" required>
</div>
</div>
<!-- form buttons -->
<div class="mb-3 row">
<div class="offset-3 col-lg-6">
<div class="btn-group w-100">
<button type="button" class="btn btn-sm btn-outline-primary" onclick="insertTag ('blogPost', 'c');">&#10004;</button>
<button type="button" class="btn btn-sm btn-outline-primary" onclick="insertTag ('blogPost', 'x');">&#10006;</button>
<button type="button" class="btn btn-sm btn-outline-primary" onclick="insertTag ('blogPost', '!');">&#10069;</button>
<button type="button" class="btn btn-sm btn-outline-primary" onclick="insertTag ('blogPost', '?');">&#10068;</button>
</div>
</div>
</div>
<!-- Message -->
<div class="mb-3 row">
<label for="blogPost" class="col-lg-3 col-form-label text-end">Post:</label>
<div class="col-lg-6">
<textarea class="form-control" name="blogPost" id="blogPost" rows="6" maxlength="2000" required>{content}</textarea>
<small class="form-text text-muted">Max: 2000 characters</small>
</div>
</div>
<!-- Hidden Token -->
<input type="hidden" name="token" value="{TOKEN}">
</fieldset>
<!-- Submit Button -->
<div class="text-center">
<button name="submit" value="publish" type="submit" class="mx-2 btn btn-lg btn-primary">Publish</button>
<button name="submit" value="saveDraft" type="submit" class="mx-2 btn btn-lg btn-primary">Save as Draft</button>
<button name="submit" value="preview" type="submit" class="mx-2 btn btn-lg btn-primary">Preview</button>
</div>
</form>
</div>

View File

@ -0,0 +1,48 @@
<div class="context-main-bg context-main p-3">
<legend class="text-center">Blog Posts</legend>
<hr>
{ADMIN_BREADCRUMBS}
<form action="{ROOT_URL}admin/blog/delete" method="post">
<table class="table table-striped">
<thead>
<tr>
<th style="width: 30%">Title</th>
<th style="width: 20%">Author</th>
<th style="width: 10%">comments</th>
<th style="width: 10%">Created</th>
<th style="width: 10%">Updated</th>
<th style="width: 5%"></th>
<th style="width: 5%"></th>
<th style="width: 10%">
<input type="checkbox" onchange="checkAll(this)" name="check.b" value="B_[]">
</th>
</tr>
</thead>
<tbody>
{LOOP}
<tr>
<td><a href="{ROOT_URL}admin/blog/view/{ID}" class="text-decoration-none">{title}</a>{isDraft}</td>
<td>{authorName}</td>
<td>{commentCount}</td>
<td>{DTC}{created}{/DTC}</td>
<td>{DTC}{edited}{/DTC}</td>
<td><a href="{ROOT_URL}admin/blog/edit/{ID}" class="btn btn-sm btn-warning"><i class="fa fa-fw fa-pencil"></i></a></td>
<td><a href="{ROOT_URL}admin/blog/delete/{ID}" class="btn btn-sm btn-danger"><i class="fa fa-fw fa-trash"></i></a></td>
<td>
<input type="checkbox" value="{ID}" name="B_[]">
</td>
</tr>
{/LOOP}
{ALT}
<tr>
<td colspan="7">
No results to show.
</td>
</tr>
{/ALT}
</tbody>
</table>
<a href="{ROOT_URL}admin/blog/create" class="btn btn-sm btn-primary">Create</a>
<button name="submit" value="submit" type="submit" class="btn btn-sm btn-danger"><i class="fa fa-fw fa-trash"></i></button>
</form>
</div>

View File

@ -0,0 +1,40 @@
<div class="row">
<div class="col-sm-8 blog-main">
<div class="blog-post">
<h2 class="blog-post-title">{title}</h2>
<p class="blog-post-meta">{DTC}{created}{/DTC} by <a href="{ROOT_URL}admin/user/view/{author}">{authorName}</a></p>
{content}
</div><!-- /.blog-post -->
</div><!-- /.blog-main -->
</div><!-- /.row -->
<form method="post" enctype="multipart/form-data">
<legend>New Blog Post</legend>
<div class="form-group">
<label for="title" class="col-lg-3 control-label">Title</label>
<div class="col-lg-3">
<input type="text" class="form-check-input" name="title" id="title" value="{title}">
</div>
</div>
<div class="form-group">
<div class="col-lg-6 col-lg-offset-3 btn-group">
<button type="button" class="btn btn-sm btn-primary" onclick="insertTag ('blogPost', 'c');">&#10004;</button>
<button type="button" class="btn btn-sm btn-primary" onclick="insertTag ('blogPost', 'x');">&#10006;</button>
<button type="button" class="btn btn-sm btn-primary" onclick="insertTag ('blogPost', '!');">&#10069;</button>
<button type="button" class="btn btn-sm btn-primary" onclick="insertTag ('blogPost', '?');">&#10068;</button>
</div>
</div>
<div class="form-group">
<label for="blogPost" class="col-lg-3 control-label">Post</label>
<div class="col-lg-6">
<textarea class="form-control" name="blogPost" maxlength="2000" rows="10" cols="50" id="blogPost">{content}</textarea>
</div>
</div>
<div class="form-group">
<div class="col-lg-6 col-lg-offset-3">
<button name="submit" value="publish" type="submit" class="btn btn-lg btn-primary">Publish</button>
<button name="submit" value="saveasdraft" type="submit" class="btn btn-lg btn-primary">Save as Draft</button>
<button name="submit" value="preview" type="submit" class="btn btn-lg btn-primary">Preview</button>
</div>
</div>
<input type="hidden" name="token" value="{TOKEN}">
</form>

View File

@ -0,0 +1,14 @@
<div class="context-main-bg context-main p-3">
<legend class="text-center">Blog Post: {title}</legend>
<hr>
{ADMIN_BREADCRUMBS}
<p class="blog-post-meta">{DTC date}{created}{/DTC} by <a href="{ROOT_URL}admin/users/view/{author}">{authorName}</a></p>
<div class="well">{content}</div>
{ADMIN}
<hr>
<a href="{ROOT_URL}admin/blog/delete/{ID}" class="btn btn-md btn-danger"><i class="fa fa-fw fa-trash"></i></a>
<a href="{ROOT_URL}admin/blog/edit/{ID}" class="btn btn-md btn-warning"><i class="fa fa-fw fa-pencil"></i></a>
<a href="{ROOT_URL}admin/comments/blog/{ID}" class="btn btn-md btn-primary">View Comments</a>
<hr>
{/ADMIN}
</div>

View File

@ -0,0 +1,7 @@
<div class="p-4 p-md-5 mb-4 rounded text-bg-dark">
<div class="col-md-6 px-0">
<h1 class="display-4 fst-italic">Title of a longer featured blog post</h1>
<p class="lead my-3">Multiple lines of text that form the lede, informing new readers quickly and efficiently about whats most interesting in this posts contents.</p>
<p class="lead mb-0"><a href="#" class="text-white fw-bold">Continue reading...</a></p>
</div>
</div>

View File

@ -0,0 +1,15 @@
{LOOP}
<article class="blog-post context-main-bg p-2">
<h2 class="blog-post-title mb-1">{title}</h2>
<p class="blog-post-meta">{DTC date}{created}{/DTC} by <a href="{ROOT_URL}home/profile/{authorName}" class="text-decoration-none">{authorName}</a></p>
<div class="well">
{contentSummary}
</div>
</article>
<hr>
{/LOOP}
{ALT}
<div class="text-center">
<p class="h5">No Posts Found</p>
</div>
{/ALT}

View File

@ -0,0 +1,18 @@
<div class="row">
<div class="col-lg-12 col-sm-12 blog-main context-main-bg p-1 p-md-2 mb-2 rounded">
<div class="blog-post mb-5 pb-5">
<h2 class="blog-post-title">{title}</h2>
<hr>
<p class="blog-post-meta">{DTC date}{created}{/DTC} by <a href="{ROOT_URL}home/profile/{authorName}" class="text-decoration-none">{authorName}</a></p>
{content}
{ADMIN}
<hr>
<a href="{ROOT_URL}admin/blog/delete/{ID}" class="btn btn-md btn-danger"><i class="fa fa-fw fa-trash"></i></a>
<a href="{ROOT_URL}admin/blog/edit/{ID}" class="btn btn-md btn-warning"><i class="fa fa-fw fa-pencil"></i></a>
<hr>
{/ADMIN}
</div><!-- /.blog-post -->
{COMMENTS}
{NEWCOMMENT}
</div><!-- /.blog-main -->
</div><!-- /.row -->

10
app/plugins/blog/views/rss.html Executable file
View File

@ -0,0 +1,10 @@
{LOOP}
<item>
<title>{title}</title>
<description>{contentSummary}</description>
<link>{ROOT_URL}blog/post/{ID}</link>
<pubDate>{DTC}{created}{/DTC}</pubDate>
</item>
{/LOOP}
{ALT}
{/ALT}

View File

@ -0,0 +1,3 @@
<legend class="text-center my-2">Search Results</legend>
<hr>
{searchResults}

View File

@ -0,0 +1,31 @@
<div class="row mb-2">
<div class="col-md-6">
<div class="row g-0 border rounded overflow-hidden flex-md-row mb-4 shadow-sm h-md-250 position-relative">
<div class="col p-4 d-flex flex-column position-static">
<strong class="d-inline-block mb-2 text-primary">World</strong>
<h3 class="mb-0">Featured post</h3>
<div class="mb-1 text-muted">Nov 12</div>
<p class="card-text mb-auto">This is a wider card with supporting text below as a natural lead-in to additional content.</p>
<a href="#" class="stretched-link">Continue reading</a>
</div>
<div class="col-auto d-none d-lg-block">
<svg class="bd-placeholder-img" width="200" height="250" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="Placeholder: Thumbnail" preserveAspectRatio="xMidYMid slice" focusable="false"><title>Placeholder</title><rect width="100%" height="100%" fill="#55595c"><text x="50%" y="50%" fill="#eceeef" dy=".3em">Thumbnail</text></svg>
</div>
</div>
</div>
<div class="col-md-6">
<div class="row g-0 border rounded overflow-hidden flex-md-row mb-4 shadow-sm h-md-250 position-relative">
<div class="col p-4 d-flex flex-column position-static">
<strong class="d-inline-block mb-2 text-success">Design</strong>
<h3 class="mb-0">Post title</h3>
<div class="mb-1 text-muted">Nov 11</div>
<p class="mb-auto">This is a wider card with supporting text below as a natural lead-in to additional content.</p>
<a href="#" class="stretched-link">Continue reading</a>
</div>
<div class="col-auto d-none d-lg-block">
<svg class="bd-placeholder-img" width="200" height="250" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="Placeholder: Thumbnail" preserveAspectRatio="xMidYMid slice" focusable="false"><title>Placeholder</title><rect width="100%" height="100%" fill="#55595c"><text x="50%" y="50%" fill="#eceeef" dy=".3em">Thumbnail</text></svg>
</div>
</div>
</div>
</div>

View File

@ -0,0 +1,11 @@
<div class="pb-2 pb-lg-3">
<div class="card rounded context-main-bg">
<div class="card-body">
<h4 class="fst-italic">About</h4>
<p class="mb-0">
The blog is mostly here to serve ass a simple way to link to long-form content on the site.
There won't be any breaking news or tell-all stories here. Just good ole fashioned boring crap no one wants to read.
</p>
</div>
</div>
</div>

View File

@ -0,0 +1,17 @@
<div class="pb-2 pb-lg-3">
<div class="card rounded context-main-bg">
<div class="card-header">
<h3 class="card-title">Archives</h3>
</div>
<div class="card-body context-second-bg">
<ol class="list-unstyled">
{LOOP}
<li>({count}) <a href="{ROOT_URL}blog/month/{month}/{year}" class="text-decoration-none">{monthText} {year}</a></li>
{/LOOP}
{ALT}
<li>None To Show</li>
{/ALT}
</ol>
</div>
</div>
</div>

View File

@ -0,0 +1,20 @@
<div class="pb-2 pb-lg-3">
<div class="card rounded context-main-bg">
<div class="card-header">
<h3 class="card-title">Recent Posts</h3>
</div>
<div class="card-body context-second-bg">
<ol class="list-unstyled">
{LOOP}
<li><a href="{ROOT_URL}blog/post/{ID}" class="text-decoration-none">{title}</a></li>
{/LOOP}
{ALT}
<li>No Posts to show</li>
{/ALT}
</ol>
</div>
<div class="card-footer">
<a href="{ROOT_URL}blog" class="text-decoration-none">View All</a>
</div>
</div>
</div>

View File

@ -0,0 +1,18 @@
<div class="pb-2 pb-lg-3">
<div class="card rounded context-main-bg">
<div class="card-body">
<form method="post" action="/blog/search">
<fieldset>
<!-- Hidden Token -->
<input type="hidden" name="token" value="{TOKEN}">
<!-- Search -->
<div class="input-group">
<input type="text" class="form-control" aria-label="Search Terms" name="searchTerm" id="searchTerm">
<button type="submit" class="btn btn-secondary bg-primary" name="submit" value="submit">Search</button>
</div>
</fieldset>
</form>
</div>
</div>
</div>

View File

@ -0,0 +1,63 @@
<?php
/**
* app/plugins/bugreport/controllers/admin/bugreport.php
*
* This is the bug report admin controller.
*
* @package TP BugReports
* @version 3.0
* @author Joey Kimsey <Joey@thetempusproject.com>
* @link https://TheTempusProject.com
* @license https://opensource.org/licenses/MIT [MIT LICENSE]
*/
namespace TheTempusProject\Controllers\Admin;
use TheTempusProject\Classes\AdminController;
use TheTempusProject\Bedrock\Functions\Input;
use TheTempusProject\Houdini\Classes\Issues;
use TheTempusProject\Houdini\Classes\Views;
use TheTempusProject\Houdini\Classes\Navigation;
use TheTempusProject\Houdini\Classes\Components;
use TheTempusProject\Models\Bugreport as BugreportModel;
class Bugreport extends AdminController {
protected static $bugreport;
public function __construct() {
parent::__construct();
self::$bugreport = new BugreportModel;
self::$title = 'Admin - Bug Reports';
$view = Navigation::activePageSelect( 'nav.admin', '/admin/bugreport' );
Components::set( 'ADMINNAV', $view );
}
public function index( $data = null ) {
Views::view( 'bugreport.admin.list', self::$bugreport->listPaginated() );
}
public function view( $id = null ) {
$data = self::$bugreport->findById( $id );
if ( $data !== false ) {
return Views::view( 'bugreport.admin.view', $data );
}
Issues::add( 'error', 'Report not found.' );
$this->index();
}
public function delete( $data = null ) {
if ( Input::exists( 'submit' ) ) {
$data = Input::post( 'BR_' );
}
if ( self::$bugreport->delete( (array) $data ) ) {
Issues::add( 'success', 'Bug Report Deleted' );
} else {
Issues::add( 'error', 'There was an error with your request.' );
}
$this->index();
}
public function clear( $data = null ) {
self::$bugreport->empty();
$this->index();
}
}

View File

@ -0,0 +1,52 @@
<?php
/**
* app/plugins/bugreport/controllers/bugreport.php
*
* This is the bug reports controller.
*
* @package TP BugReports
* @version 3.0
* @author Joey Kimsey <Joey@thetempusproject.com>
* @link https://TheTempusProject.com
* @license https://opensource.org/licenses/MIT [MIT LICENSE]
*/
namespace TheTempusProject\Controllers;
use TheTempusProject\Hermes\Functions\Redirect;
use TheTempusProject\Bedrock\Functions\Check;
use TheTempusProject\Bedrock\Functions\Input;
use TheTempusProject\Bedrock\Functions\Session;
use TheTempusProject\Houdini\Classes\Issues;
use TheTempusProject\Houdini\Classes\Views;
use TheTempusProject\Classes\Controller;
use TheTempusProject\Classes\Forms;
use TheTempusProject\Models\Bugreport as BugreportModel;
use TheTempusProject\TheTempusProject as App;
class Bugreport extends Controller {
protected static $bugreport;
public function index() {
self::$bugreport = new BugreportModel;
self::$title = 'Bug Report - {SITENAME}';
self::$pageDescription = 'On this page you can submit a bug report for the site.';
if ( !App::$isLoggedIn ) {
return Issues::add( 'notice', 'You must be logged in to report bugs.' );
}
if ( !Input::exists() ) {
return Views::view( 'bugreport.create' );
}
if ( !Forms::check( 'bugreport' ) ) {
Issues::add( 'error', [ 'There was an error with your report.' => Check::userErrors() ] );
return Views::view( 'bugreport.create' );
}
$result = self::$bugreport->create( App::$activeUser->ID, Input::post( 'url' ), Input::post( 'ourl' ), Input::post( 'repeat' ), Input::post( 'entry' ) );
if ( false != $result ) {
Session::flash( 'success', 'Your Bug Report has been received. We may contact you for more information at the email address you provided.' );
Redirect::to( 'home/index' );
} else {
Issues::add( 'error', 'There was an unresolved error while submitting your report.' );
return Views::view( 'bugreport.create' );
}
}
}

51
app/plugins/bugreport/forms.php Executable file
View File

@ -0,0 +1,51 @@
<?php
/**
* app/plugins/bugreport/forms.php
*
* This houses all of the form checking functions for this plugin.
*
* @package TP BugReports
* @version 3.0
* @author Joey Kimsey <Joey@thetempusproject.com>
* @link https://TheTempusProject.com
* @license https://opensource.org/licenses/MIT [MIT LICENSE]
*/
namespace TheTempusProject\Plugins\Bugreport;
use TheTempusProject\Bedrock\Functions\Input;
use TheTempusProject\Classes\Forms;
class BugReportForms extends Forms {
/**
* Adds these functions to the form list.
*/
public function __construct() {
self::addHandler( 'bugreport', __CLASS__, 'bugreport' );
}
/**
* Validates the bug report form.
*
* @return {bool}
*/
public static function bugreport() {
if ( !self::url( Input::post( 'url' ) ) ) {
self::addUserError( 'Invalid url.' );
return false;
}
if ( !self::url( Input::post( 'ourl' ) ) ) {
self::addUserError( 'Invalid original url.' );
return false;
}
if ( !self::tf( Input::post( 'repeat' ) ) ) {
self::addUserError( 'Invalid repeat value.' );
return false;
}
if ( !self::token() ) {
return false;
}
return true;
}
}
new BugReportForms;

View File

@ -0,0 +1,100 @@
<?php
/**
* app/plugins/bugreport/models/bugreport.php
*
* This class is used for the manipulation of the bugreports database table.
*
* @package TP BugReports
* @version 3.0
* @author Joey Kimsey <Joey@thetempusproject.com>
* @link https://TheTempusProject.com
* @license https://opensource.org/licenses/MIT [MIT LICENSE]
*/
namespace TheTempusProject\Models;
use TheTempusProject\Bedrock\Functions\Check;
use TheTempusProject\Canary\Bin\Canary as Debug;
use TheTempusProject\Canary\Classes\CustomException;
use TheTempusProject\Classes\DatabaseModel;
use TheTempusProject\Plugins\Bugreport as Plugin;
use TheTempusProject\TheTempusProject as App;
class Bugreport extends DatabaseModel {
public $tableName = 'bugreports';
public $databaseMatrix = [
[ 'userID', 'int', '11' ],
[ 'time', 'int', '10' ],
[ 'repeat', 'varchar', '5' ],
[ 'ourl', 'varchar', '256' ],
[ 'url', 'varchar', '256' ],
[ 'ip', 'varchar', '15' ],
[ 'description', 'text', '' ],
];
public $plugin;
/**
* The model constructor.
*/
public function __construct() {
parent::__construct();
$this->plugin = new Plugin;
}
/**
* This function parses the bug reports description and
* separates it into separate keys in the array.
*
* @param array $data - The data being parsed.
*
* @return array
*/
public function filter( $data, $params = [] ) {
foreach ( $data as $instance ) {
if ( !is_object( $instance ) ) {
$instance = $data;
$end = true;
}
$instance->submittedBy = self::$user->getUsername( $instance->userID );
$instance->repeatText = ( $instance->repeat ? 'yes' : 'no' );
$out[] = $instance;
if ( !empty( $end ) ) {
$out = $out[0];
break;
}
}
return $out;
}
/**
* Logs a Bug Report form.
*
* @param int $ID the user ID submitting the form
* @param string $url the url
* @param string $o_url the original url
* @param int $repeat is repeatable?
* @param string $description_ description of the event.
*
* @return null
*/
public function create( $ID, $url, $oUrl, $repeat, $description ) {
if ( !$this->plugin->checkEnabled() ) {
Debug::info( 'Bug Reporting is disabled in the config.' );
return false;
}
$fields = [
'userID' => App::$activeUser->ID,
'time' => time(),
'repeat' => $repeat,
'ourl' => $oUrl,
'url' => $url,
'ip' => $_SERVER['REMOTE_ADDR'],
'description' => $description,
];
if ( !self::$db->insert( $this->tableName, $fields ) ) {
new CustomException( 'bugreportsCreate' );
return false;
}
return self::$db->lastId();
}
}

View File

@ -0,0 +1,65 @@
<?php
/**
* app/plugins/bugreport/plugin.php
*
* This houses all of the main plugin info and functionality.
*
* @package TP BugReports
* @version 3.0
* @author Joey Kimsey <Joey@thetempusproject.com>
* @link https://TheTempusProject.com
* @license https://opensource.org/licenses/MIT [MIT LICENSE]
*/
namespace TheTempusProject\Plugins;
use ReflectionClass;
use TheTempusProject\Classes\Installer;
use TheTempusProject\Houdini\Classes\Navigation;
use TheTempusProject\Classes\Plugin;
use TheTempusProject\TheTempusProject as App;
class Bugreport extends Plugin {
public $pluginName = 'TP BugReports';
public $pluginAuthor = 'JoeyK';
public $pluginWebsite = 'https://TheTempusProject.com';
public $modelVersion = '1.0';
public $pluginVersion = '3.0';
public $pluginDescription = '';
public $configName = 'bugreports';
public $configMatrix = [
'enabled' => [
'type' => 'radio',
'pretty' => 'Enable Bug reporting.',
'default' => true,
],
'sendEmail' => [
'type' => 'radio',
'pretty' => 'Email the user after submitting.',
'default' => true,
],
'emailTemplate' => [
'type' => 'text',
'pretty' => 'Email Template',
'default' => 'BugReportEmail',
],
];
public $permissionMatrix = [
'canSendBugReports' => [
'pretty' => 'Can Submit Bug Reports',
'default' => false,
],
];
public $contact_footer_links = [
[
'text' => 'Report a Bug',
'url' => '{ROOT_URL}bugreport',
'filter' => 'loggedin',
],
];
public $admin_links = [
[
'text' => '<i class="fa fa-fw fa-bug"></i> Bug Reports',
'url' => '{ROOT_URL}admin/bugreport',
],
];
}

View File

@ -0,0 +1,45 @@
<div class="context-main-bg context-main p-3">
<legend class="text-center">Bug Reports</legend>
<hr>
{ADMIN_BREADCRUMBS}
<form action="{ROOT_URL}admin/bugreport/delete" method="post">
<table class="table table-striped">
<thead>
<tr>
<th style="width: 5%">ID</th>
<th style="width: 20%">Time</th>
<th style="width: 60%">Description</th>
<th style="width: 5%"></th>
<th style="width: 5%"></th>
<th style="width: 5%">
<input type="checkbox" onchange="checkAll(this)" name="check.br" value="BR_[]">
</th>
</tr>
</thead>
<tbody>
{LOOP}
<tr>
<td align="center">{ID}</td>
<td align="center">{DTC}{time}{/DTC}</td>
<td>{description}</td>
<td><a href="{ROOT_URL}admin/bugreport/view/{ID}" class="btn btn-sm btn-primary"><i class="fa fa-fw fa-upload"></i></a></td>
<td><a href="{ROOT_URL}admin/bugreport/delete/{ID}" class="btn btn-sm btn-danger"><i class="fa fa-fw fa-trash"></i></a></td>
<td>
<input type="checkbox" value="{ID}" name="BR_[]">
</td>
</tr>
{/LOOP}
{ALT}
<tr>
<td align="center" colspan="6">
No results to show.
</td>
</tr>
{/ALT}
</tbody>
</table>
<button name="submit" value="submit" type="submit" class="btn btn-sm btn-danger"><i class="fa fa-fw fa-trash"></i></button>
</form>
<br>
<a href="{ROOT_URL}admin/bugreport/clear">clear all</a>
</div>

View File

@ -0,0 +1,69 @@
<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">Bug Report</h3>
</div>
<!-- Card Body -->
<div class="card-body">
<div class="row align-items-center">
<!-- Log Details -->
<table class="table table-borderless">
<tbody>
<tr>
<th scope="row">ID:</th>
<td>{ID}</td>
</tr>
<tr>
<th scope="row">Time submitted</th>
<td>{DTC}{time}{/DTC}</td>
</tr>
<tr>
<th scope="row">URL:</th>
<td><a href="{URL}">{URL}</a></td>
</tr>
<tr>
<th scope="row">Original URL</th>
<td><a href="{OURL}">{OURL}</a></td>
</tr>
<tr>
<th scope="row">Multiple occurrences?</th>
<td>{repeatText}</td>
</tr>
<tr>
<th scope="row">IP:</th>
<td>{ip}</td>
</tr>
<tr>
<th scope="row">User:</th>
<td><a href="{ROOT_URL}admin/users/view/{userID}">{submittedBy}</a></td>
</tr>
<tr>
<th scope="row" colspan="2">Description:</th>
</tr>
<tr>
<td colspan="2">{description}</td>
</tr>
</tbody>
</table>
</div>
</div>
<!-- Admin Controls -->
<div class="card-footer text-center">
{ADMIN}
<form action="{ROOT_URL}admin/bugreport/delete" method="post">
<input type="hidden" name="BR_" value="{ID}">
<input type="hidden" name="token" value="{TOKEN}">
<button name="submit" value="submit" type="submit" class="btn btn-sm btn-danger"><i class="fa fa-fw fa-trash"></i></button>
</form>
{/ADMIN}
</div>
</div>
</div>
</div>
</div>

View File

@ -0,0 +1,61 @@
<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">Report a Bug</h2>
<hr>
<p class="text-center text-sm-start">
Thank you for visiting our bug reporting page. We value our users' input highly and in an effort to better serve your needs, please fill out the form below to help us address this issue.
</p>
<p class="text-center text-sm-start">
We read each and every bug report submitted, and by submitting this form you allow us to send you a follow-up email.
</p>
<form method="post">
<!-- Page URL -->
<div class="mb-3">
<label for="url" class="form-label">Page you were trying to reach:</label>
<input type="url" name="url" id="url" class="form-control" aria-describedby="urlHelp" required>
<small id="urlHelp" class="form-text text-muted">
This is the URL of the page you actually received the error.
</small>
</div>
<!-- Referrer URL -->
<div class="mb-3">
<label for="ourl" class="form-label">Page you were on:</label>
<input type="url" name="ourl" id="ourl" class="form-control" aria-describedby="ourlHelp">
<small id="ourlHelp" class="form-text text-muted">
This is the URL of the page you were on before you received the error.
</small>
</div>
<!-- Repeat Issue -->
<div class="mb-3">
<label class="form-label">*Has this happened more than once?</label>
<div class="form-check">
<input class="form-check-input" type="radio" name="repeat" id="repeatNo" value="false" checked>
<label class="form-check-label" for="repeatNo">No</label>
</div>
<div class="form-check">
<input class="form-check-input" type="radio" name="repeat" id="repeatYes" value="true">
<label class="form-check-label" for="repeatYes">Yes</label>
</div>
</div>
<!-- Description -->
<div class="mb-3">
<label for="entry" class="form-label">Describe the error you received: </label>
<textarea class="form-control" name="entry" id="entry" rows="6" maxlength="2000" aria-describedby="descHelp" required></textarea>
<small id="descHelp" class="form-text text-muted">
(max: 2000 characters)
</small>
</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">Submit</button>
</div>
</form>
</div>
</div>

View File

@ -0,0 +1,83 @@
<?php
/**
* app/plugins/comments/controllers/admin/comments.php
*
* This is the comments admin controller.
*
* @package TP Comments
* @version 3.0
* @author Joey Kimsey <Joey@thetempusproject.com>
* @link https://TheTempusProject.com
* @license https://opensource.org/licenses/MIT [MIT LICENSE]
*/
namespace TheTempusProject\Controllers\Admin;
use TheTempusProject\Houdini\Classes\Navigation;
use TheTempusProject\Houdini\Classes\Issues;
use TheTempusProject\Houdini\Classes\Views;
use TheTempusProject\Houdini\Classes\Components;
use TheTempusProject\Bedrock\Functions\Input;
use TheTempusProject\Bedrock\Functions\Check;
use TheTempusProject\Classes\Forms;
use TheTempusProject\Classes\AdminController;
use TheTempusProject\Models\Comments as CommentsModel;
class Comments extends AdminController {
protected static $comments;
public function __construct() {
parent::__construct();
self::$title = 'Admin - Comments';
self::$comments = new CommentsModel;
$view = Navigation::activePageSelect( 'nav.admin', '/admin/comments' );
Components::set( 'ADMINNAV', $view );
}
public function edit( $data = null ) {
if ( !Input::exists( 'submit' ) ) {
return Views::view( 'comments.admin.edit', self::$comments->findById( $data ) );
}
if ( !Forms::check( 'editComment' ) ) {
Issues::add( 'error', [ 'There was an error with your request.' => Check::userErrors() ] );
return Views::view( 'comments.admin.edit', self::$comments->findById( $data ) );
}
if ( self::$comments->update( $data, Input::post( 'comment' ) ) ) {
Issues::add( 'success', 'Comment updated' );
} else {
return Views::view( 'comments.admin.edit', self::$comments->findById( $data ) );
}
$this->index();
}
public function viewComments( $contentID = null ) {
if ( empty( $contentID ) ) {
Issues::add( 'error', 'Content ID not found.' );
return $this->index();
}
$contentData = self::$comments->findById( $data );
if ( empty( $contentID ) ) {
return Views::view( 'comments.list', $commentData );
}
Issues::add( 'error', 'Comment not found.' );
$this->index();
}
public function delete( $data = null ) {
if ( $data == null ) {
if ( !Input::exists( 'C_' ) ) {
return $this->index();
}
$data = Input::post( 'C_' );
}
if ( !self::$comments->delete( $data ) ) {
Issues::add( 'error', 'There was an error with your request.' );
} else {
Issues::add( 'success', 'Comment has been deleted' );
}
$this->index();
}
public function index() {
Views::view( 'comments.admin.list', self::$comments->recent() );
}
}

View File

@ -0,0 +1,36 @@
<?php
/**
* app/plugins/comments/controllers/moderator.php
*
* This is the Moderator controller.
*
* @version 3.0
* @author Joey Kimsey <Joey@thetempusproject.com>
* @link https://TheTempusProject.com
* @license https://opensource.org/licenses/MIT [MIT LICENSE]
*/
namespace TheTempusProject\Controllers;
use TheTempusProject\Houdini\Classes\Template;
use TheTempusProject\Houdini\Classes\Views;
use TheTempusProject\Houdini\Classes\Issues;
use TheTempusProject\Classes\Controller;
use TheTempusProject\TheTempusProject as App;
use TheTempusProject\Hermes\Functions\Redirect;
use TheTempusProject\Bedrock\Functions\Session;
class Moderator extends Controller {
public function __construct() {
parent::__construct();
Template::noIndex();
if ( !App::$isMod ) {
Session::flash( 'error', 'You do not have permission to view this page.' );
return Redirect::home();
}
}
public function index() {
self::$title = 'Moderator\'s Area';
Views::view( 'comments.moderator' );
}
}

View File

@ -0,0 +1,6 @@
/**
* Comments
*/
.comments {
margin-top: 120px;
}

69
app/plugins/comments/forms.php Executable file
View File

@ -0,0 +1,69 @@
<?php
/**
* app/plugins/comments/forms.php
*
* This houses all of the form checking functions for this plugin.
*
* @package TP Comments
* @version 3.0
* @author Joey Kimsey <Joey@thetempusproject.com>
* @link https://TheTempusProject.com
* @license https://opensource.org/licenses/MIT [MIT LICENSE]
*/
namespace TheTempusProject\Plugins\Comments;
use TheTempusProject\Bedrock\Functions\Input;
use TheTempusProject\Bedrock\Functions\Check;
use TheTempusProject\Classes\Forms;
class CommentsForms extends Forms {
/**
* Adds these functions to the form list.
*/
public function __construct() {
self::addHandler( 'newComment', __CLASS__, 'newComment' );
self::addHandler( 'editComment', __CLASS__, 'editComment' );
}
/**
* Validates the new comment form.
*
* @return {bool}
*/
public static function newComment() {
if ( !Input::exists( 'comment' ) ) {
Check::addUserError( 'You cannot post a blank comment.' );
return false;
}
if ( !Input::exists( 'contentId' ) ) {
Check::addUserError( 'Content ID was missing.' );
return false;
}
// these are disabled because i need to figure out a solution for pages where images are wrong
// a missing image loads a new token and messes this up
// if ( !Check::token() ) {
// return false;
// }
return true;
}
/**
* Validates the edit comment form.
*
* @return {bool}
*/
public static function editComment() {
if ( !Input::exists( 'comment' ) ) {
Check::addUserError( 'You cannot post a blank comment.' );
return false;
}
// these are disabled because i need to figure out a solution for pages where images are wrong
// a missing image loads a new token and messes this up
// if ( !Check::token() ) {
// return false;
// }
return true;
}
}
new CommentsForms;

View File

@ -0,0 +1,184 @@
<?php
/**
* app/plugins/comments/models/comment.php
*
* This class is used for the creation, retrieval, and manipulation
* of the comments table.
*
* @package TP Comments
* @version 3.0
* @author Joey Kimsey <Joey@thetempusproject.com>
* @link https://TheTempusProject.com
* @license https://opensource.org/licenses/MIT [MIT LICENSE]
*/
namespace TheTempusProject\Models;
use TheTempusProject\Bedrock\Functions\Check;
use TheTempusProject\Canary\Bin\Canary as Debug;
use TheTempusProject\Houdini\Classes\Views;
use TheTempusProject\Classes\DatabaseModel;
use TheTempusProject\TheTempusProject as App;
use TheTempusProject\Canary\Classes\CustomException;
use TheTempusProject\Houdini\Classes\Filters;
class Comments extends DatabaseModel {
public $tableName = 'comments';
public $databaseMatrix = [
[ 'author', 'int', '11' ],
[ 'contentID', 'int', '11' ],
[ 'created', 'int', '10' ],
[ 'edited', 'int', '10' ],
[ 'approved', 'int', '1' ],
[ 'contentType', 'varchar', '32' ],
[ 'content', 'text', '' ],
];
public function count( $contentType, $contentID ) {
if ( !Check::id( $contentID ) ) {
Debug::info( 'Comments: illegal ID.' );
return false;
}
if ( !Check::dataTitle( $contentType ) ) {
Debug::info( 'Comments: illegal Type.' );
return false;
}
$where = ['contentType', '=', $contentType, 'AND', 'contentID', '=', $contentID];
$data = self::$db->get( $this->tableName, $where );
if ( !$data->count() ) {
Debug::info( 'No comments found.' );
return 0;
}
return $data->count();
}
public function display( $displayCount, $contentType, $contentID ) {
if ( !Check::id( $contentID ) ) {
Debug::info( 'Comments: illegal ID.' );
return false;
}
if ( !Check::dataTitle( $contentType ) ) {
Debug::info( 'Comments: illegal Type.' );
return false;
}
$where = ['contentType', '=', $contentType, 'AND', 'contentID', '=', $contentID];
$commentData = self::$db->get( $this->tableName, $where, 'created', 'DESC', [0, $displayCount] );
if ( !$commentData->count() ) {
Debug::info( 'No comments found.' );
return false;
}
return $this->filter( $commentData->results() );
}
public function update( $id, $comment ) {
if ( empty( self::$log ) ) {
self::$log = new Log;
}
if ( !Check::id( $id ) ) {
Debug::info( 'Comments: illegal ID.' );
return false;
}
$fields = [
'edited' => time(),
'content' => $comment,
'approved' => 1,
];
if ( !self::$db->update( $this->tableName, $id, $fields ) ) {
new CustomException( 'commentUpdate' );
Debug::error( "Post: $id not updated: $fields" );
return false;
}
self::$log->admin( "Updated Comment: $id" );
return true;
}
public function create( $contentType, $contentID, $comment ) {
if ( !Check::id( $contentID ) ) {
Debug::info( 'Comments: illegal ID.' );
return false;
}
if ( !Check::dataTitle( $contentType ) ) {
Debug::info( 'Comments: illegal Type.' );
return false;
}
$fields = [
'author' => App::$activeUser->ID,
'edited' => time(),
'created' => time(),
'content' => $comment,
'contentType' => $contentType,
'contentID' => $contentID,
'approved' => 0,
];
if ( !self::$db->insert( $this->tableName, $fields ) ) {
new CustomException( 'commentCreate' );
Debug::error( "Comments: $data not created: $fields" );
return false;
}
return self::$db->lastId();
}
public function filter( $data, $params = [] ) {
foreach ( $data as $instance ) {
if ( !is_object( $instance ) ) {
$instance = $data;
$end = true;
}
if ( App::$isAdmin || ( App::$isLoggedIn && $instance->author == App::$activeUser->ID ) ) {
$instance->commentControl = Views::simpleView( 'comments.control', ['ID' => $instance->ID] );
} else {
$instance->commentControl = '';
}
$data = self::$db->get( $instance->contentType, ['ID', '=', $instance->contentID] )->results();
if ( empty( $data ) ) {
$title = 'Unknown';
} elseif ( empty( $data[0]->title ) ) {
$title = 'Unknown';
} else {
$title = $data[0]->title;
}
$authorName = self::$user->getUsername( $instance->author );
$authorAvatar = self::$user->getAvatar( $instance->author );
$instance->avatar = $authorAvatar;
$instance->authorName = $authorName;
$instance->contentTitle = $title;
$instance->content = Filters::applyOne( 'mentions.0', $instance->content, true );
$instance->content = Filters::applyOne( 'hashtags.0', $instance->content, true );
$out[] = $instance;
if ( !empty( $end ) ) {
$out = $out[0];
break;
}
}
return $out;
}
public function recent( $contentType = 'all', $limit = null ) {
if ( $contentType === 'all' ) {
$where = ['ID', '>', '0'];
} else {
$where = ['contentType', '=', $contentType];
}
if ( empty( $limit ) ) {
$commentData = self::$db->get( $this->tableName, $where, 'created', 'DESC' );
} else {
$commentData = self::$db->get( $this->tableName, $where, 'created', 'DESC', [0, $limit] );
}
if ( !$commentData->count() ) {
Debug::info( 'No comments found.' );
return false;
}
return $this->filter( $commentData->results() );
}
}

154
app/plugins/comments/plugin.php Executable file
View File

@ -0,0 +1,154 @@
<?php
/**
* app/plugins/comments/plugin.php
*
* This houses all of the main plugin info and functionality.
*
* @package TP Comments
* @version 3.0
* @author Joey Kimsey <Joey@thetempusproject.com>
* @link https://TheTempusProject.com
* @license https://opensource.org/licenses/MIT [MIT LICENSE]
*/
namespace TheTempusProject\Plugins;
use ReflectionClass;
use TheTempusProject\Bedrock\Functions\Check;
use TheTempusProject\Classes\Installer;
use TheTempusProject\Houdini\Classes\Navigation;
use TheTempusProject\Classes\Plugin;
use TheTempusProject\TheTempusProject as App;
use TheTempusProject\Bedrock\Functions\Input;
use TheTempusProject\Houdini\Classes\Components;
use TheTempusProject\Houdini\Classes\Issues;
use TheTempusProject\Houdini\Classes\Views;
use TheTempusProject\Classes\Forms;
use TheTempusProject\Hermes\Functions\Redirect;
use TheTempusProject\Bedrock\Functions\Session;
use TheTempusProject\Models\Comments as CommentsModel;
class Comments extends Plugin {
protected static $comments;
public $pluginName = 'TP Comments';
public $pluginAuthor = 'JoeyK';
public $pluginWebsite = 'https://TheTempusProject.com';
public $modelVersion = '1.0';
public $pluginVersion = '3.0';
public $pluginDescription = 'A simple plugin to add user comments for other plugins.';
public $admin_links = [
[
'text' => '<i class="fa fa-fw fa-comment"></i> Comments',
'url' => '{ROOT_URL}admin/comments',
],
];
public $main_links = [
[
'text' => 'Moderator',
'url' => '{ROOT_URL}moderator/index',
'filter' => 'mod',
]
];
public $permissionMatrix = [
'modAccess' => [
'pretty' => 'Access Moderator Areas',
'default' => false,
],
];
public $resourceMatrix = [
'groups' => [
[
'name' => 'Moderator',
'permissions' => '{"adminAccess":false}',
]
],
];
public function __construct( $load = false ) {
if ( !empty(App::$activePerms) ) {
App::$isMod = !empty(App::$activePerms['modAccess']);
} else {
App::$isMod = false;
}
$this->filters[] = [
'name' => 'mod',
'find' => '#{MOD}(.*?){/MOD}#is',
'replace' => ( App::$isMod ? '$1' : '' ),
'enabled' => true,
];
self::$comments = new CommentsModel;
parent::__construct( $load );
}
public function formPost( $type, $content, $redirect ) {
if ( ! $this->checkEnabled() ) {
Debug::info( 'Comments Plugin is disabled in the control panel.' );
Issues::add( 'error', 'Comments are disabled.' );
return false;
}
if ( !App::$isLoggedIn ) {
Session::flash( 'notice', 'You must be logged in to post comments.' );
return Redirect::to( $redirect . $content->ID );
}
if ( !Forms::check( 'newComment' ) ) {
Session::flash( 'error', [ 'There was a problem with your comment form.' => Check::userErrors() ] );
return Redirect::to( $redirect . $content->ID );
}
if ( !self::$comments->create( $type, $content->ID, Input::post( 'comment' ) ) ) {
Session::flash( 'error', [ 'There was a problem posting your comment.' => Check::userErrors() ] );
} else {
Session::flash( 'success', 'Comment posted' );
}
return Redirect::to( $redirect . $content->ID );
}
public function formEdit( $type, $content, $redirect ) {
if ( ! $this->checkEnabled() ) {
Debug::info( 'Comments Plugin is disabled in the control panel.' );
Issues::add( 'error', 'Comments are disabled.' );
return false;
}
if ( !App::$isLoggedIn ) {
Session::flash( 'notice', 'You must be logged in to do that.' );
return Redirect::to( $type );
}
if ( !App::$isAdmin && $content->author != App::$activeUser->ID ) {
Session::flash( 'error', 'You do not have permission to edit this comment' );
return Redirect::to( $type );
}
if ( !Input::exists( 'submit' ) ) {
return Views::view( 'comments.admin.edit', $content );
}
if ( !Forms::check( 'editComment' ) ) {
Issues::add( 'error', [ 'There was a problem editing your comment.' => Check::userErrors() ] );
return Views::view( 'comments.admin.edit', $content );
}
if ( !self::$comments->update( $content->ID, Input::post( 'comment' ) ) ) {
Issues::add( 'error', [ 'There was a problem editing your comment.' => Check::userErrors() ] );
return Views::view( 'comments.admin.edit', $content );
}
Session::flash( 'success', 'Comment updated' );
return Redirect::to( $redirect . $content->contentID );
}
public function formDelete( $type, $content, $redirect ) {
if ( ! $this->checkEnabled() ) {
Debug::info( 'Comments Plugin is disabled in the control panel.' );
Issues::add( 'error', 'Comments are disabled.' );
return false;
}
if ( !App::$isLoggedIn ) {
Session::flash( 'notice', 'You must be logged in to do that.' );
return Redirect::to( $type );
}
if ( !App::$isAdmin && $content->author != App::$activeUser->ID ) {
Session::flash( 'error', 'You do not have permission to edit this comment' );
return Redirect::to( $type );
}
if ( !self::$comments->delete( (array) $content->ID ) ) {
Session::flash( 'error', 'There was an error with your request.' );
} else {
Session::flash( 'success', 'Comment has been deleted' );
}
return Redirect::to( $redirect . $content->contentID );
}
}

View File

@ -0,0 +1,28 @@
<legend>New Comments</legend>
<table class="table context-main">
<thead>
<tr>
<th style="width: 20%"></th>
<th style="width: 70%"></th>
<th style="width: 5%"></th>
<th style="width: 5%"></th>
</tr>
</thead>
<tbody>
{LOOP}
<tr>
<td>{authorName}</td>
<td>{content}</td>
<td><a href="{ROOT_URL}admin/comments/edit/{ID}" class="btn btn-sm btn-warning"><i class="fa fa-fw fa-pencil"></i></a></td>
<td><a href="{ROOT_URL}admin/comments/delete/{ID}" class="btn btn-sm btn-danger"><i class="fa fa-fw fa-trash"></i></a></td>
</tr>
{/LOOP}
{ALT}
<tr>
<td align="center" colspan="4">
No results to show.
</td>
</tr>
{/ALT}
</tbody>
</table>

View File

@ -0,0 +1,25 @@
<div class="mb-4 mt-4">
<div class="offset-md-1 col-10 p-3 context-main-bg">
<legend class="text-center">Edit Comment</legend>
<hr>
{ADMIN_BREADCRUMBS}
<form method="post" class="container py-4">
<fieldset>
<div class="mb-3 row">
<label for="comment" class="col-lg-5 col-form-label text-end">Comment:</label>
<div class="col-lg-3">
<textarea class="form-control" name="comment" maxlength="2000" rows="5" cols="50" id="comment">{content}</textarea>
</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>
</div>

View File

@ -0,0 +1,45 @@
<div class="context-main-bg context-main p-3">
<legend class="text-center">Recent Comments</legend>
<hr>
{ADMIN_BREADCRUMBS}
<form action="{ROOT_URL}admin/comments/delete" method="post">
<table class="table table-striped">
<thead>
<tr>
<th style="width: 20%">Author</th>
<th style="width: 20%">Subject</th>
<th style="width: 35%">Comment</th>
<th style="width: 10%">Time</th>
<th style="width: 5%"></th>
<th style="width: 5%"></th>
<th style="width: 5%">
<input type="checkbox" onchange="checkAll(this)" name="check.c" value="C_[]">
</th>
</tr>
</thead>
<tbody>
{LOOP}
<tr>
<td><a href="{ROOT_URL}admin/users/view/{author}">{authorName}</a></td>
<td><a href="{ROOT_URL}admin/blog/view/{contentID}">{contentTitle}</a></td>
<td>{content}</td>
<td>{DTC}{created}{/DTC}</td>
<td><a href="{ROOT_URL}admin/comments/edit/{ID}" class="btn btn-sm btn-warning"><i class="fa fa-fw fa-pencil"></i></a></td>
<td><a href="{ROOT_URL}admin/comments/delete/{ID}" class="btn btn-sm btn-danger"><i class="fa fa-fw fa-trash"></i></a></td>
<td>
<input type="checkbox" value="{ID}" name="C_[]">
</td>
</tr>
{/LOOP}
{ALT}
<tr>
<td align="center" colspan="7">
No results to show.
</td>
</tr>
{/ALT}
</tbody>
</table>
<button name="submit" value="submit" type="submit" class="btn btn-sm btn-danger"><i class="fa fa-fw fa-trash"></i></button>
</form>
</div>

View File

@ -0,0 +1,8 @@
<div class="action">
<a href="{ROOT_URL}{COMMENT_TYPE}/comments/edit/{ID}" class="btn btn-warning btn-sm">
<span class="fa fa-fw fa-pencil"></span>
</a>
<a href="{ROOT_URL}{COMMENT_TYPE}/comments/delete/{ID}" class="btn btn-danger btn-sm">
<span class="fa fa-fw fa-trash"></span>
</a>
</div>

View File

@ -0,0 +1,14 @@
<form method="post" class="text-center mx-auto mt-4" style="max-width: 600px;">
<div class="mb-3">
<textarea
class="form-control"
name="comment"
maxlength="2000"
rows="4"
id="comment"
placeholder="Write your comment here..."></textarea>
</div>
<button name="submit" value="submit" type="submit" class="btn btn-lg btn-primary mb-3">Comment</button>
<input type="hidden" name="token" value="{TOKEN}">
<input type="hidden" name="contentId" value="{CONTENT_ID}">
</form>

View File

@ -0,0 +1,39 @@
<div class="card">
<div class="card-header d-flex align-items-center justify-content-between context-main-bg context-main">
<h3 class="card-title mb-0">
<i class="fa fa-fw fa-comment"></i> Comments
</h3>
<span class="badge bg-primary">{count}</span>
</div>
<div class="card-body context-second-bg">
<ul class="list-group list-group-flush">
{LOOP}
<li class="list-group-item context-main-bg context-main mb-2">
<div class="d-flex align-items-start">
<div class="me-3">
<img src="{ROOT_URL}{avatar}" class="rounded-circle" alt="User Avatar" style="width: 50px; height: 50px;">
</div>
<div>
<div class="mb-1">
<small class="text-muted">
By: <a href="{ROOT_URL}home/profile/{author}" class="text-decoration-none">{authorName}</a> on {DTC date}{created}{/DTC}
</small>
</div>
<div class="comment-text">
{content}
</div>
{commentControl}
</div>
</div>
</li>
{/LOOP}
{ALT}
<li class="list-group-item context-main-bg mb-2">
<div class="text-center">
<p class="mb-0">Be the first to comment.</p>
</div>
</li>
{/ALT}
</ul>
</div>
</div>

View File

@ -0,0 +1,7 @@
<h1>Moderator' Area</h1>
<div class="jumbotron">
<h1>Welcome!</h1>
<p>This is the moderator section. You can give some groups permission to access this area. The menu is hidden for normal users and if they get a link to a moderator's area, the authentication system will stop them from accessing any content protected this way.</p>
<p>You can even use this feature in-line with your views, hiding certain components from non-moderators</p>
<p>The idea behind this role is for them to help you in policing comments as needed.</p>
</div>

View File

@ -0,0 +1,56 @@
<?php
/**
* app/plugins/contact/controllers/admin/contact.php
*
* This is the contact admin controller.
*
* @package TP Contact
* @version 3.0
* @author Joey Kimsey <Joey@thetempusproject.com>
* @link https://TheTempusProject.com
* @license https://opensource.org/licenses/MIT [MIT LICENSE]
*/
namespace TheTempusProject\Controllers\Admin;
use TheTempusProject\Bedrock\Functions\Input;
use TheTempusProject\Houdini\Classes\Issues;
use TheTempusProject\Houdini\Classes\Views;
use TheTempusProject\Houdini\Classes\Navigation;
use TheTempusProject\Houdini\Classes\Components;
use TheTempusProject\Classes\AdminController;
use TheTempusProject\Models\Contact as ContactModel;
class Contact extends AdminController {
protected static $contact;
public function __construct() {
parent::__construct();
self::$title = 'Admin - Contact';
self::$contact = new ContactModel;
}
public function view( $id = null ) {
Views::view( 'contact.admin.view', self::$contact->findById( $id ) );
}
public function delete( $data = null ) {
if ( Input::exists( 'submit' ) ) {
$data = Input::post( 'F_' );
}
if ( self::$contact->delete( (array) $data ) ) {
Issues::add( 'success', 'contact deleted' );
} else {
Issues::add( 'error', 'There was an error with your request.' );
}
$this->index();
}
public function clear( $data = null ) {
self::$contact->clear();
$this->index();
}
public function index( $data = null ) {
Views::view( 'contact.admin.list', self::$contact->listPaginated() );
}
}

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