Initial commit

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

View File

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

View File

@ -0,0 +1,158 @@
<?php
/**
* app/plugins/blog/controllers/blog.php
*
* This is the blog controller.
*
* @package TP Blog
* @version 3.0
* @author Joey Kimsey <Joey@thetempusproject.com>
* @link https://TheTempusProject.com
* @license https://opensource.org/licenses/MIT [MIT LICENSE]
*/
namespace TheTempusProject\Controllers;
use TheTempusProject\Bedrock\Functions\Check;
use TheTempusProject\Canary\Canary as Debug;
use TheTempusProject\Bedrock\Functions\Input;
use TheTempusProject\Houdini\Classes\Components;
use TheTempusProject\Houdini\Classes\Issues;
use TheTempusProject\Houdini\Classes\Views;
use TheTempusProject\Houdini\Classes\Template;
use TheTempusProject\Classes\Controller;
use TheTempusProject\Classes\Forms;
use TheTempusProject\Hermes\Functions\Redirect;
use TheTempusProject\Bedrock\Functions\Session;
use TheTempusProject\Plugins\Blog as BlogPlugin;
use TheTempusProject\TheTempusProject as App;
use TheTempusProject\Plugins\Comments;
use TheTempusProject\Models\Comments as CommentsModel;
class Blog extends Controller {
protected static $blog;
protected static $comments;
protected static $posts;
public function __construct() {
parent::__construct();
Template::setTemplate( 'blog' );
$blog = new BlogPlugin;
self::$posts = $blog->posts;
}
public function index() {
self::$title = '{SITENAME} Blog';
self::$pageDescription = 'The {SITENAME} blog is where you can find various posts containing information ranging from current projects and general information to editorial and opinion based content.';
Views::view( 'blog.list', self::$posts->listPosts() );
}
public function rss() {
Debug::log( 'Controller initiated: ' . __METHOD__ . '.' );
self::$title = '{SITENAME} RSS Feed';
self::$pageDescription = '{SITENAME} blog RSS feed.';
Template::setTemplate( 'rss' );
header( 'Content-Type: text/xml' );
return Views::view( 'blog.rss', self::$posts->listPosts( ['stripHtml' => true] ) );
}
public function comments( $sub = null, $data = null ) {
Debug::log( 'Controller initiated: ' . __METHOD__ . '.' );
if ( empty( self::$comments ) ) {
self::$comments = new CommentsModel;
}
$plugin = new Comments;
if ( empty( $sub ) || empty( $data ) ) {
Session::flash( 'error', 'Whoops, try again.' );
Redirect::to( 'blog' );
}
switch ( $sub ) {
case 'post':
$content = self::$posts->findById( (int) $data );
if ( empty( $content ) ) {
Session::flash( 'error', 'Unknown Post.' );
Redirect::to( 'blog' );
}
return $plugin->formPost( self::$posts->tableName, $content, 'blog/post/' );
return self::$comments->formPost( 'blog', $content, 'blog/post/' );
case 'edit':
$content = self::$comments->findById( $data );
if ( empty( $content ) ) {
Session::flash( 'error', 'Unknown Comment.' );
Redirect::to( 'blog' );
}
return $plugin->formEdit( self::$posts->tableName, $content, 'blog/post/' );
return self::$comments->formEdit( 'blog', $content, 'blog/post/' );
case 'delete':
$content = self::$comments->findById( $data );
if ( empty( $content ) ) {
Session::flash( 'error', 'Unknown Comment.' );
Redirect::to( 'blog' );
}
return $plugin->formDelete( self::$posts->tableName, $content, 'blog/post/' );
return self::$comments->formDelete( 'blog', $content, 'blog/post/' );
}
}
public function post( $id = null ) {
if ( empty( $id ) ) {
return $this->index();
}
$post = self::$posts->findById( $id );
if ( empty( $post ) ) {
return $this->index();
}
if ( empty( self::$comments ) ) {
self::$comments = new CommentsModel;
}
Debug::log( 'Controller initiated: ' . __METHOD__ . '.' );
self::$title = 'Blog Post';
// I removed this once because i didn't realize.
// this triggers the comment post controller method when the comment form is submitted on the post viewing page
if ( Input::exists( 'contentId' ) ) {
$this->comments( 'post', Input::post( 'contentId' ) );
}
Components::set( 'CONTENT_ID', $id );
Components::set( 'COMMENT_TYPE', 'blog' );
if ( App::$isLoggedIn ) {
Components::set( 'NEWCOMMENT', Views::simpleView( 'comments.create' ) );
} else {
Components::set( 'NEWCOMMENT', '' );
}
$post = self::$posts->findById( $id );
Components::set( 'count', self::$comments->count( self::$posts->tableName, $post->ID ) );
Components::set( 'COMMENTS', Views::simpleView( 'comments.list', self::$comments->display( 10, self::$posts->tableName, $post->ID ) ) );
self::$title .= ' - ' . $post->title;
self::$pageDescription = strip_tags( $post->contentSummaryNoLink );
Views::view( 'blog.post', $post );
}
public function author( $data = null ) {
if ( empty( $data ) ) {
return $this->index();
}
Debug::log( 'Controller initiated: ' . __METHOD__ . '.' );
self::$title = 'Posts by author - {SITENAME}';
self::$pageDescription = '{SITENAME} blog posts easily and conveniently sorted by author.';
Views::view( 'blog.list', self::$posts->byAuthor( $data ) );
}
public function month( $month = null, $year = 0 ) {
if ( empty( $month ) ) {
return $this->index();
}
Debug::log( 'Controller initiated: ' . __METHOD__ . '.' );
self::$title = 'Posts By Month - {SITENAME}';
self::$pageDescription = '{SITENAME} blog posts easily and conveniently sorted by month.';
Views::view( 'blog.list', self::$posts->byMonth( $month, $year ) );
}
public function year( $year = null ) {
if ( empty( $year ) ) {
return $this->index();
}
Debug::log( 'Controller initiated: ' . __METHOD__ . '.' );
self::$title = 'Posts by Year - {SITENAME}';
self::$pageDescription = '{SITENAME} blog posts easily and conveniently sorted by years.';
Views::view( 'blog.list', self::$posts->byYear( $year ) );
}
}

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,112 @@
<!DOCTYPE html>
<html lang="en">
<!--
* app/plugins/blog/templates/blog.tpl
*
* @package TP Blog
* @version 3.0
* @author Joey Kimsey <Joey@thetempusproject.com>
* @link https://TheTempusProject.com
* @license https://opensource.org/licenses/MIT [MIT LICENSE]
-->
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta property="og:url" content="{CURRENT_URL}">
<meta name='twitter:card' content='summary' />
<title>{TITLE}</title>
<meta itemprop="name" content="{TITLE}">
<meta name="twitter:title" content="{TITLE}">
<meta property="og:title" content="{TITLE}">
<meta name="description" content="{PAGE_DESCRIPTION}">
<meta itemprop="description" content="{PAGE_DESCRIPTION}">
<meta name="twitter:description" content="{PAGE_DESCRIPTION}">
<meta property="og:description" content="{PAGE_DESCRIPTION}">
<meta itemprop="image" content="{META_IMAGE}">
<meta name="twitter:image" content="{META_IMAGE}">
<meta property="og:image" content="{META_IMAGE}">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="author" content="The Tempus Project">
{ROBOT}
<link rel="alternate" hreflang="en-us" href="alternateURL">
<link rel="icon" href="{ROOT_URL}images/favicon.ico">
<!-- Required CSS -->
<link rel="stylesheet" href="{FONT_AWESOME_URL}font-awesome.min.css" crossorigin="anonymous">
<link rel="stylesheet" href="{BOOTSTRAP_CDN}css/bootstrap-theme.min.css" crossorigin="anonymous">
<link rel="stylesheet" href="{BOOTSTRAP_CDN}css/bootstrap.min.css" crossorigin="anonymous">
<!-- RSS -->
<link rel="alternate" href="{ROOT_URL}blog/rss" title="{TITLE} Feed" type="application/rss+xml" />
<!-- Custom styles for this template -->
{TEMPLATE_CSS_INCLUDES}
</head>
<body>
<nav class="navbar navbar-inverse navbar-fixed-top" role="navigation">
<!--Brand and toggle should get grouped for better mobile display -->
<div class="navbar-header">
<a href="{ROOT_URL}" class="navbar-brand">{SITENAME}</a>
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-ex1-collapse" style="">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
</div>
<div class="container-fluid">
<div class="collapse navbar-collapse navbar-ex1-collapse">
{topNavLeft}
<div class="navbar-right">
<ul class="nav navbar-nav">
{topNavRight}
</ul>
</div>
</div>
</div>
</nav>
<div class="container-fluid">
<div class="foot-pad">
{ISSUES}
<div class="row">
<div class="container">
{ERROR}
{NOTICE}
{SUCCESS}
</div>
</div>
{/ISSUES}
<div class="row">
<div class="container">
<div class="page-header">
<h1 class="blog-title">{SITENAME} Blog</h1>
</div>
<div class="row">
<div class="col-sm-8 blog-main">
{BLOG_BREADCRUMBS}
{CONTENT}
</div>
<!-- /.blog-main -->
<div class="col-sm-3 col-sm-offset-1 blog-sidebar">
<div class="sidebar-module">
{SIDEBAR}
</div>
<div class="sidebar-module">
{SIDEBAR2}
</div>
</div>
<!-- /.blog-sidebar -->
</div>
<!-- /.row -->
</div>
</div>
</div>
</div>
<footer>
{FOOT}
{COPY}
</footer>
<!-- Bootstrap core JavaScript and jquery -->
<script src="{JQUERY_CDN}jquery.min.js" crossorigin="anonymous"></script>
<script src="{BOOTSTRAP_CDN}js/bootstrap.min.js" crossorigin="anonymous"></script>
<!-- Custom javascript for this template -->
{TEMPLATE_JS_INCLUDES}
</body>
</html>

View File

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

View File

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

View File

