Compare commits

...

84 Commits

Author SHA1 Message Date
15875d1fa3 wip 2025-02-25 13:38:08 -05:00
20f2f059a1 wip 2025-02-25 13:31:36 -05:00
153404dff9 share display fix 2025-02-06 04:53:10 -05:00
0f4e044e51 Merge branch 'main' into allthebookmarks-com 2025-02-06 02:52:50 -05:00
2ac64e5c49 image update 2025-02-05 22:46:24 -05:00
5590592ebe composer bump 2025-02-05 22:05:19 -05:00
b65dda1328 composer changes 2025-02-05 20:56:39 -05:00
f928e87141 bugfixes 2025-02-05 20:45:41 -05:00
a6b241c7f0 add qr-codes, share button, and pwa config toggle 2025-02-05 19:39:54 -05:00
da6adc35b0 Merge branch 'main' into allthebookmarks-com 2025-02-05 06:43:05 -05:00
72cbdc222e Merge remote-tracking branch 'gitlab/main' into allthebookmarks-com 2025-02-05 06:38:08 -05:00
a38d132e61 rename comments 2025-02-05 06:36:53 -05:00
d7e8b586d7 various updates
remove dependence on jQuery
add image delete
Admin ui fix for mobile
image updates to new style
update comments
2025-02-05 06:36:29 -05:00
02f3241965 wip 2025-02-05 06:09:07 -05:00
ea120e09bc add missing webmanifest parts 2025-02-05 05:30:23 -05:00
2aed4ec2ed composer bump 2025-02-04 21:34:36 -05:00
bd939cc078 composer bump 2025-02-03 12:47:23 -05:00
68ffa8dd7c Merge branch 'main' into allthebookmarks-com 2025-02-03 10:59:02 -05:00
1fb4d2eb57 wip 2025-02-03 10:29:09 -05:00
e52ae78ed0 composer bump 2025-02-02 18:44:53 -05:00
0b97afe7b5 merge from main 2025-02-02 04:48:49 -05:00
5e621883ff Various changes
mobile-friendly ui updates
admin user-edit bugfix
file cleanup
added searchFields
add blog search
remove unused code
add maintenance mode config
2025-02-02 02:16:43 -05:00
b5996dc7db remove unused code 2025-02-02 01:55:15 -05:00
bc33b4cac4 cleanup and added admin images control 2025-02-02 01:55:15 -05:00
2301e212bf Add mobile tutorials 2025-01-30 16:06:06 -05:00
928d78ce44 Merge branch 'main' into allthebookmarks-com 2025-01-30 13:53:35 -05:00
b8d2550901 WIP 2025-01-30 13:14:06 -05:00
02892dfe6d remove unused code 2025-01-30 12:56:14 -05:00
1180ad236a from prod 2025-01-28 17:19:24 -05:00
e7730cec54 remove unused code 2025-01-28 17:13:55 -05:00
f7371ae579 WIP 2025-01-28 17:04:05 -05:00
ca850bb46b UI fixes and composer bump 2025-01-27 00:26:43 -05:00
35b7be92a6 bugfixes and small features
Fixed config switches not registering the correct current value
Added better ux when image uploads are disabled
Fixed an issue where uploaded files were not being handled correctly
Added the ability to disable user registrations
Fixed some variables being unintendedly protected
2025-01-26 15:13:34 -05:00
d4751696f3 sendmail bugfix 2025-01-23 22:39:11 -05:00
ee2cbb0e39 add redirect link to admin 2025-01-23 22:25:53 -05:00
fa12dd20ba add redirect link to admin 2025-01-23 22:25:33 -05:00
41a6aed209 route bugfix 2025-01-23 22:22:08 -05:00
a98527daae route bugfix 2025-01-23 22:21:41 -05:00
7170cb20a0 wip from prod 2025-01-03 17:21:14 -05:00
1ef85c6c2c wip 2025-01-01 22:17:38 -05:00
ccc134d1b2 wip 2024-12-26 13:39:59 -05:00
900ca8a691 wip 2024-12-24 22:15:01 -05:00
f8e75e847d mvp 2024-12-21 16:26:05 -05:00
0c2fa757dd wip 2024-12-20 16:55:45 -05:00
7b4dc5f41f Merge branch 'main' into allthebookmarks-com 2024-12-20 14:12:15 -05:00
d428950be6 branding 2024-12-20 14:07:45 -05:00
528f003558 main 2024-12-20 05:56:40 -05:00
1496b855db many fixes and updates 2024-12-20 05:53:57 -05:00
e7ec79e727 Merge branch 'main' into allthebookmarks-com 2024-12-18 08:58:48 -05:00
59279c8349 wip 2024-12-18 08:56:58 -05:00
06bd337d36 asset changes 2024-12-18 07:51:53 -05:00
021e97c567 Merge branch 'main' into allthebookmarks-com 2024-12-17 23:04:31 -05:00
e537771708 plugin wip 2024-12-17 22:45:47 -05:00
0955fb4175 WIP on allthebookmarks-com 2024-12-15 17:25:05 -05:00
cd4aea4492 Merge branch 'main' into allthebookmarks-com 2024-12-15 17:21:03 -05:00
ab2f009e5b Add bookmark exports and sharing + various fixes 2024-12-15 17:20:57 -05:00
3ef97138a2 Merge branch 'main' into allthebookmarks-com 2024-12-14 06:16:50 -05:00
c0e211eda7 plugin wip 2024-12-14 06:16:09 -05:00
03f1c978e3 Merge branch 'main' into allthebookmarks-com 2024-12-14 05:57:26 -05:00
175cd32f80 swap images 2024-12-14 05:56:13 -05:00
c2d0f04ea4 save server stuff 2024-12-14 05:54:54 -05:00
ccad15c0b8 remove unused code 2024-12-12 17:14:55 -05:00
a8a99b37e3 Add new plugins: WIP, Suggestions, Reviews 2024-12-11 08:00:06 -05:00
6a6fe4171f Merge branch 'main' into allthebookmarks-com 2024-12-11 07:50:25 -05:00
a1c849a626 wip 2024-12-11 07:49:48 -05:00
3bc838ce24 Merge branch 'main' into allthebookmarks-com 2024-12-11 02:09:04 -05:00
861d1f0734 wip 2024-12-11 02:08:30 -05:00
c1427d490b remove unused plugins 2024-12-10 09:14:56 -05:00
07de896d1d cleanup 2024-12-10 09:04:42 -05:00
f34da1aefd Merge branch 'main' into allthebookmarks-com 2024-12-10 09:00:59 -05:00
2e789998a5 extensions and fixes 2024-12-10 09:00:35 -05:00
ab8a8e2c35 Merge branch 'main' into allthebookmarks-com 2024-12-10 08:50:53 -05:00
4dbab11fec Merge branch 'main' into allthebookmarks-com 2024-12-09 20:00:29 -05:00
b575fe82aa plugin fixes 2024-12-09 19:59:59 -05:00
66383d8d96 update to bootstrap 5.2.3 2024-12-09 19:59:35 -05:00
1ce12c8c5d Merge branch 'main' into allthebookmarks-com 2024-12-08 04:43:53 -05:00
c4cd445b7d update to bootstrap 4 p2 2024-12-08 04:40:32 -05:00
dcbe7c2ac0 update to bootstrap 4 p1, bugfix for logoLarge, logging cleanup, more 2024-12-08 04:35:25 -05:00
9abd3865f5 Merge branch 'main' into allthebookmarks-com 2024-12-08 04:17:51 -05:00
755646ba30 add bookmarks api and iimports, bugfixes 2024-12-07 01:55:13 -05:00
dcffa0a7fb cleanup 2024-12-05 15:37:43 -05:00
6144604626 Merge branch 'main' into allthebookmarks-com 2024-12-05 15:35:03 -05:00
402182714e Add donate and memberships 2024-12-05 15:33:25 -05:00
03aedc3020 add memberships and bookmarks 2024-12-03 00:16:51 -05:00
518 changed files with 14811 additions and 5169 deletions

2
.gitignore vendored
View File

