Initial commit
This commit is contained in:
105
app/plugins/blog/controllers/admin/blog.php
Normal file
105
app/plugins/blog/controllers/admin/blog.php
Normal 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' ) ) );
|
||||
}
|
||||
}
|
158
app/plugins/blog/controllers/blog.php
Normal file
158
app/plugins/blog/controllers/blog.php
Normal 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 ) );
|
||||
}
|
||||
}
|
81
app/plugins/blog/forms.php
Normal file
81
app/plugins/blog/forms.php
Normal 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;
|
321
app/plugins/blog/models/posts.php
Normal file
321
app/plugins/blog/models/posts.php
Normal 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() );
|
||||
}
|
||||
}
|
47
app/plugins/blog/plugin.php
Normal file
47
app/plugins/blog/plugin.php
Normal 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 );
|
||||
}
|
||||
}
|
37
app/plugins/blog/templates/blog.inc.php
Normal file
37
app/plugins/blog/templates/blog.inc.php
Normal 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();
|
||||
}
|
||||
}
|
112
app/plugins/blog/templates/blog.tpl
Normal file
112
app/plugins/blog/templates/blog.tpl
Normal 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>
|
19
app/plugins/blog/templates/rss.inc.php
Normal file
19
app/plugins/blog/templates/rss.inc.php
Normal 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();
|
||||
}
|
||||
}
|
11
app/plugins/blog/templates/rss.tpl
Normal file
11
app/plugins/blog/templates/rss.tpl
Normal 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>
|
31
app/plugins/blog/views/admin/create.html
Normal file
31
app/plugins/blog/views/admin/create.html
Normal 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');">✔</button>
|
||||
<button type="button" class="btn btn-sm btn-primary" onclick="insertTag ('blogPost', 'x');">✖</button>
|
||||
<button type="button" class="btn btn-sm btn-primary" onclick="insertTag ('blogPost', '!');">❕</button>
|
||||
<button type="button" class="btn btn-sm btn-primary" onclick="insertTag ('blogPost', '?');">❔</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="blogPost" class="col-lg-3 control-label">Post</label>
|
||||
<div class="col-lg-6">
|
||||
<textarea class="form-control" name="blogPost" maxlength="2000" rows="10" cols="50" id="blogPost"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="col-lg-6 col-lg-offset-3">
|
||||
<button name="submit" value="publish" type="submit" class="btn btn-lg btn-primary">Publish</button>
|
||||
<button name="submit" value="saveDraft" type="submit" class="btn btn-lg btn-primary">Save as Draft</button>
|
||||
<button name="submit" value="preview" type="submit" class="btn btn-lg btn-primary">Preview</button>
|
||||
</div>
|
||||
</div>
|
||||
<input type="hidden" name="token" value="{TOKEN}">
|
||||
</form>
|
30
app/plugins/blog/views/admin/dashboard.html
Normal file
30
app/plugins/blog/views/admin/dashboard.html
Normal 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>
|
31
app/plugins/blog/views/admin/edit.html
Normal file
31
app/plugins/blog/views/admin/edit.html
Normal 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');">✔</button>
|
||||
<button type="button" class="btn btn-sm btn-primary" onclick="insertTag ('blogPost', 'x');">✖</button>
|
||||
<button type="button" class="btn btn-sm btn-primary" onclick="insertTag ('blogPost', '!');">❕</button>
|
||||
<button type="button" class="btn btn-sm btn-primary" onclick="insertTag ('blogPost', '?');">❔</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="blogPost" class="col-lg-3 control-label">Post</label>
|
||||
<div class="col-lg-6">
|
||||
<textarea class="form-control" name="blogPost" maxlength="2000" rows="10" cols="50" id="blogPost">{content}</textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="col-lg-6 col-lg-offset-3">
|
||||
<button name="submit" value="publish" type="submit" class="btn btn-lg btn-primary">Publish</button>
|
||||
<button name="submit" value="saveDraft" type="submit" class="btn btn-lg btn-primary">Save as Draft</button>
|
||||
<button name="submit" value="preview" type="submit" class="btn btn-lg btn-primary">Preview</button>
|
||||
</div>
|
||||
</div>
|
||||
<input type="hidden" name="token" value="{TOKEN}">
|
||||
</form>
|
45
app/plugins/blog/views/admin/list.html
Normal file
45
app/plugins/blog/views/admin/list.html
Normal 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>
|
40
app/plugins/blog/views/admin/preview.html
Normal file
40
app/plugins/blog/views/admin/preview.html
Normal 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');">✔</button>
|
||||
<button type="button" class="btn btn-sm btn-primary" onclick="insertTag ('blogPost', 'x');">✖</button>
|
||||
<button type="button" class="btn btn-sm btn-primary" onclick="insertTag ('blogPost', '!');">❕</button>
|
||||
<button type="button" class="btn btn-sm btn-primary" onclick="insertTag ('blogPost', '?');">❔</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="blogPost" class="col-lg-3 control-label">Post</label>
|
||||
<div class="col-lg-6">
|
||||
<textarea class="form-control" name="blogPost" maxlength="2000" rows="10" cols="50" id="blogPost">{content}</textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="col-lg-6 col-lg-offset-3">
|
||||
<button name="submit" value="publish" type="submit" class="btn btn-lg btn-primary">Publish</button>
|
||||
<button name="submit" value="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>
|
11
app/plugins/blog/views/admin/view.html
Normal file
11
app/plugins/blog/views/admin/view.html
Normal 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 -->
|
18
app/plugins/blog/views/list.html
Normal file
18
app/plugins/blog/views/list.html
Normal 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}
|
18
app/plugins/blog/views/post.html
Normal file
18
app/plugins/blog/views/post.html
Normal 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 -->
|
15
app/plugins/blog/views/recentWidget.html
Normal file
15
app/plugins/blog/views/recentWidget.html
Normal 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>
|
10
app/plugins/blog/views/rss.html
Normal file
10
app/plugins/blog/views/rss.html
Normal 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}
|
18
app/plugins/blog/views/sidebar.html
Normal file
18
app/plugins/blog/views/sidebar.html
Normal 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>
|
14
app/plugins/blog/views/sidebar2.html
Normal file
14
app/plugins/blog/views/sidebar2.html
Normal 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>
|
402
app/plugins/bookmarks/controllers/bookmarks.php
Normal file
402
app/plugins/bookmarks/controllers/bookmarks.php
Normal 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 );
|
||||
}
|
||||
}
|
122
app/plugins/bookmarks/forms.php
Normal file
122
app/plugins/bookmarks/forms.php
Normal 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;
|
149
app/plugins/bookmarks/models/bookmarkViews.php
Normal file
149
app/plugins/bookmarks/models/bookmarkViews.php
Normal 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;
|
||||
}
|
||||
}
|
705
app/plugins/bookmarks/models/bookmarks.php
Normal file
705
app/plugins/bookmarks/models/bookmarks.php
Normal 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;
|
||||
}
|
||||
}
|
157
app/plugins/bookmarks/models/folders.php
Normal file
157
app/plugins/bookmarks/models/folders.php
Normal 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;
|
||||
}
|
||||
}
|
62
app/plugins/bookmarks/plugin.php
Normal file
62
app/plugins/bookmarks/plugin.php
Normal 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 );
|
||||
}
|
||||
}
|
45
app/plugins/bookmarks/views/bookmarks/create.html
Normal file
45
app/plugins/bookmarks/views/bookmarks/create.html
Normal 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>
|
45
app/plugins/bookmarks/views/bookmarks/edit.html
Normal file
45
app/plugins/bookmarks/views/bookmarks/edit.html
Normal 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>
|
37
app/plugins/bookmarks/views/bookmarks/list.html
Normal file
37
app/plugins/bookmarks/views/bookmarks/list.html
Normal 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>
|
84
app/plugins/bookmarks/views/bookmarks/view.html
Normal file
84
app/plugins/bookmarks/views/bookmarks/view.html
Normal 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>
|
@ -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>
|
18
app/plugins/bookmarks/views/components/bookmarkListRows.html
Normal file
18
app/plugins/bookmarks/views/components/bookmarkListRows.html
Normal 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}
|
10
app/plugins/bookmarks/views/components/folderPanelList.html
Normal file
10
app/plugins/bookmarks/views/components/folderPanelList.html
Normal 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}
|
16
app/plugins/bookmarks/views/dash.html
Normal file
16
app/plugins/bookmarks/views/dash.html
Normal 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>
|
39
app/plugins/bookmarks/views/folders/create.html
Normal file
39
app/plugins/bookmarks/views/folders/create.html
Normal 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>
|
39
app/plugins/bookmarks/views/folders/edit.html
Normal file
39
app/plugins/bookmarks/views/folders/edit.html
Normal 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>
|
33
app/plugins/bookmarks/views/folders/list.html
Normal file
33
app/plugins/bookmarks/views/folders/list.html
Normal 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>
|
47
app/plugins/bookmarks/views/folders/view.html
Normal file
47
app/plugins/bookmarks/views/folders/view.html
Normal 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>
|
5
app/plugins/bookmarks/views/nav/folderTabs.html
Normal file
5
app/plugins/bookmarks/views/nav/folderTabs.html
Normal 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}
|
9
app/plugins/bookmarks/views/nav/userFolderTabs.html
Normal file
9
app/plugins/bookmarks/views/nav/userFolderTabs.html
Normal 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>
|
63
app/plugins/bugreport/controllers/admin/bugreport.php
Normal file
63
app/plugins/bugreport/controllers/admin/bugreport.php
Normal 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();
|
||||
}
|
||||
}
|
52
app/plugins/bugreport/controllers/bugreport.php
Normal file
52
app/plugins/bugreport/controllers/bugreport.php
Normal 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' );
|
||||
}
|
||||
}
|
||||
}
|
51
app/plugins/bugreport/forms.php
Normal file
51
app/plugins/bugreport/forms.php
Normal 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;
|
101
app/plugins/bugreport/models/bugreport.php
Normal file
101
app/plugins/bugreport/models/bugreport.php
Normal 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();
|
||||
}
|
||||
}
|
64
app/plugins/bugreport/plugin.php
Normal file
64
app/plugins/bugreport/plugin.php
Normal 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',
|
||||
],
|
||||
];
|
||||
}
|
42
app/plugins/bugreport/views/admin/list.html
Normal file
42
app/plugins/bugreport/views/admin/list.html
Normal 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>
|
64
app/plugins/bugreport/views/admin/view.html
Normal file
64
app/plugins/bugreport/views/admin/view.html
Normal 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>
|
36
app/plugins/bugreport/views/create.html
Normal file
36
app/plugins/bugreport/views/create.html
Normal 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>
|
7
app/plugins/bugtracker/config/constants.php
Normal file
7
app/plugins/bugtracker/config/constants.php
Normal 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' );
|
206
app/plugins/bugtracker/controllers/admin/bugtracker.php
Normal file
206
app/plugins/bugtracker/controllers/admin/bugtracker.php
Normal 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/' );
|
||||
}
|
||||
}
|
||||
}
|
92
app/plugins/bugtracker/forms.php
Normal file
92
app/plugins/bugtracker/forms.php
Normal 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;
|
142
app/plugins/bugtracker/models/bugtracker.php
Normal file
142
app/plugins/bugtracker/models/bugtracker.php
Normal 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;
|
||||
}
|
||||
}
|
48
app/plugins/bugtracker/plugin.php
Normal file
48
app/plugins/bugtracker/plugin.php
Normal 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',
|
||||
],
|
||||
];
|
||||
}
|
6
app/plugins/bugtracker/views/admin/create.html
Normal file
6
app/plugins/bugtracker/views/admin/create.html
Normal 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>
|
30
app/plugins/bugtracker/views/admin/dashboard.html
Normal file
30
app/plugins/bugtracker/views/admin/dashboard.html
Normal 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>
|
6
app/plugins/bugtracker/views/admin/edit.html
Normal file
6
app/plugins/bugtracker/views/admin/edit.html
Normal 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>
|
45
app/plugins/bugtracker/views/admin/list.html
Normal file
45
app/plugins/bugtracker/views/admin/list.html
Normal 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>
|
74
app/plugins/bugtracker/views/admin/view.html
Normal file
74
app/plugins/bugtracker/views/admin/view.html
Normal 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>
|
593
app/plugins/calendar/controllers/calendar.php
Normal file
593
app/plugins/calendar/controllers/calendar.php
Normal 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,
|
||||
];
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
153
app/plugins/calendar/css/calendar.css
Normal file
153
app/plugins/calendar/css/calendar.css
Normal 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;
|
||||
}
|
136
app/plugins/calendar/forms.php
Normal file
136
app/plugins/calendar/forms.php
Normal 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;
|
157
app/plugins/calendar/models/calendars.php
Normal file
157
app/plugins/calendar/models/calendars.php
Normal 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;
|
||||
}
|
||||
}
|
509
app/plugins/calendar/models/events.php
Normal file
509
app/plugins/calendar/models/events.php
Normal 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,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
84
app/plugins/calendar/plugin.php
Normal file
84
app/plugins/calendar/plugin.php
Normal 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 );
|
||||
}
|
||||
}
|
41
app/plugins/calendar/templates/calendar.inc.php
Normal file
41
app/plugins/calendar/templates/calendar.inc.php
Normal 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();
|
||||
}
|
||||
}
|
96
app/plugins/calendar/templates/calendar.tpl
Normal file
96
app/plugins/calendar/templates/calendar.tpl
Normal 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>
|
25
app/plugins/calendar/views/byDay.html
Normal file
25
app/plugins/calendar/views/byDay.html
Normal 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>
|
34
app/plugins/calendar/views/byMonth.html
Normal file
34
app/plugins/calendar/views/byMonth.html
Normal 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>
|
25
app/plugins/calendar/views/byWeek.html
Normal file
25
app/plugins/calendar/views/byWeek.html
Normal 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>
|
42
app/plugins/calendar/views/byYear.html
Normal file
42
app/plugins/calendar/views/byYear.html
Normal 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>
|
34
app/plugins/calendar/views/calendar/create.html
Normal file
34
app/plugins/calendar/views/calendar/create.html
Normal 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>
|
29
app/plugins/calendar/views/calendar/edit.html
Normal file
29
app/plugins/calendar/views/calendar/edit.html
Normal 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>
|
45
app/plugins/calendar/views/calendar/list.html
Normal file
45
app/plugins/calendar/views/calendar/list.html
Normal 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 />
|
42
app/plugins/calendar/views/calendar/view.html
Normal file
42
app/plugins/calendar/views/calendar/view.html
Normal 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>
|
20
app/plugins/calendar/views/dateSelect.html
Normal file
20
app/plugins/calendar/views/dateSelect.html
Normal 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>
|
37
app/plugins/calendar/views/events/create.html
Normal file
37
app/plugins/calendar/views/events/create.html
Normal 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>
|
31
app/plugins/calendar/views/events/edit.html
Normal file
31
app/plugins/calendar/views/events/edit.html
Normal 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>
|
34
app/plugins/calendar/views/events/list.html
Normal file
34
app/plugins/calendar/views/events/list.html
Normal 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>
|
52
app/plugins/calendar/views/events/view.html
Normal file
52
app/plugins/calendar/views/events/view.html
Normal 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>
|
16
app/plugins/calendar/views/nav/calendarDropdown.html
Normal file
16
app/plugins/calendar/views/nav/calendarDropdown.html
Normal 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>
|
23
app/plugins/calendar/views/nav/cell.html
Normal file
23
app/plugins/calendar/views/nav/cell.html
Normal 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>
|
18
app/plugins/calendar/views/nav/dateDropdown.html
Normal file
18
app/plugins/calendar/views/nav/dateDropdown.html
Normal 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>
|
12
app/plugins/calendar/views/nav/row.html
Normal file
12
app/plugins/calendar/views/nav/row.html
Normal 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>
|
8
app/plugins/calendar/views/nav/topTabs.html
Normal file
8
app/plugins/calendar/views/nav/topTabs.html
Normal 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>
|
9
app/plugins/calendar/views/nav/week.html
Normal file
9
app/plugins/calendar/views/nav/week.html
Normal 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>
|
37
app/plugins/chat/controllers/admin/chat.php
Normal file
37
app/plugins/chat/controllers/admin/chat.php
Normal 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() );
|
||||
}
|
||||
}
|
45
app/plugins/chat/controllers/api/messages.php
Normal file
45
app/plugins/chat/controllers/api/messages.php
Normal 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 )]);
|
||||
}
|
||||
}
|
191
app/plugins/chat/controllers/chat.php
Normal file
191
app/plugins/chat/controllers/chat.php
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
116
app/plugins/chat/css/chat.css
Normal file
116
app/plugins/chat/css/chat.css
Normal 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;
|
||||
}
|
52
app/plugins/chat/forms.php
Normal file
52
app/plugins/chat/forms.php
Normal 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
116
app/plugins/chat/js/chat.js
Normal 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);
|
||||
// }
|
||||
});
|
145
app/plugins/chat/models/chat.php
Normal file
145
app/plugins/chat/models/chat.php
Normal 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() );
|
||||
}
|
||||
}
|
55
app/plugins/chat/plugin.php
Normal file
55
app/plugins/chat/plugin.php
Normal 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 );
|
||||
}
|
||||
}
|
32
app/plugins/chat/templates/chat.inc.php
Normal file
32
app/plugins/chat/templates/chat.inc.php
Normal 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();
|
||||
}
|
||||
}
|
88
app/plugins/chat/templates/chat.tpl
Normal file
88
app/plugins/chat/templates/chat.tpl
Normal 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>
|
0
app/plugins/chat/views/admin/index.html
Normal file
0
app/plugins/chat/views/admin/index.html
Normal file
10
app/plugins/chat/views/chat.html
Normal file
10
app/plugins/chat/views/chat.html
Normal 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}
|
9
app/plugins/chat/views/create.html
Normal file
9
app/plugins/chat/views/create.html
Normal 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>
|
18
app/plugins/chat/views/index.html
Normal file
18
app/plugins/chat/views/index.html
Normal 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
Reference in New Issue
Block a user