@ -0,0 +1,31 @@
<form action="" method="post" class="form-horizontal" enctype="multipart/form-data">
<legend>New Blog Post</legend>
<div class="form-group">
<label for="title" class="col-lg-3 control-label">Title</label>
<div class="col-lg-3">
<input type="text" class="form-check-input" name="title" id="title">
</div>
</div>
<div class="form-group">
<div class="col-lg-6 col-lg-offset-3 btn-group">
<button type="button" class="btn btn-sm btn-primary" onclick="insertTag ('blogPost', 'c');">&#10004;</button>
<button type="button" class="btn btn-sm btn-primary" onclick="insertTag ('blogPost', 'x');">&#10006;</button>
<button type="button" class="btn btn-sm btn-primary" onclick="insertTag ('blogPost', '!');">&#10069;</button>
<button type="button" class="btn btn-sm btn-primary" onclick="insertTag ('blogPost', '?');">&#10068;</button>
</div>
</div>
<div class="form-group">
<label for="blogPost" class="col-lg-3 control-label">Post</label>
<div class="col-lg-6">
<textarea class="form-control" name="blogPost" maxlength="2000" rows="10" cols="50" id="blogPost"></textarea>
</div>
</div>
<div class="form-group">
<div class="col-lg-6 col-lg-offset-3">
<button name="submit" value="publish" type="submit" class="btn btn-lg btn-primary">Publish</button>
<button name="submit" value="saveDraft" type="submit" class="btn btn-lg btn-primary">Save as Draft</button>
<button name="submit" value="preview" type="submit" class="btn btn-lg btn-primary">Preview</button>
</div>
</div>
<input type="hidden" name="token" value="{TOKEN}">
</form>

View File

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

View File

@ -0,0 +1,31 @@
<form action="" method="post" class="form-horizontal" enctype="multipart/form-data">
<legend>Edit Blog Post</legend>
<div class="form-group">
<label for="title" class="col-lg-3 control-label">Title</label>
<div class="col-lg-3">
<input type="text" class="form-check-input" name="title" id="title" value="{title}">
</div>
</div>
<div class="form-group">
<div class="col-lg-6 col-lg-offset-3 btn-group">
<button type="button" class="btn btn-sm btn-primary" onclick="insertTag ('blogPost', 'c');">&#10004;</button>
<button type="button" class="btn btn-sm btn-primary" onclick="insertTag ('blogPost', 'x');">&#10006;</button>
<button type="button" class="btn btn-sm btn-primary" onclick="insertTag ('blogPost', '!');">&#10069;</button>
<button type="button" class="btn btn-sm btn-primary" onclick="insertTag ('blogPost', '?');">&#10068;</button>
</div>
</div>
<div class="form-group">
<label for="blogPost" class="col-lg-3 control-label">Post</label>
<div class="col-lg-6">
<textarea class="form-control" name="blogPost" maxlength="2000" rows="10" cols="50" id="blogPost">{content}</textarea>
</div>
</div>
<div class="form-group">
<div class="col-lg-6 col-lg-offset-3">
<button name="submit" value="publish" type="submit" class="btn btn-lg btn-primary">Publish</button>
<button name="submit" value="saveDraft" type="submit" class="btn btn-lg btn-primary">Save as Draft</button>
<button name="submit" value="preview" type="submit" class="btn btn-lg btn-primary">Preview</button>
</div>
</div>
<input type="hidden" name="token" value="{TOKEN}">
</form>

View File

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

View File

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

View File

@ -0,0 +1,11 @@
<div class="row">
<div class="col-sm-8 blog-main">
<div class="page-header">
<h1>{title} <small>{DTC}{created}{/DTC} by <a href="{ROOT_URL}admin/users/view/{author}">{authorName}</a></small></h1>
</div>
<div class="well">{content}</div>
<a href="{ROOT_URL}admin/blog/delete/{ID}" class="btn btn-md btn-danger" role="button">Delete</a>
<a href="{ROOT_URL}admin/blog/edit/{ID}" class="btn btn-md btn-warning" role="button">Edit</a>
<a href="{ROOT_URL}admin/comments/blog/{ID}" class="btn btn-md btn-primary" role="button">View Comments</a>
</div><!-- /.blog-main -->
</div><!-- /.row -->

View File

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

View File

@ -0,0 +1,18 @@
<div class="row">
<div class="col-lg-12 col-sm-12 blog-main">
<div class="blog-post">
<h2 class="blog-post-title">{title}</h2>
<hr>
<p class="blog-post-meta">{DTC date}{created}{/DTC} by <a href="{ROOT_URL}home/profile/{author}">{authorName}</a></p>
{content}
{ADMIN}
<hr>
<a href="{ROOT_URL}admin/blog/delete/{ID}" class="btn btn-md btn-danger" role="button">Delete</a>
<a href="{ROOT_URL}admin/blog/edit/{ID}" class="btn btn-md btn-warning" role="button">Edit</a>
<hr>
{/ADMIN}
</div><!-- /.blog-post -->
{COMMENTS}
{NEWCOMMENT}
</div><!-- /.blog-main -->
</div><!-- /.row -->

View File

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

View File

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

View File

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

View File

@ -0,0 +1,14 @@
<div class="panel panel-info">
<div class="panel-heading">
<h3 class="panel-title">Archives</h3>
</div>
<div class="panel-body">
<ol class="list-unstyled">
{LOOP}
<li>({count}) <a href="{ROOT_URL}blog/month/{month}/{year}">{monthText} {year}</a></li>
{/LOOP}
{ALT}
{/ALT}
</ol>
</div>
</div>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,64 @@
<div class="container">
<div class="row">
<div class="col-xs-12 col-sm-12 col-md-6 col-lg-6 col-xs-offset-0 col-sm-offset-0 col-md-offset-3 col-lg-offset-3 top-pad" >
<div class="panel panel-primary">
<div class="panel-heading">
<h3 class="panel-title">Bug Report</h3>
</div>
<div class="panel-body">
<div class="row">
<div class=" col-md-12 col-lg-12 ">
<table class="table table-user-primary">
<tbody>
<tr>
<td align="left" width="200"><b>ID</b></td>
<td align="right">{ID}</td>
</tr>
<tr>
<td><b>Time submitted</b></td>
<td align="right">{DTC}{time}{/DTC}</td>
</tr>
<tr>
<td><b>Submitted by</b></td>
<td align="right"><a href="{ROOT_URL}admin/users/view/{userID}">{submittedBy}</a></td>
</tr>
<tr>
<td><b>IP</b></td>
<td align="right">{ip}</td>
</tr>
<tr>
<td><b>URL:</b></td>
<td align="right"><a href="{URL}">{URL}</a></td>
</tr>
<tr>
<td><b>Original URL</b></td>
<td align="right"><a href="{OURL}">{OURL}</a></td>
</tr>
<tr>
<td><b>Multiple occurrences?</b></td>
<td align="right">{repeatText}</td>
</tr>
<tr>
<td align="center" colspan="2"><b>Description</b></td>
</tr>
<tr>
<td colspan="2">{description}</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<div class="panel-footer">
{ADMIN}
<form action="{ROOT_URL}admin/bugreport/delete" method="post">
<INPUT type="hidden" name="BR_" value="{ID}"/>
<input type="hidden" name="token" value="{TOKEN}" />
<button name="submit" value="submit" type="submit" class="btn btn-sm btn-danger"><i class="glyphicon glyphicon-remove"></i></button>
</form>
{/ADMIN}
</div>
</div>
</div>
</div>
</div>

View File

@ -0,0 +1,36 @@
<legend>Bug Report</legend>
<p>Thank you for visiting Our bug reporting page. We value our users' input highly and in an effort to better serve your needs, please fill out the form below to help us address this issue.</p>
<p>We read each and every bug report submitted, and by submitting this form you allow us to send you a follow up email.</p>
<form action="" method="post" class="form-horizontal">
<label for="url">Page you were trying to reach:</label>
<input type="url" name="url" id="url" class="form-control" aria-describedby="urlHelp">
<p id="urlHelp" class="form-text text-muted">
What is the URL of the page you actually received the error on? (The URL is the website address. Example: {ROOT_URL}home)
</p>
<label for="ourl">Page you were on:</label>
<input type="url" name="ourl" id="ourl" class="form-control" aria-describedby="ourlHelp">
<p id="ourlHelp" class="form-text text-muted">
What is the URL of the page you were on before you received the error? (The URL is the website address. Example: {ROOT_URL}home/newhome)
</p>
<label for="repeat">*Has this happened more than once?</label>
<div class="form-check">
<label class="form-check-label">
<input class="form-check-input" type="radio" name="repeat" id="repeat" value="false" checked>
No
</label>
</div>
<div class="form-check">
<label class="form-check-label">
<input class="form-check-input" type="radio" name="repeat" id="repeat" value="true">
Yes
</label>
</div>
<div class="form-group">
<label for="entry" class="col-lg-3 control-label">Describe the problem/error as best as you can: (max:2000 characters)</label>
<div class="col-lg-6">
<textarea class="form-control" name="entry" maxlength="2000" rows="10" cols="50" id="entry"></textarea>
</div>
</div>
<input type="hidden" name="token" value="{TOKEN}">
<button name="submit" value="submit" type="submit" class="btn btn-lg btn-primary center-block">Submit</button>
</form>

View File

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

View File

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

View File