@ -56,7 +56,6 @@ Temporary Items
.htaccess
app/config/*
!app/config/constants.php
uploads/images/*
logs/*
.vscode/
mail.log
@ -65,3 +64,4 @@ vendor/canary/logs/*
components/*
mailhog.log
uploads/*
images/qr-codes/*

View File

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

View File

@ -1,127 +0,0 @@
<?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")
;

View File

@ -1,47 +0,0 @@
# 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/

View File

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

25
LICENSE
View File

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

131
README.md
View File

@ -1,131 +0,0 @@
# The Tempus Project
_Rapid Prototyping Framework built on PHP utilizing the MVC pattern with a Bootstrap front-end_
__Developer(s):__
- __Joey Kimsey__ - _Lead Developer_
The aim of this project is to provide a simple and stable platform from which to easily add functionality. The goal being the ability to quickly build and test new projects with a lightweight ecosystem to help.
**Notice: This code is in _still_ not production ready. This framework is provided as is, use at your own risk.**\
I am working very hard to ensure the system is safe and reliable enough for me to endorse its widespread use. Unfortunately, it still needs a lot of QA and improvements.
## Table of contents
[[_TOC_]]
## Find Us
* [DockerHub](https://hub.docker.com/repositories/thetempusproject)
* [Packagist](https://packagist.org/users/joeyk4816/packages/)
* [GitLab](https://git.thetempusproject.com/the-tempus-project/thetempusproject)
## Summary
The Tempus Project is a PHP application utilizing the MVC pattern to serve up simple pages and APIs with minimal effort. It requires a MySQL database to function and is designed to run equally well with nginx or apache powering the webserver. Most of the core functionality is developed in house and provided through dependencies. At this time, the frontend is driven on bootstrap 3 and FontAwesome for simplicity.
## Features
- A Plugin system that allows plug-and-play functionality
- A User management system
- groups
- permissions
- preferences
- registration and recovery
(All Controlled dynamically via our plugin interface)
- Compatibility with both Apache and NGINX
- Built with Bootstrap with a focus on mobile compatibility
- Incredibly easy to set-up, deploy, and develop
## Installation
The preferred method for installation is [Composer](#composer) but special attention has been given to installation and usage [without Composer](#composer).
### Composer
The simplest method to start a new project is to use composer to create a new project and automatically clone all the necessary files:
#### via create-project
```
composer create-project thetempusproject/thetempusproject test-app
```
#### via clone & install
1. Clone the directory to wherever you want to install the framework.
`git clone https://git.thetempusproject.com/the-tempus-project/thetempusproject.git <test-app>`
1. Open your terminal to the directory you previously cloned the repository.
`cd <test-app>`
1. Install using composer:
`php composer.phar install`
### Manually
1. Clone the directory to wherever you want to install the framework.
`git clone https://git.thetempusproject.com/the-tempus-project/thetempusproject.git <test-app>`
1. Open your terminal to the directory you previously cloned the repository.
`cd <test-app>/`
1. Clone the dependency directories to the vendor/ folder.
```
cd vendor/
git clone https://git.thetempusproject.com/the-tempus-project/bedrock.git bedrock
git clone https://git.thetempusproject.com/the-tempus-project/canary.git canary
git clone https://git.thetempusproject.com/the-tempus-project/hermes.git hermes
git clone https://git.thetempusproject.com/the-tempus-project/houdini.git houdini
```
__Note:__ The autoloader should automatically detect and use the dependencies, but they need to be sorted into the folders ans shown above.
## Docker
To enable quick deployment and collaboration The Tempus Project is distributed with the files to build your own docker images or stack with apache or nginx The included `docker-compose.yml` will load up an entire stack including apache and nginx, as well as a MySQL server with phpmyadmin.
You will need docker installed on your system then you can either download the latest images from DockerHud:
```
docker pull thetempusproject/ttp-apache
docker pull thetempusproject/ttp-nginx
```
Or you can build your own images from this repository. More information can be found in the included README files:
* [Apache Image](docker/ttp-apache/README.md)
* [Nginx Image](docker/ttp-nginx/README.md)
### Docker-Compose
The Docker stack included here will build new versions of the nginx and apache webserver and launch them in individual containers. It will also create 2 more containers; one for php, and one for phpmyadmin.
```
docker-compose -f docker-compose.yml up --build -d --no-cache
```
__Note:__ If you cloned the repository from git, you will need to copy the `docker/.env.example` to `.env` in the root directory and update the contents before proceeding with docker-compose.
## Contributing
TheTempusProject is an open source project and welcomes community contributions. Please refer to the [Contributing file](CONTRIBUTING.md) for more details.
## License
See the [LICENSE](LICENSE) file for licensing information as it pertains to files in this repository.
## Known Issues
- [ ] The blog plugin should add a welcome post during the installResources step of the installer. It doesn't work right now.
## Currently being developed
- [ ] Adding documentation
- [ ] Unit testing
## Future updates
- [ ] Expansion of PDO to allow different database types
- [ ] Update installer to account for database deltas, allowing easy updating.
- [ ] Implement uniformity in terms of error reporting, exceptions, logging.
- [ ] I want to make an api that allows you to download and install new plugins from a centralized repository
- [ ] i want plugin instalation to be compatible with composer for easier management of added plugins.

View File

@ -5,7 +5,7 @@
* This is the base admin controller. Every other admin controller should
* extend this class.
*
* @version 3.0
* @version 5.0.1
* @author Joey Kimsey <Joey@thetempusproject.com>
* @link https://TheTempusProject.com
* @license https://opensource.org/licenses/MIT [MIT LICENSE]

View File

@ -5,7 +5,7 @@
* This is the base api controller. Every other api controller should
* extend this class.
*
* @version 3.0
* @version 5.0.1
* @author Joey Kimsey <Joey@thetempusproject.com>
* @link https://TheTempusProject.com
* @license https://opensource.org/licenses/MIT [MIT LICENSE]

View File

@ -4,7 +4,7 @@
*
* This class handles all the hard-coded configurations.
*
* @version 3.0
* @version 5.0.1
* @author Joey Kimsey <Joey@thetempusproject.com>
* @link https://TheTempusProject.com
* @license https://opensource.org/licenses/MIT [MIT LICENSE]
@ -35,7 +35,7 @@ class Config extends BedrockConfig {
case 'radio':
case 'bool':
case 'boolean':
$fieldHtml = Forms::getSwitchHtml( $fieldname, [ 'true', 'false' ], $node['value'] );
$fieldHtml = Forms::getSwitchHtml( $fieldname, $node['value'] );
break;
case 'select':
$fieldHtml = Forms::getSelectHtml( $fieldname, $options, $node['value'] );
@ -69,14 +69,21 @@ class Config extends BedrockConfig {
$html .= '<div class="col-lg-6">';
$html .= $fieldHtml;
$html .= '</div>';
$html .= '</div>';
if ( 'file' === $node['type'] ) {
$html .= '<div class="mb-3 row">';
$html .= '<h4 class="col-lg-3 col-form-label text-end">Current Image</h4>';
$html .= '<h4 class="col-lg-3 col-form-label text-end">Current Value</h4>';
$html .= '<div class="col-lg-6">';
$html .= '<img alt="User Avatar" src="{ROOT_URL}' . $node['value'] . '" class="img-circle img-fluid p-2 avatar-125">';
$html .= '<input type="text" class="form-control" name="'.$name.'Text" value="'.$node['value'] . '">';
$html .= '</div>';
$html .= '</div>';
$html .= '<div class="mb-3 row">';
$html .= '<h4 class="col-lg-3 col-form-label text-end">Current Image</h4>';
$html .= '<div class="col-lg-6 d-flex justify-content-center">';
$html .= '<img alt="configured image" src="{ROOT_URL}' . $node['value'] . '" class="img-circle img-fluid p-2 avatar-125">';
$html .= '</div>';
$html .= '</div>';
}
$html .= '</div>';
return Template::parse( $html );
}

View File

@ -4,7 +4,7 @@
*
* This is the main controller class.
*
* @version 3.0
* @version 5.0.1
* @author Joey Kimsey <Joey@thetempusproject.com>
* @link https://TheTempusProject.com
* @license https://opensource.org/licenses/MIT [MIT LICENSE]

View File

@ -4,7 +4,7 @@
*
* This is the main TempusProject database model.
*
* @version 3.0
* @version 5.0.1
* @author Joey Kimsey <Joey@thetempusproject.com>
* @link https://TheTempusProject.com
* @license https://opensource.org/licenses/MIT [MIT LICENSE]
@ -121,7 +121,6 @@ class DatabaseModel extends BedrockDatabaseModel {
$errors = [];
foreach ( self::$installFlags as $flag_name ) {
if ( empty( $options[$flag_name] ) ) {
$module_data[ $flag_name ] = INSTALL_STATUS_SKIPPED;
continue;
}

View File

@ -4,7 +4,7 @@
*
* This is our class for constructing and sending various kinds of emails.
*
* @version 3.0
* @version 5.0.1
* @author Joey Kimsey <Joey@thetempusproject.com>
* @link https://TheTempusProject.com
* @license https://opensource.org/licenses/MIT [MIT LICENSE]

View File

@ -7,7 +7,7 @@
* error reporting to easily define exactly what feedback you
* would like to give.
*
* @version 3.0
* @version 5.0.1
* @author Joey Kimsey <Joey@thetempusproject.com>
* @link https://TheTempusProject.com
* @license https://opensource.org/licenses/MIT [MIT LICENSE]
@ -115,6 +115,8 @@ class Forms extends Check {
self::addHandler( 'adminCreateToken', __CLASS__, 'adminCreateToken' );
self::addHandler( 'apiLogin', __CLASS__, 'apiLogin' );
self::addHandler( 'updatePreference', __CLASS__, 'updatePreference' );
self::addHandler( 'renameIImage', __CLASS__, 'renameIImage' );
self::addHandler( 'addImage', __CLASS__, 'addImage' );
self::addHandler( 'installStart', __CLASS__, 'install', [ 'start' ] );
self::addHandler( 'installAgreement', __CLASS__, 'install', [ 'agreement' ] );
self::addHandler( 'installCheck', __CLASS__, 'install', [ 'check' ] );
@ -348,6 +350,10 @@ class Forms extends Check {
self::addUserError( 'Invalid username.' );
return false;
}
if ( $user->usernameExists( Input::post( 'username' ) ) ) {
self::addUserError( 'A user with that username is already registered.' );
return false;
}
if ( !self::password( Input::post( 'password' ) ) ) {
self::addUserError( 'Invalid password.' );
return false;
@ -663,4 +669,28 @@ class Forms extends Check {
}
return true;
}
public static function renameIImage() {
if ( !Input::exists( 'filelocation' ) ) {
self::addUserError( 'You must specify a location' );
return false;
}
if ( !Input::exists( 'newname' ) ) {
self::addUserError( 'You must specify a new name' );
return false;
}
return true;
}
public static function addImage() {
if ( !Input::exists( 'folderSelect' ) ) {
self::addUserError( 'You must specify a location' );
return false;
}
if ( !Input::exists( 'uploadImage' ) ) {
self::addUserError( 'You must include a file.' );
return false;
}
return true;
}
}

View File

@ -6,7 +6,7 @@
* 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
* @version 5.0.1
* @author Joey Kimsey <Joey@thetempusproject.com>
* @link https://TheTempusProject.com
* @license https://opensource.org/licenses/MIT [MIT LICENSE]
@ -158,6 +158,10 @@ class Installer {
} else {
self::$installJson['modules'][$name]['enabled_txt'] = '<span class="text-danger">No</span>';
}
// in this case only, we save an array to remove the objects later, so an array stored is a success.
if ( ! empty( self::$installJson['modules'][$name]['resources_installed'] ) && is_array( self::$installJson['modules'][$name]['resources_installed'] ) ) {
self::$installJson['modules'][$name]['resources_installed'] = INSTALL_STATUS_SUCCESS;
}
}
return self::$installJson['modules'][$name];
}
@ -422,7 +426,7 @@ class Installer {
}
// exclude any flags that have already been successfully installed
if ( !empty( $module_data->$flag_type ) && $module_data->$flag_type == INSTALL_STATUS_SUCCESS ) {
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;
}
@ -530,7 +534,7 @@ class Installer {
}
foreach ( $flags as $flag_type ) {
if ( ! in_array( $modelInfo[ $flag_type ], [ INSTALL_STATUS_SUCCESS, INSTALL_STATUS_NOT_REQUIRED ] ) ) {
if ( empty( $modelInfo[ $flag_type ] ) || ! in_array( $modelInfo[ $flag_type ], [ INSTALL_STATUS_SUCCESS, INSTALL_STATUS_NOT_REQUIRED ] ) ) {
$modelInfo['installStatus'] = INSTALL_STATUS_PARTIALLY_INSTALLED;
break;
}

View File

@ -4,7 +4,7 @@
*
* This class handles all the hard-coded permissions.
*
* @version 3.0
* @version 5.0.1
* @author Joey Kimsey <Joey@thetempusproject.com>
* @link https://TheTempusProject.com
* @license https://opensource.org/licenses/MIT [MIT LICENSE]
@ -245,7 +245,7 @@ class Permissions {
public static function getFieldEditHtml( $name, $default, $pretty ) {
$fieldname = str_ireplace( '/', '-', $name );
$fieldHtml = Forms::getSwitchHtml( $fieldname, [ 'true', 'false' ], $default );
$fieldHtml = Forms::getSwitchHtml( $fieldname, $default );
$html = '';
$html .= '<div class="mb-3 row">';
$html .= '<label for="' . $fieldname . '" class="col-lg-6 col-form-label text-end">' . $pretty . '</label>';

View File

@ -4,7 +4,7 @@
*
* This class is used as a foundation for all plugins to build from.
*
* @version 3.0
* @version 5.0.1
* @author Joey Kimsey <Joey@thetempusproject.com>
* @link https://TheTempusProject.com
* @license https://opensource.org/licenses/MIT [MIT LICENSE]
@ -89,7 +89,7 @@ class Plugin {
foreach ( self::PLUGIN_FLAG_MAP as $flag_name => $function_name ) {
if ( empty( $options[$flag_name] ) ) {
$module_data[ $flag_name ] = INSTALL_STATUS_SKIPPED;
// $module_data[ $flag_name ] = INSTALL_STATUS_SKIPPED;
continue;
}
@ -280,7 +280,7 @@ class Plugin {
public function installResources( $options = '' ) {
if ( empty( $this->resourceMatrix ) ) {
Debug::log( 'resourceMatrix is empty' );
return true;
return INSTALL_STATUS_NOT_REQUIRED;
}
$ids = [];
foreach( $this->resourceMatrix as $tableName => $entries ) {

View File

@ -4,7 +4,7 @@
*
* This class handles all the hard-coded preferences.
*
* @version 3.0
* @version 5.0.1
* @author Joey Kimsey <Joey@thetempusproject.com>
* @link https://TheTempusProject.com
* @license https://opensource.org/licenses/MIT [MIT LICENSE]
@ -19,6 +19,7 @@ use TheTempusProject\Bedrock\Functions\Check;
use TheTempusProject\Bedrock\Functions\Upload;
use TheTempusProject\Bedrock\Functions\Input;
use TheTempusProject\TheTempusProject as App;
use TheTempusProject\Bedrock\Classes\Config;
class Preferences {
public static $preferences = false;
@ -208,6 +209,15 @@ class Preferences {
if ( $tempPrefsArray['type'] == 'checkbox' ) {
$tempPrefsArray['type'] = 'switch';
}
if ( 'file' === $tempPrefsArray['type'] ) {
// dv( Config::getValue( 'uploads/images' ) );
if ( ! Config::getValue( 'uploads/images' ) ) {
Debug::info( 'Preference hidden because uploads are disabled.' );
continue;
}
}
$inputTypes[ $tempPrefsArray['type'] ][] = self::getFormFieldHtml( $name, $tempPrefsArray['pretty'], $tempPrefsArray['type'], $tempPrefsArray['value'], $tempPrefsArray['options'] );
}
foreach ( $inputTypes as $skip => $items ) {
@ -258,15 +268,15 @@ class Preferences {
}
$html .= '<div class="mb-3 row">';
$html .= '<label for="' . $fieldname . '" class="col-lg-6 col-form-label text-end">' . $fieldTitle . '</label>';
$html .= '<label for="' . $fieldname . '" class="col-lg-6 col-form-label text-start text-lg-end">' . $fieldTitle . '</label>';
$html .= '<div class="col-lg-6">';
$html .= $fieldHtml;
$html .= '</div>';
if ( 'file' === $type ) {
$html .= '<div class="mb-3 row">';
$html .= '<h4 class="col-lg-6 col-form-label text-end">Current Image</h4>';
$html .= '<h4 class="col-lg-6 col-form-label text-start text-lg-end">Current Image</h4>';
$html .= '<div class="col-lg-6">';
$html .= '<img alt="User Avatar" src="{ROOT_URL}' . $defaultValue . '" class="img-circle img-fluid p-2 avatar-125">';
$html .= '<img alt="preferred image" src="{ROOT_URL}' . $defaultValue . '" class="img-circle img-fluid p-2">';
$html .= '</div>';
}
$html .= '</div>';
@ -295,6 +305,7 @@ class Preferences {
$prefsArray[$name] = $route . Upload::last();
} else {
Issues::add( 'error', [ 'There was an error with your upload.' => Check::userErrors() ] );
unset( $prefsArray[$name] );
}
}
}

View File

@ -38,8 +38,8 @@ if ( ! defined( 'CONFIG_DIRECTORY' ) ) {
# Tempus Debugger
define( 'CANARY_SECURE_HASH', 'd73ed7591a30f0ca7d686a0e780f0d05' );
# Tempus Project Core
define( 'APP_NAME', 'The Tempus Project');
define( 'TP_DEFAULT_LOGO', 'images/logo.png');
define( 'APP_NAME', 'All The Bookmarks');
define( 'TP_DEFAULT_LOGO', 'images/logo180.png');
// Check
define( 'MINIMUM_PHP_VERSION', 8.1);
// Cookies

View File

@ -4,7 +4,7 @@
*
* This is the admin log controller.
*
* @version 3.0
* @version 5.0.1
* @author Joey Kimsey <Joey@thetempusproject.com>
* @link https://TheTempusProject.com
* @license https://opensource.org/licenses/MIT [MIT LICENSE]

View File

@ -4,7 +4,7 @@
*
* This is the composer controller. Its only very effective when using composer for autoloading.
*
* @version 3.0
* @version 5.0.1
* @author Joey Kimsey <Joey@thetempusproject.com>
* @link https://TheTempusProject.com
* @license https://opensource.org/licenses/MIT [MIT LICENSE]

View File

@ -4,7 +4,7 @@
*
* This is the error logs controller.
*
* @version 3.0
* @version 5.0.1
* @author Joey Kimsey <Joey@thetempusproject.com>
* @link https://TheTempusProject.com
* @license https://opensource.org/licenses/MIT [MIT LICENSE]

View File

@ -4,7 +4,7 @@
*
* This is the groups admin controller.
*
* @version 3.0
* @version 5.0.1
* @author Joey Kimsey <Joey@thetempusproject.com>
* @link https://TheTempusProject.com
* @license https://opensource.org/licenses/MIT [MIT LICENSE]
@ -32,8 +32,6 @@ class Groups extends AdminController {
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 ) {

View File

@ -4,7 +4,7 @@
*
* This is the admin dashboard controller.
*
* @version 3.0
* @version 5.0.1
* @author Joey Kimsey <Joey@thetempusproject.com>
* @link https://TheTempusProject.com
* @license https://opensource.org/licenses/MIT [MIT LICENSE]
@ -17,9 +17,12 @@ use TheTempusProject\Classes\AdminController;
use TheTempusProject\Models\User;
use TheTempusProject\Models\Comments;
use TheTempusProject\Models\Posts;
use TheTempusProject\Models\Contact;
use TheTempusProject\Plugins\Comments as CommentPlugin;
use TheTempusProject\Plugins\Blog as BlogPlugin;
use TheTempusProject\Plugins\Contact as ContactPlugin;
use TheTempusProject\Canary\Bin\Canary as Debug;
use TheTempusProject\Bedrock\Functions\Input;
class Home extends AdminController {
public static $user;
@ -58,6 +61,19 @@ class Home extends AdminController {
}
}
if ( class_exists( 'TheTempusProject\Plugins\Contact' ) ) {
$plugin = new ContactPlugin;
if ( ! $plugin->checkEnabled() ) {
Debug::info( 'Contact Plugin is disabled in the control panel.' );
Components::set( 'contactDash', '' );
} else {
$posts = new Contact;
$postsList = Views::simpleView( 'contact.admin.dashboard', $posts->listPaginated( 5 ) );
Components::set( 'contactDash', $postsList );
}
}
self::$user = new User;
$users = Views::simpleView( 'admin.dashboard.users', self::$user->recent( 5 ) );
Components::set( 'userDash', $users );

View File

@ -0,0 +1,337 @@
<?php
/**
* app/controllers/admin/tokens.php
*
* This is the admin app/user tokens controller.
*
* @version 5.0.1
* @author Joey Kimsey <Joey@thetempusproject.com>
* @link https://TheTempusProject.com
* @license https://opensource.org/licenses/MIT [MIT LICENSE]
*/
namespace TheTempusProject\Controllers\Admin;
use TheTempusProject\Classes\Forms as TTPForms;
use TheTempusProject\Houdini\Classes\Views;
use TheTempusProject\Houdini\Classes\Issues;
use TheTempusProject\Houdini\Classes\Navigation;
use TheTempusProject\Houdini\Classes\Components;
use TheTempusProject\Houdini\Classes\Forms;
use TheTempusProject\Classes\AdminController;
use TheTempusProject\Models\Token;
use TheTempusProject\Bedrock\Functions\Input;
use TheTempusProject\Bedrock\Functions\Check;
use TheTempusProject\Hermes\Functions\Redirect;
use TheTempusProject\Bedrock\Functions\Session;
use TheTempusProject\Hermes\Functions\Route as Routes;
use TheTempusProject\Bedrock\Functions\Upload;
use RecursiveIteratorIterator;
use RecursiveDirectoryIterator;
use FilesystemIterator;
class Images extends AdminController {
private $directories = [
APP_ROOT_DIRECTORY . 'images',
APP_ROOT_DIRECTORY . 'app/images',
APP_ROOT_DIRECTORY . 'app/plugins'
];
private $spacer = [];
private $excludedDirectories = [
'.',
'..',
'vendor',
'docker',
'logs',
'gitlab',
'uploads',
'config',
];
public function upload() {
if ( Input::exists( 'submit' ) ) {
$route = '';
$destination = '';
if ( !TTPForms::check( 'addImage' ) ) {
Issues::add( 'error', [ 'There was an error with your image upload.' => Check::userErrors() ] );
} else {
$folder = Input::post( 'folderSelect' ) . DIRECTORY_SEPARATOR;
// dv( $folder );
$upload = Upload::image( 'uploadImage', $folder );
if ( $upload ) {
$route = str_replace( APP_ROOT_DIRECTORY, '', $folder );
$destination = $route . Upload::last();
Issues::add( 'success', 'Image uploaded.' );
} else {
Issues::add( 'error', [ 'There was an error with your image upload.' => Check::userErrors() ] );
}
}
}
$folders = $this->getDirectoriesRecursive( APP_ROOT_DIRECTORY );
$folderHtml = $this->generateFolderHtml( $folders );
Components::set( 'FOLDER_SELECT_ROOT', APP_ROOT_DIRECTORY );
Components::set( 'FOLDER_SELECT', Views::simpleView( 'forms.folderSelect', $folderHtml ) );
Views::view( 'admin.images.upload' );
}
private function getFolderObject( $folder, $subdirs = '' ) {
$names = explode( DIRECTORY_SEPARATOR, $folder );
$folderName = array_pop( $names );
$out = [
'spacer' => implode( '', $this->spacer ),
'folderName' => $folderName,
'location' => $folder,
'subdirs' => $subdirs,
];
if ( ! empty( $subdirs ) ) {
$out['folderexpand'] = '<i class="fa fa-caret-down"></i>';
} else {
$out['folderexpand'] = '';
}
return (object) $out;
}
private function generateFolderHtml( $folders ) {
$rows = [];
foreach ( $folders as $top => $sub ) {
$object = $this->getFolderObject( $top );
if ( $top == $sub ) {
$html = '';
} else {
$this->spacer[] = '-> ';
$children = $this->generateFolderHtml( $sub );
array_pop( $this->spacer );
Components::set( 'parentfolderName', $object->folderName );
$html = Views::simpleView( 'forms.folderSelectParent', $children );
Components::set( 'parentfolderName', '' );
}
$rows[] = $this->getFolderObject( $top, $html );
}
return $rows;
}
private function getDirectoriesRecursive( $directory ) {
$dirs = [];
$directory = rtrim( $directory, DIRECTORY_SEPARATOR );
$directory = $directory. DIRECTORY_SEPARATOR;
$files = scandir( $directory );
$filteredFiles = array_values( array_diff( $files, $this->excludedDirectories ) );
foreach ( $filteredFiles as $key => $filename ) {
$long_name = $directory . $filename;
$is_dir = ( ( strpos( $filename, '.' ) === false ) && ( is_dir( $long_name ) === true ) );
if ( $is_dir ) {
$recursive_dirs = $this->getDirectoriesRecursive( $long_name );
if ( empty( $recursive_dirs ) ) {
$recursive_dirs = $long_name;
}
$dirs[$long_name] = $recursive_dirs;
}
}
return $dirs;
}
public function __construct() {
parent::__construct();
self::$title = 'Admin - Images';
}
public function create() {
if ( Input::exists( 'submit' ) ) {
if ( !TTPForms::check( 'addImage' ) ) {
Issues::add( 'error', [ 'There was an error with your image.' => Check::userErrors() ] );
}
if ( Input::exists( 'folder' ) ) {
$folder = Input::post('folder');
} else {
// IMAGE_DIRECTORY
$folder = UPLOAD_DIRECTORY . App::$activeUser->username . DIRECTORY_SEPARATOR . 'images' . DIRECTORY_SEPARATOR;
}
$upload = Upload::image( 'upload', $folder );
if ( $upload ) {
$route = str_replace( APP_ROOT_DIRECTORY, '', $folder );
$out = $route . Upload::last();
} else {
Debug::error( 'There was an error with your upload.');
Issues::add( 'error', [ 'There was an error with your upload.' => Check::userErrors() ] );
}
}
Views::view( 'admin.images.create' );
}
public function delete() {
if ( ! Input::exists( 'fileLocation' ) ) {
Session::flash( 'warning', 'Unknown image.' );
Redirect::to( 'admin/images' );
}
$fileLocation = Input::get('fileLocation');
// Ensure the file exists
if ( ! file_exists( $fileLocation ) ) {
Session::flash('error', 'File does not exist.');
Redirect::to('admin/images');
}
// Check if the file is an image
$validMimeTypes = ['image/jpeg', 'image/png', 'image/gif', 'image/webp'];
$fileMimeType = mime_content_type( $fileLocation );
if ( ! in_array( $fileMimeType, $validMimeTypes ) ) {
Session::flash('error', 'Invalid file type. Only images can be deleted.');
Redirect::to('admin/images');
}
// Attempt to delete the file
if (@unlink($fileLocation)) {
Session::flash('success', 'Image deleted.');
} else {
Session::flash('error', 'Failed to delete the image.');
}
Redirect::to('admin/images');
}
public function rename() {
if ( ! Input::exists( 'fileLocation' ) ) {
Session::flash( 'warning', 'Unknown image.' );
Redirect::to( 'admin/images' );
}
Components::set( 'filelocation', Input::get( 'fileLocation' ) );
if ( Input::exists( 'submit' ) ) {
if ( !TTPForms::check( 'renameIImage' ) ) {
Issues::add( 'error', [ 'There was an error renaming the image.' => Check::userErrors() ] );
} else {
$result = $this->renameFile( Input::post( 'filelocation' ), Input::post( 'newname' ) );
if ( ! empty( $result ) ) {
Session::flash( 'success', 'Image has been renamed.' );
Redirect::to( 'admin/images' );
} else {
Issues::add( 'error', [ 'There was an error with the install.' => $this->installer->getErrors() ] );
}
}
}
return Views::view( 'admin.images.rename' );
}
public function index() {
return Views::view( 'admin.images.list.combined', $this->getAllImageDetails() );
}
public function view() {
if ( Input::exists( 'fileLocation' ) ) {
return Views::view( 'admin.images.view', $this->getImageByLocation( Input::get( 'fileLocation' ) ) );
}
return $this->index();
}
private function getAllImages() {
$files = [];
foreach ($this->directories as $dir) {
if ($dir === 'app/plugins') {
$pluginDirs = glob($dir . '/*', GLOB_ONLYDIR);
foreach ($pluginDirs as $pluginDir) {
$imageDir = $pluginDir . '/images';
if (is_dir($imageDir)) {
$files = array_merge($files, $this->scanDirectoryRecursively($imageDir));
}
}
} else {
$files = array_merge($files, $this->scanDirectory($dir));
}
}
return $files;
}
private function scanDirectory($path) {
return glob($path . '/*.{jpg,jpeg,png,gif,webp}', GLOB_BRACE) ?: [];
}
private function scanDirectoryRecursively($path) {
$files = [];
$iterator = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($path, FilesystemIterator::SKIP_DOTS));
foreach ($iterator as $file) {
if (preg_match('/\.(jpg|jpeg|png|gif|webp)$/i', $file->getFilename())) {
$files[] = $file->getPathname();
}
}
return $files;
}
private function getAllImageDetails() {
$images = [];
$files = $this->getAllImages();
foreach ( $files as $file ) {
$images[] = $this->getImageByLocation( $file );
}
return $images;
}
private function getImageByLocation( $location ) {
$realPath = realpath( $location );
return (object) [
'filename' => basename( $location ),
'extension' => pathinfo( $location , PATHINFO_EXTENSION),
'fileSize' => $this->formatFileSize(filesize( $location )),
'location' => $realPath,
'locationSafe' => urlencode( $realPath ),
'url' => Routes::getAddress() . str_replace( APP_ROOT_DIRECTORY, '', $realPath ),
'folder' => dirname( $location )
];
}
private function formatFileSize($size) {
$units = ['B', 'KB', 'MB', 'GB', 'TB'];
$i = 0;
while ($size >= 1024 && $i < count($units) - 1) {
$size /= 1024;
$i++;
}
return round($size, 2) . ' ' . $units[$i];
}
private function renameFile( $currentLocation, $newFilename ) {
// Ensure the file exists
if (!file_exists($currentLocation)) {
throw new \Exception("File does not exist: $currentLocation");
}
// Extract directory and current extension
$directory = dirname($currentLocation);
$currentExtension = pathinfo($currentLocation, PATHINFO_EXTENSION);
$newExtension = pathinfo($newFilename, PATHINFO_EXTENSION);
// Ensure the file extension has not changed
if (strcasecmp($currentExtension, $newExtension) !== 0) {
throw new \Exception("File extension cannot be changed.");
}
// Construct the new file path
$newLocation = $directory . DIRECTORY_SEPARATOR . $newFilename;
// Ensure the new file name does not already exist
if (file_exists($newLocation)) {
throw new \Exception("A file with the new name already exists: $newFilename");
}
// Attempt to rename the file
if (!rename($currentLocation, $newLocation)) {
throw new \Exception("Failed to rename file.");
}
return true;
}
}

