Compare commits
19 Commits
d7d8fc4b5e
...
4.0.1
Author | SHA1 | Date | |
---|---|---|---|
5e99213601 | |||
ebfeead788 | |||
32a9711ade | |||
87e4f90bab | |||
1c5b020a87 | |||
5fe1c3aafe | |||
a859fb7ace | |||
2220c6cda3 | |||
de6d608857 | |||
41426fda4e | |||
7bef832417 | |||
aaefd66350 | |||
a0726e6578 | |||
f3f323d30f | |||
b3018de907 | |||
4ab9d33b01 | |||
485d85cb0a | |||
b93d0259e4 | |||
bf7b7ba1c9 |
3
.gitignore
vendored
3
.gitignore
vendored
@ -54,6 +54,7 @@ Temporary Items
|
||||
|
||||
# TheTempusProject Specific
|
||||
.htaccess
|
||||
app/config/*
|
||||
!app/config/constants.php
|
||||
uploads/images/*
|
||||
logs/*
|
||||
@ -62,3 +63,5 @@ mail.log
|
||||
vendor/canary/logs/*
|
||||
.env
|
||||
components/*
|
||||
mailhog.log
|
||||
uploads/*
|
||||
|
445
README.md
445
README.md
@ -1,380 +1,131 @@
|
||||
# The Tempus Project
|
||||
|
||||
_Rapid Prototyping Framework built on PHP utilizing the MVC pattern with a Bootstrap front-end_
|
||||
|
||||
need to make a vs battle for dnd. someone makes a truly broken character, we take the base character and hand it to two people and give them some time to figure out how they would break it
|
||||
__Developer(s):__
|
||||
|
||||
need to track points once a week
|
||||
|
||||
a huge table tracks points day to day then we add and erase the old data, or move it to historical...
|
||||
|
||||
## Rapid Prototyping Framework
|
||||
|
||||
### Developer(s): Joey Kimsey
|
||||
- __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.
|
||||
|
||||
**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.
|
||||
|
||||
Currently I am in the process of testing all the systems in preparation for the first production ready release. The beta is still on-going. If you would like to participate or stay up to date with the latest, you can find more information at: https://TheTempusProject.com/beta
|
||||
## 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
|
||||
|
||||
A User management system with groups, permissions, preferences, registration, and recovery. (All Controlled dynamically via our plugin interface)
|
||||
A Plugin system that allows plug-and-play functionality for a huge number of features.
|
||||
Compatibility with both Apache and NGINX.
|
||||
Built with Bootstrap with a focus on mobile compatibility.
|
||||
Incredibly easy to set-up, deploy, and develop with.
|
||||
- A Plugin system that allows plug-and-play functionality
|
||||
- A User management system
|
||||
- groups
|
||||
- permissions
|
||||
- preferences
|
||||
- registration and recovery
|
||||
(All Controlled dynamically via our plugin interface)
|
||||
- Compatibility with both Apache and NGINX
|
||||
- Built with Bootstrap with a focus on mobile compatibility
|
||||
- Incredibly easy to set-up, deploy, and develop
|
||||
|
||||
## Installation
|
||||
|
||||
Preferred method for installation is using composer.
|
||||
|
||||
### Manually
|
||||
|
||||
### Docker
|
||||
The preferred method for installation is [Composer](#composer) but special attention has been given to installation and usage [without 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:
|
||||
|
||||
#### via create-project
|
||||
|
||||
```
|
||||
composer create-project thetempusproject/thetempusproject test-app
|
||||
```
|
||||
|
||||
#### via clone & install
|
||||
|
||||
1. Clone the directory to wherever you want to install the framework.
|
||||
2. Open your terminal to the directory you previously cloned the repository.
|
||||
3. Install using composer:
|
||||
`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`
|
||||
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.
|
||||
|
||||
#### Apache
|
||||
### Manually
|
||||
|
||||
#### NGINX
|
||||
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
|
||||
```
|
||||
|
||||
#### Docker-Compose
|
||||
__Note:__ The autoloader should automatically detect and use the dependencies, but they need to be sorted into the folders ans shown above.
|
||||
|
||||
If you have any trouble with the installation, you can check out our FAQ page on the wiki for answers to common issues.
|
||||
|
||||
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
|
||||
Please note this repository is only up to the latest _stable_ release. Please continue to use composer update to get the latest development releases.
|
||||
## Docker
|
||||
|
||||
**Do not forget to remove install.php once you have finished installation!**
|
||||
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.
|
||||
|
||||
#### Currently being developed
|
||||
You will need docker installed on your system then you can either download the latest images from DockerHud:
|
||||
|
||||
```
|
||||
docker pull thetempusproject/ttp-apache
|
||||
docker pull thetempusproject/ttp-nginx
|
||||
```
|
||||
|
||||
Or you can build your own images from this repository. More information can be found in the included README files:
|
||||
|
||||
* [Apache Image](docker/ttp-apache/README.md)
|
||||
* [Nginx Image](docker/ttp-nginx/README.md)
|
||||
|
||||
### 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
|
||||
- [ ] Unit tests
|
||||
- [ ] Unit testing
|
||||
|
||||
#### Future updates
|
||||
## Future updates
|
||||
|
||||
- [ ] 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.
|
||||
- [ ] The templating system has gotten too large and needs to be split into its own repo
|
||||
|
||||
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.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
i need to be able to name models in camel case, this shit is getting crazy. Find whatever prevents this and fix it
|
||||
- [ ] 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.
|
||||
|
@ -16,17 +16,122 @@ use TheTempusProject\Houdini\Classes\Template;
|
||||
use TheTempusProject\TheTempusProject as App;
|
||||
use TheTempusProject\Hermes\Functions\Redirect;
|
||||
use TheTempusProject\Bedrock\Functions\Session;
|
||||
use TheTempusProject\Bedrock\Classes\Config;
|
||||
use TheTempusProject\Models\Token;
|
||||
use TheTempusProject\Canary\Bin\Canary as Debug;
|
||||
use TheTempusProject\Houdini\Classes\Views;
|
||||
|
||||
class ApiController extends Controller {
|
||||
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();
|
||||
if ( ! App::verifyApiRequest() ) {
|
||||
Session::flash( 'error', 'You do not have permission to view this page.' );
|
||||
return Redirect::home();
|
||||
}
|
||||
Template::setTemplate( 'api' );
|
||||
Template::noFollow();
|
||||
Template::noIndex();
|
||||
Template::addHeader( 'Content-Type: application/json; charset=utf-8' );
|
||||
Template::setTemplate( 'api' );
|
||||
$res = $this->verifyApiRequest();
|
||||
if ( $secure && ! $this->canUseApi() ) {
|
||||
exit( $res );
|
||||
}
|
||||
}
|
||||
|
||||
protected function canUseApi() {
|
||||
return ( $this->canUseUserApi() || $this->canUseAppApi() || $this->canUseAuthApi() );
|
||||
}
|
||||
|
||||
protected function canUseUserApi() {
|
||||
$apiEnabled = Config::getValue( 'api/apiAccessApp' );
|
||||
if ( empty( $apiEnabled ) ) {
|
||||
return false;
|
||||
}
|
||||
return self::$canAccessUserApi;
|
||||
}
|
||||
|
||||
protected function canUseAppApi() {
|
||||
$apiEnabled = Config::getValue( 'api/apiAccessPersonal' );
|
||||
if ( empty( $apiEnabled ) ) {
|
||||
return false;
|
||||
}
|
||||
return self::$canAccessApplicationApi;
|
||||
}
|
||||
|
||||
protected function canUseAuthApi() {
|
||||
return self::$canAccessAuthenticationApi;
|
||||
}
|
||||
|
||||
public function verifyApiRequest() {
|
||||
$tokens = new Token;
|
||||
$secret = null;
|
||||
|
||||
$bearer_token = $this->getBearerToken();
|
||||
if ( ! empty( $bearer_token ) ) {
|
||||
$token = $tokens->findByToken( $bearer_token );
|
||||
} else {
|
||||
$secret = $this->getSecretToken();
|
||||
if ( empty( $secret ) ) {
|
||||
return Views::simpleView( 'api.response', ['response' => json_encode( [ 'error' => 'invalid secret' ], true )]);
|
||||
}
|
||||
$token = $tokens->findBySecret( $secret );
|
||||
}
|
||||
if ( empty( $token ) ) {
|
||||
return Views::simpleView( 'api.response', ['response' => json_encode( [ 'error' => 'invalid token' ], true )]);
|
||||
}
|
||||
self::$authToken = $token;
|
||||
if ( $token->expiresAt <= time() && empty( $secret ) ) {
|
||||
return Views::simpleView( 'api.response', ['response' => json_encode( [ 'error' => 'token expired' ], true )]);
|
||||
}
|
||||
if ( $token->expiresAt <= time() ) {
|
||||
self::$canAccessAuthenticationApi = true;
|
||||
return;
|
||||
}
|
||||
if ( $token->token_type == 'app' ) {
|
||||
self::$canAccessApplicationApi = true;
|
||||
return;
|
||||
}
|
||||
if ( $token->token_type == 'user' ) {
|
||||
self::$canAccessUserApi = true;
|
||||
return;
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
public function getSecretToken() {
|
||||
$headers = $this->getAuthorizationHeader();
|
||||
if ( ! empty( $headers ) ) {
|
||||
if ( preg_match( '/Secret\s(\S+)/', $headers, $matches ) ) {
|
||||
return $matches[1];
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public function getBearerToken() {
|
||||
$headers = $this->getAuthorizationHeader();
|
||||
if ( ! empty( $headers ) ) {
|
||||
if ( preg_match( '/Bearer\s(\S+)/', $headers, $matches ) ) {
|
||||
return $matches[1];
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public function getAuthorizationHeader(){
|
||||
$headers = null;
|
||||
if ( isset( $_SERVER['Authorization'] ) ) {
|
||||
$headers = trim( $_SERVER["Authorization"] );
|
||||
} elseif ( isset( $_SERVER['HTTP_AUTHORIZATION'] ) ) {
|
||||
$headers = trim( $_SERVER["HTTP_AUTHORIZATION"] );
|
||||
} elseif ( function_exists( 'apache_request_headers' ) ) {
|
||||
$requestHeaders = apache_request_headers();
|
||||
$requestHeaders = array_combine(array_map('ucwords', array_keys($requestHeaders)), array_values($requestHeaders));
|
||||
if ( isset( $requestHeaders['Authorization'] ) ) {
|
||||
$headers = trim( $requestHeaders['Authorization'] );
|
||||
}
|
||||
}
|
||||
return $headers;
|
||||
}
|
||||
}
|
||||
|
@ -12,6 +12,7 @@
|
||||
namespace TheTempusProject\Classes;
|
||||
|
||||
use TheTempusProject\Houdini\Classes\Forms;
|
||||
use TheTempusProject\Houdini\Classes\Template;
|
||||
use TheTempusProject\Canary\Bin\Canary as Debug;
|
||||
use TheTempusProject\Bedrock\Functions\Check;
|
||||
use TheTempusProject\Bedrock\Functions\Input;
|
||||
@ -19,7 +20,6 @@ use TheTempusProject\Bedrock\Classes\Config as BedrockConfig;
|
||||
|
||||
class Config extends BedrockConfig {
|
||||
public static function getFieldEditHtml( $name, $includeProtected = false ) {
|
||||
// @todo: includeProtected is unused here
|
||||
$node = self::get( $name );
|
||||
if ( empty( $node ) ) {
|
||||
return;
|
||||
@ -28,13 +28,57 @@ class Config extends BedrockConfig {
|
||||
return;
|
||||
}
|
||||
$fieldname = str_ireplace( '/', '-', $name );
|
||||
$html = Forms::getFormFieldHtml(
|
||||
$fieldname,
|
||||
$node['pretty'],
|
||||
$node['type'],
|
||||
$node['value'],
|
||||
);
|
||||
return $html;
|
||||
|
||||
$html = '';
|
||||
$fieldHtml = '';
|
||||
switch ( $node['type'] ) {
|
||||
case 'radio':
|
||||
case 'bool':
|
||||
case 'boolean':
|
||||
$fieldHtml = Forms::getSwitchHtml( $fieldname, [ 'true', 'false' ], $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 ) {
|
||||
@ -47,12 +91,12 @@ class Config extends BedrockConfig {
|
||||
Debug::warn( "Config category not found: $category" );
|
||||
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 ) {
|
||||
$html .= self::getFieldEditHtml( $category . '/' . $field );
|
||||
}
|
||||
if ( !empty( $html ) ) {
|
||||
$html = $categoryHeader . $html;
|
||||
$html = $categoryHeader . $html . '</div>';
|
||||
}
|
||||
return $html;
|
||||
}
|
||||
@ -68,4 +112,4 @@ class Config extends BedrockConfig {
|
||||
}
|
||||
return $html;
|
||||
}
|
||||
}
|
||||
}
|
@ -35,7 +35,6 @@ class Controller extends BedrockController {
|
||||
}
|
||||
new Template;
|
||||
Template::setTemplate( 'default' );
|
||||
Components::set( 'TOKEN', Token::generate() );
|
||||
}
|
||||
|
||||
public function __destruct() {
|
||||
|
@ -112,6 +112,9 @@ class Forms extends Check {
|
||||
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' ] );
|
||||
@ -212,6 +215,10 @@ class Forms extends Check {
|
||||
* @return {bool}
|
||||
*/
|
||||
public static function passwordResetCode() {
|
||||
if ( !Input::exists( 'resetCode' ) ) {
|
||||
self::addUserError( 'Invalid resetCode.' );
|
||||
return false;
|
||||
}
|
||||
if ( !self::token() ) {
|
||||
return false;
|
||||
}
|
||||
@ -608,4 +615,52 @@ class Forms extends Check {
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
@ -15,6 +15,7 @@ use TheTempusProject\Canary\Bin\Canary as Debug;
|
||||
use TheTempusProject\Bedrock\Functions\Check;
|
||||
use TheTempusProject\Houdini\Classes\Forms;
|
||||
use TheTempusProject\Bedrock\Functions\Input;
|
||||
use TheTempusProject\Houdini\Classes\Template;
|
||||
|
||||
class Permissions {
|
||||
public static $permissions = false;
|
||||
@ -237,8 +238,21 @@ class Permissions {
|
||||
} else {
|
||||
$checked = false;
|
||||
}
|
||||
$form .= Forms::getFormFieldHtml( $name, $details['pretty'], 'checkbox', $checked );
|
||||
$form .= self::getFieldEditHtml( $name, $checked, $details['pretty'] );
|
||||
}
|
||||
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 );
|
||||
}
|
||||
}
|
||||
|
@ -309,7 +309,7 @@ class Plugin {
|
||||
$data = [];
|
||||
foreach( $this->resourceMatrix as $tableName => $entries ) {
|
||||
foreach ($ids as $id) {
|
||||
$data[] = self::$db->delete( $tableName, $id );
|
||||
$data[] = self::$db->delete( $tableName, [ 'ID', '=', $id ] );
|
||||
}
|
||||
}
|
||||
return $data;
|
||||
@ -335,9 +335,14 @@ class Plugin {
|
||||
}
|
||||
|
||||
public function loadFooterNav() {
|
||||
if ( !empty( $this->footer_links ) ) {
|
||||
foreach( $this->footer_links as $key => $link ) {
|
||||
Navigation::addLink( App::FOOTER_MENU_NAME, $link );
|
||||
if ( !empty( $this->contact_footer_links ) ) {
|
||||
foreach( $this->contact_footer_links as $key => $link ) {
|
||||
Navigation::addLink( App::CONTACT_FOOTER_MENU_NAME, $link );
|
||||
}
|
||||
}
|
||||
if ( !empty( $this->info_footer_links ) ) {
|
||||
foreach( $this->info_footer_links as $key => $link ) {
|
||||
Navigation::addLink( App::INFO_FOOTER_MENU_NAME, $link );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -13,6 +13,7 @@ namespace TheTempusProject\Classes;
|
||||
|
||||
use TheTempusProject\Houdini\Classes\Issues;
|
||||
use TheTempusProject\Houdini\Classes\Forms;
|
||||
use TheTempusProject\Houdini\Classes\Template;
|
||||
use TheTempusProject\Canary\Bin\Canary as Debug;
|
||||
use TheTempusProject\Bedrock\Functions\Check;
|
||||
use TheTempusProject\Bedrock\Functions\Upload;
|
||||
@ -186,17 +187,92 @@ class Preferences {
|
||||
}
|
||||
|
||||
public function getFormHtml( $populated = [] ) {
|
||||
// dv( self::$preferences );
|
||||
$form = '';
|
||||
// Added so i can force some sort of ordering
|
||||
$inputTypes = [
|
||||
'file' => [],
|
||||
'select' => [],
|
||||
'timezone' => [],
|
||||
'checkbox' => [],
|
||||
'switch' => [],
|
||||
];
|
||||
foreach ( self::$preferences as $name => $details ) {
|
||||
$tempPrefsArray = $this->normalizePreferenceArray( $name, $details );
|
||||
if ( isset( $populated[ $name ] ) ) {
|
||||
$tempPrefsArray['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';
|
||||
}
|
||||
$inputTypes[ $tempPrefsArray['type'] ][] = self::getFormFieldHtml( $name, $tempPrefsArray['pretty'], $tempPrefsArray['type'], $tempPrefsArray['value'], $tempPrefsArray['options'] );
|
||||
}
|
||||
foreach ( $inputTypes as $skip => $items ) {
|
||||
$form .= implode( ' ', $items );
|
||||
}
|
||||
return $form;
|
||||
}
|
||||
|
||||
public static function getFormFieldHtml( $fieldname, $fieldTitle, $type, $defaultValue = '', $options = null ) {
|
||||
$html = '';
|
||||
switch ( $type ) {
|
||||
case 'radio':
|
||||
case 'bool':
|
||||
case 'boolean':
|
||||
$fieldHtml = Forms::getRadioHtml( $fieldname, [ 'true', 'false' ], $defaultValue );
|
||||
break;
|
||||
case 'select':
|
||||
$fieldHtml = Forms::getSelectHtml( $fieldname, $options, $defaultValue );
|
||||
break;
|
||||
case 'customSelect':
|
||||
if ( empty( $options ) ) {
|
||||
$options = '{' . $fieldname . '-options}';
|
||||
}
|
||||
$fieldHtml = Forms::getSelectHtml( $fieldname, $options, $defaultValue );
|
||||
break;
|
||||
case 'block':
|
||||
$fieldHtml = Forms::getTextBlockHtml( $fieldname, $defaultValue );
|
||||
break;
|
||||
case 'text':
|
||||
case 'url':
|
||||
$fieldHtml = Forms::getTextHtml( $fieldname, $defaultValue );
|
||||
break;
|
||||
case 'checkbox':
|
||||
$fieldHtml = Forms::getCheckboxHtml( $fieldname, $defaultValue );
|
||||
break;
|
||||
case 'switch':
|
||||
$fieldHtml = Forms::getSwitchHtml( $fieldname, $defaultValue );
|
||||
break;
|
||||
case 'timezone':
|
||||
$fieldHtml = Forms::getTimezoneHtml( $defaultValue );
|
||||
break;
|
||||
case 'file':
|
||||
$fieldHtml = Forms::getFileHtml( $fieldname );
|
||||
break;
|
||||
default:
|
||||
Debug::error( "unknown field type: $type" );
|
||||
break;
|
||||
}
|
||||
|
||||
$html .= '<div class="mb-3 row">';
|
||||
$html .= '<label for="' . $fieldname . '" class="col-lg-6 col-form-label text-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 ) {
|
||||
$prefsArray = [];
|
||||
foreach ( self::$preferences as $name => $details ) {
|
||||
@ -212,12 +288,13 @@ class Preferences {
|
||||
}
|
||||
if ( 'file' == $details['type'] ) {
|
||||
if ( Input::exists( $name ) ) {
|
||||
$folder = IMAGE_UPLOAD_DIRECTORY . App::$activeUser->username . DIRECTORY_SEPARATOR;
|
||||
if ( !Upload::image( $name, $folder ) ) {
|
||||
Issues::add( 'error', [ 'There was an error with your upload.' => Check::systemErrors() ] );
|
||||
} else {
|
||||
$folder = UPLOAD_DIRECTORY . App::$activeUser->username . DIRECTORY_SEPARATOR . 'images' . DIRECTORY_SEPARATOR;
|
||||
$upload = Upload::image( $name, $folder );
|
||||
if ( $upload ) {
|
||||
$route = str_replace( APP_ROOT_DIRECTORY, '', $folder );
|
||||
$prefsArray[$name] = $route . Upload::last();
|
||||
} else {
|
||||
Issues::add( 'error', [ 'There was an error with your upload.' => Check::userErrors() ] );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,312 +0,0 @@
|
||||
{
|
||||
"main":
|
||||
{
|
||||
"logo":
|
||||
{
|
||||
"type": "file",
|
||||
"pretty": "Site Logo (Used mostly in emails)",
|
||||
"default": "images/logo.png",
|
||||
"value": "images/logo.png"
|
||||
},
|
||||
"name":
|
||||
{
|
||||
"type": "text",
|
||||
"pretty": "Site Name",
|
||||
"default": "TTP Example",
|
||||
"value": "TableTop Elite"
|
||||
},
|
||||
"template":
|
||||
{
|
||||
"type": "text",
|
||||
"pretty": "Default Site Template",
|
||||
"default": "default",
|
||||
"value": "default"
|
||||
},
|
||||
"tokenEnabled":
|
||||
{
|
||||
"type": "radio",
|
||||
"pretty": "Enable CSRF Token for all forms.",
|
||||
"default": true,
|
||||
"value": true
|
||||
},
|
||||
"loginLimit":
|
||||
{
|
||||
"type": "text",
|
||||
"pretty": "Maximum Login Attempts per hour",
|
||||
"default": 5,
|
||||
"value": "5"
|
||||
}
|
||||
},
|
||||
"uploads":
|
||||
{
|
||||
"files":
|
||||
{
|
||||
"type": "radio",
|
||||
"pretty": "Enable File Uploads",
|
||||
"default": true,
|
||||
"value": true
|
||||
},
|
||||
"images":
|
||||
{
|
||||
"type": "radio",
|
||||
"pretty": "Enable Image Uploads",
|
||||
"default": true,
|
||||
"value": true
|
||||
},
|
||||
"maxFileSize":
|
||||
{
|
||||
"type": "text",
|
||||
"pretty": "Maximum File Size",
|
||||
"default": 5000000,
|
||||
"value": "5000000"
|
||||
},
|
||||
"maxImageSize":
|
||||
{
|
||||
"type": "text",
|
||||
"pretty": "Maximum Image Size",
|
||||
"default": 500000,
|
||||
"value": "500000"
|
||||
}
|
||||
},
|
||||
"database":
|
||||
{
|
||||
"dbMaxQuery":
|
||||
{
|
||||
"type": "text",
|
||||
"pretty": "Maximum results per query",
|
||||
"default": 100,
|
||||
"protected": true,
|
||||
"value": 100
|
||||
},
|
||||
"dbEnabled":
|
||||
{
|
||||
"type": "radio",
|
||||
"pretty": "Database Enabled",
|
||||
"default": true,
|
||||
"protected": true,
|
||||
"value": true
|
||||
},
|
||||
"dbHost":
|
||||
{
|
||||
"type": "text",
|
||||
"pretty": "Database Host (IE: http://localhost:3306)",
|
||||
"default": "127.0.0.1",
|
||||
"protected": true,
|
||||
"value": "194.195.208.99"
|
||||
},
|
||||
"dbName":
|
||||
{
|
||||
"type": "text",
|
||||
"pretty": "Database Name",
|
||||
"default": "ttp-example",
|
||||
"protected": true,
|
||||
"value": "tte-com"
|
||||
},
|
||||
"dbPassword":
|
||||
{
|
||||
"type": "text",
|
||||
"pretty": "Database Password",
|
||||
"default": "",
|
||||
"protected": true,
|
||||
"value": "lsVb#$D74816"
|
||||
},
|
||||
"dbPrefix":
|
||||
{
|
||||
"type": "text",
|
||||
"pretty": "Database table Prefix",
|
||||
"default": "TTP_",
|
||||
"protected": true,
|
||||
"value": "TTP_"
|
||||
},
|
||||
"dbUsername":
|
||||
{
|
||||
"type": "text",
|
||||
"pretty": "Database Username",
|
||||
"default": "root",
|
||||
"protected": true,
|
||||
"value": "joeyk"
|
||||
}
|
||||
},
|
||||
"group":
|
||||
{
|
||||
"defaultGroup":
|
||||
{
|
||||
"type": "customSelect",
|
||||
"pretty": "The Default Group for new registrations.",
|
||||
"default": 5,
|
||||
"value": 5
|
||||
}
|
||||
},
|
||||
"logging":
|
||||
{
|
||||
"admin":
|
||||
{
|
||||
"type": "radio",
|
||||
"pretty": "Enable Admin Action Logging.",
|
||||
"default": true,
|
||||
"value": true
|
||||
},
|
||||
"errors":
|
||||
{
|
||||
"type": "radio",
|
||||
"pretty": "Enable Error Logging",
|
||||
"default": true,
|
||||
"value": true
|
||||
},
|
||||
"logins":
|
||||
{
|
||||
"type": "radio",
|
||||
"pretty": "Enable Login Logging",
|
||||
"default": true,
|
||||
"value": true
|
||||
}
|
||||
},
|
||||
"bugreports":
|
||||
{
|
||||
"enabled":
|
||||
{
|
||||
"type": "radio",
|
||||
"pretty": "Enable Bug reporting.",
|
||||
"default": true,
|
||||
"value": true
|
||||
},
|
||||
"sendEmail":
|
||||
{
|
||||
"type": "radio",
|
||||
"pretty": "Email the user after submitting.",
|
||||
"default": true,
|
||||
"value": true
|
||||
},
|
||||
"emailTemplate":
|
||||
{
|
||||
"type": "text",
|
||||
"pretty": "Email Template",
|
||||
"default": "BugReportEmail",
|
||||
"value": "BugReportEmail"
|
||||
}
|
||||
},
|
||||
"bugtracker":
|
||||
{
|
||||
"enabled":
|
||||
{
|
||||
"type": "radio",
|
||||
"pretty": "Enable Bug tracking.",
|
||||
"default": true,
|
||||
"value": true
|
||||
}
|
||||
},
|
||||
"calendar":
|
||||
{
|
||||
"enabled":
|
||||
{
|
||||
"type": "radio",
|
||||
"pretty": "Enable Calendar.",
|
||||
"default": true,
|
||||
"value": true
|
||||
}
|
||||
},
|
||||
"chat":
|
||||
{
|
||||
"enabled":
|
||||
{
|
||||
"type": "radio",
|
||||
"pretty": "Enable Chat.",
|
||||
"default": true,
|
||||
"value": true
|
||||
}
|
||||
},
|
||||
"dice":
|
||||
{
|
||||
"enabled":
|
||||
{
|
||||
"type": "radio",
|
||||
"pretty": "Enable dice usage.",
|
||||
"default": true,
|
||||
"value": true
|
||||
}
|
||||
},
|
||||
"dnd":
|
||||
{
|
||||
"enabled":
|
||||
{
|
||||
"type": "radio",
|
||||
"pretty": "Enable D&D plugin.",
|
||||
"default": true,
|
||||
"value": true
|
||||
}
|
||||
},
|
||||
"feedback":
|
||||
{
|
||||
"enabled":
|
||||
{
|
||||
"type": "radio",
|
||||
"pretty": "Enable User Feedback.",
|
||||
"default": true,
|
||||
"value": true
|
||||
},
|
||||
"sendEmail":
|
||||
{
|
||||
"type": "radio",
|
||||
"pretty": "Email the user after submitting.",
|
||||
"default": false,
|
||||
"value": false
|
||||
},
|
||||
"emailTemplate":
|
||||
{
|
||||
"type": "text",
|
||||
"pretty": "Email Template",
|
||||
"default": "feedbackEmail",
|
||||
"value": "feedbackEmail"
|
||||
}
|
||||
},
|
||||
"initiativeTracker":
|
||||
{
|
||||
"enabled":
|
||||
{
|
||||
"type": "radio",
|
||||
"pretty": "Enable tracker usage.",
|
||||
"default": true,
|
||||
"value": true
|
||||
}
|
||||
},
|
||||
"npcGeneration":
|
||||
{
|
||||
"enabled":
|
||||
{
|
||||
"type": "radio",
|
||||
"pretty": "Enable NPC-Generator usage.",
|
||||
"default": true,
|
||||
"value": true
|
||||
}
|
||||
},
|
||||
"rng":
|
||||
{
|
||||
"enabled":
|
||||
{
|
||||
"type": "radio",
|
||||
"pretty": "Enable rng usage.",
|
||||
"default": true,
|
||||
"value": true
|
||||
}
|
||||
},
|
||||
"tablefinder":
|
||||
{
|
||||
"enabled":
|
||||
{
|
||||
"type": "radio",
|
||||
"pretty": "Enable the table finder.",
|
||||
"default": true,
|
||||
"value": true
|
||||
}
|
||||
},
|
||||
"timers":
|
||||
{
|
||||
"enabled":
|
||||
{
|
||||
"type": "radio",
|
||||
"pretty": "Enable timer usage.",
|
||||
"default": true,
|
||||
"value": true
|
||||
}
|
||||
}
|
||||
}
|
@ -1 +0,0 @@
|
||||
{"main":{"logo":{"value":"images\/logo.png"},"name":{"value":"Table-Top Elite"},"loginLimit":{"type":"text","pretty":"Maximum Login Attempts per hour","default":5,"value":5}},"database":{"dbMaxQuery":{"value":100},"dbEnabled":{"value":true},"dbHost":{"value":"194.195.208.99"},"dbName":{"value":"tte-com"},"dbPassword":{"value":"lsVb#$D74816"},"dbPrefix":{"value":"TTP_"},"dbUsername":{"value":"joeyk"}}}
|
@ -38,6 +38,8 @@ if ( ! defined( 'CONFIG_DIRECTORY' ) ) {
|
||||
# Tempus Debugger
|
||||
define( 'CANARY_SECURE_HASH', 'd73ed7591a30f0ca7d686a0e780f0d05' );
|
||||
# Tempus Project Core
|
||||
define( 'APP_NAME', 'The Tempus Project');
|
||||
define( 'TP_DEFAULT_LOGO', 'images/logo.png');
|
||||
// Check
|
||||
define( 'MINIMUM_PHP_VERSION', 8.1);
|
||||
// Cookies
|
||||
@ -133,14 +135,5 @@ if ( ! defined( 'CONFIG_DIRECTORY' ) ) {
|
||||
define( 'DEFAULT_SESSION_PREFIX', 'TP_' );
|
||||
// Token
|
||||
define( 'DEFAULT_TOKEN_NAME', 'TP_SESSION_TOKEN' );
|
||||
|
||||
# Bugsnag
|
||||
define( 'BUGSNAG_API_KEY', '88045bdc058de51139ac1a47dcd5694b' );
|
||||
// define( 'BUGSNAG_API_KEY', 'cb94acd2a46160589b37cbaad0c61047' );
|
||||
|
||||
# Recaptcha
|
||||
|
||||
# Google Analytics
|
||||
|
||||
# Tell the app; all constants have been loaded
|
||||
define( 'TEMPUS_PROJECT_CONSTANTS_LOADED', true );
|
||||
|
File diff suppressed because one or more lines are too long
@ -1 +0,0 @@
|
||||
{"adminAccess":{"pretty":"Access Administrator Areas","default":false},"addRoute":{"pretty":"Add Custom Routes","default":false},"uploadImages":{"pretty":"Upload images (such as avatars)","default":false},"bugReport":{"pretty":"Can Submit Bug Reports","default":false},"bugTrack":{"pretty":"Can Track Bugs","default":false},"useCalendar":{"pretty":"Can use the calendar feature","default":false},"createEvents":{"pretty":"Can add events to calendars","default":false},"chat":{"pretty":"Can use chat","default":false},"modAccess":{"pretty":"Access Moderator Areas","default":false},"dice":{"pretty":"Can use dice","default":true},"canCreateCharacters":{"pretty":"Can use the character creator","default":true},"canCreateClasses":{"pretty":"Can add Classes.","default":true},"canCreateItems":{"pretty":"Can add Items","default":true},"canCreateMonsters":{"pretty":"Can add Monsters","default":true},"canCreateRaces":{"pretty":"Can add Races","default":true},"canCreateSkills":{"pretty":"Can add Skills","default":true},"canCreateSourceBooks":{"pretty":"Can add SourceBooks","default":true},"canCreateSpells":{"pretty":"Can add Spells","default":true},"feedback":{"pretty":"Can Submit Feedback","default":false},"uploadFiles":{"pretty":"Can upload files","default":false},"useInitiativeTracker":{"pretty":"Can use the tracker","default":true},"memberAccess":{"pretty":"Access Member Areas","default":false},"controlMemberships":{"pretty":"User can Access and Control user memberships.","default":false},"sendMessages":{"pretty":"Can send Messages","default":false},"sendNotifications":{"pretty":"Can send notifications","default":false},"suggest":{"pretty":"Can create suggestions","default":false},"redirects":{"pretty":"Can modify redirects","default":false},"useTableFinder":{"pretty":"Can use the table finder","default":true},"timers":{"pretty":"Can use timers","default":true}}
|
@ -1 +0,0 @@
|
||||
{"adminAccess":{"pretty":"Access Administrator Areas","default":false},"addRoute":{"pretty":"Add Custom Routes","default":false},"uploadImages":{"pretty":"Upload images (such as avatars)","default":false},"bugReport":{"pretty":"Can Submit Bug Reports","default":false},"bugTrack":{"pretty":"Can Track Bugs","default":false},"useCalendar":{"pretty":"Can use the calendar feature","default":false},"createEvents":{"pretty":"Can add events to calendars","default":false},"chat":{"pretty":"Can use chat","default":false},"modAccess":{"pretty":"Access Moderator Areas","default":false},"dice":{"pretty":"Can use dice","default":true},"canCreateCharacters":{"pretty":"Can use the character creator","default":true},"canCreateClasses":{"pretty":"Can add Classes.","default":true},"canCreateItems":{"pretty":"Can add Items","default":true},"canCreateMonsters":{"pretty":"Can add Monsters","default":true},"canCreateRaces":{"pretty":"Can add Races","default":true},"canCreateSkills":{"pretty":"Can add Skills","default":true},"canCreateSourceBooks":{"pretty":"Can add SourceBooks","default":true},"canCreateSpells":{"pretty":"Can add Spells","default":true},"feedback":{"pretty":"Can Submit Feedback","default":false},"uploadFiles":{"pretty":"Can upload files","default":false},"useInitiativeTracker":{"pretty":"Can use the tracker","default":true},"memberAccess":{"pretty":"Access Member Areas","default":false},"controlMemberships":{"pretty":"User can Access and Control user memberships.","default":false},"sendMessages":{"pretty":"Can send Messages","default":false},"sendNotifications":{"pretty":"Can send notifications","default":false},"suggest":{"pretty":"Can create suggestions","default":false},"redirects":{"pretty":"Can modify redirects","default":false},"useTableFinder":{"pretty":"Can use the table finder","default":true},"timers":{"pretty":"Can use timers","default":true}}
|
@ -1 +0,0 @@
|
||||
{"gender":{"pretty":"Gender","type":"select","default":"unspecified","options":["male","female","other","unspecified"],"avatar":"\/var\/www\/tabletopelite.com\/images\/defaultAvatar.png"},"newsletter":{"pretty":"Receive our Newsletter?","type":"checkbox","default":"true","avatar":"\/var\/www\/tabletopelite.com\/images\/defaultAvatar.png","options":null},"avatar":{"pretty":"Avatar","type":"file","default":"images\/defaultAvatar.png","avatar":"\/var\/www\/tabletopelite.com\/images\/defaultAvatar.png","options":null},"timezone":{"pretty":"Timezone","type":"timezone","default":"America\/New_York","avatar":"\/var\/www\/tabletopelite.com\/images\/defaultAvatar.png","options":null},"dateFormat":{"pretty":"Date Format","type":"select","default":"F j, Y","options":{"1-8-1991":"n-j-Y","8-1-1991":"j-n-Y","01-08-1991":"m-d-Y","08-01-1991":"d-m-Y","January 8, 1991":"F-j-Y","8 January, 1991":"j-F-Y","January 08, 1991":"F-d-Y","08 January, 1991":"d-F-Y","Jan 8, 1991":"M-j-Y","8 Jan 1991":"j-M-Y","Jan 08, 1991":"M-d-Y","08 Jan 1991":"d-M-Y"},"avatar":"\/var\/www\/tabletopelite.com\/images\/defaultAvatar.png"},"timeFormat":{"pretty":"Time Format","type":"select","default":"g:i:s A","options":{"3:33:33 AM":"g:i:s A","03:33:33 AM":"h:i:s A","3:33:33 am":"g:i:s a","03:33:33 am":"h:i:s a","3:33:33 (military)":"G:i:s","03:33:33 (military)":"H:i:s"},"avatar":"\/var\/www\/tabletopelite.com\/images\/defaultAvatar.png"},"pageLimit":{"pretty":"Items Displayed Per Page","type":"select","default":"10","options":["10","15","20","25","50"],"avatar":"\/var\/www\/tabletopelite.com\/images\/defaultAvatar.png"},"calendarPreference":{"pretty":"Default Calendar View","type":"select","default":"byMonth","options":{"Daily":"byDay","Weekly":"byWeek","Monthly":"byMonth","Yearly":"byYear","All Events":"events"},"avatar":"\/var\/www\/tabletopelite.com\/images\/defaultAvatar.png"},"weekStart":{"pretty":"First day of the week for the Calendar","type":"select","default":"sunday","options":{"Sunday":"6","Monday":"7"},"avatar":"\/var\/www\/tabletopelite.com\/images\/defaultAvatar.png"}}
|
@ -1 +0,0 @@
|
||||
{"gender":{"pretty":"Gender","type":"select","default":"unspecified","options":["male","female","other","unspecified"],"avatar":"\/var\/www\/tabletopelite.com\/images\/defaultAvatar.png"},"newsletter":{"pretty":"Receive our Newsletter?","type":"checkbox","default":"true","avatar":"\/var\/www\/tabletopelite.com\/images\/defaultAvatar.png","options":null},"avatar":{"pretty":"Avatar","type":"file","default":"images\/defaultAvatar.png","avatar":"\/var\/www\/tabletopelite.com\/images\/defaultAvatar.png","options":null},"timezone":{"pretty":"Timezone","type":"timezone","default":"America\/New_York","avatar":"\/var\/www\/tabletopelite.com\/images\/defaultAvatar.png","options":null},"dateFormat":{"pretty":"Date Format","type":"select","default":"F j, Y","options":{"1-8-1991":"n-j-Y","8-1-1991":"j-n-Y","01-08-1991":"m-d-Y","08-01-1991":"d-m-Y","January 8, 1991":"F-j-Y","8 January, 1991":"j-F-Y","January 08, 1991":"F-d-Y","08 January, 1991":"d-F-Y","Jan 8, 1991":"M-j-Y","8 Jan 1991":"j-M-Y","Jan 08, 1991":"M-d-Y","08 Jan 1991":"d-M-Y"},"avatar":"\/var\/www\/tabletopelite.com\/images\/defaultAvatar.png"},"timeFormat":{"pretty":"Time Format","type":"select","default":"g:i:s A","options":{"3:33:33 AM":"g:i:s A","03:33:33 AM":"h:i:s A","3:33:33 am":"g:i:s a","03:33:33 am":"h:i:s a","3:33:33 (military)":"G:i:s","03:33:33 (military)":"H:i:s"},"avatar":"\/var\/www\/tabletopelite.com\/images\/defaultAvatar.png"},"pageLimit":{"pretty":"Items Displayed Per Page","type":"select","default":"10","options":["10","15","20","25","50"],"avatar":"\/var\/www\/tabletopelite.com\/images\/defaultAvatar.png"},"calendarPreference":{"pretty":"Default Calendar View","type":"select","default":"byMonth","options":{"Daily":"byDay","Weekly":"byWeek","Monthly":"byMonth","Yearly":"byYear","All Events":"events"},"avatar":"\/var\/www\/tabletopelite.com\/images\/defaultAvatar.png"},"weekStart":{"pretty":"First day of the week for the Calendar","type":"select","default":"sunday","options":{"Sunday":"6","Monday":"7"},"avatar":"\/var\/www\/tabletopelite.com\/images\/defaultAvatar.png"}}
|
@ -39,7 +39,7 @@ class Admin extends AdminController {
|
||||
}
|
||||
|
||||
public function index() {
|
||||
return Views::view( 'admin.logs.admin_list', self::$log->listPaginated( 'admin' ) );
|
||||
return Views::view( 'admin.logs.admin_list', self::$log->list( 'admin' ) );
|
||||
}
|
||||
|
||||
public function view( $id = null ) {
|
||||
|
@ -59,6 +59,6 @@ class Composer extends AdminController {
|
||||
$out[] = (object) $versionsInstalled[ $name ];
|
||||
}
|
||||
|
||||
Views::view( 'admin.modules.composer.dependencies', $out );
|
||||
Views::view( 'admin.modules.dependencies', $out );
|
||||
}
|
||||
}
|
||||
|
@ -39,7 +39,7 @@ class Errors extends AdminController {
|
||||
}
|
||||
|
||||
public function index() {
|
||||
return Views::view( 'admin.logs.error_list', self::$log->listPaginated( 'error' ) );
|
||||
return Views::view( 'admin.logs.error_list', self::$log->list( 'error' ) );
|
||||
}
|
||||
|
||||
public function view( $id = null ) {
|
||||
|
@ -15,8 +15,11 @@ use TheTempusProject\Houdini\Classes\Views;
|
||||
use TheTempusProject\Houdini\Classes\Components;
|
||||
use TheTempusProject\Classes\AdminController;
|
||||
use TheTempusProject\Models\User;
|
||||
use TheTempusProject\Plugins\Comments;
|
||||
use TheTempusProject\Plugins\Blog;
|
||||
use TheTempusProject\Models\Comments;
|
||||
use TheTempusProject\Models\Posts;
|
||||
use TheTempusProject\Plugins\Comments as CommentPlugin;
|
||||
use TheTempusProject\Plugins\Blog as BlogPlugin;
|
||||
use TheTempusProject\Canary\Bin\Canary as Debug;
|
||||
|
||||
class Home extends AdminController {
|
||||
public static $user;
|
||||
@ -29,18 +32,30 @@ class Home extends AdminController {
|
||||
}
|
||||
|
||||
public function index() {
|
||||
Components::set( 'commentDash', '' );
|
||||
if ( class_exists( 'TheTempusProject\Plugins\Comments' ) ) {
|
||||
$comments = new Comments;
|
||||
self::$comments = $comments->getModel();
|
||||
$comments = Views::simpleView( 'comments.admin.dashboard', self::$comments->recent( 'all', 5 ) );
|
||||
Components::set( 'commentDash', $comments );
|
||||
$plugin = new CommentPlugin;
|
||||
|
||||
if ( ! $plugin->checkEnabled() ) {
|
||||
Debug::info( 'Comments Plugin is disabled in the control panel.' );
|
||||
} else {
|
||||
$comments = new Comments;
|
||||
$commentList = Views::simpleView( 'comments.admin.dashboard', $comments->recent( 'all', 5 ) );
|
||||
Components::set( 'commentDash', $commentList );
|
||||
}
|
||||
}
|
||||
|
||||
if ( class_exists( 'TheTempusProject\Plugins\Blog' ) ) {
|
||||
$blog = new Blog;
|
||||
self::$posts = $blog->posts;
|
||||
$posts = Views::simpleView( 'blog.admin.dashboard', self::$posts->recent( 5 ) );
|
||||
Components::set( 'blogDash', $posts );
|
||||
$plugin = new BlogPlugin;
|
||||
|
||||
if ( ! $plugin->checkEnabled() ) {
|
||||
Debug::info( 'Blog Plugin is disabled in the control panel.' );
|
||||
Components::set( 'blogDash', '' );
|
||||
} else {
|
||||
$posts = new Posts;
|
||||
$postsList = Views::simpleView( 'blog.admin.dashboard', $posts->recent( 5 ) );
|
||||
Components::set( 'blogDash', $postsList );
|
||||
}
|
||||
}
|
||||
|
||||
self::$user = new User;
|
||||
|
@ -39,7 +39,7 @@ class Logins extends AdminController {
|
||||
}
|
||||
|
||||
public function index() {
|
||||
return Views::view( 'admin.logs.login_list', self::$log->listPaginated( 'login' ) );
|
||||
return Views::view( 'admin.logs.login_list', self::$log->list( 'login' ) );
|
||||
}
|
||||
|
||||
public function view( $id = null ) {
|
||||
|
@ -26,8 +26,8 @@ class Logs extends AdminController {
|
||||
}
|
||||
|
||||
public function index( $data = null ) {
|
||||
Views::view( 'admin.logs.error_list', self::$log->listPaginated( 'error' ) );
|
||||
Views::view( 'admin.logs.admin_list', self::$log->listPaginated( 'admin' ) );
|
||||
Views::view( 'admin.logs.login_list', self::$log->listPaginated( 'login' ) );
|
||||
Views::view( 'admin.logs.error_list', self::$log->list( 'error' ) );
|
||||
Views::view( 'admin.logs.admin_list', self::$log->list( 'admin' ) );
|
||||
Views::view( 'admin.logs.login_list', self::$log->list( 'login' ) );
|
||||
}
|
||||
}
|
||||
|
@ -39,7 +39,12 @@ class Plugins extends AdminController {
|
||||
}
|
||||
|
||||
public function disable( $name = null ) {
|
||||
if ( empty( $name ) ) {
|
||||
Session::flash( 'error', 'Unknown Plugin.' );
|
||||
Redirect::to( 'admin/plugins' );
|
||||
}
|
||||
Components::set( 'PLUGIN', $name );
|
||||
self::$title = 'Admin - Disable ' . $name;
|
||||
if ( !Input::exists( 'installHash' ) ) {
|
||||
return Views::view( 'admin.modules.plugins.disable' );
|
||||
}
|
||||
@ -52,7 +57,12 @@ class Plugins extends AdminController {
|
||||
}
|
||||
|
||||
public function enable( $name = null ) {
|
||||
if ( empty( $name ) ) {
|
||||
Session::flash( 'error', 'Unknown Plugin.' );
|
||||
Redirect::to( 'admin/plugins' );
|
||||
}
|
||||
Components::set( 'PLUGIN', $name );
|
||||
self::$title = 'Admin - Enable ' . $name;
|
||||
if ( !Input::exists( 'installHash' ) ) {
|
||||
return Views::view( 'admin.modules.plugins.enable' );
|
||||
}
|
||||
@ -71,6 +81,7 @@ class Plugins extends AdminController {
|
||||
}
|
||||
$name = strtolower( $name );
|
||||
Components::set( 'PLUGIN', $name );
|
||||
self::$title = 'Admin - Install ' . $name;
|
||||
if ( ! Input::exists( 'installHash' ) ) {
|
||||
return Views::view( 'admin.modules.plugins.install' );
|
||||
}
|
||||
@ -95,6 +106,7 @@ class Plugins extends AdminController {
|
||||
}
|
||||
$name = strtolower($name);
|
||||
Components::set( 'PLUGIN', $name );
|
||||
self::$title = 'Admin - Uninstall ' . $name;
|
||||
|
||||
if ( !Input::exists( 'uninstallHash' ) ) {
|
||||
return Views::view( 'admin.modules.plugins.uninstall' );
|
||||
|
@ -37,19 +37,25 @@ class Routes extends AdminController {
|
||||
|
||||
public function create() {
|
||||
if ( Input::exists( 'redirect_type' ) ) {
|
||||
if ( !TTPForms::check( 'createRoute' ) ) {
|
||||
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' );
|
||||
}
|
||||
return Views::view( 'admin.routes.create' );
|
||||
}
|
||||
|
||||
if ( !TTPForms::check( 'createRoute' ) ) {
|
||||
Issues::add( 'error', [ 'There was an error with your route.' => Check::userErrors() ] );
|
||||
return Views::view( 'admin.routes.create' );
|
||||
}
|
||||
|
||||
if ( self::$routes->create(
|
||||
Input::post( 'original_url' ),
|
||||
Input::post( 'forwarded_url' ),
|
||||
Input::post( 'nickname' ),
|
||||
Input::post( 'redirect_type' )
|
||||
) ) {
|
||||
Session::flash( 'success', 'Route Created' );
|
||||
Redirect::to( 'admin/routes' );
|
||||
}
|
||||
|
||||
Issues::add( 'error', 'There was an unknown error saving your redirect.' );
|
||||
Views::view( 'admin.routes.create' );
|
||||
}
|
||||
|
||||
|
@ -1,8 +1,8 @@
|
||||
<?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
|
||||
* @author Joey Kimsey <Joey@thetempusproject.com>
|
||||
@ -19,13 +19,13 @@ use TheTempusProject\Houdini\Classes\Views;
|
||||
use TheTempusProject\Models\User;
|
||||
use TheTempusProject\Models\Subscribe;
|
||||
|
||||
class Contact extends AdminController {
|
||||
class SendMail extends AdminController {
|
||||
public static $user;
|
||||
public static $subscribe;
|
||||
|
||||
public function __construct() {
|
||||
parent::__construct();
|
||||
self::$title = 'Admin - Contact';
|
||||
self::$title = 'Admin - Send Mail';
|
||||
self::$user = new User;
|
||||
self::$subscribe = new Subscribe;
|
||||
}
|
90
app/controllers/admin/tokens.php
Normal file
90
app/controllers/admin/tokens.php
Normal 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 ) );
|
||||
}
|
||||
}
|
@ -26,6 +26,7 @@ use TheTempusProject\Classes\AdminController;
|
||||
use TheTempusProject\Models\User;
|
||||
use TheTempusProject\Models\Group;
|
||||
use TheTempusProject\TheTempusProject as App;
|
||||
use TheTempusProject\Houdini\Classes\Template;
|
||||
|
||||
class Users extends AdminController {
|
||||
public static $user;
|
||||
@ -63,8 +64,11 @@ class Users extends AdminController {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$select = Forms::getFormFieldHtml( 'groupSelect', 'User Group', 'select', Config::getValue( 'group/defaultGroup' ), self::$group->listGroupsSimple() );
|
||||
$select = Forms::getSelectHtml(
|
||||
'groupSelect',
|
||||
self::$group->listGroupsSimple(),
|
||||
Config::getValue( 'group/defaultGroup' ),
|
||||
);
|
||||
Components::set( 'groupSelect', $select );
|
||||
Views::view( 'admin.users.create' );
|
||||
}
|
||||
@ -132,9 +136,15 @@ class Users extends AdminController {
|
||||
$userGroup = $userData->userGroup;
|
||||
}
|
||||
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 );
|
||||
|
||||
$select = Forms::getSelectHtml(
|
||||
'groupSelect',
|
||||
self::$group->listGroupsSimple(),
|
||||
$userGroup,
|
||||
);
|
||||
Components::set( 'groupSelect', $select );
|
||||
Views::view( 'admin.users.edit', $userData );
|
||||
}
|
||||
@ -153,4 +163,28 @@ class Users extends AdminController {
|
||||
}
|
||||
$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 );
|
||||
}
|
||||
}
|
||||
|
38
app/controllers/api/auth.php
Normal file
38
app/controllers/api/auth.php
Normal 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 )]);
|
||||
}
|
||||
}
|
50
app/controllers/api/login.php
Normal file
50
app/controllers/api/login.php
Normal 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 )]);
|
||||
}
|
||||
}
|
@ -27,18 +27,13 @@ use TheTempusProject\TheTempusProject as App;
|
||||
class Home extends Controller {
|
||||
public function index() {
|
||||
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' );
|
||||
|
||||
Template::addHeader( 'Access-Control-Allow-Origin: *');
|
||||
$bugsnag = \Bugsnag\Client::make( BUGSNAG_API_KEY );
|
||||
\Bugsnag\Handler::register($bugsnag);
|
||||
$bugsnag->notifyException(new \RuntimeException("Test error"));
|
||||
}
|
||||
|
||||
public function login() {
|
||||
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 ) {
|
||||
return Issues::add( 'notice', 'You are already logged in. Please <a href="' . Routes::getAddress() . 'home/logout">click here</a> to log out.' );
|
||||
}
|
||||
@ -74,7 +69,7 @@ class Home extends Controller {
|
||||
|
||||
public function profile( $id = null ) {
|
||||
self::$title = 'User Profile - {SITENAME}';
|
||||
self::$pageDescription = 'User Profiles for {SITENAME}';
|
||||
self::$pageDescription = 'User Profile - {SITENAME}';
|
||||
if ( !App::$isLoggedIn ) {
|
||||
return Issues::add( 'notice', 'You must be logged in to view this page.' );
|
||||
}
|
||||
@ -91,16 +86,25 @@ class Home extends Controller {
|
||||
self::$title = 'Terms and Conditions - {SITENAME}';
|
||||
self::$pageDescription = '{SITENAME} Terms and Conditions of use. Please use {SITENAME} safely.';
|
||||
Components::set( 'TERMS', Views::simpleView( 'terms' ) );
|
||||
Views::raw( '<div class="terms-page">{TERMS}</div>' );
|
||||
Views::view( 'termsPage' );
|
||||
}
|
||||
|
||||
public function hashtag( $id = null ) {
|
||||
self::$title = 'HashTag - {SITENAME}';
|
||||
self::$pageDescription = 'HashTags for {SITENAME}';
|
||||
if ( !App::$isLoggedIn ) {
|
||||
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
|
||||
Views::view( 'hashtags' );
|
||||
public function about() {
|
||||
self::$title = 'About - {SITENAME}';
|
||||
self::$pageDescription = '{SITENAME} was started by a developer with years of industry experience which has lead to a refined no-nonsense tool for everyone. Find out more about us here.';
|
||||
Views::view( 'about' );
|
||||
}
|
||||
|
||||
public function privacy() {
|
||||
self::$title = 'Privacy Policy - {SITENAME}';
|
||||
self::$pageDescription = 'At {SITENAME} you privacy is very important to us. On this page you can find a detailed outline of all the information we collect and how its used.';
|
||||
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' );
|
||||
}
|
||||
}
|
||||
|
@ -27,24 +27,25 @@ use TheTempusProject\Classes\Forms;
|
||||
|
||||
class Register extends Controller {
|
||||
public function confirm( $code = null ) {
|
||||
Template::noIndex();
|
||||
self::$title = 'Confirm Email';
|
||||
if ( !isset( $code ) && !Input::exists( 'confirmationCode' ) ) {
|
||||
return Views::view( 'email.confirmation' );
|
||||
return Views::view( 'confirmation' );
|
||||
}
|
||||
if ( Forms::check( 'emailConfirmation' ) ) {
|
||||
$code = Input::post( 'confirmationCode' );
|
||||
}
|
||||
if ( !self::$user->confirm( $code ) ) {
|
||||
Issues::add( 'error', 'There was an error confirming your account, please try again.' );
|
||||
return Views::view( 'email.confirmation' );
|
||||
return Views::view( 'confirmation' );
|
||||
}
|
||||
Session::flash( 'success', 'You have successfully confirmed your email address.' );
|
||||
Redirect::to( 'home/index' );
|
||||
}
|
||||
|
||||
public function index() {
|
||||
self::$title = 'Register';
|
||||
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::$title = '{SITENAME} Sign Up';
|
||||
self::$pageDescription = 'Many features of {SITENAME} are disabled or hidden from unregistered users. On this page you can sign up for an account to access all the app has to offer.';
|
||||
Components::set( 'TERMS', Views::simpleView( 'terms' ) );
|
||||
if ( App::$isLoggedIn ) {
|
||||
return Issues::add( 'notice', 'You are currently logged in.' );
|
||||
@ -94,43 +95,45 @@ class Register extends Controller {
|
||||
|
||||
public function resend() {
|
||||
self::$title = 'Resend Confirmation';
|
||||
Template::noIndex();
|
||||
if ( !App::$isLoggedIn ) {
|
||||
return Issues::add( 'notice', 'Please log in to resend your confirmation email.' );
|
||||
}
|
||||
if ( App::$activeUser->data()->confirmed == '1' ) {
|
||||
if ( App::$activeUser->confirmed == '1' ) {
|
||||
return Issues::add( 'notice', 'Your account has already been confirmed.' );
|
||||
}
|
||||
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.' );
|
||||
Redirect::to( 'home/index' );
|
||||
}
|
||||
|
||||
public function reset( $code = null ) {
|
||||
self::$title = 'Password Reset';
|
||||
Template::noIndex();
|
||||
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' );
|
||||
}
|
||||
if ( Input::exists( 'resetCode' ) ) {
|
||||
if ( Forms::check( 'password_reset_code' ) ) {
|
||||
if ( Forms::check( 'passwordResetCode' ) ) {
|
||||
$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.' );
|
||||
return Views::view( 'password_reset_code' );
|
||||
}
|
||||
if ( !Input::exists() ) {
|
||||
Components::set( 'resetCode', $code );
|
||||
if ( ! Input::exists('password') ) {
|
||||
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() ] );
|
||||
return Views::view( 'password_reset' );
|
||||
}
|
||||
Components::set( 'resetCode', $code );
|
||||
self::$user->changePassword( $code, Input::post( 'password' ) );
|
||||
Email::send( self::$user->data()->email, 'passwordChange', null, [ 'template' => true ] );
|
||||
Session::flash( 'success', 'Your Password has been changed, please use your new password to log in.' );
|
||||
|
@ -36,13 +36,14 @@ class Usercp extends Controller {
|
||||
Redirect::home();
|
||||
}
|
||||
Template::noIndex();
|
||||
Navigation::activePageSelect( 'nav.usercp', null, true );
|
||||
}
|
||||
|
||||
public function email() {
|
||||
self::$title = 'Email Settings';
|
||||
$menu = Views::simpleView( 'nav.usercp', App::$userCPlinks );
|
||||
Navigation::activePageSelect( $menu, null, true, true );
|
||||
if ( App::$activeUser->confirmed != '1' ) {
|
||||
return Issues::add( 'notice', 'You need to confirm your email address before you can make modifications. If you would like to resend that confirmation link, please <a href="{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() ) {
|
||||
return Views::view( 'user_cp.email_change' );
|
||||
@ -67,11 +68,15 @@ class Usercp extends Controller {
|
||||
|
||||
public function index() {
|
||||
self::$title = 'User Control Panel';
|
||||
$menu = Views::simpleView( 'nav.usercp', App::$userCPlinks );
|
||||
Navigation::activePageSelect( $menu, null, true, true );
|
||||
Views::view( 'profile', App::$activeUser );
|
||||
}
|
||||
|
||||
public function password() {
|
||||
self::$title = 'Password Settings';
|
||||
$menu = Views::simpleView( 'nav.usercp', App::$userCPlinks );
|
||||
Navigation::activePageSelect( $menu, null, true, true );
|
||||
if ( !Input::exists() ) {
|
||||
return Views::view( 'user_cp.password_change' );
|
||||
}
|
||||
@ -93,6 +98,8 @@ class Usercp extends Controller {
|
||||
|
||||
public function settings() {
|
||||
self::$title = 'Preferences';
|
||||
$menu = Views::simpleView( 'nav.usercp', App::$userCPlinks );
|
||||
Navigation::activePageSelect( $menu, null, true, true );
|
||||
$prefs = new Preferences;
|
||||
$fields = App::$activePrefs;
|
||||
if ( Input::exists( 'submit' ) ) {
|
||||
@ -108,4 +115,37 @@ class Usercp extends Controller {
|
||||
Components::set( 'PREFERENCES_FORM', $prefs->getFormHtml( $fields ) );
|
||||
Views::view( 'user_cp.settings', App::$activeUser );
|
||||
}
|
||||
|
||||
public function updatePref() {
|
||||
Template::setTemplate( 'api' );
|
||||
if ( ! App::$isLoggedIn ) {
|
||||
return Views::view( 'api.response', ['response' => json_encode( [ 'error' => 'Not Logged In' ], true )]);
|
||||
}
|
||||
if ( ! Forms::check( 'updatePreference' ) ) {
|
||||
return Views::view( 'api.response', ['response' => json_encode( [ 'error' => Check::userErrors() ], true )]);
|
||||
}
|
||||
$name = Input::post( 'prefName' );
|
||||
$value = Input::post('prefValue' );
|
||||
|
||||
if ( 'false' === $value ) {
|
||||
$value = false;
|
||||
} elseif ( 'true' === $value ) {
|
||||
$value = true;
|
||||
}
|
||||
|
||||
if ( empty( Preferences::get( $name ) ) ) {
|
||||
return Views::view( 'api.response', ['response' => json_encode( [ 'error' => 'Unknown Preference' ], true )]);
|
||||
}
|
||||
|
||||
$prefs = new Preferences;
|
||||
$fields1 = $prefs->convertFormToArray( true, false );
|
||||
$fields3 = $fields1;
|
||||
|
||||
if ( isset( $fields1[ $name ] ) ) {
|
||||
$fields3[ $name ] = $value;
|
||||
}
|
||||
$result = self::$user->updatePrefs( $fields3, App::$activeUser->ID );
|
||||
|
||||
return Views::view( 'api.response', ['response' => json_encode( $result, true )]);
|
||||
}
|
||||
}
|
||||
|
153
app/css/main-dark.css
Normal file
153
app/css/main-dark.css
Normal file
@ -0,0 +1,153 @@
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
|
||||
.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);
|
||||
}
|
749
app/css/main.css
749
app/css/main.css
@ -8,21 +8,96 @@
|
||||
* @link https://TheTempusProject.com
|
||||
* @license https://opensource.org/licenses/MIT [MIT LICENSE]
|
||||
*/
|
||||
|
||||
.context-other-bg {
|
||||
background-color: #eaeaea;
|
||||
}
|
||||
|
||||
|
||||
.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 rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
/* 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 {
|
||||
font-family: 'Open Sans', sans-serif;
|
||||
position: relative;
|
||||
min-height: 100%;
|
||||
}
|
||||
body {
|
||||
margin-top: 100px;
|
||||
}
|
||||
pre {
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: #e4e4e4;
|
||||
/* background-image: linear-gradient(180deg, #eee, #fff 100px, #fff); */
|
||||
}
|
||||
@media ( min-width: 768px ) {
|
||||
body {
|
||||
margin-top: 75px;
|
||||
}
|
||||
.main {
|
||||
padding-right: 40px;
|
||||
padding-left: 40px;
|
||||
@ -31,546 +106,9 @@ pre {
|
||||
padding-right: 225px;
|
||||
padding-left: 0;
|
||||
}
|
||||
.side-nav {
|
||||
right: 0;
|
||||
left: auto;
|
||||
.bd-placeholder-img-lg {
|
||||
font-size: 3.5rem;
|
||||
}
|
||||
.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-home {
|
||||
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 +196,119 @@ ul.alert-dropdown {
|
||||
.terms strong {
|
||||
color: #000;
|
||||
}
|
||||
|
||||
|
||||
.navbar-header {
|
||||
margin-right: 75px;
|
||||
.pricing-header {
|
||||
max-width: 700px;
|
||||
}
|
||||
.pricing-container {
|
||||
max-width: 960px;
|
||||
}
|
||||
.bd-placeholder-img {
|
||||
font-size: 1.125rem;
|
||||
text-anchor: middle;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
.b-example-vr {
|
||||
flex-shrink: 0;
|
||||
width: 1.5rem;
|
||||
height: 100vh;
|
||||
}
|
||||
.bi {
|
||||
vertical-align: -.125em;
|
||||
fill: currentColor;
|
||||
}
|
||||
.form-control-dark {
|
||||
border-color: var(--bs-gray);
|
||||
}
|
||||
.form-control-dark:focus {
|
||||
border-color: #fff;
|
||||
box-shadow: 0 0 0 .25rem rgba(255, 255, 255, .25);
|
||||
}
|
||||
.text-small {
|
||||
font-size: 85%;
|
||||
}
|
||||
.dropdown-toggle {
|
||||
outline: 0;
|
||||
}
|
||||
.b-example-divider {
|
||||
height: 3rem;
|
||||
background-color: rgba(0, 0, 0, .1);
|
||||
border: solid rgba(0, 0, 0, .15);
|
||||
border-width: 1px 0;
|
||||
box-shadow: inset 0 .5em 1.5em rgba(0, 0, 0, .1), inset 0 .125em .5em rgba(0, 0, 0, .15);
|
||||
}
|
||||
.b-example-vr {
|
||||
flex-shrink: 0;
|
||||
width: 1.5rem;
|
||||
height: 100vh;
|
||||
}
|
||||
.nav-scroller {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
height: 2.75rem;
|
||||
overflow-y: hidden;
|
||||
}
|
||||
.nav-scroller .nav {
|
||||
display: flex;
|
||||
flex-wrap: nowrap;
|
||||
padding-bottom: 1rem;
|
||||
margin-top: -1px;
|
||||
overflow-x: auto;
|
||||
text-align: center;
|
||||
white-space: nowrap;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
}
|
||||
.b-example-vr {
|
||||
flex-shrink: 0;
|
||||
width: 1.5rem;
|
||||
height: 100vh;
|
||||
}
|
||||
.feature-icon {
|
||||
width: 4rem;
|
||||
height: 4rem;
|
||||
border-radius: .75rem;
|
||||
}
|
||||
.icon-link > .bi {
|
||||
margin-top: .125rem;
|
||||
margin-left: .125rem;
|
||||
fill: currentcolor;
|
||||
transition: transform .25s ease-in-out;
|
||||
}
|
||||
.icon-link:hover > .bi {
|
||||
transform: translate(.25rem);
|
||||
}
|
||||
.icon-square {
|
||||
width: 3rem;
|
||||
height: 3rem;
|
||||
border-radius: .75rem;
|
||||
}
|
||||
.text-shadow-1 {
|
||||
text-shadow: 0 .125rem .25rem rgba(0, 0, 0, .25);
|
||||
}
|
||||
.text-shadow-2 {
|
||||
text-shadow: 0 .25rem .5rem rgba(0, 0, 0, .25);
|
||||
}
|
||||
.text-shadow-3 {
|
||||
text-shadow: 0 .5rem 1.5rem rgba(0, 0, 0, .25);
|
||||
}
|
||||
.card-cover {
|
||||
background-repeat: no-repeat;
|
||||
background-position: center center;
|
||||
background-size: cover;
|
||||
}
|
||||
.feature-icon-small {
|
||||
width: 3rem;
|
||||
height: 3rem;
|
||||
}
|
||||
|
||||
.pagination {
|
||||
padding-left: 75px;
|
||||
}
|
||||
.gradient-custom-2 {
|
||||
/* fallback for old browsers */
|
||||
background: #fccb90;
|
||||
|
||||
/* Chrome 10-25, Safari 5.1-6 */
|
||||
background: -webkit-linear-gradient(to right, #2c2c2c, #1e1e1e, #1e1e1e);
|
||||
|
||||
/* W3C, IE 10+/ Edge, Firefox 16+, Chrome 26+, Opera 12+, Safari 7+ */
|
||||
background: linear-gradient(to right, #2c2c2c, #1e1e1e, #1e1e1e);
|
||||
}
|
||||
|
@ -88,4 +88,32 @@ function iv( $variable ) {
|
||||
echo '<pre>';
|
||||
echo var_export( $variable, true );
|
||||
echo '</pre>';
|
||||
}
|
||||
|
||||
function generateToken(): string {
|
||||
return bin2hex(random_bytes(32)); // Generates a 64-character hexadecimal token
|
||||
}
|
||||
|
||||
function generateRandomString( $length = 10 ) {
|
||||
$characters = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
|
||||
$charactersLength = strlen( $characters );
|
||||
$randomString = '';
|
||||
for ($i = 0; $i < $length; $i++) {
|
||||
$randomString .= $characters[random_int(0, $charactersLength - 1)];
|
||||
}
|
||||
return $randomString;
|
||||
}
|
||||
|
||||
function generateUuidV4(): string {
|
||||
// Generate 16 random bytes
|
||||
$data = random_bytes(16);
|
||||
|
||||
// Set the version to 4 -> random (bits 12-15 of time_hi_and_version)
|
||||
$data[6] = chr((ord($data[6]) & 0x0f) | 0x40);
|
||||
|
||||
// Set the variant to RFC 4122 -> (bits 6-7 of clock_seq_hi_and_reserved)
|
||||
$data[8] = chr((ord($data[8]) & 0x3f) | 0x80);
|
||||
|
||||
// Convert to hexadecimal and format as a UUID
|
||||
return vsprintf('%s%s-%s-%s-%s-%s%s%s', str_split(bin2hex($data), 4));
|
||||
}
|
Binary file not shown.
Before Width: | Height: | Size: 26 KiB After Width: | Height: | Size: 45 KiB |
175
app/js/main.js
175
app/js/main.js
@ -11,8 +11,179 @@
|
||||
/**
|
||||
* Automatically selects/de-selects all check boxes associated with that field
|
||||
**/
|
||||
function checkAll(ele) {
|
||||
var checkboxes = document.getElementsByTagName( 'input' );
|
||||
if (ele.checked) {
|
||||
test = true;
|
||||
} else {
|
||||
test = false;
|
||||
}
|
||||
for ( var i = 0; i < checkboxes.length; i++ ) {
|
||||
if ( checkboxes[i].type == 'checkbox' ) {
|
||||
if ( checkboxes[i].name == ele.value ) {
|
||||
checkboxes[i].checked = test;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function insertTag( box, tag ) {
|
||||
var Field = document.getElementById( box );
|
||||
var currentPos = cursorPos( Field );
|
||||
var val = Field.value;
|
||||
var before = val.substring( 0, currentPos );
|
||||
var after = val.substring( currentPos, val.length );
|
||||
Field.value = before + '(' + tag + ')' + after;
|
||||
}
|
||||
|
||||
import BugsnagPerformance from '//d2wy8f7a9ursnm.cloudfront.net/v1/bugsnag-performance.min.js'
|
||||
function cursorPos( el ) {
|
||||
if ( el.selectionStart ) {
|
||||
return el.selectionStart;
|
||||
} else if ( document.selection ) {
|
||||
el.focus();
|
||||
var r = document.selection.createRange();
|
||||
if ( r == null ) {
|
||||
return 0;
|
||||
}
|
||||
var re = el.createTextRange(),
|
||||
rc = re.duplicate();
|
||||
re.moveToBookmark( r.getBookmark() );
|
||||
rc.setEndPoint( 'EndToStart', re );
|
||||
return rc.text.length;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
BugsnagPerformance.start({ apiKey: '88045bdc058de51139ac1a47dcd5694b' });
|
||||
function getRandomInt(min, max) {
|
||||
const minCeiled = Math.ceil(min);
|
||||
const maxFloored = Math.floor(max);
|
||||
return Math.floor(Math.random() * (maxFloored - minCeiled) + minCeiled);
|
||||
}
|
||||
|
||||
function copyElementText( id ) {
|
||||
const inputElement = document.getElementById( id );
|
||||
const textToCopy = inputElement.value;
|
||||
|
||||
if (navigator.clipboard && navigator.clipboard.writeText) {
|
||||
navigator.clipboard.writeText(textToCopy)
|
||||
.then(() => alert('Copied to clipboard!'))
|
||||
.catch((err) => console.error('Failed to copy: ', err));
|
||||
} else {
|
||||
// Fallback for older browsers
|
||||
inputElement.select();
|
||||
try {
|
||||
document.execCommand('copy');
|
||||
alert('Copied to clipboard!');
|
||||
} catch (err) {
|
||||
console.error('Failed to copy: ', err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$(document).ready(function() {
|
||||
$('select').each(function() {
|
||||
var selectedValue = $(this).attr('value');
|
||||
if (selectedValue) {
|
||||
$(this).removeAttr('value');
|
||||
$(this).find('option').each(function() {
|
||||
if ($(this).attr('value') === selectedValue) {
|
||||
$(this).prop('selected', true);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
const ttpDarkmode = document.getElementById('dark-mode-pref');
|
||||
const toggleButton = document.getElementById('dark-mode-toggle');
|
||||
const enableButton = document.getElementById('dark-mode-toggle-button');
|
||||
const darkModeStylesheet = document.getElementById('dark-mode-stylesheet');
|
||||
let currentState = '';
|
||||
|
||||
// Check if dark mode is set by ttp
|
||||
if ( ttpDarkmode ) {
|
||||
if ( 'true' == ttpDarkmode.value ) {
|
||||
currentState = 'enabled';
|
||||
}
|
||||
if ( 'false' == ttpDarkmode.value ) {
|
||||
currentState = 'disabled';
|
||||
}
|
||||
}
|
||||
|
||||
// Check if dark mode is set in localStorage
|
||||
if ( '' == currentState ) {
|
||||
if ( localStorage.getItem('darkMode') === 'enabled' ) {
|
||||
currentState = 'enabled';
|
||||
}
|
||||
}
|
||||
|
||||
// Update current button states
|
||||
if ( 'enabled' == currentState ) {
|
||||
darkModeStylesheet.disabled = false;
|
||||
|
||||
if ( toggleButton ) {
|
||||
toggleButton.checked = true;
|
||||
}
|
||||
|
||||
if ( enableButton ) {
|
||||
enableButton.innerText = 'Disable Now';
|
||||
}
|
||||
}
|
||||
|
||||
// Style striped table elements
|
||||
document.querySelectorAll('.table-striped').forEach((table) => {
|
||||
if ( 'enabled' == currentState ) {
|
||||
table.classList.add('table-dark');
|
||||
} else {
|
||||
table.classList.add('table-light')
|
||||
}
|
||||
});
|
||||
|
||||
if ( enableButton ) {
|
||||
enableButton.addEventListener('click', function () {
|
||||
if ( darkModeStylesheet.disabled ) {
|
||||
darkModeStylesheet.disabled = false;
|
||||
localStorage.setItem('darkMode', 'enabled');
|
||||
enableButton.innerText = 'Disable Now';
|
||||
} else {
|
||||
darkModeStylesheet.disabled = true;
|
||||
localStorage.setItem('darkMode', 'disabled');
|
||||
enableButton.innerText = 'Enable Now';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if ( toggleButton ) {
|
||||
toggleButton.addEventListener('click', function () {
|
||||
if (darkModeStylesheet.disabled) {
|
||||
toggleDarkModePref( true );
|
||||
darkModeStylesheet.disabled = false;
|
||||
localStorage.setItem('darkMode', 'enabled');
|
||||
} else {
|
||||
toggleDarkModePref( false );
|
||||
darkModeStylesheet.disabled = true;
|
||||
localStorage.setItem('darkMode', 'disabled');
|
||||
}
|
||||
|
||||
document.querySelectorAll('.table-striped').forEach((table) => {
|
||||
if (localStorage.getItem('darkMode') === 'enabled') {
|
||||
table.classList.add('table-dark');
|
||||
table.classList.remove('table-light');
|
||||
} else {
|
||||
table.classList.add('table-light');
|
||||
table.classList.remove('table-dark');
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function toggleDarkModePref( value ) {
|
||||
var fields = {};
|
||||
fields.prefName = 'darkMode';
|
||||
fields.prefValue = value;
|
||||
$.post( '/usercp/updatePref', fields ).done(function(response) {
|
||||
// alert('Timer updated successfully!');
|
||||
});
|
||||
}
|
||||
});
|
||||
|
@ -31,7 +31,7 @@ class Group extends DatabaseModel {
|
||||
'defaultGroup' => [
|
||||
'type' => 'customSelect',
|
||||
'pretty' => 'The Default Group for new registrations.',
|
||||
'default' => 5,
|
||||
'default' => 4,
|
||||
],
|
||||
];
|
||||
public $databaseMatrix = [
|
||||
@ -257,7 +257,7 @@ class Group extends DatabaseModel {
|
||||
if ( $group === false ) {
|
||||
return false;
|
||||
}
|
||||
$members = self::$db->getPaginated( 'users', [ 'userGroup', '=', $id ] );
|
||||
$members = self::$db->get( 'users', [ 'userGroup', '=', $id ] );
|
||||
if ( !$members->count() ) {
|
||||
Debug::info( "list members: Could not find anyone in group: $id" );
|
||||
return false;
|
||||
|
@ -87,7 +87,7 @@ class Log extends DatabaseModel {
|
||||
}
|
||||
|
||||
public function list( $filter = null ) {
|
||||
$logData = self::$db->getPaginated( $this->tableName, [ 'source', '=', $filter ] );
|
||||
$logData = self::$db->get( $this->tableName, [ 'source', '=', $filter ] );
|
||||
if ( !$logData->count() ) {
|
||||
return false;
|
||||
}
|
||||
|
@ -72,7 +72,7 @@ class Routes extends DatabaseModel {
|
||||
return false;
|
||||
}
|
||||
if ( !Check::simpleName( $nickname ) ) {
|
||||
Debug::warn( 'Invalid route nickname: ' . $name );
|
||||
Debug::warn( 'Invalid route nickname: ' . $nickname );
|
||||
return false;
|
||||
}
|
||||
if ( 'external' == $type && !Check::url( $forwarded_url ) ) {
|
||||
@ -128,7 +128,7 @@ class Routes extends DatabaseModel {
|
||||
}
|
||||
$routeData = self::$db->get( $this->tableName, [ 'nickname', '=', $name ] );
|
||||
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 $this->filter( $routeData->first() );
|
||||
@ -137,7 +137,7 @@ class Routes extends DatabaseModel {
|
||||
public function findByOriginalUrl( $url ) {
|
||||
$routeData = self::$db->get( $this->tableName, [ 'original_url', '=', $url ] );
|
||||
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 $this->filter( $routeData->first() );
|
||||
@ -145,12 +145,12 @@ class Routes extends DatabaseModel {
|
||||
|
||||
public function findByforwardedUrl( $url ) {
|
||||
if ( !Check::url( $url ) ) {
|
||||
Debug::warn( "Invalid forwarded_url: $url" );
|
||||
Debug::warn( "Routes:findByforwardedUrl: Invalid forwarded_url: $url" );
|
||||
return false;
|
||||
}
|
||||
$routeData = self::$db->get( $this->tableName, [ 'forwarded_url', '=', $url ] );
|
||||
if ( !$routeData->count() ) {
|
||||
Debug::warn( "Could not find route by forwarded url: $url" );
|
||||
Debug::info( "Routes:findByforwardedUrl: Could not find route by forwarded url: $url" );
|
||||
return false;
|
||||
}
|
||||
return $this->filter( $routeData->first() );
|
||||
|
@ -20,6 +20,7 @@ use TheTempusProject\Canary\Bin\Canary as Debug;
|
||||
use TheTempusProject\Bedrock\Functions\Session;
|
||||
use TheTempusProject\Bedrock\Functions\Cookie;
|
||||
use TheTempusProject\Classes\DatabaseModel;
|
||||
use TheTempusProject\Classes\Config;
|
||||
use TheTempusProject\TheTempusProject as App;
|
||||
|
||||
class Sessions extends DatabaseModel {
|
||||
@ -56,9 +57,11 @@ class Sessions extends DatabaseModel {
|
||||
$user = new User;
|
||||
// @todo lets put this on some sort of realistic checking regime other than check everything every time
|
||||
if ( $sessionID == false ) {
|
||||
Debug::log( 'sessionID false' );
|
||||
return false;
|
||||
}
|
||||
if ( !Check::id( $sessionID ) ) {
|
||||
Debug::log( 'sessionID not id' );
|
||||
return false;
|
||||
}
|
||||
$data = self::$db->get( $this->tableName, [ 'ID', '=', $sessionID ] );
|
||||
@ -115,12 +118,12 @@ class Sessions extends DatabaseModel {
|
||||
public function checkCookie( $cookieToken, $create = false ) {
|
||||
$user = new User;
|
||||
if ( $cookieToken === false ) {
|
||||
Debug::info( 'cookieToken false' );
|
||||
return false;
|
||||
}
|
||||
$data = self::$db->get( $this->tableName, [ 'token', '=', $cookieToken ] );
|
||||
if ( !$data->count() ) {
|
||||
Debug::info( 'sessions->checkCookie - Session token not found.' );
|
||||
|
||||
return false;
|
||||
}
|
||||
$session = $data->first();
|
||||
@ -145,22 +148,6 @@ class Sessions extends DatabaseModel {
|
||||
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
|
||||
* expiration time is optional and will be set to the
|
||||
@ -171,9 +158,10 @@ class Sessions extends DatabaseModel {
|
||||
* @return {bool}
|
||||
*/
|
||||
public function newSession( $expire = null, $override = false, $remember = false, $userID = null ) {
|
||||
if ( ! isset( $expire ) ) {
|
||||
if ( empty( $expire ) ) {
|
||||
// default Session Expiration is 24 hours
|
||||
$expire = ( time() + ( 3600 * 24 ) );
|
||||
$expireLimit = Config::getValue( 'main/loginTimer' );
|
||||
$expire = ( time() + $expireLimit );
|
||||
Debug::log( 'Using default expiration time' );
|
||||
}
|
||||
$lastPage = App::getUrl();
|
||||
|
203
app/models/token.php
Normal file
203
app/models/token.php
Normal 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();
|
||||
}
|
||||
}
|
@ -43,7 +43,6 @@ class User extends DatabaseModel {
|
||||
[ 'name', 'varchar', '20' ],
|
||||
[ 'confirmationCode', 'varchar', '80' ],
|
||||
[ 'prefs', 'text', '' ],
|
||||
[ 'auth_token', 'text', '' ],
|
||||
];
|
||||
public $permissionMatrix = [
|
||||
'uploadImages' => [
|
||||
@ -122,6 +121,11 @@ class User extends DatabaseModel {
|
||||
'50',
|
||||
],
|
||||
],
|
||||
'darkMode' => [
|
||||
'pretty' => 'Enable Dark-Mode viewing',
|
||||
'type' => 'checkbox',
|
||||
'default' => 'false',
|
||||
],
|
||||
];
|
||||
protected static $avatars;
|
||||
protected static $preferences;
|
||||
@ -447,7 +451,7 @@ class User extends DatabaseModel {
|
||||
*/
|
||||
public function recent( $limit = null ) {
|
||||
if ( empty( $limit ) ) {
|
||||
$data = self::$db->getpaginated( $this->tableName, '*' );
|
||||
$data = self::$db->get( $this->tableName, '*' );
|
||||
} else {
|
||||
$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." );
|
||||
return false;
|
||||
}
|
||||
if ( $id === App::$activeUser->ID ) {
|
||||
$userData = $this->get( $id );
|
||||
App::$activeUser = $userData;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -694,33 +702,45 @@ class User extends DatabaseModel {
|
||||
return $this->data;
|
||||
}
|
||||
|
||||
public function findByToken( $token ) {
|
||||
$data = self::$db->get( $this->tableName, [ 'auth_token', '=', $token ] );
|
||||
if ( ! $data->count() ) {
|
||||
public function authorize( $username, $password ) {
|
||||
if ( !isset( self::$log ) ) {
|
||||
self::$log = new Log;
|
||||
}
|
||||
if ( !$this->get( $username ) ) {
|
||||
self::$log->login( 0, "API: User not found: $username" );
|
||||
return false;
|
||||
}
|
||||
return $data->first();
|
||||
}
|
||||
|
||||
public function addAccessToken( $id, $length = 64 ) {
|
||||
if ( ! Check::id( $id ) ) {
|
||||
// login attempts protection.
|
||||
$timeLimit = ( time() - 3600 );
|
||||
$limit = Config::getValue( 'main/loginLimit' );
|
||||
$user = $this->data();
|
||||
if ( $limit > 0 ) {
|
||||
$limitCheck = self::$db->get(
|
||||
'logs',
|
||||
[
|
||||
'source', '=', 'login',
|
||||
'AND',
|
||||
'userID', '=', $user->ID,
|
||||
'AND',
|
||||
'time', '>=', $timeLimit,
|
||||
'AND',
|
||||
'action', '!=', 'pass',
|
||||
]
|
||||
);
|
||||
if ( $limitCheck->count() >= $limit ) {
|
||||
self::$log->login( $user->ID, 'API: Too many failed attempts.' );
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if ( !Check::password( $password ) ) {
|
||||
self::$log->login( $user->ID, 'API: Invalid Password.' );
|
||||
return false;
|
||||
}
|
||||
$fields = [ 'auth_token' => $this->generateRandomString( $length ) ];
|
||||
if ( !self::$db->update( $this->tableName, $id, $fields ) ) {
|
||||
Debug::error( "User: $id not updated." );
|
||||
if ( !Hash::check( $password, $user->password ) ) {
|
||||
self::$log->login( $user->ID, 'API: Wrong Password.' );
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
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;
|
||||
self::$log->login( $this->data()->ID, 'API: pass' );
|
||||
return $user;
|
||||
}
|
||||
}
|
||||
|
@ -20,15 +20,14 @@ use TheTempusProject\Houdini\Classes\Navigation;
|
||||
use TheTempusProject\Houdini\Classes\Components;
|
||||
use TheTempusProject\Classes\AdminController;
|
||||
use TheTempusProject\Classes\Forms;
|
||||
use TheTempusProject\Plugins\Blog as BlogPlugin;
|
||||
use TheTempusProject\Models\Posts;
|
||||
|
||||
class Blog extends AdminController {
|
||||
public static $posts;
|
||||
|
||||
public function __construct() {
|
||||
parent::__construct();
|
||||
$blog = new BlogPlugin;
|
||||
self::$posts = $blog->posts;
|
||||
self::$posts = new Posts;
|
||||
self::$title = 'Admin - Blog';
|
||||
$view = Navigation::activePageSelect( 'nav.admin', '/admin/blog' );
|
||||
Components::set( 'ADMINNAV', $view );
|
||||
@ -47,7 +46,7 @@ class Blog extends AdminController {
|
||||
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 ) {
|
||||
Issues::add( 'success', 'Your post has been created.' );
|
||||
return $this->index();
|
||||
@ -68,7 +67,7 @@ class Blog extends AdminController {
|
||||
Issues::add( 'error', [ 'There was an error with your form.' => Check::userErrors() ] );
|
||||
return $this->index();
|
||||
}
|
||||
if ( self::$posts->updatePost( $data, Input::post( 'title' ), Input::post( 'blogPost' ), Input::post( '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.' );
|
||||
return $this->index();
|
||||
}
|
||||
|
@ -27,17 +27,16 @@ use TheTempusProject\Plugins\Blog as BlogPlugin;
|
||||
use TheTempusProject\TheTempusProject as App;
|
||||
use TheTempusProject\Plugins\Comments;
|
||||
use TheTempusProject\Models\Comments as CommentsModel;
|
||||
use TheTempusProject\Models\Posts as PostsModel;
|
||||
|
||||
class Blog extends Controller {
|
||||
protected static $blog;
|
||||
protected static $comments;
|
||||
protected static $posts;
|
||||
|
||||
public function __construct() {
|
||||
parent::__construct();
|
||||
Template::setTemplate( 'blog' );
|
||||
$blog = new BlogPlugin;
|
||||
self::$posts = $blog->posts;
|
||||
self::$posts = new PostsModel;
|
||||
}
|
||||
|
||||
public function index() {
|
||||
@ -57,39 +56,46 @@ class Blog extends Controller {
|
||||
|
||||
public function comments( $sub = null, $data = null ) {
|
||||
Debug::log( 'Controller initiated: ' . __METHOD__ . '.' );
|
||||
if ( empty( self::$comments ) ) {
|
||||
self::$comments = new CommentsModel;
|
||||
}
|
||||
$plugin = new Comments;
|
||||
|
||||
if ( empty( $sub ) || empty( $data ) ) {
|
||||
Session::flash( 'error', 'Whoops, try again.' );
|
||||
Redirect::to( 'blog' );
|
||||
Issues::add( 'error', 'There was an issue with your request. Please check the url and try again.' );
|
||||
return $this->index();
|
||||
}
|
||||
|
||||
if ( class_exists( 'TheTempusProject\Plugins\Comments' ) ) {
|
||||
$plugin = new Comments;
|
||||
if ( ! $plugin->checkEnabled() ) {
|
||||
Issues::add( 'error', 'Comments are disabled.' );
|
||||
return $this->index();
|
||||
}
|
||||
$comments = new CommentsModel;
|
||||
} else {
|
||||
Debug::info( 'error', 'Comments plugin missing.' );
|
||||
return $this->index();
|
||||
}
|
||||
|
||||
switch ( $sub ) {
|
||||
case 'post':
|
||||
$content = self::$posts->findById( (int) $data );
|
||||
if ( empty( $content ) ) {
|
||||
Session::flash( 'error', 'Unknown Post.' );
|
||||
Redirect::to( 'blog' );
|
||||
Issues::add( 'error', 'Unknown Content.' );
|
||||
return $this->index();
|
||||
}
|
||||
return $plugin->formPost( self::$posts->tableName, $content, 'blog/post/' );
|
||||
return self::$comments->formPost( 'blog', $content, 'blog/post/' );
|
||||
case 'edit':
|
||||
$content = self::$comments->findById( $data );
|
||||
$content = $comments->findById( $data );
|
||||
if ( empty( $content ) ) {
|
||||
Session::flash( 'error', 'Unknown Comment.' );
|
||||
Redirect::to( 'blog' );
|
||||
Issues::add( 'error', 'Unknown Comment.' );
|
||||
return $this->index();
|
||||
}
|
||||
return $plugin->formEdit( self::$posts->tableName, $content, 'blog/post/' );
|
||||
return self::$comments->formEdit( 'blog', $content, 'blog/post/' );
|
||||
case 'delete':
|
||||
$content = self::$comments->findById( $data );
|
||||
$content = $comments->findById( $data );
|
||||
if ( empty( $content ) ) {
|
||||
Session::flash( 'error', 'Unknown Comment.' );
|
||||
Redirect::to( 'blog' );
|
||||
Issues::add( 'error', 'Unknown Comment.' );
|
||||
return $this->index();
|
||||
}
|
||||
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 );
|
||||
if ( empty( $post ) ) {
|
||||
return $this->index();
|
||||
}
|
||||
if ( empty( self::$comments ) ) {
|
||||
self::$comments = new CommentsModel;
|
||||
$post = self::$posts->findBySlug( $id );
|
||||
if ( empty( $post ) ) {
|
||||
return $this->index();
|
||||
}
|
||||
}
|
||||
Debug::log( 'Controller initiated: ' . __METHOD__ . '.' );
|
||||
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' ) ) {
|
||||
$this->comments( 'post', Input::post( 'contentId' ) );
|
||||
}
|
||||
|
||||
Components::set( 'CONTENT_ID', $id );
|
||||
Components::set( 'COMMENT_TYPE', 'blog' );
|
||||
if ( App::$isLoggedIn ) {
|
||||
Components::set( 'NEWCOMMENT', Views::simpleView( 'comments.create' ) );
|
||||
} else {
|
||||
Components::set( 'NEWCOMMENT', '' );
|
||||
Components::set( 'COMMENT_TYPE', self::$posts->tableName );
|
||||
Components::set( 'NEWCOMMENT', '' );
|
||||
Components::set( 'count', '0' );
|
||||
Components::set( 'COMMENTS', '' );
|
||||
|
||||
if ( class_exists( 'TheTempusProject\Plugins\Comments' ) ) {
|
||||
$plugin = new Comments;
|
||||
if ( $plugin->checkEnabled() ) {
|
||||
$comments = new CommentsModel;
|
||||
if ( App::$isLoggedIn ) {
|
||||
Components::set( 'NEWCOMMENT', Views::simpleView( 'comments.create' ) );
|
||||
} else {
|
||||
Components::set( 'NEWCOMMENT', '' );
|
||||
}
|
||||
Components::set( 'count', $comments->count( self::$posts->tableName, $post->ID ) );
|
||||
Components::set( 'COMMENTS', Views::simpleView( 'comments.list', $comments->display( 10, self::$posts->tableName, $post->ID ) ) );
|
||||
}
|
||||
}
|
||||
$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::$pageDescription = strip_tags( $post->contentSummaryNoLink );
|
||||
Views::view( 'blog.post', $post );
|
||||
|
@ -32,6 +32,7 @@ class Posts extends DatabaseModel {
|
||||
[ 'edited', 'int', '10' ],
|
||||
[ 'draft', 'int', '1' ],
|
||||
[ 'title', 'varchar', '86' ],
|
||||
[ 'slug', 'varchar', '64' ],
|
||||
[ 'content', 'text', '' ],
|
||||
];
|
||||
|
||||
@ -45,7 +46,7 @@ class Posts extends DatabaseModel {
|
||||
}
|
||||
}
|
||||
|
||||
public function newPost( $title, $post, $draft ) {
|
||||
public function newPost( $title, $post, $slug, $draft ) {
|
||||
if ( !Check::dataTitle( $title ) ) {
|
||||
Debug::info( 'modelBlog: illegal title.' );
|
||||
|
||||
@ -59,6 +60,7 @@ class Posts extends DatabaseModel {
|
||||
$fields = [
|
||||
'author' => App::$activeUser->ID,
|
||||
'draft' => $draft,
|
||||
'slug' => $slug,
|
||||
'created' => time(),
|
||||
'edited' => time(),
|
||||
'content' => Sanitize::rich( $post ),
|
||||
@ -73,7 +75,7 @@ class Posts extends DatabaseModel {
|
||||
return true;
|
||||
}
|
||||
|
||||
public function updatePost( $id, $title, $content, $draft ) {
|
||||
public function updatePost( $id, $title, $content, $slug, $draft ) {
|
||||
if ( empty( self::$log ) ) {
|
||||
self::$log = new Log;
|
||||
}
|
||||
@ -94,6 +96,7 @@ class Posts extends DatabaseModel {
|
||||
}
|
||||
$fields = [
|
||||
'draft' => $draft,
|
||||
'slug' => $slug,
|
||||
'edited' => time(),
|
||||
'content' => Sanitize::rich( $content ),
|
||||
'title' => $title,
|
||||
@ -131,39 +134,47 @@ class Posts extends DatabaseModel {
|
||||
}
|
||||
$draft = '';
|
||||
$authorName = self::$user->getUsername( $instance->author );
|
||||
$cleanPost = Sanitize::contentShort( $instance->content );
|
||||
$postSpace = explode( ' ', $cleanPost );
|
||||
$postLine = explode( "\n", $cleanPost );
|
||||
// summary by words: 100
|
||||
$spaceSummary = implode( ' ', array_splice( $postSpace, 0, 100 ) );
|
||||
// summary by lines: 5
|
||||
$lineSummary = implode( "\n", array_splice( $postLine, 0, 5 ) );
|
||||
if ( strlen( $spaceSummary ) < strlen( $lineSummary ) ) {
|
||||
$contentSummary = $spaceSummary;
|
||||
if ( count( $postSpace, 1 ) <= 100 ) {
|
||||
$contentSummaryNoLink = $contentSummary;
|
||||
$contentSummary .= '... <a href="{ROOT_URL}blog/post/' . $instance->ID . '">Read More</a>';
|
||||
}
|
||||
|
||||
// Summarize
|
||||
if ( ! empty( $instance->slug ) ) {
|
||||
$identifier = $instance->slug;
|
||||
} else {
|
||||
$identifier = $instance->ID;
|
||||
}
|
||||
|
||||
$cleanPost = Sanitize::contentShort( $instance->content );
|
||||
// By Word
|
||||
$wordsArray = explode( ' ', $cleanPost );
|
||||
$wordSummary = implode( ' ', array_splice( $wordsArray, 0, 100 ) );
|
||||
// By Line
|
||||
$linesArray = explode( "\n", $cleanPost );
|
||||
$lineSummary = implode( "\n", array_splice( $linesArray, 0, 5 ) );
|
||||
|
||||
if ( strlen( $wordSummary ) < strlen( $lineSummary ) ) {
|
||||
$contentSummaryNoLink = $wordSummary;
|
||||
$contentSummary = $wordSummary . '... <a href="{ROOT_URL}blog/post/' . $identifier . '" class="text-decoration-none">Read More</a>';
|
||||
} else {
|
||||
// @todo: need to refine this after testing
|
||||
$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' ) {
|
||||
$draft = ' <b>Draft</b>';
|
||||
}
|
||||
$instance->isDraft = $draft;
|
||||
$instance->authorName = $authorName;
|
||||
$instance->contentSummaryNoLink = $contentSummaryNoLink;
|
||||
$instance->contentSummary = $contentSummary;
|
||||
if ( isset( $params['stripHtml'] ) && $params['stripHtml'] === true ) {
|
||||
$instance->contentSummary = strip_tags( $instance->content );
|
||||
}
|
||||
if ( self::$comments !== false ) {
|
||||
$instance->commentCount = self::$comments->count( 'blog', $instance->ID );
|
||||
}
|
||||
$instance->content = Filters::applyOne( 'mentions.0', $instance->content, true );
|
||||
$instance->content = Filters::applyOne( 'hashtags.0', $instance->content, true );
|
||||
|
||||
$out[] = $instance;
|
||||
if ( !empty( $end ) ) {
|
||||
$out = $out[0];
|
||||
@ -221,9 +232,9 @@ class Posts extends DatabaseModel {
|
||||
$whereClause = '*';
|
||||
}
|
||||
if ( empty( $limit ) ) {
|
||||
$postData = self::$db->getPaginated( $this->tableName, $whereClause );
|
||||
$postData = self::$db->get( $this->tableName, $whereClause );
|
||||
} 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() ) {
|
||||
Debug::info( 'No Blog posts found.' );
|
||||
@ -239,7 +250,7 @@ class Posts extends DatabaseModel {
|
||||
} else {
|
||||
$whereClause = ['draft', '=', '0'];
|
||||
}
|
||||
$postData = self::$db->getPaginated( $this->tableName, $whereClause );
|
||||
$postData = self::$db->get( $this->tableName, $whereClause );
|
||||
if ( !$postData->count() ) {
|
||||
Debug::info( 'No Blog posts found.' );
|
||||
|
||||
@ -263,7 +274,7 @@ class Posts extends DatabaseModel {
|
||||
$firstDayUnix = date( 'U', strtotime( "first day of $year" ) );
|
||||
$lastDayUnix = date( 'U', strtotime( "last day of $year" ) );
|
||||
$whereClause = array_merge( $whereClause, ['created', '<=', $lastDayUnix, 'AND', 'created', '>=', $firstDayUnix] );
|
||||
$postData = self::$db->getPaginated( $this->tableName, $whereClause );
|
||||
$postData = self::$db->get( $this->tableName, $whereClause );
|
||||
if ( !$postData->count() ) {
|
||||
Debug::info( 'No Blog posts found.' );
|
||||
|
||||
@ -282,7 +293,7 @@ class Posts extends DatabaseModel {
|
||||
$whereClause = ['draft', '=', '0', 'AND'];
|
||||
}
|
||||
$whereClause = array_merge( $whereClause, ['author' => $ID] );
|
||||
$postData = self::$db->getPaginated( $this->tableName, $whereClause );
|
||||
$postData = self::$db->get( $this->tableName, $whereClause );
|
||||
if ( !$postData->count() ) {
|
||||
Debug::info( 'No Blog posts found.' );
|
||||
|
||||
@ -291,6 +302,22 @@ class Posts extends DatabaseModel {
|
||||
return $this->filter( $postData->results() );
|
||||
}
|
||||
|
||||
public function findBySlug( $slug, $includeDraft = false ) {
|
||||
$whereClause = [];
|
||||
if ( $includeDraft !== true ) {
|
||||
$whereClause = ['draft', '=', '0', 'AND'];
|
||||
}
|
||||
|
||||
$whereClause = array_merge( $whereClause, ['slug', '=', $slug] );
|
||||
|
||||
$postData = self::$db->get( $this->tableName, $whereClause );
|
||||
if ( !$postData->count() ) {
|
||||
Debug::info( 'No Blog posts found.' );
|
||||
return false;
|
||||
}
|
||||
return $this->filter( $postData->first() );
|
||||
}
|
||||
|
||||
public function byMonth( $month, $year = 0, $includeDraft = false ) {
|
||||
if ( 0 === $year ) {
|
||||
$year = date( 'Y' );
|
||||
@ -311,7 +338,7 @@ class Posts extends DatabaseModel {
|
||||
$month = date( 'F', $firstDayUnix );
|
||||
$lastDayUnix = date( 'U', strtotime( "last day of $month $year" ) );
|
||||
$whereClause = array_merge( $whereClause, ['created', '<=', $lastDayUnix, 'AND', 'created', '>=', $firstDayUnix] );
|
||||
$postData = self::$db->getPaginated( $this->tableName, $whereClause );
|
||||
$postData = self::$db->get( $this->tableName, $whereClause );
|
||||
if ( !$postData->count() ) {
|
||||
Debug::info( 'No Blog posts found.' );
|
||||
|
||||
|
@ -12,12 +12,7 @@
|
||||
*/
|
||||
namespace TheTempusProject\Plugins;
|
||||
|
||||
use ReflectionClass;
|
||||
use TheTempusProject\Classes\Installer;
|
||||
use TheTempusProject\Houdini\Classes\Navigation;
|
||||
use TheTempusProject\Classes\Plugin;
|
||||
use TheTempusProject\Models\Posts;
|
||||
use TheTempusProject\TheTempusProject as App;
|
||||
|
||||
class Blog extends Plugin {
|
||||
public $pluginName = 'TP Blog';
|
||||
@ -28,11 +23,11 @@ class Blog extends Plugin {
|
||||
public $pluginDescription = 'A simple plugin to add a blog to your installation.';
|
||||
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',
|
||||
],
|
||||
];
|
||||
public $footer_links = [
|
||||
public $info_footer_links = [
|
||||
[
|
||||
'text' => 'Blog',
|
||||
'url' => '{ROOT_URL}blog/index',
|
||||
@ -50,10 +45,4 @@ class Blog extends Plugin {
|
||||
],
|
||||
],
|
||||
];
|
||||
public $posts;
|
||||
|
||||
public function __construct( $load = false ) {
|
||||
$this->posts = new Posts;
|
||||
parent::__construct( $load );
|
||||
}
|
||||
}
|
||||
|
@ -12,7 +12,7 @@
|
||||
*/
|
||||
namespace TheTempusProject\Templates;
|
||||
|
||||
use TheTempusProject\Plugins\Blog;
|
||||
use TheTempusProject\Models\Posts;
|
||||
use TheTempusProject\Houdini\Classes\Components;
|
||||
use TheTempusProject\Houdini\Classes\Navigation;
|
||||
use TheTempusProject\Houdini\Classes\Views;
|
||||
@ -25,10 +25,11 @@ class BlogLoader extends DefaultLoader {
|
||||
* needed by this template.
|
||||
*/
|
||||
public function __construct() {
|
||||
$blog = new Blog;
|
||||
$posts = $blog->posts;
|
||||
$posts = new Posts;
|
||||
Components::set('SIDEBAR', Views::simpleView('blog.sidebar', $posts->recent(5)));
|
||||
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' ) );
|
||||
Components::set( 'BLOG_TEMPLATE_URL', Template::parse( '{ROOT_URL}app/plugins/comments/' ) );
|
||||
$this->addCss( '<link rel="stylesheet" href="{BLOG_TEMPLATE_URL}css/comments.css">' );
|
||||
|
@ -10,10 +10,10 @@
|
||||
* @license https://opensource.org/licenses/MIT [MIT LICENSE]
|
||||
-->
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta charset="UTF-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta property="og:url" content="{CURRENT_URL}">
|
||||
<meta name='twitter:card' content='summary' />
|
||||
<meta name='twitter:card' content='summary_large_image'>
|
||||
<title>{TITLE}</title>
|
||||
<meta itemprop="name" content="{TITLE}">
|
||||
<meta name="twitter:title" content="{TITLE}">
|
||||
@ -28,84 +28,89 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta name="author" content="The Tempus Project">
|
||||
{ROBOT}
|
||||
<link rel="alternate" hreflang="en-us" href="alternateURL">
|
||||
<link rel="icon" href="{ROOT_URL}images/favicon.ico">
|
||||
<!-- Required CSS -->
|
||||
<link rel="stylesheet" href="{FONT_AWESOME_URL}font-awesome.min.css" crossorigin="anonymous">
|
||||
<link rel="stylesheet" href="{BOOTSTRAP_CDN}css/bootstrap-theme.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.min.css" crossorigin="anonymous">
|
||||
<!-- 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 -->
|
||||
{TEMPLATE_CSS_INCLUDES}
|
||||
</head>
|
||||
<body>
|
||||
<nav class="navbar navbar-inverse navbar-fixed-top" role="navigation">
|
||||
<!--Brand and toggle should get grouped for better mobile display -->
|
||||
<div class="navbar-header">
|
||||
<a href="{ROOT_URL}" class="navbar-brand">{SITENAME}</a>
|
||||
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-ex1-collapse" style="">
|
||||
<span class="sr-only">Toggle navigation</span>
|
||||
<span class="icon-bar"></span>
|
||||
<span class="icon-bar"></span>
|
||||
<span class="icon-bar"></span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="container-fluid">
|
||||
<div class="collapse navbar-collapse navbar-ex1-collapse">
|
||||
<!-- Navigation -->
|
||||
<header class="p-3 text-bg-dark">
|
||||
<div class="container">
|
||||
<div class="d-flex flex-wrap align-items-center justify-content-center justify-content-lg-start">
|
||||
<img src="{ROOT_URL}{LOGO}" class="bi me-2" width="40" height="32" role="img" aria-label="{SITENAME} Logo">
|
||||
<a href="/" class="d-flex align-items-center mb-2 mb-lg-0 text-white text-decoration-none">
|
||||
{SITENAME}
|
||||
</a>
|
||||
{topNavLeft}
|
||||
<div class="navbar-right">
|
||||
<ul class="nav navbar-nav">
|
||||
{topNavRight}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
<div class="container-fluid">
|
||||
<div class="foot-pad">
|
||||
<div class="text-end d-flex align-items-center">
|
||||
{topNavRight}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<div class="d-flex flex-column min-vh-100">
|
||||
<div class="flex-container flex-grow-1">
|
||||
{ISSUES}
|
||||
<div class="row">
|
||||
<div class="container">
|
||||
<div class="container pt-4">
|
||||
<div class="row">
|
||||
{ERROR}
|
||||
{NOTICE}
|
||||
{SUCCESS}
|
||||
{INFO}
|
||||
</div>
|
||||
</div>
|
||||
{/ISSUES}
|
||||
<div class="row">
|
||||
|
||||
<!-- Leading Content -->
|
||||
<div class="container">
|
||||
{BLOGFEATURES}
|
||||
</div>
|
||||
|
||||
<div class="pt-4">
|
||||
<div class="container">
|
||||
<div class="page-header">
|
||||
<h1 class="blog-title">{SITENAME} Blog</h1>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-8 blog-main">
|
||||
{BLOG_BREADCRUMBS}
|
||||
<h3 class="pb-4 mb-4 fst-italic border-bottom">
|
||||
{SITENAME} Blog
|
||||
</h3>
|
||||
<div class="row g-5">
|
||||
<!-- Main Content -->
|
||||
<div class="col-md-8">
|
||||
{CONTENT}
|
||||
</div>
|
||||
<!-- /.blog-main -->
|
||||
<div class="col-sm-3 col-sm-offset-1 blog-sidebar">
|
||||
<div class="sidebar-module">
|
||||
{SIDEBAR}
|
||||
</div>
|
||||
<div class="sidebar-module">
|
||||
{SIDEBAR2}
|
||||
<!-- Sidebar Content -->
|
||||
<div class="col-md-4">
|
||||
<div class="position-sticky" style="top: 2rem;">
|
||||
<div class="p-4">
|
||||
{SIDEBARABOUT}
|
||||
</div>
|
||||
<div class="p-4">
|
||||
{SIDEBAR}
|
||||
</div>
|
||||
<div class="p-4">
|
||||
{SIDEBAR2}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- /.blog-sidebar -->
|
||||
</div>
|
||||
<!-- /.row -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<footer>
|
||||
{FOOT}
|
||||
{COPY}
|
||||
</footer>
|
||||
</div>
|
||||
<!-- Bootstrap core JavaScript and jquery -->
|
||||
<script src="{JQUERY_CDN}jquery.min.js" crossorigin="anonymous"></script>
|
||||
<script src="{BOOTSTRAP_CDN}js/bootstrap.min.js" crossorigin="anonymous"></script>
|
||||
<script language="JavaScript" crossorigin="anonymous" type="text/javascript" src="{JQUERY_CDN}jquery.min.js"></script>
|
||||
<script language="JavaScript" crossorigin="anonymous" type="text/javascript" src="https://cdn.jsdelivr.net/npm/@popperjs/core@2.11.6/dist/umd/popper.min.js"></script>
|
||||
<script language="JavaScript" crossorigin="anonymous" type="text/javascript" src="{BOOTSTRAP_CDN}js/bootstrap.min.js"></script>
|
||||
<!-- Custom javascript for this template -->
|
||||
{TEMPLATE_JS_INCLUDES}
|
||||
</body>
|
||||
|
6
app/plugins/blog/views/about.html
Normal file
6
app/plugins/blog/views/about.html
Normal 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>
|
@ -1,31 +1,55 @@
|
||||
<form action="" method="post" class="form-horizontal" enctype="multipart/form-data">
|
||||
<legend>New Blog Post</legend>
|
||||
<div class="form-group">
|
||||
<label for="title" class="col-lg-3 control-label">Title</label>
|
||||
<div class="col-lg-3">
|
||||
<input type="text" class="form-check-input" name="title" id="title">
|
||||
<div class="context-main-bg context-main p-3">
|
||||
<legend class="text-center">Add Blog Post</legend>
|
||||
<hr>
|
||||
{ADMIN_BREADCRUMBS}
|
||||
<form action="" method="post">
|
||||
<fieldset>
|
||||
<!-- Title -->
|
||||
<div class="mb-3 row">
|
||||
<label for="title" class="col-lg-3 col-form-label text-end">Title:</label>
|
||||
<div class="col-lg-6">
|
||||
<input type="text" class="form-control" name="title" id="title" required>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Slug -->
|
||||
<div class="mb-3 row">
|
||||
<label for="slug" class="col-lg-3 col-form-label text-end">URL Slug (for pretty linking):</label>
|
||||
<div class="col-lg-6">
|
||||
<input type="text" class="form-control" name="slug" id="slug" required>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- form buttons -->
|
||||
<div class="mb-3 row">
|
||||
<div class="offset-3 col-lg-6">
|
||||
<div class="btn-group w-100">
|
||||
<button type="button" class="btn btn-sm btn-outline-primary" onclick="insertTag ('blogPost', 'c');">✔</button>
|
||||
<button type="button" class="btn btn-sm btn-outline-primary" onclick="insertTag ('blogPost', 'x');">✖</button>
|
||||
<button type="button" class="btn btn-sm btn-outline-primary" onclick="insertTag ('blogPost', '!');">❕</button>
|
||||
<button type="button" class="btn btn-sm btn-outline-primary" onclick="insertTag ('blogPost', '?');">❔</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 class="form-group">
|
||||
<div class="col-lg-6 col-lg-offset-3 btn-group">
|
||||
<button type="button" class="btn btn-sm btn-primary" onclick="insertTag ('blogPost', 'c');">✔</button>
|
||||
<button type="button" class="btn btn-sm btn-primary" onclick="insertTag ('blogPost', 'x');">✖</button>
|
||||
<button type="button" class="btn btn-sm btn-primary" onclick="insertTag ('blogPost', '!');">❕</button>
|
||||
<button type="button" class="btn btn-sm btn-primary" onclick="insertTag ('blogPost', '?');">❔</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>
|
||||
</form>
|
||||
</div>
|
@ -1,5 +1,5 @@
|
||||
<legend>New Posts</legend>
|
||||
<table class="table table-striped">
|
||||
<table class="table context-main">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width: 20%"></th>
|
||||
@ -14,9 +14,9 @@
|
||||
<tr>
|
||||
<td>{title}</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/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/blog/delete/{ID}" class="btn btn-sm btn-danger" role="button"><i class="glyphicon glyphicon-trash"></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"><i class="fa fa-fw fa-pencil"></i></a></td>
|
||||
<td width="30px"><a href="{ROOT_URL}admin/blog/delete/{ID}" class="btn btn-sm btn-danger"><i class="fa fa-fw fa-trash"></i></a></td>
|
||||
</tr>
|
||||
{/LOOP}
|
||||
{ALT}
|
||||
|
@ -1,31 +1,55 @@
|
||||
<form action="" method="post" class="form-horizontal" enctype="multipart/form-data">
|
||||
<legend>Edit Blog Post</legend>
|
||||
<div class="form-group">
|
||||
<label for="title" class="col-lg-3 control-label">Title</label>
|
||||
<div class="col-lg-3">
|
||||
<input type="text" class="form-check-input" name="title" id="title" value="{title}">
|
||||
<div class="context-main-bg context-main p-3">
|
||||
<legend class="text-center">Edit Blog Post</legend>
|
||||
<hr>
|
||||
{ADMIN_BREADCRUMBS}
|
||||
<form action="" method="post">
|
||||
<fieldset>
|
||||
<!-- Title -->
|
||||
<div class="mb-3 row">
|
||||
<label for="title" class="col-lg-3 col-form-label text-end">Title:</label>
|
||||
<div class="col-lg-6">
|
||||
<input type="text" class="form-control" name="title" id="title" value="{title}" required>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Slug -->
|
||||
<div class="mb-3 row">
|
||||
<label for="slug" class="col-lg-3 col-form-label text-end">URL Slug (for pretty linking):</label>
|
||||
<div class="col-lg-6">
|
||||
<input type="text" class="form-control" name="slug" id="slug" value="{slug}" required>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- form buttons -->
|
||||
<div class="mb-3 row">
|
||||
<div class="offset-3 col-lg-6">
|
||||
<div class="btn-group w-100">
|
||||
<button type="button" class="btn btn-sm btn-outline-primary" onclick="insertTag ('blogPost', 'c');">✔</button>
|
||||
<button type="button" class="btn btn-sm btn-outline-primary" onclick="insertTag ('blogPost', 'x');">✖</button>
|
||||
<button type="button" class="btn btn-sm btn-outline-primary" onclick="insertTag ('blogPost', '!');">❕</button>
|
||||
<button type="button" class="btn btn-sm btn-outline-primary" onclick="insertTag ('blogPost', '?');">❔</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 class="form-group">
|
||||
<div class="col-lg-6 col-lg-offset-3 btn-group">
|
||||
<button type="button" class="btn btn-sm btn-primary" onclick="insertTag ('blogPost', 'c');">✔</button>
|
||||
<button type="button" class="btn btn-sm btn-primary" onclick="insertTag ('blogPost', 'x');">✖</button>
|
||||
<button type="button" class="btn btn-sm btn-primary" onclick="insertTag ('blogPost', '!');">❕</button>
|
||||
<button type="button" class="btn btn-sm btn-primary" onclick="insertTag ('blogPost', '?');">❔</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>
|
||||
</form>
|
||||
</div>
|
@ -1,45 +1,48 @@
|
||||
<legend>Blog Posts</legend>
|
||||
{PAGINATION}
|
||||
<form action="{ROOT_URL}admin/blog/delete" method="post">
|
||||
<table class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width: 30%">Title</th>
|
||||
<th style="width: 20%">Author</th>
|
||||
<th style="width: 10%">comments</th>
|
||||
<th style="width: 10%">Created</th>
|
||||
<th style="width: 10%">Updated</th>
|
||||
<th style="width: 5%"></th>
|
||||
<th style="width: 5%"></th>
|
||||
<th style="width: 10%">
|
||||
<INPUT type="checkbox" onchange="checkAll(this)" name="check.b" value="B_[]"/>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{LOOP}
|
||||
<tr>
|
||||
<td><a href="{ROOT_URL}admin/blog/view/{ID}">{title}</a>{isDraft}</td>
|
||||
<td>{authorName}</td>
|
||||
<td>{commentCount}</td>
|
||||
<td>{DTC}{created}{/DTC}</td>
|
||||
<td>{DTC}{edited}{/DTC}</td>
|
||||
<td><a href="{ROOT_URL}admin/blog/edit/{ID}" class="btn btn-sm btn-warning" role="button"><i class="glyphicon glyphicon-edit"></i></a></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>
|
||||
<input type="checkbox" value="{ID}" name="B_[]">
|
||||
</td>
|
||||
</tr>
|
||||
{/LOOP}
|
||||
{ALT}
|
||||
<tr>
|
||||
<td colspan="7">
|
||||
No results to show.
|
||||
</td>
|
||||
</tr>
|
||||
{/ALT}
|
||||
</tbody>
|
||||
</table>
|
||||
<a href="{ROOT_URL}admin/blog/create" class="btn btn-sm btn-primary" role="button">Create</a>
|
||||
<button name="submit" value="submit" type="submit" class="btn btn-sm btn-danger">Delete</button>
|
||||
</form>
|
||||
<div class="context-main-bg context-main p-3">
|
||||
<legend class="text-center">Blog Posts</legend>
|
||||
<hr>
|
||||
{ADMIN_BREADCRUMBS}
|
||||
<form action="{ROOT_URL}admin/blog/delete" method="post">
|
||||
<table class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width: 30%">Title</th>
|
||||
<th style="width: 20%">Author</th>
|
||||
<th style="width: 10%">comments</th>
|
||||
<th style="width: 10%">Created</th>
|
||||
<th style="width: 10%">Updated</th>
|
||||
<th style="width: 5%"></th>
|
||||
<th style="width: 5%"></th>
|
||||
<th style="width: 10%">
|
||||
<input type="checkbox" onchange="checkAll(this)" name="check.b" value="B_[]">
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{LOOP}
|
||||
<tr>
|
||||
<td><a href="{ROOT_URL}admin/blog/view/{ID}">{title}</a>{isDraft}</td>
|
||||
<td>{authorName}</td>
|
||||
<td>{commentCount}</td>
|
||||
<td>{DTC}{created}{/DTC}</td>
|
||||
<td>{DTC}{edited}{/DTC}</td>
|
||||
<td><a href="{ROOT_URL}admin/blog/edit/{ID}" class="btn btn-sm btn-warning"><i class="fa fa-fw fa-pencil"></i></a></td>
|
||||
<td><a href="{ROOT_URL}admin/blog/delete/{ID}" class="btn btn-sm btn-danger"><i class="fa fa-fw fa-trash"></i></a></td>
|
||||
<td>
|
||||
<input type="checkbox" value="{ID}" name="B_[]">
|
||||
</td>
|
||||
</tr>
|
||||
{/LOOP}
|
||||
{ALT}
|
||||
<tr>
|
||||
<td colspan="7">
|
||||
No results to show.
|
||||
</td>
|
||||
</tr>
|
||||
{/ALT}
|
||||
</tbody>
|
||||
</table>
|
||||
<a href="{ROOT_URL}admin/blog/create" class="btn btn-sm btn-primary">Create</a>
|
||||
<button name="submit" value="submit" type="submit" class="btn btn-sm btn-danger"><i class="fa fa-fw fa-trash"></i></button>
|
||||
</form>
|
||||
</div>
|
@ -1,11 +1,14 @@
|
||||
<div class="row">
|
||||
<div class="col-sm-8 blog-main">
|
||||
<div class="page-header">
|
||||
<h1>{title} <small>{DTC}{created}{/DTC} by <a href="{ROOT_URL}admin/users/view/{author}">{authorName}</a></small></h1>
|
||||
</div>
|
||||
<div class="well">{content}</div>
|
||||
<a href="{ROOT_URL}admin/blog/delete/{ID}" class="btn btn-md btn-danger" role="button">Delete</a>
|
||||
<a href="{ROOT_URL}admin/blog/edit/{ID}" class="btn btn-md btn-warning" role="button">Edit</a>
|
||||
<a href="{ROOT_URL}admin/comments/blog/{ID}" class="btn btn-md btn-primary" role="button">View Comments</a>
|
||||
</div><!-- /.blog-main -->
|
||||
</div><!-- /.row -->
|
||||
<div class="context-main-bg context-main p-3">
|
||||
<legend class="text-center">Blog Post: {title}</legend>
|
||||
<hr>
|
||||
{ADMIN_BREADCRUMBS}
|
||||
<p class="blog-post-meta">{DTC date}{created}{/DTC} by <a href="{ROOT_URL}admin/users/view/{author}">{authorName}</a></p>
|
||||
<div class="well">{content}</div>
|
||||
{ADMIN}
|
||||
<hr>
|
||||
<a href="{ROOT_URL}admin/blog/delete/{ID}" class="btn btn-md btn-danger"><i class="fa fa-fw fa-trash"></i></a>
|
||||
<a href="{ROOT_URL}admin/blog/edit/{ID}" class="btn btn-md btn-warning"><i class="fa fa-fw fa-pencil"></i></a>
|
||||
<a href="{ROOT_URL}admin/comments/blog/{ID}" class="btn btn-md btn-primary">View Comments</a>
|
||||
<hr>
|
||||
{/ADMIN}
|
||||
</div>
|
7
app/plugins/blog/views/largeFeature.html
Normal file
7
app/plugins/blog/views/largeFeature.html
Normal 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 what’s most interesting in this post’s contents.</p>
|
||||
<p class="lead mb-0"><a href="#" class="text-white fw-bold">Continue reading...</a></p>
|
||||
</div>
|
||||
</div>
|
@ -1,18 +1,15 @@
|
||||
{PAGINATION}
|
||||
{LOOP}
|
||||
<div class="blog-post">
|
||||
<h2 class="blog-post-title"><a href="{ROOT_URL}blog/post/{ID}">{title}</a></h2>
|
||||
<hr>
|
||||
<article class="blog-post">
|
||||
<h2 class="blog-post-title mb-1">{title}</h2>
|
||||
<p class="blog-post-meta">{DTC date}{created}{/DTC} by <a href="{ROOT_URL}home/profile/{author}" class="text-decoration-none">{authorName}</a></p>
|
||||
<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}
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
<hr>
|
||||
{/LOOP}
|
||||
{ALT}
|
||||
<div class="blog-post">
|
||||
<article class="blog-post">
|
||||
<p class="blog-post-meta">No Posts Found.</p>
|
||||
</div>
|
||||
</article>
|
||||
{/ALT}
|
||||
|
@ -3,12 +3,12 @@
|
||||
<div class="blog-post">
|
||||
<h2 class="blog-post-title">{title}</h2>
|
||||
<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/{author}" class="text-decoration-none">{authorName}</a></p>
|
||||
{content}
|
||||
{ADMIN}
|
||||
<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/edit/{ID}" class="btn btn-md btn-warning" role="button">Edit</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"><i class="fa fa-fw fa-pencil"></i></a>
|
||||
<hr>
|
||||
{/ADMIN}
|
||||
</div><!-- /.blog-post -->
|
||||
|
@ -1,6 +1,6 @@
|
||||
<div class="panel panel-info">
|
||||
<div class="panel-heading">
|
||||
<h3 class="panel-title">Recent Posts</h3>
|
||||
<div class="card">
|
||||
<div class="card-header bg-info">
|
||||
<h3 class="card-title">Recent Posts</h3>
|
||||
</div>
|
||||
<ul class="list-group">
|
||||
{LOOP}
|
||||
|
@ -1,18 +1,18 @@
|
||||
<div class="panel panel-info">
|
||||
<div class="panel-heading">
|
||||
<h3 class="panel-title">Recent Posts</h3>
|
||||
<div class="card context-main-bg">
|
||||
<div class="card-header">
|
||||
<h3 class="card-title">Recent Posts</h3>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<div class="card-body">
|
||||
<ol class="list-unstyled">
|
||||
{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}
|
||||
{ALT}
|
||||
<li>No Posts to show</li>
|
||||
{/ALT}
|
||||
</ol>
|
||||
</div>
|
||||
<div class="panel-footer">
|
||||
<a href="{ROOT_URL}blog">View All</a>
|
||||
<div class="card-footer">
|
||||
<a href="{ROOT_URL}blog" class="text-decoration-none">View All</a>
|
||||
</div>
|
||||
</div>
|
@ -1,14 +1,11 @@
|
||||
<div class="panel panel-info">
|
||||
<div class="panel-heading">
|
||||
<h3 class="panel-title">Archives</h3>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<ol class="list-unstyled">
|
||||
{LOOP}
|
||||
<li>({count}) <a href="{ROOT_URL}blog/month/{month}/{year}">{monthText} {year}</a></li>
|
||||
{/LOOP}
|
||||
{ALT}
|
||||
{/ALT}
|
||||
</ol>
|
||||
</div>
|
||||
<div class="p-4">
|
||||
<h4 class="fst-italic">Archives</h4>
|
||||
<ul class="list-unstyled mb-0">
|
||||
{LOOP}
|
||||
<li>({count}) <a href="{ROOT_URL}blog/month/{month}/{year}" class="text-decoration-none">{monthText} {year}</a></li>
|
||||
{/LOOP}
|
||||
{ALT}
|
||||
<li>None To Show</li>
|
||||
{/ALT}
|
||||
</ul>
|
||||
</div>
|
31
app/plugins/blog/views/smallFeature.html
Normal file
31
app/plugins/blog/views/smallFeature.html
Normal 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>
|
@ -41,7 +41,7 @@ class Bugreport extends Controller {
|
||||
return Views::view( 'bugreport.create' );
|
||||
}
|
||||
$result = self::$bugreport->create( App::$activeUser->ID, Input::post( 'url' ), Input::post( 'ourl' ), Input::post( 'repeat' ), Input::post( 'entry' ) );
|
||||
if ( 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.' );
|
||||
Redirect::to( 'home/index' );
|
||||
} else {
|
||||
|
@ -13,7 +13,6 @@
|
||||
namespace TheTempusProject\Models;
|
||||
|
||||
use TheTempusProject\Bedrock\Functions\Check;
|
||||
use TheTempusProject\Bedrock\Classes\Config;
|
||||
use TheTempusProject\Canary\Bin\Canary as Debug;
|
||||
use TheTempusProject\Canary\Classes\CustomException;
|
||||
use TheTempusProject\Classes\DatabaseModel;
|
||||
|
@ -49,10 +49,11 @@ class Bugreport extends Plugin {
|
||||
'default' => false,
|
||||
],
|
||||
];
|
||||
public $footer_links = [
|
||||
public $contact_footer_links = [
|
||||
[
|
||||
'text' => 'Bug Report',
|
||||
'url' => '{ROOT_URL}bugreport',
|
||||
'filter' => 'loggedin',
|
||||
],
|
||||
];
|
||||
public $admin_links = [
|
||||
|
@ -1,42 +1,45 @@
|
||||
<legend>Bug Reports</legend>
|
||||
{PAGINATION}
|
||||
<form action="{ROOT_URL}admin/bugreport/delete" method="post">
|
||||
<table class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width: 5%">ID</th>
|
||||
<th style="width: 20%">Time</th>
|
||||
<th style="width: 60%">Description</th>
|
||||
<th style="width: 5%"></th>
|
||||
<th style="width: 5%"></th>
|
||||
<th style="width: 5%">
|
||||
<INPUT type="checkbox" onchange="checkAll(this)" name="check.br" value="BR_[]"/>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{LOOP}
|
||||
<tr>
|
||||
<td align="center">{ID}</td>
|
||||
<td align="center">{DTC}{time}{/DTC}</td>
|
||||
<td>{description}</td>
|
||||
<td><a href="{ROOT_URL}admin/bugreport/view/{ID}" class="btn btn-sm btn-primary" role="button"><i class="glyphicon glyphicon-open"></i></a></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>
|
||||
<input type="checkbox" value="{ID}" name="BR_[]">
|
||||
</td>
|
||||
</tr>
|
||||
{/LOOP}
|
||||
{ALT}
|
||||
<tr>
|
||||
<td align="center" colspan="6">
|
||||
No results to show.
|
||||
</td>
|
||||
</tr>
|
||||
{/ALT}
|
||||
</tbody>
|
||||
</table>
|
||||
<button name="submit" value="submit" type="submit" class="btn btn-sm btn-danger">Delete</button>
|
||||
</form>
|
||||
<br />
|
||||
<a href="{ROOT_URL}admin/bugreport/clear">clear all</a>
|
||||
<div class="context-main-bg context-main p-3">
|
||||
<legend class="text-center">Bug Reports</legend>
|
||||
<hr>
|
||||
{ADMIN_BREADCRUMBS}
|
||||
<form action="{ROOT_URL}admin/bugreport/delete" method="post">
|
||||
<table class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width: 5%">ID</th>
|
||||
<th style="width: 20%">Time</th>
|
||||
<th style="width: 60%">Description</th>
|
||||
<th style="width: 5%"></th>
|
||||
<th style="width: 5%"></th>
|
||||
<th style="width: 5%">
|
||||
<input type="checkbox" onchange="checkAll(this)" name="check.br" value="BR_[]">
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{LOOP}
|
||||
<tr>
|
||||
<td align="center">{ID}</td>
|
||||
<td align="center">{DTC}{time}{/DTC}</td>
|
||||
<td>{description}</td>
|
||||
<td><a href="{ROOT_URL}admin/bugreport/view/{ID}" class="btn btn-sm btn-primary"><i class="fa fa-fw fa-upload"></i></a></td>
|
||||
<td><a href="{ROOT_URL}admin/bugreport/delete/{ID}" class="btn btn-sm btn-danger"><i class="fa fa-fw fa-trash"></i></a></td>
|
||||
<td>
|
||||
<input type="checkbox" value="{ID}" name="BR_[]">
|
||||
</td>
|
||||
</tr>
|
||||
{/LOOP}
|
||||
{ALT}
|
||||
<tr>
|
||||
<td align="center" colspan="6">
|
||||
No results to show.
|
||||
</td>
|
||||
</tr>
|
||||
{/ALT}
|
||||
</tbody>
|
||||
</table>
|
||||
<button name="submit" value="submit" type="submit" class="btn btn-sm btn-danger"><i class="fa fa-fw fa-trash"></i></button>
|
||||
</form>
|
||||
<br>
|
||||
<a href="{ROOT_URL}admin/bugreport/clear">clear all</a>
|
||||
</div>
|
@ -1,64 +1,69 @@
|
||||
<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 Report</h3>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<div class="row">
|
||||
<div class=" col-md-12 col-lg-12 ">
|
||||
<table class="table table-user-primary">
|
||||
<tbody>
|
||||
<div class="container py-4">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-8">
|
||||
{ADMIN_BREADCRUMBS}
|
||||
<div class="card shadow">
|
||||
<!-- Card Header -->
|
||||
<div class="card-header text-center bg-dark text-white">
|
||||
<h3 class="card-title mb-0">Bug Report</h3>
|
||||
</div>
|
||||
|
||||
<!-- Card Body -->
|
||||
<div class="card-body">
|
||||
<div class="row align-items-center">
|
||||
<!-- Log Details -->
|
||||
<table class="table table-borderless">
|
||||
<tbody>
|
||||
<tr>
|
||||
<th scope="row">ID:</th>
|
||||
<td>{ID}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="left" width="200"><b>ID</b></td>
|
||||
<td align="right">{ID}</td>
|
||||
<th scope="row">Time submitted</th>
|
||||
<td>{DTC}{time}{/DTC}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><b>Time submitted</b></td>
|
||||
<td align="right">{DTC}{time}{/DTC}</td>
|
||||
<th scope="row">URL:</th>
|
||||
<td><a href="{URL}">{URL}</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><b>Submitted by</b></td>
|
||||
<td align="right"><a href="{ROOT_URL}admin/users/view/{userID}">{submittedBy}</a></td>
|
||||
<th scope="row">Original URL</th>
|
||||
<td><a href="{OURL}">{OURL}</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><b>IP</b></td>
|
||||
<td align="right">{ip}</td>
|
||||
<th scope="row">Multiple occurrences?</th>
|
||||
<td>{repeatText}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">IP:</th>
|
||||
<td>{ip}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">User:</th>
|
||||
<td><a href="{ROOT_URL}admin/users/view/{userID}">{submittedBy}</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><b>URL:</b></td>
|
||||
<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>
|
||||
<th scope="row" colspan="2">Description:</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="2">{description}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel-footer">
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Admin Controls -->
|
||||
<div class="card-footer text-center">
|
||||
{ADMIN}
|
||||
<form action="{ROOT_URL}admin/bugreport/delete" method="post">
|
||||
<INPUT type="hidden" name="BR_" value="{ID}"/>
|
||||
<input type="hidden" name="token" value="{TOKEN}" />
|
||||
<button name="submit" value="submit" type="submit" class="btn btn-sm btn-danger"><i class="glyphicon glyphicon-remove"></i></button>
|
||||
<input type="hidden" name="BR_" value="{ID}">
|
||||
<input type="hidden" name="token" value="{TOKEN}">
|
||||
<button name="submit" value="submit" type="submit" class="btn btn-sm btn-danger"><i class="fa fa-fw fa-trash"></i></button>
|
||||
</form>
|
||||
{/ADMIN}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
@ -1,36 +1,52 @@
|
||||
<legend>Bug Report</legend>
|
||||
<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>
|
||||
<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>
|
||||
<form action="" method="post" class="form-horizontal">
|
||||
<label for="url">Page you were trying to reach:</label>
|
||||
<input type="url" name="url" id="url" class="form-control" aria-describedby="urlHelp">
|
||||
<p id="urlHelp" 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)
|
||||
</p>
|
||||
<label for="ourl">Page you were on:</label>
|
||||
<input type="url" name="ourl" id="ourl" class="form-control" aria-describedby="ourlHelp">
|
||||
<p 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)
|
||||
</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 class="col-8 mx-auto p-4 rounded shadow-sm mb-5 context-main-bg mt-4 container">
|
||||
<h2 class="text-center mb-4">Bug Report</h2>
|
||||
<hr>
|
||||
<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>
|
||||
<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>
|
||||
<form action="" method="post">
|
||||
<!-- Page URL -->
|
||||
<div class="mb-3">
|
||||
<label for="url" class="form-label">Page you were trying to reach:</label>
|
||||
<input type="url" name="url" id="url" class="form-control" aria-describedby="urlHelp" required>
|
||||
<small id="urlHelp" class="form-text text-muted">
|
||||
What is the URL of the page you actually received the error on? (The URL is the website address. Example: {ROOT_URL}home)
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
<input type="hidden" name="token" value="{TOKEN}">
|
||||
<button name="submit" value="submit" type="submit" class="btn btn-lg btn-primary center-block">Submit</button>
|
||||
</form>
|
||||
|
||||
<!-- Referrer URL -->
|
||||
<div class="mb-3">
|
||||
<label for="ourl" class="form-label">Page you were on:</label>
|
||||
<input type="url" name="ourl" id="ourl" class="form-control" aria-describedby="ourlHelp">
|
||||
<small id="ourlHelp" class="form-text text-muted">
|
||||
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>
|
@ -49,9 +49,13 @@ class Comments extends AdminController {
|
||||
$this->index();
|
||||
}
|
||||
|
||||
public function viewComment( $data = null ) {
|
||||
$commentData = self::$comments->findById( $data );
|
||||
if ( $commentData !== false ) {
|
||||
public function viewComments( $contentID = null ) {
|
||||
if ( empty( $contentID ) ) {
|
||||
Issues::add( 'error', 'Content ID not found.' );
|
||||
return $this->index();
|
||||
}
|
||||
$contentData = self::$comments->findById( $data );
|
||||
if ( empty( $contentID ) ) {
|
||||
return Views::view( 'comments.list', $commentData );
|
||||
}
|
||||
Issues::add( 'error', 'Comment not found.' );
|
||||
|
@ -170,7 +170,7 @@ class Comments extends DatabaseModel {
|
||||
$where = ['contentType', '=', $contentType];
|
||||
}
|
||||
if ( empty( $limit ) ) {
|
||||
$commentData = self::$db->getPaginated( $this->tableName, $where, 'created', 'DESC' );
|
||||
$commentData = self::$db->get( $this->tableName, $where, 'created', 'DESC' );
|
||||
} else {
|
||||
$commentData = self::$db->get( $this->tableName, $where, 'created', 'DESC', [0, $limit] );
|
||||
}
|
||||
|
@ -25,9 +25,10 @@ use TheTempusProject\Houdini\Classes\Views;
|
||||
use TheTempusProject\Classes\Forms;
|
||||
use TheTempusProject\Hermes\Functions\Redirect;
|
||||
use TheTempusProject\Bedrock\Functions\Session;
|
||||
use TheTempusProject\Models\Comments as CommentModel;
|
||||
use TheTempusProject\Models\Comments as CommentsModel;
|
||||
|
||||
class Comments extends Plugin {
|
||||
protected static $comments;
|
||||
public $pluginName = 'TP Comments';
|
||||
public $pluginAuthor = 'JoeyK';
|
||||
public $pluginWebsite = 'https://TheTempusProject.com';
|
||||
@ -61,7 +62,6 @@ class Comments extends Plugin {
|
||||
]
|
||||
],
|
||||
];
|
||||
public $comments;
|
||||
|
||||
public function __construct( $load = false ) {
|
||||
if ( !empty(App::$activePerms) ) {
|
||||
@ -75,11 +75,16 @@ class Comments extends Plugin {
|
||||
'replace' => ( App::$isMod ? '$1' : '' ),
|
||||
'enabled' => true,
|
||||
];
|
||||
$this->getModel();
|
||||
self::$comments = new CommentsModel;
|
||||
parent::__construct( $load );
|
||||
}
|
||||
|
||||
public function formPost( $type, $content, $redirect ) {
|
||||
if ( ! $this->checkEnabled() ) {
|
||||
Debug::info( 'Comments Plugin is disabled in the control panel.' );
|
||||
Issues::add( 'error', 'Comments are disabled.' );
|
||||
return false;
|
||||
}
|
||||
if ( !App::$isLoggedIn ) {
|
||||
Session::flash( 'notice', 'You must be logged in to post comments.' );
|
||||
return Redirect::to( $redirect . $content->ID );
|
||||
@ -88,7 +93,7 @@ class Comments extends Plugin {
|
||||
Session::flash( 'error', [ 'There was a problem with your comment form.' => Check::userErrors() ] );
|
||||
return Redirect::to( $redirect . $content->ID );
|
||||
}
|
||||
if ( !$this->comments->create( $type, $content->ID, Input::post( 'comment' ) ) ) {
|
||||
if ( !self::$comments->create( $type, $content->ID, Input::post( 'comment' ) ) ) {
|
||||
Session::flash( 'error', [ 'There was a problem posting your comment.' => Check::userErrors() ] );
|
||||
} else {
|
||||
Session::flash( 'success', 'Comment posted' );
|
||||
@ -97,6 +102,11 @@ class Comments extends Plugin {
|
||||
}
|
||||
|
||||
public function formEdit( $type, $content, $redirect ) {
|
||||
if ( ! $this->checkEnabled() ) {
|
||||
Debug::info( 'Comments Plugin is disabled in the control panel.' );
|
||||
Issues::add( 'error', 'Comments are disabled.' );
|
||||
return false;
|
||||
}
|
||||
if ( !App::$isLoggedIn ) {
|
||||
Session::flash( 'notice', 'You must be logged in to do that.' );
|
||||
return Redirect::to( $type );
|
||||
@ -112,7 +122,7 @@ class Comments extends Plugin {
|
||||
Issues::add( 'error', [ 'There was a problem editing your comment.' => Check::userErrors() ] );
|
||||
return Views::view( 'comments.admin.edit', $content );
|
||||
}
|
||||
if ( !$this->comments->update( $content->ID, Input::post( 'comment' ) ) ) {
|
||||
if ( !self::$comments->update( $content->ID, Input::post( 'comment' ) ) ) {
|
||||
Issues::add( 'error', [ 'There was a problem editing your comment.' => Check::userErrors() ] );
|
||||
return Views::view( 'comments.admin.edit', $content );
|
||||
}
|
||||
@ -121,6 +131,11 @@ class Comments extends Plugin {
|
||||
}
|
||||
|
||||
public function formDelete( $type, $content, $redirect ) {
|
||||
if ( ! $this->checkEnabled() ) {
|
||||
Debug::info( 'Comments Plugin is disabled in the control panel.' );
|
||||
Issues::add( 'error', 'Comments are disabled.' );
|
||||
return false;
|
||||
}
|
||||
if ( !App::$isLoggedIn ) {
|
||||
Session::flash( 'notice', 'You must be logged in to do that.' );
|
||||
return Redirect::to( $type );
|
||||
@ -129,18 +144,11 @@ class Comments extends Plugin {
|
||||
Session::flash( 'error', 'You do not have permission to edit this comment' );
|
||||
return Redirect::to( $type );
|
||||
}
|
||||
if ( !$this->comments->delete( (array) $content->ID ) ) {
|
||||
if ( !self::$comments->delete( (array) $content->ID ) ) {
|
||||
Session::flash( 'error', 'There was an error with your request.' );
|
||||
} else {
|
||||
Session::flash( 'success', 'Comment has been deleted' );
|
||||
}
|
||||
return Redirect::to( $redirect . $content->contentID );
|
||||
}
|
||||
|
||||
public function getModel() {
|
||||
if ( empty($this->comments) ) {
|
||||
$this->comments = new CommentModel;
|
||||
}
|
||||
return $this->comments;
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
<legend>New Comments</legend>
|
||||
<table class="table table-striped">
|
||||
<table class="table context-main">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width: 20%"></th>
|
||||
@ -13,8 +13,8 @@
|
||||
<tr>
|
||||
<td>{authorName}</td>
|
||||
<td>{content}</td>
|
||||
<td><a href="{ROOT_URL}admin/comments/edit/{ID}" class="btn btn-sm btn-warning" role="button"><i class="glyphicon glyphicon-edit"></i></a></td>
|
||||
<td><a href="{ROOT_URL}admin/comments/delete/{ID}" class="btn btn-sm btn-danger" role="button"><i class="glyphicon glyphicon-trash"></i></a></td>
|
||||
<td><a href="{ROOT_URL}admin/comments/edit/{ID}" class="btn btn-sm btn-warning"><i class="fa fa-fw fa-pencil"></i></a></td>
|
||||
<td><a href="{ROOT_URL}admin/comments/delete/{ID}" class="btn btn-sm btn-danger"><i class="fa fa-fw fa-trash"></i></a></td>
|
||||
</tr>
|
||||
{/LOOP}
|
||||
{ALT}
|
||||
|
@ -1,9 +1,25 @@
|
||||
<form action="" method="post" class="form-horizontal">
|
||||
<div class="form-group center-block">
|
||||
<div class="col-lg-4 col-lg-offset-4">
|
||||
<textarea class="form-control" name="comment" maxlength="2000" rows="10" cols="50" id="comment">{content}</textarea>
|
||||
</div>
|
||||
<div class="mb-4 mt-4">
|
||||
<div class="offset-md-1 col-10 p-3 context-main-bg">
|
||||
<legend class="text-center">Edit Comment</legend>
|
||||
<hr>
|
||||
{ADMIN_BREADCRUMBS}
|
||||
<form action="" method="post" class="container py-4">
|
||||
<fieldset>
|
||||
<div class="mb-3 row">
|
||||
<label for="comment" class="col-lg-5 col-form-label text-end">Comment:</label>
|
||||
<div class="col-lg-3">
|
||||
<textarea class="form-control" name="comment" maxlength="2000" rows="5" cols="50" id="comment">{content}</textarea>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Hidden Token -->
|
||||
<input type="hidden" name="token" value="{TOKEN}">
|
||||
|
||||
<!-- Submit Button -->
|
||||
<div class="text-center">
|
||||
<button type="submit" name="submit" value="submit" class="btn btn-primary btn-lg">Save</button>
|
||||
</div>
|
||||
</fieldset>
|
||||
</form>
|
||||
</div>
|
||||
<button name="submit" value="submit" type="submit" class="btn btn-lg btn-primary center-block">Comment</button>
|
||||
<input type="hidden" name="token" value="{TOKEN}">
|
||||
</form>
|
||||
</div>
|
@ -1,43 +1,45 @@
|
||||
<legend>Recent Comments</legend>
|
||||
{PAGINATION}
|
||||
<form action="{ROOT_URL}admin/comments/delete" method="post">
|
||||
<table class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width: 20%">Author</th>
|
||||
<th style="width: 20%">Subject</th>
|
||||
<th style="width: 35%">Comment</th>
|
||||
<th style="width: 10%">Time</th>
|
||||
<th style="width: 5%"></th>
|
||||
<th style="width: 5%"></th>
|
||||
<th style="width: 5%">
|
||||
<INPUT type="checkbox" onchange="checkAll(this)" name="check.c" value="C_[]"/>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{LOOP}
|
||||
<tr>
|
||||
<td><a href="{ROOT_URL}admin/users/view/{author}">{authorName}</a></td>
|
||||
<td><a href="{ROOT_URL}admin/blog/view/{contentID}">{contentTitle}</a></td>
|
||||
<td>{content}</td>
|
||||
<td>{DTC}{created}{/DTC}</td>
|
||||
<td><a href="{ROOT_URL}admin/comments/edit/{ID}" class="btn btn-sm btn-warning" role="button"><i class="glyphicon glyphicon-edit"></i></a></td>
|
||||
<td><a href="{ROOT_URL}admin/comments/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="C_[]">
|
||||
</td>
|
||||
</tr>
|
||||
{/LOOP}
|
||||
{ALT}
|
||||
<tr>
|
||||
<td align="center" colspan="7">
|
||||
No results to show.
|
||||
</td>
|
||||
</tr>
|
||||
{/ALT}
|
||||
</tbody>
|
||||
</table>
|
||||
<button name="submit" value="submit" type="submit" class="btn btn-sm btn-danger">Delete</button>
|
||||
</form>
|
||||
<br />
|
||||
<div class="context-main-bg context-main p-3">
|
||||
<legend class="text-center">Recent Comments</legend>
|
||||
<hr>
|
||||
{ADMIN_BREADCRUMBS}
|
||||
<form action="{ROOT_URL}admin/comments/delete" method="post">
|
||||
<table class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width: 20%">Author</th>
|
||||
<th style="width: 20%">Subject</th>
|
||||
<th style="width: 35%">Comment</th>
|
||||
<th style="width: 10%">Time</th>
|
||||
<th style="width: 5%"></th>
|
||||
<th style="width: 5%"></th>
|
||||
<th style="width: 5%">
|
||||
<input type="checkbox" onchange="checkAll(this)" name="check.c" value="C_[]">
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{LOOP}
|
||||
<tr>
|
||||
<td><a href="{ROOT_URL}admin/users/view/{author}">{authorName}</a></td>
|
||||
<td><a href="{ROOT_URL}admin/blog/view/{contentID}">{contentTitle}</a></td>
|
||||
<td>{content}</td>
|
||||
<td>{DTC}{created}{/DTC}</td>
|
||||
<td><a href="{ROOT_URL}admin/comments/edit/{ID}" class="btn btn-sm btn-warning"><i class="fa fa-fw fa-pencil"></i></a></td>
|
||||
<td><a href="{ROOT_URL}admin/comments/delete/{ID}" class="btn btn-sm btn-danger"><i class="fa fa-fw fa-trash"></i></a></td>
|
||||
<td>
|
||||
<input type="checkbox" value="{ID}" name="C_[]">
|
||||
</td>
|
||||
</tr>
|
||||
{/LOOP}
|
||||
{ALT}
|
||||
<tr>
|
||||
<td align="center" colspan="7">
|
||||
No results to show.
|
||||
</td>
|
||||
</tr>
|
||||
{/ALT}
|
||||
</tbody>
|
||||
</table>
|
||||
<button name="submit" value="submit" type="submit" class="btn btn-sm btn-danger"><i class="fa fa-fw fa-trash"></i></button>
|
||||
</form>
|
||||
</div>
|
@ -1,8 +1,8 @@
|
||||
<div class="action">
|
||||
<a href="{ROOT_URL}{COMMENT_TYPE}/comments/edit/{ID}" class="btn btn-warning btn-xs" role="button">
|
||||
<span class="glyphicon glyphicon-pencil"></span>
|
||||
<a href="{ROOT_URL}{COMMENT_TYPE}/comments/edit/{ID}" class="btn btn-warning btn-sm">
|
||||
<span class="fa fa-fw fa-pencil"></span>
|
||||
</a>
|
||||
<a href="{ROOT_URL}{COMMENT_TYPE}/comments/delete/{ID}" class="btn btn-danger btn-xs" role="button">
|
||||
<span class="glyphicon glyphicon-trash"></span>
|
||||
<a href="{ROOT_URL}{COMMENT_TYPE}/comments/delete/{ID}" class="btn btn-danger btn-sm">
|
||||
<span class="fa fa-fw fa-trash"></span>
|
||||
</a>
|
||||
</div>
|
@ -1,10 +1,14 @@
|
||||
<form action="" method="post" class="form-horizontal">
|
||||
<div class="form-group center-block">
|
||||
<div class="col-lg-8 col-lg-offset-2">
|
||||
<textarea class="form-control" name="comment" maxlength="2000" rows="4" cols="50" id="comment"></textarea>
|
||||
</div>
|
||||
<form action="" method="post" class="text-center mx-auto mt-4" style="max-width: 600px;">
|
||||
<div class="mb-3">
|
||||
<textarea
|
||||
class="form-control"
|
||||
name="comment"
|
||||
maxlength="2000"
|
||||
rows="4"
|
||||
id="comment"
|
||||
placeholder="Write your comment here..."></textarea>
|
||||
</div>
|
||||
<button name="submit" value="submit" type="submit" class="btn btn-lg btn-primary center-block">Comment</button>
|
||||
<button name="submit" value="submit" type="submit" class="btn btn-lg btn-primary mb-3">Comment</button>
|
||||
<input type="hidden" name="token" value="{TOKEN}">
|
||||
<input type="hidden" name="contentId" value="{CONTENT_ID}">
|
||||
</form>
|
||||
</form>
|
||||
|
@ -1,22 +1,23 @@
|
||||
<div class="panel panel-info widget comments">
|
||||
<div class="panel-heading">
|
||||
<span class="glyphicon glyphicon-comment"></span>
|
||||
<h3 class="panel-title">Comments</h3>
|
||||
<span class="label label-primary">{count}</span>
|
||||
<div class="card">
|
||||
<div class="card-header d-flex align-items-center justify-content-between context-second-bg context-main">
|
||||
<h3 class="card-title mb-0">
|
||||
<i class="fa fa-fw fa-comment"></i> Comments
|
||||
</h3>
|
||||
<span class="badge bg-primary">{count}</span>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<ul class="list-group">
|
||||
<div class="card-body context-main-bg context-main">
|
||||
<ul class="list-group list-group-flush">
|
||||
{LOOP}
|
||||
<li class="list-group-item">
|
||||
<div class="row">
|
||||
<div class="col-xs-2 col-md-1">
|
||||
<img src="{ROOT_URL}{avatar}" class="img-circle img-responsive" alt="" />
|
||||
<li class="list-group-item context-second-bg context-main mb-2">
|
||||
<div class="d-flex align-items-start">
|
||||
<div class="me-3">
|
||||
<img src="{ROOT_URL}{avatar}" class="rounded-circle" alt="User Avatar" style="width: 50px; height: 50px;">
|
||||
</div>
|
||||
<div class="col-xs-10 col-md-11">
|
||||
<div>
|
||||
<div class="mic-info">
|
||||
By: <a href="{ROOT_URL}home/profile/{author}">{authorName}</a> on {DTC date}{created}{/DTC}
|
||||
</div>
|
||||
<div>
|
||||
<div class="mb-1">
|
||||
<small class="text-muted">
|
||||
By: <a href="{ROOT_URL}home/profile/{author}" class="text-decoration-none">{authorName}</a> on {DTC date}{created}{/DTC}
|
||||
</small>
|
||||
</div>
|
||||
<div class="comment-text">
|
||||
{content}
|
||||
@ -27,13 +28,9 @@
|
||||
</li>
|
||||
{/LOOP}
|
||||
{ALT}
|
||||
<li class="list-group-item">
|
||||
<div class="row">
|
||||
<div class="col-xs-10 col-md-11">
|
||||
<div class="comment-text">
|
||||
<p class="text-center">Be the first to comment.</p>
|
||||
</div>
|
||||
</div>
|
||||
<li class="list-group-item context-second-bg context-main mb-2">
|
||||
<div class="text-center">
|
||||
<p class="mb-0">Be the first to comment.</p>
|
||||
</div>
|
||||
</li>
|
||||
{/ALT}
|
||||
|
@ -1,10 +1,10 @@
|
||||
<?php
|
||||
/**
|
||||
* app/plugins/feedback/controllers/admin/feedback.php
|
||||
* app/plugins/contact/controllers/admin/contact.php
|
||||
*
|
||||
* This is the feedback admin controller.
|
||||
* This is the contact admin controller.
|
||||
*
|
||||
* @package TP Feedback
|
||||
* @package TP Contact
|
||||
* @version 3.0
|
||||
* @author Joey Kimsey <Joey@thetempusproject.com>
|
||||
* @link https://TheTempusProject.com
|
||||
@ -18,29 +18,29 @@ use TheTempusProject\Houdini\Classes\Views;
|
||||
use TheTempusProject\Houdini\Classes\Navigation;
|
||||
use TheTempusProject\Houdini\Classes\Components;
|
||||
use TheTempusProject\Classes\AdminController;
|
||||
use TheTempusProject\Models\Feedback as FeedbackModel;
|
||||
use TheTempusProject\Models\Contact as ContactModel;
|
||||
|
||||
class Feedback extends AdminController {
|
||||
protected static $feedback;
|
||||
class Contact extends AdminController {
|
||||
protected static $contact;
|
||||
|
||||
public function __construct() {
|
||||
parent::__construct();
|
||||
self::$title = 'Admin - Feedback';
|
||||
self::$feedback = new FeedbackModel;
|
||||
$view = Navigation::activePageSelect( 'nav.admin', '/admin/feedback' );
|
||||
self::$title = 'Admin - Contact';
|
||||
self::$contact = new ContactModel;
|
||||
$view = Navigation::activePageSelect( 'nav.admin', '/admin/contact' );
|
||||
Components::set( 'ADMINNAV', $view );
|
||||
}
|
||||
|
||||
public function view( $id = null ) {
|
||||
Views::view( 'feedback.admin.view', self::$feedback->findById( $id ) );
|
||||
Views::view( 'contact.admin.view', self::$contact->findById( $id ) );
|
||||
}
|
||||
|
||||
public function delete( $data = null ) {
|
||||
if ( Input::exists( 'submit' ) ) {
|
||||
$data = Input::post( 'F_' );
|
||||
}
|
||||
if ( self::$feedback->delete( (array) $data ) ) {
|
||||
Issues::add( 'success', 'feedback deleted' );
|
||||
if ( self::$contact->delete( (array) $data ) ) {
|
||||
Issues::add( 'success', 'contact deleted' );
|
||||
} else {
|
||||
Issues::add( 'error', 'There was an error with your request.' );
|
||||
}
|
||||
@ -48,11 +48,11 @@ class Feedback extends AdminController {
|
||||
}
|
||||
|
||||
public function clear( $data = null ) {
|
||||
self::$feedback->clear();
|
||||
self::$contact->clear();
|
||||
$this->index();
|
||||
}
|
||||
|
||||
public function index( $data = null ) {
|
||||
Views::view( 'feedback.admin.list', self::$feedback->listPaginated() );
|
||||
Views::view( 'contact.admin.list', self::$contact->listPaginated() );
|
||||
}
|
||||
}
|
@ -1,10 +1,10 @@
|
||||
<?php
|
||||
/**
|
||||
* app/plugins/feedback/controllers/feedback.php
|
||||
* app/plugins/contact/controllers/contact.php
|
||||
*
|
||||
* This is the home controller for the feedback plugin.
|
||||
* This is the home controller for the contact plugin.
|
||||
*
|
||||
* @package TP Feedback
|
||||
* @package TP Contact
|
||||
* @version 3.0
|
||||
* @author Joey Kimsey <Joey@thetempusproject.com>
|
||||
* @link https://TheTempusProject.com
|
||||
@ -20,25 +20,25 @@ use TheTempusProject\Bedrock\Functions\Check;
|
||||
use TheTempusProject\Bedrock\Functions\Input;
|
||||
use TheTempusProject\Bedrock\Functions\Session;
|
||||
use TheTempusProject\Hermes\Functions\Redirect;
|
||||
use TheTempusProject\Models\Feedback as FeedbackModel;
|
||||
use TheTempusProject\Models\Contact as ContactModel;
|
||||
|
||||
class Feedback extends Controller {
|
||||
protected static $feedback;
|
||||
class Contact extends Controller {
|
||||
protected static $contact;
|
||||
|
||||
public function index() {
|
||||
self::$feedback = new FeedbackModel;
|
||||
self::$title = 'Feedback - {SITENAME}';
|
||||
self::$pageDescription = 'At {SITENAME}, we value our users\' input. You can provide any feedback or suggestions using this form.';
|
||||
self::$contact = new ContactModel;
|
||||
self::$title = 'Contact - {SITENAME}';
|
||||
self::$pageDescription = 'At {SITENAME}, we value our users\' input. You can provide any contact or suggestions using this form.';
|
||||
if ( !Input::exists() ) {
|
||||
return Views::view( 'feedback.feedback' );
|
||||
return Views::view( 'contact.create' );
|
||||
}
|
||||
if ( !Forms::check( 'feedback' ) ) {
|
||||
if ( !Forms::check( 'contact' ) ) {
|
||||
Issues::add( 'error', [ 'There was an error with your form, please check your submission and try again.' => Check::userErrors() ] );
|
||||
return Views::view( 'feedback.feedback' );
|
||||
return Views::view( 'contact.create' );
|
||||
}
|
||||
$result = self::$feedback->create( Input::post( 'name' ), Input::post( 'feedbackEmail' ), Input::post( 'entry' ) );
|
||||
$result = self::$contact->create( Input::post( 'name' ), Input::post( 'contactEmail' ), Input::post( 'entry' ) );
|
||||
if ( $result ) {
|
||||
Session::flash( 'success', 'Thank you! Your feedback has been received.' );
|
||||
Session::flash( 'success', 'Thank you! Your contact has been received.' );
|
||||
Redirect::to( 'home/index' );
|
||||
} else {
|
||||
Issues::add( 'error', [ 'There was an error with your form, please check your submission and try again.' => Check::userErrors() ] );
|
@ -1,35 +1,35 @@
|
||||
<?php
|
||||
/**
|
||||
* app/plugins/feedback/forms.php
|
||||
* app/plugins/contact/forms.php
|
||||
*
|
||||
* This houses all of the form checking functions for this plugin.
|
||||
*
|
||||
* @package TP Feedback
|
||||
* @package TP Contact
|
||||
* @version 3.0
|
||||
* @author Joey Kimsey <Joey@thetempusproject.com>
|
||||
* @link https://TheTempusProject.com
|
||||
* @license https://opensource.org/licenses/MIT [MIT LICENSE]
|
||||
*/
|
||||
namespace TheTempusProject\Plugins\Feedback;
|
||||
namespace TheTempusProject\Plugins\Contact;
|
||||
|
||||
use TheTempusProject\Bedrock\Functions\Input;
|
||||
use TheTempusProject\Bedrock\Functions\Check;
|
||||
use TheTempusProject\Classes\Forms;
|
||||
|
||||
class FeedbackForms extends Forms {
|
||||
class ContactForms extends Forms {
|
||||
/**
|
||||
* Adds these functions to the form list.
|
||||
*/
|
||||
public function __construct() {
|
||||
self::addHandler( 'feedback', __CLASS__, 'feedback' );
|
||||
self::addHandler( 'contact', __CLASS__, 'contact' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the feedback form.
|
||||
* Validates the contact form.
|
||||
*
|
||||
* @return {bool}
|
||||
*/
|
||||
public static function feedback() {
|
||||
public static function contact() {
|
||||
if ( !Input::exists( 'name' ) ) {
|
||||
Check::addUserError( 'You must provide a name.' );
|
||||
return false;
|
||||
@ -38,12 +38,12 @@ class FeedbackForms extends Forms {
|
||||
Check::addUserError( 'Invalid name.' );
|
||||
return false;
|
||||
}
|
||||
if ( !empty( Input::post( 'feedbackEmail' ) ) && !Check::email( Input::post( 'feedbackEmail' ) ) ) {
|
||||
if ( !empty( Input::post( 'contactEmail' ) ) && !Check::email( Input::post( 'contactEmail' ) ) ) {
|
||||
Check::addUserError( 'Invalid Email.' );
|
||||
return false;
|
||||
}
|
||||
if ( Input::post( 'entry' ) == '' ) {
|
||||
Check::addUserError( 'Feedback cannot be empty.' );
|
||||
Check::addUserError( 'Contact cannot be empty.' );
|
||||
return false;
|
||||
}
|
||||
if ( !Check::token() ) {
|
||||
@ -53,4 +53,4 @@ class FeedbackForms extends Forms {
|
||||
}
|
||||
}
|
||||
|
||||
new FeedbackForms;
|
||||
new ContactForms;
|
@ -1,12 +1,12 @@
|
||||
<?php
|
||||
/**
|
||||
* app/plugins/feedback/models/feedback.php
|
||||
* app/plugins/contact/models/contact.php
|
||||
*
|
||||
* This class is used for the manipulation of the feedback database table.
|
||||
*
|
||||
* @todo make this send a confirmation email
|
||||
*
|
||||
* @package TP Feedback
|
||||
* @package TP Contact
|
||||
* @version 3.0
|
||||
* @author Joey Kimsey <Joey@thetempusproject.com>
|
||||
* @link https://TheTempusProject.com
|
||||
@ -14,13 +14,12 @@
|
||||
*/
|
||||
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\Plugins\Feedback as Plugin;
|
||||
use TheTempusProject\Plugins\Contact as Plugin;
|
||||
|
||||
class Feedback extends DatabaseModel {
|
||||
class Contact extends DatabaseModel {
|
||||
public $tableName = 'feedback';
|
||||
public $databaseMatrix = [
|
||||
[ 'name', 'varchar', '128' ],
|
||||
@ -40,7 +39,7 @@ class Feedback extends DatabaseModel {
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves a feedback form to the db.
|
||||
* Saves a contact form to the db.
|
||||
*
|
||||
* @param string $name -the name on the form
|
||||
* @param string $email -the email provided
|
||||
@ -49,7 +48,7 @@ class Feedback extends DatabaseModel {
|
||||
*/
|
||||
public function create( $name, $email, $feedback ) {
|
||||
if ( !$this->plugin->checkEnabled() ) {
|
||||
Debug::info( 'Feedback is disabled in the config.' );
|
||||
Debug::info( 'Contact is disabled in the config.' );
|
||||
return false;
|
||||
}
|
||||
$fields = [
|
||||
@ -60,14 +59,14 @@ class Feedback extends DatabaseModel {
|
||||
'ip' => $_SERVER['REMOTE_ADDR'],
|
||||
];
|
||||
if ( !self::$db->insert( $this->tableName, $fields ) ) {
|
||||
Debug::info( 'Feedback::create - failed to insert to db' );
|
||||
Debug::info( 'Contact::create - failed to insert to db' );
|
||||
return false;
|
||||
}
|
||||
return self::$db->lastId();
|
||||
}
|
||||
|
||||
/**
|
||||
* Function to clear feedback from the DB.
|
||||
* Function to clear contact from the DB.
|
||||
*
|
||||
* @todo is there a way i could check for success here I'm pretty sure this is just a bad idea?
|
||||
* @return bool
|
||||
@ -77,8 +76,8 @@ class Feedback extends DatabaseModel {
|
||||
self::$log = new Log;
|
||||
}
|
||||
self::$db->delete( $this->tableName, ['ID', '>=', '0'] );
|
||||
self::$log->admin( 'Cleared Feedback' );
|
||||
Debug::info( 'Feedback Cleared' );
|
||||
self::$log->admin( 'Contacts Cleared' );
|
||||
Debug::info( 'Contacts Cleared' );
|
||||
return true;
|
||||
}
|
||||
}
|
@ -1,10 +1,10 @@
|
||||
<?php
|
||||
/**
|
||||
* app/plugins/feedback/plugin.php
|
||||
* app/plugins/contact/plugin.php
|
||||
*
|
||||
* This houses all of the main plugin info and functionality.
|
||||
*
|
||||
* @package TP Feedback
|
||||
* @package TP Contact
|
||||
* @version 3.0
|
||||
* @author Joey Kimsey <Joey@thetempusproject.com>
|
||||
* @link https://TheTempusProject.com
|
||||
@ -14,18 +14,18 @@ namespace TheTempusProject\Plugins;
|
||||
|
||||
use TheTempusProject\Classes\Plugin;
|
||||
|
||||
class Feedback extends Plugin {
|
||||
public $pluginName = 'TP Feedback';
|
||||
class Contact extends Plugin {
|
||||
public $pluginName = 'TP Contact';
|
||||
public $pluginAuthor = 'JoeyK';
|
||||
public $pluginWebsite = 'https://TheTempusProject.com';
|
||||
public $modelVersion = '1.0';
|
||||
public $pluginVersion = '3.0';
|
||||
public $pluginDescription = 'A simple plugin which adds a form and management for user submitted feedback.';
|
||||
public $configName = 'feedback';
|
||||
public $pluginDescription = 'A simple plugin which adds a form and management for user submitted contact forms.';
|
||||
public $configName = 'contact';
|
||||
public $configMatrix = [
|
||||
'enabled' => [
|
||||
'type' => 'radio',
|
||||
'pretty' => 'Enable User Feedback.',
|
||||
'pretty' => 'Enable User Contact.',
|
||||
'default' => true,
|
||||
],
|
||||
'sendEmail' => [
|
||||
@ -36,25 +36,25 @@ class Feedback extends Plugin {
|
||||
'emailTemplate' => [
|
||||
'type' => 'text',
|
||||
'pretty' => 'Email Template',
|
||||
'default' => 'feedbackEmail',
|
||||
'default' => 'contactEmail',
|
||||
],
|
||||
];
|
||||
public $permissionMatrix = [
|
||||
'feedback' => [
|
||||
'pretty' => 'Can Submit Feedback',
|
||||
'default' => false,
|
||||
'contact' => [
|
||||
'pretty' => 'Can Submit Contact',
|
||||
'default' => true,
|
||||
],
|
||||
];
|
||||
public $footer_links = [
|
||||
public $contact_footer_links = [
|
||||
[
|
||||
'text' => 'Feedback',
|
||||
'url' => '{ROOT_URL}feedback',
|
||||
'text' => 'Contact',
|
||||
'url' => '{ROOT_URL}contact',
|
||||
],
|
||||
];
|
||||
public $admin_links = [
|
||||
[
|
||||
'text' => '<i class="fa fa-fw fa-support"></i> Feedback',
|
||||
'url' => '{ROOT_URL}admin/feedback',
|
||||
'text' => '<i class="fa fa-fw fa-briefcase"></i> Contact',
|
||||
'url' => '{ROOT_URL}admin/contact',
|
||||
],
|
||||
];
|
||||
}
|
45
app/plugins/contact/views/admin/list.html
Normal file
45
app/plugins/contact/views/admin/list.html
Normal file
@ -0,0 +1,45 @@
|
||||
<div class="context-main-bg context-main p-3">
|
||||
<legend class="text-center">Contact Forms</legend>
|
||||
<hr>
|
||||
{ADMIN_BREADCRUMBS}
|
||||
<form action="{ROOT_URL}admin/contact/delete" method="post">
|
||||
<table class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width: 5%">ID</th>
|
||||
<th style="width: 25%">Time</th>
|
||||
<th style="width: 55%">Feedback</th>
|
||||
<th style="width: 5%"></th>
|
||||
<th style="width: 5%"></th>
|
||||
<th style="width: 5%">
|
||||
<input type="checkbox" onchange="checkAll(this)" name="check.f" value="F_[]">
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{LOOP}
|
||||
<tr>
|
||||
<td>{ID}</td>
|
||||
<td>{DTC}{time}{/DTC}</td>
|
||||
<td>{feedback}</td>
|
||||
<td><a href="{ROOT_URL}admin/contact/view/{ID}" class="btn btn-sm btn-primary"><i class="fa fa-fw fa-upload"></i></a></td>
|
||||
<td><a href="{ROOT_URL}admin/contact/delete/{ID}" class="btn btn-sm btn-danger"><i class="fa fa-fw fa-trash"></i></a></td>
|
||||
<td>
|
||||
<input type="checkbox" value="{ID}" name="F_[]">
|
||||
</td>
|
||||
</tr>
|
||||
{/LOOP}
|
||||
{ALT}
|
||||
<tr>
|
||||
<td align="center" colspan="6">
|
||||
No results to show.
|
||||
</td>
|
||||
</tr>
|
||||
{/ALT}
|
||||
</tbody>
|
||||
</table>
|
||||
<button name="submit" value="submit" type="submit" class="btn btn-sm btn-danger"><i class="fa fa-fw fa-trash"></i></button>
|
||||
</form>
|
||||
<br>
|
||||
<a href="{ROOT_URL}admin/contact/clear">clear all</a>
|
||||
</div>
|
61
app/plugins/contact/views/admin/view.html
Normal file
61
app/plugins/contact/views/admin/view.html
Normal file
@ -0,0 +1,61 @@
|
||||
<div class="container py-4">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-8">
|
||||
{ADMIN_BREADCRUMBS}
|
||||
<div class="card shadow">
|
||||
<!-- Card Header -->
|
||||
<div class="card-header text-center bg-dark text-white">
|
||||
<h3 class="card-title mb-0">Contact Form</h3>
|
||||
</div>
|
||||
|
||||
<!-- Card Body -->
|
||||
<div class="card-body">
|
||||
<div class="row align-items-center">
|
||||
<!-- Log Details -->
|
||||
<table class="table table-borderless">
|
||||
<tbody>
|
||||
<tr>
|
||||
<th scope="row">ID:</th>
|
||||
<td>{ID}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Time submitted</th>
|
||||
<td>{DTC}{time}{/DTC}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Email:</th>
|
||||
<td>{email}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Name:</th>
|
||||
<td>{name}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">IP:</th>
|
||||
<td>{ip}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row" colspan="2">Feedback:</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="2">{feedback}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Admin Controls -->
|
||||
<div class="card-footer text-center">
|
||||
{ADMIN}
|
||||
<form action="{ROOT_URL}admin/contact/delete" method="post">
|
||||
<input type="hidden" name="F_" value="{ID}">
|
||||
<input type="hidden" name="token" value="{TOKEN}">
|
||||
<button name="submit" value="submit" type="submit" class="btn btn-sm btn-danger"><i class="fa fa-fw fa-trash"></i></button>
|
||||
</form>
|
||||
{/ADMIN}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
45
app/plugins/contact/views/create.html
Normal file
45
app/plugins/contact/views/create.html
Normal file
@ -0,0 +1,45 @@
|
||||
<div class="context-main-bg container py-4 my-4">
|
||||
<h2 class="text-center mb-4">Contact Us</h2>
|
||||
<div class="col-lg-6 offset-md-3">
|
||||
<p>
|
||||
Here at <strong>{SITENAME}</strong>, we highly value your feedback. We constantly strive to provide our users with the highest level of quality in everything we do.
|
||||
</p>
|
||||
<p>
|
||||
If you would like to provide any suggestions or comments on our service, we ask that you please fill out the quick form below and let us know what's on your mind.
|
||||
</p>
|
||||
</div>
|
||||
<form action="" method="post">
|
||||
<!-- Name -->
|
||||
<div class="mb-3 row">
|
||||
<label for="name" class="col-lg-3 col-form-label text-end">Name:</label>
|
||||
<div class="col-lg-6">
|
||||
<input type="text" class="form-control" name="name" id="name" required>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Email (Optional) -->
|
||||
<div class="mb-3 row">
|
||||
<label for="contactEmail" class="col-lg-3 col-form-label text-end">E-mail: (optional)</label>
|
||||
<div class="col-lg-6">
|
||||
<input type="email" class="form-control" name="contactEmail" id="contactEmail">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Feedback -->
|
||||
<div class="mb-3 row">
|
||||
<label for="entry" class="col-lg-3 col-form-label text-end">Feedback:</label>
|
||||
<div class="col-lg-6">
|
||||
<textarea class="form-control" name="entry" id="entry" 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}">
|
||||
|
||||
<!-- Submit Button -->
|
||||
<div class="text-center">
|
||||
<button type="submit" name="submit" value="submit" class="btn btn-primary btn-lg">Submit</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
@ -1,42 +0,0 @@
|
||||
<legend>Feedback</legend>
|
||||
{PAGINATION}
|
||||
<form action="{ROOT_URL}admin/feedback/delete" method="post">
|
||||
<table class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width: 5%">ID</th>
|
||||
<th style="width: 25%">Time</th>
|
||||
<th style="width: 55%">Feedback</th>
|
||||
<th style="width: 5%"></th>
|
||||
<th style="width: 5%"></th>
|
||||
<th style="width: 5%">
|
||||
<INPUT type="checkbox" onchange="checkAll(this)" name="check.f" value="F_[]"/>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{LOOP}
|
||||
<tr>
|
||||
<td>{ID}</td>
|
||||
<td>{DTC}{time}{/DTC}</td>
|
||||
<td>{feedback}</td>
|
||||
<td><a href="{ROOT_URL}admin/feedback/view/{ID}" class="btn btn-sm btn-primary" role="button"><i class="glyphicon glyphicon-open"></i></a></td>
|
||||
<td><a href="{ROOT_URL}admin/feedback/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="F_[]">
|
||||
</td>
|
||||
</tr>
|
||||
{/LOOP}
|
||||
{ALT}
|
||||
<tr>
|
||||
<td align="center" colspan="6">
|
||||
No results to show.
|
||||
</td>
|
||||
</tr>
|
||||
{/ALT}
|
||||
</tbody>
|
||||
</table>
|
||||
<button name="submit" value="submit" type="submit" class="btn btn-sm btn-danger">Delete</button>
|
||||
</form>
|
||||
<br />
|
||||
<a href="{ROOT_URL}admin/feedback/clear">clear all</a>
|
@ -1,56 +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">Feedback</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="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>IP:</td>
|
||||
<td align="right">{ip}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Email:</td>
|
||||
<td align="right">{email}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Name</td>
|
||||
<td align="right">{name}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center" colspan="2">Feedback</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="2">{feedback}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel-footer">
|
||||
{ADMIN}
|
||||
<form action="{ROOT_URL}admin/feedback/delete" method="post">
|
||||
<INPUT type="hidden" name="F_" value="{ID}"/>
|
||||
<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>
|
||||
</form>
|
||||
{/ADMIN}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
@ -1,27 +0,0 @@
|
||||
<form action="" method="post" class="form-horizontal">
|
||||
<legend>Feedback</legend>
|
||||
<p>Here at {SITENAME} we highly value your feedback. We constantly strive to provide our users with the highest level of quality in everything we do.</p>
|
||||
<p>If you would like to provide any suggestions or comments on our service, we ask that you please fill out the quick form below and let us know what's on your mind.</p>
|
||||
<fieldset>
|
||||
<div class="form-group">
|
||||
<label for="name" class="col-lg-1 control-label">Name:</label>
|
||||
<div class="col-lg-2">
|
||||
<input class="form-control" type="text" name="name" id="name">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="feedbackEmail" class="col-lg-1 control-label">E-mail: (optional)</label>
|
||||
<div class="col-lg-2">
|
||||
<input class="form-control" type="text" name="feedbackEmail" id="feedbackEmail">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="entry" class="col-lg-3 control-label">Feedback:<br> (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>
|
||||
</fieldset>
|
||||
<input type="hidden" name="token" value="{TOKEN}">
|
||||
<button name="submit" value="submit" type="submit" class="btn btn-lg btn-primary center-block">Submit</button><br>
|
||||
</form>
|
@ -19,6 +19,9 @@ use TheTempusProject\Houdini\Classes\Views;
|
||||
use TheTempusProject\Houdini\Classes\Issues;
|
||||
use TheTempusProject\Bedrock\Functions\Check;
|
||||
use TheTempusProject\Bedrock\Functions\Input;
|
||||
use TheTempusProject\TheTempusProject as App;
|
||||
use TheTempusProject\Hermes\Functions\Redirect;
|
||||
use TheTempusProject\Bedrock\Functions\Session;
|
||||
|
||||
class Messages extends Controller {
|
||||
private static $message;
|
||||
@ -27,6 +30,10 @@ class Messages extends Controller {
|
||||
parent::__construct();
|
||||
self::$title = 'Messages';
|
||||
self::$message = new Message;
|
||||
if ( ! App::$isLoggedIn ) {
|
||||
Session::flash( 'error', 'You do not have permission to access this page.' );
|
||||
return Redirect::home();
|
||||
}
|
||||
}
|
||||
|
||||
public function create() {
|
||||
@ -71,8 +78,9 @@ class Messages extends Controller {
|
||||
}
|
||||
|
||||
public function index() {
|
||||
Views::view( 'messages.inbox', self::$message->getInbox() );
|
||||
Views::view( 'messages.outbox', self::$message->getOutbox() );
|
||||
Components::set( 'message_inbox', Views::simpleView( 'messages.inbox', self::$message->getInbox() ) );
|
||||
Components::set( 'message_outbox', Views::simpleView( 'messages.outbox', self::$message->getOutbox() ) );
|
||||
Views::view( 'messages.index' );
|
||||
}
|
||||
|
||||
public function read( $id = '' ) {
|
||||
|
@ -61,7 +61,7 @@ class Message extends DatabaseModel {
|
||||
Debug::info( 'Invalid user ID' );
|
||||
return false;
|
||||
}
|
||||
$messageData = self::$db->getPaginated( $this->tableName, [ 'ID', '=', $parent ] );
|
||||
$messageData = self::$db->get( $this->tableName, [ 'ID', '=', $parent ] );
|
||||
if ( $messageData->count() == 0 ) {
|
||||
Debug::info( 'Message not found.' );
|
||||
return false;
|
||||
@ -71,7 +71,7 @@ class Message extends DatabaseModel {
|
||||
if ( $type !== null ) {
|
||||
$params = array_merge( $params, [ 'AND', $type, '=', $user ] );
|
||||
}
|
||||
$messageData = self::$db->getPaginated( $this->tableName, $params, 'ID', 'DESC', [ 0, 1 ] );
|
||||
$messageData = self::$db->get( $this->tableName, $params, 'ID', 'DESC', [ 0, 1 ] );
|
||||
if ( $messageData->count() != 0 ) {
|
||||
if ( $messageData->first()->recieverDeleted == 0 ) {
|
||||
$message = $messageData->first();
|
||||
@ -93,7 +93,7 @@ class Message extends DatabaseModel {
|
||||
Debug::info( 'Invalid ID' );
|
||||
return false;
|
||||
}
|
||||
$messageData = self::$db->getPaginated( $this->tableName, [ 'ID', '=', $id ] );
|
||||
$messageData = self::$db->get( $this->tableName, [ 'ID', '=', $id ] );
|
||||
if ( $messageData->count() == 0 ) {
|
||||
Debug::info( 'Message not found.' );
|
||||
return false;
|
||||
@ -122,7 +122,7 @@ class Message extends DatabaseModel {
|
||||
} else {
|
||||
$find = $message->ID;
|
||||
}
|
||||
$messageData = self::$db->getPaginated( $this->tableName, [ 'ID', '=', $find, 'OR', 'Parent', '=', $find ], 'ID', 'ASC' )->results();
|
||||
$messageData = self::$db->get( $this->tableName, [ 'ID', '=', $find, 'OR', 'Parent', '=', $find ], 'ID', 'ASC' )->results();
|
||||
Components::set( 'PID', $find );
|
||||
|
||||
if ( $markRead == true ) {
|
||||
@ -138,7 +138,7 @@ class Message extends DatabaseModel {
|
||||
$limit = 10;
|
||||
}
|
||||
$limit = [ 0, $limit ];
|
||||
$messageData = self::$db->getPaginated(
|
||||
$messageData = self::$db->get(
|
||||
$this->tableName,
|
||||
[
|
||||
'parent', '=', 0,
|
||||
@ -154,7 +154,7 @@ class Message extends DatabaseModel {
|
||||
$limit
|
||||
);
|
||||
if ( $messageData->count() == 0 ) {
|
||||
Debug::info( 'No messages found' );
|
||||
Debug::info( 'getInbox: No messages found' );
|
||||
return false;
|
||||
}
|
||||
$filters = [
|
||||
@ -175,7 +175,7 @@ class Message extends DatabaseModel {
|
||||
$limit = 10;
|
||||
}
|
||||
$limit = [ 0, $limit ];
|
||||
$messageData = self::$db->getPaginated(
|
||||
$messageData = self::$db->get(
|
||||
$this->tableName,
|
||||
[
|
||||
'parent', '=', 0,
|
||||
@ -187,7 +187,7 @@ class Message extends DatabaseModel {
|
||||
$limit
|
||||
);
|
||||
if ( $messageData->count() == 0 ) {
|
||||
Debug::info( 'No messages found' );
|
||||
Debug::info( 'getOutbox: No messages found' );
|
||||
return false;
|
||||
}
|
||||
$filters = [
|
||||
|
@ -20,7 +20,6 @@ use TheTempusProject\Houdini\Classes\Views;
|
||||
|
||||
class Messages extends Plugin {
|
||||
public $pluginName = 'TP Messages';
|
||||
public $configName = 'messages';
|
||||
public $pluginAuthor = 'JoeyK';
|
||||
public $pluginWebsite = 'https://TheTempusProject.com';
|
||||
public $modelVersion = '1.0';
|
||||
@ -33,25 +32,24 @@ class Messages extends Plugin {
|
||||
],
|
||||
];
|
||||
private static $loaded = false;
|
||||
|
||||
public function __construct() {
|
||||
// This was taken directly from the main app
|
||||
// load the message template data as part of the template
|
||||
$messages = new Message;
|
||||
Components::set( 'MESSAGE_COUNT', $messages->unreadCount() );
|
||||
if ( $messages->unreadCount() > 0 ) {
|
||||
$messageBadge = Views::simpleView( 'messages.badge' );
|
||||
} else {
|
||||
$messageBadge = '';
|
||||
}
|
||||
Components::set( 'MBADGE', $messageBadge );
|
||||
if ( App::$isLoggedIn ) {
|
||||
Components::set( 'RECENT_MESSAGES', Views::simpleView( 'messages.nav.recentMessagesDropdown', $messages->getInbox( 5 ) ) );
|
||||
} else {
|
||||
Components::set( 'RECENT_MESSAGES', '' );
|
||||
}
|
||||
if ( ! self::$loaded ) {
|
||||
$messages = new Message;
|
||||
Components::set( 'MESSAGE_COUNT', $messages->unreadCount() );
|
||||
if ( $messages->unreadCount() > 0 ) {
|
||||
$messageBadge = Views::simpleView( 'messages.badge' );
|
||||
} else {
|
||||
$messageBadge = '';
|
||||
}
|
||||
Components::set( 'MBADGE', $messageBadge );
|
||||
if ( App::$isLoggedIn ) {
|
||||
Components::set( 'RECENT_MESSAGES', Views::simpleView( 'messages.nav.recentMessagesDropdown', $messages->getInbox( 5 ) ) );
|
||||
} else {
|
||||
Components::set( 'RECENT_MESSAGES', '' );
|
||||
}
|
||||
App::$topNavRight .= '{RECENT_MESSAGES}';
|
||||
App::$topNavRightDropdown .= '<li><a href="{ROOT_URL}messages"><i class="fa fa-fw fa-envelope"></i> Inbox {MBADGE}</a></li>';
|
||||
App::$topNavRightDropdown .= '<li><a href="{ROOT_URL}messages" class="dropdown-item"><i class="fa fa-fw fa-envelope"></i> Inbox {MBADGE}</a></li>';
|
||||
self::$loaded = true;
|
||||
}
|
||||
parent::__construct();
|
||||
|
@ -1 +1 @@
|
||||
<span class="label label-danger">{MESSAGE_COUNT}</span>
|
||||
<span class="badge bg-danger rounded-pill">{MESSAGE_COUNT}</span>
|
@ -1,25 +1,57 @@
|
||||
<form action="" method="post" class="form-horizontal">
|
||||
<legend>New Message</legend>
|
||||
<fieldset>
|
||||
<div class="form-group">
|
||||
<label for="toUser" class="col-lg-1 control-label">To:</label>
|
||||
<div class="col-lg-2">
|
||||
<input class="form-control" type="text" name="toUser" id="toUser" value="{prepopuser}">
|
||||
<div class="container py-4">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-4">
|
||||
<form action="" method="post" class="needs-validation">
|
||||
<legend class="mb-4">New Message</legend>
|
||||
<fieldset>
|
||||
<!-- To User Field -->
|
||||
<div class="mb-3 row">
|
||||
<label for="toUser" class="col-sm-6 col-form-label">To:</label>
|
||||
<div class="col-sm-6">
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
name="toUser"
|
||||
id="toUser"
|
||||
value="{prepopuser}"
|
||||
required>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Subject Field -->
|
||||
<div class="mb-3 row">
|
||||
<label for="subject" class="col-sm-6 col-form-label">Subject:</label>
|
||||
<div class="col-sm-6">
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
name="subject"
|
||||
id="subject"
|
||||
required>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Message Field -->
|
||||
<div class="mb-3">
|
||||
<label for="message" class="form-label">Message:</label>
|
||||
<textarea
|
||||
class="form-control"
|
||||
name="message"
|
||||
id="message"
|
||||
rows="6"
|
||||
maxlength="2000"
|
||||
required></textarea>
|
||||
</div>
|
||||
</fieldset>
|
||||
<input type="hidden" name="token" value="{TOKEN}">
|
||||
<button
|
||||
type="submit"
|
||||
name="submit"
|
||||
value="submit"
|
||||
class="btn btn-primary btn-lg">
|
||||
Send
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="subject" class="col-lg-1 control-label">Subject:</label>
|
||||
<div class="col-lg-2">
|
||||
<input class="form-control" type="text" name="subject" id="subject">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="entry" class="col-lg-3 control-label">Message:</label>
|
||||
<div class="col-lg-6">
|
||||
<textarea class="form-control" name="message" maxlength="2000" rows="10" cols="50" id="message"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
<input type="hidden" name="token" value="{TOKEN}">
|
||||
<button name="submit" value="submit" type="submit" class="btn btn-lg btn-primary center-block">Send</button><br>
|
||||
</form>
|
||||
</div>
|
||||
|
@ -1,7 +1,6 @@
|
||||
<h2>Inbox</h2>
|
||||
{PAGINATION}
|
||||
<form action="{ROOT_URL}messages/delete" method="post">
|
||||
<table class="table table-hover">
|
||||
<table class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width: 20%">From</th>
|
||||
|
8
app/plugins/messages/views/index.html
Normal file
8
app/plugins/messages/views/index.html
Normal file
@ -0,0 +1,8 @@
|
||||
<div class="context-main context-main-bg col-10 offset-1 my-3 p-3">
|
||||
<div class="my-3 p-3">
|
||||
{message_inbox}
|
||||
</div>
|
||||
<div class="my-3 p-3">
|
||||
{message_outbox}
|
||||
</div>
|
||||
</div>
|
@ -1,14 +1,14 @@
|
||||
<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="col-sm-12 col-md-6 col-lg-6 col-sm-offset-0 col-md-offset-3 col-lg-offset-3 top-pad" >
|
||||
<div class="card panel-primary">
|
||||
{LOOP}
|
||||
{SINGLE}
|
||||
<div class="panel-heading">
|
||||
<h3 class="panel-title">{subject}</h3>
|
||||
<div class="card-header">
|
||||
<h3 class="card-title">{subject}</h3>
|
||||
</div>
|
||||
{/SINGLE}
|
||||
<div class="panel-body">
|
||||
<div class="card-body">
|
||||
<div class="row">
|
||||
<div class="col-md-3 col-lg-3 " align="center">
|
||||
<a href="{ROOT_URL}home/profile/{userFrom}">{userFrom}</a><br>
|
||||
@ -23,10 +23,10 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel-footer">
|
||||
<div class="card-footer">
|
||||
{ADMIN}
|
||||
{ID}
|
||||
<span class="pull-right">
|
||||
<span class="float-right">
|
||||
{DTC}{sent}{/DTC}
|
||||
</span>
|
||||
{/ADMIN}
|
||||
|
@ -1,43 +1,37 @@
|
||||
<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="context-main context-main-bg col-8 offset-2 my-3 p-3">
|
||||
<div class="col-sm-12 col-md-6 col-lg-6 col-sm-offset-0 col-md-offset-3 col-lg-offset-3 top-pad" >
|
||||
<div class="card panel-primary">
|
||||
{LOOP}
|
||||
{SINGLE}
|
||||
<div class="panel-heading">
|
||||
<h3 class="panel-title">{subject}</h3>
|
||||
</div>
|
||||
{/SINGLE}
|
||||
<div class="panel-body">
|
||||
<div class="row">
|
||||
<div class="col-md-3 col-lg-3 " align="center">
|
||||
<a href="{ROOT_URL}home/profile/{userFrom}">{userFrom}</a><br>
|
||||
<img alt="User Pic" src="{ROOT_URL}{fromAvatar}" class="img-circle img-responsive">
|
||||
{SINGLE}
|
||||
<div class="card-header context-main-bg">
|
||||
<h5 class="card-title context-main">{subject}</h5>
|
||||
</div>
|
||||
<div class=" col-md-9 col-lg-9 ">
|
||||
<table class="table table-user-information">
|
||||
<tbody>
|
||||
<td>{message}</td>
|
||||
</tbody>
|
||||
</table>
|
||||
{/SINGLE}
|
||||
<div class="card-body context-second-bg">
|
||||
<div class="row">
|
||||
<div class="col-md-3 col-lg-3 text-center">
|
||||
<a href="{ROOT_URL}home/profile/{userFrom}">{userFrom}</a><br>
|
||||
<img alt="User Pic" src="{ROOT_URL}{fromAvatar}" class="img-circle img-fluid">
|
||||
</div>
|
||||
<div class=" col-md-9 col-lg-9 context-main">
|
||||
{message}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel-footer">
|
||||
{ADMIN}
|
||||
{ID}
|
||||
<span class="pull-right">
|
||||
{DTC}{sent}{/DTC}
|
||||
</span>
|
||||
{/ADMIN}
|
||||
</div>
|
||||
<div class="card-footer context-main-bg">
|
||||
{ADMIN}
|
||||
{ID}
|
||||
<span class="float-right">
|
||||
{DTC}{sent}{/DTC}
|
||||
</span>
|
||||
{/ADMIN}
|
||||
</div>
|
||||
{/LOOP}
|
||||
</div>
|
||||
<form action="{ROOT_URL}messages/reply" method="post">
|
||||
<input type="hidden" name="token" value="{TOKEN}">
|
||||
<input type="hidden" name="messageID" value="{PID}">
|
||||
<button name="submit" value="reply" type="submit" class="btn btn-sm btn-primary">Reply</button>
|
||||
<button name="submit" value="reply" type="submit" class="btn btn-md btn-primary my-4">Reply</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
@ -1,41 +1,46 @@
|
||||
<li class="dropdown">
|
||||
<a href="#" class="dropdown-toggle" data-toggle="dropdown"><i class="glyphicon glyphicon-envelope"></i>{MBADGE}</a>
|
||||
<ul class="dropdown-menu message-dropdown">
|
||||
<li class="message-header">
|
||||
<div class="media">
|
||||
<div class="media-body text-center" style="padding-bottom: 10px; padding-top: 10px">
|
||||
{MESSAGE_COUNT} unread message(s) total
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
<div class="dropdown nav-item mx-2">
|
||||
<a
|
||||
href="#"
|
||||
class="d-flex align-items-center text-white text-decoration-none dropdown-toggle"
|
||||
id="messagesDropdown"
|
||||
data-bs-toggle="dropdown"
|
||||
aria-haspopup="true"
|
||||
aria-expanded="false">
|
||||
<i class="fa fa-fw fa-envelope"></i><span class="">{MBADGE}</span>
|
||||
</a>
|
||||
<ul class="dropdown-menu dropdown-menu-dark dropdown-menu-end text-small shadow" aria-labelledby="messagesDropdown">
|
||||
{LOOP}
|
||||
<li class="message-preview">
|
||||
<a href="{ROOT_URL}messages/view/{ID}">
|
||||
<div class="media">
|
||||
<span class="pull-left">
|
||||
<img class="media-object avatar-round-40" src="{ROOT_URL}{fromAvatar}" alt="">
|
||||
</span>
|
||||
<div class="media-body">
|
||||
<h5 class="media-heading"><strong>{userFrom}</strong>
|
||||
</h5>
|
||||
<p class="small text-muted"><i class="fa fa-clock-o"></i> {DTC}{lastReply}{/DTC}</p>
|
||||
{summary}
|
||||
<!-- Message Item -->
|
||||
<li>
|
||||
<a href="{ROOT_URL}messages/view/{ID}" class="dropdown-item">
|
||||
<div class="d-flex">
|
||||
<h5 class="media-heading text-start">
|
||||
<img class="" style="width: 40px;" src="{ROOT_URL}{fromAvatar}" alt="">
|
||||
<strong>{userFrom}</strong>
|
||||
</h5>
|
||||
<div class="text-end">
|
||||
<div class="media-body">
|
||||
<p class="small text-muted mb-1"><i class="fa fa-clock-o me-1"></i> {DTC}{lastReply}{/DTC}</p>
|
||||
<span>{summary}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</li>
|
||||
{/LOOP}
|
||||
{ALT}
|
||||
<li class="message-preview">
|
||||
<div class="media">
|
||||
<div class="media-body text-center" style="padding-bottom: 10px; padding-top: 10px">
|
||||
<h5 class="media-heading"><strong>No Messages</strong></h5>
|
||||
</div>
|
||||
</div>
|
||||
<li class="px-3 text-center">
|
||||
<strong>No Messages</strong>
|
||||
</li>
|
||||
{/ALT}
|
||||
<li class="message-footer text-center">
|
||||
<a href="{ROOT_URL}messages">Read All New Messages</a>
|
||||
<!-- Footer -->
|
||||
<li>
|
||||
<hr class="dropdown-divider">
|
||||
</li>
|
||||
<li>
|
||||
<a href="/messages" class="dropdown-item text-center">
|
||||
Read All New Messages
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
</div>
|
@ -1,7 +1,6 @@
|
||||
<h2>Outbox</h2>
|
||||
{PAGINATION}
|
||||
<form action="{ROOT_URL}messages/delete" method="post">
|
||||
<table class="table table-hover">
|
||||
<table class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width: 20%">To</th>
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user