@ -0,0 +1,92 @@
<?php
/**
* app/plugins/bugtracker/forms.php
*
* This houses all of the form checking functions for this plugin.
*
* @package TP BugTracker
* @version 3.0
* @author Joey Kimsey <Joey@thetempusproject.com>
* @link https://TheTempusProject.com
* @license https://opensource.org/licenses/MIT [MIT LICENSE]
*/
namespace TheTempusProject\Plugins\Bugreport;
use TheTempusProject\Bedrock\Functions\Input;
use TheTempusProject\Classes\Forms;
class BugTrackerForms extends Forms {
/**
* Adds these functions to the form list.
*/
public function __construct() {
self::addHandler( 'newBugTracker', __CLASS__, 'newBugTracker' );
self::addHandler( 'editBugTracker', __CLASS__, 'editBugTracker' );
}
/**
* Validates the bug tracker create form.
*
* @return {bool}
*/
public static function newBugTracker() {
if ( !empty(Input::post( 'url' )) && !self::url( Input::post( 'url' ) ) ) {
self::addUserError( 'Invalid url: <code>' . Input::post( 'url' ) . '</code>' );
return false;
}
if ( !self::tf( Input::post( 'repeat' ) ) ) {
self::addUserError( 'Invalid repeat value: ' . Input::post( 'repeat' ) );
return false;
}
if ( !Input::exists( 'title' ) ) {
self::addUserError( 'You must specify title' );
return false;
}
if ( !self::dataTitle( Input::post( 'title' ) ) ) {
self::addUserError( 'Invalid title' );
return false;
}
if ( !Input::exists( 'description' ) ) {
self::addUserError( 'You must specify a description' );
return false;
}
// if ( !self::token() ) {
// return false;
// }
return true;
}
/**
* Validates the bug tracker create form.
*
* @return {bool}
*/
public static function editBugTracker() {
if ( !empty(Input::post( 'url' )) && !self::url( Input::post( 'url' ) ) ) {
self::addUserError( 'Invalid url.' . Input::post( 'url' ) );
return false;
}
if ( !self::tf( Input::post( 'repeat' ) ) ) {
self::addUserError( 'Invalid repeat value.' );
return false;
}
if ( !Input::exists( 'title' ) ) {
self::addUserError( 'You must specify title' );
return false;
}
if ( !self::dataTitle( Input::post( 'title' ) ) ) {
self::addUserError( 'Invalid title' );
return false;
}
if ( !Input::exists( 'description' ) ) {
self::addUserError( 'You must specify a description' );
return false;
}
// if ( !self::token() ) {
// return false;
// }
return true;
}
}
new BugTrackerForms;

View File

@ -0,0 +1,142 @@
<?php
/**
* app/plugins/bugtracker/models/bugtracker.php
*
* This class is used for the manipulation of the bugs database table.
*
* @package TP BugTracker
* @version 3.0
* @author Joey Kimsey <Joey@thetempusproject.com>
* @link https://TheTempusProject.com
* @license https://opensource.org/licenses/MIT [MIT LICENSE]
*/
namespace TheTempusProject\Models;
use TheTempusProject\Bedrock\Functions\Check;
use TheTempusProject\Bedrock\Classes\Config;
use TheTempusProject\Canary\Canary as Debug;
use TheTempusProject\Bedrock\Classes\CustomException;
use TheTempusProject\Classes\DatabaseModel;
use TheTempusProject\TheTempusProject as App;
use TheTempusProject\Plugins\Bugtracker as Plugin;
class Bugtracker extends DatabaseModel {
public $tableName = 'bugs';
public $databaseMatrix = [
[ 'userID', 'int', '11' ],
[ 'time', 'int', '10' ],
[ 'title', 'varchar', '128' ],
[ 'status', 'varchar', '64' ],
[ 'description', 'text', '' ],
[ 'image', 'varchar', '256' ],
[ 'repeatable', 'varchar', '5' ],
[ 'url', 'varchar', '256' ],
];
public $plugin;
/**
* The model constructor.
*/
public function __construct() {
parent::__construct();
$this->plugin = new Plugin;
}
/**
* This function parses the bug reports description and
* separates it into separate keys in the array.
*
* @param array $data - The data being parsed.
*
* @return array
*/
public function filter( $data, $params = [] ) {
foreach ( $data as $instance ) {
if ( !is_object( $instance ) ) {
$instance = $data;
$end = true;
}
$instance->submittedBy = self::$user->getUsername( $instance->userID );
$instance->repeatText = ( 'false' == $instance->repeatable ? 'no' : 'yes' );
$out[] = $instance;
if ( !empty( $end ) ) {
$out = $out[0];
break;
}
}
return $out;
}
/**
* Logs a Bug Report form.
*
* @param int $ID the user ID submitting the form
* @param string $url the url
* @param string $o_url the original url
* @param int $repeat is repeatable?
* @param string $description_ description of the event.
*
* @return null
*/
public function create( $ID, $url, $image, $repeat, $description, $title ) {
if ( !$this->plugin->checkEnabled() ) {
Debug::info( 'Bug Tracking is disabled in the config.' );
return false;
}
if ( !Check::dataTitle( $title ) ) {
Debug::info( 'bugTracker: illegal title.' );
return false;
}
$fields = [
'userID' => App::$activeUser->ID,
'time' => time(),
'title' => $title,
'status' => TRACKER_STATUS_NEW,
'description' => $description,
'image' => $image,
'repeatable' => $repeat,
'url' => $url,
];
if ( !self::$db->insert( $this->tableName, $fields ) ) {
new CustomException( $this->tableName );
return false;
}
return self::$db->lastId();
}
public function updateTracker( $ID, $url, $image, $repeat, $description, $title, $status ) {
if ( !$this->plugin->checkEnabled() ) {
Debug::info( 'Bug Tracking is disabled in the config.' );
return false;
}
if ( empty( self::$log ) ) {
self::$log = new Log;
}
if ( !Check::id( $ID ) ) {
Debug::info( 'bugTracker: illegal ID.' );
return false;
}
if ( !Check::dataTitle( $title ) ) {
Debug::info( 'bugTracker: illegal title.' );
return false;
}
$fields = [
'url' => $url,
'description' => $description,
'repeatable' => $repeat,
'title' => $title,
'status' => $status,
];
if ( !empty( $image ) ) {
$fields['image'] = $image;
}
if ( !self::$db->update( $this->tableName, $ID, $fields ) ) {
new CustomException( $this->tableName );
Debug::error( "Tracker Post: $ID not updated: $fields" );
return false;
}
self::$log->admin( "Updated Tracker Post: $ID" );
return true;
}
}

View File