View File

@ -4,7 +4,7 @@
*
* This is the login logs controller.
*
* @version 3.0
* @version 5.0.1
* @author Joey Kimsey <Joey@thetempusproject.com>
* @link https://TheTempusProject.com
* @license https://opensource.org/licenses/MIT [MIT LICENSE]

View File

@ -4,7 +4,7 @@
*
* This is the generic logs controller.
*
* @version 3.0
* @version 5.0.1
* @author Joey Kimsey <Joey@thetempusproject.com>
* @link https://TheTempusProject.com
* @license https://opensource.org/licenses/MIT [MIT LICENSE]

View File

@ -4,7 +4,7 @@
*
* This is the installed plugins controller.
*
* @version 3.0
* @version 5.0.1
* @author Joey Kimsey <Joey@thetempusproject.com>
* @link https://TheTempusProject.com
* @license https://opensource.org/licenses/MIT [MIT LICENSE]
@ -30,8 +30,6 @@ class Plugins extends AdminController {
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() {

View File

@ -4,7 +4,7 @@
*
* This is the admin routes/redirects controller.
*
* @version 3.0
* @version 5.0.1
* @author Joey Kimsey <Joey@thetempusproject.com>
* @link https://TheTempusProject.com
* @license https://opensource.org/licenses/MIT [MIT LICENSE]
@ -31,12 +31,10 @@ class Routes extends AdminController {
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 ( ! Input::exists( 'redirect_type' ) ) {
return Views::view( 'admin.routes.create' );
}

View File

@ -4,7 +4,7 @@
*
* This is the admin email controller. The only real use is to send out emails to the various lists.
*
* @version 3.0
* @version 5.0.1
* @author Joey Kimsey <Joey@thetempusproject.com>
* @link https://TheTempusProject.com
* @license https://opensource.org/licenses/MIT [MIT LICENSE]
@ -18,6 +18,7 @@ use TheTempusProject\Houdini\Classes\Issues;
use TheTempusProject\Houdini\Classes\Views;
use TheTempusProject\Models\User;
use TheTempusProject\Models\Subscribe;
use TheTempusProject\Plugins\Subscribe as Plugin;
class SendMail extends AdminController {
public static $user;
@ -27,10 +28,24 @@ class SendMail extends AdminController {
parent::__construct();
self::$title = 'Admin - Send Mail';
self::$user = new User;
self::$subscribe = new Subscribe;
if ( class_exists( 'TheTempusProject\Plugins\Subscribe' ) ) {
$plugin = new Plugin;
if ( ! $plugin->checkEnabled() ) {
Issues::add( 'notice', 'Subscriptions are disabled so those feature will be unavailable.' );
} else {
self::$subscribe = new Subscribe;
}
} else {
Issues::add( 'notice', 'Subscriptions plugin is not installed so those feature will be unavailable.' );
}
}
private function emailSubscribers( $params ) {
if ( empty( self::$subscribe ) ) {
Issues::add( 'error', 'Subscriptions plugin is unavailable' );
return;
}
$list = self::$subscribe->list();
if ( empty( $list ) ) {
Issues::add( 'error', 'No subscribers found' );

View File

@ -4,7 +4,7 @@
*
* This is the configuration and settings controller.
*
* @version 3.0
* @version 5.0.1
* @author Joey Kimsey <Joey@thetempusproject.com>
* @link https://TheTempusProject.com
* @license https://opensource.org/licenses/MIT [MIT LICENSE]

View File

@ -4,7 +4,7 @@
*
* This is the admin app/user tokens controller.
*
* @version 3.0
* @version 5.0.1
* @author Joey Kimsey <Joey@thetempusproject.com>
* @link https://TheTempusProject.com
* @license https://opensource.org/licenses/MIT [MIT LICENSE]
@ -31,8 +31,6 @@ class Tokens extends AdminController {
parent::__construct();
self::$title = 'Admin - Tokens';
self::$token = new Token;
$view = Navigation::activePageSelect( 'nav.admin', '/admin/tokens' );
Components::set( 'ADMINNAV', $view );
}
public function create() {

View File

@ -4,7 +4,7 @@
*
* This is the users admin controller.
*
* @version 3.0
* @version 5.0.1
* @author Joey Kimsey <Joey@thetempusproject.com>
* @link https://TheTempusProject.com
* @license https://opensource.org/licenses/MIT [MIT LICENSE]
@ -27,6 +27,7 @@ use TheTempusProject\Models\User;
use TheTempusProject\Models\Group;
use TheTempusProject\TheTempusProject as App;
use TheTempusProject\Houdini\Classes\Template;
use TheTempusProject\Bedrock\Functions\Upload;
class Users extends AdminController {
public static $user;
@ -37,8 +38,6 @@ class Users extends AdminController {
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() {
@ -106,7 +105,7 @@ class Users extends AdminController {
}
if ( Input::exists( 'submit' ) ) {
if ( !FormChecker::check( 'editUser' ) ) {
if ( ! FormChecker::check( 'editUser' ) ) {
Issues::add( 'error', [ 'There was an error with your request.' => Check::userErrors() ] );
} else {
$fields = [
@ -114,6 +113,25 @@ class Users extends AdminController {
'email' => Input::post( 'email' ),
'userGroup' => Input::post( 'groupSelect' ),
];
if ( Input::exists( 'avatar' ) ) {
$folder = UPLOAD_DIRECTORY . $userData->username . DIRECTORY_SEPARATOR . 'images' . DIRECTORY_SEPARATOR;
$upload = Upload::image( 'avatar', $folder );
if ( $upload ) {
$route = str_replace( APP_ROOT_DIRECTORY, '', $folder );
$prefs = [];
$prefs['avatar'] = $route . Upload::last();
self::$user->updatePrefs( $prefs, $userData->ID );
} else {
Issues::add( 'error', [ 'There was an error with your avatar.' => Check::userErrors() ] );
}
}
if ( Input::exists( 'password' ) ) {
$fields['password'] = Hash::make( Input::post( 'password' ) );
}
if ( Input::exists( 'confirmed' ) ) {
$fields['confirmed'] = 1;
} else {
@ -121,6 +139,7 @@ class Users extends AdminController {
$fields['confirmationCode'] = Code::genConfirmation();
}
}
if ( self::$user->update( $userData->ID, $fields ) ) {
Issues::add( 'success', 'User Updated.' );
return $this->index();

View File

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

@ -4,7 +4,7 @@
*
* This is the api authentication controller.
*
* @version 3.0
* @version 5.0.1
* @author Joey Kimsey <Joey@thetempusproject.com>
* @link https://TheTempusProject.com
* @license https://opensource.org/licenses/MIT [MIT LICENSE]

View File

@ -4,7 +4,7 @@
*
* This is the api authentication controller.
*
* @version 3.0
* @version 5.0.1
* @author Joey Kimsey <Joey@thetempusproject.com>
* @link https://TheTempusProject.com
* @license https://opensource.org/licenses/MIT [MIT LICENSE]

View File

@ -4,7 +4,7 @@
*
* This is the users' api controller.
*
* @version 3.0
* @version 5.0.1
* @author Joey Kimsey <Joey@thetempusproject.com>
* @link https://TheTempusProject.com
* @license https://opensource.org/licenses/MIT [MIT LICENSE]

View File

@ -4,7 +4,7 @@
*
* This is the error controller.
*
* @version 3.0
* @version 5.0.1
* @author Joey Kimsey <Joey@thetempusproject.com>
* @link https://TheTempusProject.com
* @license https://opensource.org/licenses/MIT [MIT LICENSE]

View File

@ -4,7 +4,7 @@
*
* This is the home or 'index' controller.
*
* @version 3.0
* @version 5.0.1
* @author Joey Kimsey <Joey@thetempusproject.com>
* @link https://TheTempusProject.com
* @license https://opensource.org/licenses/MIT [MIT LICENSE]
@ -27,7 +27,10 @@ use TheTempusProject\TheTempusProject as App;
class Home extends Controller {
public function index() {
self::$title = '{SITENAME}';
self::$pageDescription = '{SITENAME} is here to provide you a better, faster, and easier - way to create and manage your own web applications.';
self::$pageDescription = 'AllTheBookmarks.com is here to provide you a better, faster, and easier - way to manage bookmarks across multiple browsers and devices seamlessly.';
if ( App::$isLoggedIn ) {
return Views::view( 'bookmarks.introduction' );
}
Views::view( 'index' );
}
@ -38,15 +41,15 @@ class Home extends Controller {
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' );
return Views::view( 'auth.login' );
}
if ( !Forms::check( 'login' ) ) {
Issues::add( 'error', [ 'There was an error with your login.' => Check::userErrors() ] );
return Views::view( 'login' );
return Views::view( 'auth.login' );
}
if ( !self::$user->logIn( Input::post( 'username' ), Input::post( 'password' ), Input::post( 'remember' ) ) ) {
Issues::add( 'error', 'Username or password was incorrect.' );
return Views::view( 'login' );
return Views::view( 'auth.login' );
}
Session::flash( 'success', 'You have been logged in.' );
if ( Input::exists( 'rurl' ) ) {
@ -79,13 +82,13 @@ class Home extends Controller {
}
self::$title = $user->username . '\'s Profile - {SITENAME}';
self::$pageDescription = 'User Profile for ' . $user->username . ' - {SITENAME}';
Views::view( 'profile', $user );
Views::view( 'profilePage', $user );
}
public function terms() {
self::$title = 'Terms and Conditions - {SITENAME}';
self::$pageDescription = '{SITENAME} Terms and Conditions of use. Please use {SITENAME} safely.';
Components::set( 'TERMS', Views::simpleView( 'terms' ) );
Components::set( 'TERMS', Views::simpleView( 'auth.terms' ) );
Views::view( 'termsPage' );
}
@ -98,8 +101,7 @@ class Home extends Controller {
public function privacy() {
self::$title = 'Privacy Policy - {SITENAME}';
self::$pageDescription = 'At {SITENAME} you privacy is very important to us. On this page you can find a detailed outline of all the information we collect and how its used.';
Components::set( 'PRIVACY', Views::simpleView( 'privacy' ) );
Views::raw( '<div class="col-lg-8 mx-auto">{PRIVACY}</div>' );
Views::view( 'privacy' );
}
public function faq() {

View File

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

View File

@ -4,7 +4,7 @@
*
* This is the user registration controller.
*
* @version 3.0
* @version 5.0.1
* @author Joey Kimsey <Joey@thetempusproject.com>
* @link https://TheTempusProject.com
* @license https://opensource.org/licenses/MIT [MIT LICENSE]
@ -24,20 +24,21 @@ use TheTempusProject\Houdini\Classes\Views;
use TheTempusProject\TheTempusProject as App;
use TheTempusProject\Classes\Controller;
use TheTempusProject\Classes\Forms;
use TheTempusProject\Bedrock\Classes\Config;
class Register extends Controller {
public function confirm( $code = null ) {
Template::noIndex();
self::$title = 'Confirm Email';
if ( !isset( $code ) && !Input::exists( 'confirmationCode' ) ) {
return Views::view( 'confirmation' );
return Views::view( 'auth.confirmation' );
}
if ( Forms::check( 'emailConfirmation' ) ) {
$code = Input::post( 'confirmationCode' );
}
if ( !self::$user->confirm( $code ) ) {
Issues::add( 'error', 'There was an error confirming your account, please try again.' );
return Views::view( 'confirmation' );
return Views::view( 'auth.confirmation' );
}
Session::flash( 'success', 'You have successfully confirmed your email address.' );
Redirect::to( 'home/index' );
@ -46,16 +47,53 @@ class Register extends Controller {
public function index() {
self::$title = '{SITENAME} Sign Up';
self::$pageDescription = 'Many features of {SITENAME} are disabled or hidden from unregistered users. On this page you can sign up for an account to access all the app has to offer.';
Components::set( 'TERMS', Views::simpleView( 'terms' ) );
Components::append( 'TEMPLATE_JS_INCLUDES', '<script src="https://challenges.cloudflare.com/turnstile/v0/api.js" async defer></script>' );
Components::set( 'TURNSTILE_API_KEY', '0x4AAAAAAA1yKVCfYqpnMbvA' );
if ( ! Config::getValue( 'main/registrationEnabled' ) ) {
return Issues::add( 'notice', 'The site administrator has disable the ability to register a new account.' );
}
Components::set( 'TERMS', Views::simpleView( 'auth.terms' ) );
if ( App::$isLoggedIn ) {
return Issues::add( 'notice', 'You are currently logged in.' );
}
if ( !Input::exists() ) {
return Views::view( 'register' );
return Views::view( 'auth.register' );
}
if ( Input::exists( 'userEmail' ) ) {
// for the really bad AI / headless bots
Session::flash( 'success', 'Thank you for registering! Please check your email to confirm your account.' );
Redirect::to( 'home/index' );
}
if ( !Forms::check( 'register' ) ) {
Issues::add( 'error', [ 'There was an error with your registration.' => Check::userErrors() ] );
return Views::view( 'register' );
return Views::view( 'auth.register' );
}
if ( ! Input::exists('cf-turnstile-response') ) {
Issues::add( 'notice', 'Turnstile verification failed. Please try again.' );
return Views::view( 'auth.register' );
}
// Verify Turnstile response with Cloudflare API
$secret_key = "0x4AAAAAAA1yKZdXiv9_JrXXhF9Iw2tvQTE";
$verify_url = "https://challenges.cloudflare.com/turnstile/v0/siteverify";
$data = [
"secret" => $secret_key,
"response" => Input::post('cf-turnstile-response'),
"remoteip" => $_SERVER["REMOTE_ADDR"] // Optional, helps detect abuse
];
$options = [
"http" => [
"header" => "Content-Type: application/x-www-form-urlencoded",
"method" => "POST",
"content" => http_build_query($data)
]
];
$context = stream_context_create($options);
$response = file_get_contents($verify_url, false, $context);
$result = json_decode($response, true);
if ( ! $result["success"]) {
Issues::add( 'notice', 'Turnstile verification failed. Please try again. If the issue persists, please contact the site administrator.' );
return Views::view( 'auth.register' );
}
self::$user->create( [
'username' => Input::post( 'username' ),
@ -74,7 +112,7 @@ class Register extends Controller {
self::$title = 'Recover Account - {SITENAME}';
Template::noIndex();
if ( !Input::exists() ) {
return Views::view( 'forgot' );
return Views::view( 'auth.forgot' );
}
if ( Check::email( Input::post( 'entry' ) ) && self::$user->findByEmail( Input::post( 'entry' ) ) ) {
$userData = self::$user->data();
@ -90,7 +128,7 @@ class Register extends Controller {
Redirect::to( 'home/login' );
}
Issues::add( 'error', 'User not found.' );
Views::view( 'forgot' );
Views::view( 'auth.forgot' );
}
public function resend() {
@ -103,7 +141,7 @@ class Register extends Controller {
return Issues::add( 'notice', 'Your account has already been confirmed.' );
}
if ( !Forms::check( 'confirmationResend' ) ) {
return Views::view( 'confirmation_resend' );
return Views::view( 'auth.confirmation_resend' );
}
Email::send( App::$activeUser->email, 'confirmation', App::$activeUser->confirmationCode, [ 'template' => true ] );
Session::flash( 'success', 'Your confirmation email has been sent to the email for your account.' );
@ -115,7 +153,7 @@ class Register extends Controller {
Template::noIndex();
if ( !isset( $code ) && !Input::exists( 'resetCode' ) ) {
Issues::add( 'info', 'Please provide a reset code.' );
return Views::view( 'password_reset_code' );
return Views::view( 'auth.password_reset_code' );
}
if ( Input::exists( 'resetCode' ) ) {
if ( Forms::check( 'passwordResetCode' ) ) {
@ -124,15 +162,15 @@ class Register extends Controller {
}
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' );
return Views::view( 'auth.password_reset_code' );
}
Components::set( 'resetCode', $code );
if ( ! Input::exists('password') ) {
return Views::view( 'password_reset' );
return Views::view( 'auth.password_reset' );
}
if ( ! Forms::check( 'passwordReset' ) ) {
Issues::add( 'error', [ 'There was an error with your request.' => Check::userErrors() ] );
return Views::view( 'password_reset' );
return Views::view( 'auth.password_reset' );
}
self::$user->changePassword( $code, Input::post( 'password' ) );
Email::send( self::$user->data()->email, 'passwordChange', null, [ 'template' => true ] );

View File

@ -4,7 +4,7 @@
*
* This is the user control panel controller.
*
* @version 3.0
* @version 5.0.1
* @author Joey Kimsey <Joey@thetempusproject.com>
* @link https://TheTempusProject.com
* @license https://opensource.org/licenses/MIT [MIT LICENSE]
@ -31,7 +31,7 @@ use TheTempusProject\Bedrock\Functions\Session;
class Usercp extends Controller {
public function __construct() {
parent::__construct();
if ( !App::$isLoggedIn ) {
if ( ! App::$isLoggedIn ) {
Session::flash( 'notice', 'You must be logged in to view this page!' );
Redirect::home();
}
@ -70,7 +70,7 @@ class Usercp extends Controller {
self::$title = 'User Control Panel';
$menu = Views::simpleView( 'nav.usercp', App::$userCPlinks );
Navigation::activePageSelect( $menu, null, true, true );
Views::view( 'profile', App::$activeUser );
Views::view( 'user_cp.profile', App::$activeUser );
}
public function password() {
@ -101,7 +101,7 @@ class Usercp extends Controller {
$menu = Views::simpleView( 'nav.usercp', App::$userCPlinks );
Navigation::activePageSelect( $menu, null, true, true );
$prefs = new Preferences;
$fields = App::$activePrefs;
$userPrefs = App::$activePrefs;
if ( Input::exists( 'submit' ) ) {
$fields = $prefs->convertFormToArray( true, false );
// @TODO now i may need to rework the form checker to work with this....
@ -110,6 +110,12 @@ class Usercp extends Controller {
// }
self::$user->updatePrefs( $fields, App::$activeUser->ID );
Issues::add( 'success', 'Your preferences have been updated.' );
// if the image upload fails, need to fall back on original
if ( empty( $fields['avatar'] ) ) {
$fields['avatar'] = $userPrefs['avatar'];
}
} else {
$fields = $userPrefs;
}
Components::set( 'AVATAR_SETTINGS', $fields['avatar'] );
Components::set( 'PREFERENCES_FORM', $prefs->getFormHtml( $fields ) );

View File

@ -3,7 +3,7 @@
*
* This is css used in the debuging console.
*
* @version 3.0
* @version 5.0.1
* @author Joey Kimsey <Joey@thetempusproject.com>
* @link https://TheTempusProject.com
* @license https://opensource.org/licenses/MIT [MIT LICENSE]

View File

@ -3,38 +3,55 @@
*
* This file provides dark mode styles to override existing Bootstrap 5 base styles.
*
* @version 3.0-dark
* @version 5.0.1
* @author Joey Kimsey <Joey@thetempusproject.com>
* @link https://TheTempusProject.com
* @license https://opensource.org/licenses/MIT [MIT LICENSE]
*/
.context-popover {
background-color: #383838;
color: white;
}
.context-main {
color: #fff;
.context-popover .popover-header {
background-color: #2c2c2c;
}
.context-second {
color: #1e1e1e;
.context-main-border {
border-color: #f5f5f5!important;
}
.context-main-border-other {
border-color: #1e1e1e!important;
}
.context-main-bg {
background-color: #2c2c2c;
}
.context-second-bg {
background-color: #1e1e1e;
background-color: #383838;
}
.context-third-bg {
background-color: #3a3a3a;
}
.context-other-bg {
background-color: #1e1e1e;
}
.context-main {
color: #fff;
}
.bg-default {
background-color: #2c2c2c;
}
hr {
color: #f5f5f5;
}
.bg-none,.bg-warning {
color: #000 !important;
}
.context-other {
color: #000;
}
.accordion-button:not(.collapsed) {
color: #f5f5f5;
background-color: var(--bs-accordion-dark-active-bg);
@ -64,12 +81,6 @@ body {
.install-terms strong {
color: #ffffff;
}
.context-main {
color: #ffffff;
}
.context-other {
color: #ffffff;
}
/**
* Terms Page
@ -145,6 +156,7 @@ body {
background-color: #1f1f1f;
color: #e0e0e0;
}
.form-control:focus {
color: #e0e0e0;
border-color: #1e90ff;

View File

@ -3,19 +3,76 @@
*
* This file is for any css that should be applied site wide.
*
* @version 3.0
* @version 5.0.1
* @author Joey Kimsey <Joey@thetempusproject.com>
* @link https://TheTempusProject.com
* @license https://opensource.org/licenses/MIT [MIT LICENSE]
*/
.context-other-bg {
background-color: #eaeaea;
.facebook {
border-color: #1877F2 !important; /* Facebook Blue */
color: #1877F2 !important;
}
.x-black {
border-color: #000000 !important; /* X (formerly Twitter) Black */
color: #000000 !important;
}
.reddit {
border-color: #FF4500 !important; /* Reddit Orange */
color: #FF4500 !important;
}
.opera {
border-color: #FF1B2D !important; /* Opera Red */
color: #FF1B2D !important;
}
.firefox {
border-color: #FF7139 !important; /* Firefox Orange */
color: #FF7139 !important;
}
.edge {
border-color: #0078D7 !important; /* Microsoft Edge Blue */
color: #0078D7 !important;
}
.safari {
border-color: #0B78E3 !important; /* Safari Blue */
color: #0B78E3 !important;
}
.context-main-border {
border-color: #1e1e1e!important;
}
.context-main-border-other {
border-color: #f5f5f5!important;
}
.context-main-bg {
background-color: #f7f7f7;
/* background-color: #b1b; */
}
.context-second-bg {
background-color: #eaeaea;
/* background-color: #b1b; */
}
.context-third-bg {
background-color: #ccc;
/* background-color: #b1b; */
}
.context-main {
color: #000;
}
.nav-link.active {
font-weight: bold; /* Make the text bold */
}
hr {
color: #000;
}
/* Base styles for the switch container */
@ -57,7 +114,7 @@
bottom: 2.5px;
left: 5px;
transition: transform 0.3s ease-in-out;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
box-shadow: 0 2px 4px #00000033;
}
/* Change background color when checked */
@ -70,20 +127,6 @@
transform: translateX(25px); /* Adjust based on switch width */
}
.context-main {
color: #000;
}
.context-other {
color: #fff;
}
html {
font-family: 'Open Sans', sans-serif;
position: relative;
@ -312,3 +355,79 @@ body {
/* W3C, IE 10+/ Edge, Firefox 16+, Chrome 26+, Opera 12+, Safari 7+ */
background: linear-gradient(to right, #2c2c2c, #1e1e1e, #1e1e1e);
}
.atb-green {
color: #85bd3e;
}
a.atb-green:hover {
color: #1b947f;
}
button.atb-green:hover {
color: #1b947f;
}
a.atb-green.active {
color: #1b947f !important;
border-color: #1b947f;
}
.atb-green-outline-only {
border-color: #85bd3e;
}
a.atb-green-outline-only:hover {
border-color: #85bd3e;
}
button.atb-green-outline-only:hover {
border-color: #85bd3e;
}
.atb-green-outline {
color: #85bd3e;
border-color: #85bd3e;
}
a.atb-green-outline:hover {
color: #1b947f;
border-color: #1b947f;
}
button.atb-green-outline:hover {
color: #1b947f;
border-color: #1b947f;
}
.atb-green-bg {
color: #fff;
background-color: #85bd3e;
border-color: #85bd3e;
}
a.atb-green-bg:hover {
background-color: #1b947f;
border-color: #1b947f;
}
button.atb-green-bg:hover {
background-color: #1b947f;
border-color: #1b947f;
}
.bookmark-card.dragging {
opacity: 0.7;
cursor: move; /* Show a move cursor when dragging */
}
.form-switch .form-check-input {
border-color: #1b947f;
}
.form-switch .form-check-input:checked {
border-color: #1b947f;
background-color: #85bd3e;
}
.form-switch .form-check-input:focus {
border-color: #1b947f;
box-shadow: #85bd3e;
}

View File

@ -1,218 +0,0 @@
/**
* 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;
}
}

View File

@ -2,7 +2,7 @@
/**
* app/functions/common.php
*
* @version 3.0
* @version 5.0.1
* @author Joey Kimsey <Joey@thetempusproject.com>
* @link https://TheTempusProject.com
* @license https://opensource.org/licenses/MIT [MIT LICENSE]

BIN
app/images/clean-simple.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

BIN
app/images/in-one-place.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

BIN
app/images/keep-track.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 80 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 56 KiB

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 117 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 45 KiB

View File

@ -3,11 +3,68 @@
*
* This file is for 'access anywhere' javascript.
*
* @version 3.0
* @version 5.0.1
* @author Joey Kimsey <Joey@thetempusproject.com>
* @link https://TheTempusProject.com
* @license https://opensource.org/licenses/MIT [MIT LICENSE]
*/
let deferredPrompt;
const installPrompt = document.getElementById("install-prompt");
const installButton = document.getElementById("install-button");
const dismissButton = document.querySelector("#install-prompt .btn-close");
// Check if the user previously dismissed the prompt
if (!localStorage.getItem("pwaInstallDismissed")) {
window.addEventListener("beforeinstallprompt", (event) => {
event.preventDefault();
deferredPrompt = event;
installPrompt.classList.remove("d-none");
installPrompt.classList.add("d-block"); // Show the prompt
});
}
// Handle Install Button Click
if ( installButton ) {
installButton.addEventListener("click", async () => {
if (deferredPrompt) {
deferredPrompt.prompt();
const { outcome } = await deferredPrompt.userChoice;
if (outcome === "dismissed") {
setInstallDismissed(); // Store that the user dismissed the prompt
}
deferredPrompt = null; // Reset prompt
installPrompt.classList.remove("d-block");
installPrompt.classList.add("d-none");
}
});
}
// Handle Close Button Click
if ( dismissButton ) {
dismissButton.addEventListener("click", () => {
setInstallDismissed(); // Store that the user dismissed the prompt
});
}
// Function to remember user choice for 7 days
function setInstallDismissed() {
localStorage.setItem("pwaInstallDismissed", Date.now() + 7 * 24 * 60 * 60 * 1000);
installPrompt.classList.remove("d-block"); // Hide the prompt
installPrompt.classList.add("d-none");
}
// Check if the 7-day period has passed
if (localStorage.getItem("pwaInstallDismissed")) {
const dismissUntil = parseInt(localStorage.getItem("pwaInstallDismissed"), 10);
if (Date.now() < dismissUntil) {
//
} else {
localStorage.removeItem("pwaInstallDismissed"); // Reset after 7 days
}
}
/**
* Automatically selects/de-selects all check boxes associated with that field
**/
@ -80,14 +137,14 @@ function copyElementText( id ) {
}
}
$(document).ready(function() {
$('select').each(function() {
var selectedValue = $(this).attr('value');
document.addEventListener("DOMContentLoaded", function () {
document.querySelectorAll("select").forEach(function (select) {
var selectedValue = select.getAttribute("value");
if (selectedValue) {
$(this).removeAttr('value');
$(this).find('option').each(function() {
if ($(this).attr('value') === selectedValue) {
$(this).prop('selected', true);
select.removeAttribute("value");
select.querySelectorAll("option").forEach(function (option) {
if (option.getAttribute("value") === selectedValue) {
option.selected = true;
}
});
}
@ -125,7 +182,7 @@ document.addEventListener('DOMContentLoaded', function () {
if ( toggleButton ) {
toggleButton.checked = true;
}
if ( enableButton ) {
enableButton.innerText = 'Disable Now';
}
@ -158,32 +215,63 @@ document.addEventListener('DOMContentLoaded', function () {
toggleButton.addEventListener('click', function () {
if (darkModeStylesheet.disabled) {
toggleDarkModePref( true );
darkModeStylesheet.disabled = false;
localStorage.setItem('darkMode', 'enabled');
} else {
toggleDarkModePref( false );
darkModeStylesheet.disabled = true;
localStorage.setItem('darkMode', 'disabled');
}
document.querySelectorAll('.table-striped').forEach((table) => {
if (localStorage.getItem('darkMode') === 'enabled') {
table.classList.add('table-dark');
table.classList.remove('table-light');
darkModeStylesheet.disabled = false;
localStorage.setItem('darkMode', 'enabled');
} else {
table.classList.add('table-light');
table.classList.remove('table-dark');
toggleDarkModePref( false );
darkModeStylesheet.disabled = true;
localStorage.setItem('darkMode', 'disabled');
}
document.querySelectorAll('.table-striped').forEach((table) => {
if (localStorage.getItem('darkMode') === 'enabled') {
table.classList.add('table-dark');
table.classList.remove('table-light');
} else {
table.classList.add('table-light');
table.classList.remove('table-dark');
}
});
});
}
function toggleDarkModePref( value ) {
var fields = {};
fields.prefName = 'darkMode';
fields.prefValue = value;
$.post( '/usercp/updatePref', fields ).done(function(response) {
// alert('Timer updated successfully!');
});
function toggleDarkModePref(value) {
var fields = new URLSearchParams();
fields.append("prefName", "darkMode");
fields.append("prefValue", value);
fetch("/usercp/updatePref", {
method: "POST",
body: fields,
headers: {
"Content-Type": "application/x-www-form-urlencoded",
},
})
// .then(response => response.text()) // Handle response if needed
.catch(error => console.error("Error:", error));
}
});
// this reverses the carets for the folderSelect
document.querySelectorAll('[data-bs-toggle="collapse"]').forEach(button => {
button.addEventListener('click', () => {
setTimeout(() => {
const icon = button.querySelector('i');
// Only proceed if the icon already has one of the relevant classes
if (icon && (icon.classList.contains('fa-caret-down') || icon.classList.contains('fa-caret-up'))) {
icon.classList.toggle('fa-caret-down', button.classList.contains('collapsed'));
icon.classList.toggle('fa-caret-up', !button.classList.contains('collapsed'));
}
}, 150);
});
});
// this should load all popovers
document.addEventListener("DOMContentLoaded", function () {
var popoverTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="popover"]'));
var popoverList = popoverTriggerList.map(function (popoverTriggerEl) {
return new bootstrap.Popover(popoverTriggerEl);
});
});

View File

@ -1,233 +0,0 @@
/**
* 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');
}
}

View File

@ -4,7 +4,7 @@
*
* This class is used for the manipulation of the groups database table.
*
* @version 3.0
* @version 5.0.1
* @author Joey Kimsey <Joey@thetempusproject.com>
* @link https://TheTempusProject.com
* @license https://opensource.org/licenses/MIT [MIT LICENSE]
@ -38,11 +38,22 @@ class Group extends DatabaseModel {
[ 'name', 'varchar', '32' ],
[ 'permissions', 'text', '' ],
];
public $searchFields = [
'name',
];
public $permissionMatrix = [
'adminAccess' => [
'pretty' => 'Access Administrator Areas',
'default' => false,
],
'uploadImages' => [
'pretty' => 'Upload images (such as avatars)',
'default' => false,
],
'maintenanceAccess' => [
'pretty' => 'Upload images (such as avatars)',
'default' => false,
],
];
public $resourceMatrix = [
[

View File

@ -4,7 +4,7 @@
*
* Model for handling all logging.
*
* @version 3.0
* @version 5.0.1
* @author Joey Kimsey <Joey@thetempusproject.com>
* @link https://TheTempusProject.com
* @license https://opensource.org/licenses/MIT [MIT LICENSE]
@ -46,6 +46,9 @@ class Log extends DatabaseModel {
[ 'source', 'varchar', '64' ],
[ 'action', 'text', '' ],
];
public $searchFields = [
'source',
];
/**
* The model constructor.

View File

@ -4,7 +4,7 @@
*
* This class is used for the manipulation of the routes database table.
*
* @version 3.0
* @version 5.0.1
* @author Joey Kimsey <Joey@thetempusproject.com>
* @link https://TheTempusProject.com
* @license https://opensource.org/licenses/MIT [MIT LICENSE]
@ -24,6 +24,9 @@ class Routes extends DatabaseModel {
[ 'original_url', 'varchar', '32' ],
[ 'forwarded_url', 'text', '' ],
];
public $searchFields = [
'nickname',
];
public $resourceMatrix = [
[
'original_url' => 'fb',

View File

@ -7,7 +7,7 @@
* 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
* @version 5.0.1
* @author Joey Kimsey <Joey@thetempusproject.com>
* @link https://TheTempusProject.com
* @license https://opensource.org/licenses/MIT [MIT LICENSE]
@ -36,6 +36,9 @@ class Sessions extends DatabaseModel {
[ 'username', 'varchar', '20' ],
[ 'token', 'varchar', '120' ],
];
public $searchFields = [
'username',
];
public static $activeSession = false;
/**

View File

@ -4,7 +4,7 @@
*
* This class is used for the manipulation of the tokens database table.
*
* @version 3.0
* @version 5.0.1
* @author Joey Kimsey <Joey@thetempusproject.com>
* @link https://TheTempusProject.com
* @license https://opensource.org/licenses/MIT [MIT LICENSE]
@ -31,6 +31,10 @@ class Token extends DatabaseModel {
[ 'createdBy', 'int', '10' ],
[ 'expiresAt', 'int', '10' ],
];
public $searchFields = [
'name',
'token',
];
public $permissionMatrix = [
'addAppToken' => [
'pretty' => 'Add Application Tokens',

View File

@ -8,7 +8,7 @@
* @todo finish fixing the check functions that were migrated here
* These could go in the Forms class?
*
* @version 3.0
* @version 5.0.1
* @author Joey Kimsey <Joey@thetempusproject.com>
* @link https://TheTempusProject.com
* @license https://opensource.org/licenses/MIT [MIT LICENSE]
@ -44,11 +44,8 @@ class User extends DatabaseModel {
[ 'confirmationCode', 'varchar', '80' ],
[ 'prefs', 'text', '' ],
];
public $permissionMatrix = [
'uploadImages' => [
'pretty' => 'Upload images (such as avatars)',
'default' => false,
],
public $searchFields = [
'username',
];
public $preferenceMatrix = [
'gender' => [
@ -428,7 +425,7 @@ class User extends DatabaseModel {
if ( ! empty( $filter ) ) {
switch ( $filter ) {
case 'newsletter':
$data = self::$db->search( $this->tableName, 'prefs', 'newsletter":"true' );
$data = self::$db->searchColumn( $this->tableName, 'prefs', 'newsletter":"true' );
break;
default:
$data = self::$db->get( $this->tableName, '*' );
@ -546,6 +543,7 @@ class User extends DatabaseModel {
$instance->prefs = json_decode( $instance->prefs, true );
$instance->gender = $instance->prefs['gender'];
$instance->avatar = $instance->prefs['avatar'];
$instance->usernamePretty = \ucfirst( $instance->username );
$out[] = $instance;
if ( !empty( $end ) ) {
$out = $out[0];
@ -659,7 +657,7 @@ class User extends DatabaseModel {
}
if ( !self::$db->update( $this->tableName, $id, $fields ) ) {
new CustomException( 'userUpdate' );
Debug::error( "User: $id not updated: $fields" );
Debug::error( "User: $id not updated: " . var_export( $fields, true ) );
return false;
}
return true;

View File

@ -5,7 +5,7 @@
* This is the Blog admin controller.
*
* @package TP Blog
* @version 3.0
* @version 5.0.1
* @author Joey Kimsey <Joey@thetempusproject.com>
* @link https://TheTempusProject.com
* @license https://opensource.org/licenses/MIT [MIT LICENSE]
@ -29,8 +29,6 @@ class Blog extends AdminController {
parent::__construct();
self::$posts = new Posts;
self::$title = 'Admin - Blog';
$view = Navigation::activePageSelect( 'nav.admin', '/admin/blog' );
Components::set( 'ADMINNAV', $view );
}
public function index( $data = null ) {

View File

@ -5,7 +5,7 @@
* This is the blog controller.
*
* @package TP Blog
* @version 3.0
* @version 5.0.1
* @author Joey Kimsey <Joey@thetempusproject.com>
* @link https://TheTempusProject.com
* @license https://opensource.org/licenses/MIT [MIT LICENSE]
@ -171,4 +171,16 @@ class Blog extends Controller {
self::$pageDescription = '{SITENAME} blog posts easily and conveniently sorted by years.';
Views::view( 'blog.list', self::$posts->byYear( $year ) );
}
public function search() {
$results = [];
if ( Input::exists( 'submit' ) ) {
$dbResults = self::$posts->search( Input::post('searchTerm') );
if ( ! empty( $dbResults ) ) {
$results = $dbResults;
}
}
Components::set( 'searchResults', Views::simpleView( 'blog.list', $results ) );
Views::view( 'blog.searchResults' );
}
}

View File

@ -5,7 +5,7 @@
* This houses all of the form checking functions for this plugin.
*
* @package TP Blog
* @version 3.0
* @version 5.0.1
* @author Joey Kimsey <Joey@thetempusproject.com>
* @link https://TheTempusProject.com
* @license https://opensource.org/licenses/MIT [MIT LICENSE]

View File

@ -5,7 +5,7 @@
* This class is used for the manipulation of the blog database table.
*
* @package TP Blog
* @version 3.0
* @version 5.0.1
* @author Joey Kimsey <Joey@thetempusproject.com>
* @link https://TheTempusProject.com
* @license https://opensource.org/licenses/MIT [MIT LICENSE]
@ -24,6 +24,11 @@ use TheTempusProject\Models\Comments;
class Posts extends DatabaseModel {
public $tableName = 'posts';
public $searchFields = [
'title',
'slug',
'content',
];
public static $comments = false;
public $databaseMatrix = [
@ -168,9 +173,11 @@ class Posts extends DatabaseModel {
$draft = ' <b>Draft</b>';
}
$instance->isDraft = $draft;
$instance->authorName = $authorName;
$instance->authorName = \ucfirst( $authorName );
if ( self::$comments !== false ) {
$instance->commentCount = self::$comments->count( 'blog', $instance->ID );
} else {
$instance->commentCount = 0;
}
$instance->content = Filters::applyOne( 'mentions.0', $instance->content, true );
$instance->content = Filters::applyOne( 'hashtags.0', $instance->content, true );

View File

@ -5,7 +5,7 @@
* This houses all of the main plugin info and functionality.
*
* @package TP Blog
* @version 3.0
* @version 5.0.1
* @author Joey Kimsey <Joey@thetempusproject.com>
* @link https://TheTempusProject.com
* @license https://opensource.org/licenses/MIT [MIT LICENSE]
@ -37,6 +37,7 @@ class Blog extends Plugin {
'posts' => [
[
'title' => 'Welcome',
'slug' => 'welcome',
'content' => '<p>This is just a simple message to say thank you for installing The Tempus Project. If you have any questions you can find everything through our website <a href="https://TheTempusProject.com">here</a>.</p>',
'author' => 1,
'created' => '{time}',

View File

@ -5,7 +5,7 @@
* This is the loader for the blog template.
*
* @package TP Blog
* @version 3.0
* @version 5.0.1
* @author Joey Kimsey <Joey@thetempusproject.com>
* @link https://TheTempusProject.com
* @license https://opensource.org/licenses/MIT [MIT LICENSE]
@ -26,9 +26,10 @@ class BlogLoader extends DefaultLoader {
*/
public function __construct() {
$posts = new Posts;
Components::set('SIDEBAR', Views::simpleView('blog.sidebar', $posts->recent(5)));
Components::set('SIDEBAR2', Views::simpleView('blog.sidebar2', $posts->archive()));
Components::set('SIDEBARABOUT', Views::simpleView('blog.about'));
Components::set('SIDEBAR', Views::simpleView('blog.widgets.recent', $posts->recent(5)));
Components::set('SIDEBAR2', Views::simpleView('blog.widgets.archive', $posts->archive()));
Components::set('SIDEBARABOUT', Views::simpleView('blog.widgets.about'));
Components::set('SIDEBARSEARCH', Views::simpleView('blog.widgets.search'));
Components::set('BLOGFEATURES', '');
Navigation::setCrumbComponent( 'BLOG_BREADCRUMBS', Input::get( 'url' ) );
Components::set( 'BLOG_TEMPLATE_URL', Template::parse( '{ROOT_URL}app/plugins/comments/' ) );

View File

@ -4,7 +4,7 @@
* app/plugins/blog/templates/blog.tpl
*
* @package TP Blog
* @version 3.0
* @version 5.0.1
* @author Joey Kimsey <Joey@thetempusproject.com>
* @link https://TheTempusProject.com
* @license https://opensource.org/licenses/MIT [MIT LICENSE]
@ -39,25 +39,44 @@
</head>
<body>
<!-- Navigation -->
<header class="p-3 text-bg-dark">
<div class="container">
<div class="d-flex flex-wrap align-items-center justify-content-center justify-content-lg-start">
<img src="{ROOT_URL}{LOGO}" class="bi me-2" width="40" height="32" role="img" aria-label="{SITENAME} Logo">
<a href="/" class="d-flex align-items-center mb-2 mb-lg-0 text-white text-decoration-none">
{SITENAME}
</a>
{topNavLeft}
<div class="text-end d-flex align-items-center">
{topNavRight}
</div>
</div>
</div>
</header>
<header class="p-3 text-bg-dark">
<div class="container">
<div class="d-flex align-items-center position-relative">
<!-- Navbar Toggler (Left) -->
<!-- Centered Logo (Now inside normal document flow) -->
<a href="/" class="align-items-center text-white text-decoration-none d-flex d-md-none">
<img src="{ROOT_URL}{LOGO}" width="40" height="32" alt="{SITENAME} Logo" class="bi">
</a>
<!-- Logo (Normal Position for Large Screens) -->
<a href="/" class="align-items-center text-white text-decoration-none d-none d-md-flex">
<img src="{ROOT_URL}{LOGO}" width="40" height="32" alt="{SITENAME} Logo" class="bi">
</a>
<div class="navbar-expand-md flex-grow-1">
<div class="collapse navbar-collapse d-md-flex" id="mainMenu">
<!-- Centered Navigation -->
<div class="d-none d-md-block d-flex justify-content-center position-absolute start-50 translate-middle-x">
{topNavLeft}
</div>
<div class="d-flex justify-content-center flex-grow-1 d-md-none">
{topNavLeft}
</div>
<!-- Right-Side Content (Push to End) -->
<div class="d-flex flex-row justify-content-center align-items-center mt-3 mt-md-0 ms-md-auto">
{topNavRight}
</div>
</div> <!-- End Collapse -->
</div> <!-- End Navbar Expand -->
<button class="me-3 d-md-none btn btn-md btn-outline-light" type="button" data-bs-toggle="collapse" data-bs-target="#mainMenu" aria-controls="mainMenu" aria-expanded="false" aria-label="Toggle navigation">
<i class="fa fa-bars"></i>
</button>
</div>
</div>
</header>
<div class="d-flex flex-column min-vh-100">
<div class="flex-container flex-grow-1">
@ -79,24 +98,27 @@
<div class="pt-4">
<div class="container">
<h3 class="pb-4 mb-4 fst-italic border-bottom">
<h3 class="pb-4 mb-4 fst-italic border-bottom context-main-border">
{SITENAME} Blog
</h3>
<div class="row g-5">
<div class="d-md-flex g-5">
<!-- Main Content -->
<div class="col-md-8">
<div class="col-12 col-md-8">
{CONTENT}
</div>
<!-- Sidebar Content -->
<div class="col-md-4">
<div class="col-12 col-md-4">
<div class="position-sticky" style="top: 2rem;">
<div class="p-4">
<div class="ps-md-2 ps-lg-5">
{SIDEBARABOUT}
</div>
<div class="p-4">
<div class="ps-md-2 ps-lg-5">
{SIDEBARSEARCH}
</div>
<div class="ps-md-2 ps-lg-5">
{SIDEBAR}
</div>
<div class="p-4">
<div class="ps-md-2 ps-lg-5">
{SIDEBAR2}
</div>
</div>
@ -108,7 +130,6 @@
{FOOT}
</div>
<!-- Bootstrap core JavaScript and jquery -->
<script language="JavaScript" crossorigin="anonymous" type="text/javascript" src="{JQUERY_CDN}jquery.min.js"></script>
<script language="JavaScript" crossorigin="anonymous" type="text/javascript" src="https://cdn.jsdelivr.net/npm/@popperjs/core@2.11.6/dist/umd/popper.min.js"></script>
<script language="JavaScript" crossorigin="anonymous" type="text/javascript" src="{BOOTSTRAP_CDN}js/bootstrap.min.js"></script>
<!-- Custom javascript for this template -->

View File

@ -5,7 +5,7 @@
* This is the loader for the rss template.
*
* @package TP Blog
* @version 3.0
* @version 5.0.1
* @author Joey Kimsey <Joey@thetempusproject.com>
* @link https://TheTempusProject.com
* @license https://opensource.org/licenses/MIT [MIT LICENSE]

View File

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

View File

@ -2,7 +2,7 @@
<legend class="text-center">Add Blog Post</legend>
<hr>
{ADMIN_BREADCRUMBS}
<form action="" method="post">
<form method="post">
<fieldset>
<!-- Title -->
<div class="mb-3 row">

View File

@ -2,7 +2,7 @@
<legend class="text-center">Edit Blog Post</legend>
<hr>
{ADMIN_BREADCRUMBS}
<form action="" method="post">
<form method="post">
<fieldset>
<!-- Title -->
<div class="mb-3 row">

View File

@ -21,7 +21,7 @@
<tbody>
{LOOP}
<tr>
<td><a href="{ROOT_URL}admin/blog/view/{ID}">{title}</a>{isDraft}</td>
<td><a href="{ROOT_URL}admin/blog/view/{ID}" class="text-decoration-none">{title}</a>{isDraft}</td>
<td>{authorName}</td>
<td>{commentCount}</td>
<td>{DTC}{created}{/DTC}</td>

View File

@ -7,7 +7,7 @@
</div><!-- /.blog-post -->
</div><!-- /.blog-main -->
</div><!-- /.row -->
<form action="" method="post" class="form-horizontal" enctype="multipart/form-data">
<form method="post" enctype="multipart/form-data">
<legend>New Blog Post</legend>
<div class="form-group">
<label for="title" class="col-lg-3 control-label">Title</label>

View File

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

View File

@ -1,9 +1,9 @@
<div class="row">
<div class="col-lg-12 col-sm-12 blog-main">
<div class="blog-post">
<div class="col-lg-12 col-sm-12 blog-main context-main-bg p-1 p-md-2 mb-2 rounded">
<div class="blog-post mb-5 pb-5">
<h2 class="blog-post-title">{title}</h2>
<hr>
<p class="blog-post-meta">{DTC date}{created}{/DTC} by <a href="{ROOT_URL}home/profile/{author}" class="text-decoration-none">{authorName}</a></p>
<p class="blog-post-meta">{DTC date}{created}{/DTC} by <a href="{ROOT_URL}home/profile/{authorName}" class="text-decoration-none atb-green">{authorName}</a></p>
{content}
{ADMIN}
<hr>

View File

@ -1,15 +0,0 @@
<div class="card">
<div class="card-header bg-info">
<h3 class="card-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,3 @@
<legend class="text-center my-2">Search Results</legend>
<hr>
{searchResults}

View File

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

View File

@ -1,11 +0,0 @@
<div class="p-4">
<h4 class="fst-italic">Archives</h4>
<ul class="list-unstyled mb-0">
{LOOP}
<li>({count}) <a href="{ROOT_URL}blog/month/{month}/{year}" class="text-decoration-none">{monthText} {year}</a></li>
{/LOOP}
{ALT}
<li>None To Show</li>
{/ALT}
</ul>
</div>

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,68 @@
<?php
/**
* app/plugins/bookmarks/controllers/api/bookmark_folders.php
*
* This is the api bookmark folders controller.
*
* @version 3.0
* @author Joey Kimsey <Joey@thetempusproject.com>
* @link https://TheTempusProject.com
* @license https://opensource.org/licenses/MIT [MIT LICENSE]
*/
namespace TheTempusProject\Controllers\Api;
use TheTempusProject\Models\User;
use TheTempusProject\Classes\ApiController;
use TheTempusProject\Houdini\Classes\Views;
use TheTempusProject\Canary\Bin\Canary as Debug;
use TheTempusProject\Bedrock\Functions\Input;
use TheTempusProject\Models\Folders;
class BookmarkFolders extends ApiController {
protected static $folders;
public function __construct() {
header('Access-Control-Allow-Origin: *');
parent::__construct();
self::$folders = new Folders;
}
public function create() {
$user = self::$authToken->createdBy;
$payload = @file_get_contents('php://input');
$payload = json_decode( $payload, true );
$result = self::$folders->create(
$payload['name'],
$payload['folder'] ?? 0,
$payload['notes'] ?? '',
$payload['color'] ?? 'default',
$payload['privacy'] ?? 'private',
$user
);
if ( ! $result ) {
$responseType = 'error';
$response = 'There was an error creating your folder.';
} else {
$responseType = 'id';
$response = $result;
}
Views::view( 'api.response', ['response' => json_encode( [ $responseType => $response ], true )]);
}
public function list( $id = '' ) {
$user = self::$authToken->createdBy;
$folders = self::$folders->bySpecificUser( $user );
if ( ! $folders ) {
$responseType = 'error';
$response = 'There was an error creating your folder.';
} else {
$responseType = 'folders';
$response = $folders;
}
Views::view( 'api.response', ['response' => json_encode( [ $responseType => $response ], true )]);
}
}

View File

@ -0,0 +1,56 @@
<?php
/**
* app/plugins/bookmarks/controllers/api/bookmarks.php
*
* This is the api bookmarks controller.
*
* @version 3.0
* @author Joey Kimsey <Joey@thetempusproject.com>
* @link https://TheTempusProject.com
* @license https://opensource.org/licenses/MIT [MIT LICENSE]
*/
namespace TheTempusProject\Controllers\Api;
use TheTempusProject\Models\User;
use TheTempusProject\Classes\ApiController;
use TheTempusProject\Houdini\Classes\Views;
use TheTempusProject\Canary\Bin\Canary as Debug;
use TheTempusProject\Bedrock\Functions\Input;
use TheTempusProject\Models\Bookmarks as Bookmark;
class Bookmarks extends ApiController {
protected static $bookmarks;
public function __construct() {
parent::__construct();
self::$bookmarks = new Bookmark;
}
public function create() {
header('Access-Control-Allow-Origin: *');
$user = self::$authToken->createdBy;
$payload = @file_get_contents('php://input');
$payload = json_decode( $payload, true );
$result = self::$bookmarks->create(
$payload['name'],
$payload['url'],
$payload['folder'] ?? 0,
$payload['notes'] ?? '',
$payload['color'] ?? 'default',
$payload['privacy'] ?? 'private',
'external',
$user
);
if ( ! $result ) {
$responseType = 'error';
$response = 'There was an error creating your folder.';
} else {
$responseType = 'data';
$response = $result;
}
Views::view( 'api.response', ['response' => json_encode( [ $responseType => $response ], true )]);
}
}

View File

@ -0,0 +1,992 @@
<?php
/**
* app/plugins/bookmarks/controllers/bookmarks.php
*
* This is the bookmarks controller.
*
* @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\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\Models\BookmarkDashboards as Dashboards;
use TheTempusProject\TheTempusProject as App;
use TheTempusProject\Houdini\Classes\Components;
use TheTempusProject\Houdini\Classes\Forms as HoudiniForms;
use TheTempusProject\Houdini\Classes\Navigation;
use TheTempusProject\Houdini\Classes\Template;
use TheTempusProject\Hermes\Functions\Route as Routes;
use TheTempusProject\Models\User;
use TheTempusProject\Classes\Preferences;
use TheTempusProject\Canary\Bin\Canary as Debug;
class Bookmarks extends Controller {
protected static $bookmarks;
protected static $folders;
protected static $dashboards;
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::$dashboards = new Dashboards;
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 );
Components::set( 'SITE_URL', Routes::getAddress() );
Views::raw( $tabsView );
Components::append( 'TEMPLATE_JS_INCLUDES', Template::parse('<script language="JavaScript" crossorigin="anonymous" type="text/javascript" src="{ROOT_URL}app/plugins/bookmarks/js/bookmarks.js"></script>' ) );
$viewOptions = Views::simpleView( 'bookmarks.nav.viewOptions' );
Components::set( 'VIEW_OPTIONS', $viewOptions );
$dashOptions = Views::simpleView( 'bookmarks.dashboards.dashOptions' );
Components::set( 'DASH_OPTIONS', $dashOptions );
$this->setPrefToggles();
}
public function index() {
self::$title = 'Manage Bookmarks - {SITENAME}';
if ( Input::exists('submit') ) {
$prefs = new Preferences;
$user = new User;
$fields = $prefs->convertFormToArray( true );
$out = $user->updatePrefs( $fields, App::$activeUser->ID );
$this->setPrefToggles();
}
$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->uuid = $folder->uuid;
$folderObject->bookmarkListRows = Views::simpleView( 'bookmarks.components.bookmarkListRows', $folderObject->bookmarks );
$panelArray[] = $folderObject;
}
}
if ( ! empty( $folders ) ) {
Components::set( 'folderPanels', Views::simpleView( 'bookmarks.components.bookmarkListPanel', $panelArray ) );
return Views::view( 'bookmarks.dash' );
}
return Views::view( 'bookmarks.indexExplainer' );
}
/**
* 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 unsorted() {
self::$title = 'Unsorted Bookmarks - {SITENAME}';
$bookmarks = self::$bookmarks->noFolder();
Views::view( 'bookmarks.bookmarks.unsorted', $bookmarks );
}
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 );
$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.bookmarks.listPage', $panelArray );
}
public function createBookmark( $id = null ) {
self::$title = 'Add Bookmark - {SITENAME}';
$folderID = Input::get('folder_id') ? Input::get('folder_id') : $id;
$folderID = Input::post('folder_id') ? Input::post('folder_id') : $id;
$this->setFolderSelect( $folderID );
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.' );
if ( ! empty( $folderID ) ) {
Redirect::to( 'bookmarks/bookmarks/'. $folderID );
} else {
Redirect::to( 'bookmarks/index' );
}
}
public function editBookmark( $id = null ) {
self::$title = 'Edit Bookmark - {SITENAME}';
$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;
}
$this->setFolderSelect( $folderID );
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();
Components::set( 'foldersList', Views::simpleView( 'bookmarks.folders.list', $folders ) );
return Views::view( 'bookmarks.folders.listPage' );
}
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 ) {
self::$title = 'Create Folder - {SITENAME}';
$folderID = Input::exists('folder_id') ? Input::post('folder_id') : $id;
$folders = self::$folders->simpleByUser();
if ( ! empty( $folders ) ) {
$this->setFolderSelect( $folderID );
} 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 ) {
self::$title = 'Edit Folder - {SITENAME}';
$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');
$this->setFolderSelect( $folderID );
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' );
}
/**
* Dashboards
*/
public function addDash() {
self::$title = 'Add Dashboard - {SITENAME}';
if ( !App::$isMember ) {
Issues::add( 'notice', 'You must have an active membership to add dashboards.' );
return $this->index();
}
$folders = self::$folders->byUser() ?? [];
if ( !empty( $folders ) ) {
foreach ( $folders as &$folder ) {
$folder->selected = '';
}
}
$linkSelect = Views::simpleView( 'bookmarks.components.linkSelect', $folders );
Components::set( 'LINK_SELECT', $linkSelect );
if ( ! Input::exists( 'submit' ) ) {
return Views::view( 'bookmarks.dashboards.create' );
}
if ( !Forms::check( 'createDashboard' ) ) {
Issues::add( 'error', [ 'There was an error creating your dashboard.' => Check::userErrors() ] );
return Views::view( 'bookmarks.dashboards.create' );
}
if ( is_array( Input::post('link_filter') ) && ! empty( Input::post('link_filter') ) ) {
$filters = implode( ',', Input::post('link_filter') );
} else {
$filters = '';
}
if ( is_array( Input::post('link_order') ) && ! empty( Input::post('link_order') ) ) {
$folders = implode( ',', Input::post('link_order') );
} else {
$folders = '';
}
$result = self::$dashboards->create( Input::post('title'), $filters, $folders, Input::post('description') );
if ( !$result ) {
Issues::add( 'error', [ 'There was an error creating your dashboard.' => Check::userErrors() ] );
return Views::view( 'bookmarks.dashboards.create' );
}
Issues::add( 'success', 'Your dashboard has been created.' );
return $this->dashboards();
}
public function editDash( $id = null ) {
self::$title = 'Edit Dashboard - {SITENAME}';
if ( !App::$isMember ) {
Issues::add( 'notice', 'You must have an active membership to edit dashboards.' );
return $this->index();
}
$dash = self::$dashboards->findById( $id );
if ( $dash == false ) {
Issues::add( 'error', 'Unknown Dashboard' );
return $this->dashboards();
}
if ( $dash->createdBy != App::$activeUser->ID ) {
Issues::add( 'error', 'You do not have permission to view this dashboard.' );
return $this->dashboards();
}
$this->setDashToggles( explode( ',', $dash->saved_prefs ) );
$folders = self::$folders->byUser() ?? [];
$selectedFolders = explode( ',', $dash->link_order );
if ( !empty( $folders ) ) {
foreach ( $folders as &$folder ) {
if ( in_array( $folder->ID, $selectedFolders ) ) {
$folder->selected = ' checked';
} else {
$folder->selected = '';
}
}
}
$linkSelect = Views::simpleView( 'bookmarks.components.linkSelect', $folders );
Components::set( 'LINK_SELECT', $linkSelect );
if ( ! Input::exists( 'submit' ) ) {
return Views::view( 'bookmarks.dashboards.edit', $dash );
}
if ( ! Forms::check( 'editDashboard' ) ) {
Issues::add( 'error', [ 'There was an error editing your dashboard.' => Check::userErrors() ] );
return Views::view( 'bookmarks.dashboards.edit', $dash );
}
if ( is_array( Input::post('link_filter') ) && ! empty( Input::post('link_filter') ) ) {
$filters = implode( ',', Input::post('link_filter') );
} else {
$filters = '';
}
if ( is_array( Input::post('link_order') ) && ! empty( Input::post('link_order') ) ) {
$folders = implode( ',', Input::post('link_order') );
} else {
$folders = '';
}
$result = self::$dashboards->update( $id, Input::post('title'), $filters, $folders, Input::post('description') );
if ( !$result ) {
Issues::add( 'error', [ 'There was an error updating your dashboard.' => Check::userErrors() ] );
return Views::view( 'bookmarks.dashboards.edit', $dash );
}
Issues::add( 'success', 'Your dashboard has been updated.' );
return $this->dashboards();
}
public function deleteDash( $id = null ) {
if ( !App::$isMember ) {
Issues::add( 'notice', 'You must have an active membership to delete dashboards.' );
return $this->index();
}
$dash = self::$dashboards->findById( $id );
if ( $dash == false ) {
Issues::add( 'error', 'Unknown Dashboard' );
return $this->dashboards();
}
if ( $dash->createdBy != App::$activeUser->ID ) {
Issues::add( 'error', 'You do not have permission to delete this dash.' );
return $this->dashboards();
}
$result = self::$dashboards->delete( $id );
if ( !$result ) {
Issues::add( 'error', 'There was an error deleting the dashboard(s)' );
} else {
Issues::add( 'success', 'Dashboard deleted' );
}
return $this->dashboards();
}
public function dashboard( $uuid = null ) {
self::$title = 'Bookmark Dashboard - {SITENAME}';
if ( !App::$isMember ) {
Issues::add( 'notice', 'You must have an active membership to view dashboards.' );
return $this->index();
}
$dash = self::$dashboards->findByUuid( $uuid );
if ( $dash == false ) {
return $this->dashboards();
}
if ( $dash->createdBy != App::$activeUser->ID ) {
Issues::add( 'error', 'You do not have permission to view this dash.' );
return $this->dashboards();
}
if ( Input::exists( 'submit' ) ) {
if ( Forms::check( 'updateDashboard' ) ) {
$filters = '';
$folders = '';
if ( is_array( Input::post('link_filter') ) && ! empty( Input::post('link_filter') ) ) {
$filters = implode( ',', Input::post('link_filter') );
}
if ( ! empty( Input::post('link_order') ) ) {
$folders = Input::post('link_order');
}
$result = self::$dashboards->updateDash( $dash->ID, $filters, $folders );
if ( !$result ) {
Issues::add( 'error', [ 'There was an error saving your dashboard.' => Check::userErrors() ] );
} else {
Issues::add( 'success', 'Your dashboard has been saved.' );
}
} else {
Issues::add( 'error', [ 'There was an error saving your dashboard.' => Check::userErrors() ] );
}
unset( $_POST );
}
$dash = self::$dashboards->findByUuid( $uuid );
$foldersArray = [];
if ( ! empty( $dash->link_order ) ) {
$folders = explode( ',', $dash->link_order );
foreach ( $folders as $key => $id ) {
$folder = self::$folders->findById( $id );
if ( empty( $folder ) ) {
continue;
}
$bookmarks = self::$bookmarks->byFolder( $folder->ID );
if ( empty( $bookmarks ) ) {
continue;
}
$folderObject = new \stdClass();
$folderObject->ID = $folder->ID;
$folderObject->title = $folder->title;
$folderObject->color = $folder->color;
$folderObject->uuid = $folder->uuid;
$folderObject->bookmarkRows = Views::simpleView( 'bookmarks.dashboards.bookmarkRows', $bookmarks );
$foldersArray[] = $folderObject;
}
}
Components::set( 'folderPanels', Views::simpleView( 'bookmarks.dashboards.folderPanels', $foldersArray ) );
if ( ! empty( $dash->saved_prefs ) ) {
$this->setDashToggles( explode( ',', $dash->saved_prefs ) );
} else {
$this->setDashToggles( [] );
}
return Views::view( 'bookmarks.dashboards.view', $dash );
}
public function dashboards() {
self::$title = 'Bookmark Dashboards - {SITENAME}';
if ( !App::$isMember ) {
Issues::add( 'notice', 'You will need an active subscription to start creating dashboards. You can check our <a href="/member/join" class="text-decoration-none">Pricing</a> page for more details.' );
return Views::view( 'bookmarks.dashboardExplainer' );
}
$dashboards = self::$dashboards->byUser();
return Views::view( 'bookmarks.dashboards.list', $dashboards );
}
/**
* Functionality
*/
public function publish( $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->publish( $id );
Session::flash( 'success', 'Bookmark mad Public.' );
return Redirect::to( 'bookmarks/share' );
}
public function retract( $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->retract( $id );
Session::flash( 'success', 'Bookmark made Private.' );
return Redirect::to( 'bookmarks/share' );
}
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 );
}
public function import() {
self::$title = 'Bookmark Import - {SITENAME}';
if ( !App::$isMember ) {
Issues::add( 'notice', 'You will need an active subscription to start importing bookmarks. You can check our <a href="/member/join" class="text-decoration-none">Pricing</a> page for more details.' );
return Views::view( 'bookmarks.importExplainer' );
}
if ( ! Input::exists('submit') ) {
return Views::view( 'bookmarks.import' );
}
if ( ! Forms::check( 'importBookmarks' ) ) {
Issues::add( 'error', [ 'There was an error importing your bookmarks.' => Check::userErrors() ] );
return Views::view( 'bookmarks.import' );
}
if ( isset( $_FILES['bookmark_file'] ) ) {
$file = $_FILES['bookmark_file'];
if ($file['size'] > 1024 * 1024) { // 1024 KB = 1 MB
Issues::add( 'error', 'There is a 1 meg limit on bookmark imports at this time.' );
return Views::view( 'bookmarks.import' );
}
$fileExtension = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION));
if ($fileExtension !== 'html') {
Issues::add( 'error', 'Invalid file type. Only .html files are allowed.' );
return Views::view( 'bookmarks.import' );
}
$fileContent = file_get_contents($file['tmp_name']);
} else {
Issues::add( 'error', 'No Import detected' );
return Views::view( 'bookmarks.import' );
}
$out = $this->parseBookmarks($fileContent);
$date = date('F j, Y');
$description = 'Imported on ' . $date . ' from file: ' . $file['name'];
$importFolder = self::$folders->create( 'New Import', 0, $description );
foreach ($out as $folder => $bookmarks) {
$currentFolder = self::$folders->create( $folder, $importFolder, $description );
foreach ($bookmarks as $index => $bookmark) {
self::$bookmarks->create( $bookmark['name'], $bookmark['url'], $currentFolder);
}
}
Session::flash( 'success', 'Your Bookmark has been created.' );
Redirect::to( 'bookmarks/bookmarks/'. $importFolder );
}
public function export() {
self::$title = 'Bookmark Export - {SITENAME}';
if ( !App::$isMember ) {
Issues::add( 'notice', 'You will need an active subscription to start exporting bookmarks. You can check our <a href="/member/join" class="text-decoration-none">Pricing</a> page for more details.' );
return Views::view( 'bookmarks.exportExplainer' );
}
$folders = self::$folders->byUser();
if ( ! Input::exists('submit') ) {
return Views::view( 'bookmarks.export', $folders );
}
if ( ! Forms::check( 'exportBookmarks' ) ) {
Issues::add( 'error', [ 'There was an error exporting your bookmarks.' => Check::userErrors() ] );
return Views::view( 'bookmarks.export', $folders );
}
$htmlDoc = '';
$htmlDoc .= '<!DOCTYPE NETSCAPE-Bookmark-file-1>' . PHP_EOL;
$htmlDoc .= '<!-- This is an automatically generated file.' . PHP_EOL;
$htmlDoc .= ' It will be read and overwritten.' . PHP_EOL;
$htmlDoc .= ' DO NOT EDIT! -->' . PHP_EOL;
$htmlDoc .= '<META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=UTF-8">' . PHP_EOL;
$htmlDoc .= '<TITLE>Bookmarks</TITLE>' . PHP_EOL;
$htmlDoc .= '<H1>Bookmarks</H1>' . PHP_EOL;
$htmlDoc .= '<DL><p>' . PHP_EOL;
foreach ( Input::post('BF_') as $key => $id ) {
if ( $id == 'unsorted' ) {
continue;
}
$folder = self::$folders->findById( $id );
if ( $folder == false ) {
Session::flash( 'error', 'Folder not found.' );
return Redirect::to( 'bookmarks/index' );
}
$hiddenExcluded = ! ( Input::post('hiddenIncluded') ?? false );
$archivedExcluded = ! ( Input::post('archivedIncluded') ?? false );
$links = self::$bookmarks->byFolder( $folder->ID, $hiddenExcluded, $archivedExcluded );
$htmlDoc .= $this->exportFolder( $folder->title, $folder->createdAt, $folder->createdAt, $links );
}
$htmlDoc .= '</DL><p>' . PHP_EOL;
$folder = UPLOAD_DIRECTORY . App::$activeUser->username;
if ( !file_exists( $folder ) ) {
mkdir( $folder, 0777, true );
}
$file = $folder . DIRECTORY_SEPARATOR . 'export.html';
$result = file_put_contents( $file, $htmlDoc );
if ($result !== false) {
$filename = basename($file);
$downloadUrl = '/uploads/' . App::$activeUser->username . '/export.html';
Session::flash( 'success', 'Your Export is available for download <a target="_blank" href="' . $downloadUrl . '">here</a>.' );
Redirect::to( 'bookmarks/export' );
} else {
Session::flash( 'error', 'There was an issue exporting your bookmarks, please try again.' );
return Redirect::to( 'bookmarks/export' );
}
}
public function share() {
$panelArray = [];
$folders = self::$folders->byUser();
if ( empty( $folders ) ) {
return Views::view( 'bookmarks.shareExplainer' );
}
foreach ( $folders as $key => $folder ) {
$panel = new \stdClass();
$folderObject = new \stdClass();
if ( $folder->privacy == 'private' ) {
$links = self::$bookmarks->publicByFolder( $folder->ID );
} else {
$links = self::$bookmarks->byFolder( $folder->ID );
}
$folderObject->bookmarks = $links;
$folderObject->ID = $folder->ID;
$folderObject->uuid = $folder->uuid;
$folderObject->title = $folder->title;
$folderObject->color = $folder->color;
$folderObject->privacyBadge = $folder->privacyBadge;
$folderObject->bookmarkListRows = Views::simpleView( 'bookmarks.components.shareListRows', $folderObject->bookmarks );
$panel->panel = Views::simpleView( 'bookmarks.components.shareListPanel', [$folderObject] );
$panelArray[] = $panel;
}
return Views::view( 'bookmarks.share', $panelArray );
}
/**
* Private
*/
private function exportFolder( $title, $editedAt, $createdAt, $links ) {
$htmlDoc = '<DT><H3 ADD_DATE="'.$createdAt.'" LAST_MODIFIED="'.$editedAt.'">'.$title.'</H3>' . PHP_EOL;
$htmlDoc .= '<DL><p>' . PHP_EOL;
if ( ! empty( $links ) ) {
foreach ( $links as $key => $link ) {
$htmlDoc .= $this->exportLink( $link->url, $link->icon, $link->createdAt, $link->title );
}
}
$htmlDoc .= '</DL><p>' . PHP_EOL;
return $htmlDoc;
}
private function exportLink( $url, $icon, $createdAt, $title ) {
$htmlDoc = '<DT><A HREF="'.$url.'" ADD_DATE="'.$createdAt.'" ICON="'.$icon.'">'.$title.'</A>' . PHP_EOL;
return $htmlDoc;
}
private function parseBookmarks($htmlContent) {
$started = false;
$out = [];
$currentFolder = [];
$folderName = ['unknown'];
$lines = explode("\n", $htmlContent);
foreach ($lines as $line) {
if ( $started == false ) {
if (preg_match("/<h1>(.*?)<\/h1>/i", $line, $matches)) {
$started = true;
}
continue;
}
if (preg_match('/<DL><p>/i', $line, $matches)) {
continue;
}
if (preg_match('/<H3(.*)>(.*?)<\/h3>/i', $line, $matches)) {
$newFolder = $matches[2];
$out[$newFolder] = [];
array_unshift($folderName, $newFolder );
continue;
}
if (preg_match('/<\/DL><p>/i', $line, $matches)) {
array_shift($folderName);
continue;
}
if (preg_match('/<A HREF="(.*?)"(.*)>(.*?)<\/A>/i', $line, $matches)) {
$href = $matches[1];
$guts = $matches[2];
$text = $matches[3];
$added = '';
$icon = '';
if (preg_match('/ADD_DATE="(.*?)"/i', $guts, $addMatches)) {
$added = $addMatches[1];
}
if (preg_match('/ICON="(.*?)"/i', $guts, $iconMatches)) {
$icon = $iconMatches[1];
}
$currentFolder = $folderName[0];
$out[$currentFolder][] = [
'name' => $text,
'url' => $href,
'addDate' => $added,
'icon' => $icon,
'folderName' => $folderName[0],
];
continue;
}
}
return $out;
}
private function setFolderSelect( $folderID ) {
$options = self::$folders->simpleByUser();
$out = '';
$out .= '<div class="mb-3 row">';
$out .= '<label for="folder_id" class="col-lg-5 col-form-label text-end">Folder</label>';
$out .= '<div class="col-lg-3">';
$out .= '<select name="folder_id" id="folder_id" class="form-control">';
if ( isset( $options[0] ) ) {
$assocOptions = [];
foreach ( $options as $key => $value ) {
$assocOptions[$value] = $value;
}
$options = $assocOptions;
}
if ( ! empty( $options ) ) {
foreach ( $options as $fieldname => $value ) {
if ( $folderID == $value ) {
$selected = ' selected';
} else {
$selected = '';
}
$out .= '<option value="' . $value . '"' . $selected . '>' . $fieldname . '</option>';
}
} else {
$out .= '<option value="0" selected>No Folder</option>';
}
$out .= '</select>';
$out .= '</div>';
$out .= '</div>';
$folderSelect = Template::parse( $out );
Components::set( 'folderSelect', $folderSelect );
}
private function setPrefToggles() {
$prefsArray = [
'editModeSwitch',
'showArchivedSwitch',
'showHiddenSwitch',
'archiveButtonSwitch',
'visibilityButtonSwitch',
'privacyButtonSwitch',
'shareButtonSwitch',
'addButtonSwitch'
];
foreach ($prefsArray as $key => $name) {
if ( empty( App::$activeUser->prefs[$name] ) ) {
Components::set( $name . '_IS_CHECKED', '' );
} else {
Components::set( $name . '_IS_CHECKED', ' checked' );
}
}
}
private function setDashToggles( $current = [] ) {
$prefsArray = [
'editModeSwitch',
'showArchivedSwitch',
'showHiddenSwitch',
'archiveButtonSwitch',
'visibilityButtonSwitch',
'privacyButtonSwitch',
'shareButtonSwitch',
'addButtonSwitch'
];
foreach ( $prefsArray as $key => $name ) {
Debug::error( $name );
Debug::error( $current );
if ( ! in_array( $name, $current ) ) {
Components::set( $name . '_IS_CHECKED', '' );
} else {
Debug::error( '_IS_CHECKED' );
Components::set( $name . '_IS_CHECKED', ' checked' );
}
}
}
}

View File

@ -0,0 +1,148 @@
<?php
/**
* app/plugins/bookmarks/controllers/extensions.php
*
* This is the extensions controller.
*
* @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\Controllers;
use TheTempusProject\Classes\Controller;
use TheTempusProject\Houdini\Classes\Views;
use TheTempusProject\Houdini\Classes\Issues;
use TheTempusProject\TheTempusProject as App;
use TheTempusProject\Hermes\Functions\Route as Routes;
use TheTempusProject\Models\Token;
use TheTempusProject\Houdini\Classes\Components;
use TheTempusProject\Models\Folders;
use TheTempusProject\Houdini\Classes\Template;
use TheTempusProject\Bedrock\Functions\Input;
class Extensions extends Controller {
public function index() {
self::$title = 'Browser Extensions';
self::$pageDescription = 'Our extensions cover all major browsers with the notable exception of Safari. Chrome, Opera, Brave, Firefox, and Edge are all represented; giving users a simple way to add bookmarks and folders from any page.';
if ( App::$isLoggedIn ) {
Issues::add( 'success', 'We also have a simple solution to using the app from your mobile devices <a href="/extensions/mobile">here</a>.' );
}
Views::view( 'bookmarks.extensions.index' );
}
public function mobile() {
self::$title = 'Mobile Bookmarklet';
self::$pageDescription = 'When you find yourself on the go, sometimes an extension isn\'t an option. For those times, we have the mobile bookmarklet to allow you to add bookmarks from your mobile devices.';
if ( ! App::$isLoggedIn ) {
Issues::add( 'notice', 'Since the bookmarklet is tied directly to your account, you will need to <a href="/home/login">log in</a> to generate the code for your account.' );
return Views::view( 'bookmarks.extensions.bookmarkletExplainer' );
}
if ( Input::exists('privacy') ) {
$privacy = 'const privacy = "' . Input::post('privacy') . '";';
} else {
$privacy = 'const privacy = prompt("Enter privacy level (e.g., public/private):");';
}
Components::set( 'BK_JS_PRIVACY', $privacy );
if ( Input::exists('includeDescription') ) {
$description = 'const notes = prompt("Enter a description (optional):");';
} else {
$description = '';
}
Components::set( 'BK_JS_NOTES', $description );
if ( Input::exists('includeColor') ) {
$color = 'const color = "'.Input::post('color').'";';
} else {
$color = '';
}
Components::set( 'BK_JS_COLOR', $color );
if ( Input::exists('includeFolder') ) {
$folder = 'const folder = "'.Input::post('folder_id').'";';
} else {
$folder = '';
}
Components::set( 'BK_JS_FOLDER', $folder );
$this->setFolderSelect(Input::post('folder'));
$tokens = new Token;
$apiKey = $tokens->findOrCreateUserToken( App::$activeUser->ID, true );
$apiUrl = Routes::getAddress() . 'api/bookmarks/create';
Components::set( 'BK_API_KEY', $apiKey );
Components::set( 'BK_API_URL', $apiUrl );
Views::view( 'bookmarks.extensions.bookmarklet' );
}
public function chrome() {
self::$title = 'Chrome Extension';
self::$pageDescription = 'Our Chrome extension allows you to quickly add new bookmarks or folders right from your browser\'s toolbar.';
Views::view( 'bookmarks.extensions.chrome' );
}
public function firefox() {
self::$title = 'Firefox Extension';
self::$pageDescription = 'Our Firefox extension allows you to quickly add new bookmarks or folders right from your browser\'s toolbar.';
Views::view( 'bookmarks.extensions.firefox' );
}
public function opera() {
self::$title = 'Opera Extension';
self::$pageDescription = 'Our Opera extension allows you to quickly add new bookmarks or folders right from your browser\'s toolbar.';
Views::view( 'bookmarks.extensions.opera' );
}
public function edge() {
self::$title = 'Edge Extension';
self::$pageDescription = 'Our Edge extension allows you to quickly add new bookmarks or folders right from your browser\'s toolbar.';
Views::view( 'bookmarks.extensions.edge' );
}
public function brave() {
self::$title = 'Brave Extension';
self::$pageDescription = 'Our Brave extension allows you to quickly add new bookmarks or folders right from your browser\'s toolbar.';
Views::view( 'bookmarks.extensions.brave' );
}
public function safari() {
self::$title = 'Safari Extension';
self::$pageDescription = 'Our Safari extension allows you to quickly add new bookmarks or folders right from your browser\'s toolbar.';
Views::view( 'bookmarks.extensions.safari' );
}
private function setFolderSelect( $folderID = null ) {
$folders = new Folders;
$options = $folders->simpleByUser();
$out = '';
$out .= '<select name="folder_id" id="folder_id" class="form-control">';
if ( isset( $options[0] ) ) {
$assocOptions = [];
foreach ( $options as $key => $value ) {
$assocOptions[$value] = $value;
}
$options = $assocOptions;
}
if ( ! empty( $options ) ) {
foreach ( $options as $fieldname => $value ) {
if ( $value == $folderID ) {
$selected = ' selected';
} else {
$selected = '';
}
$out .= '<option value="' . $value . '"' . $selected . '>' . $fieldname . '</option>';
}
} else {
$out .= '<option value="0" selected>No Folder</option>';
}
$out .= '</select>';
$folderSelect = Template::parse( $out );
Components::set( 'folderSelect', $folderSelect );
}
}

View File

@ -0,0 +1,107 @@
<?php
/**
* app/plugins/bookmarks/controllers/shared.php
*
* This is the bug reports controller.
*
* @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\Controllers;
use TheTempusProject\Hermes\Functions\Redirect;
use TheTempusProject\Bedrock\Functions\Session;
use TheTempusProject\Houdini\Classes\Views;
use TheTempusProject\Classes\Controller;
use TheTempusProject\Models\Bookmarks as Bookmark;
use TheTempusProject\Models\Folders;
use TheTempusProject\TheTempusProject as App;
use TheTempusProject\Houdini\Classes\Components;
use TheTempusProject\Hermes\Functions\Route as Routes;
class Shared extends Controller {
protected static $bookmarks;
protected static $folders;
public function __construct() {
parent::__construct();
self::$bookmarks = new Bookmark;
self::$folders = new Folders;
self::$title = 'Bookmarks - {SITENAME}';
self::$pageDescription = 'Add and save url bookmarks here.';
Components::set( 'SITE_URL', Routes::getAddress() );
}
public function shared( $type = '', $id = '' ) {
if ( empty( $type ) ) {
Session::flash( 'error', 'Unknown share' );
return Redirect::to( 'home/index' );
}
if ( empty( $id ) ) {
Session::flash( 'error', 'Unknown share' );
return Redirect::to( 'home/index' );
}
$type = strtolower( $type );
if ( ! in_array( $type, ['link','folder'] ) ) {
Session::flash( 'error', 'Unknown share' );
return Redirect::to( 'home/index' );
}
$this->$type( $id );
}
public function link( $id = '' ) {
if ( empty( $id ) ) {
Session::flash( 'error', 'Unknown share' );
return Redirect::to( 'home/index' );
}
$bookmark = self::$bookmarks->findByUuid( $id );
if ( $bookmark == false ) {
Session::flash( 'error', 'Unknown share' );
return Redirect::to( 'home/index' );
}
if ( $bookmark->createdBy != App::$activeUser->ID ) {
if ( $bookmark->privacy == 'private' ) {
if ( empty( $bookmark->folderID ) ) {
Session::flash( 'error', 'Unknown share' );
return Redirect::to( 'home/index' );
}
$folder = self::$folders->findByUuid( $bookmark->folderID );
if ( $folder == false ) {
Session::flash( 'error', 'Unknown share' );
return Redirect::to( 'home/index' );
}
if ( $folder->privacy == 'private' ) {
Session::flash( 'error', 'Unknown share' );
return Redirect::to( 'home/index' );
}
}
}
return Views::view( 'bookmarks.public.bookmark', $bookmark );
}
public function folder( $id = '' ) {
if ( empty( $id ) ) {
Session::flash( 'error', 'Unknown share' );
return Redirect::to( 'home/index' );
}
$folder = self::$folders->findByUuid( $id );
if ( $folder == false ) {
Session::flash( 'error', 'Unknown share' );
return Redirect::to( 'home/index' );
}
if ( $folder->privacy == 'private' ) {
Session::flash( 'error', 'Unknown share' );
return Redirect::to( 'home/index' );
}
$folder->bookmarks = self::$bookmarks->unsafeByFolder( $folder->ID );
$folder->bookmarkListRows = Views::simpleView( 'bookmarks.components.publicListRows', $folder->bookmarks );
$folder->panel = Views::simpleView( 'bookmarks.components.publicList', [$folder] );
return Views::view( 'bookmarks.public.folder', $folder );
}
}

View File

@ -0,0 +1,114 @@
<?php
/**
* app/plugins/bookmarks/controllers/tutorials.php
*
* This is the tutorials controller.
*
* @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\Controllers;
use TheTempusProject\Houdini\Classes\Issues;
use TheTempusProject\Houdini\Classes\Views;
use TheTempusProject\Classes\Controller;
use TheTempusProject\Houdini\Classes\Navigation;
use TheTempusProject\Houdini\Classes\Components;
use TheTempusProject\Bedrock\Functions\Input;
class Tutorials extends Controller {
public function __construct() {
parent::__construct();
self::$title = 'Tutorials - {SITENAME}';
self::$pageDescription = 'We have detailed walkthroughs on how to perform a number of tasks for every major browser.';
}
public function index( $browser = '', $tutorial = '' ) {
return Views::view( 'bookmarks.tutorials.list' );
}
public function brave( $tutorial = '' ) {
Navigation::setCrumbComponent( 'tutorialCrumbs', Input::get( 'url' ) );
if ( ! in_array( $tutorial, ['pin','settings','import','export'] ) ) {
$test = new \stdClass();
$test->pretty = 'Brave';
$test->printed = 'brave';
return Views::view( 'bookmarks.tutorials.card', [ $test ] );
}
return Views::view( 'bookmarks.tutorials.brave.' . $tutorial );
}
public function chrome( $tutorial = '' ) {
Navigation::setCrumbComponent( 'tutorialCrumbs', Input::get( 'url' ) );
if ( ! in_array( $tutorial, ['pin','settings','import','export'] ) ) {
$test = new \stdClass();
$test->pretty = 'Chrome';
$test->printed = 'chrome';
return Views::view( 'bookmarks.tutorials.card', [ $test ] );
}
return Views::view( 'bookmarks.tutorials.chrome.' . $tutorial );
}
public function edge( $tutorial = '' ) {
Navigation::setCrumbComponent( 'tutorialCrumbs', Input::get( 'url' ) );
if ( ! in_array( $tutorial, ['pin','settings','import','export'] ) ) {
$test = new \stdClass();
$test->pretty = 'Edge';
$test->printed = 'edge';
return Views::view( 'bookmarks.tutorials.card', [ $test ] );
}
return Views::view( 'bookmarks.tutorials.edge.' . $tutorial );
}
public function opera( $tutorial = '' ) {
Navigation::setCrumbComponent( 'tutorialCrumbs', Input::get( 'url' ) );
if ( ! in_array( $tutorial, ['pin','settings','import','export'] ) ) {
$test = new \stdClass();
$test->pretty = 'Opera';
$test->printed = 'opera';
return Views::view( 'bookmarks.tutorials.card', [ $test ] );
}
return Views::view( 'bookmarks.tutorials.opera.' . $tutorial );
}
public function firefox( $tutorial = '' ) {
Navigation::setCrumbComponent( 'tutorialCrumbs', Input::get( 'url' ) );
if ( ! in_array( $tutorial, ['pin','settings','import','export'] ) ) {
$test = new \stdClass();
$test->pretty = 'Firefox';
$test->printed = 'firefox';
return Views::view( 'bookmarks.tutorials.card', [ $test ] );
}
return Views::view( 'bookmarks.tutorials.firefox.' . $tutorial );
}
public function safari( $tutorial = '' ) {
Issues::add( 'notice', 'Safari is not supported at this time.' );
return;
if ( ! in_array( $tutorial, ['pin','settings','import','export'] ) ) {
Issues::add( 'notice', 'Unknown tutorial' );
return Views::view( 'bookmarks.tutorials.list' );
}
return Views::view( 'bookmarks.tutorials.safari.' . $tutorial );
}
public function mobile( $tutorial = '' ) {
Navigation::setCrumbComponent( 'tutorialCrumbs', Input::get( 'url' ) );
if ( ! in_array( $tutorial, ['iphone','android'] ) ) {
$test = new \stdClass();
$test->pretty = 'Mobile';
$test->printed = 'mobile';
return Views::view( 'bookmarks.tutorials.mobileCard', [ $test ] );
}
return Views::view( 'bookmarks.tutorials.mobile.' . $tutorial );
}
}

View File

@ -0,0 +1,225 @@
<?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' );
self::addHandler( 'importBookmarks', __CLASS__, 'importBookmarks' );
self::addHandler( 'exportBookmarks', __CLASS__, 'exportBookmarks' );
self::addHandler( 'createDashboard', __CLASS__, 'createDashboard' );
self::addHandler( 'editDashboard', __CLASS__, 'editDashboard' );
self::addHandler( 'updateDashboard', __CLASS__, 'updateDashboard' );
}
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;
}
public static function importBookmarks() {
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;
}
public static function exportBookmarks() {
if ( ! Input::exists( 'submit' ) ) {
return false;
}
if ( ! Input::exists( 'BF_' ) ) {
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;
}
public static function createDashboard() {
if ( ! Input::exists( 'submit' ) ) {
return false;
}
if ( ! Input::exists( 'title' ) ) {
Check::addUserError( 'You must include a title.' );
return false;
}
if ( ! Input::exists( 'link_order' ) ) {
Check::addUserError( 'You must include at least 1 link or folder.' );
return false;
}
if ( !self::token() ) {
Check::addUserError( 'There was an issue with your request.' );
return false;
}
return true;
}
public static function updateDashboard() {
if ( ! Input::exists( 'submit' ) ) {
return false;
}
if ( !self::token() ) {
Check::addUserError( 'There was an issue with your request.' );
return false;
}
return true;
}
public static function editDashboard() {
if ( ! Input::exists( 'submit' ) ) {
return false;
}
if ( ! Input::exists( 'title' ) ) {
Check::addUserError( 'You must include a title.' );
return false;
}
if ( ! Input::exists( 'link_order' ) ) {
Check::addUserError( 'You must include at least 1 link or folder.' );
return false;
}
if ( !self::token() ) {
Check::addUserError( 'There was an issue with your request.' );
return false;
}
return true;
}
}
new BookmarksForms;

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 152 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

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