Compare commits

...

55 Commits

Author SHA1 Message Date
ca850bb46b UI fixes and composer bump 2025-01-27 00:26:43 -05:00
35b7be92a6 bugfixes and small features
Fixed config switches not registering the correct current value
Added better ux when image uploads are disabled
Fixed an issue where uploaded files were not being handled correctly
Added the ability to disable user registrations
Fixed some variables being unintendedly protected
2025-01-26 15:13:34 -05:00
d4751696f3 sendmail bugfix 2025-01-23 22:39:11 -05:00
fa12dd20ba add redirect link to admin 2025-01-23 22:25:33 -05:00
41a6aed209 route bugfix 2025-01-23 22:22:08 -05:00
509a10bc36 more fixes
remove unused redirects plugin after functionality was moved to core
fixed after-install links
2025-01-22 14:46:14 -05:00
5e99213601 bugfixes
improved dark mode user pref
Fixed invalid default Group
Fixed subscriptions showing when plugin was disabled
Fixed messages and notifications showing when disabled
2025-01-22 14:11:52 -05:00
ebfeead788 composer bumps for deps 2025-01-21 21:09:57 -05:00
32a9711ade wip from ATB 2025-01-21 19:19:06 -05:00
87e4f90bab visual fixes 2024-12-20 14:10:31 -05:00
1c5b020a87 cleanup 2024-12-20 05:56:18 -05:00
5fe1c3aafe various bugfixes 2024-12-18 08:58:35 -05:00
a859fb7ace Bugfixes and Bootstrap 5 finalized 2024-12-17 22:57:55 -05:00
2220c6cda3 Bootstrap 5 continued + bugfixes 2024-12-15 17:19:08 -05:00
de6d608857 Bootstrap 5 updates continued 2024-12-14 06:15:47 -05:00
41426fda4e add specific images for chrome extension 2024-12-14 05:56:55 -05:00
7bef832417 Bootstrap 5 cont 2024-12-11 07:50:14 -05:00
aaefd66350 Bootstrap 5 + Darkmode changes 2024-12-11 02:08:53 -05:00
a0726e6578 update to bootstrap 5.2.3 continued 2024-12-10 09:00:49 -05:00
f3f323d30f update to bootstrap 5.2.3 continued 2024-12-10 01:46:00 -05:00
b3018de907 update to bootstrap 5.2.3 2024-12-09 20:00:22 -05:00
4ab9d33b01 bootstrap 4 update and bugfixes 2024-12-08 04:43:06 -05:00
485d85cb0a token support, api fixes and security, dark mode 2024-12-07 01:58:27 -05:00
b93d0259e4 bugfixes, cleanup, usercp menu changes 2024-12-05 15:34:36 -05:00
bf7b7ba1c9 Carousel fix + fiixes 2024-12-02 19:00:00 -05:00
113499254b Merge remote-tracking branch 'origin/update_PHP_deps_20240821121538' 2024-08-21 08:16:05 -04:00
3b6d7f3a2c Update PHP dependencies [20240821121538] 2024-08-21 12:15:43 +00:00
09f96c369a fixes to support composer 2024-08-21 08:15:27 -04:00
afb9624971 Merge remote-tracking branch 'origin/update_PHP_deps_20240821101926' 2024-08-21 06:20:22 -04:00
8d3dee2ac2 Update PHP dependencies [20240821101926] 2024-08-21 10:19:29 +00:00
355e509e68 composer bump 2024-08-21 06:19:13 -04:00
c3f46cf6c3 Merge remote-tracking branch 'origin/update_PHP_deps_20240821101740' 2024-08-21 06:19:00 -04:00
4a521afa88 Update PHP dependencies [20240821101740] 2024-08-21 10:17:43 +00:00
0fa83b0ae6 composer bump 2024-08-21 06:17:29 -04:00
5c7a320c2a bugfixes for pagination and installation 2024-08-21 06:14:54 -04:00
551f6654f6 composer version bumps 2024-08-20 06:42:29 -04:00
80048ad1dd various bugfixes 2024-08-20 06:37:38 -04:00
80b8226218 Readme updates 2024-08-20 06:32:38 -04:00
cc9e57888f more fixes 2024-08-16 22:17:41 -04:00
ec317e6426 cleanup 2024-08-16 22:14:43 -04:00
910c9646b8 Readme updates 2024-08-15 13:50:59 -04:00
44dcb29060 remove non-standard plugins 2024-08-15 12:49:34 -04:00
431b1214fa lock down composer versions for final testing 2024-08-15 12:40:31 -04:00
9972f6a654 Readme Updates 2024-08-15 12:30:57 -04:00
b658614ce8 Readme Updates 2024-08-15 12:30:52 -04:00
55a725cd9e readme and license updates 2024-08-15 12:30:45 -04:00
a8b972acf5 Readme Updates 2024-08-15 12:30:45 -04:00
fd8d37d67a Readme Updates 2024-08-15 12:30:36 -04:00
c8eed20814 Readme Updates 2024-08-15 12:30:10 -04:00
b6767ae846 Docker updates 2024-08-15 12:30:10 -04:00
156f19ee97 Bookmark plugin renaming continued 2024-08-15 12:30:10 -04:00
0a9a5a433f Bookmark plugin renaming 2024-08-15 12:30:09 -04:00
b8c75a128a Merge remote-tracking branch 'origin/update_PHP_deps_20240813063608' 2024-08-13 02:36:31 -04:00
e4e72905fc Update PHP dependencies [20240813063608] 2024-08-13 06:36:12 +00:00
74e9af4539 bookmark bugfix 2024-08-13 02:35:55 -04:00
407 changed files with 6967 additions and 17976 deletions

4
.gitignore vendored
View File

@ -61,5 +61,7 @@ logs/*
.vscode/ .vscode/
mail.log mail.log
vendor/canary/logs/* vendor/canary/logs/*
docker/.env
.env .env
components/*
mailhog.log
uploads/*

View File

@ -1,6 +1,10 @@
MIT License Copyright (c) 2024-present Joey Kimsey
Copyright (c) 2024 Joey Kimsey Portions of this software are licensed as follows:
* All content residing under the "app/" directory of this repository, excluding "app/plugins/"; is licensed under "Creative Commons: CC BY-SA 4.0 license".
* All third party components incorporated into The Tempus Project Software including plugins are licensed under the original license provided by the owner of the applicable component.
* Content outside of the above mentioned directories or restrictions above is available under the "MIT Expat" license as defined below.
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal

409
README.md
View File

@ -1,14 +1,30 @@
# The Tempus Project # The Tempus Project
## Rapid Prototyping Framework _Rapid Prototyping Framework built on PHP utilizing the MVC pattern with a Bootstrap front-end_
## Developer(s): Joey Kimsey __Developer(s):__
- __Joey Kimsey__ - _Lead Developer_
The aim of this project is to provide a simple and stable platform from which to easily add functionality. The goal being the ability to quickly build and test new projects with a lightweight ecosystem to help. The aim of this project is to provide a simple and stable platform from which to easily add functionality. The goal being the ability to quickly build and test new projects with a lightweight ecosystem to help.
**Notice: This code is in _still_ not production ready. This framework is provided as is, use at your own risk.** **Notice: This code is in _still_ not production ready. This framework is provided as is, use at your own risk.**\
I am working very hard to ensure the system is safe and reliable enough for me to endorse its widespread use. Unfortunately, it still needs a lot of QA and improvements. I am working very hard to ensure the system is safe and reliable enough for me to endorse its widespread use. Unfortunately, it still needs a lot of QA and improvements.
## Table of contents
[[_TOC_]]
## Find Us
* [DockerHub](https://hub.docker.com/repositories/thetempusproject)
* [Packagist](https://packagist.org/users/joeyk4816/packages/)
* [GitLab](https://git.thetempusproject.com/the-tempus-project/thetempusproject)
## Summary
The Tempus Project is a PHP application utilizing the MVC pattern to serve up simple pages and APIs with minimal effort. It requires a MySQL database to function and is designed to run equally well with nginx or apache powering the webserver. Most of the core functionality is developed in house and provided through dependencies. At this time, the frontend is driven on bootstrap 3 and FontAwesome for simplicity.
## Features ## Features
- A Plugin system that allows plug-and-play functionality - A Plugin system that allows plug-and-play functionality
@ -24,355 +40,92 @@ I am working very hard to ensure the system is safe and reliable enough for me t
## Installation ## Installation
Preferred method for installation is using composer. Special attention has been given to use without composer and more information is included below. The preferred method for installation is [Composer](#composer) but special attention has been given to installation and usage [without Composer](#composer).
### Composer ### Composer
The simplest method to start a new project is to use composer to create a new project and automatically clone all the necessary files: The simplest method to start a new project is to use composer to create a new project and automatically clone all the necessary files:
#### via create-project
``` ```
composer create-project thetempusproject/thetempusproject test-app composer create-project thetempusproject/thetempusproject test-app
``` ```
1. Clone the directory to wherever you want to install the framework. #### via clone & install
2. Open your terminal to the directory you previously cloned the repository.
3. Install using composer:
`php composer.phar install`
4. Open your browser and navigate to install.php (it will be in the root directory of your installation)
5. When prompted, complete the forms and complete the process.
1. Clone the directory to wherever you want to install the framework.
`git clone https://git.thetempusproject.com/the-tempus-project/thetempusproject.git <test-app>`
1. Open your terminal to the directory you previously cloned the repository.
`cd <test-app>`
1. Install using composer:
`php composer.phar install`
### Manually ### Manually
### Docker 1. Clone the directory to wherever you want to install the framework.
`git clone https://git.thetempusproject.com/the-tempus-project/thetempusproject.git <test-app>`
1. Open your terminal to the directory you previously cloned the repository.
`cd <test-app>/`
1. Clone the dependency directories to the vendor/ folder.
```
cd vendor/
git clone https://git.thetempusproject.com/the-tempus-project/bedrock.git bedrock
git clone https://git.thetempusproject.com/the-tempus-project/canary.git canary
git clone https://git.thetempusproject.com/the-tempus-project/hermes.git hermes
git clone https://git.thetempusproject.com/the-tempus-project/houdini.git houdini
```
__Note:__ The autoloader should automatically detect and use the dependencies, but they need to be sorted into the folders ans shown above.
#### Apache ## Docker
#### NGINX To enable quick deployment and collaboration The Tempus Project is distributed with the files to build your own docker images or stack with apache or nginx The included `docker-compose.yml` will load up an entire stack including apache and nginx, as well as a MySQL server with phpmyadmin.
#### Docker-Compose You will need docker installed on your system then you can either download the latest images from DockerHud:
If you have any trouble with the installation, you can check out our FAQ page on the wiki for answers to common issues. ```
docker pull thetempusproject/ttp-apache
docker pull thetempusproject/ttp-nginx
```
If you would like a full copy of the project with all of its included dependencies you can find it at https://github.com/TheTempusProject/TempusProjectFull Or you can build your own images from this repository. More information can be found in the included README files:
Please note this repository is only up to the latest _stable_ release. Please continue to use composer update to get the latest development releases.
**Do not forget to remove install.php once you have finished installation!** * [Apache Image](docker/ttp-apache/README.md)
* [Nginx Image](docker/ttp-nginx/README.md)
#### Currently being developed ### Docker-Compose
The Docker stack included here will build new versions of the nginx and apache webserver and launch them in individual containers. It will also create 2 more containers; one for php, and one for phpmyadmin.
```
docker-compose -f docker-compose.yml up --build -d --no-cache
```
__Note:__ If you cloned the repository from git, you will need to copy the `docker/.env.example` to `.env` in the root directory and update the contents before proceeding with docker-compose.
## Contributing
TheTempusProject is an open source project and welcomes community contributions. Please refer to the [Contributing file](CONTRIBUTING.md) for more details.
## License
See the [LICENSE](LICENSE) file for licensing information as it pertains to files in this repository.
## Known Issues
- [ ] The blog plugin should add a welcome post during the installResources step of the installer. It doesn't work right now.
## Currently being developed
- [ ] Adding documentation - [ ] Adding documentation
- [ ] Unit tests - [ ] Unit testing
#### Future updates ## Future updates
- [ ] Expansion of PDO to allow different database types - [ ] Expansion of PDO to allow different database types
- [ ] Update installer to account for updates. - [ ] Update installer to account for database deltas, allowing easy updating.
- [ ] Implement uniformity in terms of error reporting, exceptions, logging. - [ ] Implement uniformity in terms of error reporting, exceptions, logging.
- [ ] The templating system has gotten too large and needs to be split into its own repo - [ ] I want to make an api that allows you to download and install new plugins from a centralized repository
- [ ] i want plugin instalation to be compatible with composer for easier management of added plugins.
TTP ToDo:
need to integrate new plugins for some moved features
canary
comments
members
messages
Split inbox and outbox apart
split messages from usercp
redirects
need to make sure all 'use ' statements are updated to new repo names
namespace TempusDebugger; => namespace TheTempusProject\Canary;
namespace TheTempusProject\Houdini; => namespace TheTempusProject\houdini;
namespace TheTempusProject/TempusTool; => namespace TheTempusProject\Overwatch;
need a mechanism for handeling config/constants.php in each plugin
migrate all 'secondary' constants (constants not used in the default execution of the application) to plugin folders
Perform final F & R for:
"tpc"
need better handeling around blog filters like month and day
split profile from usercp
need a way to secure the api
need a standard way to do apis
need a way to show parts conditionally like {@if}
need
if
else
elseif
need a way to show something conditionally if a plugin is enabled
like {@enabled:comments}
{comments}
{@enabled}
https://css-tricks.com/drag-and-drop-file-uploading/
https://www.smashingmagazine.com/2018/01/drag-drop-file-uploader-vanilla-js/
need to merge both autoloaders into the same one under bin
need to be able to install multiple database tables for the same plugin
rename default.js and .css to main.js/css
fix where i moved those to the app/css and app/js folders
make a new template repo/dependency
make a new Debug repo/dependency
Fix the plugin
fix the console logger
add the ability to include js files
add the ability to include css files as needed
chat should include a config for the refresh timer
and better error handeling for models and plugins
need to make a singular list function to remove or combine these:
listGroupsSimple
listPosts
i need to move everything moderator relateed to comments
i also need to make sure that moderators can actually moderate
the get form html thing should work perfectly with the database array to create hella simple to generate forms for anything
we are not doing anything with requiredPlugins
comments and blog are being manually added in the admin dashboard, this could be a problem when they are disabled
removed from blog filter
commentCount
need to address the error handler just failing to work
and the exception handler picking up random errors
need to revisit all of the form checking and make sure it is apparent to the user when and how they mess something up.
many pages are missing descriptions, need to add them
https://jsonapi.org/format/
need a way for the template system to:
switch between the meta-header content types for the sharing info
xlarge
large
etc
need better checking around title, meta-image, and descriptions
prevent accidently feeding bad images or text to these fields
need to manages js and css includes better, and incorperate it into templating system
the get timezone getdate gettime format functions all need to be migrated to app, stored as static vars and refactored
in core, am i using htaccess.html or nginx.html anywhere, if not, change them to .example
Routes -> getHost is using a terrible conditional for docker hosts, need to improve
Need to test all the filters for the editor stuff
need the ability for the autoloader to accept specific file name associations
needs a require_all
need to re-namespace all classes and functions
some classes need to be converted to non-static
some functions need to be converted to more static
run from the command line
initiated // is in so many controllers, i def want this removed initialized
tempus_project.php
test running commands from cli
if we move install.php to the bin, it will be unaccessible to the web server??
if its unaccessible except theough the index.php router, we don't need to delete it because its unaccessible again
can i use submodules?
errors should be able to be customized
if its in the app
should add more logging, esp for admin actions
need to add self::$pageDescription to many pages
man, messages is hot garbage, def needs a rework
need a mechanism to add listeners and events
ability to restore backups of perms prefs and configs
if your controller has no index method, you're just SOL
a blank page is called and no method is loaded
Warns should be for failed checks
add a check for having write access to the config folder and the uploads folder
and whatever is going to be needed to the plugin downloading
some configs have been removed and need to be accounted for
Unused:
---------------------------
Config::getValue('bugReports/sendEmail')
Config::getValue('bugReports/emailTemplate')
Config::getValue('feedback/sendEmail')
Config::getValue('feedback/emailTemplate')
Config::getValue('uploads/files')
Config::getValue('uploads/images')
Config::getValue('uploads/maxFileSize')
after all changes are pushed up and available, docker needs to be tested and updated
when using composer, the composer page is populated and correct
the config step of install should be checking the db creds
// need to make notes of other standards as i go to update the contributing document
// need to cross refrence the configs from core and ttp
// ensure the resources folder is current
// document, fix, and remove @TODO's where possible
Search for cuss words, they make you look stupid
fuck
shit
dam
damm
damn
god
ass
cunt
bitch
ffs
wtf
had to remove the tracking pixel that was to be used with the contacts form, this will need to be re- added in a future update
had to remove the rest controller, its currently just unused
// this can be used for the tempus project
composer create-project laravel/laravel example-app
# Release Checklist
=====================
- [] Spell check every file.
- [] All documentation must be reviewed for accuracy.
- [] If a new year has passed, ensure the year has been updated where applicable.
- [] If default permissions, preferences, configs, base classes or models have been updated, update resources accordingly.
- []
namespace TempusDebugger; => namespace TheTempusProject\Canary;
need to make sure a template loader can be called and still use the default template file, IE always add these CSS or JS resources.
discord bot that shares updates on your party from the site
maybe a summary after each session
warning that time is coming up
changes made to anything
D&D news
is it possible to store a campaigns state on the blockchain?
keeping this as a repository for podcasts would get more people to check it out
same for youtube
people love sharing their resources, so make it EASY to find podcasts, and youtube channels, and etsy stores, and give people a place to share it with their groups
try and earn commisions from this and do featured XYZ every x days or weeks or whatever
have different "kinds" of dice to portray on the dice roll page
maybe spinners instead of conventional die
maybe weird health potions for D4's
What is my goal here?
I would like to play Dungeons and Dragons once a week with my friends. In an ideal world, I would DM this game and spend all week building tools for us to use that I then put on a website which sells memberships to other players so they can use the tools too.

View File

@ -1,8 +1,8 @@
<?php <?php
/** /**
* app/classes/admin_controller.php * app/classes/api_controller.php
* *
* This is the base admin controller. Every other admin controller should * This is the base api controller. Every other api controller should
* extend this class. * extend this class.
* *
* @version 3.0 * @version 3.0
@ -16,17 +16,122 @@ use TheTempusProject\Houdini\Classes\Template;
use TheTempusProject\TheTempusProject as App; use TheTempusProject\TheTempusProject as App;
use TheTempusProject\Hermes\Functions\Redirect; use TheTempusProject\Hermes\Functions\Redirect;
use TheTempusProject\Bedrock\Functions\Session; 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 { class ApiController extends Controller {
public function __construct() { 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(); parent::__construct();
if ( ! App::verifyApiRequest() ) { Template::setTemplate( 'api' );
Session::flash( 'error', 'You do not have permission to view this page.' );
return Redirect::home();
}
Template::noFollow(); Template::noFollow();
Template::noIndex(); Template::noIndex();
Template::addHeader( 'Content-Type: application/json; charset=utf-8' ); $res = $this->verifyApiRequest();
Template::setTemplate( 'api' ); 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;
} }
} }

View File

@ -1,17 +1,18 @@
<?php <?php
/** /**
* classes/config.php * app/classes/config.php
* *
* This class handles all the hard-coded configurations. * This class handles all the hard-coded configurations.
* *
* @version 3.0 * @version 3.0
* @author Joey Kimsey <Joey@thetempusproject.com> * @author Joey Kimsey <Joey@thetempusproject.com>
* @link https://TheTempusProject.com/Core * @link https://TheTempusProject.com
* @license https://opensource.org/licenses/MIT [MIT LICENSE] * @license https://opensource.org/licenses/MIT [MIT LICENSE]
*/ */
namespace TheTempusProject\Classes; namespace TheTempusProject\Classes;
use TheTempusProject\Houdini\Classes\Forms; use TheTempusProject\Houdini\Classes\Forms;
use TheTempusProject\Houdini\Classes\Template;
use TheTempusProject\Canary\Bin\Canary as Debug; use TheTempusProject\Canary\Bin\Canary as Debug;
use TheTempusProject\Bedrock\Functions\Check; use TheTempusProject\Bedrock\Functions\Check;
use TheTempusProject\Bedrock\Functions\Input; use TheTempusProject\Bedrock\Functions\Input;
@ -19,7 +20,6 @@ use TheTempusProject\Bedrock\Classes\Config as BedrockConfig;
class Config extends BedrockConfig { class Config extends BedrockConfig {
public static function getFieldEditHtml( $name, $includeProtected = false ) { public static function getFieldEditHtml( $name, $includeProtected = false ) {
// @todo: includeProtected is unused here
$node = self::get( $name ); $node = self::get( $name );
if ( empty( $node ) ) { if ( empty( $node ) ) {
return; return;
@ -28,13 +28,57 @@ class Config extends BedrockConfig {
return; return;
} }
$fieldname = str_ireplace( '/', '-', $name ); $fieldname = str_ireplace( '/', '-', $name );
$html = Forms::getFormFieldHtml(
$fieldname, $html = '';
$node['pretty'], $fieldHtml = '';
$node['type'], switch ( $node['type'] ) {
$node['value'], case 'radio':
); case 'bool':
return $html; 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="User Avatar" 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 ) { public static function getCategoryEditHtml( $category ) {
@ -47,12 +91,12 @@ class Config extends BedrockConfig {
Debug::warn( "Config category not found: $category" ); Debug::warn( "Config category not found: $category" );
return; return;
} }
$categoryHeader = '<div class="form-group"><label>' . ucfirst( $category ) . ':</label><hr></div>'; $categoryHeader = '<div class=""><h3 class="text-center">' . ucfirst( $category ) . ':</h3><hr>';
foreach ( self::$config[$category] as $field => $node ) { foreach ( self::$config[$category] as $field => $node ) {
$html .= self::getFieldEditHtml( $category . '/' . $field ); $html .= self::getFieldEditHtml( $category . '/' . $field );
} }
if ( !empty( $html ) ) { if ( !empty( $html ) ) {
$html = $categoryHeader . $html; $html = $categoryHeader . $html . '</div>';
} }
return $html; return $html;
} }

View File

@ -35,7 +35,6 @@ class Controller extends BedrockController {
} }
new Template; new Template;
Template::setTemplate( 'default' ); Template::setTemplate( 'default' );
Components::set( 'TOKEN', Token::generate() );
} }
public function __destruct() { public function __destruct() {

View File

@ -16,11 +16,19 @@ namespace TheTempusProject\Classes;
use TheTempusProject\Bedrock\Functions\Check; use TheTempusProject\Bedrock\Functions\Check;
use TheTempusProject\Canary\Bin\Canary as Debug; 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 { class Forms extends Check {
private static $formHandlers = []; private static $formHandlers = [];
private static $initialized = false;
public static function check( $formName ) { public static function check( $formName ) {
if ( self::$initialized !== true ) {
self::initHandlers();
}
if ( empty( self::$formHandlers[ $formName ] ) ) { if ( empty( self::$formHandlers[ $formName ] ) ) {
Debug::error( "Form not found: $formName" ); Debug::error( "Form not found: $formName" );
return false; return false;
@ -74,4 +82,585 @@ class Forms extends Check {
} }
return true; 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( '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;
}
} }

View File

@ -439,7 +439,7 @@ class Installer {
// exclude any flags we don't have a matric map for // exclude any flags we don't have a matric map for
if ( empty( $module_data->class_object->$matrix ) ) { if ( empty( $module_data->class_object->$matrix ) ) {
Debug::warn( "$flag_type does not have a proper matrix map and cannot be uninstalled." ); Debug::warn( "$flag_type does not have a proper matrix map and cannot be installed." );
$module_data->$flag_type = INSTALL_STATUS_NOT_FOUND; $module_data->$flag_type = INSTALL_STATUS_NOT_FOUND;
} }
} }
@ -500,7 +500,7 @@ class Installer {
// exclude any flags we don't have a matric map for // exclude any flags we don't have a matric map for
if ( empty( $module_data->class_object->$matrix ) ) { if ( empty( $module_data->class_object->$matrix ) ) {
Debug::warn( "$flag_type does not have a proper matrix map and cannot be installed." ); Debug::warn( "$flag_type does not have a proper matrix map and cannot be uninstalled." );
$module_data->$flag_type = INSTALL_STATUS_NOT_FOUND; $module_data->$flag_type = INSTALL_STATUS_NOT_FOUND;
} }
} }

View File

@ -15,6 +15,7 @@ use TheTempusProject\Canary\Bin\Canary as Debug;
use TheTempusProject\Bedrock\Functions\Check; use TheTempusProject\Bedrock\Functions\Check;
use TheTempusProject\Houdini\Classes\Forms; use TheTempusProject\Houdini\Classes\Forms;
use TheTempusProject\Bedrock\Functions\Input; use TheTempusProject\Bedrock\Functions\Input;
use TheTempusProject\Houdini\Classes\Template;
class Permissions { class Permissions {
public static $permissions = false; public static $permissions = false;
@ -237,8 +238,21 @@ class Permissions {
} else { } else {
$checked = false; $checked = false;
} }
$form .= Forms::getFormFieldHtml( $name, $details['pretty'], 'checkbox', $checked ); $form .= self::getFieldEditHtml( $name, $checked, $details['pretty'] );
} }
return $form; return $form;
} }
public static function getFieldEditHtml( $name, $default, $pretty ) {
$fieldname = str_ireplace( '/', '-', $name );
$fieldHtml = Forms::getSwitchHtml( $fieldname, [ 'true', 'false' ], $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 );
}
} }

View File

@ -309,7 +309,7 @@ class Plugin {
$data = []; $data = [];
foreach( $this->resourceMatrix as $tableName => $entries ) { foreach( $this->resourceMatrix as $tableName => $entries ) {
foreach ($ids as $id) { foreach ($ids as $id) {
$data[] = self::$db->delete( $tableName, $id ); $data[] = self::$db->delete( $tableName, [ 'ID', '=', $id ] );
} }
} }
return $data; return $data;
@ -335,9 +335,14 @@ class Plugin {
} }
public function loadFooterNav() { public function loadFooterNav() {
if ( !empty( $this->footer_links ) ) { if ( !empty( $this->contact_footer_links ) ) {
foreach( $this->footer_links as $key => $link ) { foreach( $this->contact_footer_links as $key => $link ) {
Navigation::addLink( App::FOOTER_MENU_NAME, $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 );
} }
} }
} }

View File