@ -0,0 +1,48 @@
<?php
/**
* app/plugins/bugtracker/plugin.php
*
* This houses all of the main plugin info and functionality.
*
* @package TP BugTracker
* @version 3.0
* @author Joey Kimsey <Joey@thetempusproject.com>
* @link https://TheTempusProject.com
* @license https://opensource.org/licenses/MIT [MIT LICENSE]
*/
namespace TheTempusProject\Plugins;
use ReflectionClass;
use TheTempusProject\Classes\Installer;
use TheTempusProject\Houdini\Classes\Navigation;
use TheTempusProject\Classes\Plugin;
use TheTempusProject\TheTempusProject as App;
class Bugtracker extends Plugin {
public $pluginName = 'TP BugTracker';
public $pluginAuthor = 'JoeyK';
public $pluginWebsite = 'https://TheTempusProject.com';
public $modelVersion = '1.0';
public $pluginVersion = '3.0';
public $pluginDescription = '';
public $configName = 'bugtracker';
public $configMatrix = [
'enabled' => [
'type' => 'radio',
'pretty' => 'Enable Bug tracking.',
'default' => true,
],
];
public $permissionMatrix = [
'bugTrack' => [
'pretty' => 'Can Track Bugs',
'default' => false,
],
];
public $admin_links = [
[
'text' => '<i class="fa fa-fw fa-bug"></i> Bug Tracker',
'url' => '{ROOT_URL}admin/bugtracker',
],
];
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,41 @@
<?php
/**
* app/plugins/blog/templates/blog.inc.php
*
* This is the loader for the blog template.
*
* @package TP Blog
* @version 3.0
* @author Joey Kimsey <Joey@thetempusproject.com>
* @link https://TheTempusProject.com
* @license https://opensource.org/licenses/MIT [MIT LICENSE]
*/
namespace TheTempusProject\Templates;
use TheTempusProject\Plugins\Calendar;
use TheTempusProject\Houdini\Classes\Components;
use TheTempusProject\Houdini\Classes\Navigation;
use TheTempusProject\Houdini\Classes\Views;
use TheTempusProject\Houdini\Classes\Template;
use TheTempusProject\Bedrock\Functions\Input;
use TheTempusProject\Models\Events;
use TheTempusProject\Models\Calendars;
class CalendarLoader extends DefaultLoader {
/**
* This is the function used to generate any components that may be
* needed by this template.
*/
public function __construct() {
$events = new Events;
$calendars = new Calendars;
Navigation::setCrumbComponent( 'CALENDAR_BREADCRUMBS', Input::get( 'url' ) );
Components::set( 'todaysDate', date( 'F d, Y', time()) );
Components::set( 'currentDay', date( 'F d, Y', time()) );
Components::set( 'weekOf', 'Week of ' . date( 'F d, Y', strtotime( 'this week' ) ) );
Components::set( 'year', date( 'Y', time() ));
Components::set( 'month', date( 'F', time() ));
parent::__construct();
}
}

View File

@ -0,0 +1,96 @@
<!DOCTYPE html>
<html lang="en">
<!--
* app/plugins/calendar/templates/calendar.tpl
*
* @package TP Calendar
* @version 3.0
* @author Joey Kimsey <Joey@thetempusproject.com>
* @link https://TheTempusProject.com
* @license https://opensource.org/licenses/MIT [MIT LICENSE]
-->
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta property="og:url" content="{CURRENT_URL}">
<meta name='twitter:card' content='summary' />
<title>{TITLE}</title>
<meta itemprop="name" content="{TITLE}">
<meta name="twitter:title" content="{TITLE}">
<meta property="og:title" content="{TITLE}">
<meta name="description" content="{PAGE_DESCRIPTION}">
<meta itemprop="description" content="{PAGE_DESCRIPTION}">
<meta name="twitter:description" content="{PAGE_DESCRIPTION}">
<meta property="og:description" content="{PAGE_DESCRIPTION}">
<meta itemprop="image" content="{META_IMAGE}">
<meta name="twitter:image" content="{META_IMAGE}">
<meta property="og:image" content="{META_IMAGE}">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="author" content="The Tempus Project">
{ROBOT}
<link rel="alternate" hreflang="en-us" href="alternateURL">
<link rel="icon" href="{ROOT_URL}images/favicon.ico">
<!-- Required CSS -->
<link rel="stylesheet" href="{FONT_AWESOME_URL}font-awesome.min.css" crossorigin="anonymous">
<link rel="stylesheet" href="{BOOTSTRAP_CDN}css/bootstrap-theme.min.css" crossorigin="anonymous">
<link rel="stylesheet" href="{BOOTSTRAP_CDN}css/bootstrap.min.css" crossorigin="anonymous">
<!-- RSS -->
<link rel="alternate" href="{ROOT_URL}blog/rss" title="{TITLE} Feed" type="application/rss+xml" />
<!-- Custom styles for this template -->
{TEMPLATE_CSS_INCLUDES}
<link rel="stylesheet" href="{ROOT_URL}app/plugins/calendar/css/calendar.css" />
</head>
<body>
<nav class="navbar navbar-inverse navbar-fixed-top" role="navigation">
<!--Brand and toggle should get grouped for better mobile display -->
<div class="navbar-header">
<a href="{ROOT_URL}" class="navbar-brand">{SITENAME}</a>
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-ex1-collapse" style="">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
</div>
<div class="container-fluid">
<div class="collapse navbar-collapse navbar-ex1-collapse">
{topNavLeft}
<div class="navbar-right">
<ul class="nav navbar-nav">
{topNavRight}
</ul>
</div>
</div>
</div>
</nav>
<div class="container-fluid">
<div class="foot-pad">
{ISSUES}
<div class="row">
<div class="container">
{ERROR}
{NOTICE}
{SUCCESS}
</div>
</div>
{/ISSUES}
<div class="row">
<div class="container-fluid calendar-container">
<div class="row">
{CalendarNav}
{CONTENT}
</div>
</div>
</div>
</div>
</div>
<footer>
{COPY}
</footer>
<!-- Bootstrap core JavaScript and jquery -->
<script src="{JQUERY_CDN}jquery.min.js" crossorigin="anonymous"></script>
<script src="{BOOTSTRAP_CDN}js/bootstrap.min.js" crossorigin="anonymous"></script>
<!-- Custom javascript for this template -->
{TEMPLATE_JS_INCLUDES}
</body>
</html>

View File

@ -0,0 +1,25 @@
<h2 class="pull-left">
<a href="{ROOT_URL}calendar/byDay/{calendarID}?day={lastDay}&month={lastDayMonth}" role="button" class="btn btn-primary">
<i class="glyphicon glyphicon-chevron-left"></i>
</a>
<span class="dateDayContainer">{activeDate}</span>
<a href="{ROOT_URL}calendar/byDay/{calendarID}?day={nextDay}&month={nextDayMonth}" role="button" class="btn btn-primary">
<i class="glyphicon glyphicon-chevron-right"></i>
</a>
</h2>
{dateDropdown}
<div class="container-fluid">
{LOOP}
<div class="hour-row{hourSelected}">
<h2 class="hour-header">{hour}:00</h2>
{EventCells}
<span class="hour-footer">
<a href="{ROOT_URL}calendar/createEvent?calendar_id={calendarID}&hour={hour}&day={day}" class="btn btn-sm btn-success" role="button"><i class="glyphicon glyphicon-plus"></i></a>
</span>
</div>
{/LOOP}
{ALT}
<div class="hour-row{hourSelected}">
</div>
{/ALT}
</div>

View File

@ -0,0 +1,34 @@
<h2 class="pull-left">
<a href="{ROOT_URL}calendar/byMonth/{calendarID}?month={lastMonth}&year={lastMonthYear}" role="button" class="btn btn-primary">
<i class="glyphicon glyphicon-chevron-left"></i>
</a>
<span class="dateMonthContainer">{dateMonth}</span>
<a href="{ROOT_URL}calendar/byMonth/{calendarID}?month={nextMonth}&year={nextMonthYear}" role="button" class="btn btn-primary">
<i class="glyphicon glyphicon-chevron-right"></i>
</a>
</h2>
{dateDropdown}
<div class="container-fluid">
<!-- Header -->
<div class="row calendar-row">
<div class="col-xs-1 calendar-cell"><h4>Sunday</h4></div>
<div class="col-xs-1 calendar-cell"><h4>Monday</h4></div>
<div class="col-xs-1 calendar-cell"><h4>Tuesday</h4></div>
<div class="col-xs-1 calendar-cell"><h4>Wednesday</h4></div>
<div class="col-xs-1 calendar-cell"><h4>Thursday</h4></div>
<div class="col-xs-1 calendar-cell"><h4>Friday</h4></div>
<div class="col-xs-1 calendar-cell"><h4>Saturday</h4></div>
</div>
<!-- Week 1 -->
{week1Element}
<!-- Week 2 -->
{week2Element}
<!-- Week 3 -->
{week3Element}
<!-- Week 4 -->
{week4Element}
<!-- Week 5 -->
{week5Element}
<!-- Week 6 -->
{week6Element}
</div>

View File

@ -0,0 +1,25 @@
<h2 class="pull-left">
<a href="{ROOT_URL}calendar/byWeek/{calendarID}?month={lastWeekMonth}&day={lastWeek}&year={lastYear}" role="button" class="btn btn-primary">
<i class="glyphicon glyphicon-chevron-left"></i>
</a>
<span class="dateWeekContainer">{dateWeek}</span>
<a href="{ROOT_URL}calendar/byWeek/{calendarID}?month={nextWeekMonth}&day={nextWeek}&year={nextYear}" role="button" class="btn btn-primary">
<i class="glyphicon glyphicon-chevron-right"></i>
</a>
</h2>
{dateDropdown}
<div class="container-fluid">
<!-- Header -->
<div class="row calendar-row">
<div class="col-xs-1 calendar-cell"><h4>Sunday</h4></div>
<div class="col-xs-1 calendar-cell"><h4>Monday</h4></div>
<div class="col-xs-1 calendar-cell"><h4>Tuesday</h4></div>
<div class="col-xs-1 calendar-cell"><h4>Wednesday</h4></div>
<div class="col-xs-1 calendar-cell"><h4>Thursday</h4></div>
<div class="col-xs-1 calendar-cell"><h4>Friday</h4></div>
<div class="col-xs-1 calendar-cell"><h4>Saturday</h4></div>
</div>
<!-- Week 1 -->
{week1Element}
</div>

View File

@ -0,0 +1,42 @@
<h2 class="pull-left">
<a href="{ROOT_URL}calendar/byYear/{calendarID}?year={lastYear}" role="button" class="btn btn-primary">
<i class="glyphicon glyphicon-chevron-left"></i>
</a>
<span class="dateYearContainer">{dateYear}</span>
<a href="{ROOT_URL}calendar/byYear/{calendarID}?year={nextYear}" role="button" class="btn btn-primary">
<i class="glyphicon glyphicon-chevron-right"></i>
</a>
</h2>
{dateDropdown}
<table class="table table-striped">
<thead>
<tr>
<th style="width: 10%">ID</th>
<th style="width: 20%">Time</th>
<th style="width: 40%">Title</th>
<th style="width: 10%"></th>
<th style="width: 10%"></th>
<th style="width: 10%"></th>
</tr>
</thead>
<tbody>
{LOOP}
<tr>
<td style="text-align: center;">{ID}</td>
<td style="text-align: center;">{DTC}{event_time}{/DTC}</td>
<td style="text-align: center;">{title}</td>
<td><a href="{ROOT_URL}calendar/event/{ID}" class="btn btn-sm btn-primary" role="button"><i class="glyphicon glyphicon-open"></i></a></td>
<td><a href="{ROOT_URL}calendar/editEvent/{ID}" class="btn btn-sm btn-warning" role="button"><i class="glyphicon glyphicon-edit"></i></a></td>
<td><a href="{ROOT_URL}calendar/deleteEvent/{ID}" class="btn btn-sm btn-danger" role="button"><i class="glyphicon glyphicon-trash"></i></a></td>
</tr>
{/LOOP}
{ALT}
<tr>
<td style="text-align: center;" colspan="6">
No results to show.
</td>
</tr>
{/ALT}
</tbody>
</table>
<a href="{ROOT_URL}calendar/createEvent" class="btn btn-sm btn-primary" role="button">Create</a>

View File

@ -0,0 +1,34 @@
<legend>Create Calendar</legend>
<form action="" method="post" class="form-horizontal">
<input type="hidden" name="token" value="{TOKEN}">
<div class="form-group">
<label for="title" class="col-lg-3 control-label">Title</label>
<div class="col-lg-3">
<input type="text" class="form-control" name="title" id="title">
</div>
</div>
<div class="form-group">
<label for="description" class="col-lg-3 control-label">Description</label>
<div class="col-lg-3">
<textarea class="form-control" name="description" maxlength="2000" rows="10" cols="50" id="description"></textarea>
</div>
</div>
<div class="form-group">
<label for="description" class="col-lg-3 control-label">Timezone</label>
<div class="col-lg-3">
{timezoneSelect}
</div>
</div>
<div class="form-group">
<label for="color" class="col-lg-3 control-label">Calendar Color</label>
<div class="col-lg-3 select-container" id="colorContainer">
{colorSelect}
</div>
</div>
<div class="form-group">
<label for="submit" class="col-lg-3 control-label"></label>
<div class="col-lg-3">
<button name="submit" value="submit" type="submit" class="btn btn-lg btn-primary center-block ">Submit</button>
</div>
</div>
</form>

View File

@ -0,0 +1,29 @@
<legend>Edit Calendar</legend>
<form action="" method="post" class="form-horizontal">
<div class="form-group">
<label for="title" class="col-lg-3 control-label">Title</label>
<div class="col-lg-3">
<input type="text" class="form-check-input form-control" name="title" id="title" value="{title}">
</div>
</div>
<div class="form-group">
<label for="description" class="col-lg-3 control-label">Description</label>
<div class="col-lg-6">
<textarea class="form-control" name="description" maxlength="2000" rows="10" cols="50" id="description">{description}</textarea>
</div>
</div>
<div class="form-group">
<label for="description" class="col-lg-3 control-label">Timezone</label>
<div class="col-lg-3">
{timezoneSelect}
</div>
</div>
<div class="form-group">
<label for="color" class="col-lg-3 control-label">Calendar Color</label>
<div class="col-lg-3 select-container" id="colorContainer">
{colorSelect}
</div>
</div>
<input type="hidden" name="token" value="{TOKEN}">
<button name="submit" value="submit" type="submit" class="btn btn-lg btn-primary center-block">Submit</button>
</form>

View File

@ -0,0 +1,45 @@
<legend>Calendars</legend>
<form action="{ROOT_URL}calendar/deleteCalendar" method="post">
<table class="table table-striped">
<thead>
<tr>
<th style="width: 5%">ID</th>
<th style="width: 20%">Title</th>
<th style="width: 50%">Description</th>
<th style="width: 5%"></th>
<th style="width: 5%"></th>
<th style="width: 5%"></th>
<th style="width: 5%"></th>
<th style="width: 5%">
<input type="checkbox" onchange="checkAll(this)" name="check.br" value="CAL_[]"/>
</th>
</tr>
</thead>
<tbody>
{LOOP}
<tr>
<td align="center">{ID}</td>
<td align="center">{title}</td>
<td>{description}</td>
<td><a href="{ROOT_URL}calendar/byMonth/{ID}" class="btn btn-sm btn-primary" role="button"><i class="glyphicon glyphicon-share-alt"></i></a></td>
<td><a href="{ROOT_URL}calendar/calendar/{ID}" class="btn btn-sm btn-primary" role="button"><i class="glyphicon glyphicon-info-sign"></i></a></td>
<td><a href="{ROOT_URL}calendar/editCalendar/{ID}" class="btn btn-sm btn-warning" role="button"><i class="glyphicon glyphicon-edit"></i></a></td>
<td><a href="{ROOT_URL}calendar/deleteCalendar/{ID}" class="btn btn-sm btn-danger" role="button"><i class="glyphicon glyphicon-trash"></i></a></td>
<td>
<input type="checkbox" value="{ID}" name="CAL_[]">
</td>
</tr>
{/LOOP}
{ALT}
<tr>
<td align="center" colspan="7">
No results to show.
</td>
</tr>
{/ALT}
</tbody>
</table>
<a href="{ROOT_URL}calendar/createCalendar" class="btn btn-sm btn-primary" role="button">Create</a>
<button name="submit" value="submit" type="submit" class="btn btn-sm btn-danger">Delete</button>
</form>
<br />

View File

@ -0,0 +1,42 @@
<div class="container col-md-4 col-lg-4">
<div class="row">
<div class="panel panel-primary">
<div class="panel-heading">
<h3 class="panel-title">Calendar</h3>
</div>
<div class="panel-body">
<div class="row">
<div class="">
<table class="table table-user-primary">
<tbody>
<tr>
<td align="left" width="200"><b>Title</b></td>
<td align="right">{title}</td>
</tr>
<tr>
<td><b>Created</b></td>
<td align="right">{DTC}{createdAt}{/DTC}</td>
</tr>
<tr>
<td align="center" colspan="2"><b>Description</b></td>
</tr>
<tr>
<td colspan="2">{description}</td>
</tr>
<tr>
<td><b>TimeZone</b></td>
<td align="right">{timezone}</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<div class="panel-footer">
<a href="{ROOT_URL}calendar/deleteCalendar/{ID}" class="btn btn-md btn-danger" role="button">Delete</a>
<a href="{ROOT_URL}calendar/editCalendar/{ID}" class="btn btn-md btn-warning" role="button">Edit</a>
<a href="{ROOT_URL}calendar/byMonth/{ID}" class="btn btn-md btn-primary" role="button">View Events</a>
</div>
</div>
</div>
</div>

View File

@ -0,0 +1,20 @@
<div class="form-group">
<label for="day" class="col-lg-3 control-label">All-Day Event</label>
<div class="col-lg-3">
<input class="" type="checkbox" name="allDay" id="allDay" value="true" {CHECKED:allDay=true}>
</div>
</div>
<div class="form-group">
<label for="month" class="col-lg-3 control-label">Event Start</label>
<div class="col-lg-3">
<input type="date" name="date" id="date" class="form-control" value="{date}" />
<input type="time" name="time" id="time" class="form-control" value="{time}" />
</div>
</div>
<div class="form-group">
<label for="month" class="col-lg-3 control-label">Event End</label>
<div class="col-lg-3">
<input type="date" name="endDate" id="endDate" class="form-control" value="{endDate}" />
<input type="time" name="endTime" id="endTime" class="form-control" value="{endTime}" />
</div>
</div>

View File

@ -0,0 +1,37 @@
<legend>Create Event</legend>
<form action="" method="post" class="form-horizontal">
<input type="hidden" name="token" value="{TOKEN}">
{calendarSelect}
<div class="form-group">
<label for="title" class="col-lg-3 control-label">Title</label>
<div class="col-lg-3">
<input type="text" class="form-control" name="title" id="title">
</div>
</div>
<div class="form-group">
<label for="description" class="col-lg-3 control-label">Description</label>
<div class="col-lg-3">
<textarea class="form-control" name="description" maxlength="2000" rows="6" cols="30" id="description"></textarea>
</div>
</div>
<div class="form-group">
<label for="location" class="col-lg-3 control-label">Location</label>
<div class="col-lg-3">
<input type="text" class="form-control" name="location" id="location">
</div>
</div>
<div class="form-group">
<label for="color" class="col-lg-3 control-label">Event Color</label>
<div class="col-lg-3 select-container" id="colorContainer">
{colorSelect}
</div>
</div>
{dateSelect}
{repeatSelect}
<div class="form-group">
<label for="submit" class="col-lg-3 control-label"></label>
<div class="col-lg-3">
<button name="submit" value="submit" type="submit" class="btn btn-lg btn-primary center-block">Submit</button>
</div>
</div>
</form>

View File

@ -0,0 +1,31 @@
<legend>Edit Event</legend>
<form action="" method="post" class="form-horizontal">
<div class="form-group">
<label for="title" class="col-lg-3 control-label">Title</label>
<div class="col-lg-3">
<input type="text" class="form-control" name="title" id="title" value="{title}">
</div>
</div>
<div class="form-group">
<label for="description" class="col-lg-3 control-label">Description</label>
<div class="col-lg-6">
<textarea class="form-control" name="description" maxlength="2000" rows="10" cols="50" id="description">{description}</textarea>
</div>
</div>
<div class="form-group">
<label for="location" class="col-lg-3 control-label">Location</label>
<div class="col-lg-6">
<input type="text" class="form-control" name="location" id="location" value="{location}">
</div>
</div>
<div class="form-group">
<label for="color" class="col-lg-3 control-label">Event Color</label>
<div class="col-lg-3 select-container" id="colorContainer">
{colorSelect}
</div>
</div>
{dateSelect}
{repeatSelect}
<input type="hidden" name="token" value="{TOKEN}">
<button name="submit" value="submit" type="submit" class="btn btn-lg btn-primary center-block">Submit</button>
</form>

View File

@ -0,0 +1,34 @@
{PAGINATION}
<table class="table table-striped">
<thead>
<tr>
<th style="width: 10%">ID</th>
<th style="width: 20%">Time</th>
<th style="width: 40%">Title</th>
<th style="width: 10%"></th>
<th style="width: 10%"></th>
<th style="width: 10%"></th>
</tr>
</thead>
<tbody>
{LOOP}
<tr>
<td style="text-align: center;">{ID}</td>
<td style="text-align: center;">{DTC}{event_time}{/DTC}</td>
<td style="text-align: center;">{title}</td>
<td><a href="{ROOT_URL}calendar/event/{ID}" class="btn btn-sm btn-primary" role="button"><i class="glyphicon glyphicon-open"></i></a></td>
<td><a href="{ROOT_URL}calendar/editEvent/{ID}" class="btn btn-sm btn-warning" role="button"><i class="glyphicon glyphicon-edit"></i></a></td>
<td><a href="{ROOT_URL}calendar/deleteEvent/{ID}" class="btn btn-sm btn-danger" role="button"><i class="glyphicon glyphicon-trash"></i></a></td>
</tr>
{/LOOP}
{ALT}
<tr>
<td style="text-align: center;" colspan="6">
No results to show.
</td>
</tr>
{/ALT}
</tbody>
</table>
{PAGINATION}
<a href="{ROOT_URL}calendar/createEvent" class="btn btn-sm btn-primary" role="button">Create</a>

View File

@ -0,0 +1,52 @@
<div class="container col-md-4 col-lg-4">
<div class="row">
<div class="panel panel-primary">
<div class="panel-heading">
<h3 class="panel-title">Event</h3>
</div>
<div class="panel-body">
<div class="row">
<table class="table table-user-primary">
<tbody>
<tr>
<td style="text-align: left;" width="200"><b>Title</b></td>
<td style="text-align: right;">{title}</td>
</tr>
<tr>
<td><b>Created</b></td>
<td style="text-align: right;">{DTC}{createdAt}{/DTC}</td>
</tr>
<tr>
<td><b>Event Time</b></td>
<td style="text-align: right;">{DTC}{event_time}{/DTC}</td>
</tr>
<tr>
<td style="text-align: center;" colspan="2"><b>Description</b></td>
</tr>
<tr>
<td colspan="2">{description}</td>
</tr>
<tr>
<td><b>Location</b></td>
<td style="text-align: right;">{location}</td>
</tr>
<tr>
<td><b>Frequency</b></td>
<td style="text-align: right;">{repeatsText}</td>
</tr>
<tr>
<td><b>Calendar</b></td>
<td style="text-align: right;">{calendarName}</td>
</tr>
</tbody>
</table>
</div>
</div>
<div class="panel-footer">
<a href="{ROOT_URL}calendar/deleteEvent/{ID}" class="btn btn-md btn-danger" role="button">Delete</a>
<a href="{ROOT_URL}calendar/editEvent/{ID}" class="btn btn-md btn-warning" role="button">Edit</a>
<a href="{ROOT_URL}calendar/byMonth/{calendar_id}" class="btn btn-md btn-primary" role="button">View Events</a>
</div>
</div>
</div>
</div>

View File

@ -0,0 +1,16 @@
<li class="pull-right dropdown">
<button type="button" class="btn btn-primary dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
Calendars <span class="caret"></span>
</button>
<ul class="dropdown-menu">
<li><a href="{ROOT_URL}calendar/{currentView}/">All</a></li>
<li role="separator" class="divider"></li>
{LOOP}
<li><a href="{ROOT_URL}calendar/{currentView}/{ID}">{title}</a></li>
{/LOOP}
<li role="separator" class="divider"></li>
<li><a href="{ROOT_URL}calendar/createCalendar">Create Calendar</a></li>
<li><a href="{ROOT_URL}calendar/editCalendar/{calendarID}">Edit Calendar</a></li>
<li><a href="{ROOT_URL}calendar/deleteCalendar/{calendarID}">Delete Calendar</a></li>
</ul>
</li>

View File

@ -0,0 +1,23 @@
<ul class="list-group">
{LOOP}
<li class="list-group-item btn-{displayColor}">
<p style="text-align: center;">{title}</p>
<p style="text-align: center;">{DTC}{event_time}{/DTC}</p>
<p>
<a href="{ROOT_URL}calendar/event/{ID}" class="btn btn-sm btn-primary" role="button"><i class="glyphicon glyphicon-open"></i></a>
<a href="{ROOT_URL}calendar/editEvent/{ID}" class="btn btn-sm btn-warning" role="button"><i class="glyphicon glyphicon-edit"></i></a>
<a href="{ROOT_URL}calendar/deleteEvent/{ID}" class="btn btn-sm btn-danger" role="button"><i class="glyphicon glyphicon-trash"></i></a>
</p>
</li>
{/LOOP}
{ALT}
<li class="list-group-item">
<br />
<br />
<br />
<br />
<br />
</li>
{/ALT}
<a href="{ROOT_URL}calendar/createEvent?calendar_id={calendarID}&month={currentMonth}&day={currentDay}" class="list-group-item list-group-item-success"><i class="glyphicon glyphicon-plus"></i></a>
</ul>

View File

@ -0,0 +1,18 @@
<ul class="nav nav-pills">
<li class="dropdown pull-right">
<button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
Switch Date <span class="caret"></span>
</button>
<ul class="dropdown-menu">
<form action="" method="post" class="form-horizontal">
<li><input type="date" name="date" id="date" class="form-control" value="{date}" /></li>
<li role="separator" class="divider"></li>
<li>
<button name="submit" value="submit" type="submit" class="btn btn-primary center-block">
Select Date
</button>
</li>
</form>
</ul>
</li>
</ul>

View File

@ -0,0 +1,12 @@
<span class="hour-body">
{LOOP}
<div class="hour-cell btn-{displayColor}">
<p class="event-title">{title}</p>
<a href="{ROOT_URL}calendar/event/{ID}" class="btn btn-sm btn-primary" role="button"><i class="glyphicon glyphicon-open"></i></a>
<a href="{ROOT_URL}calendar/editEvent/{ID}" class="btn btn-sm btn-warning" role="button"><i class="glyphicon glyphicon-edit"></i></a>
<a href="{ROOT_URL}calendar/deleteEvent/{ID}" class="btn btn-sm btn-danger" role="button"><i class="glyphicon glyphicon-trash"></i></a>
</div>
{/LOOP}
{ALT}
{/ALT}
</span>

View File

@ -0,0 +1,8 @@
<ul class="nav nav-tabs">
<li><a href="{ROOT_URL}calendar/byDay/{calendarID}">Daily</a></li>
<li><a href="{ROOT_URL}calendar/byWeek/{calendarID}">Weekly</a></li>
<li><a href="{ROOT_URL}calendar/byMonth/{calendarID}">Monthly</a></li>
<li><a href="{ROOT_URL}calendar/byYear/{calendarID}">Yearly</a></li>
<li><a href="{ROOT_URL}calendar/events/{calendarID}">Events</a></li>
{calendarDropdown}
</ul>

View File

@ -0,0 +1,9 @@
<div class="row calendar-row">
{LOOP}
<div class="col-xs-1 calendar-cell">
<h4 class="{highlightedDate}">{day}</h4>{dayEventList}
</div>
{/LOOP}
{ALT}
{/ALT}
</div>

View File

@ -0,0 +1,37 @@
<?php
/**
* app/plugins/chat/controllers/admin/chat.php
*
* This is the chat admin controller.
*
* @package TP Chat
* @version 3.0
* @author Joey Kimsey <Joey@thetempusproject.com>
* @link https://TheTempusProject.com
* @license https://opensource.org/licenses/MIT [MIT LICENSE]
*/
namespace TheTempusProject\Controllers\Admin;
use TheTempusProject\Bedrock\Functions\Input;
use TheTempusProject\Houdini\Classes\Issues;
use TheTempusProject\Houdini\Classes\Views;
use TheTempusProject\Houdini\Classes\Navigation;
use TheTempusProject\Houdini\Classes\Components;
use TheTempusProject\Classes\AdminController;
use TheTempusProject\Models\Chat as ChatModel;
class Chat extends AdminController {
protected static $chat;
public function __construct() {
parent::__construct();
self::$title = 'Admin - Chat';
self::$chat = new ChatModel;
$view = Navigation::activePageSelect( 'nav.admin', '/admin/chat' );
Components::set( 'ADMINNAV', $view );
}
public function index() {
// Views::view( 'chat.admin.list', self::$chat->list() );
}
}

View File

@ -0,0 +1,45 @@
<?php
/**
* app/controllers/api/users.php
*
* This is the users' api controller.
*
* @version 3.0
* @author Joey Kimsey <Joey@thetempusproject.com>
* @link https://TheTempusProject.com
* @license https://opensource.org/licenses/MIT [MIT LICENSE]
*/
namespace TheTempusProject\Controllers\Api;
use TheTempusProject\Models\User;
use TheTempusProject\Classes\ApiController;
use TheTempusProject\Houdini\Classes\Views;
use TheTempusProject\Bedrock\Functions\Input;
use TheTempusProject\Classes\Forms;
use TheTempusProject\Houdini\Classes\Template;
use TheTempusProject\Models\Chat;
class Messages extends ApiController {
public static $chat;
public function __construct() {
parent::__construct();
self::$chat = new Chat;
Template::setTemplate( 'api' );
}
public function sendMessage() {
$response = true;
if ( ! Forms::check( 'newChatMessage' ) ) {
$response = 'Invalid Form';
} else {
$response = self::$chat->create( Input::post( 'chatMessage' ) );
}
Views::view( 'api.response', ['response' => json_encode( [ 'data' => $response ], true )]);
}
public function getMessages() {
$response = Views::simpleView( 'chat.chat', self::$chat->recent( 50 ) );
Views::view( 'api.response', ['response' => json_encode( [ 'data' => $response ], true )]);
}
}

View File

@ -0,0 +1,191 @@
<?php
/**
* app/plugins/chat/controllers/chat.php
*
* This is the public chat controller.
*
* @package TP Chat
* @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\Upload;
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\Chat as ChatModel;
use TheTempusProject\Models\Upload as UploadModel;
use TheTempusProject\TheTempusProject as App;
use TheTempusProject\Houdini\Classes\Components;
use TheTempusProject\Houdini\Classes\Template;
use TheTempusProject\Canary\Canary as Debug;
class Chat extends Controller {
protected static $chat;
public function __construct() {
parent::__construct();
self::$chat = new ChatModel;
Template::setTemplate( 'chat' );
}
public function index() {
if ( !App::$isMember ) {
Session::flash( 'error', 'You do not have permission to view this page.' );
return Redirect::home();
}
self::$title = '{SITENAME} Chat';
self::$pageDescription = 'One of the privleges of membership is the ability to chat with your fellow members.';
if ( App::$isLoggedIn ) {
Components::set( 'CREATE_MESSAGE', Views::simpleView( 'chat.create' ) );
} else {
Components::set( 'CREATE_MESSAGE', '' );
}
$upload = '';
$sharePlugin = 'TheTempusProject\Plugins\Fileshare';
if ( class_exists( $sharePlugin ) ) {
$plugin = new $sharePlugin;
if ( $plugin->checkEnabled() ) {
$upload = Views::simpleView( 'chat.upload' );
}
}
Components::set( 'FILE_UPLOAD', $upload );
Components::set( 'CHAT', Views::simpleView( 'chat.chat' ) );
return Views::view( 'chat.index' );
}
public function sendMessage() {
Template::setTemplate( 'api' );
$out = [ 'response' => true ];
if ( !Forms::check( 'newChatMessage' ) ) {
$out = [ 'response' => false ];
echo json_encode($out);
return;
}
self::$chat->create(
Input::post( 'chatMessage' ),
);
echo json_encode($out);
return;
}
public function uploadFile() {
Template::setTemplate( 'api' );
$out = [ 'response' => true ];
if ( ! Forms::check( 'newFileUpload' ) ) {
$out = [ 'response' => false ];
echo json_encode($out);
return;
}
$sharePlugin = 'TheTempusProject\Plugins\Fileshare';
if ( ! class_exists( $sharePlugin ) ) {
$out = [ 'error' => 'Fileshare must be installed and enabled for this feature to work1.' ];
echo json_encode($out);
return;
}
$plugin = new $sharePlugin;
if ( ! $plugin->checkEnabled() ) {
$out = [ 'error' => 'Fileshare must be installed and enabled for this feature to work2.' ];
echo json_encode($out);
return;
}
$folder = UPLOAD_DIRECTORY . App::$activeUser->username . DIRECTORY_SEPARATOR;
if ( ! Upload::image( 'file', $folder ) ) {
$out = [ 'error' => 'could not upload image' ];
echo json_encode($out);
return;
}
$route = str_replace( APP_ROOT_DIRECTORY, '', $folder );
$location = $route . Upload::last();
$uploads = new UploadModel;
$result = $uploads->create( 'Chat Upload', $location );
if ( ! $result ) {
$out = [ 'error' => 'could not add upload to fileshare.' ];
echo json_encode( $out );
return;
}
self::$chat->create(
'Shared a file with the chat: ' .
'<a href="/' . $location . '" target="_blank">Upload</a>'
);
echo json_encode($out);
return;
}
public function getMessages() {
Template::setTemplate( 'api' );
echo Views::simpleView( 'chat.chat', self::$chat->recent( 50 ) );
return;
}
public function getMessageEvents() {
if ($_SERVER['HTTP_ACCEPT'] !== 'text/event-stream') {
Debug::info( 'connection refused, wrong HTTP_ACCEPT' );
exit();
}
header("X-Accel-Buffering: no");
header('Content-Type: text/event-stream');
header('Cache-Control: no-cache');
header('Connection: keep-alive');
// Ensure unlimited script execution time
set_time_limit(0);
// Disable output buffering
ini_set('output_buffering', 'off');
ini_set('zlib.output_compression', false);
if (Input::exists('lastId')) {
$lastId = Input::get('lastId');
} else {
$lastId = 0;
}
while ( true ) {
echo "id: 0\n";
echo "event: ping\n";
echo 'data: {"time": "' . time() . '"}';
echo "\n\n";
if ( connection_aborted() ) {
Debug::info( 'getMessageEvents connection aborted' );
break;
}
$newMessages = self::$chat->sinceMessage( $lastId );
if ( ! empty( $newMessages )) {
foreach ( $newMessages as $message ) {
$lastId = $message->ID;
echo "id: {$message->ID}\n";
echo "data: " . json_encode($message) . "\n\n";
}
}
// If there were any messages added, flush the output buffer
if (ob_get_contents()) {
ob_end_flush();
}
flush();
// sessions will block the end-user from sending messages unless we close the session
session_write_close();
sleep(1);
}
}
}

View File

@ -0,0 +1,116 @@
* {
margin: 0;
padding: 0;
}
body {
margin: 20px auto;
font-family: "Lato";
font-weight: 300;
}
form {
padding: 15px 25px;
display: flex;
gap: 10px;
justify-content: center;
}
form label {
font-size: 1.5rem;
font-weight: bold;
}
input {
font-family: "Lato";
}
a {
color: #0000ff;
text-decoration: none;
}
a:hover {
text-decoration: underline;
}
#chatMessages {
text-align: left;
margin: 0 auto;
padding: 10px;
height: 300px;
border: 1px solid #a7a7a7;
overflow: auto;
}
#usermsg {
flex: 1;
border-radius: 4px;
border: 1px solid #ff9800;
}
#name {
border-radius: 4px;
border: 1px solid #ff9800;
padding: 2px 8px;
}
#submitmsg,
#enter{
background: #ff9800;
border: 2px solid #e65100;
color: white;
padding: 4px 10px;
font-weight: bold;
border-radius: 4px;
}
.error {
color: #ff0000;
}
#menu {
padding: 15px 25px;
display: flex;
}
#menu p.welcome {
flex: 1;
}
a#exit {
color: white;
background: #c62828;
padding: 4px 8px;
border-radius: 4px;
font-weight: bold;
}
.msgln {
margin: 0 0 5px 0;
}
.span.left-info {
color: orangered;
}
span .chat-time {
color: #666;
font-size: 80%;
vertical-align: super;
width: 100px;
}
.msgln b.user-name, .msgln b.user-name-left {
font-weight: bold;
background: #546e7a;
color: white;
padding: 2px 4px;
font-size: 90%;
border-radius: 4px;
margin: 0 5px 0 0;
}
.msgln b.user-name-left {
background: orangered;
}

View File

@ -0,0 +1,52 @@
<?php
/**
* app/plugins/chat/forms.php
*
* This houses all of the form checking functions for this plugin.
*
* @package TP Chat
* @version 3.0
* @author Joey Kimsey <Joey@thetempusproject.com>
* @link https://TheTempusProject.com
* @license https://opensource.org/licenses/MIT [MIT LICENSE]
*/
namespace TheTempusProject\Plugins\Chat;
use TheTempusProject\Bedrock\Functions\Input;
use TheTempusProject\Bedrock\Functions\Check;
use TheTempusProject\Classes\Forms;
class ChatForms extends Forms {
/**
* Adds these functions to the form list.
*/
public function __construct() {
self::addHandler( 'newChatMessage', __CLASS__, 'newChatMessage' );
self::addHandler( 'newFileUpload', __CLASS__, 'newFileUpload' );
}
/**
* Validates the new blog post form.
*
* @return {bool}
*/
public static function newChatMessage() {
if ( !Input::exists( 'chatMessage' ) ) {
self::addUserError( 'You must includes a message' );
return false;
}
// if (!self::token()) {
// return false;
// }
return true;
}
public static function newFileUpload() {
if ( !Input::exists( 'file' ) ) {
self::addUserError( 'You must includes a file' );
return false;
}
return true;
}
}
new ChatForms;

116
app/plugins/chat/js/chat.js Normal file
View File

@ -0,0 +1,116 @@
$(document).ready(function() {
var chatform = $('#sendChatMessage');
var uploadForm = $('#uploadFile');
chatform.bind('submit', function(event) {
event.preventDefault(); // Prevent page reload
var msg = $("#chatMessage").val();
console.log("Submitting message:", msg);
var ajax_params = {
url: '/chat/sendMessage',
type: 'POST',
data: { chatMessage: msg },
success: function(response) {
console.log("Message sent successfully:", response);
$("#chatMessage").val("");
},
error: function(xhr, status, error) {
console.error("Error sending message:", error, status, xhr);
},
complete: function() {
console.log("AJAX request complete");
}
};
$.ajax(ajax_params);
return false;
});
uploadForm.bind('submit', function(event) {
event.preventDefault(); // Prevent page reload
var formData = new FormData(this); // Create FormData object
$.ajax({
url: '/chat/uploadFile',
type: 'POST',
data: formData,
processData: false, // Don't process the files
contentType: false, // Set content type to false as jQuery will tell the server its a query string request
success: function(response) {
console.log("File uploaded successfully:", response);
$("#file").val("");
},
error: function(xhr, status, error) {
console.error("Error uploading file:", error, status, xhr);
},
complete: function() {
console.log("AJAX request complete");
}
});
return false;
});
const eventSource = new EventSource('/chat/getMessageEvents');
eventSource.onmessage = function( event ) {
const message = JSON.parse( event.data );
var userPopup = "<div class='media'>" +
"<span class='pull-left'>" +
"<img class='media-object avatar-round-40' src='/"+message.avatar+"' alt=''>" +
"</span>" +
"<div class='media-body'>" +
"<h5 class='media-heading'>" +
"<strong>"+message.submittedByName+"</strong>" +
"</h5>" +
"<a href='/home/profile/"+message.submittedByName+"' class='btn btn-sm btn-primary' role='button'><i class='glyphicon glyphicon-open'></i> View Profile</a>" +
"</div>" +
"</div>";
$("#chatMessages").append(
"<span class='chat-time'>" +
new Date(message.submittedAt * 1000).toLocaleString('en-US', { month: 'long', day: 'numeric', hour: 'numeric', minute: 'numeric', hour12: true }) +
// new Date(message.submittedAt * 1000).toLocaleString() +
"</span> " +
'<a tabindex="0" role="button" data-toggle="popover" data-html="true" data-trigger="focus" title="'+message.submittedByName+'" data-content="'+userPopup+'">' +
message.submittedByName +
'</a> ' +
message.chatMessage + "<br>"
);
$("[data-toggle=popover]").popover();
$("#chatMessages").scrollTop($("#chatMessages")[0].scrollHeight);
};
eventSource.onerror = function(event) {
console.error('EventSource failed:', event);
};
window.addEventListener('beforeunload', function() {
eventSource.close();
});
window.addEventListener('unload', function() {
eventSource.close();
});
// function getUserProfile( id ) {
// var ajax_params = {
// url: '/api/users/find/' + id,
// type: 'GET',
// success: function(response) {
// console.log("User retrieved:", response);
// $("#chatMessage").val("");
// },
// error: function(xhr, status, error) {
// console.error("Error retrieved user:", error, status, xhr);
// },
// complete: function() {
// console.log("AJAX request complete");
// }
// };
// $.ajax(ajax_params);
// }
});

View File

@ -0,0 +1,145 @@
<?php
/**
* app/plugins/chat/models/chat.php
*
* This class is used for the manipulation of the chat database table.
*
* @todo make this send a confirmation email
*
* @package TP Chat
* @version 3.0
* @author Joey Kimsey <Joey@thetempusproject.com>
* @link https://TheTempusProject.com
* @license https://opensource.org/licenses/MIT [MIT LICENSE]
*/
namespace TheTempusProject\Models;
use TheTempusProject\Bedrock\Classes\Config;
use TheTempusProject\Bedrock\Functions\Check;
use TheTempusProject\Canary\Canary as Debug;
use TheTempusProject\Classes\DatabaseModel;
use TheTempusProject\TheTempusProject as App;
use TheTempusProject\Houdini\Classes\Filters;
class Chat extends DatabaseModel {
public $tableName = 'chat';
public $databaseMatrix = [
[ 'submittedAt', 'int', '10' ],
[ 'submittedBy', 'int', '11' ],
[ 'chatMessage', 'text', '' ],
];
/**
* The model constructor.
*/
public function __construct() {
parent::__construct();
}
/**
* Saves a chat form to the db.
*
* @param string $message -contents of the chat form.
* @return bool
*/
public function create( $message ) {
$fields = [
'submittedBy' => App::$activeUser->ID,
'submittedAt' => time(),
'chatMessage' => $message,
];
if ( !self::$db->insert( $this->tableName, $fields ) ) {
Debug::info( 'Chat::create - failed to insert to db' );
return false;
}
return self::$db->lastId();
}
public function filter( $data, $params = [] ) {
foreach ( $data as $instance ) {
if ( !is_object( $instance ) ) {
$instance = $data;
$end = true;
}
$instance->chatMessage = Filters::applyOne( 'mentions.0', $instance->chatMessage, true );
$instance->chatMessage = Filters::applyOne( 'hashtags.0', $instance->chatMessage, true );
$user = self::$user->findById( $instance->submittedBy );
if ( ! empty( $user ) ) {
$instance->submittedByName = $user->username;
$instance->profileUrl = '/home/profile/' . $user->username;
} else {
$instance->submittedByName = 'Unknown';
$instance->profileUrl = '#';
}
$instance->avatar = self::$user->getAvatar( $instance->submittedBy );
$out[] = $instance;
if ( !empty( $end ) ) {
$out = $out[0];
break;
}
}
return $out;
}
/**
* Function to clear chat from the DB.
*
* @todo is there a way i could check for success here I'm pretty sure this is just a bad idea?
* @return bool
*/
public function clear() {
if ( empty( self::$log ) ) {
self::$log = new Log;
}
self::$db->delete( $this->tableName, ['ID', '>=', '0'] );
self::$log->admin( 'Cleared Chat' );
Debug::info( 'Chat Cleared' );
return true;
}
public function recent( $limit = null ) {
if ( empty( $limit ) ) {
$postData = self::$db->get( $this->tableName, '*' );
} else {
$postData = self::$db->get( $this->tableName, '*', 'ID', 'ASC', [0, $limit] );
}
if ( !$postData->count() ) {
Debug::info( 'No messages found.' );
return false;
}
return $this->filter( $postData->results() );
}
public function sinceMessage($id = 0) {
if (empty($id)) {
$postData = self::$db->get($this->tableName, '*', 'ID', 'DESC', [0, 20]);
} else {
$postData = self::$db->get($this->tableName, ['ID', '>', $id], 'ID', 'ASC', [0, 20]);
}
if ( ! $postData->count() ) {
Debug::debug( 'No messages found.' );
return false;
}
if (empty($id)) {
$results = array_reverse($postData->results()); // Reverse the order to get ascending IDs
} else {
$results = $postData->results();
}
return $this->filter($results);
}
public function sinceMessageSingle( $id = 0 ) {
if ( empty( $id ) ) {
$postData = self::$db->get( $this->tableName, '*', 'ID', 'ASC', [0, 1] );
} else {
$postData = self::$db->get( $this->tableName, [ 'ID', '>', $id ], 'ID', 'ASC', [0, 1] );
}
if ( ! $postData->count() ) {
Debug::debug( 'No messages found.' );
return false;
}
return $this->filter( $postData->results() );
}
}

View File

@ -0,0 +1,55 @@
<?php
/**
* app/plugins/chat/plugin.php
*
* This houses all of the main plugin info and functionality.
*
* @package TP Chat
* @version 3.0
* @author Joey Kimsey <Joey@thetempusproject.com>
* @link https://TheTempusProject.com
* @license https://opensource.org/licenses/MIT [MIT LICENSE]
*/
namespace TheTempusProject\Plugins;
use TheTempusProject\Classes\Plugin;
use TheTempusProject\TheTempusProject as App;
class Chat extends Plugin {
public $pluginName = 'TP Chat';
public $configName = 'chat';
public $pluginAuthor = 'JoeyK';
public $pluginWebsite = 'https://TheTempusProject.com';
public $modelVersion = '1.0';
public $pluginVersion = '3.0';
public $pluginDescription = 'A simple plugin which adds a site wide chat system.';
public $permissionMatrix = [
'chat' => [
'pretty' => 'Can use chat',
'default' => false,
],
];
public $admin_links = [
[
'text' => '<i class="fa fa-fw fa-copy"></i> Chat',
'url' => '{ROOT_URL}admin/chat',
],
];
public $main_links = [
[
'text' => 'Chat',
'url' => '{ROOT_URL}chat/index',
'filter' => 'loggedin',
],
];
public $configMatrix = [
'enabled' => [
'type' => 'radio',
'pretty' => 'Enable Chat.',
'default' => true,
],
];
public function __construct( $load = false ) {
parent::__construct( $load );
}
}

View File

@ -0,0 +1,32 @@
<?php
/**
* app/plugins/blog/templates/blog.inc.php
*
* This is the loader for the blog template.
*
* @package TP Blog
* @version 3.0
* @author Joey Kimsey <Joey@thetempusproject.com>
* @link https://TheTempusProject.com
* @license https://opensource.org/licenses/MIT [MIT LICENSE]
*/
namespace TheTempusProject\Templates;
use TheTempusProject\Plugins\Calendar;
use TheTempusProject\Houdini\Classes\Components;
use TheTempusProject\Houdini\Classes\Navigation;
use TheTempusProject\Houdini\Classes\Views;
use TheTempusProject\Houdini\Classes\Template;
use TheTempusProject\Bedrock\Functions\Input;
class ChatLoader extends DefaultLoader {
/**
* This is the function used to generate any components that may be
* needed by this template.
*/
public function __construct() {
$this->addJs( '<script language="JavaScript" crossorigin="anonymous" type="text/javascript" src="{ROOT_URL}app/plugins/chat/js/chat.js"></script>' );
parent::__construct();
}
}

View File

@ -0,0 +1,88 @@
<!DOCTYPE html>
<html lang="en">
<!--
* app/templates/default/default.tpl
*
* @version 3.0
* @author Joey Kimsey <Joey@thetempusproject.com>
* @link https://TheTempusProject.com
* @license https://opensource.org/licenses/MIT [MIT LICENSE]
-->
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<meta property="og:url" content="{CURRENT_URL}">
<meta name='twitter:card' content='summary_large_image' />
<title>{TITLE}</title>
<meta itemprop="name" content="{TITLE}">
<meta name="twitter:title" content="{TITLE}">
<meta property="og:title" content="{TITLE}">
<meta name="description" content="{PAGE_DESCRIPTION}">
<meta itemprop="description" content="{PAGE_DESCRIPTION}">
<meta name="twitter:description" content="{PAGE_DESCRIPTION}">
<meta property="og:description" content="{PAGE_DESCRIPTION}">
<meta itemprop="image" content="{META_IMAGE}">
<meta name="twitter:image" content="{META_IMAGE}">
<meta property="og:image" content="{META_IMAGE}">
<meta name="viewport" content="width=device-width, initial-scale=1">
{AUTHOR}
{ROBOT}
<link rel="icon" href="{ROOT_URL}images/favicon.ico">
<!-- Required CSS -->
<link rel="stylesheet" href="{FONT_AWESOME_URL}font-awesome.min.css" crossorigin="anonymous">
<link rel="stylesheet" href="{BOOTSTRAP_CDN}css/bootstrap-theme.min.css" crossorigin="anonymous">
<link rel="stylesheet" href="{BOOTSTRAP_CDN}css/bootstrap.min.css" crossorigin="anonymous">
<!-- Custom styles for this template -->
{TEMPLATE_CSS_INCLUDES}
</head>
<body>
<nav class="navbar navbar-inverse navbar-fixed-top" role="navigation">
<!--Brand and toggle should get grouped for better mobile display but I had to account for additional menus-->
<div class="navbar-header">
<a href="{ROOT_URL}" class="navbar-brand">{SITENAME}</a>
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-ex1-collapse" style="">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
</div>
<div class="container-fluid">
<div class="collapse navbar-collapse navbar-ex1-collapse">
{topNavLeft}
<div class="navbar-right">
<ul class="nav navbar-nav">
{topNavRight}
</ul>
</div>
</div>
</div>
</nav>
<div class="container-fluid top-pad foot-pad">
{ISSUES}
<div class="container">
<div class="row">
{ERROR}
{NOTICE}
{SUCCESS}
{INFO}
</div>
</div>
{/ISSUES}
<div class="container">
<div class="row">
{CONTENT}
</div>
</div>
</div>
<footer>
{FOOT}
{COPY}
</footer>
<!-- 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="{BOOTSTRAP_CDN}js/bootstrap.min.js"></script>
<!-- Custom javascript for this template -->
{TEMPLATE_JS_INCLUDES}
</body>
</html>

View File

View File

@ -0,0 +1,10 @@
{LOOP}
<div class='msgln'>
<span class='chat-time'>{DTC time}{submittedAt}{/DTC}</span> <b class='user-name'>{submittedByName}</b>: {chatMessage}<br>
</div>
{/LOOP}
{ALT}
<div class="msgln">
No messages Found.<br>
</div>
{/ALT}

View File

@ -0,0 +1,9 @@
<form class="form-horizontal" id="sendChatMessage">
<input type="hidden" name="token" value="{TOKEN}">
<div class="form-group col-lg-10">
<input type="text" class="form-control" name="chatMessage" id="chatMessage">
</div>
<div class="form-group col-lg-2">
<button id="submitChatMessage" name="submitChatMessage" value="button" class="btn btn-sm btn-primary center-block ">Send</button>
</div>
</form>

View File

@ -0,0 +1,18 @@
<link rel="stylesheet" href="{ROOT_URL}app/plugins/chat/css/chat.css" crossorigin="anonymous">
<span class="col-lg-12" id="Chat">
<div class="panel panel-default" role="tab">
<div class="panel-heading">
<button type="button" class="btn btn-default btn-sm" data-target="#Collapse${id}" data-toggle="collapse" aria-expanded="true" aria-controls="#Collapse${id}">
<i class="glyphicon glyphicon-th-list"></i> {USERNAME}
</button>
</div>
<div id="Collapse${id}" class="panel-collapse collapse in" style="width:100%; position: relative;" role="tabpanel" aria-expanded="true">
<div class="panel-body" id="chatMessages">
</div>
<div class="panel-footer">
{CREATE_MESSAGE}
{FILE_UPLOAD}
</div>
</div>
</div>
</span>

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