Initial commit

This commit is contained in:
Joey Kimsey
2024-08-04 21:15:59 -04:00
parent c9d1fb983f
commit 0d469501ee
695 changed files with 70184 additions and 71 deletions

67
.gitignore vendored Normal file
View File

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

127
.php-cs-fixer.php Normal file
View File

@ -0,0 +1,127 @@
<?php
$finder = PhpCsFixer\Finder::create()
//->exclude('somedir')
//->notPath('src/Symfony/Component/Translation/Tests/fixtures/resources.php'
->in(__DIR__)
;
$config = new \PhpCsFixer\Config();
return $config->setRules([
'@PSR2' => true,
'array_indentation' => true,
'array_syntax' => ['syntax' => 'short'],
'combine_consecutive_unsets' => true,
'class_attributes_separation' => ['elements' => ['method' => 'one',]],
'multiline_whitespace_before_semicolons' => false,
'single_quote' => true,
'strict_param' => false,
'binary_operator_spaces' => [
'operators' => [
// '=>' => 'align',
// '=' => 'align'
]
],
// 'blank_line_after_opening_tag' => true,
// 'blank_line_before_statement' => true,
'braces' => [
'allow_single_line_closure' => true,
'position_after_functions_and_oop_constructs' => 'same'
],
// 'cast_spaces' => true,
// 'class_definition' => array('singleLine' => true),
'concat_space' => ['spacing' => 'one'],
// 'declare_equal_normalize' => true,
// 'function_typehint_space' => true,
// 'single_line_comment_style' => ['comment_types' => ['hash']],
// 'include' => true,
// 'lowercase_cast' => true,
// 'native_function_casing' => true,
// 'new_with_braces' => true,
// 'no_blank_lines_after_class_opening' => true,
// 'no_blank_lines_after_phpdoc' => true,
// 'no_blank_lines_before_namespace' => true,
'no_empty_comment' => true,
'no_empty_phpdoc' => true,
// 'no_empty_statement' => true,
'no_extra_blank_lines' => [
'tokens' => [
// 'curly_brace_block',
// 'extra',
// 'parenthesis_brace_block',
// 'square_brace_block',
// 'throw',
// 'use',
]
],
'no_leading_import_slash' => true,
'no_leading_namespace_whitespace' => true,
'no_mixed_echo_print' => ['use' => 'echo'],
'no_multiline_whitespace_around_double_arrow' => true,
'no_short_bool_cast' => true,
'no_singleline_whitespace_before_semicolons' => true,
'no_spaces_around_offset' => ['positions' => ['outside']],
'no_trailing_comma_in_singleline' => ['elements' => ['arguments', 'array_destructuring', 'array', 'group_import']],
'spaces_inside_parentheses' => ['space' => 'single'],
// 'no_spaces_inside_parenthesis' => false,
'control_structure_braces' => true,
'curly_braces_position' => [
'control_structures_opening_brace' => 'same_line',
'functions_opening_brace' => 'same_line',
'classes_opening_brace' => 'same_line',
],
// need to add space after array declaration
// need to put each element on a line by itself when an array is multi line
'no_unneeded_control_parentheses' => true,
'no_unused_imports' => true,
'no_whitespace_before_comma_in_array' => true,
'no_whitespace_in_blank_line' => true,
'normalize_index_brace' => true,
// 'object_operator_without_whitespace' => true,
// 'php_unit_fqcn_annotation' => true,
// 'phpdoc_align' => true,
// 'phpdoc_annotation_without_dot' => true,
// 'phpdoc_indent' => true,
// 'phpdoc_inline_tag' => true,
// 'phpdoc_no_access' => true,
// 'phpdoc_no_alias_tag' => true,
// 'phpdoc_no_empty_return' => true,
// 'phpdoc_no_package' => true,
// 'phpdoc_no_useless_inheritdoc' => true,
// 'phpdoc_return_self_reference' => true,
// 'phpdoc_scalar' => true,
// 'phpdoc_separation' => true,
// 'phpdoc_single_line_var_spacing' => true,
// 'phpdoc_summary' => true,
// 'phpdoc_to_comment' => true,
// 'phpdoc_trim' => true,
// 'phpdoc_types' => true,
'phpdoc_var_without_name' => false,
'increment_style' => ['style' => 'post'],
'return_type_declaration' => true,
// 'self_accessor' => true, // risky
'short_scalar_cast' => true,
'single_class_element_per_statement' => true,
'standardize_not_equals' => true,
'ternary_operator_spaces' => true,
'trailing_comma_in_multiline' => true,
'trim_array_spaces' => false,
'unary_operator_spaces' => true,
'whitespace_after_comma_in_array' => true,
'single_blank_line_at_eof' => true
])
->setLineEnding("\n")
;

47
CODE_OF_CONDUCT.md Normal file
View File

@ -0,0 +1,47 @@
# Contributor Covenant Code of Conduct
## Our Pledge
In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.
## Our Standards
Examples of behavior that contributes to creating a positive environment include:
* Using welcoming and inclusive language
* Being respectful of differing viewpoints and experiences
* Gracefully accepting constructive criticism
* Focusing on what is best for the community
* Showing empathy towards other community members
Examples of unacceptable behavior by participants include:
* The use of sexualized language or imagery and unwelcome sexual attention or advances
* Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or electronic address, without explicit permission
* Other conduct which could reasonably be considered inappropriate in a professional setting
## Our Responsibilities
Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
## Scope
This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at webmaster@thetempusproject.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version]
[homepage]: http://contributor-covenant.org
[version]: http://contributor-covenant.org/version/1/4/

135
CONTRIBUTING.md Normal file
View File