@ -13,11 +13,13 @@ namespace TheTempusProject\Classes;
use TheTempusProject\Houdini\Classes\Issues; use TheTempusProject\Houdini\Classes\Issues;
use TheTempusProject\Houdini\Classes\Forms; use TheTempusProject\Houdini\Classes\Forms;
use TheTempusProject\Houdini\Classes\Template;
use TheTempusProject\Canary\Bin\Canary as Debug; use TheTempusProject\Canary\Bin\Canary as Debug;
use TheTempusProject\Bedrock\Functions\Check; use TheTempusProject\Bedrock\Functions\Check;
use TheTempusProject\Bedrock\Functions\Upload; use TheTempusProject\Bedrock\Functions\Upload;
use TheTempusProject\Bedrock\Functions\Input; use TheTempusProject\Bedrock\Functions\Input;
use TheTempusProject\TheTempusProject as App; use TheTempusProject\TheTempusProject as App;
use TheTempusProject\Bedrock\Classes\Config;
class Preferences { class Preferences {
public static $preferences = false; public static $preferences = false;
@ -186,17 +188,101 @@ class Preferences {
} }
public function getFormHtml( $populated = [] ) { public function getFormHtml( $populated = [] ) {
// dv( self::$preferences );
$form = ''; $form = '';
// Added so i can force some sort of ordering
$inputTypes = [
'file' => [],
'select' => [],
'timezone' => [],
'checkbox' => [],
'switch' => [],
];
foreach ( self::$preferences as $name => $details ) { foreach ( self::$preferences as $name => $details ) {
$tempPrefsArray = $this->normalizePreferenceArray( $name, $details ); $tempPrefsArray = $this->normalizePreferenceArray( $name, $details );
if ( isset( $populated[ $name ] ) ) { if ( isset( $populated[ $name ] ) ) {
$tempPrefsArray['default'] = $populated[$name]; $tempPrefsArray['value'] = $populated[$name];
} else {
$tempPrefsArray['value'] = $tempPrefsArray['default'];
} }
$form .= Forms::getFormFieldHtml( $name, $tempPrefsArray['pretty'], $tempPrefsArray['type'], $tempPrefsArray['default'], $tempPrefsArray['options'] ); // $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; 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-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-end">Current Image</h4>';
$html .= '<div class="col-lg-6">';
$html .= '<img alt="User Avatar" src="{ROOT_URL}' . $defaultValue . '" class="img-circle img-fluid p-2 avatar-125">';
$html .= '</div>';
}
$html .= '</div>';
return Template::parse( $html );
}
public function convertFormToArray( $fillMissing = true, $defaultsOnly = true ) { public function convertFormToArray( $fillMissing = true, $defaultsOnly = true ) {
$prefsArray = []; $prefsArray = [];
foreach ( self::$preferences as $name => $details ) { foreach ( self::$preferences as $name => $details ) {
@ -212,12 +298,14 @@ class Preferences {
} }
if ( 'file' == $details['type'] ) { if ( 'file' == $details['type'] ) {
if ( Input::exists( $name ) ) { if ( Input::exists( $name ) ) {
$folder = IMAGE_UPLOAD_DIRECTORY . App::$activeUser->username . DIRECTORY_SEPARATOR; $folder = UPLOAD_DIRECTORY . App::$activeUser->username . DIRECTORY_SEPARATOR . 'images' . DIRECTORY_SEPARATOR;
if ( !Upload::image( $name, $folder ) ) { $upload = Upload::image( $name, $folder );
Issues::add( 'error', [ 'There was an error with your upload.' => Check::systemErrors() ] ); if ( $upload ) {
} else {
$route = str_replace( APP_ROOT_DIRECTORY, '', $folder ); $route = str_replace( APP_ROOT_DIRECTORY, '', $folder );
$prefsArray[$name] = $route . Upload::last(); $prefsArray[$name] = $route . Upload::last();
} else {
Issues::add( 'error', [ 'There was an error with your upload.' => Check::userErrors() ] );
unset( $prefsArray[$name] );
} }
} }
} }

View File

@ -38,11 +38,15 @@ if ( ! defined( 'CONFIG_DIRECTORY' ) ) {
# Tempus Debugger # Tempus Debugger
define( 'CANARY_SECURE_HASH', 'd73ed7591a30f0ca7d686a0e780f0d05' ); define( 'CANARY_SECURE_HASH', 'd73ed7591a30f0ca7d686a0e780f0d05' );
# Tempus Project Core # Tempus Project Core
define( 'APP_NAME', 'The Tempus Project');
define( 'TP_DEFAULT_LOGO', 'images/logo.png');
// Check // Check
define( 'MINIMUM_PHP_VERSION', 8.1); define( 'MINIMUM_PHP_VERSION', 8.1);
// Cookies // Cookies
define( 'DEFAULT_COOKIE_PREFIX', 'TP_'); define( 'DEFAULT_COOKIE_PREFIX', 'TP_');
// Debug // Debug
define( 'CANARY_DEBUG_DIRECTORY', APP_ROOT_DIRECTORY . 'logs' . DIRECTORY_SEPARATOR );
define( 'CANARY_DEBUG_LEVEL_ERROR', 'error' ); define( 'CANARY_DEBUG_LEVEL_ERROR', 'error' );
define( 'CANARY_DEBUG_LEVEL_WARN', 'warn' ); define( 'CANARY_DEBUG_LEVEL_WARN', 'warn' );
define( 'CANARY_DEBUG_LEVEL_INFO', 'info' ); define( 'CANARY_DEBUG_LEVEL_INFO', 'info' );

View File

@ -59,6 +59,6 @@ class Composer extends AdminController {
$out[] = (object) $versionsInstalled[ $name ]; $out[] = (object) $versionsInstalled[ $name ];
} }
Views::view( 'admin.dependencies', $out ); Views::view( 'admin.modules.dependencies', $out );
} }
} }

View File

@ -95,7 +95,7 @@ class Groups extends AdminController {
} }
public function index( $data = null ) { public function index( $data = null ) {
Views::view( 'admin.groups.list', self::$group->list() ); Views::view( 'admin.groups.list', self::$group->listPaginated() );
} }
public function listmembers( $data = null ) { public function listmembers( $data = null ) {

View File

@ -15,8 +15,13 @@ use TheTempusProject\Houdini\Classes\Views;
use TheTempusProject\Houdini\Classes\Components; use TheTempusProject\Houdini\Classes\Components;
use TheTempusProject\Classes\AdminController; use TheTempusProject\Classes\AdminController;
use TheTempusProject\Models\User; use TheTempusProject\Models\User;
use TheTempusProject\Plugins\Comments; use TheTempusProject\Models\Comments;
use TheTempusProject\Plugins\Blog; 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;
class Home extends AdminController { class Home extends AdminController {
public static $user; public static $user;
@ -29,18 +34,43 @@ class Home extends AdminController {
} }
public function index() { public function index() {
Components::set( 'commentDash', '' );
if ( class_exists( 'TheTempusProject\Plugins\Comments' ) ) { if ( class_exists( 'TheTempusProject\Plugins\Comments' ) ) {
$comments = new Comments; $plugin = new CommentPlugin;
self::$comments = $comments->getModel();
$comments = Views::simpleView( 'comments.admin.dashboard', self::$comments->recent( 'all', 5 ) ); if ( ! $plugin->checkEnabled() ) {
Components::set( 'commentDash', $comments ); 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' ) ) { if ( class_exists( 'TheTempusProject\Plugins\Blog' ) ) {
$blog = new Blog; $plugin = new BlogPlugin;
self::$posts = $blog->posts;
$posts = Views::simpleView( 'blog.admin.dashboard', self::$posts->recent( 5 ) ); if ( ! $plugin->checkEnabled() ) {
Components::set( 'blogDash', $posts ); 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; self::$user = new User;

View File

@ -39,7 +39,12 @@ class Plugins extends AdminController {
} }
public function disable( $name = null ) { public function disable( $name = null ) {
if ( empty( $name ) ) {
Session::flash( 'error', 'Unknown Plugin.' );
Redirect::to( 'admin/plugins' );
}
Components::set( 'PLUGIN', $name ); Components::set( 'PLUGIN', $name );
self::$title = 'Admin - Disable ' . $name;
if ( !Input::exists( 'installHash' ) ) { if ( !Input::exists( 'installHash' ) ) {
return Views::view( 'admin.modules.plugins.disable' ); return Views::view( 'admin.modules.plugins.disable' );
} }
@ -52,11 +57,16 @@ class Plugins extends AdminController {
} }
public function enable( $name = null ) { public function enable( $name = null ) {
if ( empty( $name ) ) {
Session::flash( 'error', 'Unknown Plugin.' );
Redirect::to( 'admin/plugins' );
}
Components::set( 'PLUGIN', $name ); Components::set( 'PLUGIN', $name );
self::$title = 'Admin - Enable ' . $name;
if ( !Input::exists( 'installHash' ) ) { if ( !Input::exists( 'installHash' ) ) {
return Views::view( 'admin.modules.plugins.enable' ); return Views::view( 'admin.modules.plugins.enable' );
} }
if ( !Plugin::enable( $name ) ) { if ( ! Plugin::enable( $name ) ) {
Session::flash( 'error', 'There was an error enabling the plugin.' ); Session::flash( 'error', 'There was an error enabling the plugin.' );
} else { } else {
Session::flash( 'success', 'Plugin has been enabled.' ); Session::flash( 'success', 'Plugin has been enabled.' );
@ -71,6 +81,7 @@ class Plugins extends AdminController {
} }
$name = strtolower( $name ); $name = strtolower( $name );
Components::set( 'PLUGIN', $name ); Components::set( 'PLUGIN', $name );
self::$title = 'Admin - Install ' . $name;
if ( ! Input::exists( 'installHash' ) ) { if ( ! Input::exists( 'installHash' ) ) {
return Views::view( 'admin.modules.plugins.install' ); return Views::view( 'admin.modules.plugins.install' );
} }
@ -95,6 +106,7 @@ class Plugins extends AdminController {
} }
$name = strtolower($name); $name = strtolower($name);
Components::set( 'PLUGIN', $name ); Components::set( 'PLUGIN', $name );
self::$title = 'Admin - Uninstall ' . $name;
if ( !Input::exists( 'uninstallHash' ) ) { if ( !Input::exists( 'uninstallHash' ) ) {
return Views::view( 'admin.modules.plugins.uninstall' ); return Views::view( 'admin.modules.plugins.uninstall' );

View File

@ -11,7 +11,7 @@
*/ */
namespace TheTempusProject\Controllers\Admin; namespace TheTempusProject\Controllers\Admin;
use TheTempusProject\TTPForms; use TheTempusProject\Classes\Forms as TTPForms;
use TheTempusProject\Houdini\Classes\Views; use TheTempusProject\Houdini\Classes\Views;
use TheTempusProject\Houdini\Classes\Issues; use TheTempusProject\Houdini\Classes\Issues;
use TheTempusProject\Houdini\Classes\Navigation; use TheTempusProject\Houdini\Classes\Navigation;
@ -36,20 +36,26 @@ class Routes extends AdminController {
} }
public function create() { public function create() {
if ( Input::exists( 'redirect_type' ) ) { if ( ! Input::exists( 'redirect_type' ) ) {
if ( !TTPForms::check( 'createRoute' ) ) { return Views::view( 'admin.routes.create' );
Issues::add( 'error', [ 'There was an error with your route.' => Check::userErrors() ] );
}
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' );
}
} }
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' ); Views::view( 'admin.routes.create' );
} }
@ -88,7 +94,7 @@ class Routes extends AdminController {
} }
public function index() { public function index() {
return Views::view( 'admin.routes.list', self::$routes->list() ); return Views::view( 'admin.routes.list', self::$routes->listPaginated() );
} }
public function view( $id = null ) { public function view( $id = null ) {

View File

@ -1,8 +1,8 @@
<?php <?php
/** /**
* app/controllers/admin/contact.php * app/controllers/admin/send_mail.php
* *
* This is the admin contact controller. The only real use is to send out emails to the various lists. * This is the admin email controller. The only real use is to send out emails to the various lists.
* *
* @version 3.0 * @version 3.0
* @author Joey Kimsey <Joey@thetempusproject.com> * @author Joey Kimsey <Joey@thetempusproject.com>
@ -18,19 +18,34 @@ use TheTempusProject\Houdini\Classes\Issues;
use TheTempusProject\Houdini\Classes\Views; use TheTempusProject\Houdini\Classes\Views;
use TheTempusProject\Models\User; use TheTempusProject\Models\User;
use TheTempusProject\Models\Subscribe; use TheTempusProject\Models\Subscribe;
use TheTempusProject\Plugins\Subscribe as Plugin;
class Contact extends AdminController { class SendMail extends AdminController {
public static $user; public static $user;
public static $subscribe; public static $subscribe;
public function __construct() { public function __construct() {
parent::__construct(); parent::__construct();
self::$title = 'Admin - Contact'; self::$title = 'Admin - Send Mail';
self::$user = new User; self::$user = new User;
self::$subscribe = new Subscribe;
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 ) { private function emailSubscribers( $params ) {
if ( empty( self::$subscribe ) ) {
Issues::add( 'error', 'Subscriptions plugin is unavailable' );
return;
}
$list = self::$subscribe->list(); $list = self::$subscribe->list();
if ( empty( $list ) ) { if ( empty( $list ) ) {
Issues::add( 'error', 'No subscribers found' ); Issues::add( 'error', 'No subscribers found' );

View File

@ -0,0 +1,90 @@
<?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;
$view = Navigation::activePageSelect( 'nav.admin', '/admin/tokens' );
Components::set( 'ADMINNAV', $view );
}
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 ) );
}
}

View File

@ -26,6 +26,7 @@ use TheTempusProject\Classes\AdminController;
use TheTempusProject\Models\User; use TheTempusProject\Models\User;
use TheTempusProject\Models\Group; use TheTempusProject\Models\Group;
use TheTempusProject\TheTempusProject as App; use TheTempusProject\TheTempusProject as App;
use TheTempusProject\Houdini\Classes\Template;
class Users extends AdminController { class Users extends AdminController {
public static $user; public static $user;
@ -63,8 +64,11 @@ class Users extends AdminController {
} }
} }
} }
$select = Forms::getSelectHtml(
$select = Forms::getFormFieldHtml( 'groupSelect', 'User Group', 'select', Config::getValue( 'group/defaultGroup' ), self::$group->listGroupsSimple() ); 'groupSelect',
self::$group->listGroupsSimple(),
Config::getValue( 'group/defaultGroup' ),
);
Components::set( 'groupSelect', $select ); Components::set( 'groupSelect', $select );
Views::view( 'admin.users.create' ); Views::view( 'admin.users.create' );
} }
@ -132,15 +136,21 @@ class Users extends AdminController {
$userGroup = $userData->userGroup; $userGroup = $userData->userGroup;
} }
Forms::selectRadio( 'confirmed', $userData->confirmed ); Forms::selectRadio( 'confirmed', $userData->confirmed );
$avatar = Forms::getFormFieldHtml( 'avatar', 'User Avatar', 'file', $avatarLocation );
$select = Forms::getFormFieldHtml( 'groupSelect', 'User Group', 'select', $userGroup, self::$group->listGroupsSimple() ); $avatar = $this->getAvatar( 'avatar', $avatarLocation );
Components::set( 'AvatarSettings', $avatar ); Components::set( 'AvatarSettings', $avatar );
$select = Forms::getSelectHtml(
'groupSelect',
self::$group->listGroupsSimple(),
$userGroup,
);
Components::set( 'groupSelect', $select ); Components::set( 'groupSelect', $select );
Views::view( 'admin.users.edit', $userData ); Views::view( 'admin.users.edit', $userData );
} }
public function index() { public function index() {
Views::view( 'admin.users.list', self::$user->userList() ); Views::view( 'admin.users.list', self::$user->listPaginated() );
} }
public function view( $id = null ) { public function view( $id = null ) {
@ -153,4 +163,28 @@ class Users extends AdminController {
} }
$this->index(); $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 );
}
} }

View File

@ -0,0 +1,38 @@
<?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 )]);
}
}

View File

@ -0,0 +1,50 @@
<?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 )]);
}
}

View File

@ -30,7 +30,7 @@ class Users extends ApiController {
$response = 'No user found.'; $response = 'No user found.';
} else { } else {
$responseType = 'data'; $responseType = 'data';
$response = $user; $response = $user->ID;
} }
Views::view( 'api.response', ['response' => json_encode( [ $responseType => $response ], true )]); Views::view( 'api.response', ['response' => json_encode( [ $responseType => $response ], true )]);
} }

View File

@ -27,13 +27,13 @@ use TheTempusProject\TheTempusProject as App;
class Home extends Controller { class Home extends Controller {
public function index() { public function index() {
self::$title = '{SITENAME}'; self::$title = '{SITENAME}';
self::$pageDescription = 'This is the homepage of your new Tempus Project Installation. Thank you for installing. find more info at https://thetempusproject.com'; 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' ); Views::view( 'index' );
} }
public function login() { public function login() {
self::$title = 'Portal - {SITENAME}'; self::$title = 'Portal - {SITENAME}';
self::$pageDescription = 'Please log in to use {SITENAME} member features.'; self::$pageDescription = 'Please log in to access all of the great features {SITENAME} has to offer.';
if ( App::$isLoggedIn ) { 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.' ); return Issues::add( 'notice', 'You are already logged in. Please <a href="' . Routes::getAddress() . 'home/logout">click here</a> to log out.' );
} }
@ -69,7 +69,7 @@ class Home extends Controller {
public function profile( $id = null ) { public function profile( $id = null ) {
self::$title = 'User Profile - {SITENAME}'; self::$title = 'User Profile - {SITENAME}';
self::$pageDescription = 'User Profiles for {SITENAME}'; self::$pageDescription = 'User Profile - {SITENAME}';
if ( !App::$isLoggedIn ) { if ( !App::$isLoggedIn ) {
return Issues::add( 'notice', 'You must be logged in to view this page.' ); return Issues::add( 'notice', 'You must be logged in to view this page.' );
} }
@ -86,16 +86,25 @@ class Home extends Controller {
self::$title = 'Terms and Conditions - {SITENAME}'; self::$title = 'Terms and Conditions - {SITENAME}';
self::$pageDescription = '{SITENAME} Terms and Conditions of use. Please use {SITENAME} safely.'; self::$pageDescription = '{SITENAME} Terms and Conditions of use. Please use {SITENAME} safely.';
Components::set( 'TERMS', Views::simpleView( 'terms' ) ); Components::set( 'TERMS', Views::simpleView( 'terms' ) );
Views::raw( '<div class="terms-page">{TERMS}</div>' ); Views::view( 'termsPage' );
} }
public function hashtag( $id = null ) { public function about() {
self::$title = 'HashTag - {SITENAME}'; self::$title = 'About - {SITENAME}';
self::$pageDescription = 'HashTags for {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.';
if ( !App::$isLoggedIn ) { Views::view( 'about' );
return Issues::add( 'notice', 'You must be logged in to view this page.' ); }
}
// this should look up comments and blog posts with the hashtag in them public function privacy() {
Views::view( 'hashtags' ); 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.';
Components::set( 'PRIVACY', Views::simpleView( 'privacy' ) );
Views::raw( '<div class="col-lg-8 mx-auto">{PRIVACY}</div>' );
}
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' );
} }
} }

View File

@ -24,27 +24,34 @@ use TheTempusProject\Houdini\Classes\Views;
use TheTempusProject\TheTempusProject as App; use TheTempusProject\TheTempusProject as App;
use TheTempusProject\Classes\Controller; use TheTempusProject\Classes\Controller;
use TheTempusProject\Classes\Forms; use TheTempusProject\Classes\Forms;
use TheTempusProject\Bedrock\Classes\Config;
class Register extends Controller { class Register extends Controller {
public function confirm( $code = null ) { public function confirm( $code = null ) {
Template::noIndex();
self::$title = 'Confirm Email'; self::$title = 'Confirm Email';
if ( !isset( $code ) && !Input::exists( 'confirmationCode' ) ) { if ( !isset( $code ) && !Input::exists( 'confirmationCode' ) ) {
return Views::view( 'email.confirmation' ); return Views::view( 'confirmation' );
} }
if ( Forms::check( 'emailConfirmation' ) ) { if ( Forms::check( 'emailConfirmation' ) ) {
$code = Input::post( 'confirmationCode' ); $code = Input::post( 'confirmationCode' );
} }
if ( !self::$user->confirm( $code ) ) { if ( !self::$user->confirm( $code ) ) {
Issues::add( 'error', 'There was an error confirming your account, please try again.' ); Issues::add( 'error', 'There was an error confirming your account, please try again.' );
return Views::view( 'email.confirmation' ); return Views::view( 'confirmation' );
} }
Session::flash( 'success', 'You have successfully confirmed your email address.' ); Session::flash( 'success', 'You have successfully confirmed your email address.' );
Redirect::to( 'home/index' ); Redirect::to( 'home/index' );
} }
public function index() { public function index() {
self::$title = 'Register'; self::$title = '{SITENAME} Sign Up';
self::$pageDescription = 'Many features of the site are disabled or even hidden from unregistered users. On this page you can sign up for an account to access all the app has to offer.'; 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( 'terms' ) ); Components::set( 'TERMS', Views::simpleView( 'terms' ) );
if ( App::$isLoggedIn ) { if ( App::$isLoggedIn ) {
return Issues::add( 'notice', 'You are currently logged in.' ); return Issues::add( 'notice', 'You are currently logged in.' );
@ -94,43 +101,45 @@ class Register extends Controller {
public function resend() { public function resend() {
self::$title = 'Resend Confirmation'; self::$title = 'Resend Confirmation';
Template::noIndex();
if ( !App::$isLoggedIn ) { if ( !App::$isLoggedIn ) {
return Issues::add( 'notice', 'Please log in to resend your confirmation email.' ); return Issues::add( 'notice', 'Please log in to resend your confirmation email.' );
} }
if ( App::$activeUser->data()->confirmed == '1' ) { if ( App::$activeUser->confirmed == '1' ) {
return Issues::add( 'notice', 'Your account has already been confirmed.' ); return Issues::add( 'notice', 'Your account has already been confirmed.' );
} }
if ( !Forms::check( 'confirmationResend' ) ) { if ( !Forms::check( 'confirmationResend' ) ) {
return Views::view( 'email.confirmation_resend' ); return Views::view( 'confirmation_resend' );
} }
Email::send( App::$activeUser->data()->email, 'confirmation', App::$activeUser->data()->confirmationCode, [ 'template' => true ] ); 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.' ); Session::flash( 'success', 'Your confirmation email has been sent to the email for your account.' );
Redirect::to( 'home/index' ); Redirect::to( 'home/index' );
} }
public function reset( $code = null ) { public function reset( $code = null ) {
self::$title = 'Password Reset'; self::$title = 'Password Reset';
Template::noIndex();
if ( !isset( $code ) && !Input::exists( 'resetCode' ) ) { if ( !isset( $code ) && !Input::exists( 'resetCode' ) ) {
Issues::add( 'error', 'No reset code provided.' ); Issues::add( 'info', 'Please provide a reset code.' );
return Views::view( 'password_reset_code' ); return Views::view( 'password_reset_code' );
} }
if ( Input::exists( 'resetCode' ) ) { if ( Input::exists( 'resetCode' ) ) {
if ( Forms::check( 'password_reset_code' ) ) { if ( Forms::check( 'passwordResetCode' ) ) {
$code = Input::post( 'resetCode' ); $code = Input::post( 'resetCode' );
} }
} }
if ( !self::$user->checkCode( $code ) ) { if ( ! self::$user->checkCode( $code ) ) {
Issues::add( 'error', 'There was an error with your reset code. Please try again.' ); Issues::add( 'error', 'There was an error with your reset code. Please try again.' );
return Views::view( 'password_reset_code' ); return Views::view( 'password_reset_code' );
} }
if ( !Input::exists() ) { Components::set( 'resetCode', $code );
if ( ! Input::exists('password') ) {
return Views::view( 'password_reset' ); return Views::view( 'password_reset' );
} }
if ( !Forms::check( 'passwordReset' ) ) { if ( ! Forms::check( 'passwordReset' ) ) {
Issues::add( 'error', [ 'There was an error with your request.' => Check::userErrors() ] ); Issues::add( 'error', [ 'There was an error with your request.' => Check::userErrors() ] );
return Views::view( 'password_reset' ); return Views::view( 'password_reset' );
} }
Components::set( 'resetCode', $code );
self::$user->changePassword( $code, Input::post( 'password' ) ); self::$user->changePassword( $code, Input::post( 'password' ) );
Email::send( self::$user->data()->email, 'passwordChange', null, [ 'template' => true ] ); 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.' ); Session::flash( 'success', 'Your Password has been changed, please use your new password to log in.' );

View File

@ -36,13 +36,14 @@ class Usercp extends Controller {
Redirect::home(); Redirect::home();
} }
Template::noIndex(); Template::noIndex();
Navigation::activePageSelect( 'nav.usercp', null, true );
} }
public function email() { public function email() {
self::$title = 'Email Settings'; self::$title = 'Email Settings';
$menu = Views::simpleView( 'nav.usercp', App::$userCPlinks );
Navigation::activePageSelect( $menu, null, true, true );
if ( App::$activeUser->confirmed != '1' ) { 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="{BASE}register/resend">click here</a>', true ); 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() ) { if ( !Input::exists() ) {
return Views::view( 'user_cp.email_change' ); return Views::view( 'user_cp.email_change' );
@ -67,11 +68,15 @@ class Usercp extends Controller {
public function index() { public function index() {
self::$title = 'User Control Panel'; self::$title = 'User Control Panel';
$menu = Views::simpleView( 'nav.usercp', App::$userCPlinks );
Navigation::activePageSelect( $menu, null, true, true );
Views::view( 'profile', App::$activeUser ); Views::view( 'profile', App::$activeUser );
} }
public function password() { public function password() {
self::$title = 'Password Settings'; self::$title = 'Password Settings';
$menu = Views::simpleView( 'nav.usercp', App::$userCPlinks );
Navigation::activePageSelect( $menu, null, true, true );
if ( !Input::exists() ) { if ( !Input::exists() ) {
return Views::view( 'user_cp.password_change' ); return Views::view( 'user_cp.password_change' );
} }
@ -93,19 +98,56 @@ class Usercp extends Controller {
public function settings() { public function settings() {
self::$title = 'Preferences'; self::$title = 'Preferences';
$menu = Views::simpleView( 'nav.usercp', App::$userCPlinks );
Navigation::activePageSelect( $menu, null, true, true );
$prefs = new Preferences; $prefs = new Preferences;
$fields = App::$activePrefs; $userPrefs = App::$activePrefs;
if ( Input::exists( 'submit' ) ) { if ( Input::exists( 'submit' ) ) {
$fields = $prefs->convertFormToArray( true, false ); $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 ); self::$user->updatePrefs( $fields, App::$activeUser->ID );
Issues::add( 'success', 'Your preferences have been updated.' ); 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( 'AVATAR_SETTINGS', $fields['avatar'] );
Components::set( 'PREFERENCES_FORM', $prefs->getFormHtml( $fields ) ); Components::set( 'PREFERENCES_FORM', $prefs->getFormHtml( $fields ) );
Views::view( 'user_cp.settings', App::$activeUser ); 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 )]);
}
} }

158
app/css/main-dark.css Normal 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 {
color: #fff;
}
.context-second {
color: #1e1e1e;
}
.context-main-bg {
background-color: #2c2c2c;
}
.context-second-bg {
background-color: #1e1e1e;
}
.context-third-bg {
background-color: #3a3a3a;
}
.bg-default {
background-color: #2c2c2c;
}
hr {
color: #f5f5f5;
}
.bg-none,.bg-warning {
color: #000 !important;
}
.context-other {
color: #000;
}
.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;
}
.context-main {
color: #ffffff;
}
.context-other {
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);
}

View File

@ -8,21 +8,103 @@
* @link https://TheTempusProject.com * @link https://TheTempusProject.com
* @license https://opensource.org/licenses/MIT [MIT LICENSE] * @license https://opensource.org/licenses/MIT [MIT LICENSE]
*/ */
.context-other-bg {
background-color: #eaeaea;
}
.nav-link.active {
font-weight: bold; /* Make the text bold */
}
hr {
color: #000;
}
.context-main-bg {
background-color: #f7f7f7;
}
/* 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 */
}
.context-main {
color: #000;
}
.context-other {
color: #fff;
}
html { html {
font-family: 'Open Sans', sans-serif; font-family: 'Open Sans', sans-serif;
position: relative; position: relative;
min-height: 100%; min-height: 100%;
} }
body {
margin-top: 100px;
}
pre { pre {
white-space: pre-wrap; white-space: pre-wrap;
} }
body {
background-color: #e4e4e4;
/* background-image: linear-gradient(180deg, #eee, #fff 100px, #fff); */
}
@media ( min-width: 768px ) { @media ( min-width: 768px ) {
body {
margin-top: 75px;
}
.main { .main {
padding-right: 40px; padding-right: 40px;
padding-left: 40px; padding-left: 40px;
@ -31,546 +113,9 @@ pre {
padding-right: 225px; padding-right: 225px;
padding-left: 0; padding-left: 0;
} }
.side-nav { .bd-placeholder-img-lg {
right: 0; font-size: 3.5rem;
left: auto;
} }
.side-nav {
position: fixed;
top: 51px;
left: 225px;
width: 225px;
margin-left: -225px;
border: none;
border-radius: 0;
overflow-y: auto;
background-color: #222;
bottom: 53px;
overflow-x: hidden;
padding-bottom: 10px;
}
.side-nav>li>a {
width: 225px;
}
.side-nav li a:hover,
.side-nav li a:focus {
outline: none;
background-color: #000 !important;
}
}
/**
* Other
*/
.custom-nav {
display: relative;
float: right;
}
.navbar-form-alt {
margin-top: 10px;
margin-bottom: 10px;
}
.bars {
display: block;
width: 60px;
height: 3px;
background-color: #333;
box-shadow: 0 5px 0 #333, 0 10px 0 #333;
}
.slide-text-bg {
opacity: 0.6;
}
.avatar-125 {
height: 125px;
width: 125px;
}
.full {
width: 100%;
}
.gap {
height: 30px;
width: 100%;
clear: both;
display: block;
}
.supportLi h4 {
font-size: 20px;
font-weight: lighter;
line-height: normal;
margin-bottom: 0 !important;
padding-bottom: 0;
}
.bg-gray {
background-image: -moz-linear-gradient( center bottom, #BBBBBB 0%, #F0F0F0 100% );
box-shadow: 0 1px 0 #B4B3B3;
}
.payments {
font-size: 1.5em;
}
.UI-buffer {
padding-top: 35px;
height: auto;
border-bottom: 1px solid #CCCCCC;
}
.avatar {
max-width: 33px;
}
.UI-page-buffer {
padding-top: 30px;
position: relative;
height: auto;
border-bottom: 1px solid #CCCCCC;
}
.main {
padding: 20px;
padding-bottom: 75px;
}
.user-row {
margin-bottom: 14px;
}
.user-row:last-child {
margin-bottom: 0;
}
.dropdown-user {
margin: 13px 0;
padding: 5px;
height: 100%;
}
.dropdown-user:hover {
cursor: pointer;
}
.table-user-information>tbody>tr {
border-top: 1px solid rgb( 221, 221, 221 );
}
.table-user-information>tbody>tr:first-child {
border-top: 0;
}
.table-user-information>tbody>tr>td {
border-top: 0;
}
.top-pad {
margin-top: 70px;
}
.foot-pad {
padding-bottom: 0;
/* padding-bottom: 261px; */
}
.dynamic-footer-padding {
padding-bottom: var(--footer-height);
}
.footer-head .navbar-toggle {
display: inline-block;
float: none;
}
.avatar-round-40 {
height: 40px;
width: 40px;
}
.sticky-foot-head {
z-index: 10;
position: fixed;
bottom: 51px;
width: 100%;
background: #EDEFF1;
border-bottom: 1px solid #CCCCCC;
border-top: 1px solid #DDDDDD;
}
.sticky-foot {
background-color: #000;
position: fixed;
bottom: 0;
width: 100%;
}
.sticky-copy {
z-index: 10;
padding-top: 10px;
padding-bottom: 10px;
height: 50px;
background: #E3E3E3;
border-bottom: 1px solid #CCCCCC;
border-top: 1px solid #DDDDDD;
}
/**
* Main Carousel
*/
.main-text {
padding-bottom: 0px;
padding-top: 0px;
top: 10px;
bottom: auto;
z-index: 10;
width: auto;
color: #FFF;
}
.btn-min-block {
min-width: 170px;
line-height: 26px;
}
.btn-clear {
color: #FFF;
background-color: transparent;
border-color: #FFF;
margin-right: 15px;
}
.btn-clear:hover {
color: #000;
background-color: #FFF;
}
#carousel-example-generic {
margin-bottom: 30px;
}
.col-centered {
float: none;
margin: 0 auto;
}
/**
* Top Navigation
*/
.top-nav {
padding: 0 15px;
}
.top-nav>li {
display: inline-block;
float: left;
}
.top-nav>li>a {
padding-top: 15px;
padding-bottom: 15px;
line-height: 20px;
color: #999;
}
.top-nav>li>a:hover,
.top-nav>li>a:focus,
.top-nav>.open>a,
.top-nav>.open>a:hover,
.top-nav>.open>a:focus {
color: #fff;
background-color: #000;
}
.top-nav>.open>.dropdown-menu {
float: left;
position: absolute;
margin-top: 0;
border: 1px solid rgba( 0, 0, 0, .15 );
border-top-left-radius: 0;
border-top-right-radius: 0;
background-color: #fff;
-webkit-box-shadow: 0 6px 12px rgba( 0, 0, 0, .175 );
box-shadow: 0 6px 12px rgba( 0, 0, 0, .175 );
}
.top-nav>.open>.dropdown-menu>li>a {
white-space: normal;
}
/**
* Messages Dropdown
*/
ul.message-dropdown {
padding: 0;
max-height: 250px;
overflow-x: hidden;
overflow-y: auto;
}
li.message-header {
margin: 5px 0;
border-bottom: 1px solid rgba( 0, 0, 0, .15 );
}
li.message-preview {
width: 275px;
border-bottom: 1px solid rgba( 0, 0, 0, .15 );
}
li.message-preview>a {
padding-top: 15px;
padding-bottom: 15px;
}
li.message-footer {
margin: 5px 0;
}
ul.alert-dropdown {
width: 200px;
}
/**
* Widget
*/
.widget .list-group {
margin-bottom: 0;
}
.widget .panel-title {
display: inline
}
.widget .label {
float: right;
}
.widget li.list-group-item {
border-radius: 0;
border: 0;
border-top: 1px solid #ddd;
}
.widget li.list-group-item:hover {
background-color: rgba( 86, 61, 124, .1 );
}
.widget .mic-info {
color: #666666;
font-size: 11px;
}
.widget .action {
margin-top: 5px;
}
.widget .comment-text {
font-size: 12px;
}
.widget .btn-block {
border-top-left-radius: 0px;
border-top-right-radius: 0px;
}
/**
* Signin Form
*/
.form-signin {
max-width: 330px;
padding: 15px;
margin: 0 auto;
}
.form-signin .form-signin-heading,
.form-signin .checkbox {
margin-bottom: 10px;
}
.form-signin .checkbox {
font-weight: normal;
}
.form-signin .form-control {
position: relative;
height: auto;
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
padding: 10px;
font-size: 16px;
}
.form-signin .form-control:focus {
z-index: 2;
}
.form-signin input[type="text"] {
margin-bottom: -1px;
border-bottom-right-radius: 0;
border-bottom-left-radius: 0;
}
.form-signin input[type="password"] {
margin-bottom: 10px;
border-top-left-radius: 0;
border-top-right-radius: 0;
}
/**
* Footer and Copyright
*/
.copy {
z-index: 10;
padding-top: 10px;
padding-bottom: 10px;
position: absolute;
bottom: 0;
width: 100%;
height: 50px;
background: #E3E3E3;
border-bottom: 1px solid #CCCCCC;
border-top: 1px solid #DDDDDD;
}
.footer-head {
z-index: 10;
position: absolute;
bottom: 51px;
width: 100%;
background: #EDEFF1;
border-bottom: 1px solid #CCCCCC;
border-top: 1px solid #DDDDDD;
}
.footer-head p {
margin: 0;
}
.footer-head img {
max-width: 100%;
}
.footer-head h3 {
border-bottom: 1px solid #BAC1C8;
color: #54697E;
font-size: 18px;
font-weight: 600;
line-height: 27px;
padding: 5px 0 10px;
text-transform: uppercase;
}
.footer-head ul {
font-size: 13px;
list-style-type: none;
margin-left: 0;
padding-left: 0;
margin-top: 15px;
color: #7F8C8D;
}
.footer-head ul li a {
padding: 0 0 5px 0;
display: inline-block;
}
.footer-head a {
color: #78828D
}
/**
* Side Navigation
*/
.side-nav>li>ul {
padding: 0;
}
.side-nav>li>ul>li>a {
display: block;
padding: 10px 15px 10px 38px;
text-decoration: none;
color: #999;
}
.side-nav>li>ul>li>a:hover {
color: #fff;
}
.side-nav .active > a {
color: #fff;
background-color: #080808;
}
.side-nav .active > a:hover {
color: #fff;
background-color: #080808;
}
/**
* Social
*/
.social {
margin-top: 75px;
bottom: 0;
}
.content {
position: absolute;
}
.social span {
background: none repeat scroll 0 0 #B5B5B5;
border: 2px solid #B5B5B5;
-webkit-border-radius: 50%;
-moz-border-radius: 50%;
-o-border-radius: 50%;
-ms-border-radius: 50%;
border-radius: 50%;
float: center;
height: 36px;
line-height: 36px;
margin: 0 8px 0 0;
padding: 0;
text-align: center;
width: 41px;
transition: all 0.5s ease 0s;
-moz-transition: all 0.5s ease 0s;
-webkit-transition: all 0.5s ease 0s;
-ms-transition: all 0.5s ease 0s;
-o-transition: all 0.5s ease 0s;
}
.social span:hover {
transform: scale( 1.15 ) rotate( 360deg) ;
-webkit-transform: scale( 1.1 ) rotate( 360deg) ;
-moz-transform: scale( 1.1 ) rotate( 360deg) ;
-ms-transform: scale( 1.1 ) rotate( 360deg) ;
-o-transform: scale( 1.1 ) rotate( 360deg) ;
}
.social span a {
color: #EDEFF1;
}
.social span:hover {
border: 2px solid #2c3e50;
background: #2c3e50;
}
.social span a i {
font-size: 16px;
margin: 0 0 0 5px;
color: #EDEFF1 !important;
}
/**
* Newsletter Box
*/
.newsletter-box input#appendedInputButton {
background: #FFFFFF;
display: inline-block;
float: center;
height: 30px;
clear: both;
width: 100%;
}
.newsletter-box .btn {
border: medium none;
-webkit-border-radius: 3px;
-moz-border-radius: 3px;
-o-border-radius: 3px;
-ms-border-radius: 3px;
border-radius: 3px;
display: inline-block;
height: 40px;
padding: 0;
width: 100%;
color: #fff;
}
.newsletter-box {
overflow: hidden;
}
/**
* Colored Badges
*/
.badge {
padding: 1px 9px 2px;
font-size: 12.025px;
font-weight: bold;
white-space: nowrap;
color: #ffffff;
background-color: #999999;
-webkit-border-radius: 9px;
-moz-border-radius: 9px;
border-radius: 9px;
}
.badge:hover {
color: #ffffff;
text-decoration: none;
cursor: pointer;
}
.badge-error {
background-color: #b94a48;
}
.badge-error:hover {
background-color: #953b39;
}
.badge-warning {
background-color: #f89406;
}
.badge-warning:hover {
background-color: #c67605;
}
.badge-success {
background-color: #468847;
}
.badge-success:hover {
background-color: #356635;
}
.badge-info {
background-color: #3a87ad;
}
.badge-info:hover {
background-color: #2d6987;
}
.badge-inverse {
background-color: #333333;
}
.badge-inverse:hover {
background-color: #1a1a1a;
} }
/** /**
@ -658,12 +203,119 @@ ul.alert-dropdown {
.terms strong { .terms strong {
color: #000; color: #000;
} }
.pricing-header {
max-width: 700px;
.navbar-header { }
margin-right: 75px; .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;
} }
.pagination { .gradient-custom-2 {
padding-left: 75px; /* 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);
} }

View File

@ -89,3 +89,31 @@ function iv( $variable ) {
echo var_export( $variable, true ); echo var_export( $variable, true );
echo '</pre>'; 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));
}

View File

@ -1,542 +0,0 @@
<?php
/**
* app/functions/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;
use TheTempusProject\Bedrock\Functions\Input;
use TheTempusProject\Bedrock\Functions\Check;
use TheTempusProject\Models\User;
use TheTempusProject\Classes\Forms;
use TheTempusProject\Bedrock\Classes\Database;
class TTPForms extends Forms {
/**
* Adds these functions to the form list.
*/
public function __construct() {
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( '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' ] );
}
/**
* 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 ( !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;
}
}
new TTPForms;

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

After

Width:  |  Height:  |  Size: 45 KiB

View File

@ -27,15 +27,6 @@ function checkAll(ele) {
} }
} }
function copyAll( ele ) {
var eleName = '#' + ele;
var text = $( eleName ).text();
text = text.replaceAll( "''", "\n" ).trim();
text = text.substring( 1, text.length - 1 );
navigator.clipboard.writeText( text );
console.log( '#' + ele );
}
function insertTag( box, tag ) { function insertTag( box, tag ) {
var Field = document.getElementById( box ); var Field = document.getElementById( box );
var currentPos = cursorPos( Field ); var currentPos = cursorPos( Field );
@ -69,6 +60,26 @@ function getRandomInt(min, max) {
return Math.floor(Math.random() * (maxFloored - minCeiled) + minCeiled); 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() { $(document).ready(function() {
$('select').each(function() { $('select').each(function() {
var selectedValue = $(this).attr('value'); var selectedValue = $(this).attr('value');
@ -83,29 +94,96 @@ $(document).ready(function() {
}); });
}); });
// with the dynamic footer, you need to adjust the content padding to make sure the footer doesn't overlap the content document.addEventListener('DOMContentLoaded', function () {
window.onload = function () { const ttpDarkmode = document.getElementById('dark-mode-pref');
function updateFooterPadding() { const toggleButton = document.getElementById('dark-mode-toggle');
var footer = document.querySelector('footer'); const enableButton = document.getElementById('dark-mode-toggle-button');
var container = document.querySelector('.container-fluid.top-pad'); const darkModeStylesheet = document.getElementById('dark-mode-stylesheet');
if ( ! container ) { let currentState = '';
return;
// Check if dark mode is set by ttp
if ( ttpDarkmode ) {
if ( 'true' == ttpDarkmode.value ) {
currentState = 'enabled';
}
if ( 'false' == ttpDarkmode.value ) {
currentState = 'disabled';
} }
// footer has no height but its children do!
var footerHeight = Array.from(footer.children).reduce((totalHeight, child) => {
return totalHeight + child.offsetHeight;
}, 0);
footerHeight += 20; // Add 20px for padding
// console.error(footerHeight);
container.style.setProperty('--footer-height', footerHeight + 'px');
} }
// Update padding on initial load // Check if dark mode is set in localStorage
updateFooterPadding(); if ( '' == currentState ) {
if ( localStorage.getItem('darkMode') === 'enabled' ) {
currentState = 'enabled';
}
}
// Update padding on window resize // Update current button states
window.addEventListener('resize', updateFooterPadding); 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!');
});
}
});

View File

@ -31,7 +31,7 @@ class Group extends DatabaseModel {
'defaultGroup' => [ 'defaultGroup' => [
'type' => 'customSelect', 'type' => 'customSelect',
'pretty' => 'The Default Group for new registrations.', 'pretty' => 'The Default Group for new registrations.',
'default' => 5, 'default' => 4,
], ],
]; ];
public $databaseMatrix = [ public $databaseMatrix = [

View File

@ -13,7 +13,7 @@ namespace TheTempusProject\Models;
use TheTempusProject\Bedrock\Functions\Check; use TheTempusProject\Bedrock\Functions\Check;
use TheTempusProject\Bedrock\Classes\Config; use TheTempusProject\Bedrock\Classes\Config;
use TheTempusProject\Bedrock\Classes\CustomException; use TheTempusProject\Canary\Classes\CustomException;
use TheTempusProject\TheTempusProject as App; use TheTempusProject\TheTempusProject as App;
use TheTempusProject\Classes\DatabaseModel; use TheTempusProject\Classes\DatabaseModel;
use TheTempusProject\Canary\Bin\Canary as Debug; use TheTempusProject\Canary\Bin\Canary as Debug;
@ -87,7 +87,7 @@ class Log extends DatabaseModel {
} }
public function list( $filter = null ) { public function list( $filter = null ) {
$logData = self::$db->getPaginated( $this->tableName, [ 'source', '=', $filter ] ); $logData = self::$db->get( $this->tableName, [ 'source', '=', $filter ] );
if ( !$logData->count() ) { if ( !$logData->count() ) {
return false; return false;
} }

View File

@ -72,7 +72,7 @@ class Routes extends DatabaseModel {
return false; return false;
} }
if ( !Check::simpleName( $nickname ) ) { if ( !Check::simpleName( $nickname ) ) {
Debug::warn( 'Invalid route nickname: ' . $name ); Debug::warn( 'Invalid route nickname: ' . $nickname );
return false; return false;
} }
if ( 'external' == $type && !Check::url( $forwarded_url ) ) { if ( 'external' == $type && !Check::url( $forwarded_url ) ) {
@ -128,7 +128,7 @@ class Routes extends DatabaseModel {
} }
$routeData = self::$db->get( $this->tableName, [ 'nickname', '=', $name ] ); $routeData = self::$db->get( $this->tableName, [ 'nickname', '=', $name ] );
if ( !$routeData->count() ) { if ( !$routeData->count() ) {
Debug::warn( "Could not find a group named: $name" ); Debug::info( "Routes:findByName: Could not find a route named: $name" );
return false; return false;
} }
return $this->filter( $routeData->first() ); return $this->filter( $routeData->first() );
@ -137,7 +137,7 @@ class Routes extends DatabaseModel {
public function findByOriginalUrl( $url ) { public function findByOriginalUrl( $url ) {
$routeData = self::$db->get( $this->tableName, [ 'original_url', '=', $url ] ); $routeData = self::$db->get( $this->tableName, [ 'original_url', '=', $url ] );
if ( !$routeData->count() ) { if ( !$routeData->count() ) {
Debug::warn( "Could not find route by original url: $url" ); Debug::info( "Routes:findByOriginalUrl: Could not find route by original url: $url" );
return false; return false;
} }
return $this->filter( $routeData->first() ); return $this->filter( $routeData->first() );
@ -145,12 +145,12 @@ class Routes extends DatabaseModel {
public function findByforwardedUrl( $url ) { public function findByforwardedUrl( $url ) {
if ( !Check::url( $url ) ) { if ( !Check::url( $url ) ) {
Debug::warn( "Invalid forwarded_url: $url" ); Debug::warn( "Routes:findByforwardedUrl: Invalid forwarded_url: $url" );
return false; return false;
} }
$routeData = self::$db->get( $this->tableName, [ 'forwarded_url', '=', $url ] ); $routeData = self::$db->get( $this->tableName, [ 'forwarded_url', '=', $url ] );
if ( !$routeData->count() ) { if ( !$routeData->count() ) {
Debug::warn( "Could not find route by forwarded url: $url" ); Debug::info( "Routes:findByforwardedUrl: Could not find route by forwarded url: $url" );
return false; return false;
} }
return $this->filter( $routeData->first() ); return $this->filter( $routeData->first() );

View File

@ -20,6 +20,7 @@ use TheTempusProject\Canary\Bin\Canary as Debug;
use TheTempusProject\Bedrock\Functions\Session; use TheTempusProject\Bedrock\Functions\Session;
use TheTempusProject\Bedrock\Functions\Cookie; use TheTempusProject\Bedrock\Functions\Cookie;
use TheTempusProject\Classes\DatabaseModel; use TheTempusProject\Classes\DatabaseModel;
use TheTempusProject\Classes\Config;
use TheTempusProject\TheTempusProject as App; use TheTempusProject\TheTempusProject as App;
class Sessions extends DatabaseModel { class Sessions extends DatabaseModel {
@ -56,9 +57,11 @@ class Sessions extends DatabaseModel {
$user = new User; $user = new User;
// @todo lets put this on some sort of realistic checking regime other than check everything every time // @todo lets put this on some sort of realistic checking regime other than check everything every time
if ( $sessionID == false ) { if ( $sessionID == false ) {
Debug::log( 'sessionID false' );
return false; return false;
} }
if ( !Check::id( $sessionID ) ) { if ( !Check::id( $sessionID ) ) {
Debug::log( 'sessionID not id' );
return false; return false;
} }
$data = self::$db->get( $this->tableName, [ 'ID', '=', $sessionID ] ); $data = self::$db->get( $this->tableName, [ 'ID', '=', $sessionID ] );
@ -115,12 +118,12 @@ class Sessions extends DatabaseModel {
public function checkCookie( $cookieToken, $create = false ) { public function checkCookie( $cookieToken, $create = false ) {
$user = new User; $user = new User;
if ( $cookieToken === false ) { if ( $cookieToken === false ) {
Debug::info( 'cookieToken false' );
return false; return false;
} }
$data = self::$db->get( $this->tableName, [ 'token', '=', $cookieToken ] ); $data = self::$db->get( $this->tableName, [ 'token', '=', $cookieToken ] );
if ( !$data->count() ) { if ( !$data->count() ) {
Debug::info( 'sessions->checkCookie - Session token not found.' ); Debug::info( 'sessions->checkCookie - Session token not found.' );
return false; return false;
} }
$session = $data->first(); $session = $data->first();
@ -145,22 +148,6 @@ class Sessions extends DatabaseModel {
return true; return true;
} }
public function checkToken( $apiToken, $create = false ) {
$user = new User;
if ( $apiToken === false ) {
return false;
}
$result = $user->findByToken( $apiToken );
if ( $result === false ) {
Debug::info( 'sessions->checkToken - could not find user by token.' );
return false;
}
if ( $create ) {
return $this->newSession( null, false, false, $result->ID );
}
return true;
}
/** /**
* Creates a new session from the data provided. The * Creates a new session from the data provided. The
* expiration time is optional and will be set to the * expiration time is optional and will be set to the
@ -171,9 +158,10 @@ class Sessions extends DatabaseModel {
* @return {bool} * @return {bool}
*/ */
public function newSession( $expire = null, $override = false, $remember = false, $userID = null ) { public function newSession( $expire = null, $override = false, $remember = false, $userID = null ) {
if ( ! isset( $expire ) ) { if ( empty( $expire ) ) {
// default Session Expiration is 24 hours // default Session Expiration is 24 hours
$expire = ( time() + ( 3600 * 24 ) ); $expireLimit = Config::getValue( 'main/loginTimer' );
$expire = ( time() + $expireLimit );
Debug::log( 'Using default expiration time' ); Debug::log( 'Using default expiration time' );
} }
$lastPage = App::getUrl(); $lastPage = App::getUrl();

203
app/models/token.php Normal file
View File

@ -0,0 +1,203 @@
<?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 $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();
}
}

View File

@ -21,7 +21,7 @@ use TheTempusProject\Bedrock\Functions\Hash;
use TheTempusProject\Bedrock\Functions\Session; use TheTempusProject\Bedrock\Functions\Session;
use TheTempusProject\Bedrock\Functions\Code; use TheTempusProject\Bedrock\Functions\Code;
use TheTempusProject\Bedrock\Classes\Config; use TheTempusProject\Bedrock\Classes\Config;
use TheTempusProject\Bedrock\Classes\CustomException; use TheTempusProject\Canary\Classes\CustomException;
use TheTempusProject\Classes\Email; use TheTempusProject\Classes\Email;
use TheTempusProject\Classes\DatabaseModel; use TheTempusProject\Classes\DatabaseModel;
use TheTempusProject\Classes\Preferences; use TheTempusProject\Classes\Preferences;
@ -43,7 +43,6 @@ class User extends DatabaseModel {
[ 'name', 'varchar', '20' ], [ 'name', 'varchar', '20' ],
[ 'confirmationCode', 'varchar', '80' ], [ 'confirmationCode', 'varchar', '80' ],
[ 'prefs', 'text', '' ], [ 'prefs', 'text', '' ],
[ 'auth_token', 'text', '' ],
]; ];
public $permissionMatrix = [ public $permissionMatrix = [
'uploadImages' => [ 'uploadImages' => [
@ -122,6 +121,11 @@ class User extends DatabaseModel {
'50', '50',
], ],
], ],
'darkMode' => [
'pretty' => 'Enable Dark-Mode viewing',
'type' => 'checkbox',
'default' => 'false',
],
]; ];
protected static $avatars; protected static $avatars;
protected static $preferences; protected static $preferences;
@ -447,7 +451,7 @@ class User extends DatabaseModel {
*/ */
public function recent( $limit = null ) { public function recent( $limit = null ) {
if ( empty( $limit ) ) { if ( empty( $limit ) ) {
$data = self::$db->getpaginated( $this->tableName, '*' ); $data = self::$db->get( $this->tableName, '*' );
} else { } else {
$data = self::$db->get( $this->tableName, [ 'ID', '>', '0' ], 'ID', 'DESC', [ 0, $limit ] ); $data = self::$db->get( $this->tableName, [ 'ID', '>', '0' ], 'ID', 'DESC', [ 0, $limit ] );
} }
@ -682,6 +686,10 @@ class User extends DatabaseModel {
Debug::error( "User: $id not updated." ); Debug::error( "User: $id not updated." );
return false; return false;
} }
if ( $id === App::$activeUser->ID ) {
$userData = $this->get( $id );
App::$activeUser = $userData;
}
return true; return true;
} }
@ -694,33 +702,45 @@ class User extends DatabaseModel {
return $this->data; return $this->data;
} }
public function findByToken( $token ) { public function authorize( $username, $password ) {
$data = self::$db->get( $this->tableName, [ 'auth_token', '=', $token ] ); if ( !isset( self::$log ) ) {
if ( ! $data->count() ) { self::$log = new Log;
}
if ( !$this->get( $username ) ) {
self::$log->login( 0, "API: User not found: $username" );
return false; return false;
} }
return $data->first(); // login attempts protection.
} $timeLimit = ( time() - 3600 );
$limit = Config::getValue( 'main/loginLimit' );
public function addAccessToken( $id, $length = 64 ) { $user = $this->data();
if ( ! Check::id( $id ) ) { 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; return false;
} }
$fields = [ 'auth_token' => $this->generateRandomString( $length ) ]; if ( !Hash::check( $password, $user->password ) ) {
if ( !self::$db->update( $this->tableName, $id, $fields ) ) { self::$log->login( $user->ID, 'API: Wrong Password.' );
Debug::error( "User: $id not updated." );
return false; return false;
} }
return true; self::$log->login( $this->data()->ID, 'API: pass' );
} return $user;
private 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;
} }
} }

View File

@ -20,15 +20,14 @@ use TheTempusProject\Houdini\Classes\Navigation;
use TheTempusProject\Houdini\Classes\Components; use TheTempusProject\Houdini\Classes\Components;
use TheTempusProject\Classes\AdminController; use TheTempusProject\Classes\AdminController;
use TheTempusProject\Classes\Forms; use TheTempusProject\Classes\Forms;
use TheTempusProject\Plugins\Blog as BlogPlugin; use TheTempusProject\Models\Posts;
class Blog extends AdminController { class Blog extends AdminController {
public static $posts; public static $posts;
public function __construct() { public function __construct() {
parent::__construct(); parent::__construct();
$blog = new BlogPlugin; self::$posts = new Posts;
self::$posts = $blog->posts;
self::$title = 'Admin - Blog'; self::$title = 'Admin - Blog';
$view = Navigation::activePageSelect( 'nav.admin', '/admin/blog' ); $view = Navigation::activePageSelect( 'nav.admin', '/admin/blog' );
Components::set( 'ADMINNAV', $view ); Components::set( 'ADMINNAV', $view );
@ -47,7 +46,7 @@ class Blog extends AdminController {
return $this->index(); return $this->index();
} }
$result = self::$posts->newPost( Input::post( 'title' ), Input::post( 'blogPost' ), Input::post( 'submit' ) ); $result = self::$posts->newPost( Input::post( 'title' ), Input::post( 'blogPost' ), Input::post( 'slug' ), Input::post( 'submit' ) );
if ( $result ) { if ( $result ) {
Issues::add( 'success', 'Your post has been created.' ); Issues::add( 'success', 'Your post has been created.' );
return $this->index(); return $this->index();
@ -68,7 +67,7 @@ class Blog extends AdminController {
Issues::add( 'error', [ 'There was an error with your form.' => Check::userErrors() ] ); Issues::add( 'error', [ 'There was an error with your form.' => Check::userErrors() ] );
return $this->index(); return $this->index();
} }
if ( self::$posts->updatePost( $data, Input::post( 'title' ), Input::post( 'blogPost' ), Input::post( 'submit' ) ) === true ) { if ( self::$posts->updatePost( $data, Input::post( 'title' ), Input::post( 'blogPost' ), Input::post( 'slug' ), Input::post( 'submit' ) ) === true ) {
Issues::add( 'success', 'Post Updated.' ); Issues::add( 'success', 'Post Updated.' );
return $this->index(); return $this->index();
} }

View File

@ -27,17 +27,16 @@ use TheTempusProject\Plugins\Blog as BlogPlugin;
use TheTempusProject\TheTempusProject as App; use TheTempusProject\TheTempusProject as App;
use TheTempusProject\Plugins\Comments; use TheTempusProject\Plugins\Comments;
use TheTempusProject\Models\Comments as CommentsModel; use TheTempusProject\Models\Comments as CommentsModel;
use TheTempusProject\Models\Posts as PostsModel;
class Blog extends Controller { class Blog extends Controller {
protected static $blog; protected static $blog;
protected static $comments;
protected static $posts; protected static $posts;
public function __construct() { public function __construct() {
parent::__construct(); parent::__construct();
Template::setTemplate( 'blog' ); Template::setTemplate( 'blog' );
$blog = new BlogPlugin; self::$posts = new PostsModel;
self::$posts = $blog->posts;
} }
public function index() { public function index() {
@ -57,39 +56,46 @@ class Blog extends Controller {
public function comments( $sub = null, $data = null ) { public function comments( $sub = null, $data = null ) {
Debug::log( 'Controller initiated: ' . __METHOD__ . '.' ); Debug::log( 'Controller initiated: ' . __METHOD__ . '.' );
if ( empty( self::$comments ) ) {
self::$comments = new CommentsModel;
}
$plugin = new Comments;
if ( empty( $sub ) || empty( $data ) ) { if ( empty( $sub ) || empty( $data ) ) {
Session::flash( 'error', 'Whoops, try again.' ); Issues::add( 'error', 'There was an issue with your request. Please check the url and try again.' );
Redirect::to( 'blog' ); 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 ) { switch ( $sub ) {
case 'post': case 'post':
$content = self::$posts->findById( (int) $data ); $content = self::$posts->findById( (int) $data );
if ( empty( $content ) ) { if ( empty( $content ) ) {
Session::flash( 'error', 'Unknown Post.' ); Issues::add( 'error', 'Unknown Content.' );
Redirect::to( 'blog' ); return $this->index();
} }
return $plugin->formPost( self::$posts->tableName, $content, 'blog/post/' ); return $plugin->formPost( self::$posts->tableName, $content, 'blog/post/' );
return self::$comments->formPost( 'blog', $content, 'blog/post/' );
case 'edit': case 'edit':
$content = self::$comments->findById( $data ); $content = $comments->findById( $data );
if ( empty( $content ) ) { if ( empty( $content ) ) {
Session::flash( 'error', 'Unknown Comment.' ); Issues::add( 'error', 'Unknown Comment.' );
Redirect::to( 'blog' ); return $this->index();
} }
return $plugin->formEdit( self::$posts->tableName, $content, 'blog/post/' ); return $plugin->formEdit( self::$posts->tableName, $content, 'blog/post/' );
return self::$comments->formEdit( 'blog', $content, 'blog/post/' );
case 'delete': case 'delete':
$content = self::$comments->findById( $data ); $content = $comments->findById( $data );
if ( empty( $content ) ) { if ( empty( $content ) ) {
Session::flash( 'error', 'Unknown Comment.' ); Issues::add( 'error', 'Unknown Comment.' );
Redirect::to( 'blog' ); return $this->index();
} }
return $plugin->formDelete( self::$posts->tableName, $content, 'blog/post/' ); return $plugin->formDelete( self::$posts->tableName, $content, 'blog/post/' );
return self::$comments->formDelete( 'blog', $content, 'blog/post/' );
} }
} }
@ -99,28 +105,38 @@ class Blog extends Controller {
} }
$post = self::$posts->findById( $id ); $post = self::$posts->findById( $id );
if ( empty( $post ) ) { if ( empty( $post ) ) {
return $this->index(); $post = self::$posts->findBySlug( $id );
} if ( empty( $post ) ) {
if ( empty( self::$comments ) ) { return $this->index();
self::$comments = new CommentsModel; }
} }
Debug::log( 'Controller initiated: ' . __METHOD__ . '.' ); Debug::log( 'Controller initiated: ' . __METHOD__ . '.' );
self::$title = 'Blog Post'; self::$title = 'Blog Post';
// I removed this once because i didn't realize.
// this triggers the comment post controller method when the comment form is submitted on the post viewing page
if ( Input::exists( 'contentId' ) ) { if ( Input::exists( 'contentId' ) ) {
$this->comments( 'post', Input::post( 'contentId' ) ); $this->comments( 'post', Input::post( 'contentId' ) );
} }
Components::set( 'CONTENT_ID', $id ); Components::set( 'CONTENT_ID', $id );
Components::set( 'COMMENT_TYPE', 'blog' ); Components::set( 'COMMENT_TYPE', self::$posts->tableName );
if ( App::$isLoggedIn ) { Components::set( 'NEWCOMMENT', '' );
Components::set( 'NEWCOMMENT', Views::simpleView( 'comments.create' ) ); Components::set( 'count', '0' );
} else { Components::set( 'COMMENTS', '' );
Components::set( 'NEWCOMMENT', '' );
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 ) ) );
}
} }
$post = self::$posts->findById( $id );
Components::set( 'count', self::$comments->count( self::$posts->tableName, $post->ID ) );
Components::set( 'COMMENTS', Views::simpleView( 'comments.list', self::$comments->display( 10, self::$posts->tableName, $post->ID ) ) );
self::$title .= ' - ' . $post->title; self::$title .= ' - ' . $post->title;
self::$pageDescription = strip_tags( $post->contentSummaryNoLink ); self::$pageDescription = strip_tags( $post->contentSummaryNoLink );
Views::view( 'blog.post', $post ); Views::view( 'blog.post', $post );

View File

@ -17,11 +17,14 @@ use TheTempusProject\Bedrock\Functions\Check;
use TheTempusProject\Bedrock\Functions\Sanitize; use TheTempusProject\Bedrock\Functions\Sanitize;
use TheTempusProject\Classes\DatabaseModel; use TheTempusProject\Classes\DatabaseModel;
use TheTempusProject\TheTempusProject as App; use TheTempusProject\TheTempusProject as App;
use TheTempusProject\Bedrock\Classes\CustomException; use TheTempusProject\Canary\Classes\CustomException;
use TheTempusProject\Houdini\Classes\Filters; use TheTempusProject\Houdini\Classes\Filters;
use TheTempusProject\Plugins\Comments as CommentPlugin;
use TheTempusProject\Models\Comments;
class Posts extends DatabaseModel { class Posts extends DatabaseModel {
public $tableName = 'posts'; public $tableName = 'posts';
public static $comments = false;
public $databaseMatrix = [ public $databaseMatrix = [
[ 'author', 'int', '11' ], [ 'author', 'int', '11' ],
@ -29,25 +32,21 @@ class Posts extends DatabaseModel {
[ 'edited', 'int', '10' ], [ 'edited', 'int', '10' ],
[ 'draft', 'int', '1' ], [ 'draft', 'int', '1' ],
[ 'title', 'varchar', '86' ], [ 'title', 'varchar', '86' ],
[ 'slug', 'varchar', '64' ],
[ 'content', 'text', '' ], [ 'content', 'text', '' ],
]; ];
public $resourceMatrix = [
[
'title' => '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,
],
];
public function __construct() { public function __construct() {
parent::__construct(); parent::__construct();
if ( class_exists( 'TheTempusProject\Plugins\Comments' ) ) {
$comments = new CommentPlugin;
if ( $comments->checkEnabled() ) {
self::$comments = new Comments;
}
}
} }
public function newPost( $title, $post, $draft ) { public function newPost( $title, $post, $slug, $draft ) {
if ( !Check::dataTitle( $title ) ) { if ( !Check::dataTitle( $title ) ) {
Debug::info( 'modelBlog: illegal title.' ); Debug::info( 'modelBlog: illegal title.' );
@ -61,6 +60,7 @@ class Posts extends DatabaseModel {
$fields = [ $fields = [
'author' => App::$activeUser->ID, 'author' => App::$activeUser->ID,
'draft' => $draft, 'draft' => $draft,
'slug' => $slug,
'created' => time(), 'created' => time(),
'edited' => time(), 'edited' => time(),
'content' => Sanitize::rich( $post ), 'content' => Sanitize::rich( $post ),
@ -75,7 +75,7 @@ class Posts extends DatabaseModel {
return true; return true;
} }
public function updatePost( $id, $title, $content, $draft ) { public function updatePost( $id, $title, $content, $slug, $draft ) {
if ( empty( self::$log ) ) { if ( empty( self::$log ) ) {
self::$log = new Log; self::$log = new Log;
} }
@ -96,6 +96,7 @@ class Posts extends DatabaseModel {
} }
$fields = [ $fields = [
'draft' => $draft, 'draft' => $draft,
'slug' => $slug,
'edited' => time(), 'edited' => time(),
'content' => Sanitize::rich( $content ), 'content' => Sanitize::rich( $content ),
'title' => $title, 'title' => $title,
@ -133,36 +134,49 @@ class Posts extends DatabaseModel {
} }
$draft = ''; $draft = '';
$authorName = self::$user->getUsername( $instance->author ); $authorName = self::$user->getUsername( $instance->author );
$cleanPost = Sanitize::contentShort( $instance->content );
$postSpace = explode( ' ', $cleanPost ); // Summarize
$postLine = explode( "\n", $cleanPost ); if ( ! empty( $instance->slug ) ) {
// summary by words: 100 $identifier = $instance->slug;
$spaceSummary = implode( ' ', array_splice( $postSpace, 0, 100 ) ); } else {
// summary by lines: 5 $identifier = $instance->ID;
$lineSummary = implode( "\n", array_splice( $postLine, 0, 5 ) ); }
if ( strlen( $spaceSummary ) < strlen( $lineSummary ) ) {
$contentSummary = $spaceSummary; $cleanPost = Sanitize::contentShort( $instance->content );
if ( count( $postSpace, 1 ) <= 100 ) { // By Word
$contentSummaryNoLink = $contentSummary; $wordsArray = explode( ' ', $cleanPost );
$contentSummary .= '... <a href="{ROOT_URL}blog/post/' . $instance->ID . '">Read More</a>'; $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 { } else {
// @todo: need to refine this after testing
$contentSummaryNoLink = $lineSummary; $contentSummaryNoLink = $lineSummary;
$contentSummary = $lineSummary . '... <a href="{ROOT_URL}blog/post/' . $instance->ID . '">Read More</a>'; $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' ) { if ( $instance->draft != '0' ) {
$draft = ' <b>Draft</b>'; $draft = ' <b>Draft</b>';
} }
$instance->isDraft = $draft; $instance->isDraft = $draft;
$instance->authorName = $authorName; $instance->authorName = \ucfirst( $authorName );
$instance->contentSummaryNoLink = $contentSummaryNoLink; if ( self::$comments !== false ) {
$instance->contentSummary = $contentSummary; $instance->commentCount = self::$comments->count( 'blog', $instance->ID );
if ( isset( $params['stripHtml'] ) && $params['stripHtml'] === true ) { } else {
$instance->contentSummary = strip_tags( $instance->content ); $instance->commentCount = 0;
} }
$instance->content = Filters::applyOne( 'mentions.0', $instance->content, true ); $instance->content = Filters::applyOne( 'mentions.0', $instance->content, true );
$instance->content = Filters::applyOne( 'hashtags.0', $instance->content, true ); $instance->content = Filters::applyOne( 'hashtags.0', $instance->content, true );
$out[] = $instance; $out[] = $instance;
if ( !empty( $end ) ) { if ( !empty( $end ) ) {
$out = $out[0]; $out = $out[0];
@ -220,9 +234,9 @@ class Posts extends DatabaseModel {
$whereClause = '*'; $whereClause = '*';
} }
if ( empty( $limit ) ) { if ( empty( $limit ) ) {
$postData = self::$db->getPaginated( $this->tableName, $whereClause ); $postData = self::$db->get( $this->tableName, $whereClause );
} else { } else {
$postData = self::$db->getPaginated( $this->tableName, $whereClause, 'ID', 'DESC', [0, $limit] ); $postData = self::$db->get( $this->tableName, $whereClause, 'ID', 'DESC', [0, $limit] );
} }
if ( !$postData->count() ) { if ( !$postData->count() ) {
Debug::info( 'No Blog posts found.' ); Debug::info( 'No Blog posts found.' );
@ -238,7 +252,7 @@ class Posts extends DatabaseModel {
} else { } else {
$whereClause = ['draft', '=', '0']; $whereClause = ['draft', '=', '0'];
} }
$postData = self::$db->getPaginated( $this->tableName, $whereClause ); $postData = self::$db->get( $this->tableName, $whereClause );
if ( !$postData->count() ) { if ( !$postData->count() ) {
Debug::info( 'No Blog posts found.' ); Debug::info( 'No Blog posts found.' );
@ -262,7 +276,7 @@ class Posts extends DatabaseModel {
$firstDayUnix = date( 'U', strtotime( "first day of $year" ) ); $firstDayUnix = date( 'U', strtotime( "first day of $year" ) );
$lastDayUnix = date( 'U', strtotime( "last day of $year" ) ); $lastDayUnix = date( 'U', strtotime( "last day of $year" ) );
$whereClause = array_merge( $whereClause, ['created', '<=', $lastDayUnix, 'AND', 'created', '>=', $firstDayUnix] ); $whereClause = array_merge( $whereClause, ['created', '<=', $lastDayUnix, 'AND', 'created', '>=', $firstDayUnix] );
$postData = self::$db->getPaginated( $this->tableName, $whereClause ); $postData = self::$db->get( $this->tableName, $whereClause );
if ( !$postData->count() ) { if ( !$postData->count() ) {
Debug::info( 'No Blog posts found.' ); Debug::info( 'No Blog posts found.' );
@ -281,7 +295,7 @@ class Posts extends DatabaseModel {
$whereClause = ['draft', '=', '0', 'AND']; $whereClause = ['draft', '=', '0', 'AND'];
} }
$whereClause = array_merge( $whereClause, ['author' => $ID] ); $whereClause = array_merge( $whereClause, ['author' => $ID] );
$postData = self::$db->getPaginated( $this->tableName, $whereClause ); $postData = self::$db->get( $this->tableName, $whereClause );
if ( !$postData->count() ) { if ( !$postData->count() ) {
Debug::info( 'No Blog posts found.' ); Debug::info( 'No Blog posts found.' );
@ -290,6 +304,22 @@ class Posts extends DatabaseModel {
return $this->filter( $postData->results() ); 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 ) { public function byMonth( $month, $year = 0, $includeDraft = false ) {
if ( 0 === $year ) { if ( 0 === $year ) {
$year = date( 'Y' ); $year = date( 'Y' );
@ -310,7 +340,7 @@ class Posts extends DatabaseModel {
$month = date( 'F', $firstDayUnix ); $month = date( 'F', $firstDayUnix );
$lastDayUnix = date( 'U', strtotime( "last day of $month $year" ) ); $lastDayUnix = date( 'U', strtotime( "last day of $month $year" ) );
$whereClause = array_merge( $whereClause, ['created', '<=', $lastDayUnix, 'AND', 'created', '>=', $firstDayUnix] ); $whereClause = array_merge( $whereClause, ['created', '<=', $lastDayUnix, 'AND', 'created', '>=', $firstDayUnix] );
$postData = self::$db->getPaginated( $this->tableName, $whereClause ); $postData = self::$db->get( $this->tableName, $whereClause );
if ( !$postData->count() ) { if ( !$postData->count() ) {
Debug::info( 'No Blog posts found.' ); Debug::info( 'No Blog posts found.' );

View File

@ -12,12 +12,7 @@
*/ */
namespace TheTempusProject\Plugins; namespace TheTempusProject\Plugins;
use ReflectionClass;
use TheTempusProject\Classes\Installer;
use TheTempusProject\Houdini\Classes\Navigation;
use TheTempusProject\Classes\Plugin; use TheTempusProject\Classes\Plugin;
use TheTempusProject\Models\Posts;
use TheTempusProject\TheTempusProject as App;
class Blog extends Plugin { class Blog extends Plugin {
public $pluginName = 'TP Blog'; public $pluginName = 'TP Blog';
@ -28,20 +23,26 @@ class Blog extends Plugin {
public $pluginDescription = 'A simple plugin to add a blog to your installation.'; public $pluginDescription = 'A simple plugin to add a blog to your installation.';
public $admin_links = [ public $admin_links = [
[ [
'text' => '<i class="glyphicon glyphicon-text-size"></i> Blog', 'text' => '<i class="fa fa-fw fa-font"></i> Blog',
'url' => '{ROOT_URL}admin/blog', 'url' => '{ROOT_URL}admin/blog',
], ],
]; ];
public $footer_links = [ public $info_footer_links = [
[ [
'text' => 'Blog', 'text' => 'Blog',
'url' => '{ROOT_URL}blog/index', 'url' => '{ROOT_URL}blog/index',
], ],
]; ];
public $posts; public $resourceMatrix = [
'posts' => [
public function __construct( $load = false ) { [
$this->posts = new Posts; 'title' => 'Welcome',
parent::__construct( $load ); '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

@ -12,7 +12,7 @@
*/ */
namespace TheTempusProject\Templates; namespace TheTempusProject\Templates;
use TheTempusProject\Plugins\Blog; use TheTempusProject\Models\Posts;
use TheTempusProject\Houdini\Classes\Components; use TheTempusProject\Houdini\Classes\Components;
use TheTempusProject\Houdini\Classes\Navigation; use TheTempusProject\Houdini\Classes\Navigation;
use TheTempusProject\Houdini\Classes\Views; use TheTempusProject\Houdini\Classes\Views;
@ -25,10 +25,11 @@ class BlogLoader extends DefaultLoader {
* needed by this template. * needed by this template.
*/ */
public function __construct() { public function __construct() {
$blog = new Blog; $posts = new Posts;
$posts = $blog->posts;
Components::set('SIDEBAR', Views::simpleView('blog.sidebar', $posts->recent(5))); Components::set('SIDEBAR', Views::simpleView('blog.sidebar', $posts->recent(5)));
Components::set('SIDEBAR2', Views::simpleView('blog.sidebar2', $posts->archive())); Components::set('SIDEBAR2', Views::simpleView('blog.sidebar2', $posts->archive()));
Components::set('SIDEBARABOUT', Views::simpleView('blog.about'));
Components::set('BLOGFEATURES', '');
Navigation::setCrumbComponent( 'BLOG_BREADCRUMBS', Input::get( 'url' ) ); Navigation::setCrumbComponent( 'BLOG_BREADCRUMBS', Input::get( 'url' ) );
Components::set( 'BLOG_TEMPLATE_URL', Template::parse( '{ROOT_URL}app/plugins/comments/' ) ); Components::set( 'BLOG_TEMPLATE_URL', Template::parse( '{ROOT_URL}app/plugins/comments/' ) );
$this->addCss( '<link rel="stylesheet" href="{BLOG_TEMPLATE_URL}css/comments.css">' ); $this->addCss( '<link rel="stylesheet" href="{BLOG_TEMPLATE_URL}css/comments.css">' );

View File

@ -10,10 +10,10 @@
* @license https://opensource.org/licenses/MIT [MIT LICENSE] * @license https://opensource.org/licenses/MIT [MIT LICENSE]
--> -->
<head> <head>
<meta charset="utf-8"> <meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta property="og:url" content="{CURRENT_URL}"> <meta property="og:url" content="{CURRENT_URL}">
<meta name='twitter:card' content='summary' /> <meta name='twitter:card' content='summary_large_image'>
<title>{TITLE}</title> <title>{TITLE}</title>
<meta itemprop="name" content="{TITLE}"> <meta itemprop="name" content="{TITLE}">
<meta name="twitter:title" content="{TITLE}"> <meta name="twitter:title" content="{TITLE}">
@ -28,84 +28,89 @@
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="author" content="The Tempus Project"> <meta name="author" content="The Tempus Project">
{ROBOT} {ROBOT}
<link rel="alternate" hreflang="en-us" href="alternateURL">
<link rel="icon" href="{ROOT_URL}images/favicon.ico"> <link rel="icon" href="{ROOT_URL}images/favicon.ico">
<!-- Required CSS --> <!-- Required CSS -->
<link rel="stylesheet" href="{FONT_AWESOME_URL}font-awesome.min.css" crossorigin="anonymous"> <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-theme.min.css" crossorigin="anonymous">
<link rel="stylesheet" href="{BOOTSTRAP_CDN}css/bootstrap.min.css" crossorigin="anonymous"> <link rel="stylesheet" href="{BOOTSTRAP_CDN}css/bootstrap.min.css" crossorigin="anonymous">
<!-- RSS --> <!-- RSS -->
<link rel="alternate" href="{ROOT_URL}blog/rss" title="{TITLE} Feed" type="application/rss+xml" /> <link rel="alternate" href="{ROOT_URL}blog/rss" title="{TITLE} Feed" type="application/rss+xml">
<!-- Custom styles for this template --> <!-- Custom styles for this template -->
{TEMPLATE_CSS_INCLUDES} {TEMPLATE_CSS_INCLUDES}
</head> </head>
<body> <body>
<nav class="navbar navbar-inverse navbar-fixed-top" role="navigation"> <!-- Navigation -->
<!--Brand and toggle should get grouped for better mobile display --> <header class="p-3 text-bg-dark">
<div class="navbar-header"> <div class="container">
<a href="{ROOT_URL}" class="navbar-brand">{SITENAME}</a> <div class="d-flex flex-wrap align-items-center justify-content-center justify-content-lg-start">
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-ex1-collapse" style=""> <img src="{ROOT_URL}{LOGO}" class="bi me-2" width="40" height="32" role="img" aria-label="{SITENAME} Logo">
<span class="sr-only">Toggle navigation</span> <a href="/" class="d-flex align-items-center mb-2 mb-lg-0 text-white text-decoration-none">
<span class="icon-bar"></span> {SITENAME}
<span class="icon-bar"></span> </a>
<span class="icon-bar"></span>
</button>
</div>
<div class="container-fluid">
<div class="collapse navbar-collapse navbar-ex1-collapse">
{topNavLeft} {topNavLeft}
<div class="navbar-right"> <div class="text-end d-flex align-items-center">
<ul class="nav navbar-nav"> {topNavRight}
{topNavRight} </div>
</ul> </div>
</div> </div>
</div> </header>
</div>
</nav>
<div class="container-fluid">
<div class="foot-pad">
<div class="d-flex flex-column min-vh-100">
<div class="flex-container flex-grow-1">
{ISSUES} {ISSUES}
<div class="row"> <div class="container pt-4">
<div class="container"> <div class="row">
{ERROR} {ERROR}
{NOTICE} {NOTICE}
{SUCCESS} {SUCCESS}
{INFO}
</div> </div>
</div> </div>
{/ISSUES} {/ISSUES}
<div class="row">
<!-- Leading Content -->
<div class="container">
{BLOGFEATURES}
</div>
<div class="pt-4">
<div class="container"> <div class="container">
<div class="page-header"> <h3 class="pb-4 mb-4 fst-italic border-bottom">
<h1 class="blog-title">{SITENAME} Blog</h1> {SITENAME} Blog
</div> </h3>
<div class="row"> <div class="row g-5">
<div class="col-sm-8 blog-main"> <!-- Main Content -->
{BLOG_BREADCRUMBS} <div class="col-md-8">
{CONTENT} {CONTENT}
</div> </div>
<!-- /.blog-main --> <!-- Sidebar Content -->
<div class="col-sm-3 col-sm-offset-1 blog-sidebar"> <div class="col-md-4">
<div class="sidebar-module"> <div class="position-sticky" style="top: 2rem;">
{SIDEBAR} <div class="p-4">
</div> {SIDEBARABOUT}
<div class="sidebar-module"> </div>
{SIDEBAR2} <div class="p-4">
{SIDEBAR}
</div>
<div class="p-4">
{SIDEBAR2}
</div>
</div> </div>
</div> </div>
<!-- /.blog-sidebar -->
</div> </div>
<!-- /.row -->
</div> </div>
</div> </div>
</div> </div>
</div>
<footer>
{FOOT} {FOOT}
{COPY} </div>
</footer>
<!-- Bootstrap core JavaScript and jquery --> <!-- Bootstrap core JavaScript and jquery -->
<script src="{JQUERY_CDN}jquery.min.js" crossorigin="anonymous"></script> <script language="JavaScript" crossorigin="anonymous" type="text/javascript" src="{JQUERY_CDN}jquery.min.js"></script>
<script src="{BOOTSTRAP_CDN}js/bootstrap.min.js" crossorigin="anonymous"></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 --> <!-- Custom javascript for this template -->
{TEMPLATE_JS_INCLUDES} {TEMPLATE_JS_INCLUDES}
</body> </body>

View File

@ -0,0 +1,6 @@
<div class="p-4 mb-3 rounded context-main-bg">
<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>

View File

@ -1,31 +1,55 @@
<form action="" method="post" class="form-horizontal" enctype="multipart/form-data"> <div class="context-main-bg context-main p-3">
<legend>New Blog Post</legend> <legend class="text-center">Add Blog Post</legend>
<div class="form-group"> <hr>
<label for="title" class="col-lg-3 control-label">Title</label> {ADMIN_BREADCRUMBS}
<div class="col-lg-3"> <form action="" method="post">
<input type="text" class="form-check-input" name="title" id="title"> <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> </div>
</div> </form>
<div class="form-group"> </div>
<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"></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="saveDraft" 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

@ -1,5 +1,5 @@
<legend>New Posts</legend> <legend>New Posts</legend>
<table class="table table-striped"> <table class="table context-main">
<thead> <thead>
<tr> <tr>
<th style="width: 20%"></th> <th style="width: 20%"></th>
@ -14,9 +14,9 @@
<tr> <tr>
<td>{title}</td> <td>{title}</td>
<td>{contentSummary}</td> <td>{contentSummary}</td>
<td><a href="{ROOT_URL}admin/blog/view/{ID}" class="btn btn-sm btn-primary" role="button"><i class="glyphicon glyphicon-open"></i></a></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" role="button"><i class="glyphicon glyphicon-edit"></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" role="button"><i class="glyphicon glyphicon-trash"></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> </tr>
{/LOOP} {/LOOP}
{ALT} {ALT}

View File

@ -1,31 +1,55 @@
<form action="" method="post" class="form-horizontal" enctype="multipart/form-data"> <div class="context-main-bg context-main p-3">
<legend>Edit Blog Post</legend> <legend class="text-center">Edit Blog Post</legend>
<div class="form-group"> <hr>
<label for="title" class="col-lg-3 control-label">Title</label> {ADMIN_BREADCRUMBS}
<div class="col-lg-3"> <form action="" method="post">
<input type="text" class="form-check-input" name="title" id="title" value="{title}"> <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> </div>
</div> </form>
<div class="form-group"> </div>
<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="saveDraft" 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

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

@ -1,11 +1,14 @@
<div class="row"> <div class="context-main-bg context-main p-3">
<div class="col-sm-8 blog-main"> <legend class="text-center">Blog Post: {title}</legend>
<div class="page-header"> <hr>
<h1>{title} <small>{DTC}{created}{/DTC} by <a href="{ROOT_URL}admin/users/view/{author}">{authorName}</a></small></h1> {ADMIN_BREADCRUMBS}
</div> <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> <div class="well">{content}</div>
<a href="{ROOT_URL}admin/blog/delete/{ID}" class="btn btn-md btn-danger" role="button">Delete</a> {ADMIN}
<a href="{ROOT_URL}admin/blog/edit/{ID}" class="btn btn-md btn-warning" role="button">Edit</a> <hr>
<a href="{ROOT_URL}admin/comments/blog/{ID}" class="btn btn-md btn-primary" role="button">View Comments</a> <a href="{ROOT_URL}admin/blog/delete/{ID}" class="btn btn-md btn-danger"><i class="fa fa-fw fa-trash"></i></a>
</div><!-- /.blog-main --> <a href="{ROOT_URL}admin/blog/edit/{ID}" class="btn btn-md btn-warning"><i class="fa fa-fw fa-pencil"></i></a>
</div><!-- /.row --> <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

@ -1,18 +1,15 @@
{PAGINATION}
{LOOP} {LOOP}
<div class="blog-post"> <article class="blog-post">
<h2 class="blog-post-title"><a href="{ROOT_URL}blog/post/{ID}">{title}</a></h2> <h2 class="blog-post-title mb-1">{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>
<div class="well"> <div class="well">
<p class="blog-post-meta">
Posted on <i>{DTC date}{created}{/DTC}</i> by <a href="{ROOT_URL}home/profile/{author}"><strong>{authorName}</strong></a>
</p>
{contentSummary} {contentSummary}
</div> </div>
</div> </article>
<hr>
{/LOOP} {/LOOP}
{ALT} {ALT}
<div class="blog-post"> <article class="blog-post">
<p class="blog-post-meta">No Posts Found.</p> <p class="blog-post-meta">No Posts Found.</p>
</div> </article>
{/ALT} {/ALT}

View File

@ -3,12 +3,12 @@
<div class="blog-post"> <div class="blog-post">
<h2 class="blog-post-title">{title}</h2> <h2 class="blog-post-title">{title}</h2>
<hr> <hr>
<p class="blog-post-meta">{DTC date}{created}{/DTC} by <a href="{ROOT_URL}home/profile/{author}">{authorName}</a></p> <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} {content}
{ADMIN} {ADMIN}
<hr> <hr>
<a href="{ROOT_URL}admin/blog/delete/{ID}" class="btn btn-md btn-danger" role="button">Delete</a> <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" role="button">Edit</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> <hr>
{/ADMIN} {/ADMIN}
</div><!-- /.blog-post --> </div><!-- /.blog-post -->

View File

@ -1,6 +1,6 @@
<div class="panel panel-info"> <div class="card">
<div class="panel-heading"> <div class="card-header bg-info">
<h3 class="panel-title">Recent Posts</h3> <h3 class="card-title">Recent Posts</h3>
</div> </div>
<ul class="list-group"> <ul class="list-group">
{LOOP} {LOOP}

View File

@ -1,18 +1,18 @@
<div class="panel panel-info"> <div class="card context-main-bg">
<div class="panel-heading"> <div class="card-header">
<h3 class="panel-title">Recent Posts</h3> <h3 class="card-title">Recent Posts</h3>
</div> </div>
<div class="panel-body"> <div class="card-body">
<ol class="list-unstyled"> <ol class="list-unstyled">
{LOOP} {LOOP}
<li><a href="{ROOT_URL}blog/post/{ID}">{title}</a></li> <li><a href="{ROOT_URL}blog/post/{ID}" class="text-decoration-none">{title}</a></li>
{/LOOP} {/LOOP}
{ALT} {ALT}
<li>No Posts to show</li> <li>No Posts to show</li>
{/ALT} {/ALT}
</ol> </ol>
</div> </div>
<div class="panel-footer"> <div class="card-footer">
<a href="{ROOT_URL}blog">View All</a> <a href="{ROOT_URL}blog" class="text-decoration-none">View All</a>
</div> </div>
</div> </div>

View File

@ -1,13 +1,14 @@
<div class="panel panel-info"> <div class="card context-main-bg">
<div class="panel-heading"> <div class="card-header">
<h3 class="panel-title">Archives</h3> <h3 class="card-title">Archives</h3>
</div> </div>
<div class="panel-body"> <div class="card-body">
<ol class="list-unstyled"> <ol class="list-unstyled">
{LOOP} {LOOP}
<li>({count}) <a href="{ROOT_URL}blog/month/{month}/{year}">{monthText} {year}</a></li> <li>({count}) <a href="{ROOT_URL}blog/month/{month}/{year}" class="text-decoration-none">{monthText} {year}</a></li>
{/LOOP} {/LOOP}
{ALT} {ALT}
<li>None To Show</li>
{/ALT} {/ALT}
</ol> </ol>
</div> </div>

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

@ -1,402 +0,0 @@
<?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\Bookmarks as Bookmark;
use TheTempusProject\Models\Folders;
use TheTempusProject\TheTempusProject as App;
use TheTempusProject\Houdini\Classes\Components;
use TheTempusProject\Houdini\Classes\Forms as HoudiniForms;
use TheTempusProject\Houdini\Classes\Navigation;
class Bookmarks extends Controller {
protected static $bookmarks;
protected static $folders;
public function __construct() {
parent::__construct();
if ( !App::$isLoggedIn ) {
Session::flash( 'notice', 'You must be logged in to create or manage bookmarks.' );
return Redirect::home();
}
self::$bookmarks = new Bookmark;
self::$folders = new Folders;
self::$title = 'Bookmarks - {SITENAME}';
self::$pageDescription = 'Add and save url bookmarks here.';
$folderTabs = Views::simpleView( 'bookmarks.nav.folderTabs' );
if ( stripos( Input::get('url'), 'bookmarks/bookmarks' ) !== false ) {
$tabsView = Navigation::activePageSelect( $folderTabs, '/bookmarks/folders/', false, true );
$userFolderTabs = Views::simpleView('bookmarks.nav.userFolderTabs', self::$folders->simpleObjectByUser(true) );
$userFolderTabsView = Navigation::activePageSelect( $userFolderTabs, Input::get( 'url' ), false, true );
} else {
$tabsView = Navigation::activePageSelect( $folderTabs, Input::get( 'url' ), false, true );
$userFolderTabsView = '';
}
Components::set( 'userFolderTabs', $userFolderTabsView );
Views::raw( $tabsView );
}
public function index() {
$bookmarks = self::$bookmarks->noFolder();
$folders = self::$folders->byUser();
$panelArray = [];
if ( !empty( $folders ) ) {
foreach ( $folders as $folder ) {
$panel = new \stdClass();
$folderObject = new \stdClass();
$folderObject->bookmarks = self::$bookmarks->byFolder( $folder->ID );
$folderObject->ID = $folder->ID;
$folderObject->title = $folder->title;
$folderObject->color = $folder->color;
$folderObject->bookmarkListRows = Views::simpleView( 'bookmarks.components.bookmarkListRows', $folderObject->bookmarks );
$panel->panel = Views::simpleView( 'bookmarks.components.bookmarkListPanel', [$folderObject] );
$panelArray[] = $panel;
}
}
Components::set( 'foldersList', Views::simpleView( 'bookmarks.folders.list', $folders ) );
Components::set( 'folderPanels', Views::simpleView( 'bookmarks.components.folderPanelList', $panelArray ) );
Components::set( 'bookmarksList', Views::simpleView( 'bookmarks.bookmarks.list', $bookmarks ) );
return Views::view( 'bookmarks.dash' );
}
/**
* Bookmarks
*/
public function bookmark( $id = 0 ) {
$bookmark = self::$bookmarks->findById( $id );
if ( $bookmark == false ) {
Session::flash( 'error', 'Bookmark not found.' );
return Redirect::to( 'bookmarks/index' );
}
if ( $bookmark->createdBy != App::$activeUser->ID ) {
Session::flash( 'error', 'You do not have permission to modify this bookmark.' );
return Redirect::to( 'bookmarks/index' );
}
Navigation::setCrumbComponent( 'BookmarkBreadCrumbs', 'bookmarks/bookmark/' . $id );
return Views::view( 'bookmarks.bookmarks.view', $bookmark );
}
public function bookmarks( $id = null ) {
$folder = self::$folders->findById( $id );
if ( $folder == false ) {
Session::flash( 'error', 'Folder not found.' );
return Redirect::to( 'bookmarks/index' );
}
if ( $folder->createdBy != App::$activeUser->ID ) {
Session::flash( 'error', 'You do not have permission to view this folder.' );
return Redirect::to( 'bookmarks/index' );
}
Navigation::setCrumbComponent( 'BookmarkBreadCrumbs', 'bookmarks/bookmarks/' . $id );
$bookmarks = self::$bookmarks->noFolder();
$panelArray = [];
$panel = new \stdClass();
$folderObject = new \stdClass();
$folderObject->bookmarks = self::$bookmarks->byFolder( $folder->ID );
$folderObject->ID = $folder->ID;
$folderObject->title = $folder->title;
$folderObject->color = $folder->color;
$folderObject->bookmarkListRows = Views::simpleView( 'bookmarks.components.bookmarkListRows', $folderObject->bookmarks );
$panel->panel = Views::simpleView( 'bookmarks.components.bookmarkListPanel', [$folderObject] );
$panelArray[] = $panel;
return Views::view( 'bookmarks.components.folderPanelList', $panelArray );
}
public function createBookmark( $id = null ) {
$folderID = Input::get('folder_id') ? Input::get('folder_id') : $id;
$folderID = Input::post('folder_id') ? Input::post('folder_id') : $id;
$folderSelect = HoudiniForms::getFormFieldHtml( 'folder_id', 'Folder', 'select', $folderID, self::$folders->simpleByUser() );
Components::set( 'folderSelect', $folderSelect );
if ( ! Input::exists() ) {
return Views::view( 'bookmarks.bookmarks.create' );
}
if ( ! Forms::check( 'createBookmark' ) ) {
Issues::add( 'error', [ 'There was an error with your form.' => Check::userErrors() ] );
return Views::view( 'bookmarks.bookmarks.create' );
}
$result = self::$bookmarks->create(
Input::post('title'),
Input::post('url'),
$folderID,
Input::post('description'),
Input::post('color'),
Input::post('privacy'),
);
if ( ! $result ) {
Issues::add( 'error', [ 'There was an error creating your bookmark.' => Check::userErrors() ] );
return Views::view( 'bookmarks.bookmarks.create' );
}
self::$bookmarks->refreshInfo( $result );
Session::flash( 'success', 'Your Bookmark has been created.' );
Redirect::to( 'bookmarks/bookmarks/'. $folderID );
}
public function editBookmark( $id = null ) {
$folderID = Input::exists('folder_id') ? Input::post('folder_id') : '';
$bookmark = self::$bookmarks->findById( $id );
if ( $bookmark == false ) {
Issues::add( 'error', 'Bookmark not found.' );
return Redirect::to( 'bookmarks/index' );
}
if ( $bookmark->createdBy != App::$activeUser->ID ) {
Issues::add( 'error', 'You do not have permission to modify this bookmark.' );
return Redirect::to( 'bookmarks/index' );
}
if ( empty( $folderID ) ) {
$folderID = $bookmark->folderID;
}
$folderSelect = HoudiniForms::getFormFieldHtml( 'folder_id', 'Folder', 'select', $folderID, self::$folders->simpleByUser() );
Components::set( 'folderSelect', $folderSelect );
Components::set( 'color', $bookmark->color );
if ( ! Input::exists( 'submit' ) ) {
return Views::view( 'bookmarks.bookmarks.edit', $bookmark );
}
if ( ! Forms::check( 'editBookmark' ) ) {
Issues::add( 'error', [ 'There was an error updating your bookmark.' => Check::userErrors() ] );
return Views::view( 'bookmarks.bookmarks.edit', $bookmark );
}
$result = self::$bookmarks->update(
$id,
Input::post('title'),
Input::post('url'),
$folderID,
Input::post('description'),
Input::post('color'),
Input::post('privacy'),
);
if ( ! $result ) {
Issues::add( 'error', [ 'There was an error updating your bookmark.' => Check::userErrors() ] );
return Views::view( 'bookmarks.bookmarks.edit', $bookmark );
}
Session::flash( 'success', 'Your Bookmark has been updated.' );
Redirect::to( 'bookmarks/folders/'. $bookmark->folderID );
}
public function deleteBookmark( $id = null ) {
$bookmark = self::$bookmarks->findById( $id );
if ( $bookmark == false ) {
Issues::add( 'error', 'Bookmark not found.' );
return $this->index();
}
if ( $bookmark->createdBy != App::$activeUser->ID ) {
Issues::add( 'error', 'You do not have permission to modify this bookmark.' );
return $this->index();
}
$result = self::$bookmarks->delete( $id );
if ( !$result ) {
Session::flash( 'error', 'There was an error deleting the bookmark(s)' );
} else {
Session::flash( 'success', 'Bookmark deleted' );
}
Redirect::to( 'bookmarks/folders/'. $bookmark->folderID );
}
/**
* Folders
*/
public function folders( $id = null) {
$folder = self::$folders->findById( $id );
if ( $folder == false ) {
$folders = self::$folders->byUser();
return Views::view( 'bookmarks.folders.list', $folders );
}
if ( $folder->createdBy != App::$activeUser->ID ) {
Session::flash( 'error', 'You do not have permission to view this folder.' );
return Redirect::to( 'bookmarks/index' );
}
Navigation::setCrumbComponent( 'BookmarkBreadCrumbs', 'bookmarks/folders/' . $id );
return Views::view( 'bookmarks.folders.view', $folder );
}
public function createFolder( $id = 0 ) {
$folderID = Input::exists('folder_id') ? Input::post('folder_id') : $id;
$folders = self::$folders->simpleByUser();
if ( ! empty( $folders ) ) {
$folderSelect = HoudiniForms::getFormFieldHtml( 'folder_id', 'Folder', 'select', $folderID, $folders );
} else {
$folderSelect = '';
}
Components::set( 'folderSelect', $folderSelect );
if ( ! Input::exists() ) {
return Views::view( 'bookmarks.folders.create' );
}
if ( ! Forms::check( 'createFolder' ) ) {
Issues::add( 'error', [ 'There was an error creating your folder.' => Check::userErrors() ] );
return Views::view( 'bookmarks.folders.create' );
}
$folder = self::$folders->create( Input::post('title'), $folderID, Input::post('description'), Input::post('color'), Input::post('privacy') );
if ( ! $folder ) {
return Views::view( 'bookmarks.folders.create' );
}
Session::flash( 'success', 'Your Folder has been created.' );
Redirect::to( 'bookmarks/folders' );
}
public function editFolder( $id = null ) {
$folder = self::$folders->findById( $id );
if ( $folder == false ) {
Issues::add( 'error', 'Folder not found.' );
return $this->index();
}
if ( $folder->createdBy != App::$activeUser->ID ) {
Issues::add( 'error', 'You do not have permission to modify this folder.' );
return $this->index();
}
$folderID = ( false === Input::exists('folder_id') ) ? $folder->ID : Input::post('folder_id');
$folderSelect = HoudiniForms::getFormFieldHtml( 'folder_id', 'Folder', 'select', $folderID, self::$folders->simpleByUser() );
Components::set( 'folderSelect', $folderSelect );
Components::set( 'color', $folder->color );
if ( ! Input::exists( 'submit' ) ) {
return Views::view( 'bookmarks.folders.edit', $folder );
}
if ( !Forms::check( 'editFolder' ) ) {
Issues::add( 'error', [ 'There was an error editing your folder.' => Check::userErrors() ] );
return Views::view( 'bookmarks.folders.edit', $folder );
}
$result = self::$folders->update( $id, Input::post('title'), $folderID, Input::post('description'), Input::post('color'), Input::post('privacy') );
if ( !$result ) {
Issues::add( 'error', [ 'There was an error updating your folder.' => Check::userErrors() ] );
return Views::view( 'bookmarks.folders.edit', $folder );
}
Session::flash( 'success', 'Your Folder has been updated.' );
Redirect::to( 'bookmarks/folders/'. $folder->ID );
}
public function deleteFolder( $id = null ) {
$folder = self::$folders->findById( $id );
if ( $folder == false ) {
Issues::add( 'error', 'Folder not found.' );
return $this->index();
}
if ( $folder->createdBy != App::$activeUser->ID ) {
Issues::add( 'error', 'You do not have permission to modify this folder.' );
return $this->index();
}
$results = self::$bookmarks->deleteByFolder( $id );
$result = self::$folders->delete( $id );
if ( !$result ) {
Session::flash( 'error', 'There was an error deleting the folder(s)' );
} else {
Session::flash( 'success', 'Folder deleted' );
}
Redirect::to( 'bookmarks/folders' );
}
/**
* Functionality
*/
public function hideBookmark( $id = null ) {
$bookmark = self::$bookmarks->findById( $id );
if ( $bookmark == false ) {
Session::flash( 'error', 'Bookmark not found.' );
return Redirect::to( 'bookmarks/index' );
}
if ( $bookmark->createdBy != App::$activeUser->ID ) {
Session::flash( 'error', 'You do not have permission to modify this bookmark.' );
return Redirect::to( 'bookmarks/index' );
}
self::$bookmarks->hide( $id );
Session::flash( 'success', 'Bookmark hidden.' );
return Redirect::to( 'bookmarks/index' );
}
public function archiveBookmark( $id = null ) {
$bookmark = self::$bookmarks->findById( $id );
if ( $bookmark == false ) {
Session::flash( 'error', 'Bookmark not found.' );
return Redirect::to( 'bookmarks/index' );
}
if ( $bookmark->createdBy != App::$activeUser->ID ) {
Session::flash( 'error', 'You do not have permission to modify this bookmark.' );
return Redirect::to( 'bookmarks/index' );
}
self::$bookmarks->archive( $id );
Session::flash( 'success', 'Bookmark archived.' );
return Redirect::to( 'bookmarks/index' );
}
public function showBookmark( $id = null ) {
$bookmark = self::$bookmarks->findById( $id );
if ( $bookmark == false ) {
Session::flash( 'error', 'Bookmark not found.' );
return Redirect::to( 'bookmarks/index' );
}
if ( $bookmark->createdBy != App::$activeUser->ID ) {
Session::flash( 'error', 'You do not have permission to modify this bookmark.' );
return Redirect::to( 'bookmarks/index' );
}
self::$bookmarks->show( $id );
Session::flash( 'success', 'Bookmark shown.' );
return Redirect::to( 'bookmarks/index' );
}
public function unarchiveBookmark( $id = null ) {
$bookmark = self::$bookmarks->findById( $id );
if ( $bookmark == false ) {
Session::flash( 'error', 'Bookmark not found.' );
return Redirect::to( 'bookmarks/index' );
}
if ( $bookmark->createdBy != App::$activeUser->ID ) {
Session::flash( 'error', 'You do not have permission to modify this bookmark.' );
return Redirect::to( 'bookmarks/index' );
}
self::$bookmarks->unarchive( $id );
Session::flash( 'success', 'Bookmark un-archived.' );
return Redirect::to( 'bookmarks/index' );
}
public function refreshBookmark( $id = null ) {
$bookmark = self::$bookmarks->findById( $id );
if ( $bookmark == false ) {
Session::flash( 'error', 'Bookmark not found.' );
return Redirect::to( 'bookmarks/index' );
}
if ( $bookmark->createdBy != App::$activeUser->ID ) {
Session::flash( 'error', 'You do not have permission to modify this bookmark.' );
return Redirect::to( 'bookmarks/index' );
}
$info = self::$bookmarks->refreshInfo( $id );
if ( false == $info ) {
Session::flash( 'error', 'Issue refreshing your bookmark.' );
return Redirect::to( 'bookmarks/bookmark/' . $bookmark->ID );
}
Session::flash( 'success', 'Bookmark data refreshed.' );
return Redirect::to( 'bookmarks/bookmark/' . $bookmark->ID );
}
}

View File

@ -1,122 +0,0 @@
<?php
/**
* app/plugins/bookmarks/forms.php
*
* This houses all of the form checking functions for this plugin.
*
* @package TP Bookmarks
* @version 3.0
* @author Joey Kimsey <Joey@thetempusproject.com>
* @link https://TheTempusProject.com
* @license https://opensource.org/licenses/MIT [MIT LICENSE]
*/
namespace TheTempusProject\Plugins\Bookmarks;
use TheTempusProject\Bedrock\Functions\Input;
use TheTempusProject\Bedrock\Functions\Check;
use TheTempusProject\Classes\Forms;
class BookmarksForms extends Forms {
/**
* Adds these functions to the form list.
*/
public function __construct() {
self::addHandler( 'createBookmark', __CLASS__, 'createBookmark' );
self::addHandler( 'createFolder', __CLASS__, 'createFolder' );
self::addHandler( 'editBookmark', __CLASS__, 'editBookmark' );
self::addHandler( 'editFolder', __CLASS__, 'editFolder' );
}
public static function createBookmark() {
// if ( ! Input::exists( 'title' ) ) {
// Check::addUserError( 'You must include a title.' );
// return false;
// }
if ( ! Input::exists( 'url' ) ) {
Check::addUserError( 'You must include a url.' );
return false;
}
// if ( ! Input::exists( 'color' ) ) {
// Check::addUserError( 'You must include a color.' );
// return false;
// }
// if ( ! Input::exists( 'privacy' ) ) {
// Check::addUserError( 'You must include a privacy.' );
// return false;
// }
// if ( !self::token() ) {
// Check::addUserError( 'token - comment out later.' );
// return false;
// }
return true;
}
public static function createFolder() {
if ( ! Input::exists( 'title' ) ) {
Check::addUserError( 'You must include a title.' );
return false;
}
// if ( ! Input::exists( 'color' ) ) {
// Check::addUserError( 'You must include a color.' );
// return false;
// }
// if ( ! Input::exists( 'privacy' ) ) {
// Check::addUserError( 'You must include a privacy.' );
// return false;
// }
// if ( ! self::token() ) {
// Check::addUserError( 'token - comment out later.' );
// return false;
// }
return true;
}
public static function editBookmark() {
// if ( ! Input::exists( 'title' ) ) {
// Check::addUserError( 'You must include a title.' );
// return false;
// }
if ( ! Input::exists( 'url' ) ) {
Check::addUserError( 'You must include a url.' );
return false;
}
// if ( ! Input::exists( 'color' ) ) {
// Check::addUserError( 'You must include a color.' );
// return false;
// }
// if ( ! Input::exists( 'privacy' ) ) {
// Check::addUserError( 'You must include a privacy.' );
// return false;
// }
// if ( !self::token() ) {
// Check::addUserError( 'token - comment out later.' );
// return false;
// }
return true;
}
public static function editFolder() {
if ( ! Input::exists( 'submit' ) ) {
return false;
}
if ( ! Input::exists( 'title' ) ) {
Check::addUserError( 'You must include a title.' );
return false;
}
// if ( ! Input::exists( 'color' ) ) {
// Check::addUserError( 'You must include a color.' );
// return false;
// }
// if ( ! Input::exists( 'privacy' ) ) {
// Check::addUserError( 'You must include a privacy.' );
// return false;
// }
// if ( !self::token() ) {
// Check::addUserError( 'token - comment out later.' );
// return false;
// }
return true;
}
}
new BookmarksForms;

View File

@ -1,149 +0,0 @@
<?php
/**
* app/plugins/bookmarks/models/bookmarkViews.php
*
* This class is used for the manipulation of the bookmark_views database table.
*
* @package TP Bookmarks
* @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\Classes\Config;
use TheTempusProject\Bedrock\Functions\Check;
use TheTempusProject\Canary\Bin\Canary as Debug;
use TheTempusProject\Classes\DatabaseModel;
use TheTempusProject\TheTempusProject as App;
use TheTempusProject\Houdini\Classes\Filters;
use TheTempusProject\Bedrock\Classes\CustomException;
class BookmarkViews extends DatabaseModel {
public $tableName = 'bookmark_views';
public $databaseMatrix = [
[ 'title', 'varchar', '256' ],
[ 'description', 'text', '' ],
[ 'privacy', 'varchar', '48' ],
[ 'createdBy', 'int', '11' ],
[ 'createdAt', 'int', '11' ],
[ 'updatedAt', 'int', '11' ],
];
/**
* The model constructor.
*/
public function __construct() {
parent::__construct();
}
public function create( $title, $description = '', $privacy = 'private' ) {
if ( ! Check::dataTitle( $title ) ) {
Debug::info( 'Views: illegal title.' );
return false;
}
$fields = [
'title' => $title,
'description' => $description,
'privacy' => $privacy,
'createdBy' => App::$activeUser->ID,
'createdAt' => time(),
];
if ( ! self::$db->insert( $this->tableName, $fields ) ) {
new CustomException( 'viewCreate' );
Debug::error( "Views: not created " . var_export($fields,true) );
return false;
}
return self::$db->lastId();
}
public function update( $id, $title, $description = '', $privacy = 'private' ) {
if ( !Check::id( $id ) ) {
Debug::info( 'Views: illegal ID.' );
return false;
}
if ( !Check::dataTitle( $title ) ) {
Debug::info( 'Views: illegal title.' );
return false;
}
$fields = [
'title' => $title,
'description' => $description,
'privacy' => $privacy,
];
if ( !self::$db->update( $this->tableName, $id, $fields ) ) {
new CustomException( 'viewUpdate' );
Debug::error( "Views: $id not updated: $fields" );
return false;
}
return true;
}
public function byUser( $limit = null ) {
$whereClause = ['createdBy', '=', App::$activeUser->ID];
if ( empty( $limit ) ) {
$views = self::$db->get( $this->tableName, $whereClause );
} else {
$views = self::$db->get( $this->tableName, $whereClause, 'ID', 'DESC', [0, $limit] );
}
if ( !$views->count() ) {
Debug::info( 'No Views found.' );
return false;
}
return $this->filter( $views->results() );
}
public function getName( $id ) {
$views = self::findById( $id );
if (false == $views) {
return 'unknown';
}
return $views->title;
}
public function simpleByUser() {
$whereClause = ['createdBy', '=', App::$activeUser->ID];
$views = self::$db->get( $this->tableName, $whereClause );
if ( !$views->count() ) {
Debug::warn( 'Could not find any Views' );
return false;
}
$views = $views->results();
$out = [];
foreach ( $views as $view ) {
$out[ $view->title ] = $view->ID;
}
return $out;
}
public function simpleObjectByUser() {
$whereClause = ['createdBy', '=', App::$activeUser->ID];
$views = self::$db->get( $this->tableName, $whereClause );
if ( !$views->count() ) {
Debug::warn( 'Could not find any Views' );
return false;
}
$views = $views->results();
$out = [];
foreach ( $views as $view ) {
$obj = new \stdClass();
$obj->title = $view->title;
$obj->ID = $view->ID;
$out[] = $obj;
}
return $out;
}
}

View File

@ -1,705 +0,0 @@
<?php
/**
* app/plugins/bookmarks/models/bookmarks.php
*
* This class is used for the manipulation of the bookmarks database table.
*
* @package TP Bookmarks
* @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\Classes\Config;
use TheTempusProject\Bedrock\Functions\Check;
use TheTempusProject\Canary\Bin\Canary as Debug;
use TheTempusProject\Classes\DatabaseModel;
use TheTempusProject\TheTempusProject as App;
use TheTempusProject\Houdini\Classes\Filters;
use TheTempusProject\Bedrock\Classes\CustomException;
class Bookmarks extends DatabaseModel {
public $tableName = 'bookmarks';
public $linkTypes = [
'Open in New Tab' => 'external',
'Open in Same Tab' => 'internal',
];
public $databaseMatrix = [
[ 'title', 'varchar', '256' ],
[ 'url', 'text', '' ],
[ 'color', 'varchar', '48' ],
[ 'privacy', 'varchar', '48' ],
[ 'folderID', 'int', '11' ],
[ 'description', 'text', '' ],
[ 'createdBy', 'int', '11' ],
[ 'createdAt', 'int', '11' ],
[ 'meta', 'text', '' ],
[ 'icon', 'text', '' ],
[ 'archivedAt', 'int', '11' ],
[ 'refreshedAt', 'int', '11' ],
[ 'hiddenAt', 'int', '11' ],
[ 'order', 'int', '11' ],
[ 'linkType', 'varchar', '32' ],
];
/**
* The model constructor.
*/
public function __construct() {
parent::__construct();
}
public function create( $title, $url, $folderID = 0, $description = '', $color = 'default', $privacy = 'private', $type = 'external' ) {
$fields = [
'title' => $title,
'url' => $url,
'description' => $description,
'color' => $color,
'privacy' => $privacy,
'createdBy' => App::$activeUser->ID,
'createdAt' => time(),
];
if ( !empty( $folderID ) ) {
$fields['folderID'] = $folderID;
} else {
$fields['folderID'] = null;
}
if ( ! self::$db->insert( $this->tableName, $fields ) ) {
new CustomException( 'bookmarkCreate' );
Debug::error( "Bookmarks: not created " . var_export($fields,true) );
return false;
}
return self::$db->lastId();
}
public function update( $id, $title, $url, $folderID = 0, $description = '', $color = 'default', $privacy = 'private', $type = 'external', $order = 0 ) {
if ( !Check::id( $id ) ) {
Debug::info( 'Bookmarks: illegal ID.' );
return false;
}
$fields = [
'title' => $title,
'url' => $url,
'description' => $description,
'color' => $color,
'privacy' => $privacy,
// 'linkType' => $type,
// 'order' => $order,
];
if ( !empty( $folderID ) ) {
$fields['folderID'] = $folderID;
}
if ( !self::$db->update( $this->tableName, $id, $fields ) ) {
new CustomException( 'bookmarkUpdate' );
Debug::error( "Bookmarks: $id not updated" );
return false;
}
return true;
}
public function byUser( $limit = null ) {
$whereClause = ['createdBy', '=', App::$activeUser->ID];
if ( empty( $limit ) ) {
$bookmarks = self::$db->get( $this->tableName, $whereClause );
} else {
$bookmarks = self::$db->get( $this->tableName, $whereClause, 'ID', 'DESC', [0, $limit] );
}
if ( !$bookmarks->count() ) {
Debug::info( 'No Bookmarks found.' );
return false;
}
return $this->filter( $bookmarks->results() );
}
public function byFolder( $id, $limit = null ) {
$whereClause = ['createdBy', '=', App::$activeUser->ID, 'AND'];
$whereClause = array_merge( $whereClause, [ 'folderID', '=', $id ] );
if ( empty( $limit ) ) {
$bookmarks = self::$db->get( $this->tableName, $whereClause );
} else {
$bookmarks = self::$db->get( $this->tableName, $whereClause, 'ID', 'DESC', [0, $limit] );
}
if ( !$bookmarks->count() ) {
Debug::info( 'No Bookmarks found.' );
return false;
}
return $this->filter( $bookmarks->results() );
}
public function noFolder( $id = 0, $limit = 10 ) {
$whereClause = ['createdBy', '=', App::$activeUser->ID, 'AND'];
if ( !empty( $id ) ) {
$whereClause = array_merge( $whereClause, ['folderID', '!=', $id] );
} else {
$whereClause = array_merge( $whereClause, [ 'folderID', 'IS', null] );
}
if ( empty( $limit ) ) {
$bookmarks = self::$db->get( $this->tableName, $whereClause );
} else {
$bookmarks = self::$db->get( $this->tableName, $whereClause, 'ID', 'DESC', [ 0, $limit ] );
}
if ( !$bookmarks->count() ) {
Debug::info( 'No Bookmarks found.' );
return false;
}
return $this->filter( $bookmarks->results() );
}
public function getName( $id ) {
$bookmarks = self::findById( $id );
if (false == $bookmarks) {
return 'unknown';
}
return $bookmarks->title;
}
public function getColor( $id ) {
$bookmarks = self::findById( $id );
if (false == $bookmarks) {
return 'default';
}
return $bookmarks->color;
}
public function simpleByUser() {
$whereClause = ['createdBy', '=', App::$activeUser->ID];
$bookmarks = self::$db->get( $this->tableName, $whereClause );
if ( !$bookmarks->count() ) {
Debug::warn( 'Could not find any bookmarks' );
return false;
}
$bookmarks = $bookmarks->results();
$out = [];
foreach ( $bookmarks as $bookmarks ) {
$out[ $bookmarks->title ] = $bookmarks->ID;
}
return $out;
}
public function simpleObjectByUser() {
$whereClause = ['createdBy', '=', App::$activeUser->ID];
$bookmarks = self::$db->get( $this->tableName, $whereClause );
if ( !$bookmarks->count() ) {
Debug::warn( 'Could not find any bookmarks' );
return false;
}
$bookmarks = $bookmarks->results();
$out = [];
foreach ( $bookmarks as $bookmarks ) {
$obj = new \stdClass();
$obj->title = $bookmarks->title;
$obj->ID = $bookmarks->ID;
$out[] = $obj;
}
return $out;
}
public function deleteByFolder( $folderID ) {
$whereClause = [ 'createdBy', '=', App::$activeUser->ID, 'AND' ];
$whereClause = array_merge( $whereClause, [ 'folderID', '=', $folderID ] );
$bookmarks = self::$db->get( $this->tableName, $whereClause );
if ( ! $bookmarks->count() ) {
Debug::info( 'No ' . $this->tableName . ' data found.' );
return [];
}
foreach( $bookmarks->results() as $bookmark ) {
$this->delete( $bookmark->ID );
}
return true;
}
private function resolveShortenedUrl( $url ) {
$ch = curl_init($url);
// Set curl options
curl_setopt($ch, CURLOPT_NOBODY, true); // We don't need the body
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); // Follow redirects
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); // Return the response
curl_setopt($ch, CURLOPT_TIMEOUT, 30); // Set a timeout
// curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 10); // Maybe sketchy?
// Maybe sketchy?
// curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false); // Disable SSL host verification
// curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); // Disable SSL peer verification
// curl_setopt($ch, CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1_2);
// added to support the regex site
// curl_setopt($ch, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
// =
// curl_setopt($ch, CURLOPT_SSL_CIPHER_LIST, 'AES256+EECDH:AES256+EDH' );
// Execute curl
$response = curl_exec( $ch );
// Check if there was an error
if ( curl_errno( $ch ) ) {
// Get error details
$errorCode = curl_errno($ch);
$errorMessage = curl_error($ch);
// Log or display the error details
dv('cURL Error: ' . $errorMessage . ' (Error Code: ' . $errorCode . ')');
curl_close($ch);
// return $url; // Return the original URL if there was an error
$url = rtrim( $url, '/' );
$ch2 = curl_init($url);
curl_setopt($ch2, CURLOPT_NOBODY, true); // We don't need the body
curl_setopt($ch2, CURLOPT_FOLLOWLOCATION, true); // Follow redirects
curl_setopt($ch2, CURLOPT_RETURNTRANSFER, true); // Return the response
curl_setopt($ch2, CURLOPT_TIMEOUT, 5); // Set a timeout
curl_exec($ch2);
if ( curl_errno( $ch2 ) ) {
}
curl_close( $ch );
return $url;
}
// Get the effective URL (the final destination after redirects)
$finalUrl = curl_getinfo( $ch, CURLINFO_EFFECTIVE_URL );
curl_close( $ch );
return $finalUrl;
// $headers = get_headers( $url, 1 );
$headers = @get_headers($url, 1);
if ( $headers === false ) {
}
if ( isset( $headers['Location'] ) ) {
if (is_array($headers['Location'])) {
return end($headers['Location']);
} else {
return $headers['Location'];
}
} else {
return $url;
}
}
public function filter( $data, $params = [] ) {
foreach ( $data as $instance ) {
if ( !is_object( $instance ) ) {
$instance = $data;
$end = true;
}
$base_url = $this->getBaseUrl( $instance->url );
if ( empty( $instance->icon ) ) {
$instance->iconHtml = '<i class="glyphicon glyphicon-link"></i>';
} else {
if (strpos($instance->icon, 'http') !== false) {
$instance->iconHtml = '<img src="' . $instance->icon .'" />';
} else {
$instance->iconHtml = '<img src="' . $base_url . ltrim( $instance->icon, '/' ) .'" />';
}
}
if ( empty( $instance->hiddenAt ) ) {
$instance->hideBtn = '
<a href="{ROOT_URL}bookmarks/hideBookmark/'.$instance->ID.'" class="btn btn-sm btn-warning" role="button">
<i class="glyphicon glyphicon-eye-open"></i>
</a>';
} else {
$instance->hideBtn = '
<a href="{ROOT_URL}bookmarks/showBookmark/'.$instance->ID.'" class="btn btn-sm btn-default" role="button">
<i class="glyphicon glyphicon-eye-open"></i>
</a>';
}
if ( empty( $instance->archivedAt ) ) {
$instance->archiveBtn = '
<a href="{ROOT_URL}bookmarks/archiveBookmark/'.$instance->ID.'" class="btn btn-sm btn-info" role="button">
<i class="glyphicon glyphicon-briefcase"></i>
</a>';
} else {
$instance->archiveBtn = '
<a href="{ROOT_URL}bookmarks/unarchiveBookmark/'.$instance->ID.'" class="btn btn-sm btn-default" role="button">
<i class="glyphicon glyphicon-briefcase"></i>
</a>';
}
if ( ! empty( $instance->refreshedAt ) && time() < ( $instance->refreshedAt + ( 60 * 10 ) ) ) {
$instance->refreshBtn = '
<a href="{ROOT_URL}bookmarks/refreshBookmark/'.$instance->ID.'" class="btn btn-sm btn-danger" role="button">
<i class="glyphicon glyphicon-refresh"></i>
</a>';
} else {
$instance->refreshBtn = '
<a href="{ROOT_URL}bookmarks/refreshBookmark/'.$instance->ID.'" class="btn btn-sm btn-success" role="button">
<i class="glyphicon glyphicon-refresh"></i>
</a>';
}
$out[] = $instance;
if ( !empty( $end ) ) {
$out = $out[0];
break;
}
}
return $out;
}
public function hide( $id ) {
if ( !Check::id( $id ) ) {
Debug::info( 'Bookmarks: illegal ID.' );
return false;
}
$fields = [
'hiddenAt' => time(),
];
if ( !self::$db->update( $this->tableName, $id, $fields ) ) {
new CustomException( 'bookmarkUpdate' );
Debug::error( "Bookmarks: $id not updated" );
return false;
}
return true;
}
public function show( $id ) {
if ( !Check::id( $id ) ) {
Debug::info( 'Bookmarks: illegal ID.' );
return false;
}
$fields = [
'hiddenAt' => 0,
];
if ( !self::$db->update( $this->tableName, $id, $fields ) ) {
new CustomException( 'bookmarkUpdate' );
Debug::error( "Bookmarks: $id not updated" );
return false;
}
return true;
}
public function archive( $id ) {
if ( !Check::id( $id ) ) {
Debug::info( 'Bookmarks: illegal ID.' );
return false;
}
$fields = [
'archivedAt' => time(),
];
if ( !self::$db->update( $this->tableName, $id, $fields ) ) {
new CustomException( 'bookmarkUpdate' );
Debug::error( "Bookmarks: $id not updated" );
return false;
}
return true;
}
public function unarchive( $id ) {
if ( !Check::id( $id ) ) {
Debug::info( 'Bookmarks: illegal ID.' );
return false;
}
$fields = [
'archivedAt' => 0,
];
if ( !self::$db->update( $this->tableName, $id, $fields ) ) {
new CustomException( 'bookmarkUpdate' );
Debug::error( "Bookmarks: $id not updated" );
return false;
}
return true;
}
public function extractMetaTags($htmlContent) {
$doc = new \DOMDocument();
@$doc->loadHTML($htmlContent);
$metaTags = [];
foreach ($doc->getElementsByTagName('meta') as $meta) {
$name = $meta->getAttribute('name') ?: $meta->getAttribute('property');
$content = $meta->getAttribute('content');
if ($name && $content) {
$metaTags[$name] = $content;
}
}
return $metaTags;
}
public function fetchUrlData($url) {
$ch = curl_init();
// Set cURL options
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HEADER, true); // Include headers in the output
curl_setopt($ch, CURLOPT_NOBODY, false); // Include the body in the output
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); // Follow redirects
curl_setopt($ch, CURLOPT_TIMEOUT, 30); // Set a timeout
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false); // Disable SSL host verification for testing
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); // Disable SSL peer verification for testing
// Execute cURL request
$response = curl_exec($ch);
// Check if there was an error
if (curl_errno($ch)) {
$errorMessage = curl_error($ch);
curl_close($ch);
throw new \Exception('cURL Error: ' . $errorMessage);
}
// Get HTTP status code
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
// Separate headers and body
$headerSize = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
$headers = substr($response, 0, $headerSize);
$body = substr($response, $headerSize);
curl_close($ch);
// Parse headers into an associative array
$headerLines = explode("\r\n", trim($headers));
$headerArray = [];
foreach ($headerLines as $line) {
$parts = explode(': ', $line, 2);
if (count($parts) == 2) {
$headerArray[$parts[0]] = $parts[1];
}
}
return [
'http_code' => $httpCode,
'headers' => $headerArray,
'body' => $body,
];
}
private function getMetaTagsAndFavicon( $url ) {
try {
// $url = 'https://runescape.wiki';
$data = $this->fetchUrlData($url);
// iv($data);
// Get headers
$headers = $data['headers'];
iv($headers);
// Get meta tags
$metaTags = $this->extractMetaTags($data['body']);
} catch (Exception $e) {
dv( 'Error: ' . $e->getMessage());
}
$metaInfo = [
'url' => $url,
'title' => null,
'description' => null,
'image' => null,
'favicon' => null
];
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$html = curl_exec($ch);
curl_close($ch);
if ($html === false) {
return null;
}
$meta = @get_meta_tags( $url );
$dom = new \DOMDocument('1.0', 'utf-8');
$dom->strictErrorChecking = false;
$dom->loadHTML($html, LIBXML_NOERROR);
$xml = simplexml_import_dom($dom);
$arr = $xml->xpath('//link[@rel="shortcut icon"]');
// Get the title of the page
$titles = $dom->getElementsByTagName('title');
if ($titles->length > 0) {
$metaInfo['title'] = $titles->item(0)->nodeValue;
}
// Get the meta tags
$metaTags = $dom->getElementsByTagName('meta');
$metadata = [];
foreach ($metaTags as $meta) {
$metadata[] = [
'name' => $meta->getAttribute('name'),
'property' => $meta->getAttribute('property'),
'content' => $meta->getAttribute('content')
];
if ($meta->getAttribute('name') === 'description') {
$metaInfo['description'] = $meta->getAttribute('content');
}
if ($meta->getAttribute('itemprop') === 'image') {
$metaInfo['google_image'] = $meta->getAttribute('content');
}
if ($meta->getAttribute('property') === 'og:image') {
$metaInfo['image'] = $meta->getAttribute('content');
}
}
// Get the link tags to find the favicon
$linkTags = $dom->getElementsByTagName('link');
$metadata['links'] = [];
foreach ($linkTags as $link) {
$metadata['links'][] = [ $link->getAttribute('rel') => $link->getAttribute('href') ];
if ( $link->getAttribute('rel') === 'icon' || $link->getAttribute('rel') === 'shortcut icon') {
$metaInfo['favicon'] = $link->getAttribute('href');
break;
}
}
$metaInfo['metadata'] = $metadata;
return $metaInfo;
}
public function retrieveInfo( $url ) {
$finalDestination = $this->resolveShortenedUrl( $url );
$info = $this->getMetaTagsAndFavicon( $finalDestination );
$base_url = $this->getBaseUrl( $finalDestination );
if ( ! empty( $info['favicon'] ) ) {
echo 'favicon exists' . PHP_EOL;
if ( stripos( $info['favicon'], 'http' ) !== false) {
echo 'favicon is full url' . PHP_EOL;
$imageUrl = $info['favicon'];
} else {
echo 'favicon is not full url' . PHP_EOL;
$imageUrl = trim( $base_url, '/' ) . '/' . ltrim( $info['favicon'], '/' );
}
if ( $this->isValidImageUrl( $imageUrl ) ) {
echo 'image is valid' . PHP_EOL;
$info['favicon'] = $imageUrl;
} else {
echo 'image is not valid' . PHP_EOL;
$base_info = $this->getMetaTagsAndFavicon( $base_url );
if ( ! empty( $base_info['favicon'] ) ) {
echo 'parent favicon exists!';
if ( stripos( $base_info['favicon'], 'http' ) !== false) {
echo 'parent favicon is full url' . PHP_EOL;
$imageUrl = $base_info['favicon'];
} else {
echo 'parent favicon is not full url' . PHP_EOL;
$imageUrl = trim( $base_url, '/' ) . '/' . ltrim( $base_info['favicon'], '/' );
}
if ( $this->isValidImageUrl( $imageUrl ) ) {
echo 'parent favicon image is valid' . PHP_EOL;
$info['favicon'] = $imageUrl;
} else {
echo 'parent favicon image is not valid' . PHP_EOL;
}
}
}
} else {
echo 'favicon does not exist' . PHP_EOL;
$base_info = $this->getMetaTagsAndFavicon( $base_url );
if ( ! empty( $base_info['favicon'] ) ) {
echo 'parent favicon exists!' . PHP_EOL;
if ( stripos( $base_info['favicon'], 'http' ) !== false) {
echo 'parent favicon is full url' . PHP_EOL;
$imageUrl = $base_info['favicon'];
} else {
echo 'parent favicon is not full url' . PHP_EOL;
$imageUrl = trim( $base_url, '/' ) . '/' . ltrim( $base_info['favicon'], '/' );
}
if ( $this->isValidImageUrl( $imageUrl ) ) {
echo 'parent favicon image is valid' . PHP_EOL;
$info['favicon'] = $imageUrl;
} else {
echo 'parent favicon image is not valid' . PHP_EOL;
}
}
}
return $info;
}
public function refreshInfo( $id ) {
if ( !Check::id( $id ) ) {
Debug::info( 'Bookmarks: illegal ID.' );
return false;
}
$bookmark = self::findById( $id );
if ( $bookmark == false ) {
Debug::info( 'Bookmarks not found.' );
return false;
}
if ( $bookmark->createdBy != App::$activeUser->ID ) {
Debug::info( 'You do not have permission to modify this bookmark.' );
return false;
}
if ( time() < ( $bookmark->refreshedAt + ( 60 * 10 ) ) ) {
Debug::info( 'You may only fetch bookmarks once every 10 minutes.' );
return false;
}
$info = $this->retrieveInfo( $bookmark->url );
$fields = [
// 'refreshedAt' => time(),
];
if ( empty( $bookmark->title ) && ! empty( $info['title'] ) ) {
$fields['title'] = $info['title'];
}
if ( empty( $bookmark->description ) && ! empty( $info['description'] ) ) {
$fields['description'] = $info['description'];
}
if ( ( empty( $bookmark->icon ) || ! $this->isValidImageUrl( $bookmark->icon ) ) && ! empty( $info['favicon'] ) ) {
$fields['icon'] = $info['favicon'];
}
$fields['meta'] = json_encode( $info['metadata'] );
if ( !self::$db->update( $this->tableName, $id, $fields ) ) {
new CustomException( 'bookmarkUpdate' );
Debug::error( "Bookmarks: $id not updated" );
return false;
}
return true;
}
private function getBaseUrl ($url ) {
$parsedUrl = parse_url($url);
if (isset($parsedUrl['scheme']) && isset($parsedUrl['host'])) {
return $parsedUrl['scheme'] . '://' . $parsedUrl['host'] . '/';
} else {
return null; // URL is not valid or cannot be parsed
}
}
function isValidImageUrl($url) {
$headers = @get_headers($url);
if ($headers && strpos($headers[0], '200') !== false) {
return true;
// Further check to ensure it's an image
foreach ($headers as $header) {
if (strpos(strtolower($header), 'content-type:') !== false) {
if (strpos(strtolower($header), 'image/') !== false) {
return true;
}
}
}
}
return false;
}
}

View File

@ -1,157 +0,0 @@
<?php
/**
* app/plugins/bookmarks/models/folders.php
*
* This class is used for the manipulation of the folders database table.
*
* @package TP Bookmarks
* @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\Classes\Config;
use TheTempusProject\Bedrock\Functions\Check;
use TheTempusProject\Canary\Bin\Canary as Debug;
use TheTempusProject\Classes\DatabaseModel;
use TheTempusProject\TheTempusProject as App;
use TheTempusProject\Houdini\Classes\Filters;
use TheTempusProject\Bedrock\Classes\CustomException;
class Folders extends DatabaseModel {
public $tableName = 'folders';
public $databaseMatrix = [
[ 'title', 'varchar', '256' ],
[ 'color', 'varchar', '48' ],
[ 'privacy', 'varchar', '48' ],
[ 'description', 'text', '' ],
[ 'folderID', 'int', '11' ],
[ 'createdBy', 'int', '11' ],
[ 'createdAt', 'int', '11' ],
];
/**
* The model constructor.
*/
public function __construct() {
parent::__construct();
}
public function create( $title, $folderID = 0, $description = '', $color = 'default', $privacy = 'private' ) {
if ( ! Check::dataTitle( $title ) ) {
Debug::info( 'Folders: illegal title.' );
return false;
}
$fields = [
'title' => $title,
'description' => $description,
'color' => $color,
'privacy' => $privacy,
'createdBy' => App::$activeUser->ID,
'createdAt' => time(),
];
if ( !empty( $folderID ) ) {
$fields['folderID'] = $folderID;
}
if ( ! self::$db->insert( $this->tableName, $fields ) ) {
new CustomException( 'folderCreate' );
Debug::error( "Folders: not created " . var_export($fields,true) );
return false;
}
return self::$db->lastId();
}
public function update( $id, $title, $folderID = 0, $description = '', $color = 'default', $privacy = 'private' ) {
if ( !Check::id( $id ) ) {
Debug::info( 'Folders: illegal ID.' );
return false;
}
if ( !Check::dataTitle( $title ) ) {
Debug::info( 'Folders: illegal title.' );
return false;
}
$fields = [
'title' => $title,
'description' => $description,
'color' => $color,
'privacy' => $privacy,
];
if ( !empty( $folderID ) ) {
$fields['folderID'] = $folderID;
}
if ( !self::$db->update( $this->tableName, $id, $fields ) ) {
new CustomException( 'folderUpdate' );
Debug::error( "Folders: $id not updated: $fields" );
return false;
}
return true;
}
public function byUser( $limit = null ) {
$whereClause = ['createdBy', '=', App::$activeUser->ID];
if ( empty( $limit ) ) {
$folders = self::$db->get( $this->tableName, $whereClause );
} else {
$folders = self::$db->get( $this->tableName, $whereClause, 'ID', 'DESC', [0, $limit] );
}
if ( !$folders->count() ) {
Debug::info( 'No Folders found.' );
return false;
}
return $this->filter( $folders->results() );
}
public function getName( $id ) {
$folder = self::findById( $id );
if (false == $folder) {
return 'unknown';
}
return $folder->title;
}
public function getColor( $id ) {
$folder = self::findById( $id );
if (false == $folder) {
return 'default';
}
return $folder->color;
}
public function simpleByUser() {
$whereClause = ['createdBy', '=', App::$activeUser->ID];
$folders = self::$db->get( $this->tableName, $whereClause );
if ( !$folders->count() ) {
Debug::warn( 'Could not find any folders' );
return false;
}
$folders = $folders->results();
$out = [];
$out[ 'No Folder' ] = 0;
foreach ( $folders as $folder ) {
$out[ $folder->title ] = $folder->ID;
}
return $out;
}
public function simpleObjectByUser() {
$whereClause = ['createdBy', '=', App::$activeUser->ID];
$folders = self::$db->get( $this->tableName, $whereClause );
if ( !$folders->count() ) {
Debug::warn( 'Could not find any folders' );
return false;
}
$folders = $folders->results();
$out = [];
foreach ( $folders as $folder ) {
$obj = new \stdClass();
$obj->title = $folder->title;
$obj->ID = $folder->ID;
$out[] = $obj;
}
return $out;
}
}

View File

@ -1,50 +0,0 @@
<?php
/**
* app/plugins/bookmarks/plugin.php
*
* This houses all of the main plugin info and functionality.
*
* @package TP Bookmarks
* @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;
use TheTempusProject\Models\Events;
use TheTempusProject\Models\Bookmarks as Bookmark;
use TheTempusProject\Models\Folders;
use TheTempusProject\Houdini\Classes\Components;
use TheTempusProject\Houdini\Classes\Template;
class Bookmarks extends Plugin {
public $pluginName = 'TP Bookmarks';
public $configName = 'bookmarks';
public $pluginAuthor = 'JoeyK';
public $pluginWebsite = 'https://TheTempusProject.com';
public $modelVersion = '1.0';
public $pluginVersion = '3.0';
public $pluginDescription = 'A simple plugin which adds a site wide bookmark system.';
public $permissionMatrix = [
'useBookmarks' => [
'pretty' => 'Can use the bookmarks feature',
'default' => false,
],
];
public $main_links = [
[
'text' => 'Bookmarks',
'url' => '{ROOT_URL}bookmarks/index',
'filter' => 'loggedin',
],
];
public $configMatrix = [
'enabled' => [
'type' => 'radio',
'pretty' => 'Enable Bookmarks.',
'default' => true,
],
];
}

View File

@ -1,45 +0,0 @@
<legend>Create Bookmark</legend>
{BookmarkBreadCrumbs}
<form action="" method="post" class="form-horizontal">
<input type="hidden" name="token" value="{TOKEN}">
<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-control" name="title" id="title">
</div>
</div>
<div class="form-group">
<label for="url" class="col-lg-3 control-label">URL:</label>
<div class="col-lg-3">
<input type="text" class="form-control" name="url" id="url">
</div>
</div>
<div class="form-group">
<label for="description" class="col-lg-3 control-label">Description:</label>
<div class="col-lg-3">
<textarea class="form-control" name="description" maxlength="2000" rows="10" cols="50" id="description"></textarea>
</div>
</div>
{folderSelect}
<div class="form-group">
<label for="privacy" class="col-lg-3 control-label">Privacy</label>
<div class="col-lg-3 select-container" id="colorContainer">
<select id="privacy" name="privacy" class="form-control custom-select">
<option value="private">Private</option>
<option value="public">Public</option>
</select>
</div>
</div>
<div class="form-group">
<label for="color" class="col-lg-3 control-label">Color</label>
<div class="col-lg-3 select-container" id="colorContainer">
{colorSelect}
</div>
</div>
<div class="form-group">
<label for="submit" class="col-lg-3 control-label"></label>
<div class="col-lg-3">
<button name="submit" value="submit" type="submit" class="btn btn-lg btn-primary center-block ">Submit</button>
</div>
</div>
</form>

View File

@ -1,45 +0,0 @@
<legend>Edit Bookmark</legend>
{BookmarkBreadCrumbs}
<form action="" method="post" class="form-horizontal">
<input type="hidden" name="token" value="{TOKEN}">
<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-control" name="title" id="title" value="{title}">
</div>
</div>
<div class="form-group">
<label for="url" class="col-lg-3 control-label">URL:</label>
<div class="col-lg-3">
<input type="text" class="form-control" name="url" id="url" value="{url}">
</div>
</div>
<div class="form-group">
<label for="description" class="col-lg-3 control-label">Description:</label>
<div class="col-lg-3">
<textarea class="form-control" name="description" maxlength="2000" rows="10" cols="50" id="description">{description}</textarea>
</div>
</div>
{folderSelect}
<div class="form-group">
<label for="privacy" class="col-lg-3 control-label">Privacy</label>
<div class="col-lg-3 select-container" id="colorContainer">
<select id="privacy" name="privacy" class="form-control custom-select" value="{privacy}">
<option value="private">Private</option>
<option value="public">Public</option>
</select>
</div>
</div>
<div class="form-group">
<label for="color" class="col-lg-3 control-label">Color</label>
<div class="col-lg-3 select-container" id="colorContainer">
{colorSelect}
</div>
</div>
<div class="form-group">
<label for="submit" class="col-lg-3 control-label"></label>
<div class="col-lg-3">
<button name="submit" value="submit" type="submit" class="btn btn-lg btn-primary center-block ">Submit</button>
</div>
</div>
</form>

View File

@ -1,37 +0,0 @@
{BookmarkBreadCrumbs}
<table class="table table-striped">
<thead>
<tr>
<th style="width: 75%">Bookmark</th>
<th style="width: 10%"></th>
<th style="width: 5%"></th>
<th style="width: 5%"></th>
<th style="width: 5%"></th>
</tr>
</thead>
<tbody>
{LOOP}
<tr>
<td style="text-align: center;">
<a href="{url}" role="button">
{title}
</a>
</td>
<td style="text-align: center;">
{privacy}
</td>
<td><a href="{ROOT_URL}bookmarks/bookmarks/{ID}" class="btn btn-sm btn-primary" role="button"><i class="glyphicon glyphicon-open"></i></a></td>
<td><a href="{ROOT_URL}bookmarks/editBookmark/{ID}" class="btn btn-sm btn-warning" role="button"><i class="glyphicon glyphicon-edit"></i></a></td>
<td><a href="{ROOT_URL}bookmarks/deleteBookmark/{ID}" class="btn btn-sm btn-danger" role="button"><i class="glyphicon glyphicon-trash"></i></a></td>
</tr>
{/LOOP}
{ALT}
<tr>
<td style="text-align: center;" colspan="6">
No results to show.
</td>
</tr>
{/ALT}
</tbody>
</table>
<a href="{ROOT_URL}bookmarks/createBookmark" class="btn btn-sm btn-primary" role="button">Create</a>

View File

@ -1,84 +0,0 @@
{BookmarkBreadCrumbs}<br />
<div class="container col-md-4 col-lg-4">
<div class="row">
<div class="panel panel-{color}">
<div class="panel-heading">
<h3 class="panel-title">Bookmark</h3>
</div>
<div class="panel-body">
<div class="row">
<div class="">
<table class="table table-user-primary">
<tbody>
<tr>
<td align="left" width="200"><b>Title</b></td>
<td align="right">{title}</td>
</tr>
<tr>
<td align="left" width="200"><b>URL</b></td>
<td align="right">{url}</td>
</tr>
<tr>
<td align="left" width="200"><b>Type</b></td>
<td align="right">{linkType}</td>
</tr>
<tr>
<td align="left" width="200"><b>Privacy</b></td>
<td align="right">{privacy}</td>
</tr>
<tr>
<td align="left" width="200"><b>Color</b></td>
<td align="right">{color}</td>
</tr>
<tr>
<td align="center" colspan="2"><b>Description</b></td>
</tr>
<tr>
<td colspan="2">{description}</td>
</tr>
<tr>
<td align="center" colspan="2"><b>Icon</b></td>
</tr>
<tr>
<td colspan="2">{iconHtml}</td>
</tr>
<tr>
<td colspan="2">{icon}</td>
</tr>
<tr>
<td align="center" colspan="2"><b>Meta</b></td>
</tr>
<tr>
<td colspan="2">{meta}</td>
</tr>
<tr>
<td><b>Created</b></td>
<td align="right">{DTC}{createdAt}{/DTC}</td>
</tr>
<tr>
<td><b>Archived</b></td>
<td align="right">{DTC}{archivedAt}{/DTC}</td>
</tr>
<tr>
<td><b>Hidden</b></td>
<td align="right">{DTC}{hiddenAt}{/DTC}</td>
</tr>
<tr>
<td><b>Last Refreshed</b></td>
<td align="right">{DTC}{refreshedAt}{/DTC}</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<div class="panel-footer">
{refreshBtn}
{hideBtn}
{archiveBtn}
<a href="{ROOT_URL}bookmarks/editBookmark/{ID}" class="btn btn-sm btn-warning" role="button"><i class="glyphicon glyphicon-edit"></i></a>
<a href="{ROOT_URL}bookmarks/deleteBookmark/{ID}" class="btn btn-sm btn-danger" role="button"><i class="glyphicon glyphicon-trash"></i></a>
</div>
</div>
</div>
</div>

View File

@ -1,21 +0,0 @@
<div class="panel panel-{color}">
<div class="panel-heading" data-target="#Collapse{ID}" data-toggle="collapse" aria-expanded="true" aria-controls="#Collapse{ID}">
{title}
</div>
<div id="Collapse{ID}" class="panel-collapse collapse in" style="width:100%; position: relative;" role="tabpanel" aria-expanded="true">
<div class="panel-body">
<ul class="list-group">
{bookmarkListRows}
</ul>
</div>
<div class="panel-footer">
<a href="{ROOT_URL}bookmarks/createBookmark/{ID}" class="btn btn-sm btn-success" role="button"><i class="glyphicon glyphicon-plus-sign"></i></a></td>
<span class="pull-right">
<a href="{ROOT_URL}bookmarks/bookmarks/{ID}" class="btn btn-sm btn-primary" role="button"><i class="glyphicon glyphicon-th-list"></i></a>
<a href="{ROOT_URL}bookmarks/folders/{ID}" class="btn btn-sm btn-primary" role="button"><i class="glyphicon glyphicon-info-sign"></i></a></td>
<a href="{ROOT_URL}bookmarks/editFolder/{ID}" class="btn btn-sm btn-warning" role="button"><i class="glyphicon glyphicon-edit"></i></a></td>
<a href="{ROOT_URL}bookmarks/deleteFolder/{ID}" class="btn btn-sm btn-danger" role="button"><i class="glyphicon glyphicon-trash"></i></a></td>
</span>
</div>
</div>
</div>

View File

@ -1,18 +0,0 @@
{LOOP}
<li class="list-group-item list-group-item-{color}">
<a href="{ROOT_URL}bookmarks/bookmarks/{ID}" class="btn btn-sm" role="button">{iconHtml}</a>
<a href="{url}" class="list-group"> {title}</a>
<span class="pull-right">
{hideBtn}
{archiveBtn}
<a href="{ROOT_URL}bookmarks/editBookmark/{ID}" class="btn btn-sm btn-warning" role="button"><i class="glyphicon glyphicon-edit"></i></a>
<a href="{ROOT_URL}bookmarks/deleteBookmark/{ID}" class="btn btn-sm btn-danger" role="button"><i class="glyphicon glyphicon-trash"></i></a>
</span>
</li>
{/LOOP}
{ALT}
<li class="list-group-item">
<a href="#" class="list-group">No Bookmarks</a>
</li>
{/ALT}

View File

@ -1,10 +0,0 @@
{LOOP}
<div class="col-xlg-6 col-lg-6 col-md-6 col-sm-6 col-xs-6">
{panel}
</div>
{/LOOP}
{ALT}
<div class="col-xlg-6 col-lg-6 col-md-6 col-sm-6 col-xs-6">
<p>no folders</p>
</div>
{/ALT}

View File

@ -1,16 +0,0 @@
{BookmarkBreadCrumbs}
<div class="row">
<div class="col-xlg-6 col-lg-6 col-md-6 col-sm-6 col-xs-6">
<legend>Unsorted Bookmarks</legend>
{bookmarksList}
</div>
<div class="col-xlg-6 col-lg-6 col-md-6 col-sm-6 col-xs-6">
<legend>Folders List</legend>
{foldersList}
</div>
</div>
<legend>Folders</legend>
<div class="row">
{folderPanels}
</div>

View File

@ -1,39 +0,0 @@
<legend>Create Folder</legend>
{BookmarkBreadCrumbs}
<form action="" method="post" class="form-horizontal">
<input type="hidden" name="token" value="{TOKEN}">
<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-control" name="title" id="title">
</div>
</div>
<div class="form-group">
<label for="description" class="col-lg-3 control-label">Description:</label>
<div class="col-lg-3">
<textarea class="form-control" name="description" maxlength="2000" rows="10" cols="50" id="description"></textarea>
</div>
</div>
{folderSelect}
<div class="form-group">
<label for="privacy" class="col-lg-3 control-label">Privacy</label>
<div class="col-lg-3 select-container" id="colorContainer">
<select id="privacy" name="privacy" class="form-control custom-select">
<option value="private">Private</option>
<option value="public">Public</option>
</select>
</div>
</div>
<div class="form-group">
<label for="color" class="col-lg-3 control-label">Color</label>
<div class="col-lg-3 select-container" id="colorContainer">
{colorSelect}
</div>
</div>
<div class="form-group">
<label for="submit" class="col-lg-3 control-label"></label>
<div class="col-lg-3">
<button name="submit" value="submit" type="submit" class="btn btn-lg btn-primary center-block ">Submit</button>
</div>
</div>
</form>

View File

@ -1,39 +0,0 @@
<legend>Edit Folder</legend>
{BookmarkBreadCrumbs}
<form action="" method="post" class="form-horizontal">
<input type="hidden" name="token" value="{TOKEN}">
<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-control" name="title" id="title" value="{title}">
</div>
</div>
<div class="form-group">
<label for="description" class="col-lg-3 control-label">Description:</label>
<div class="col-lg-3">
<textarea class="form-control" name="description" maxlength="2000" rows="10" cols="50" id="description">{description}</textarea>
</div>
</div>
{folderSelect}
<div class="form-group">
<label for="privacy" class="col-lg-3 control-label">Privacy</label>
<div class="col-lg-3 select-container" id="colorContainer">
<select id="privacy" name="privacy" class="form-control custom-select" value="{privacy}">
<option value="private">Private</option>
<option value="public">Public</option>
</select>
</div>
</div>
<div class="form-group">
<label for="color" class="col-lg-3 control-label">Color</label>
<div class="col-lg-3 select-container" id="colorContainer">
{colorSelect}
</div>
</div>
<div class="form-group">
<label for="submit" class="col-lg-3 control-label"></label>
<div class="col-lg-3">
<button name="submit" value="submit" type="submit" class="btn btn-lg btn-primary center-block ">Submit</button>
</div>
</div>
</form>

View File

@ -1,33 +0,0 @@
{BookmarkBreadCrumbs}
<table class="table table-striped">
<thead>
<tr>
<th style="width: 35%">Title</th>
<th style="width: 20%">Privacy</th>
<th style="width: 30%">Description</th>
<th style="width: 5%"></th>
<th style="width: 5%"></th>
<th style="width: 5%"></th>
</tr>
</thead>
<tbody>
{LOOP}
<tr>
<td align="center">{title}</td>
<td align="center">{privacy}</td>
<td>{description}</td>
<td><a href="{ROOT_URL}bookmarks/folders/{ID}" class="btn btn-sm btn-primary" role="button"><i class="glyphicon glyphicon-info-sign"></i></a></td>
<td><a href="{ROOT_URL}bookmarks/editFolder/{ID}" class="btn btn-sm btn-warning" role="button"><i class="glyphicon glyphicon-edit"></i></a></td>
<td><a href="{ROOT_URL}bookmarks/deleteFolder/{ID}" class="btn btn-sm btn-danger" role="button"><i class="glyphicon glyphicon-trash"></i></a></td>
</tr>
{/LOOP}
{ALT}
<tr>
<td align="center" colspan="7">
No results to show.
</td>
</tr>
{/ALT}
</tbody>
</table>
<a href="{ROOT_URL}bookmarks/createFolder" class="btn btn-sm btn-primary" role="button">Create</a>

View File

@ -1,47 +0,0 @@
{BookmarkBreadCrumbs}<br />
<div class="container col-md-4 col-lg-4">
<div class="row">
<div class="panel panel-{color}">
<div class="panel-heading">
<h3 class="panel-title">Bookmark Folder</h3>
</div>
<div class="panel-body">
<div class="row">
<div class="">
<table class="table table-user-primary">
<tbody>
<tr>
<td align="left" width="200"><b>Title</b></td>
<td align="right">{title}</td>
</tr>
<tr>
<td align="left" width="200"><b>Privacy</b></td>
<td align="right">{privacy}</td>
</tr>
<tr>
<td align="left" width="200"><b>Color</b></td>
<td align="right">{color}</td>
</tr>
<tr>
<td align="center" colspan="2"><b>Description</b></td>
</tr>
<tr>
<td colspan="2">{description}</td>
</tr>
<tr>
<td><b>Created</b></td>
<td align="right">{DTC}{createdAt}{/DTC}</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<div class="panel-footer">
<a href="{ROOT_URL}bookmarks/bookmarks/{ID}" class="btn btn-sm btn-primary" role="button"><i class="glyphicon glyphicon-th-list"></i></a>
<a href="{ROOT_URL}bookmarks/editFolder/{ID}" class="btn btn-sm btn-warning" role="button"><i class="glyphicon glyphicon-edit"></i></a>
<a href="{ROOT_URL}bookmarks/deleteFolder/{ID}" class="btn btn-sm btn-danger" role="button"><i class="glyphicon glyphicon-trash"></i></a>
</div>
</div>
</div>
</div>

View File

@ -1,5 +0,0 @@
<ul class="nav nav-tabs">
<li><a href="{ROOT_URL}bookmarks/index/">Dashboard</a></li>
<li><a href="{ROOT_URL}bookmarks/folders/">Folders</a></li>
</ul>
{userFolderTabs}

View File

@ -1,9 +0,0 @@
<ul class="nav nav-tabs">
<li><a href="{ROOT_URL}bookmarks/folders/">All</a></li>
{LOOP}
<li><a href="{ROOT_URL}bookmarks/bookmarks/{ID}">{title}</a></li>
{/LOOP}
{ALT}
<li></li>
{/ALT}
</ul>

View File

@ -41,7 +41,7 @@ class Bugreport extends Controller {
return Views::view( 'bugreport.create' ); return Views::view( 'bugreport.create' );
} }
$result = self::$bugreport->create( App::$activeUser->ID, Input::post( 'url' ), Input::post( 'ourl' ), Input::post( 'repeat' ), Input::post( 'entry' ) ); $result = self::$bugreport->create( App::$activeUser->ID, Input::post( 'url' ), Input::post( 'ourl' ), Input::post( 'repeat' ), Input::post( 'entry' ) );
if ( true === $result ) { 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.' ); 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' ); Redirect::to( 'home/index' );
} else { } else {

View File

@ -13,9 +13,8 @@
namespace TheTempusProject\Models; namespace TheTempusProject\Models;
use TheTempusProject\Bedrock\Functions\Check; use TheTempusProject\Bedrock\Functions\Check;
use TheTempusProject\Bedrock\Classes\Config;
use TheTempusProject\Canary\Bin\Canary as Debug; use TheTempusProject\Canary\Bin\Canary as Debug;
use TheTempusProject\Bedrock\Classes\CustomException; use TheTempusProject\Canary\Classes\CustomException;
use TheTempusProject\Classes\DatabaseModel; use TheTempusProject\Classes\DatabaseModel;
use TheTempusProject\Plugins\Bugreport as Plugin; use TheTempusProject\Plugins\Bugreport as Plugin;
use TheTempusProject\TheTempusProject as App; use TheTempusProject\TheTempusProject as App;

View File

@ -49,10 +49,11 @@ class Bugreport extends Plugin {
'default' => false, 'default' => false,
], ],
]; ];
public $footer_links = [ public $contact_footer_links = [
[ [
'text' => 'Bug Report', 'text' => 'Bug Report',
'url' => '{ROOT_URL}bugreport', 'url' => '{ROOT_URL}bugreport',
'filter' => 'loggedin',
], ],
]; ];
public $admin_links = [ public $admin_links = [

View File

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

View File

@ -1,64 +1,69 @@
<div class="container"> <div class="container py-4">
<div class="row"> <div class="row justify-content-center">
<div class="col-xs-12 col-sm-12 col-md-6 col-lg-6 col-xs-offset-0 col-sm-offset-0 col-md-offset-3 col-lg-offset-3 top-pad" > <div class="col-md-8">
<div class="panel panel-primary"> {ADMIN_BREADCRUMBS}
<div class="panel-heading"> <div class="card shadow">
<h3 class="panel-title">Bug Report</h3> <!-- Card Header -->
</div> <div class="card-header text-center bg-dark text-white">
<div class="panel-body"> <h3 class="card-title mb-0">Bug Report</h3>
<div class="row"> </div>
<div class=" col-md-12 col-lg-12 ">
<table class="table table-user-primary"> <!-- Card Body -->
<tbody> <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> <tr>
<td align="left" width="200"><b>ID</b></td> <th scope="row">Time submitted</th>
<td align="right">{ID}</td> <td>{DTC}{time}{/DTC}</td>
</tr> </tr>
<tr> <tr>
<td><b>Time submitted</b></td> <th scope="row">URL:</th>
<td align="right">{DTC}{time}{/DTC}</td> <td><a href="{URL}">{URL}</a></td>
</tr> </tr>
<tr> <tr>
<td><b>Submitted by</b></td> <th scope="row">Original URL</th>
<td align="right"><a href="{ROOT_URL}admin/users/view/{userID}">{submittedBy}</a></td> <td><a href="{OURL}">{OURL}</a></td>
</tr> </tr>
<tr> <tr>
<td><b>IP</b></td> <th scope="row">Multiple occurrences?</th>
<td align="right">{ip}</td> <td>{repeatText}</td>
</tr> </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> <tr>
<td><b>URL:</b></td> <th scope="row" colspan="2">Description:</th>
<td align="right"><a href="{URL}">{URL}</a></td>
</tr>
<tr>
<td><b>Original URL</b></td>
<td align="right"><a href="{OURL}">{OURL}</a></td>
</tr>
<tr>
<td><b>Multiple occurrences?</b></td>
<td align="right">{repeatText}</td>
</tr>
<tr>
<td align="center" colspan="2"><b>Description</b></td>
</tr> </tr>
<tr> <tr>
<td colspan="2">{description}</td> <td colspan="2">{description}</td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
</div> </div>
</div> </div>
</div>
<div class="panel-footer"> <!-- Admin Controls -->
<div class="card-footer text-center">
{ADMIN} {ADMIN}
<form action="{ROOT_URL}admin/bugreport/delete" method="post"> <form action="{ROOT_URL}admin/bugreport/delete" method="post">
<INPUT type="hidden" name="BR_" value="{ID}"/> <input type="hidden" name="BR_" value="{ID}">
<input type="hidden" name="token" value="{TOKEN}" /> <input type="hidden" name="token" value="{TOKEN}">
<button name="submit" value="submit" type="submit" class="btn btn-sm btn-danger"><i class="glyphicon glyphicon-remove"></i></button> <button name="submit" value="submit" type="submit" class="btn btn-sm btn-danger"><i class="fa fa-fw fa-trash"></i></button>
</form> </form>
{/ADMIN} {/ADMIN}
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>

View File

@ -1,36 +1,52 @@
<legend>Bug Report</legend> <div class="col-8 mx-auto p-4 rounded shadow-sm mb-5 context-main-bg mt-4 container">
<p>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> <h2 class="text-center mb-4">Bug Report</h2>
<p>We read each and every bug report submitted, and by submitting this form you allow us to send you a follow up email.</p> <hr>
<form action="" method="post" class="form-horizontal"> <p>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>
<label for="url">Page you were trying to reach:</label> <p>We read each and every bug report submitted, and by submitting this form you allow us to send you a follow-up email.</p>
<input type="url" name="url" id="url" class="form-control" aria-describedby="urlHelp"> <form action="" method="post">
<p id="urlHelp" class="form-text text-muted"> <!-- Page URL -->
What is the URL of the page you actually received the error on? (The URL is the website address. Example: {ROOT_URL}home) <div class="mb-3">
</p> <label for="url" class="form-label">Page you were trying to reach:</label>
<label for="ourl">Page you were on:</label> <input type="url" name="url" id="url" class="form-control" aria-describedby="urlHelp" required>
<input type="url" name="ourl" id="ourl" class="form-control" aria-describedby="ourlHelp"> <small id="urlHelp" class="form-text text-muted">
<p id="ourlHelp" class="form-text text-muted"> What is the URL of the page you actually received the error on? (The URL is the website address. Example: {ROOT_URL}home)
What is the URL of the page you were on before you received the error? (The URL is the website address. Example: {ROOT_URL}home/newhome) </small>
</p>
<label for="repeat">*Has this happened more than once?</label>
<div class="form-check">
<label class="form-check-label">
<input class="form-check-input" type="radio" name="repeat" id="repeat" value="false" checked>
No
</label>
</div>
<div class="form-check">
<label class="form-check-label">
<input class="form-check-input" type="radio" name="repeat" id="repeat" value="true">
Yes
</label>
</div>
<div class="form-group">
<label for="entry" class="col-lg-3 control-label">Describe the problem/error as best as you can: (max:2000 characters)</label>
<div class="col-lg-6">
<textarea class="form-control" name="entry" maxlength="2000" rows="10" cols="50" id="entry"></textarea>
</div> </div>
</div>
<input type="hidden" name="token" value="{TOKEN}"> <!-- Referrer URL -->
<button name="submit" value="submit" type="submit" class="btn btn-lg btn-primary center-block">Submit</button> <div class="mb-3">
</form> <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">
What is the URL of the page you were on before you received the error? (The URL is the website address. Example: {ROOT_URL}home/newhome)
</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 problem/error as best as you can: (max: 2000 characters)</label>
<textarea class="form-control" name="entry" id="entry" rows="6" maxlength="2000" required></textarea>
</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>

View File

@ -1,7 +0,0 @@
<?php
# bugTracker
define( 'TRACKER_STATUS_IN_PROGRESS', 'In Progress' );
define( 'TRACKER_STATUS_TESTING', 'Needs Testing' );
define( 'TRACKER_STATUS_NEW', 'New' );
define( 'TRACKER_STATUS_FIXED', 'Fixed' );
define( 'TRACKER_STATUS_CLOSED', 'Closed' );

View File

@ -1,206 +0,0 @@
<?php
/**
* app/plugins/bugtracker/controllers/admin/bugtracker.php
*
* This is the Bug-Tracker admin controller.
*
* @package TP BugTracker
* @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\Upload;
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\Houdini\Classes\Forms as FormGen;
use TheTempusProject\Classes\AdminController;
use TheTempusProject\Classes\Forms;
use TheTempusProject\Models\Bugtracker as BugtrackerModel;
use TheTempusProject\TheTempusProject as App;
use TheTempusProject\Models\Comments;
class Bugtracker extends AdminController {
protected static $tracker;
protected static $comments;
public function __construct() {
parent::__construct();
self::$tracker = new BugtrackerModel;
self::$title = 'Admin - Bug-Tracker';
$view = Navigation::activePageSelect( 'nav.admin', '/admin/bugtracker' );
Components::set( 'ADMINNAV', $view );
}
public function index( $data = null ) {
Views::view( 'bugtracker.admin.list', self::$tracker->list() );
}
public function create( $data = null ) {
$form = '';
$form .= FormGen::getFormFieldHtml( 'title', 'Title', 'text', Input::post('title') );
$form .= FormGen::getFormFieldHtml( 'description', 'Description', 'block', Input::post('description') );
$form .= FormGen::getFormFieldHtml( 'bugImage', 'Screenshot', 'file', '' );
$form .= FormGen::getFormFieldHtml( 'url', 'URL (if possible)', 'text', '' );
$form .= FormGen::getFormFieldHtml( 'repeat', 'Has this happened more than once', 'radio' );
Components::set( 'TRACKER_FORM', $form );
if ( !Input::exists( 'submit' ) ) {
return Views::view( 'bugtracker.admin.create' );
}
if ( !Forms::check( 'newBugTracker' ) ) {
Issues::add( 'error', [ 'There was an error with your request.' => Check::userErrors() ] );
return Views::view( 'bugtracker.admin.create' );
}
$image = '';
if ( Input::exists( 'bugImage' ) ) {
$folder = IMAGE_UPLOAD_DIRECTORY . App::$activeUser->username . DIRECTORY_SEPARATOR;
if ( !Upload::image( 'bugImage', $folder ) ) {
Issues::add( 'error', [ 'There was an error with your upload.' => Check::systemErrors() ] );
} else {
$route = str_replace( APP_ROOT_DIRECTORY, '', $folder );
$image = $route . Upload::last();
}
}
$result = self::$tracker->create(
App::$activeUser->ID,
Input::post( 'url' ),
$image,
Input::post( 'repeat' ),
Input::post( 'description' ),
Input::post( 'title' ),
);
if ( $result ) {
Issues::add( 'success', 'Your tracker 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' ) ) {
$bug = self::$tracker->findById( $data );
$statusList = [
TRACKER_STATUS_IN_PROGRESS => TRACKER_STATUS_IN_PROGRESS,
TRACKER_STATUS_TESTING => TRACKER_STATUS_TESTING,
TRACKER_STATUS_NEW => TRACKER_STATUS_NEW,
TRACKER_STATUS_FIXED => TRACKER_STATUS_FIXED,
TRACKER_STATUS_CLOSED => TRACKER_STATUS_CLOSED,
];
$form = '';
$form .= FormGen::getFormFieldHtml( 'title', 'Title', 'text', $bug->title );
$form .= FormGen::getFormFieldHtml( 'description', 'Description', 'block', $bug->description );
$form .= FormGen::getFormFieldHtml( 'bugImage', 'Screenshot', 'file', $bug->image );
$form .= FormGen::getFormFieldHtml( 'url', 'Page you were on', 'text', $bug->url );
$form .= FormGen::getFormFieldHtml( 'repeat', 'Has this happened more than once', 'radio', $bug->repeatable );
$form .= FormGen::getFormFieldHtml( 'status', 'Status', 'customSelect', $bug->status, $statusList );
Components::set( 'TRACKER_FORM', $form );
return Views::view( 'bugtracker.admin.edit', $bug );
}
if ( !Forms::check( 'editBugTracker' ) ) {
Issues::add( 'error', [ 'There was an error with your form.' => Check::userErrors() ] );
return $this->index();
}
$image = '';
if ( Input::exists( 'bugImage' ) ) {
$folder = IMAGE_UPLOAD_DIRECTORY . App::$activeUser->username . DIRECTORY_SEPARATOR;
if ( !Upload::image( 'bugImage', $folder ) ) {
Issues::add( 'error', [ 'There was an error with your upload.' => Check::systemErrors() ] );
} else {
$image = $folder . Upload::last();
}
}
$tracker = self::$tracker->updateTracker(
$data,
Input::post( 'url' ),
$image,
Input::post( 'repeat' ),
Input::post( 'description' ),
Input::post( 'title' ),
Input::post( 'status' )
);
if ( true === $tracker ) {
Issues::add( 'success', 'Tracker Updated.' );
return $this->index();
}
Issues::add( 'error', 'There was an error with your request.' );
$this->index();
}
public function view( $id = null ) {
if ( empty( self::$comments ) ) {
self::$comments = new Comments;
}
$data = self::$tracker->findById( $id );
if ( $data === false ) {
Issues::add( 'error', 'Tracker not found.' );
return $this->index();
}
if ( Input::exists( 'contentId' ) ) {
$this->comments( 'post', Input::post( 'contentId' ) );
}
if ( empty( self::$comments ) ) {
self::$comments = new Comments;
}
Components::set( 'CONTENT_ID', $id );
Components::set( 'COMMENT_TYPE', 'admin/bugtracker' );
Components::set( 'count', self::$comments->count( 'tracker', $id ) );
Components::set( 'NEWCOMMENT', Views::simpleView( 'comments.create' ) );
Components::set( 'COMMENTS', Views::simpleView( 'comments.list', self::$comments->display( 10, 'tracker', $id ) ) );
Views::view( 'bugtracker.admin.view', $data );
}
public function delete( $data = null ) {
if ( $data == null ) {
if ( Input::exists( 'T_' ) ) {
$data = Input::post( 'T_' );
}
}
if ( !self::$tracker->delete( $data ) ) {
Issues::add( 'error', 'There was an error with your request.' );
} else {
Issues::add( 'success', 'Tracker has been deleted' );
}
$this->index();
}
public function comments( $sub = null, $data = null ) {
if ( empty( self::$comments ) ) {
self::$comments = new Comments;
}
if ( empty( $sub ) || empty( $data ) ) {
Session::flash( 'error', 'Whoops, try again.' );
Redirect::to( 'admin/bugtracker' );
}
switch ( $sub ) {
case 'post':
$content = self::$tracker->findById( $data );
if ( empty( $content ) ) {
Session::flash( 'error', 'Unknown Post.' );
Redirect::to( 'admin/bugtracker' );
}
return self::$comments->formPost( 'tracker', $content, 'admin/bugtracker/view/' );
case 'edit':
$content = self::$comments->findById( $data );
if ( empty( $content ) ) {
Session::flash( 'error', 'Unknown Comment.' );
Redirect::to( 'admin/bugtracker' );
}
return self::$comments->formEdit( 'tracker', $content, 'admin/bugtracker/view/' );
case 'delete':
$content = self::$comments->findById( $data );
if ( empty( $content ) ) {
Session::flash( 'error', 'Unknown Comment.' );
Redirect::to( 'admin/bugtracker' );
}
return self::$comments->formDelete( 'tracker', $content, 'admin/bugtracker/view/' );
}
}
}

View File

@ -1,92 +0,0 @@
<?php
/**
* app/plugins/bugtracker/forms.php
*
* This houses all of the form checking functions for this plugin.
*
* @package TP BugTracker
* @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 BugTrackerForms extends Forms {
/**
* Adds these functions to the form list.
*/
public function __construct() {
self::addHandler( 'newBugTracker', __CLASS__, 'newBugTracker' );
self::addHandler( 'editBugTracker', __CLASS__, 'editBugTracker' );
}
/**
* Validates the bug tracker create form.
*
* @return {bool}
*/
public static function newBugTracker() {
if ( !empty(Input::post( 'url' )) && !self::url( Input::post( 'url' ) ) ) {
self::addUserError( 'Invalid url: <code>' . Input::post( 'url' ) . '</code>' );
return false;
}
if ( !self::tf( Input::post( 'repeat' ) ) ) {
self::addUserError( 'Invalid repeat value: ' . Input::post( 'repeat' ) );
return false;
}
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( 'description' ) ) {
self::addUserError( 'You must specify a description' );
return false;
}
// if ( !self::token() ) {
// return false;
// }
return true;
}
/**
* Validates the bug tracker create form.
*
* @return {bool}
*/
public static function editBugTracker() {
if ( !empty(Input::post( 'url' )) && !self::url( Input::post( 'url' ) ) ) {
self::addUserError( 'Invalid url.' . Input::post( 'url' ) );
return false;
}
if ( !self::tf( Input::post( 'repeat' ) ) ) {
self::addUserError( 'Invalid repeat value.' );
return false;
}
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( 'description' ) ) {
self::addUserError( 'You must specify a description' );
return false;
}
// if ( !self::token() ) {
// return false;
// }
return true;
}
}
new BugTrackerForms;

View File

@ -1,142 +0,0 @@
<?php
/**
* app/plugins/bugtracker/models/bugtracker.php
*
* This class is used for the manipulation of the bugs database table.
*
* @package TP BugTracker
* @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\Bin\Canary as Debug;
use TheTempusProject\Bedrock\Classes\CustomException;
use TheTempusProject\Classes\DatabaseModel;
use TheTempusProject\TheTempusProject as App;
use TheTempusProject\Plugins\Bugtracker as Plugin;
class Bugtracker extends DatabaseModel {
public $tableName = 'bugs';
public $databaseMatrix = [
[ 'userID', 'int', '11' ],
[ 'time', 'int', '10' ],
[ 'title', 'varchar', '128' ],
[ 'status', 'varchar', '64' ],
[ 'description', 'text', '' ],
[ 'image', 'varchar', '256' ],
[ 'repeatable', 'varchar', '5' ],
[ 'url', 'varchar', '256' ],
];
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 = ( 'false' == $instance->repeatable ? 'no' : 'yes' );
$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, $image, $repeat, $description, $title ) {
if ( !$this->plugin->checkEnabled() ) {
Debug::info( 'Bug Tracking is disabled in the config.' );
return false;
}
if ( !Check::dataTitle( $title ) ) {
Debug::info( 'bugTracker: illegal title.' );
return false;
}
$fields = [
'userID' => App::$activeUser->ID,
'time' => time(),
'title' => $title,
'status' => TRACKER_STATUS_NEW,
'description' => $description,
'image' => $image,
'repeatable' => $repeat,
'url' => $url,
];
if ( !self::$db->insert( $this->tableName, $fields ) ) {
new CustomException( $this->tableName );
return false;
}
return self::$db->lastId();
}
public function updateTracker( $ID, $url, $image, $repeat, $description, $title, $status ) {
if ( !$this->plugin->checkEnabled() ) {
Debug::info( 'Bug Tracking is disabled in the config.' );
return false;
}
if ( empty( self::$log ) ) {
self::$log = new Log;
}
if ( !Check::id( $ID ) ) {
Debug::info( 'bugTracker: illegal ID.' );
return false;
}
if ( !Check::dataTitle( $title ) ) {
Debug::info( 'bugTracker: illegal title.' );
return false;
}
$fields = [
'url' => $url,
'description' => $description,
'repeatable' => $repeat,
'title' => $title,
'status' => $status,
];
if ( !empty( $image ) ) {
$fields['image'] = $image;
}
if ( !self::$db->update( $this->tableName, $ID, $fields ) ) {
new CustomException( $this->tableName );
Debug::error( "Tracker Post: $ID not updated: $fields" );
return false;
}
self::$log->admin( "Updated Tracker Post: $ID" );
return true;
}
}

View File

@ -1,48 +0,0 @@
<?php
/**
* app/plugins/bugtracker/plugin.php
*
* This houses all of the main plugin info and functionality.
*
* @package TP BugTracker
* @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 Bugtracker extends Plugin {
public $pluginName = 'TP BugTracker';
public $pluginAuthor = 'JoeyK';
public $pluginWebsite = 'https://TheTempusProject.com';
public $modelVersion = '1.0';
public $pluginVersion = '3.0';
public $pluginDescription = '';
public $configName = 'bugtracker';
public $configMatrix = [
'enabled' => [
'type' => 'radio',
'pretty' => 'Enable Bug tracking.',
'default' => true,
],
];
public $permissionMatrix = [
'bugTrack' => [
'pretty' => 'Can Track Bugs',
'default' => false,
],
];
public $admin_links = [
[
'text' => '<i class="fa fa-fw fa-bug"></i> Bug Tracker',
'url' => '{ROOT_URL}admin/bugtracker',
],
];
}

View File

@ -1,6 +0,0 @@
<form action="" method="post" class="form-horizontal" enctype="multipart/form-data">
<legend>Create Bug Tracker</legend>
{TRACKER_FORM}
<input type="hidden" name="token" value="{TOKEN}">
<button name="submit" value="submit" type="submit" class="btn btn-lg btn-primary center-block">Save</button><br>
</form>

View File

@ -1,30 +0,0 @@
<legend>New Bugs</legend>
<table class="table table-striped">
<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>{description}</td>
<td><a href="{ROOT_URL}admin/bugtracker/view/{ID}" class="btn btn-sm btn-primary" role="button"><i class="glyphicon glyphicon-open"></i></a></td>
<td><a href="{ROOT_URL}admin/bugtracker/edit/{ID}" class="btn btn-sm btn-warning" role="button"><i class="glyphicon glyphicon-edit"></i></a></td>
<td width="30px"><a href="{ROOT_URL}admin/bugtracker/delete/{ID}" class="btn btn-sm btn-danger" role="button"><i class="glyphicon glyphicon-trash"></i></a></td>
</tr>
{/LOOP}
{ALT}
<tr>
<td align="center" colspan="5">
No results to show.
</td>
</tr>
{/ALT}
</tbody>
</table>

View File

@ -1,6 +0,0 @@
<form action="" method="post" class="form-horizontal" enctype="multipart/form-data">
<legend>Edit Bug Tracker</legend>
{TRACKER_FORM}
<input type="hidden" name="token" value="{TOKEN}">
<button name="submit" value="submit" type="submit" class="btn btn-lg btn-primary center-block">Save</button><br>
</form>

View File

@ -1,45 +0,0 @@
<legend>Bug Trackers</legend>
{PAGINATION}
<form action="{ROOT_URL}admin/bugtracker/delete" method="post">
<table class="table table-striped">
<thead>
<tr>
<th style="width: 30%">Title</th>
<th style="width: 40%">Description</th>
<th style="width: 10%">Status</th>
<th style="width: 5%">Time Submitted</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="T_[]"/>
</th>
</tr>
</thead>
<tbody>
{LOOP}
<tr>
<td><a href="{ROOT_URL}admin/bugtracker/view/{ID}">{title}</a></td>
<td>{description}</td>
<td>{status}</td>
<td align="center">{DTC}{time}{/DTC}</td>
<td><a href="{ROOT_URL}admin/bugtracker/edit/{ID}" class="btn btn-sm btn-warning" role="button"><i class="glyphicon glyphicon-edit"></i></a></td>
<td><a href="{ROOT_URL}admin/bugtracker/delete/{ID}" class="btn btn-sm btn-danger" role="button"><i class="glyphicon glyphicon-trash"></i></a></td>
<td>
<input type="checkbox" value="{ID}" name="T_[]">
</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">Delete</button>
<a href="{ROOT_URL}admin/bugtracker/create" class="btn btn-sm btn-primary" role="button">Create</a>
</form>
<br />
<a href="{ROOT_URL}admin/bugtracker/clear">clear all</a>

View File

@ -1,74 +0,0 @@
<div class="container">
<div class="row">
<div class="col-xs-12 col-sm-12 col-md-6 col-lg-6 col-xs-offset-0 col-sm-offset-0 col-md-offset-3 col-lg-offset-3 top-pad" >
<div class="panel panel-primary">
<div class="panel-heading">
<h3 class="panel-title">Bug Tracker</h3>
</div>
<div class="panel-body">
<div class="row">
<div class=" col-md-12 col-lg-12 ">
<table class="table table-user-primary">
<tbody>
<tr>
<td align="center" colspan="2">Title</td>
</tr>
<tr>
<td colspan="2">{title}</td>
</tr>
<tr>
<td align="center" colspan="2">Description</td>
</tr>
<tr>
<td colspan="2">{description}</td>
</tr>
</tr>
<tr>
<td colspan="2">
<div align="center">
<a href="{ROOT_URL}{image}"><img alt="Screenshot" src="{ROOT_URL}{image}" class="img-responsive"></a>
</div>
</td>
</tr>
<tr>
<td align="left" width="200">Status</td>
<td align="right">{status}</td>
</tr>
<tr>
<td align="left" width="200">ID</td>
<td align="right">{ID}</td>
</tr>
<tr>
<td>Time submitted</td>
<td align="right">{DTC}{time}{/DTC}</td>
</tr>
<tr>
<td>Submitted by</td>
<td align="right"><a href="{ROOT_URL}admin/users/view/{userID}">{submittedBy}</a></td>
</tr>
<tr>
<td>URL:</td>
<td align="right">{URL}</td>
</tr>
<tr>
<td>Multiple occurrences?</td>
<td align="right">{repeatText}</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<div class="panel-footer">
{ADMIN}
<a href="{ROOT_URL}admin/bugtracker/delete/{ID}" class="btn btn-md btn-danger" role="button">Delete</a>
<a href="{ROOT_URL}admin/bugtracker/edit/{ID}" class="btn btn-md btn-warning" role="button">Edit</a>
<a href="{ROOT_URL}admin/comments/tracker/{ID}" class="btn btn-md btn-primary" role="button">View Comments</a>
{/ADMIN}
</div>
</div>
{COMMENTS}
{NEWCOMMENT}
</div>
</div>
</div>

View File

@ -1,593 +0,0 @@
<?php
/**
* app/plugins/calendar/controllers/calendar.php
*
* This is the calendar controller.
*
* @package TP Calendar
* @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\Bedrock\Functions\Input;
use TheTempusProject\Bedrock\Functions\Session;
use TheTempusProject\Bedrock\Functions\Date;
use TheTempusProject\Classes\Controller;
use TheTempusProject\Houdini\Classes\Forms as HoudiniForms;
use TheTempusProject\Classes\Forms;
use TheTempusProject\Houdini\Classes\Issues;
use TheTempusProject\Houdini\Classes\Views;
use TheTempusProject\Houdini\Classes\Template;
use TheTempusProject\Houdini\Classes\Components;
use TheTempusProject\Hermes\Functions\Redirect;
use TheTempusProject\TheTempusProject as App;
use TheTempusProject\Models\Events;
use TheTempusProject\Models\Calendars;
use TheTempusProject\Houdini\Classes\Navigation;
class Calendar extends Controller {
protected static $events;
protected static $calendars;
protected static $defaultView;
protected static $defaultCalendar;
protected static $selectedDate;
public function __construct() {
if ( !App::$isLoggedIn ) {
Session::flash( 'notice', 'You must be logged in to create or manage calendars.' );
return Redirect::home();
}
parent::__construct();
self::$title = 'Calendar - {SITENAME}';
self::$pageDescription = 'The {SITENAME} calendar is where you can find various features for creating and managing events and schedules.';
self::$events = new Events;
self::$calendars = new Calendars;
$prefs = App::$activePrefs;
if ( ! empty( $prefs['calendarPreference'] ) ) {
$view = $prefs['calendarPreference'];
} else {
Session::flash( 'info', 'You can select a default view for this page in your user settings in the top right.' );
$view = 'byMonth';
}
self::$defaultView = $view;
self::$selectedDate = intval( strtotime( Date::determineDateInput() ) );
Template::setTemplate( 'calendar' );
// Date Dropdown
$dateDropdownView = Views::simpleView('calendar.nav.dateDropdown', Date::getDateBreakdown( self::$selectedDate ) );
Components::set( 'dateDropdown', $dateDropdownView );
// Calendar Top Tabs
$calendarTabs = Views::simpleView('calendar.nav.topTabs');
$tabsView = Navigation::activePageSelect( $calendarTabs, Input::get( 'url' ), false, true );
// must be done after top-nav gets selected
$calendarDropdown = Views::simpleView('calendar.nav.calendarDropdown', self::$calendars->simpleObjectByUser() );
$calendarDropdownView = Navigation::activePageSelect( $calendarDropdown, Input::get( 'url' ), false, true ); // add notebookID as second param
Components::set( 'calendarDropdown', $calendarDropdownView);
// must be done after dropdown gets added
Components::set( 'CalendarNav', Template::parse( $calendarTabs ) );
}
public function index() {
if ( method_exists( $this, self::$defaultView ) ) {
Redirect::to( 'calendar/'.self::$defaultView.'/' );
} else {
Redirect::to( 'calendar/byMonth/' );
}
}
/**
* Events
*/
public function event( $id = null ) {
$event = $this->findEventOrFail( $id );
Views::view( 'calendar.events.view', $event );
}
public function createEvent() {
// get the url calendar input
$newCalendar = Input::get('calendar_id') ? Input::get('calendar_id') : '';// for loading the form
$calendarSelect = HoudiniForms::getFormFieldHtml( 'calendar_id', 'Calendar', 'select', $newCalendar, self::$calendars->simpleByUser() );
Components::set( 'calendarSelect', $calendarSelect );
$repeatSelect = HoudiniForms::getFormFieldHtml( 'repeats', 'Frequency', 'select', 'none', self::$events->repeatOptions );
Components::set( 'repeatSelect', $repeatSelect );
$dateSelect = Views::simpleView( 'calendar.dateSelect', $this->getEventBreakdown( self::$selectedDate ) );
Components::set( 'dateSelect', $dateSelect );
if ( ! Input::exists() ) {
return Views::view( 'calendar.events.create' );
}
/** Attempt to save the form data somewhat */
// get the form calendar input
$calendarID = Input::post('calendar_id') ? Input::post('calendar_id') : ''; // for submitting the form
$calendarSelect = HoudiniForms::getFormFieldHtml( 'calendar_id', 'Calendar', 'select', $calendarID, self::$calendars->simpleByUser() );
Components::set( 'calendarSelect', $calendarSelect );
$repeatSelect = HoudiniForms::getFormFieldHtml( 'repeats', 'Frequency', 'select', Input::post('repeats'), self::$events->repeatOptions );
Components::set( 'repeatSelect', $repeatSelect );
if ( ! Forms::check( 'createEvent' ) ) {
Issues::add( 'error', [ 'There was an error with your form.' => Check::userErrors() ] );
return Views::view( 'calendar.events.create' );
}
$event_id = self::$events->create(
$calendarID,
Input::post('title'),
Date::applyUtcToDate( Date::determineDateInput() ),
Input::post('description'),
Input::post('location'),
Input::post('repeats'),
Input::post('color'),
0,
);
if ( ! $event_id ) {
Issues::add( 'error', [ 'There was an error creating your event.' => Check::userErrors() ] );
return Views::view( 'calendar.events.create' );
}
if ( Input::post('repeats') != 'none' ) {
$childrens = self::$events->generateChildEvents( $event_id, true, true );
}
Session::flash( 'success', 'Your Event has been created.' );
Redirect::to( 'calendar/'.self::$defaultView.'/'. $calendarID );
}
public function editEvent( $id = null ) {
$calendar = Input::exists('calendar_id') ? Input::post('calendar_id') : '';
$event = self::$events->findById( $id );
$readableDate = date('Y-m-d H:i:s', $event->event_time);
if ( $event == false ) {
Session::flash( 'error', 'Event not found.' );
return Redirect::to( 'calendar/'.self::$defaultView.'/' );
}
if ( $event->createdBy != App::$activeUser->ID ) {
Session::flash( 'error', 'You do not have permission to modify this event.' );
return Redirect::to( 'calendar/'.self::$defaultView.'/' );
}
$repeatSelect = HoudiniForms::getFormFieldHtml( 'repeats', 'Frequency', 'select', $event->repeats, self::$events->repeatOptions );
Components::set( 'repeatSelect', $repeatSelect );
// there should be a way to do this natively
$event_at = Date::getDateBreakdown( $event->event_time, true );
// $readableDate = date('Y-m-d H:i:s', $readableDate);
$dateSelect = Views::simpleView( 'calendar.dateSelect', $event_at );
Components::set( 'dateSelect', $dateSelect );
Components::set( 'color', $event->color );
if ( ! Input::exists( 'submit' ) ) {
return Views::view( 'calendar.events.edit', $event );
}
if ( ! Forms::check( 'editEvent' ) ) {
Issues::add( 'error', [ 'There was an error updating your event.' => Check::userErrors() ] );
return Views::view( 'calendar.events.edit', $event );
}
$result = self::$events->update(
$id,
Input::post('title'),
Date::applyUtcToDate( Date::determineDateInput() ),
Input::post('description'),
Input::post('location'),
Input::post('repeats'),
Input::post('color'),
);
if ( Input::post('repeats') != 'none' && $event->parent_id == 0 ) {
$childrens = self::$events->generateChildEvents( $event->ID, true, true );
}
if ( ! $result ) {
Issues::add( 'error', [ 'There was an error updating your event.' => Check::userErrors() ] );
return Views::view( 'calendar.events.edit', $event->ID );
}
Session::flash( 'success', 'Your Event has been updated.' );
Redirect::to( 'calendar/'.self::$defaultView.'/'. $event->calendar_id );
}
public function deleteEvent( $id = null ) {
$event = self::$events->findById( $id );
if ( $event == false ) {
Issues::add( 'error', 'Event not found.' );
return $this->index();
}
if ( $event->createdBy != App::$activeUser->ID ) {
Issues::add( 'error', 'You do not have permission to modify this event.' );
return $this->index();
}
$result = self::$events->delete( $id );
if ( !$result ) {
Session::flash( 'error', 'There was an error deleting the event(s)' );
} else {
Session::flash( 'success', 'Event deleted' );
}
return $this->index();
}
/**
* Calendars
*/
public function calendar( $id = null ) {
$calendar = $this->findCalendarOrFail( $id );
Views::view( 'calendar.calendar.view', $calendar );
}
public function createCalendar() {
$timezoneSelect = HoudiniForms::getTimezoneHtml( Date::getTimezone() );
Components::set( 'timezoneSelect', $timezoneSelect );
if ( ! Input::exists() ) {
return Views::view( 'calendar.calendar.create' );
}
if ( ! Forms::check( 'createCalendar' ) ) {
Issues::add( 'error', [ 'There was an error creating your calendar111.' => Check::userErrors() ] );
return Views::view( 'calendar.calendar.create' );
}
$calendar = self::$calendars->create( Input::post('title'), Input::post('description'), Input::post('timezone'), Input::post('color') );
if ( ! $calendar ) {
return Views::view( 'calendar.calendar.create' );
}
Session::flash( 'success', 'Your Calendar has been created.' );
Redirect::to( 'calendar/'.self::$defaultView.'/');
}
public function editCalendar( $id = null ) {
$calendar = $this->findCalendarOrFail( $id );
$timezoneSelect = HoudiniForms::getTimezoneHtml( Date::getTimezone() );
Components::set( 'timezoneSelect', $timezoneSelect );
Components::set( 'color', $calendar->color );
if ( ! Input::exists( 'submit' ) ) {
return Views::view( 'calendar.calendar.edit', $calendar );
}
if ( !Forms::check( 'editCalendar' ) ) {
Issues::add( 'error', [ 'There was an error editing your calendar.' => Check::userErrors() ] );
return Views::view( 'calendar.calendar.edit', $calendar );
}
$result = self::$calendars->update( $id, Input::post('title'), Input::post('description'), Input::post('timezone'), Input::post('color') );
if ( !$result ) {
Issues::add( 'error', [ 'There was an error updating your calendar.' => Check::userErrors() ] );
return Views::view( 'calendar.calendar.edit', $calendar );
}
Session::flash( 'success', 'Your Calendar has been updated.' );
Redirect::to( 'calendar/'.self::$defaultView.'/'. $calendar->ID );
}
public function deleteCalendar( $id = null ) {
$calendar = self::$calendars->findById( $id );
if ( $calendar == false ) {
Issues::add( 'error', 'Calendar not found.' );
return $this->index();
}
if ( $calendar->createdBy != App::$activeUser->ID ) {
Issues::add( 'error', 'You do not have permission to modify this calendar.' );
return $this->index();
}
$results = self::$events->deleteByCalendar( $id );
$result = self::$calendars->delete( $id );
if ( !$result ) {
Session::flash( 'error', 'There was an error deleting the calendar(s)' );
} else {
Session::flash( 'success', 'Calendar deleted' );
}
Redirect::to( 'calendar/'.self::$defaultView.'/' );
}
/**
* Views
*/
public function byDay( $id = 0 ) {
// Set base components
Components::set( 'currentView', __FUNCTION__ );
Components::set( 'calendarID', $id );
// Set components for next / back arrows
$nextDayMonth = date( 'M', strtotime( 'tomorrow', self::$selectedDate ) );
$nextDay = date( 'd', strtotime( 'tomorrow', self::$selectedDate ) );
$lastDayMonth = date( 'M', strtotime( 'yesterday', self::$selectedDate ) );
$lastDay = date( 'd', strtotime( 'yesterday', self::$selectedDate ) );
Components::set( 'nextDayMonth', $nextDayMonth );
Components::set( 'nextDay', $nextDay );
Components::set( 'lastDayMonth', $lastDayMonth );
Components::set( 'lastDay', $lastDay );
// Get the data
$dayArray = self::$events->dayArray( $id, self::$selectedDate );
// build the results
$selectedHour = Date::getCurrentHour();
$day = date( 'd', self::$selectedDate );
$month = date( 'M', self::$selectedDate );
$year = date( 'Y', self::$selectedDate );
$fullDay = [];
foreach ( $dayArray as $hour => $events ) {
Components::set( 'hour', $hour );
$hourCell = new \stdClass();
$hourCell->hour = $hour;
if ( $hour == $selectedHour ) {
$hourCell->hourSelected = ' hour-active';
} else {
$hourCell->hourSelected = '';
}
$hourCell->day = $day;
$hourCell->month = $month;
$hourCell->year = $year;
$hourCell->EventCells = Views::simpleView( 'calendar.nav.row', $events );
$fullDay[] = $hourCell;
}
// Calendar Top Tabs
Components::unset( 'calendarDropdown');
$calendarTabs = Views::simpleView('calendar.nav.topTabs');
$tabsView = Navigation::activePageSelect( $calendarTabs, '/calendar/byDay/', false, true );
// must be done after top-nav gets selected
$calendarDropdown = Views::simpleView('calendar.nav.calendarDropdown', self::$calendars->simpleObjectByUser() );
$calendarDropdownView = Navigation::activePageSelect( $calendarDropdown, Input::get( 'url' ), false, true ); // add notebookID as second param
Components::set( 'calendarDropdown', $calendarDropdownView);
Components::set( 'CalendarNav', Template::parse( $tabsView ) );
Components::set( 'activeDate', date( 'F d, Y', self::$selectedDate ) );
Views::view( 'calendar.byDay', $fullDay );
}
public function byWeek( $id = 0 ) {
// Set base components
Components::set( 'currentView', __FUNCTION__ );
Components::set( 'calendarID', $id );
// Set components for next / back arrows
$lastWeekMonth = date( 'M', strtotime( '-7 days', self::$selectedDate) );
$lastWeekDay = date( 'd', strtotime( '-7 days', self::$selectedDate) );
$lastWeekYear = date( 'Y', strtotime( '-7 days', self::$selectedDate) );
$nextWeekMonth = date( 'M', strtotime( '+7 days', self::$selectedDate) );
$nextWeekDay = date( 'd', strtotime( '+7 days', self::$selectedDate) );
$nextWeekYear = date( 'Y', strtotime( '+7 days', self::$selectedDate) );
Components::set( 'lastWeekMonth', $lastWeekMonth );
Components::set( 'lastWeek', $lastWeekDay );
Components::set( 'lastYear', $lastWeekYear );
Components::set( 'nextWeekMonth', $nextWeekMonth );
Components::set( 'nextWeek', $nextWeekDay );
Components::set( 'nextYear', $nextWeekYear );
// Get the data
$weekArray = self::$events->weekArray( $id, self::$selectedDate );
// build the results
$presentMonth = date( 'F', self::$selectedDate );
$presentDay = date( 'd', self::$selectedDate );
foreach ( $weekArray as $week => $days ) {
$weekFormat = [];
foreach ( $days as $day => $events ) {
if ( empty( $first ) ) {
$first = true;
if ( intval( $presentDay ) >= $day ) {
$month = date( 'F', self::$selectedDate );
} else {
$month = date( 'F', strtotime( '-7 days', self::$selectedDate ) );
}
}
if ( $day == '01' ) {
$month = date( 'F', strtotime( '+7 days', self::$selectedDate ) );
}
Components::set( 'currentMonth', $month );
Components::set( 'currentDay', $day );
$dayCell = new \stdClass();
if ( $presentDay == $day && $presentMonth == $month ) {
$dayCell->highlightedDate = ' today';
} else {
$dayCell->highlightedDate = '';
}
$dayCell->day = $day;
$dayCell->dayEventList = Views::simpleView( 'calendar.nav.cell', $events );
$weekFormat[] = $dayCell;
}
$weekView = Views::simpleView( 'calendar.nav.week', $weekFormat );
Components::set( $week . 'Element', $weekView );
}
// Calendar Top Tabs
Components::unset( 'calendarDropdown');
$calendarTabs = Views::simpleView('calendar.nav.topTabs');
$tabsView = Navigation::activePageSelect( $calendarTabs, '/calendar/byWeek/', false, true );
// must be done after top-nav gets selected
$calendarDropdown = Views::simpleView('calendar.nav.calendarDropdown', self::$calendars->simpleObjectByUser() );
$calendarDropdownView = Navigation::activePageSelect( $calendarDropdown, Input::get( 'url' ), false, true ); // add notebookID as second param
Components::set( 'calendarDropdown', $calendarDropdownView);
Components::set( 'CalendarNav', Template::parse( $tabsView ) );
Components::set( 'dateWeek', 'Week of ' . date( 'F d, Y', self::$selectedDate ) );
Views::view( 'calendar.byWeek' );
}
public function byMonth( $id = 0 ) {
// Set base components
Components::set( 'currentView', __FUNCTION__ );
Components::set( 'calendarID', $id );
// Set components for next / back arrows
$month = date( 'F', strtotime( 'first day of last month', self::$selectedDate ) );
$lastMonthYear = date( 'Y', strtotime( 'first day of last month', self::$selectedDate ) );
$nextMonth = date( 'F', strtotime( 'first day of next month', self::$selectedDate ) );
$nextMonthYear = date( 'Y', strtotime( 'first day of next month', self::$selectedDate ) );
Components::set( 'lastMonth', $month );
Components::set( 'lastMonthYear', $lastMonthYear );
Components::set( 'nextMonth', $nextMonth );
Components::set( 'nextMonthYear', $nextMonthYear );
// Get the data
$monthArray = self::$events->monthArray( $id, self::$selectedDate );
// build the results
$lastMonth = strtotime( 'last month', self::$selectedDate );
$presentMonth = date( 'F', self::$selectedDate );
$presentDay = date( 'd', self::$selectedDate );
foreach ( $monthArray as $week => $days ) {
$weekFormat = [];
foreach ( $days as $day => $events ) {
if ( $day == '01' ) {
$month = date( 'F', strtotime( '+1 month', $lastMonth ) );
$lastMonth = strtotime( '+1 month', $lastMonth) ;
}
Components::set( 'currentMonth', $month );
Components::set( 'currentDay', $day );
$dayCell = new \stdClass();
if ( $presentDay == $day && $presentMonth == $month ) {
$dayCell->highlightedDate = ' today';
} else {
$dayCell->highlightedDate = '';
}
$dayCell->day = $day;
// prevent duplicated entries for previous/next month
if ( $lastMonth < strtotime( 'this month',self::$selectedDate ) || $lastMonth == strtotime( 'next month',self::$selectedDate )) {
$events = [];
}
$dayCell->dayEventList = Views::simpleView( 'calendar.nav.cell', $events );
$weekFormat[] = $dayCell;
}
$weekView = Views::simpleView( 'calendar.nav.week', $weekFormat );
Components::set( $week . 'Element', $weekView );
}
// Calendar Top Tabs
Components::unset( 'calendarDropdown');
$calendarTabs = Views::simpleView('calendar.nav.topTabs');
$tabsView = Navigation::activePageSelect( $calendarTabs, '/calendar/byMonth/', false, true );
// must be done after top-nav gets selected
$calendarDropdown = Views::simpleView('calendar.nav.calendarDropdown', self::$calendars->simpleObjectByUser() );
$calendarDropdownView = Navigation::activePageSelect( $calendarDropdown, Input::get( 'url' ), false, true ); // add notebookID as second param
Components::set( 'calendarDropdown', $calendarDropdownView);
Components::set( 'CalendarNav', Template::parse( $tabsView ) );
Components::set( 'dateMonth', date( 'F Y', self::$selectedDate ) );
Views::view( 'calendar.byMonth' );
}
public function byYear( $id = 0 ) {
// Set base components
Components::set( 'calendarID', $id );
Components::set( 'currentView', __FUNCTION__ );
// Set components for next / back arrows
$nextYear = date( 'Y', strtotime( 'next year', self::$selectedDate ) );
$lastYear = date( 'Y', strtotime( 'last year', self::$selectedDate ) );
Components::set( 'lastYear', $lastYear );
Components::set( 'nextYear', $nextYear );
// Get the data
$eventList = self::$events->yearArray( $id, self::$selectedDate );
// Calendar Top Tabs
Components::unset( 'calendarDropdown');
$calendarTabs = Views::simpleView('calendar.nav.topTabs');
$tabsView = Navigation::activePageSelect( $calendarTabs, '/calendar/byYear/', false, true );
// must be done after top-nav gets selected
$calendarDropdown = Views::simpleView('calendar.nav.calendarDropdown', self::$calendars->simpleObjectByUser() );
$calendarDropdownView = Navigation::activePageSelect( $calendarDropdown, Input::get( 'url' ), false, true ); // add notebookID as second param
Components::set( 'calendarDropdown', $calendarDropdownView);
Components::set( 'CalendarNav', Template::parse( $tabsView ) );
Components::set( 'dateYear', date( 'Y', self::$selectedDate ) );
Views::view( 'calendar.byYear', $eventList );
}
public function events( $id = 0 ) {
Components::set( 'currentView', __FUNCTION__ );
Components::set( 'calendarID', $id );
if ( ! empty( $id ) ) {
$calendar = self::$calendars->findById( $id );
if ( $calendar == false ) {
Issues::add( 'error', 'Calendar not found.' );
return $this->index();
}
if ( $calendar->createdBy != App::$activeUser->ID ) {
Issues::add( 'error', 'You do not have permission to view this calendar.' );
return $this->index();
}
Components::set( 'calendarName', $calendar->title );
$eventList = self::$events->findByCalendar( $id );
} else {
$eventList = self::$events->userEvents( 5 );
}
// Calendar Top Tabs
Components::unset( 'calendarDropdown');
$calendarTabs = Views::simpleView('calendar.nav.topTabs');
$tabsView = Navigation::activePageSelect( $calendarTabs, '/calendar/events/', false, true );
// must be done after top-nav gets selected
$calendarDropdown = Views::simpleView('calendar.nav.calendarDropdown', self::$calendars->simpleObjectByUser() );
$calendarDropdownView = Navigation::activePageSelect( $calendarDropdown, Input::get( 'url' ), false, true ); // add notebookID as second param
Components::set( 'calendarDropdown', $calendarDropdownView);
Components::set( 'CalendarNav', Template::parse( $tabsView ) );
Views::view( 'calendar.events.list', $eventList );
}
/**
* Support Functions
*/
private function findCalendarOrFail( $id = null ) {
$calendar = self::$calendars->findById( $id );
if ( $calendar == false ) {
Session::flash( 'error', 'Calendar not found.' );
Redirect::to( 'calendar/'.self::$defaultView.'/' );
}
if ( $calendar->createdBy != App::$activeUser->ID ) {
Session::flash( 'error', 'Permissions Error.' );
Redirect::to( 'calendar/'.self::$defaultView.'/' );
}
return $calendar;
}
private function findEventOrFail( $id = null ) {
$event = self::$events->findById( $id );
if ( $event == false ) {
Session::flash( 'error', 'Event not found.' );
Redirect::to( 'calendar/'.self::$defaultView.'/' );
}
if ( $event->createdBy != App::$activeUser->ID ) {
Session::flash( 'error', 'Permissions Error.' );
Redirect::to( 'calendar/'.self::$defaultView.'/' );
}
return $event;
}
private function getEventBreakdown( $start_timestamp = 0, $end_timestamp = 0, $with_timezone = false ) {
$out = new \stdClass();
if ( empty( $end_timestamp ) ) {
$out->allDay = 'true';
// $out->date = ;
// $out->time = ;
$out->endDate = $out->date;
$out->endTime = $out->time;
} else {
$out->allDay = 'false';
// $out->date = ;
// $out->time = ;
$out->endDate = $out->date;
$out->endTime = $out->time;
}
$timestamp = intval( $timestamp );
$init = date('Y-m-d H:i:s', $timestamp);
if ( true === $with_timezone ) {
$readableDate = self::applyTimezoneToTimestamp( $timestamp );
} else {
$readableDate = date('Y-m-d H:i:s', $timestamp);
}
$date = date( 'Y-m-d', strtotime( $readableDate ) );
$time = date( 'H:i', strtotime( $readableDate ) );
return [
'time' => $time,
'date' => $date,
];
}
}

View File

@ -1,153 +0,0 @@
.calendar-container {
margin-left: 20px;
margin-right: 20px;
}
.calendar-row {
display: flex;
justify-content: space-between;
width: 100%;
}
.calendar-cell {
flex: 1;
border: 1px solid #ddd;
text-align: center;
padding: 10px 0;
margin: 0 5px;
}
.calendar-cell h4 {
margin: 0;
padding: 5px;
border-radius: 4px;
background-color: #f8f8f8;
}
.hour-row {
display: flex;
justify-content: space-between;
width: 100%;
}
.hour-body {
display: flex; /* Add this line to use flexbox */
flex-direction: row; /* Ensure the direction is row */
flex: 4;
border: 1px solid #ddd;
text-align: center;
padding: 10px 0;
margin-left: 5px;
margin-right: 5px;
align-items: center;
}
.hour-cell {
flex: 1;
border: 1px solid #ddd;
text-align: center;
padding: 10px 0;
margin-left: 5px;
margin-right: 5px;
align-items: center;
max-width: 250px;
}
.today {
background-color: #5cb85c !important;
}
.hour-header, .hour-footer {
flex: 0 0 10%;
background-color: #f8f8f8;
text-align: center;
padding: 10px 0;
margin: 0 5px;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
}
/* Adjusting the first and last cell margins to align with the container edges */
.calendar-cell:first-child {
margin-left: 0;
}
.calendar-cell:last-child {
margin-right: 0;
}
.select-container {
color: white;
}
.custom-select {
/* color: red; */
color: #000;
}
.custom-select .white {
background-color: #ffffff;
}
.custom-select .primary {
background-color: #337ab7;
}
.custom-select .success {
background-color: #5cb85c;
}
.custom-select .info {
background-color: #5bc0de;
}
.custom-select .warning {
background-color: #f0ad4e;
}
.custom-select .danger {
background-color: #d9534f;
}
.select-container .primary {
background-color: #337ab7;
}
.select-container .white {
background-color: #ffffff;
}
.select-container .success {
background-color: #5cb85c;
}
.select-container .info {
background-color: #5bc0de;
}
.select-container .warning {
background-color: #f0ad4e;
}
.select-container .danger {
background-color: #d9534f;
}
.dateDayContainer {
width: 250px;
display: inline-block;
text-align: center;
}
.dateWeekContainer {
width: 450px;
display: inline-block;
text-align: center;
}
.dateMonthContainer {
width: 250px;
display: inline-block;
text-align: center;
}
.dateYearContainer {
width: 150px;
display: inline-block;
text-align: center;
}
.hour-active {
background-color: #217025 !important;
/* color: #00ff0d !important; */
background-image:none;
}

View File

@ -1,136 +0,0 @@
<?php
/**
* app/plugins/calendar/forms.php
*
* This houses all of the form checking functions for this plugin.
*
* @package TP Calendar
* @version 3.0
* @author Joey Kimsey <Joey@thetempusproject.com>
* @link https://TheTempusProject.com
* @license https://opensource.org/licenses/MIT [MIT LICENSE]
*/
namespace TheTempusProject\Plugins\Calendar;
use TheTempusProject\Bedrock\Functions\Input;
use TheTempusProject\Bedrock\Functions\Check;
use TheTempusProject\Classes\Forms;
class CalendarForms extends Forms {
/**
* Adds these functions to the form list.
*/
public function __construct() {
self::addHandler( 'calendarSelect', __CLASS__, 'calendarSelect' );
self::addHandler( 'createEvent', __CLASS__, 'createEvent' );
self::addHandler( 'createCalendar', __CLASS__, 'createCalendar' );
self::addHandler( 'editEvent', __CLASS__, 'editEvent' );
self::addHandler( 'editCalendar', __CLASS__, 'editCalendar' );
}
/**
* Validates the password re-send form.
*
* @return {bool}
*/
public static function calendarSelect() {
if ( ! Input::exists( 'calendar_id' ) ) {
Check::addUserError( 'You must include a title.' );
return false;
}
// if ( !self::token() ) {
// Check::addUserError( 'token - comment out later.' );
// return false;
// }
return true;
}
public static function createEvent() {
if ( ! Input::exists( 'title' ) ) {
Check::addUserError( 'You must include a title.' );
return false;
}
if ( ! Input::exists( 'date' ) ) {
Check::addUserError( 'You must include a date.' );
return false;
}
if ( ! Input::exists( 'time' ) ) {
Check::addUserError( 'You must include a time.' );
return false;
}
if ( ! Input::exists( 'repeats' ) ) {
Check::addUserError( 'You must include a frequency.' );
return false;
}
if ( ! Input::exists( 'calendar_id' ) ) {
Check::addUserError( 'You must select a calendar.' );
return false;
}
// if ( !self::token() ) {
// Check::addUserError( 'token - comment out later.' );
// return false;
// }
return true;
}
public static function createCalendar() {
if ( ! Input::exists( 'title' ) ) {
Check::addUserError( 'You must include a title.' );
return false;
}
if ( ! Input::exists( 'timezone' ) ) {
Check::addUserError( 'You must include a timezone.' );
return false;
}
// if ( ! self::token() ) {
// Check::addUserError( 'token - comment out later.' );
// return false;
// }
return true;
}
public static function editEvent() {
if ( ! Input::exists( 'title' ) ) {
Check::addUserError( 'You must include a title.' );
return false;
}
if ( ! Input::exists( 'date' ) ) {
Check::addUserError( 'You must include a date.' );
return false;
}
if ( ! Input::exists( 'time' ) ) {
Check::addUserError( 'You must include a time.' );
return false;
}
if ( ! Input::exists( 'repeats' ) ) {
Check::addUserError( 'You must include a frequency.' );
return false;
}
// if ( !self::token() ) {
// Check::addUserError( 'token - comment out later.' );
// return false;
// }
return true;
}
public static function editCalendar() {
if ( ! Input::exists( 'submit' ) ) {
return false;
}
if ( ! Input::exists( 'title' ) ) {
Check::addUserError( 'You must include a title.' );
return false;
}
if ( ! Input::exists( 'timezone' ) ) {
Check::addUserError( 'You must include a timezone.' );
return false;
}
// if ( !self::token() ) {
// Check::addUserError( 'token - comment out later.' );
// return false;
// }
return true;
}
}
new CalendarForms;

View File

@ -1,157 +0,0 @@
<?php
/**
* app/plugins/calendar/models/events.php
*
* This class is used for the manipulation of the calendars database table.
*
* @package TP Calendar
* @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\Classes\Config;
use TheTempusProject\Bedrock\Functions\Check;
use TheTempusProject\Bedrock\Functions\Date;
use TheTempusProject\Canary\Bin\Canary as Debug;
use TheTempusProject\Classes\DatabaseModel;
use TheTempusProject\TheTempusProject as App;
use TheTempusProject\Houdini\Classes\Filters;
use TheTempusProject\Bedrock\Classes\CustomException;
class Calendars extends DatabaseModel {
public $tableName = 'calendars';
public $databaseMatrix = [
[ 'title', 'varchar', '256' ],
[ 'color', 'varchar', '48' ],
[ 'privacy', 'varchar', '48' ],
[ 'description', 'text', '' ],
[ 'createdBy', 'int', '11' ],
[ 'createdAt', 'int', '11' ],
[ 'timezone', 'varchar', '256' ],
];
/**
* The model constructor.
*/
public function __construct() {
parent::__construct();
}
public function create( $title, $description = '', $timezone = '', $color = 'default' ) {
if ( ! Check::dataTitle( $title ) ) {
Debug::info( 'Calendars: illegal title.' );
return false;
}
if ( empty( $timezone ) ) {
$timezone = Date::getTimezone();
}
$fields = [
'title' => $title,
'description' => $description,
'timezone' => $timezone,
'color' => $color,
'createdBy' => App::$activeUser->ID,
'createdAt' => time(),
];
if ( ! self::$db->insert( $this->tableName, $fields ) ) {
new CustomException( 'calendarCreate' );
Debug::error( "Calendar: not created " . var_export($fields,true) );
return false;
}
return self::$db->lastId();
}
public function update( $id, $title, $description = '', $timezone = '', $color = 'default' ) {
if ( empty( $timezone ) ) {
$timezone = Date::getTimezone();
}
if ( !Check::id( $id ) ) {
Debug::info( 'Calendars: illegal ID.' );
return false;
}
if ( !Check::dataTitle( $title ) ) {
Debug::info( 'Calendars: illegal title.' );
return false;
}
$fields = [
'title' => $title,
'description' => $description,
'timezone' => $timezone,
'color' => $color,
];
if ( !self::$db->update( $this->tableName, $id, $fields ) ) {
new CustomException( 'calendarUpdate' );
Debug::error( "Calendar: $id not updated: $fields" );
return false;
}
return true;
}
public function byUser( $limit = null ) {
$whereClause = ['createdBy', '=', App::$activeUser->ID];
if ( empty( $limit ) ) {
$calendars = self::$db->get( $this->tableName, $whereClause );
} else {
$calendars = self::$db->get( $this->tableName, $whereClause, 'ID', 'DESC', [0, $limit] );
}
if ( !$calendars->count() ) {
Debug::info( 'No Calendars found.' );
return false;
}
return $this->filter( $calendars->results() );
}
public function getName( $id ) {
$calendar = self::findById( $id );
if (false == $calendar) {
return 'unknown';
}
return $calendar->title;
}
public function getColor( $id ) {
$calendar = self::findById( $id );
if (false == $calendar) {
return 'default';
}
return $calendar->color;
}
public function simpleByUser() {
$whereClause = ['createdBy', '=', App::$activeUser->ID];
$calendars = self::$db->get( $this->tableName, $whereClause );
if ( !$calendars->count() ) {
Debug::warn( 'Could not find any calendars' );
return false;
}
$calendars = $calendars->results();
$out = [];
foreach ( $calendars as &$calendar ) {
$out[ $calendar->title ] = $calendar->ID;
}
return $out;
}
public function simpleObjectByUser() {
$whereClause = ['createdBy', '=', App::$activeUser->ID];
$calendars = self::$db->get( $this->tableName, $whereClause );
if ( !$calendars->count() ) {
Debug::warn( 'Could not find any calendars' );
return false;
}
$calendars = $calendars->results();
$out = [];
foreach ( $calendars as &$calendar ) {
$obj = new \stdClass();
$obj->title = $calendar->title;
$obj->ID = $calendar->ID;
$out[] = $obj;
}
return $out;
}
}

View File

@ -1,509 +0,0 @@
<?php
/**
* app/plugins/calendar/models/events.php
*
* This class is used for the manipulation of the events database table.
*
* @package TP Calendar
* @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\Classes\Config;
use TheTempusProject\Bedrock\Functions\Check;
use TheTempusProject\Canary\Bin\Canary as Debug;
use TheTempusProject\Classes\DatabaseModel;
use TheTempusProject\TheTempusProject as App;
use TheTempusProject\Houdini\Classes\Filters;
use TheTempusProject\Models\Calendars;
use TheTempusProject\Bedrock\Classes\CustomException;
use TheTempusProject\Bedrock\Functions\Date;
use TheTempusProject\Bedrock\Classes\Pagination;
class Events extends DatabaseModel {
public $tableName = 'events';
public $repeatOptions = [
'Does Not Repeat' => 'none',
'Every Day' => 'daily',
'Every Week' => 'weekly',
'Every Month' => 'monthly',
'Every Year' => 'yearly',
];
public $databaseMatrix = [
[ 'calendar_id', 'int', '11' ],
[ 'title', 'varchar', '256' ],
[ 'event_time', 'int', '11' ],
[ 'event_ends', 'int', '11' ],
[ 'description', 'text', '' ],
[ 'location', 'varchar', '256' ],
[ 'repeats', 'varchar', '48' ],
[ 'color', 'varchar', '48' ],
[ 'parent_id', 'int', '11' ],
[ 'createdBy', 'int', '11' ],
[ 'createdAt', 'int', '11' ],
];
protected static $calendars;
/**
* The model constructor.
*/
public function __construct() {
parent::__construct();
self::$calendars = new Calendars;
}
/**
* Saves a chat form to the db.
*
* @param string $message -contents of the chat form.
* @return bool
*/
public function create( $calendar_id, $title, $event_time, $description = '', $location = '', $repeats = 'none', $color = 'default', $parent_id = 0 ) {
if ( ! Check::id( $calendar_id ) ) {
Debug::info( 'calendar event: illegal calendar ID.' );
return false;
}
$fields = [
'calendar_id' => $calendar_id,
'title' => $title,
'event_time' => $event_time,
'description' => $description,
'location' => $location,
'repeats' => $repeats,
'color' => $color,
'parent_id' => $parent_id,
'createdBy' => App::$activeUser->ID,
'createdAt' => time(),
];
if ( !self::$db->insert( $this->tableName, $fields ) ) {
Debug::info( 'Events::create - failed to insert to db' );
return false;
}
return self::$db->lastId();
}
public function update( $id, $title, $event_time, $description = '', $location ='', $repeats = 'none', $color = 'default', $parent_id = 0, $calendar_id = '' ) {
if ( ! Check::id( $id ) ) {
Debug::info( 'calendar event: illegal ID.' );
return false;
}
$fields = [
'title' => $title,
'event_time' => $event_time,
'description' => $description,
'location' => $location,
'repeats' => $repeats,
'color' => $color,
'parent_id' => $parent_id,
];
if ( ! self::$db->update( $this->tableName, $id, $fields ) ) {
new CustomException( 'calendarEventUpdate' );
Debug::error( "Event: $id not updated: $fields" );
return false;
}
return true;
}
public function dayArray( $calendar_id = 0, $date = 0 ) {
$whereClause = [ 'createdBy', '=', App::$activeUser->ID, 'AND' ];
if ( ! empty( $calendar_id ) ) {
$whereClause = array_merge( $whereClause, [ 'calendar_id', '=', $calendar_id, 'AND' ] );
}
$whereClause = array_merge( $whereClause, [
'event_time', '>=', Date::getDayStartTimestamp( $date, true ), 'AND',
'event_time', '<=', Date::getDayEndTimestamp( $date, true ),
] );
$events = self::$db->get( $this->tableName, $whereClause );
if ( ! $events->count() ) {
$results = [];
} else {
$results = $events->results();
}
$events = $this->sortByEventTimeHour(
$this->filter(
$results
)
);
// Generate day array
$currentDay = [];
$currentHour = Date::getDayStartTimestamp( $date );
$lastHour = Date::getDayEndTimestamp( $date );
while ( $currentHour <= $lastHour ) {
$hour = date( 'H', $currentHour );
if ( ! empty( $events[ $hour ] ) ) {
$dailyEvents = $events[ $hour ];
} else {
$dailyEvents = [];
}
$currentDay[ $hour ] = $dailyEvents;
$currentHour = strtotime('+1 hour', $currentHour);
}
return $currentDay;
}
public function weekArray( $calendar_id = 0, $date = null ) {
$whereClause = [ 'createdBy', '=', App::$activeUser->ID, 'AND' ];
if ( ! empty( $calendar_id ) ) {
$whereClause = array_merge( $whereClause, [ 'calendar_id', '=', $calendar_id, 'AND' ] );
}
$whereClause = array_merge( $whereClause, [
'event_time', '>=', Date::getWeekStartTimestamp( $date, true ), 'AND',
'event_time', '<=', Date::getWeekEndTimestamp( $date, true ),
] );
$events = self::$db->get( $this->tableName, $whereClause );
if ( ! $events->count() ) {
$results = [];
} else {
$results = $events->results();
}
$events = $this->sortByEventTime(
$this->filter(
$results
),
);
// Generate weeks array
$output = [];
$currentWeek = [];
$currentDay = Date::getWeekStartTimestamp( $date );
$weekCounter = 1;
$dayCounter = 1;
// Re-index the array using the date derived from event_time as the key
while ( $currentDay <= Date::getWeekEndTimestamp( $date ) ) {
$month = date( 'F', $currentDay );
$dayOfMonth = date( 'd', $currentDay );
if ( ! empty( $events[ $month ][ $dayOfMonth ] ) ) {
$dailyEvents = $events[ $month ][ $dayOfMonth ];
} else {
$dailyEvents = [];
}
$currentWeek[ $dayOfMonth ] = $dailyEvents;
$currentDay = strtotime('+1 day', $currentDay);
}
// Handle any remaining days in the last week
if ( ! empty( $currentWeek ) ) {
$output["week$weekCounter"] = $currentWeek;
}
return $output;
}
public function monthArray( $calendar_id = 0, $date = null ) {
$whereClause = [ 'createdBy', '=', App::$activeUser->ID, 'AND' ];
if ( ! empty( $calendar_id ) ) {
$whereClause = array_merge( $whereClause, [ 'calendar_id', '=', $calendar_id, 'AND' ] );
}
$whereClause = array_merge( $whereClause, [
'event_time', '>=', Date::getMonthStartTimestamp( $date, true ), 'AND',
'event_time', '<=', Date::getMonthEndTimestamp( $date, true ),
] );
$events = self::$db->get( $this->tableName, $whereClause );
if ( ! $events->count() ) {
$results = [];
} else {
$results = $events->results();
}
$events = $this->sortByEventTime(
$this->filter(
$results
),
);
// Generate weeks array
$output = [];
$currentWeek = [];
$currentDay = Date::getMonthStartTimestamp( $date );
$weekCounter = 1;
$dayCounter = 1;
// Re-index the array using the date derived from event_time as the key
while ( $currentDay <= Date::getMonthEndTimestamp( $date ) ) {
$month = date( 'F', $currentDay );
$dayOfMonth = date( 'd', $currentDay );
if ( ! empty( $events[ $month ][ $dayOfMonth ] ) ) {
$dailyEvents = $events[ $month ][ $dayOfMonth ];
} else {
$dailyEvents = [];
}
$currentWeek[$dayOfMonth] = $dailyEvents;
if (count($currentWeek) == 7) {
$output["week$weekCounter"] = $currentWeek;
$currentWeek = [];
$weekCounter++;
}
$currentDay = strtotime('+1 day', $currentDay);
}
// Handle any remaining days in the last week
if ( ! empty( $currentWeek ) ) {
$output["week$weekCounter"] = $currentWeek;
}
while ( $weekCounter < 7 ) {
$output["week$weekCounter"] = [];
$weekCounter++;
}
return $output;
}
public function yearArray( $calendar_id = 0, $date = null ) {
if ( empty( $date ) ) {
$date = time();
}
$year = date( 'Y', $date );
$firstDayUnix = date( 'U', strtotime( "Jan 1, $year" ) );
$lastDayUnix = date( 'U', strtotime( "December 31, $year" ) );
$whereClause = [ 'createdBy', '=', App::$activeUser->ID, 'AND' ];
if ( ! empty( $calendar_id ) ) {
$whereClause = array_merge( $whereClause, [ 'calendar_id', '=', $calendar_id, 'AND' ] );
}
$whereClause = array_merge( $whereClause, [
'event_time', '<=', $lastDayUnix, 'AND',
'event_time', '>=', $firstDayUnix,
] );
$events = self::$db->get( $this->tableName, $whereClause );
if ( ! $events->count() ) {
Debug::info( 'No ' . $this->tableName . ' data found.' );
return [];
}
return $this->filter( $events->results() );
}
public function compareEventTime($a, $b) {
return $a->event_time - $b->event_time;
}
public function sortByEventTimeHour( $results ) {
usort( $results, [ $this,'compareEventTime' ] );
$sortedResults = array();
foreach ( $results as $result ) {
$date = new \DateTime();
$date->setTimestamp( $result->event_time );
$timezone = new \DateTimeZone( Date::getTimezone() );
$date->setTimezone( $timezone );
$dateKey = $date->format('H');
$sortedResults[ $dateKey ][] = $result;
}
return $sortedResults;
}
public function sortByEventTime( $results ) {
usort( $results, [ $this,'compareEventTime' ] );
$sortedResults = array();
foreach ( $results as $result ) {
$date = new \DateTime();
$date->setTimestamp( $result->event_time );
$timezone = new \DateTimeZone( Date::getTimezone() );
$date->setTimezone( $timezone );
$month = $date->format('F');
$dateKey = $date->format('d');
if ( ! isset( $sortedResults[ $month ] ) ) {
$sortedResults[ $month ] = [];
}
if ( ! isset( $sortedResults[ $month ][ $dateKey ] ) ) {
$sortedResults[ $month ][ $dateKey ] = [];
}
$sortedResults[ $month ][ $dateKey ][] = $result;
}
return $sortedResults;
}
public function findByCalendar( $id ) {
$calendar = $this->verifyCalendar( $id );
if ( empty( $calendar ) ) {
return [];
}
$whereClause = [ 'createdBy', '=', App::$activeUser->ID, 'AND' ];
$whereClause = array_merge( $whereClause, [ 'calendar_id', '=', $id ] );
$events = self::$db->getPaginated( $this->tableName, $whereClause );
if ( ! $events->count() ) {
Debug::info( 'No ' . $this->tableName . ' data found.' );
return [];
}
return $this->filter( $events->results() );
}
public function userEvents( $limit = 0 ) {
$whereClause = [ 'createdBy', '=', App::$activeUser->ID ];
$events = self::$db->getPaginated( $this->tableName, $whereClause );
if ( ! $events->count() ) {
Debug::info( 'No ' . $this->tableName . ' data found.' );
return [];
}
return $this->filter( $events->results() );
}
public function filter( $data, $params = [] ) {
$out = [];
foreach ( $data as $instance ) {
if ( !is_object( $instance ) ) {
$instance = $data;
$end = true;
}
$instance->repeatsText = array_search('none', $this->repeatOptions);
$instance->calendarName = self::$calendars->getName( $instance->calendar_id );
if ( empty( $instance->color ) || $instance->color == 'default' || $instance->color == 'none') {
$instance->displayColor = self::$calendars->getColor( $instance->calendar_id );
} else {
$instance->displayColor = $instance->color;
}
$out[] = $instance;
if ( !empty( $end ) ) {
$out = $out[0];
break;
}
}
return $out;
}
public function today( $limit = 0 ) {
$date = time();
$day = date('d', $date);
$month = date('M', $date);
$year = date( 'Y', $date );
$firstDayUnix = date( 'U', strtotime( "00:00:00 $day $month $year" ) );
$lastDayUnix = date( 'U', strtotime( "23:59:59 $day $month $year" ) );
$whereClause = [ 'createdBy', '=', App::$activeUser->ID, 'AND' ];
$whereClause = array_merge( $whereClause, [
'event_time', '<=', $lastDayUnix, 'AND',
'event_time', '>=', $firstDayUnix,
] );
if ( empty( $limit ) ) {
$events = self::$db->get( $this->tableName, $whereClause );
} else {
$events = self::$db->get( $this->tableName, $whereClause, 'ID', 'DESC', [0, $limit] );
}
if ( ! $events->count() ) {
Debug::info( 'No ' . $this->tableName . ' data found.' );
return [];
}
return $this->filter( $events->results() );
}
private function verifyCalendar( $calendar_id ) {
if ( ! Check::id( $calendar_id ) ) {
Debug::info( 'Invalid Calendar ID' );
return false;
}
$calendar = self::$calendars->findById( $calendar_id );
if ( $calendar == false ) {
Debug::info( 'Calendar not found' );
return false;
}
if ( $calendar->createdBy != App::$activeUser->ID ) {
Debug::info( 'You do not have permission to view this calendar' );
return false;
}
return $calendar;
}
public function deleteByCalendar( $calendar_id ) {
$calendar = $this->verifyCalendar( $calendar_id );
if ( empty( $calendar ) ) {
return [];
}
$whereClause = [ 'createdBy', '=', App::$activeUser->ID, 'AND' ];
$whereClause = array_merge( $whereClause, [ 'calendar_id', '=', $calendar_id ] );
$events = self::$db->get( $this->tableName, $whereClause );
if ( ! $events->count() ) {
Debug::info( 'No ' . $this->tableName . ' data found.' );
return [];
}
foreach( $events->results() as $event ) {
$this->deleteByParent( $event->ID );
}
return true;
}
public function deleteByParent( $event_id ) {
$whereClause = [ 'createdBy', '=', App::$activeUser->ID, 'AND' ];
$whereClause = array_merge( $whereClause, [ 'parent_id', '=', $event_id ] );
$events = self::$db->get( $this->tableName, $whereClause );
if ( ! $events->count() ) {
Debug::info( 'No ' . $this->tableName . ' data found.' );
return [];
}
foreach( $events->results() as $event ) {
$this->delete( $event->ID );
}
// need to delete all child events accordingly
return true;
}
public function delete( $idArray ) {
if ( !is_array( $idArray ) ) {
$idArray = [ $idArray ];
}
return parent::delete( $idArray );
}
public function generateChildEvents( $event_id, $remove_existing_children = true, $remove_history = false ) {
$event = self::findById( $event_id );
if (empty($event)) {
return false;
}
if ($event->parent_id != 0) {
return false;
}
if (!in_array($event->repeats, $this->repeatOptions)) {
return false;
}
$startDate = time();
$whereClause = [
'parent_id', '=', $event_id,
];
if ( $remove_history && ! $remove_existing_children) {
$whereClause = array_merge( $whereClause, [ 'AND', 'event_time', '<=', $startDate ] );
} elseif ( $remove_existing_children && ! $remove_history ) {
$whereClause = array_merge( $whereClause, [ 'AND', 'event_time', '>=', $startDate ] );
} elseif ( !$remove_existing_children && !$remove_history ) {
return false;
}
self::$db->delete($this->tableName, $whereClause);
switch ($event->repeats) {
case 'daily':
return $this->generateEvents( $event, 'P90D' );
case 'weekly':
return $this->generateEvents( $event, 'P1W' );
case 'monthly':
return $this->generateEvents( $event, 'P1M' );
case 'yearly':
return $this->generateEvents( $event, 'P1Y' );
default:
return false;
}
}
private function generateEvents( $event, $interval, $years = 2 ) {
$endDate = strtotime('+'.$years.' years');
$interval = new \DateInterval( $interval );
$period = new \DatePeriod(new \DateTime('@' . $event->event_time), $interval, new \DateTime('@' . $endDate));
foreach ($period as $date) {
if ( $date->getTimestamp() <= time() ) {
continue;
}
$result = $this->create(
$event->calendar_id,
$event->title,
$date->getTimestamp(),
$event->description,
$event->location,
$event->repeats,
$event->color,
$event->ID,
);
}
}
}

View File

@ -1,84 +0,0 @@
<?php
/**
* app/plugins/calendar/plugin.php
*
* This houses all of the main plugin info and functionality.
*
* @package TP Calendar
* @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;
use TheTempusProject\Models\Events;
use TheTempusProject\Models\Calendars;
use TheTempusProject\Houdini\Classes\Components;
use TheTempusProject\Houdini\Classes\Template;
class Calendar extends Plugin {
public $pluginName = 'TP Calendar';
public $configName = 'calendar';
public $pluginAuthor = 'JoeyK';
public $pluginWebsite = 'https://TheTempusProject.com';
public $modelVersion = '1.0';
public $pluginVersion = '3.0';
public $pluginDescription = 'A simple plugin which adds a site wide calendar system.';
public $permissionMatrix = [
'useCalendar' => [
'pretty' => 'Can use the calendar feature',
'default' => false,
],
'createEvents' => [
'pretty' => 'Can add events to calendars',
'default' => false,
],
];
public $main_links = [
[
'text' => 'Calendar',
'url' => '{ROOT_URL}calendar/index',
'filter' => 'loggedin',
],
];
public $configMatrix = [
'enabled' => [
'type' => 'radio',
'pretty' => 'Enable Calendar.',
'default' => true,
],
];
public $preferenceMatrix = [
'calendarPreference' => [
'pretty' => 'Default Calendar View',
'type' => 'select',
'default' => 'byMonth',
'options' => [
'Daily' => 'byDay',
'Weekly' => 'byWeek',
'Monthly' => 'byMonth',
'Yearly' => 'byYear',
'All Events' => 'events',
],
],
'weekStart' => [
'pretty' => 'First day of the week for the Calendar',
'type' => 'select',
'default' => 'sunday',
'options' => [
'Sunday' => '6',
'Monday' => '7',
],
],
];
public $events;
public $calendars;
public function __construct( $load = false ) {
$this->events = new Events;
$this->calendars = new Calendars;
parent::__construct( $load );
}
}

View File

@ -1,41 +0,0 @@
<?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\Plugins\Calendar;
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;
use TheTempusProject\Models\Events;
use TheTempusProject\Models\Calendars;
class CalendarLoader extends DefaultLoader {
/**
* This is the function used to generate any components that may be
* needed by this template.
*/
public function __construct() {
$events = new Events;
$calendars = new Calendars;
Navigation::setCrumbComponent( 'CALENDAR_BREADCRUMBS', Input::get( 'url' ) );
Components::set( 'todaysDate', date( 'F d, Y', time()) );
Components::set( 'currentDay', date( 'F d, Y', time()) );
Components::set( 'weekOf', 'Week of ' . date( 'F d, Y', strtotime( 'this week' ) ) );
Components::set( 'year', date( 'Y', time() ));
Components::set( 'month', date( 'F', time() ));
parent::__construct();
}
}

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