@ -0,0 +1,135 @@
# Contribution Guidelines for TheTempusProject
Contributing to TheTempusProject is completely voluntary and should follow all of the guidelines listed here in order to ensure the highest probability of acceptance. It is highly recommended to use a php linter to automate more of this process. The project is maintained on github and all contributions need to be submitted via pull request to their specific repository under the `dev` branch. In order to contribute, simply follow the instructions for [creating a pull request](#creating-a-pull-request) below.
## Pull Request Requirements
- All revisions must follow TTP naming conventions (see [Naming Conventions](#naming-conventions) Section)
- Include a clear and concise explanation of the features or changes included in your revision listed by file.
- All code must follow [PSR 2](http://www.php-fig.org/psr/psr-2/) standards
- prefer the use of [] for arrays over array()
- All functions must be documented with the exception of controller methods (see [Documentation](#documentation) Section)
- Controller methods may be doc-blocked when necessary for clarity (see [Documentation](#documentation) Section)
- All new Classes must include a class level doc-block (see [Documentation](#documentation) Section)
- Any new dependencies will have a longer validation process and should be accompanied by the required information (see [Dependencies](#dependencies) Section)
## Naming Conventions
- File names are to be lower case
- All class names must be upper case
- Any data being stored as a file must be saved in the app directory (with the exception of config which should be stored under config/)
- Controllers must have a constructor and destructor using the constructor and destructor methods found in resources/
- Views must be named using lowerCamelCase
## Dependencies
Whenever a dependency is updated or added, pull requests must include a section that answers the following questions.
- Why is this dependency required
- Could this be reasonably accomplished within the app by implementing new features in a later version? explain.
- What is the latest stable version that can be used
- What features are absolutely necessary for your feature or modification to work
## Documentation
### Classes
New classes must be prefaced with a doc-block following this style:
```
/**
* app/controllers/admin/admin.php
*
* This is the admin controller.
*
* @version 3.0
* @author Joey Kimsey <Joey@thetempusproject.com>
* @link https://TheTempusProject.com
* @license https://opensource.org/licenses/MIT [MIT LICENSE]
*/
```
From top to bottom:
- Filename on the second line
- A description for the file
- The TTP version this file was built for
`@version 1.0`
- The Authors name or alias and email
`@author first last <email@link.com>`
- A copy of the MIT license
`@license https://opensource.org/licenses/MIT [MIT LICENSE]`
- May include a link for more information
`@link http://link.com`
### Functions
Functions must be prefaced with a doc-block following this style:
```
/**
* Intended as a self-destruct session. If the specified session does not
* exist, it is created. If the specified session does exist, it will be
* destroyed and returned.
*
* @param string $name - Session name to be created or checked
* @param string $string - The string to be used if session needs to be
* created. (optional)
*
* @return bool|string - Returns bool if creating, and a string if the
* check is successful.
*/
```
From top to bottom:
- There must be a description of the functions intended usage on the second line
- All parameters should be documented like this
`@param [type] $name - description`
- Any function with a return statement must also be documented as such
`@return [type] - description`
## Creating a Pull Request
This is a simple explanation of how to create a pull request for changes to TheTempusProject. You can find a detailed walk-through on how to [create a pull request](https://help.github.com/articles/creating-a-pull-request/) on github.
1. First ensure you have followed all the contributing guidelines
2. Squash your merge into a single revision. This will make it easier to view the changes as a whole.
3. You can submit a pull request [here](https://github.com/TheTempusProject/TheTempusProject/compare)
4. Please submit all pull requests to the dev branch or they will be ignored.
add spaces after everything
avoid "return;" just make the previous line the return
use []
do not use array()
do not use (array)
do not add useless variables
if you are going to set something or check if its empty, just never set it to begin with, don't set it to null

21
LICENSE Normal file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2024 Joey Kimsey
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

423
README.md
View File

@ -1,93 +1,374 @@
# TheTempusProject # The Tempus Project
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
## Getting started need to track points once a week
To make it easy for you to get started with GitLab, here's a list of recommended next steps. a huge table tracks points day to day then we add and erase the old data, or move it to historical...
Already a pro? Just edit this README.md and make it your own. Want to make it easy? [Use the template at the bottom](#editing-this-readme)! ## Rapid Prototyping Framework
## Add your files ### Developer(s): Joey Kimsey
- [ ] [Create](https://docs.gitlab.com/ee/user/project/repository/web_editor.html#create-a-file) or [upload](https://docs.gitlab.com/ee/user/project/repository/web_editor.html#upload-a-file) files 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.
- [ ] [Add files using the command line](https://docs.gitlab.com/ee/gitlab-basics/add-file.html#add-a-file-using-the-command-line) or push an existing Git repository with the following command:
``` **Notice: This code is in _still_ not production ready. This framework is provided as is, use at your own risk.**
cd existing_repo 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.
git remote add origin https://170-187-142-254.ip.linodeusercontent.com/the-tempus-project/thetempusproject.git
git branch -M main
git push -uf origin main
```
## Integrate with your tools 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
- [ ] [Set up project integrations](https://170-187-142-254.ip.linodeusercontent.com/the-tempus-project/thetempusproject/-/settings/integrations) ## Features
## Collaborate with your team 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.
- [ ] [Invite team members and collaborators](https://docs.gitlab.com/ee/user/project/members/) Compatibility with both Apache and NGINX.
- [ ] [Create a new merge request](https://docs.gitlab.com/ee/user/project/merge_requests/creating_merge_requests.html) Built with Bootstrap with a focus on mobile compatibility.
- [ ] [Automatically close issues from merge requests](https://docs.gitlab.com/ee/user/project/issues/managing_issues.html#closing-issues-automatically) Incredibly easy to set-up, deploy, and develop with.
- [ ] [Enable merge request approvals](https://docs.gitlab.com/ee/user/project/merge_requests/approvals/)
- [ ] [Set auto-merge](https://docs.gitlab.com/ee/user/project/merge_requests/merge_when_pipeline_succeeds.html)
## Test and Deploy
Use the built-in continuous integration in GitLab.
- [ ] [Get started with GitLab CI/CD](https://docs.gitlab.com/ee/ci/quick_start/index.html)
- [ ] [Analyze your code for known vulnerabilities with Static Application Security Testing (SAST)](https://docs.gitlab.com/ee/user/application_security/sast/)
- [ ] [Deploy to Kubernetes, Amazon EC2, or Amazon ECS using Auto Deploy](https://docs.gitlab.com/ee/topics/autodevops/requirements.html)
- [ ] [Use pull-based deployments for improved Kubernetes management](https://docs.gitlab.com/ee/user/clusters/agent/)
- [ ] [Set up protected environments](https://docs.gitlab.com/ee/ci/environments/protected_environments.html)
***
# Editing this README
When you're ready to make this README your own, just edit this file and use the handy template below (or feel free to structure it however you want - this is just a starting point!). Thanks to [makeareadme.com](https://www.makeareadme.com/) for this template.
## Suggestions for a good README
Every project is different, so consider which of these sections apply to yours. The sections used in the template are suggestions for most open source projects. Also keep in mind that while a README can be too long and detailed, too long is better than too short. If you think your README is too long, consider utilizing another form of documentation rather than cutting out information.
## Name
Choose a self-explaining name for your project.
## Description
Let people know what your project can do specifically. Provide context and add a link to any reference visitors might be unfamiliar with. A list of Features or a Background subsection can also be added here. If there are alternatives to your project, this is a good place to list differentiating factors.
## Badges
On some READMEs, you may see small images that convey metadata, such as whether or not all the tests are passing for the project. You can use Shields to add some to your README. Many services also have instructions for adding a badge.
## Visuals
Depending on what you are making, it can be a good idea to include screenshots or even a video (you'll frequently see GIFs rather than actual videos). Tools like ttygif can help, but check out Asciinema for a more sophisticated method.
## Installation ## Installation
Within a particular ecosystem, there may be a common way of installing things, such as using Yarn, NuGet, or Homebrew. However, consider the possibility that whoever is reading your README is a novice and would like more guidance. Listing specific steps helps remove ambiguity and gets people to using your project as quickly as possible. If it only runs in a specific context like a particular programming language version or operating system or has dependencies that have to be installed manually, also add a Requirements subsection.
## Usage Preferred method for installation is using composer.
Use examples liberally, and show the expected output if you can. It's helpful to have inline the smallest example of usage that you can demonstrate, while providing links to more sophisticated examples if they are too long to reasonably include in the README.
## Support ### Manually
Tell people where they can go to for help. It can be any combination of an issue tracker, a chat room, an email address, etc.
## Roadmap ### Docker
If you have ideas for releases in the future, it is a good idea to list them in the README.
## Contributing ### Composer
State if you are open to contributions and what your requirements are for accepting them.
For people who want to make changes to your project, it's helpful to have some documentation on how to get started. Perhaps there is a script that they should run or some environment variables that they need to set. Make these steps explicit. These instructions could also be useful to your future self. 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:
`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.
You can also document commands to lint the code or run tests. These steps help to ensure high code quality and reduce the likelihood that the changes inadvertently break something. Having instructions for running tests is especially helpful if it requires external setup, such as starting a Selenium server for testing in a browser. #### Apache
## Authors and acknowledgment #### NGINX
Show your appreciation to those who have contributed to the project.
## License #### Docker-Compose
For open source projects, say how it is licensed.
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.
**Do not forget to remove install.php once you have finished installation!**
#### Currently being developed
- [ ] Adding documentation
- [ ] Unit tests
#### Future updates
- [ ] Expansion of PDO to allow different database types
- [ ] Update installer to account for updates.
- [ ] 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.
## Project status
If you have run out of energy or time for your project, put a note at the top of the README saying that development has slowed down or stopped completely. Someone may choose to fork your project or volunteer to step in as a maintainer or owner, allowing your project to keep going. You can also make an explicit request for maintainers.

View File

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

View File

@ -0,0 +1,32 @@
<?php
/**
* app/classes/admin_controller.php
*
* This is the base admin controller. Every other admin controller should
* extend this class.
*
* @version 3.0
* @author Joey Kimsey <Joey@thetempusproject.com>
* @link https://TheTempusProject.com
* @license https://opensource.org/licenses/MIT [MIT LICENSE]
*/
namespace TheTempusProject\Classes;
use TheTempusProject\Houdini\Classes\Template;
use TheTempusProject\TheTempusProject as App;
use TheTempusProject\Hermes\Functions\Redirect;
use TheTempusProject\Bedrock\Functions\Session;
class ApiController extends Controller {
public function __construct() {
parent::__construct();
if ( ! App::verifyApiRequest() ) {
Session::flash( 'error', 'You do not have permission to view this page.' );
return Redirect::home();
}
Template::noFollow();
Template::noIndex();
Template::addHeader( 'Content-Type: application/json; charset=utf-8' );
Template::setTemplate( 'api' );
}
}

71
app/classes/config.php Normal file
View File

@ -0,0 +1,71 @@
<?php
/**
* classes/config.php
*
* This class handles all the hard-coded configurations.
*
* @version 3.0
* @author Joey Kimsey <Joey@thetempusproject.com>
* @link https://TheTempusProject.com/Core
* @license https://opensource.org/licenses/MIT [MIT LICENSE]
*/
namespace TheTempusProject\Classes;
use TheTempusProject\Houdini\Classes\Forms;
use TheTempusProject\Canary\Canary as Debug;
use TheTempusProject\Bedrock\Functions\Check;
use TheTempusProject\Bedrock\Functions\Input;
use TheTempusProject\Bedrock\Classes\Config as BedrockConfig;
class Config extends BedrockConfig {
public static function getFieldEditHtml( $name, $includeProtected = false ) {
// @todo: includeProtected is unused here
$node = self::get( $name );
if ( empty( $node ) ) {
return;
}
if ( true === $node['protected'] ) {
return;
}
$fieldname = str_ireplace( '/', '-', $name );
$html = Forms::getFormFieldHtml(
$fieldname,
$node['pretty'],
$node['type'],
$node['value'],
);
return $html;
}
public static function getCategoryEditHtml( $category ) {
$html = '';
if ( self::$config === false ) {
Debug::warn( 'Config not loaded.' );
return;
}
if ( empty( self::$config[$category] ) ) {
Debug::warn( "Config category not found: $category" );
return;
}
$categoryHeader = '<div class="form-group"><label>' . ucfirst( $category ) . ':</label><hr></div>';
foreach ( self::$config[$category] as $field => $node ) {
$html .= self::getFieldEditHtml( $category . '/' . $field );
}
if ( !empty( $html ) ) {
$html = $categoryHeader . $html;
}
return $html;
}
public static function getEditHtml() {
if ( self::$config === false ) {
Debug::warn( 'Config not loaded.' );
return;
}
$html = '';
foreach ( self::$config as $category => $fields ) {
$html .= self::getCategoryEditHtml( $category );
}
return $html;
}
}

View File

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

View File

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

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

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

77
app/classes/forms.php Normal file
View File

@ -0,0 +1,77 @@
<?php
/**
* app/classes/forms.php
*
* This class is used in conjunction with TheTempusProject\Bedrock\Classes\Check
* to house complete form verification. You can utilize the
* error reporting to easily define exactly what feedback you
* would like to give.
*
* @version 3.0
* @author Joey Kimsey <Joey@thetempusproject.com>
* @link https://TheTempusProject.com
* @license https://opensource.org/licenses/MIT [MIT LICENSE]
*/
namespace TheTempusProject\Classes;
use TheTempusProject\Bedrock\Functions\Check;
use TheTempusProject\Canary\Canary as Debug;
class Forms extends Check {
private static $formHandlers = [];
public static function check( $formName ) {
if ( empty( self::$formHandlers[ $formName ] ) ) {
Debug::error( "Form not found: $formName" );
return false;
}
$handler = self::$formHandlers[ $formName ];
return call_user_func_array( [ $handler['class'], $handler['method'] ], $handler['params'] );
}
public static function addHandler( $formName, $class, $method, $params = [] ) {
if ( !empty( self::$formHandlers[ $formName ] ) ) {
return false;
}
self::$formHandlers[$formName] = [
'class' => $class,
'method' => $method,
'params' => $params,
];
}
/**
* Checks username formatting.
*
* Requirements:
* - 4 - 16 characters long
* - must only contain numbers and letters: [A - Z] , [a - z], [0 - 9]
*
* @param string $data - The string being tested.
*
* @return boolean
*/
public static function checkUsername( $data ) {
if ( strlen( $data ) > 16 ) {
self::addError( 'Username must be be 4 to 16 numbers or letters.', $data );
return false;
}
if ( strlen( $data ) < 4 ) {
self::addError( 'Username must be be 4 to 16 numbers or letters.', $data );
return false;
}
if ( !ctype_alnum( $data ) ) {
self::addError( 'Username must be be 4 to 16 numbers or letters.', $data );
return false;
}
return true;
}
public static function date( $data ) {
if ( strtotime( $data ) == false ) {
self::addError( 'Username must be be 4 to 16 numbers or letters.', $data );
return false;
}
return true;
}
}

640
app/classes/installer.php Normal file
View File

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

244
app/classes/permissions.php Normal file
View File

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

493
app/classes/plugin.php Normal file
View File

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

297
app/classes/preferences.php Normal file
View File

@ -0,0 +1,297 @@
<?php
/**
* app/classes/preferences.php
*
* This class handles all the hard-coded preferences.
*
* @version 3.0
* @author Joey Kimsey <Joey@thetempusproject.com>
* @link https://TheTempusProject.com
* @license https://opensource.org/licenses/MIT [MIT LICENSE]
*/
namespace TheTempusProject\Classes;
use TheTempusProject\Houdini\Classes\Issues;
use TheTempusProject\Houdini\Classes\Forms;
use TheTempusProject\Canary\Canary as Debug;
use TheTempusProject\Bedrock\Functions\Check;
use TheTempusProject\Bedrock\Functions\Upload;
use TheTempusProject\Bedrock\Functions\Input;
use TheTempusProject\TheTempusProject as App;
class Preferences {
public static $preferences = false;
private static $location = false;
private static $initialized = false;
/**
* Default constructor which will attempt to load the preferences from the location specified.
*
* @param {string} [$location]
* @return {null|object}
*/
public function __construct( $location = '' ) {
if ( self::$initialized !== false ) {
Debug::log( 'Preferences already initialized.' );
return $this;
}
if ( empty( $location ) ) {
$location = PREFERENCES_JSON;
}
self::$initialized = $this->load( $location );
if ( self::$initialized !== false ) {
Debug::log( 'Preferences initialization succeeded.' );
return $this;
}
Debug::warn( 'Preferences initialization failed.' );
}
/**
* Attempts to retrieve then set the preferences from a file.
* @note This function will reset the preferences every time it is used.
*
* @param {string} [$location]
* @return {bool}
*/
public function load( $location ) {
self::$preferences = $this->getPrefsFile( $location );
self::$location = $location;
if ( self::$preferences === false || empty( self::$preferences ) ) {
Debug::warn( 'Preferences load failed.' );
return false;
}
Debug::log( 'Preferences load succeeded.' );
return true;
}
/**
* Opens and decodes the preferences json from the location provided.
*
* @param {string} [$location]
* @return {bool|array}
*/
public function getPrefsFile( $location ) {
if ( file_exists( $location ) ) {
Debug::debug( "Preferences json found: $location" );
return json_decode( file_get_contents( $location ), true );
} else {
Debug::warn( "Preferences json not found: $location" );
return false;
}
}
/**
* Retrieves the preference option for $name.
*
* @param {string} [$name]
* @return {WILD}
*/
public static function get( $name ) {
if ( self::$preferences === false ) {
return Debug::warn( 'Preferences not loaded.' );
}
if ( isset( self::$preferences[$name] ) ) {
return self::$preferences[$name];
}
return Debug::warn( "Preference not found: $name" );
}
public function getType( $name ) {
$pref = $this->get( $name );
if ( empty( $pref ) || empty( $pref['type'] ) ) {
return Debug::warn( "Preference Type not found: $name" );
}
return $pref['type'];
}
public function getDefault( $name ) {
$pref = $this->get( $name );
if ( empty( $pref ) || empty( $pref['default'] ) ) {
return Debug::warn( "Preference Default not found: $name" );
}
return $pref['default'];
}
public function getOptions( $name ) {
$pref = $this->get( $name );
if ( empty( $pref ) || empty( $pref['options'] ) ) {
return Debug::warn( "Preference Options not found: $name" );
}
return $pref['options'];
}
public function getPrettyName( $name ) {
$pref = $this->get( $name );
if ( empty( $pref ) || empty( $pref['pretty'] ) ) {
return Debug::warn( "Preference Pretty Name not found: $name" );
}
return $pref['pretty'];
}
/**
* Saves the current preferences.
*
* @param {bool} [$default] - Whether or not to save a default copy.
* @return {bool}
*/
public function save( $backup = true ) {
if ( self::$preferences === false ) {
Debug::warn( 'Preferences not loaded.' );
return false;
}
if ( self::$location === false ) {
Debug::warn( 'Preferences location not set.' );
return false;
}
if ( $backup ) {
$locationArray = explode( '.', self::$location );
$locationArray[] = 'bak';
$backupLoction = implode( '.', $locationArray );
if ( !file_put_contents( $backupLoction, json_encode( self::$preferences ) ) ) {
return false;
}
}
if ( file_put_contents( self::$location, json_encode( self::$preferences ) ) ) {
return true;
}
return false;
}
/**
* Adds a new preference to the $preferences array.
*
* @param {string} [$name]
* @param {string} [$value]
* @return {bool}
*/
public function add( $name, $details ) {
if ( !Check::simpleName( $name ) ) {
Debug::error( "Preference name invalid: $name" );
return false;
}
if ( isset( self::$preferences[$name] ) ) {
Debug::warn( "Preference already exists: $name" );
return false;
}
if ( self::$preferences === false ) {
self::$preferences = [];
}
$prefsArray = $this->normalizePreferenceArray( $name, $details );
if ( false === $prefsArray ) {
Debug::warn( 'Preference array failed to load properly.' );
return false;
}
self::$preferences[$name] = $prefsArray;
return true;
}
public function getFormHtml( $populated = [] ) {
$form = '';
foreach ( self::$preferences as $name => $details ) {
$tempPrefsArray = $this->normalizePreferenceArray( $name, $details );
if ( isset( $populated[ $name ] ) ) {
$tempPrefsArray['default'] = $populated[$name];
}
$form .= Forms::getFormFieldHtml( $name, $tempPrefsArray['pretty'], $tempPrefsArray['type'], $tempPrefsArray['default'], $tempPrefsArray['options'] );
}
return $form;
}
public function convertFormToArray( $fillMissing = true, $defaultsOnly = true ) {
$prefsArray = [];
foreach ( self::$preferences as $name => $details ) {
if ( true === $fillMissing ) {
if ( true !== $defaultsOnly && !empty( App::$activePrefs[$name] ) ) {
$prefsArray[$name] = App::$activePrefs[$name];
} else {
$prefsArray[$name] = $details['default'];
}
}
if ( Input::exists( $name ) ) {
$prefsArray[$name] = Input::post( $name );
}
if ( 'file' == $details['type'] ) {
if ( Input::exists( $name ) ) {
$folder = 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 {
$route = str_replace( APP_ROOT_DIRECTORY, '', $folder );
$prefsArray[$name] = $route . Upload::last();
}
}
}
if ( 'checkbox' == $details['type'] ) {
if ( Input::exists( $name ) ) {
$prefsArray[$name] = true;
} else {
$prefsArray[$name] = false;
}
}
}
return $prefsArray;
}
public function getDefaultPreferencesArray() {
if ( self::$preferences === false ) {
Debug::warn( 'Preferences not loaded.' );
return false;
}
$prefsArray = [];
foreach ( self::$preferences as $name => $details ) {
$tempPrefsArray = $this->normalizePreferenceArray( $name, $details );
$prefsArray[$name] = $tempPrefsArray['default'];
}
return $prefsArray;
}
public function normalizePreferenceArray( $name, $prefsArray ) {
if ( !is_array( $prefsArray ) ) {
Debug::warn( 'Preference array was not an array.' );
return false;
}
if ( !isset( $prefsArray['type'] ) ) {
if ( isset( $prefsArray['options'] ) ) {
$prefsArray['type'] = 'select';
} else {
$prefsArray['type'] = 'text';
}
}
if ( !isset( $prefsArray['pretty'] ) ) {
$prefsArray['pretty'] = ucfirst( $name );
}
if ( !isset( $prefsArray['default'] ) ) {
$prefsArray['default'] = '';
}
if ( ( empty( $prefsArray['avatar'] ) ) || ( $prefsArray['avatar'] == 'defaultAvatar.png' ) ) {
$prefsArray['avatar'] = IMAGE_DIRECTORY . 'defaultAvatar.png';
}
if ( !isset( $prefsArray['options'] ) ) {
$prefsArray['options'] = null;
}
return $prefsArray;
}
/**
* Removes an existing preference from the $preferences array.
*
* @param {string} [$name]
* @param {bool} [$save]
* @return {bool}
*/
public function remove( $name, $save = false ) {
if ( self::$preferences === false ) {
Debug::warn( 'Preferences not loaded.' );
return false;
}
if ( !isset( self::$preferences[$name] ) ) {
Debug::error( "Preference does not exist: $name" );
return false;
}
unset( self::$preferences[$name] );
if ( $save === true ) {
return $this->save( true );
}
return true;
}
}

135
app/config/constants.php Normal file
View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,52 @@
<?php
/**
* app/controllers/admin/home.php
*
* This is the admin dashboard controller.
*
* @version 3.0
* @author Joey Kimsey <Joey@thetempusproject.com>
* @link https://TheTempusProject.com
* @license https://opensource.org/licenses/MIT [MIT LICENSE]
*/
namespace TheTempusProject\Controllers\Admin;
use TheTempusProject\Houdini\Classes\Views;
use TheTempusProject\Houdini\Classes\Components;
use TheTempusProject\Classes\AdminController;
use TheTempusProject\Models\User;
use TheTempusProject\Plugins\Comments;
use TheTempusProject\Plugins\Blog;
class Home extends AdminController {
public static $user;
public static $comments;
public static $posts;
public function __construct() {
parent::__construct();
self::$title = 'Admin - Home';
}
public function index() {
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 );
}
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 );
}
self::$user = new User;
$users = Views::simpleView( 'admin.dashboard.users', self::$user->recent( 5 ) );
Components::set( 'userDash', $users );
Views::view( 'admin.dashboard.dash' );
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,156 @@
<?php
/**
* app/controllers/admin/users.php
*
* This is the users admin controller.
*
* @version 3.0
* @author Joey Kimsey <Joey@thetempusproject.com>
* @link https://TheTempusProject.com
* @license https://opensource.org/licenses/MIT [MIT LICENSE]
*/
namespace TheTempusProject\Controllers\Admin;
use TheTempusProject\Bedrock\Functions\Check;
use TheTempusProject\Bedrock\Functions\Input;
use TheTempusProject\Bedrock\Functions\Code;
use TheTempusProject\Bedrock\Functions\Hash;
use TheTempusProject\Houdini\Classes\Navigation;
use TheTempusProject\Houdini\Classes\Components;
use TheTempusProject\Houdini\Classes\Forms;
use TheTempusProject\Houdini\Classes\Issues;
use TheTempusProject\Houdini\Classes\Views;
use TheTempusProject\Bedrock\Classes\Config;
use TheTempusProject\Classes\Forms as FormChecker;
use TheTempusProject\Classes\AdminController;
use TheTempusProject\Models\User;
use TheTempusProject\Models\Group;
use TheTempusProject\TheTempusProject as App;
class Users extends AdminController {
public static $user;
public static $group;
public function __construct() {
parent::__construct();
self::$title = 'Admin - Users';
self::$user = new User;
self::$group = new Group;
$view = Navigation::activePageSelect( 'nav.admin', '/admin/users' );
Components::set( 'ADMINNAV', $view );
}
public function create() {
if ( Input::exists( 'submit' ) ) {
if ( !FormChecker::check( 'createUser' ) ) {
Issues::add( 'error', [ 'There was an error with your request.' => Check::userErrors() ] );
} else {
$fields = [
'username' => Input::post( 'username' ),
'password' => Hash::make( Input::post( 'password' ) ),
'email' => Input::post( 'email' ),
'userGroup' => Input::post( 'groupSelect' ),
'terms' => 0,
];
if ( !Input::exists( 'confirmation' ) ) {
$fields['confirmed'] = 1;
}
if ( self::$user->create( $fields ) ) {
Issues::add( 'success', 'User Created' );
return $this->index();
} else {
Issues::add( 'error', 'There was an error creating the user' );
}
}
}
$select = Forms::getFormFieldHtml( 'groupSelect', 'User Group', 'select', Config::getValue( 'group/defaultGroup' ), self::$group->listGroupsSimple() );
Components::set( 'groupSelect', $select );
Views::view( 'admin.users.create' );
}
public function delete( $id = null ) {
if ( Input::exists( 'submit' ) ) {
$id = Input::post( 'U_' );
}
if ( self::$user->delete( $id ) ) {
Issues::add( 'success', 'User deleted' );
} else {
Issues::add( 'error', 'There was an error deleting user(s)' );
}
$this->index();
}
public function edit( $id = null ) {
if ( !Check::id( $id ) ) {
return Issues::add( 'error', 'Invalid user' );
}
$userData = self::$user->findById( $id );
if ( in_array( $userData->groupName, self::$group::$protectedGroups ) ) {
switch ( $userData->groupName ) {
case 'Super':
if ( 'Super' !== App::$activeGroup->name ) {
Issues::add( 'error', 'You do not have permission to do that.' );
return $this->index();
}
case 'Admin':
if ( 'Super' !== App::$activeGroup->name ) {
Issues::add( 'error', 'You do not have permission to do that.' );
return $this->index();
}
}
}
if ( Input::exists( 'submit' ) ) {
if ( !FormChecker::check( 'editUser' ) ) {
Issues::add( 'error', [ 'There was an error with your request.' => Check::userErrors() ] );
} else {
$fields = [
'username' => Input::post( 'username' ),
'email' => Input::post( 'email' ),
'userGroup' => Input::post( 'groupSelect' ),
];
if ( Input::exists( 'confirmed' ) ) {
$fields['confirmed'] = 1;
} else {
if ( Input::exists( 'confirmation' ) ) {
$fields['confirmationCode'] = Code::genConfirmation();
}
}
if ( self::$user->update( $userData->ID, $fields ) ) {
Issues::add( 'success', 'User Updated.' );
return $this->index();
} else {
Issues::add( 'notice', 'There was an error with your request, please try again.' );
}
}
}
if ( empty( $avatarLocation ) ) {
$avatarLocation = $userData->prefs['avatar'];
}
if ( empty( $userGroup ) ) {
$userGroup = $userData->userGroup;
}
Forms::selectRadio( 'confirmed', $userData->confirmed );
$avatar = Forms::getFormFieldHtml( 'avatar', 'User Avatar', 'file', $avatarLocation );
$select = Forms::getFormFieldHtml( 'groupSelect', 'User Group', 'select', $userGroup, self::$group->listGroupsSimple() );
Components::set( 'AvatarSettings', $avatar );
Components::set( 'groupSelect', $select );
Views::view( 'admin.users.edit', $userData );
}
public function index() {
Views::view( 'admin.users.list', self::$user->userList() );
}
public function view( $id = null ) {
if ( !empty( $id ) ) {
$userData = self::$user->findById( $id );
if ( $userData !== false ) {
return Views::view( 'admin.users.view', $userData );
}
Issues::add( 'error', 'User not found.' );
}
$this->index();
}
}

35
app/controllers/alpha.php Normal file
View File

@ -0,0 +1,35 @@
<?php
/**
* app/controllers/alpha.php
*
* This is the friends and family alpha controller.
*
* @version 3.0
* @author Joey Kimsey <Joey@thetempusproject.com>
* @link https://TheTempusProject.com
* @license https://opensource.org/licenses/MIT [MIT LICENSE]
*/
namespace TheTempusProject\Controllers;
use TheTempusProject\Classes\Controller;
use TheTempusProject\Houdini\Classes\Views;
class Alpha extends Controller {
public function index() {
self::$title = 'Friends and Family Alpha';
self::$pageDescription = 'The Tempus Project friends and family alpha has begun. Please join me and take part in bringing a dream to reality.';
Views::view( 'alpha.index' );
}
public function crashcourse() {
self::$title = 'Friends and Family Crash-Course';
self::$pageDescription = 'The Tempus Project runs not only this site, but it can be used and deployed for any number of sites. This crash course is intended to give you all the knowledge you will need to start building your own applications powered by The Tempus Project.';
Views::view( 'alpha.crashcourse' );
}
public function certification() {
self::$title = 'Friends and Family Certification';
self::$pageDescription = 'The Tempus Project runs not only this site, but it can be used and deployed for any number of sites. This certification course is intended to give experienced users all the information they will need to start building your own applications powered by The Tempus Project.';
Views::view( 'alpha.certification' );
}
}

View File

@ -0,0 +1,37 @@
<?php
/**
* app/controllers/api/users.php
*
* This is the users' api controller.
*
* @version 3.0
* @author Joey Kimsey <Joey@thetempusproject.com>
* @link https://TheTempusProject.com
* @license https://opensource.org/licenses/MIT [MIT LICENSE]
*/
namespace TheTempusProject\Controllers\Api;
use TheTempusProject\Models\User;
use TheTempusProject\Classes\ApiController;
use TheTempusProject\Houdini\Classes\Views;
class Users extends ApiController {
public static $user;
public function __construct() {
parent::__construct();
self::$user = new User;
}
public function find( $id = null ) {
$user = self::$user->get( $id );
if ( ! $user ) {
$responseType = 'error';
$response = 'No user found.';
} else {
$responseType = 'data';
$response = $user;
}
Views::view( 'api.response', ['response' => json_encode( [ $responseType => $response ], true )]);
}
}

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

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

101
app/controllers/home.php Normal file
View File

@ -0,0 +1,101 @@
<?php
/**
* app/controllers/home.php
*
* This is the home or 'index' controller.
*
* @version 3.0
* @author Joey Kimsey <Joey@thetempusproject.com>
* @link https://TheTempusProject.com
* @license https://opensource.org/licenses/MIT [MIT LICENSE]
*/
namespace TheTempusProject\Controllers;
use TheTempusProject\Hermes\Functions\Redirect;
use TheTempusProject\Bedrock\Functions\Session;
use TheTempusProject\Bedrock\Functions\Check;
use TheTempusProject\Bedrock\Functions\Input;
use TheTempusProject\Hermes\Functions\Route as Routes;
use TheTempusProject\Houdini\Classes\Issues;
use TheTempusProject\Houdini\Classes\Views;
use TheTempusProject\Houdini\Classes\Components;
use TheTempusProject\Houdini\Classes\Template;
use TheTempusProject\Classes\Controller;
use TheTempusProject\Classes\Forms;
use TheTempusProject\TheTempusProject as App;
class Home extends Controller {
public function index() {
self::$title = '{SITENAME}';
self::$pageDescription = 'This is the homepage of your new Tempus Project Installation. Thank you for installing. find more info at https://thetempusproject.com';
Views::view( 'index' );
}
public function login() {
self::$title = 'Portal - {SITENAME}';
self::$pageDescription = 'Please log in to use {SITENAME} member features.';
if ( App::$isLoggedIn ) {
return Issues::add( 'notice', 'You are already logged in. Please <a href="' . Routes::getAddress() . 'home/logout">click here</a> to log out.' );
}
if ( !Input::exists() ) {
return Views::view( 'login' );
}
if ( !Forms::check( 'login' ) ) {
Issues::add( 'error', [ 'There was an error with your login.' => Check::userErrors() ] );
return Views::view( 'login' );
}
if ( !self::$user->logIn( Input::post( 'username' ), Input::post( 'password' ), Input::post( 'remember' ) ) ) {
Issues::add( 'error', 'Username or password was incorrect.' );
return Views::view( 'login' );
}
Session::flash( 'success', 'You have been logged in.' );
if ( Input::exists( 'rurl' ) ) {
Redirect::to( Input::post( 'rurl' ) );
} else {
Redirect::to( 'home/index' );
}
}
public function logout() {
self::$title = 'Log Out - {SITENAME}';
Template::noIndex();
if ( !App::$isLoggedIn ) {
return Issues::add( 'notice', 'You are not logged in.' );
}
self::$user->logOut();
Session::flash( 'success', 'You have been logged out.' );
Redirect::to( 'home/index' );
}
public function profile( $id = null ) {
self::$title = 'User Profile - {SITENAME}';
self::$pageDescription = 'User Profiles for {SITENAME}';
if ( !App::$isLoggedIn ) {
return Issues::add( 'notice', 'You must be logged in to view this page.' );
}
$user = self::$user->get( $id );
if ( !$user ) {
return Issues::add( 'notice', 'No user found.' );
}
self::$title = $user->username . '\'s Profile - {SITENAME}';
self::$pageDescription = 'User Profile for ' . $user->username . ' - {SITENAME}';
Views::view( 'profile', $user );
}
public function terms() {
self::$title = 'Terms and Conditions - {SITENAME}';
self::$pageDescription = '{SITENAME} Terms and Conditions of use. Please use {SITENAME} safely.';
Components::set( 'TERMS', Views::simpleView( 'terms' ) );
Views::raw( '<div class="terms-page">{TERMS}</div>' );
}
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' );
}
}

View File

@ -0,0 +1,3 @@
<?php
// the idea is that this will be info pages for the plugin various/info
?>

View File

@ -0,0 +1,139 @@
<?php
/**
* app/controllers/register.php
*
* This is the user registration controller.
*
* @version 3.0
* @author Joey Kimsey <Joey@thetempusproject.com>
* @link https://TheTempusProject.com
* @license https://opensource.org/licenses/MIT [MIT LICENSE]
*/
namespace TheTempusProject\Controllers;
use TheTempusProject\Houdini\Classes\Template;
use TheTempusProject\Classes\Email;
use TheTempusProject\Bedrock\Functions\Check;
use TheTempusProject\Bedrock\Functions\Session;
use TheTempusProject\Bedrock\Functions\Input;
use TheTempusProject\Bedrock\Functions\Hash;
use TheTempusProject\Hermes\Functions\Redirect;
use TheTempusProject\Houdini\Classes\Issues;
use TheTempusProject\Houdini\Classes\Components;
use TheTempusProject\Houdini\Classes\Views;
use TheTempusProject\TheTempusProject as App;
use TheTempusProject\Classes\Controller;
use TheTempusProject\Classes\Forms;
class Register extends Controller {
public function confirm( $code = null ) {
self::$title = 'Confirm Email';
if ( !isset( $code ) && !Input::exists( 'confirmationCode' ) ) {
return Views::view( 'email.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' );
}
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.';
Components::set( 'TERMS', Views::simpleView( 'terms' ) );
if ( App::$isLoggedIn ) {
return Issues::add( 'notice', 'You are currently logged in.' );
}
if ( !Input::exists() ) {
return Views::view( 'register' );
}
if ( !Forms::check( 'register' ) ) {
Issues::add( 'error', [ 'There was an error with your registration.' => Check::userErrors() ] );
return Views::view( 'register' );
}
self::$user->create( [
'username' => Input::post( 'username' ),
'password' => Hash::make( Input::post( 'password' ) ),
'email' => Input::post( 'email' ),
'terms' => 1,
] );
Session::flash( 'success', 'Thank you for registering! Please check your email to confirm your account.' );
Redirect::to( 'home/index' );
}
/**
* @todo Come back and separate this into multiple forms because this is gross.
*/
public function recover() {
self::$title = 'Recover Account - {SITENAME}';
Template::noIndex();
if ( !Input::exists() ) {
return Views::view( 'forgot' );
}
if ( Check::email( Input::post( 'entry' ) ) && self::$user->findByEmail( Input::post( 'entry' ) ) ) {
$userData = self::$user->data();
Email::send( $userData->email, 'forgotUsername', $userData->username, [ 'template' => true ] );
Session::flash( 'notice', 'Your Username has been sent to your registered email address.' );
Redirect::to( 'home/login' );
} elseif ( self::$user->get( Input::post( 'entry' ) ) ) {
self::$user->newCode( self::$user->data()->ID );
self::$user->get( Input::post( 'entry' ) );
$userData = self::$user->data();
Email::send( $userData->email, 'forgotPassword', $userData->confirmationCode, [ 'template' => true ] );
Session::flash( 'notice', 'Details for resetting your password have been sent to your registered email address' );
Redirect::to( 'home/login' );
}
Issues::add( 'error', 'User not found.' );
Views::view( 'forgot' );
}
public function resend() {
self::$title = 'Resend Confirmation';
if ( !App::$isLoggedIn ) {
return Issues::add( 'notice', 'Please log in to resend your confirmation email.' );
}
if ( App::$activeUser->data()->confirmed == '1' ) {
return Issues::add( 'notice', 'Your account has already been confirmed.' );
}
if ( !Forms::check( 'confirmationResend' ) ) {
return Views::view( 'email.confirmation_resend' );
}
Email::send( App::$activeUser->data()->email, 'confirmation', App::$activeUser->data()->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';
if ( !isset( $code ) && !Input::exists( 'resetCode' ) ) {
Issues::add( 'error', 'No reset code provided.' );
return Views::view( 'password_reset_code' );
}
if ( Input::exists( 'resetCode' ) ) {
if ( Forms::check( 'password_reset_code' ) ) {
$code = Input::post( 'resetCode' );
}
}
if ( !self::$user->checkCode( $code ) ) {
Issues::add( 'error', 'There was an error with your reset code. Please try again.' );
return Views::view( 'password_reset_code' );
}
if ( !Input::exists() ) {
return Views::view( 'password_reset' );
}
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.' );
Redirect::to( 'home/login' );
}
}

111
app/controllers/usercp.php Normal file
View File

@ -0,0 +1,111 @@
<?php
/**
* app/controllers/usercp.php
*
* This is the user control panel controller.
*
* @version 3.0
* @author Joey Kimsey <Joey@thetempusproject.com>
* @link https://TheTempusProject.com
* @license https://opensource.org/licenses/MIT [MIT LICENSE]
*/
namespace TheTempusProject\Controllers;
use TheTempusProject\Houdini\Classes\Template;
use TheTempusProject\Classes\Email;
use TheTempusProject\Bedrock\Functions\Code;
use TheTempusProject\Bedrock\Functions\Check;
use TheTempusProject\Bedrock\Functions\Input;
use TheTempusProject\Bedrock\Functions\Hash;
use TheTempusProject\Houdini\Classes\Components;
use TheTempusProject\Houdini\Classes\Views;
use TheTempusProject\Houdini\Classes\Issues;
use TheTempusProject\Houdini\Classes\Navigation;
use TheTempusProject\TheTempusProject as App;
use TheTempusProject\Classes\Controller;
use TheTempusProject\Classes\Preferences;
use TheTempusProject\Classes\Forms;
use TheTempusProject\Hermes\Functions\Redirect;
use TheTempusProject\Bedrock\Functions\Session;
class Usercp extends Controller {
public function __construct() {
parent::__construct();
if ( !App::$isLoggedIn ) {
Session::flash( 'notice', 'You must be logged in to view this page!' );
Redirect::home();
}
Template::noIndex();
Navigation::activePageSelect( 'nav.usercp', null, true );
}
public function email() {
self::$title = 'Email Settings';
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 );
}
if ( !Input::exists() ) {
return Views::view( 'user_cp.email_change' );
}
if ( !Forms::check( 'changeEmail' ) ) {
Issues::add( 'error', [ 'There was an error with your request.' => Check::userErrors() ] );
return Views::view( 'user_cp.email_change' );
}
$code = Code::genConfirmation();
self::$user->update(
App::$activeUser->ID,
[
'confirmed' => 0,
'email' => Input::post( 'email' ),
'confirmationCode' => $code,
],
);
Email::send( App::$activeUser->email, 'emailChangeNotice', $code, [ 'template' => true ] );
Email::send( Input::post( 'email' ), 'emailChange', $code, [ 'template' => true ] );
Issues::add( 'notice', 'Email has been changed, please check your email to confirm it.' );
}
public function index() {
self::$title = 'User Control Panel';
Views::view( 'profile', App::$activeUser );
}
public function password() {
self::$title = 'Password Settings';
if ( !Input::exists() ) {
return Views::view( 'user_cp.password_change' );
}
if ( !Hash::check( Input::post( 'curpass' ), App::$activeUser->password ) ) {
Issues::add( 'error', 'Current password was incorrect.' );
return Views::view( 'user_cp.password_change' );
}
if ( !Forms::check( 'changePassword' ) ) {
Issues::add( 'error', [ 'There was an error with your request.' => Check::userErrors() ] );
return Views::view( 'user_cp.password_change' );
}
self::$user->update(
App::$activeUser->ID,
[ 'password' => Hash::make( Input::post( 'password' ) ) ],
);
Email::send( App::$activeUser->email, 'passwordChange', null, [ 'template' => true ] );
Issues::add( 'notice', 'Your Password has been changed!' );
}
public function settings() {
self::$title = 'Preferences';
$prefs = new Preferences;
$fields = App::$activePrefs;
if ( Input::exists( 'submit' ) ) {
$fields = $prefs->convertFormToArray( true, false );
// @TODO now i may need to rework the form checker to work with this....
// if (!Forms::check('userPrefs')) {
// Issues::add( 'error', [ 'There was an error with your request.' => Check::userErrors() ] );
// }
self::$user->updatePrefs( $fields, App::$activeUser->ID );
Issues::add( 'success', 'Your preferences have been updated.' );
}
Components::set( 'AVATAR_SETTINGS', $fields['avatar'] );
Components::set( 'PREFERENCES_FORM', $prefs->getFormHtml( $fields ) );
Views::view( 'user_cp.settings', App::$activeUser );
}
}

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

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

669
app/css/main.css Normal file
View File

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

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

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

91
app/functions/common.php Normal file
View File

@ -0,0 +1,91 @@
<?php
/**
* app/functions/common.php
*
* @version 3.0
* @author Joey Kimsey <Joey@thetempusproject.com>
* @link https://TheTempusProject.com
* @license https://opensource.org/licenses/MIT [MIT LICENSE]
*/
function getClassName($class) {
$className = get_class($class);
$classNameParts = explode('\\', $className);
return end($classNameParts);
}
function convertClassNameToFileName( $class_name ) {
$lower = lcfirst( $class_name ) . '.php';
$upper_split = preg_split( '/(?=[A-Z])/', $lower );
$file_name = strtolower( implode( '_', $upper_split ) );
return $file_name;
}
function convertFileNameToClassName( $file_name ) {
$file_name = str_ireplace( '.php', '', $file_name );
$file_name = rtrim( $file_name, DIRECTORY_SEPARATOR );
$class_name = '';
if ( stripos( $file_name, '_' ) ) {
$exploded = explode( '_', $file_name );
foreach ( $exploded as $key => $value ) {
$class_name .= ucfirst( $value );
}
} else {
$class_name .= ucfirst( $file_name );
}
return $class_name;
}
function convertFolderToClassName( $folder ) {
$file_name = rtrim( $folder, DIRECTORY_SEPARATOR );
$parts_array = explode(DIRECTORY_SEPARATOR, $file_name);
$file_name = array_pop($parts_array);
$class_name = '';
if ( stripos( $file_name, '_' ) ) {
$exploded = explode( '_', $file_name );
foreach ( $exploded as $key => $value ) {
$class_name .= ucfirst( $value );
}
} else {
$class_name .= ucfirst( $file_name );
}
return $class_name;
}
function convertFileNameToPluginClass( $file_name ) {
$class_name = convertFolderToClassName( $file_name );
$class = (string) APP_SPACE . '\\Plugins\\' . $class_name;
return $class;
}
function convertFileNameToModelClass( $file_name ) {
$class_name = convertFileNameToClassName( $file_name );
$class = (string) APP_SPACE . '\\Models\\' . $class_name;
return $class;
}
function getFileList( $folder = '' ) {
if ( empty( $folder ) ) {
$folder = PLUGIN_DIRECTORY;
}
if ( !file_exists( $folder ) ) {
return false;
}
$pluginFolders = scandir( $folder );
array_shift( $pluginFolders ); // remove the .
array_shift( $pluginFolders ); // remove the ..
return $pluginFolders;
}
function dv( $variable ) {
echo '<pre>';
echo var_export( $variable, true );
echo '</pre>';
exit;
}
function iv( $variable ) {
echo '<pre>';
echo var_export( $variable, true );
echo '</pre>';
}

542
app/functions/forms.php Normal file
View File

@ -0,0 +1,542 @@
<?php
/**
* app/functions/forms.php
*
* This class is used in conjunction with TheTempusProject\Bedrock\Classes\Check
* to house complete form verification. You can utilize the error reporting
* to easily define exactly what feedback you would like to give.
*
* @version 3.0
* @author Joey Kimsey <Joey@thetempusproject.com>
* @link https://TheTempusProject.com
* @license https://opensource.org/licenses/MIT [MIT LICENSE]
*/
namespace TheTempusProject;
use TheTempusProject\Bedrock\Functions\Input;
use TheTempusProject\Bedrock\Functions\Check;
use TheTempusProject\Models\User;
use TheTempusProject\Classes\Forms;
use TheTempusProject\Bedrock\Classes\Database;
class TTPForms extends Forms {
/**
* Adds these functions to the form list.
*/
public function __construct() {
self::addHandler( 'passwordResetCode', __CLASS__, 'passwordResetCode' );
self::addHandler( 'createRoute', __CLASS__, 'createRoute' );
self::addHandler( 'editRoute', __CLASS__, 'editRoute' );
self::addHandler( 'register', __CLASS__, 'register' );
self::addHandler( 'createUser', __CLASS__, 'createUser' );
self::addHandler( 'editUser', __CLASS__, 'editUser' );
self::addHandler( 'login', __CLASS__, 'login' );
self::addHandler( 'changeEmail', __CLASS__, 'changeEmail' );
self::addHandler( 'changePassword', __CLASS__, 'changePassword' );
self::addHandler( 'passwordReset', __CLASS__, 'passwordReset' );
self::addHandler( 'emailConfirmation', __CLASS__, 'emailConfirmation' );
self::addHandler( 'confirmationResend', __CLASS__, 'confirmationResend' );
self::addHandler( 'replyMessage', __CLASS__, 'replyMessage' );
self::addHandler( 'newMessage', __CLASS__, 'newMessage' );
self::addHandler( 'userPrefs', __CLASS__, 'userPrefs' );
self::addHandler( 'newGroup', __CLASS__, 'newGroup' );
self::addHandler( 'editGroup', __CLASS__, 'editGroup' );
self::addHandler( 'install', __CLASS__, 'install' );
self::addHandler( 'installStart', __CLASS__, 'install', [ 'start' ] );
self::addHandler( 'installAgreement', __CLASS__, 'install', [ 'agreement' ] );
self::addHandler( 'installCheck', __CLASS__, 'install', [ 'check' ] );
self::addHandler( 'installConfigure', __CLASS__, 'install', [ 'configure' ] );
self::addHandler( 'installRouting', __CLASS__, 'install', [ 'routing' ] );
self::addHandler( 'installModels', __CLASS__, 'install', [ 'models' ] );
self::addHandler( 'installPlugins', __CLASS__, 'install', [ 'plugins' ] );
self::addHandler( 'installResources', __CLASS__, 'install', [ 'resources' ] );
self::addHandler( 'installAdminUser', __CLASS__, 'install', [ 'adminUser' ] );
}
/**
* Validates the installer forms.
*
* @return {bool}
*/
public static function install( $page = '' ) {
// if ( !self::token() ) {
// return false;
// }
switch ( $page ) {
case 'configure':
if ( ! Input::exists( 'submit' ) ) {
return false;
}
if ( !Database::check( Input::post( 'dbHost' ), Input::post( 'dbName' ), Input::post( 'dbUsername' ), Input::post( 'dbPassword' ) ) ) {
self::addUserError( 'DB connection error.' );
return false;
}
return true;
case 'adminUser':
if ( !self::checkUsername( Input::post( 'newUsername' ) ) ) {
self::addUserError( 'Invalid username.' );
return false;
}
if ( !self::password( Input::post( 'userPassword' ) ) ) {
self::addUserError( 'Invalid password.' );
return false;
}
if ( Input::post( 'userPassword' ) !== Input::post( 'userPassword2' ) ) {
self::addUserError( 'Passwords do not match.' );
return false;
}
if ( Input::post( 'userEmail' ) !== Input::post( 'userEmail2' ) ) {
self::addUserError( 'Emails do not match.' );
return false;
}
return true;
case 'check':
if ( !self::uploads() ) {
self::addUserError( 'Uploads are disabled.' );
return false;
}
if ( !self::php() ) {
self::addUserError( 'PHP version is too old.' );
return false;
}
if ( !self::phpExtensions() ) {
self::addUserError( 'PHP extensions are missing.' );
return false;
}
if ( !self::sessions() ) {
self::addUserError( 'There is an error with Sessions.' );
return false;
}
if ( !self::mail() ) {
self::addUserError( 'PHP mail is not enabled.' );
return false;
}
if ( !self::safe() ) {
self::addUserError( 'Safe mode is enabled.' );
return false;
}
if ( ! Input::exists( 'submit' ) ) {
return false;
}
return true;
case 'start':
case 'agreement':
case 'routing':
case 'models':
case 'plugins':
case 'resources':
if ( ! Input::exists( 'submit' ) ) {
return false;
}
return true;
default:
return false;
}
return false;
}
/**
* Validates the password re-send form.
*
* @return {bool}
*/
public static function passwordResetCode() {
if ( !self::token() ) {
return false;
}
return true;
}
/**
* Validates the route creation form.
*
* @return {bool}
*/
public static function createRoute() {
if ( !Input::exists( 'redirect_type' ) ) {
return false;
}
if ( 'external' == Input::post( 'redirect_type' ) && !self::url( Input::post( 'forwarded_url' ) ) ) {
return false;
}
if ( !self::token() ) {
return false;
}
return true;
}
/**
* Validates the route edit form.
*
* @return {bool}
*/
public static function editRoute() {
if ( !Input::exists( 'redirect_type' ) ) {
return false;
}
if ( 'external' == Input::post( 'redirect_type' ) && !self::url( Input::post( 'forwarded_url' ) ) ) {
return false;
}
if ( !self::token() ) {
return false;
}
return true;
}
/**
* Validates the user creation form.
*
* @return {bool}
*/
public static function createUser() {
$user = new User;
if ( !$user->checkUsername( Input::post( 'username' ) ) ) {
self::addUserError( 'Invalid username.' );
return false;
}
if ( !self::password( Input::post( 'password' ) ) ) {
self::addUserError( 'Invalid password.' );
return false;
}
if ( !self::email( Input::post( 'email' ) ) ) {
self::addUserError( 'Invalid Email.' );
return false;
}
if ( !$user->noEmailExists( Input::post( 'email' ) ) ) {
self::addUserError( 'A user with that email is already registered.' );
return false;
}
if ( Input::post( 'password' ) !== Input::post( 'password2' ) ) {
self::addUserError( 'Passwords do not match.' );
return false;
}
if ( Input::post( 'email' ) !== Input::post( 'email2' ) ) {
self::addUserError( 'Emails do not match.' );
return false;
}
if ( !Input::post( 'groupSelect' ) ) {
self::addUserError( 'You must select a group for the new user.' );
return false;
}
if ( !self::token() ) {
return false;
}
return true;
}
/**
* Validates the user edit form.
*
* @return {bool}
*/
public static function editUser() {
$user = new User;
if ( !$user->checkUsername( Input::post( 'username' ) ) ) {
self::addUserError( 'Invalid username.' );
return false;
}
if ( Input::exists( 'password' ) ) {
if ( !self::password( Input::post( 'password' ) ) ) {
self::addUserError( 'Invalid password.' );
return false;
}
if ( Input::post( 'password' ) !== Input::post( 'password2' ) ) {
self::addUserError( 'Passwords do not match.' );
return false;
}
}
if ( !self::email( Input::post( 'email' ) ) ) {
self::addUserError( 'Invalid Email.' );
return false;
}
if ( !Input::post( 'groupSelect' ) ) {
self::addUserError( 'You must select a group for the new user.' );
return false;
}
if ( !self::token() ) {
return false;
}
return true;
}
/**
* Validates the user registration form.
*
* @return {bool}
*/
public static function register() {
$user = new User;
if ( !self::checkUsername( Input::post( 'username' ) ) ) {
self::addUserError( 'Invalid username.' );
return false;
}
if ( !self::password( Input::post( 'password' ) ) ) {
self::addUserError( 'Invalid password.' );
return false;
}
if ( !self::email( Input::post( 'email' ) ) ) {
self::addUserError( 'Invalid Email.' );
return false;
}
if ( !$user->noEmailExists( Input::post( 'email' ) ) ) {
self::addUserError( 'A user with that email is already registered.' );
return false;
}
if ( Input::post( 'password' ) !== Input::post( 'password2' ) ) {
self::addUserError( 'Passwords do not match.' );
return false;
}
if ( Input::post( 'email' ) !== Input::post( 'email2' ) ) {
self::addUserError( 'Emails do not match.' );
return false;
}
if ( Input::post( 'terms' ) != '1' ) {
self::addUserError( 'You must agree to the terms of service.' );
return false;
}
if ( !self::token() ) {
return false;
}
return true;
}
/**
* Validates the user login form.
*
* @return {bool}
*/
public static function login() {
if ( !self::checkUsername( Input::post( 'username' ) ) ) {
self::addUserError( 'Invalid username.' );
return false;
}
if ( !self::password( Input::post( 'password' ) ) ) {
self::addUserError( 'Invalid password.' );
return false;
}
if ( !self::token() ) {
return false;
}
return true;
}
/**
* Validates the email change form.
*
* @return {bool}
*/
public static function changeEmail() {
if ( !self::email( Input::post( 'email' ) ) ) {
self::addUserError( 'Invalid Email.' );
return false;
}
if ( Input::post( 'email' ) !== Input::post( 'email2' ) ) {
self::addUserError( 'Emails do not match.' );
return false;
}
if ( !self::token() ) {
return false;
}
return true;
}
/**
* Validates the password change form.
*
* @return {bool}
*/
public static function changePassword() {
if ( !self::password( Input::post( 'password' ) ) ) {
self::addUserError( 'Invalid password.' );
return false;
}
if ( Input::post( 'password' ) !== Input::post( 'password2' ) ) {
self::addUserError( 'Passwords do not match.' );
return false;
}
if ( !self::token() ) {
return false;
}
return true;
}
/**
* Validates the password reset form.
*
* @return {bool}
*/
public static function passwordReset() {
if ( !self::password( Input::post( 'password' ) ) ) {
self::addUserError( 'Invalid password.' );
return false;
}
if ( Input::post( 'password' ) !== Input::post( 'password2' ) ) {
self::addUserError( 'Passwords do not match.' );
return false;
}
if ( !self::token() ) {
return false;
}
return true;
}
/**
* Validates the email confirmation re-send form.
*
* @return {bool}
*/
public static function emailConfirmation() {
if ( !Input::exists( 'confirmationCode' ) ) {
self::addUserError( 'No confirmation code provided.' );
return false;
}
if ( !self::token() ) {
return false;
}
return true;
}
/**
* Validates the email confirmation re-send form.
*
* @return {bool}
*/
public static function confirmationResend() {
if ( !Input::exists( 'resendConfirmation' ) ) {
self::addUserError( 'Confirmation not provided.' );
return false;
}
if ( !self::token() ) {
return false;
}
return true;
}
/**
* Validates the reply message form.
*
* @return {bool}
*/
public static function replyMessage() {
if ( !Input::exists( 'message' ) ) {
self::addUserError( 'Reply cannot be empty.' );
return false;
}
if ( !Input::exists( 'messageID' ) ) {
return false;
}
if ( !self::token() ) {
return false;
}
return true;
}
/**
* Validates the new message form.
*
* @return {bool}
*/
public static function newMessage() {
if ( !Input::exists( 'toUser' ) ) {
self::addUserError( 'You must specify a user to send the message to.' );
return false;
}
if ( !Input::exists( 'subject' ) ) {
self::addUserError( 'You must have a subject for your message.' );
return false;
}
if ( !Input::exists( 'message' ) ) {
self::addUserError( 'No message entered.' );
return false;
}
if ( !self::token() ) {
return false;
}
return true;
}
/**
* Validates the user preferences form.
*
* @return {bool}
*/
public static function userPrefs() {
// @todo make this a real check
if ( !Input::exists( 'timeFormat' ) ) {
self::addUserError( 'You must specify timeFormat' );
return false;
}
if ( !Input::exists( 'pageLimit' ) ) {
self::addUserError( 'You must specify pageLimit' );
return false;
}
if ( !Input::exists( 'gender' ) ) {
self::addUserError( 'You must specify gender' );
return false;
}
if ( !Input::exists( 'dateFormat' ) ) {
self::addUserError( 'You must specify dateFormat' );
return false;
}
if ( !Input::exists( 'timezone' ) ) {
self::addUserError( 'You must specify timezone' );
return false;
}
if ( !Input::exists( 'updates' ) ) {
self::addUserError( 'You must specify updates' );
return false;
}
if ( !Input::exists( 'newsletter' ) ) {
self::addUserError( 'You must specify newsletter' );
return false;
}
if ( !self::token() ) {
return false;
}
return true;
}
/**
* Validates the group creation form.
*
* @return {bool}
*/
public static function newGroup() {
if ( !Input::exists( 'name' ) ) {
self::addUserError( 'You must specify a name' );
return false;
}
if ( !self::dataTitle( Input::exists( 'name' ) ) ) {
self::addUserError( 'invalid group name' );
return false;
}
if ( !self::token() ) {
return false;
}
return true;
}
/**
* Validates the group edit form.
*
* @return {bool}
*/
public static function editGroup() {
if ( !Input::exists( 'name' ) ) {
self::addUserError( 'You must specify a name' );
return false;
}
if ( !self::dataTitle( Input::exists( 'name' ) ) ) {
self::addUserError( 'invalid group name' );
return false;
}
if ( !self::token() ) {
return false;
}
return true;
}
}
new TTPForms;

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

BIN
app/images/ttp.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

111
app/js/main.js Normal file
View File

@ -0,0 +1,111 @@
/**
* app/js/main.js
*
* This file is for 'access anywhere' javascript.
*
* @version 3.0
* @author Joey Kimsey <Joey@thetempusproject.com>
* @link https://TheTempusProject.com
* @license https://opensource.org/licenses/MIT [MIT LICENSE]
*/
/**
* Automatically selects/de-selects all check boxes associated with that field
**/
function checkAll(ele) {
var checkboxes = document.getElementsByTagName( 'input' );
if (ele.checked) {
test = true;
} else {
test = false;
}
for ( var i = 0; i < checkboxes.length; i++ ) {
if ( checkboxes[i].type == 'checkbox' ) {
if ( checkboxes[i].name == ele.value ) {
checkboxes[i].checked = test;
}
}
}
}
function copyAll( ele ) {
var eleName = '#' + ele;
var text = $( eleName ).text();
text = text.replaceAll( "''", "\n" ).trim();
text = text.substring( 1, text.length - 1 );
navigator.clipboard.writeText( text );
console.log( '#' + ele );
}
function insertTag( box, tag ) {
var Field = document.getElementById( box );
var currentPos = cursorPos( Field );
var val = Field.value;
var before = val.substring( 0, currentPos );
var after = val.substring( currentPos, val.length );
Field.value = before + '(' + tag + ')' + after;
}
function cursorPos( el ) {
if ( el.selectionStart ) {
return el.selectionStart;
} else if ( document.selection ) {
el.focus();
var r = document.selection.createRange();
if ( r == null ) {
return 0;
}
var re = el.createTextRange(),
rc = re.duplicate();
re.moveToBookmark( r.getBookmark() );
rc.setEndPoint( 'EndToStart', re );
return rc.text.length;
}
return 0;
}
function getRandomInt(min, max) {
const minCeiled = Math.ceil(min);
const maxFloored = Math.floor(max);
return Math.floor(Math.random() * (maxFloored - minCeiled) + minCeiled);
}
$(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);
}
});
}
});
});
// with the dynamic footer, you need to adjust the content padding to make sure the footer doesn't overlap the content
window.onload = function () {
function updateFooterPadding() {
var footer = document.querySelector('footer');
var container = document.querySelector('.container-fluid.top-pad');
if ( ! container ) {
return;
}
// footer has no height but its children do!
var footerHeight = Array.from(footer.children).reduce((totalHeight, child) => {
return totalHeight + child.offsetHeight;
}, 0);
footerHeight += 20; // Add 20px for padding
// console.error(footerHeight);
container.style.setProperty('--footer-height', footerHeight + 'px');
}
// Update padding on initial load
updateFooterPadding();
// Update padding on window resize
window.addEventListener('resize', updateFooterPadding);
};

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

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

286
app/models/group.php Normal file
View File

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

197
app/models/log.php Normal file
View File

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

158
app/models/routes.php Normal file
View File

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

283
app/models/sessions.php Normal file
View File

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

726
app/models/user.php Normal file
View File

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

View File

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

View File

@ -0,0 +1,158 @@
<?php
/**
* app/plugins/blog/controllers/blog.php
*
* This is the blog controller.
*
* @package TP Blog
* @version 3.0
* @author Joey Kimsey <Joey@thetempusproject.com>
* @link https://TheTempusProject.com
* @license https://opensource.org/licenses/MIT [MIT LICENSE]
*/
namespace TheTempusProject\Controllers;
use TheTempusProject\Bedrock\Functions\Check;
use TheTempusProject\Canary\Canary as Debug;
use TheTempusProject\Bedrock\Functions\Input;
use TheTempusProject\Houdini\Classes\Components;
use TheTempusProject\Houdini\Classes\Issues;
use TheTempusProject\Houdini\Classes\Views;
use TheTempusProject\Houdini\Classes\Template;
use TheTempusProject\Classes\Controller;
use TheTempusProject\Classes\Forms;
use TheTempusProject\Hermes\Functions\Redirect;
use TheTempusProject\Bedrock\Functions\Session;
use TheTempusProject\Plugins\Blog as BlogPlugin;
use TheTempusProject\TheTempusProject as App;
use TheTempusProject\Plugins\Comments;
use TheTempusProject\Models\Comments as CommentsModel;
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;
}
public function index() {
self::$title = '{SITENAME} Blog';
self::$pageDescription = 'The {SITENAME} blog is where you can find various posts containing information ranging from current projects and general information to editorial and opinion based content.';
Views::view( 'blog.list', self::$posts->listPosts() );
}
public function rss() {
Debug::log( 'Controller initiated: ' . __METHOD__ . '.' );
self::$title = '{SITENAME} RSS Feed';
self::$pageDescription = '{SITENAME} blog RSS feed.';
Template::setTemplate( 'rss' );
header( 'Content-Type: text/xml' );
return Views::view( 'blog.rss', self::$posts->listPosts( ['stripHtml' => true] ) );
}
public function comments( $sub = null, $data = null ) {
Debug::log( 'Controller initiated: ' . __METHOD__ . '.' );
if ( empty( self::$comments ) ) {
self::$comments = new CommentsModel;
}
$plugin = new Comments;
if ( empty( $sub ) || empty( $data ) ) {
Session::flash( 'error', 'Whoops, try again.' );
Redirect::to( 'blog' );
}
switch ( $sub ) {
case 'post':
$content = self::$posts->findById( (int) $data );
if ( empty( $content ) ) {
Session::flash( 'error', 'Unknown Post.' );
Redirect::to( 'blog' );
}
return $plugin->formPost( self::$posts->tableName, $content, 'blog/post/' );
return self::$comments->formPost( 'blog', $content, 'blog/post/' );
case 'edit':
$content = self::$comments->findById( $data );
if ( empty( $content ) ) {
Session::flash( 'error', 'Unknown Comment.' );
Redirect::to( 'blog' );
}
return $plugin->formEdit( self::$posts->tableName, $content, 'blog/post/' );
return self::$comments->formEdit( 'blog', $content, 'blog/post/' );
case 'delete':
$content = self::$comments->findById( $data );
if ( empty( $content ) ) {
Session::flash( 'error', 'Unknown Comment.' );
Redirect::to( 'blog' );
}
return $plugin->formDelete( self::$posts->tableName, $content, 'blog/post/' );
return self::$comments->formDelete( 'blog', $content, 'blog/post/' );
}
}
public function post( $id = null ) {
if ( empty( $id ) ) {
return $this->index();
}
$post = self::$posts->findById( $id );
if ( empty( $post ) ) {
return $this->index();
}
if ( empty( self::$comments ) ) {
self::$comments = new CommentsModel;
}
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', '' );
}
$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 );
}
public function author( $data = null ) {
if ( empty( $data ) ) {
return $this->index();
}
Debug::log( 'Controller initiated: ' . __METHOD__ . '.' );
self::$title = 'Posts by author - {SITENAME}';
self::$pageDescription = '{SITENAME} blog posts easily and conveniently sorted by author.';
Views::view( 'blog.list', self::$posts->byAuthor( $data ) );
}
public function month( $month = null, $year = 0 ) {
if ( empty( $month ) ) {
return $this->index();
}
Debug::log( 'Controller initiated: ' . __METHOD__ . '.' );
self::$title = 'Posts By Month - {SITENAME}';
self::$pageDescription = '{SITENAME} blog posts easily and conveniently sorted by month.';
Views::view( 'blog.list', self::$posts->byMonth( $month, $year ) );
}
public function year( $year = null ) {
if ( empty( $year ) ) {
return $this->index();
}
Debug::log( 'Controller initiated: ' . __METHOD__ . '.' );
self::$title = 'Posts by Year - {SITENAME}';
self::$pageDescription = '{SITENAME} blog posts easily and conveniently sorted by years.';
Views::view( 'blog.list', self::$posts->byYear( $year ) );
}
}

View File

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

View File

@ -0,0 +1,321 @@
<?php
/**
* app/plugins/blog/models/blog.php
*
* This class is used for the manipulation of the blog database table.
*
* @package TP Blog
* @version 3.0
* @author Joey Kimsey <Joey@thetempusproject.com>
* @link https://TheTempusProject.com
* @license https://opensource.org/licenses/MIT [MIT LICENSE]
*/
namespace TheTempusProject\Models;
use TheTempusProject\Canary\Canary as Debug;
use TheTempusProject\Bedrock\Functions\Check;
use TheTempusProject\Bedrock\Functions\Sanitize;
use TheTempusProject\Classes\DatabaseModel;
use TheTempusProject\TheTempusProject as App;
use TheTempusProject\Bedrock\Classes\CustomException;
use TheTempusProject\Houdini\Classes\Filters;
class Posts extends DatabaseModel {
public $tableName = 'posts';
public $databaseMatrix = [
[ 'author', 'int', '11' ],
[ 'created', 'int', '10' ],
[ 'edited', 'int', '10' ],
[ 'draft', 'int', '1' ],
[ 'title', 'varchar', '86' ],
[ 'content', 'text', '' ],
];
public $resourceMatrix = [
[
'title' => 'Welcome',
'content' => '<p>This is just a simple message to say thank you for installing The Tempus Project. If you have any questions you can find everything through our website <a href="https://TheTempusProject.com">here</a>.</p>',
'author' => 1,
'created' => '{time}',
'edited' => '{time}',
'draft' => 0,
],
];
public function __construct() {
parent::__construct();
}
public function newPost( $title, $post, $draft ) {
if ( !Check::dataTitle( $title ) ) {
Debug::info( 'modelBlog: illegal title.' );
return false;
}
if ( $draft === 'saveDraft' ) {
$draft = 1;
} else {
$draft = 0;
}
$fields = [
'author' => App::$activeUser->ID,
'draft' => $draft,
'created' => time(),
'edited' => time(),
'content' => Sanitize::rich( $post ),
'title' => $title,
];
if ( !self::$db->insert( $this->tableName, $fields ) ) {
Debug::error( "Blog Post: $data not updated: $fields" );
new customException( 'blogCreate' );
return false;
}
return true;
}
public function updatePost( $id, $title, $content, $draft ) {
if ( empty( self::$log ) ) {
self::$log = new Log;
}
if ( !Check::id( $id ) ) {
Debug::info( 'modelBlog: illegal ID.' );
return false;
}
if ( !Check::dataTitle( $title ) ) {
Debug::info( 'modelBlog: illegal title.' );
return false;
}
if ( $draft === 'saveDraft' ) {
$draft = 1;
} else {
$draft = 0;
}
$fields = [
'draft' => $draft,
'edited' => time(),
'content' => Sanitize::rich( $content ),
'title' => $title,
];
if ( !self::$db->update( $this->tableName, $id, $fields ) ) {
new CustomException( 'blogUpdate' );
Debug::error( "Blog Post: $id not updated: $fields" );
return false;
}
self::$log->admin( "Updated Blog Post: $id" );
return true;
}
public function preview( $title, $content ) {
if ( !Check::dataTitle( $title ) ) {
Debug::info( 'modelBlog: illegal characters.' );
return false;
}
$fields = [
'title' => $title,
'content' => $content,
'authorName' => App::$activeUser->username,
'created' => time(),
];
return (object) $fields;
}
public function filter( $postArray, $params = [] ) {
foreach ( $postArray as $instance ) {
if ( !is_object( $instance ) ) {
$instance = $postArray;
$end = true;
}
$draft = '';
$authorName = self::$user->getUsername( $instance->author );
$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>';
}
} else {
// @todo: need to refine this after testing
$contentSummaryNoLink = $lineSummary;
$contentSummary = $lineSummary . '... <a href="{ROOT_URL}blog/post/' . $instance->ID . '">Read More</a>';
}
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 );
}
$instance->content = Filters::applyOne( 'mentions.0', $instance->content, true );
$instance->content = Filters::applyOne( 'hashtags.0', $instance->content, true );
$out[] = $instance;
if ( !empty( $end ) ) {
$out = $out[0];
break;
}
}
return $out;
}
public function archive( $includeDraft = false ) {
$whereClause = [];
$currentTimeUnix = time();
$x = 0;
$dataOut = [];
$month = date( 'F', $currentTimeUnix );
$year = date( 'Y', $currentTimeUnix );
$previous = date( 'U', strtotime( "$month 1st $year" ) );
if ( $includeDraft !== true ) {
$whereClause = ['draft', '=', '0', 'AND'];
}
while ( $x <= 5 ) {
$where = array_merge( $whereClause, ['created', '<=', $currentTimeUnix, 'AND', 'created', '>=', $previous] );
$data = self::$db->get( $this->tableName, $where );
$x++;
$month = date( 'm', $previous );
$montht = date( 'F', $previous );
$year = date( 'Y', $previous );
if ( !$data ) {
$count = 0;
} else {
$count = $data->count();
}
$dataOut[] = (object) [
'count' => $count,
'month' => $month,
'year' => $year,
'monthText' => $montht,
];
$currentTimeUnix = $previous;
$previous = date( 'U', strtotime( '-1 months', $currentTimeUnix ) );
}
if ( !$data ) {
Debug::info( 'No Blog posts found.' );
return false;
}
return (object) $dataOut;
}
public function recent( $limit = null, $includeDraft = false ) {
$whereClause = [];
if ( $includeDraft !== true ) {
$whereClause = ['draft', '=', '0'];
} else {
$whereClause = '*';
}
if ( empty( $limit ) ) {
$postData = self::$db->getPaginated( $this->tableName, $whereClause );
} else {
$postData = self::$db->getPaginated( $this->tableName, $whereClause, 'ID', 'DESC', [0, $limit] );
}
if ( !$postData->count() ) {
Debug::info( 'No Blog posts found.' );
return false;
}
return $this->filter( $postData->results() );
}
public function listPosts( $params = [] ) {
if ( isset( $params['includeDrafts'] ) && $params['includeDrafts'] === true ) {
$whereClause = '*';
} else {
$whereClause = ['draft', '=', '0'];
}
$postData = self::$db->getPaginated( $this->tableName, $whereClause );
if ( !$postData->count() ) {
Debug::info( 'No Blog posts found.' );
return false;
}
if ( isset( $params['stripHtml'] ) && $params['stripHtml'] === true ) {
return $this->filter( $postData->results(), ['stripHtml' => true] );
}
return $this->filter( $postData->results() );
}
public function byYear( $year, $includeDraft = false ) {
if ( !Check::id( $year ) ) {
Debug::info( 'Invalid Year' );
return false;
}
$whereClause = [];
if ( $includeDraft !== true ) {
$whereClause = ['draft', '=', '0', 'AND'];
}
$firstDayUnix = date( 'U', strtotime( "first day of $year" ) );
$lastDayUnix = date( 'U', strtotime( "last day of $year" ) );
$whereClause = array_merge( $whereClause, ['created', '<=', $lastDayUnix, 'AND', 'created', '>=', $firstDayUnix] );
$postData = self::$db->getPaginated( $this->tableName, $whereClause );
if ( !$postData->count() ) {
Debug::info( 'No Blog posts found.' );
return false;
}
return $this->filter( $postData->results() );
}
public function byAuthor( $ID, $includeDraft = false ) {
if ( !Check::id( $ID ) ) {
Debug::info( 'Invalid Author' );
return false;
}
$whereClause = [];
if ( $includeDraft !== true ) {
$whereClause = ['draft', '=', '0', 'AND'];
}
$whereClause = array_merge( $whereClause, ['author' => $ID] );
$postData = self::$db->getPaginated( $this->tableName, $whereClause );
if ( !$postData->count() ) {
Debug::info( 'No Blog posts found.' );
return false;
}
return $this->filter( $postData->results() );
}
public function byMonth( $month, $year = 0, $includeDraft = false ) {
if ( 0 === $year ) {
$year = date( 'Y' );
}
if ( !Check::id( $month ) ) {
Debug::info( 'Invalid Month' );
return false;
}
if ( !Check::id( $year ) ) {
Debug::info( 'Invalid Year' );
return false;
}
$whereClause = [];
if ( $includeDraft !== true ) {
$whereClause = ['draft', '=', '0', 'AND'];
}
$firstDayUnix = date( 'U', strtotime( "$month/01/$year" ) );
$month = date( 'F', $firstDayUnix );
$lastDayUnix = date( 'U', strtotime( "last day of $month $year" ) );
$whereClause = array_merge( $whereClause, ['created', '<=', $lastDayUnix, 'AND', 'created', '>=', $firstDayUnix] );
$postData = self::$db->getPaginated( $this->tableName, $whereClause );
if ( !$postData->count() ) {
Debug::info( 'No Blog posts found.' );
return false;
}
return $this->filter( $postData->results() );
}
}

View File

@ -0,0 +1,47 @@
<?php
/**
* app/plugins/blog/plugin.php
*
* This houses all of the main plugin info and functionality.
*
* @package TP Blog
* @version 3.0
* @author Joey Kimsey <Joey@thetempusproject.com>
* @link https://TheTempusProject.com
* @license https://opensource.org/licenses/MIT [MIT LICENSE]
*/
namespace TheTempusProject\Plugins;
use 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';
public $pluginAuthor = 'JoeyK';
public $pluginWebsite = 'https://TheTempusProject.com';
public $modelVersion = '1.0';
public $pluginVersion = '3.0';
public $pluginDescription = 'A simple plugin to add a blog to your installation.';
public $admin_links = [
[
'text' => '<i class="glyphicon glyphicon-text-size"></i> Blog',
'url' => '{ROOT_URL}admin/blog',
],
];
public $footer_links = [
[
'text' => 'Blog',
'url' => '{ROOT_URL}blog/index',
],
];
public $posts;
public function __construct( $load = false ) {
$this->posts = new Posts;
parent::__construct( $load );
}
}

View File

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

View File

@ -0,0 +1,112 @@
<!DOCTYPE html>
<html lang="en">
<!--
* app/plugins/blog/templates/blog.tpl
*
* @package TP Blog
* @version 3.0
* @author Joey Kimsey <Joey@thetempusproject.com>
* @link https://TheTempusProject.com
* @license https://opensource.org/licenses/MIT [MIT LICENSE]
-->
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta property="og:url" content="{CURRENT_URL}">
<meta name='twitter:card' content='summary' />
<title>{TITLE}</title>
<meta itemprop="name" content="{TITLE}">
<meta name="twitter:title" content="{TITLE}">
<meta property="og:title" content="{TITLE}">
<meta name="description" content="{PAGE_DESCRIPTION}">
<meta itemprop="description" content="{PAGE_DESCRIPTION}">
<meta name="twitter:description" content="{PAGE_DESCRIPTION}">
<meta property="og:description" content="{PAGE_DESCRIPTION}">
<meta itemprop="image" content="{META_IMAGE}">
<meta name="twitter:image" content="{META_IMAGE}">
<meta property="og:image" content="{META_IMAGE}">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="author" content="The Tempus Project">
{ROBOT}
<link rel="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="{BOOTSTRAP_CDN}css/bootstrap.min.css" crossorigin="anonymous">
<!-- RSS -->
<link rel="alternate" href="{ROOT_URL}blog/rss" title="{TITLE} Feed" type="application/rss+xml" />
<!-- Custom styles for this template -->
{TEMPLATE_CSS_INCLUDES}
</head>
<body>
<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">
{topNavLeft}
<div class="navbar-right">
<ul class="nav navbar-nav">
{topNavRight}
</ul>
</div>
</div>
</div>
</nav>
<div class="container-fluid">
<div class="foot-pad">
{ISSUES}
<div class="row">
<div class="container">
{ERROR}
{NOTICE}
{SUCCESS}
</div>
</div>
{/ISSUES}
<div class="row">
<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}
{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}
</div>
</div>
<!-- /.blog-sidebar -->
</div>
<!-- /.row -->
</div>
</div>
</div>
</div>
<footer>
{FOOT}
{COPY}
</footer>
<!-- 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>
<!-- Custom javascript for this template -->
{TEMPLATE_JS_INCLUDES}
</body>
</html>

View File

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

View File

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

View File

@ -0,0 +1,31 @@
<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>
</div>
<div class="form-group">
<div class="col-lg-6 col-lg-offset-3 btn-group">
<button type="button" class="btn btn-sm btn-primary" onclick="insertTag ('blogPost', 'c');">&#10004;</button>
<button type="button" class="btn btn-sm btn-primary" onclick="insertTag ('blogPost', 'x');">&#10006;</button>
<button type="button" class="btn btn-sm btn-primary" onclick="insertTag ('blogPost', '!');">&#10069;</button>
<button type="button" class="btn btn-sm btn-primary" onclick="insertTag ('blogPost', '?');">&#10068;</button>
</div>
</div>
<div class="form-group">
<label for="blogPost" class="col-lg-3 control-label">Post</label>
<div class="col-lg-6">
<textarea class="form-control" name="blogPost" maxlength="2000" rows="10" cols="50" id="blogPost"></textarea>
</div>
</div>
<div class="form-group">
<div class="col-lg-6 col-lg-offset-3">
<button name="submit" value="publish" type="submit" class="btn btn-lg btn-primary">Publish</button>
<button name="submit" value="saveDraft" type="submit" class="btn btn-lg btn-primary">Save as Draft</button>
<button name="submit" value="preview" type="submit" class="btn btn-lg btn-primary">Preview</button>
</div>
</div>
<input type="hidden" name="token" value="{TOKEN}">
</form>

View File

@ -0,0 +1,30 @@
<legend>New Posts</legend>
<table class="table table-striped">
<thead>
<tr>
<th style="width: 20%"></th>
<th style="width: 65%"></th>
<th style="width: 5%"></th>
<th style="width: 5%"></th>
<th style="width: 5%"></th>
</tr>
</thead>
<tbody>
{LOOP}
<tr>
<td>{title}</td>
<td>{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>
</tr>
{/LOOP}
{ALT}
<tr>
<td align="center" colspan="5">
No results to show.
</td>
</tr>
{/ALT}
</tbody>
</table>

View File

@ -0,0 +1,31 @@
<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>
</div>
<div class="form-group">
<div class="col-lg-6 col-lg-offset-3 btn-group">
<button type="button" class="btn btn-sm btn-primary" onclick="insertTag ('blogPost', 'c');">&#10004;</button>
<button type="button" class="btn btn-sm btn-primary" onclick="insertTag ('blogPost', 'x');">&#10006;</button>
<button type="button" class="btn btn-sm btn-primary" onclick="insertTag ('blogPost', '!');">&#10069;</button>
<button type="button" class="btn btn-sm btn-primary" onclick="insertTag ('blogPost', '?');">&#10068;</button>
</div>
</div>
<div class="form-group">
<label for="blogPost" class="col-lg-3 control-label">Post</label>
<div class="col-lg-6">
<textarea class="form-control" name="blogPost" maxlength="2000" rows="10" cols="50" id="blogPost">{content}</textarea>
</div>
</div>
<div class="form-group">
<div class="col-lg-6 col-lg-offset-3">
<button name="submit" value="publish" type="submit" class="btn btn-lg btn-primary">Publish</button>
<button name="submit" value="saveDraft" type="submit" class="btn btn-lg btn-primary">Save as Draft</button>
<button name="submit" value="preview" type="submit" class="btn btn-lg btn-primary">Preview</button>
</div>
</div>
<input type="hidden" name="token" value="{TOKEN}">
</form>

View File

@ -0,0 +1,45 @@
<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>

View File

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

View File

@ -0,0 +1,11 @@
<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 -->

View File

@ -0,0 +1,18 @@
{PAGINATION}
{LOOP}
<div class="blog-post">
<h2 class="blog-post-title"><a href="{ROOT_URL}blog/post/{ID}">{title}</a></h2>
<hr>
<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>
{/LOOP}
{ALT}
<div class="blog-post">
<p class="blog-post-meta">No Posts Found.</p>
</div>
{/ALT}

View File

@ -0,0 +1,18 @@
<div class="row">
<div class="col-lg-12 col-sm-12 blog-main">
<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>
{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>
<hr>
{/ADMIN}
</div><!-- /.blog-post -->
{COMMENTS}
{NEWCOMMENT}
</div><!-- /.blog-main -->
</div><!-- /.row -->

View File

@ -0,0 +1,15 @@
<div class="panel panel-info">
<div class="panel-heading">
<h3 class="panel-title">Recent Posts</h3>
</div>
<ul class="list-group">
{LOOP}
<li class="list-group-item">
<a href="{ROOT_URL}blog/post/{ID}">{title}</a>
</li>
{/LOOP}
{ALT}
<li class="list-group-item">No Posts to show</li>
{/ALT}
</ul>
</div>

View File

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

View File

@ -0,0 +1,18 @@
<div class="panel panel-info">
<div class="panel-heading">
<h3 class="panel-title">Recent Posts</h3>
</div>
<div class="panel-body">
<ol class="list-unstyled">
{LOOP}
<li><a href="{ROOT_URL}blog/post/{ID}">{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>
</div>

View File

@ -0,0 +1,14 @@
<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>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,62 @@
<?php
/**
* app/plugins/bookmarks/plugin.php
*
* This houses all of the main plugin info and functionality.
*
* @package TP Bookmarks
* @version 3.0
* @author Joey Kimsey <Joey@thetempusproject.com>
* @link https://TheTempusProject.com
* @license https://opensource.org/licenses/MIT [MIT LICENSE]
*/
namespace TheTempusProject\Plugins;
use TheTempusProject\Classes\Plugin;
use TheTempusProject\Models\Events;
use TheTempusProject\Models\Bookmarks as Bookmark;
use TheTempusProject\Models\Folders;
use TheTempusProject\Houdini\Classes\Components;
use TheTempusProject\Houdini\Classes\Template;
class Bookmarks extends Plugin {
public $pluginName = 'TP Bookmarks';
public $configName = 'bookmarks';
public $pluginAuthor = 'JoeyK';
public $pluginWebsite = 'https://TheTempusProject.com';
public $modelVersion = '1.0';
public $pluginVersion = '3.0';
public $pluginDescription = 'A simple plugin which adds a site wide bookmark system.';
public $permissionMatrix = [
'useBookmarks' => [
'pretty' => 'Can use the bookmarks feature',
'default' => false,
],
'createEvents' => [
'pretty' => 'Can add events to bookmarks',
'default' => false,
],
];
public $main_links = [
[
'text' => 'Bookmarks',
'url' => '{ROOT_URL}bookmarks/index',
'filter' => 'loggedin',
],
];
public $configMatrix = [
'enabled' => [
'type' => 'radio',
'pretty' => 'Enable Bookmarks.',
'default' => true,
],
];
public $bookmarks;
public $folders;
public function __construct( $load = false ) {
$this->bookmarks = new Bookmark;
$this->folders = new Folders;
parent::__construct( $load );
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,42 @@
<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>

View File

@ -0,0 +1,64 @@
<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>
<tr>
<td align="left" width="200"><b>ID</b></td>
<td align="right">{ID}</td>
</tr>
<tr>
<td><b>Time submitted</b></td>
<td align="right">{DTC}{time}{/DTC}</td>
</tr>
<tr>
<td><b>Submitted by</b></td>
<td align="right"><a href="{ROOT_URL}admin/users/view/{userID}">{submittedBy}</a></td>
</tr>
<tr>
<td><b>IP</b></td>
<td align="right">{ip}</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>
</tr>
<tr>
<td colspan="2">{description}</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<div class="panel-footer">
{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>
</form>
{/ADMIN}
</div>
</div>
</div>
</div>
</div>

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