hfkfhkfhgjkuhgfkjfghkj
This commit is contained in:
102
app/plugins/blog/controllers/admin/blog.php
Executable file
102
app/plugins/blog/controllers/admin/blog.php
Executable file
@ -0,0 +1,102 @@
|
||||
<?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\Models\Posts;
|
||||
|
||||
class Blog extends AdminController {
|
||||
public static $posts;
|
||||
|
||||
public function __construct() {
|
||||
parent::__construct();
|
||||
self::$posts = new Posts;
|
||||
self::$title = 'Admin - Blog';
|
||||
}
|
||||
|
||||
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( 'slug' ), 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( 'slug' ), 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' ) ) );
|
||||
}
|
||||
}
|
186
app/plugins/blog/controllers/blog.php
Executable file
186
app/plugins/blog/controllers/blog.php
Executable file
@ -0,0 +1,186 @@
|
||||
<?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\Bin\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;
|
||||
use TheTempusProject\Models\Posts as PostsModel;
|
||||
|
||||
class Blog extends Controller {
|
||||
protected static $blog;
|
||||
protected static $posts;
|
||||
|
||||
public function __construct() {
|
||||
parent::__construct();
|
||||
Template::setTemplate( 'blog' );
|
||||
self::$posts = new PostsModel;
|
||||
}
|
||||
|
||||
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( $sub ) || empty( $data ) ) {
|
||||
Issues::add( 'error', 'There was an issue with your request. Please check the url and try again.' );
|
||||
return $this->index();
|
||||
}
|
||||
|
||||
if ( class_exists( 'TheTempusProject\Plugins\Comments' ) ) {
|
||||
$plugin = new Comments;
|
||||
if ( ! $plugin->checkEnabled() ) {
|
||||
Issues::add( 'error', 'Comments are disabled.' );
|
||||
return $this->index();
|
||||
}
|
||||
$comments = new CommentsModel;
|
||||
} else {
|
||||
Debug::info( 'error', 'Comments plugin missing.' );
|
||||
return $this->index();
|
||||
}
|
||||
|
||||
switch ( $sub ) {
|
||||
case 'post':
|
||||
$content = self::$posts->findById( (int) $data );
|
||||
if ( empty( $content ) ) {
|
||||
Issues::add( 'error', 'Unknown Content.' );
|
||||
return $this->index();
|
||||
}
|
||||
return $plugin->formPost( self::$posts->tableName, $content, 'blog/post/' );
|
||||
case 'edit':
|
||||
$content = $comments->findById( $data );
|
||||
if ( empty( $content ) ) {
|
||||
Issues::add( 'error', 'Unknown Comment.' );
|
||||
return $this->index();
|
||||
}
|
||||
return $plugin->formEdit( self::$posts->tableName, $content, 'blog/post/' );
|
||||
case 'delete':
|
||||
$content = $comments->findById( $data );
|
||||
if ( empty( $content ) ) {
|
||||
Issues::add( 'error', 'Unknown Comment.' );
|
||||
return $this->index();
|
||||
}
|
||||
return $plugin->formDelete( self::$posts->tableName, $content, 'blog/post/' );
|
||||
}
|
||||
}
|
||||
|
||||
public function post( $id = null ) {
|
||||
if ( empty( $id ) ) {
|
||||
return $this->index();
|
||||
}
|
||||
$post = self::$posts->findById( $id );
|
||||
if ( empty( $post ) ) {
|
||||
$post = self::$posts->findBySlug( $id );
|
||||
if ( empty( $post ) ) {
|
||||
return $this->index();
|
||||
}
|
||||
}
|
||||
Debug::log( 'Controller initiated: ' . __METHOD__ . '.' );
|
||||
self::$title = 'Blog Post';
|
||||
|
||||
if ( Input::exists( 'contentId' ) ) {
|
||||
$this->comments( 'post', Input::post( 'contentId' ) );
|
||||
}
|
||||
|
||||
Components::set( 'CONTENT_ID', $id );
|
||||
Components::set( 'COMMENT_TYPE', self::$posts->tableName );
|
||||
Components::set( 'NEWCOMMENT', '' );
|
||||
Components::set( 'count', '0' );
|
||||
Components::set( 'COMMENTS', '' );
|
||||
|
||||
if ( class_exists( 'TheTempusProject\Plugins\Comments' ) ) {
|
||||
$plugin = new Comments;
|
||||
if ( $plugin->checkEnabled() ) {
|
||||
$comments = new CommentsModel;
|
||||
if ( App::$isLoggedIn ) {
|
||||
Components::set( 'NEWCOMMENT', Views::simpleView( 'comments.create' ) );
|
||||
} else {
|
||||
Components::set( 'NEWCOMMENT', '' );
|
||||
}
|
||||
Components::set( 'count', $comments->count( self::$posts->tableName, $post->ID ) );
|
||||
Components::set( 'COMMENTS', Views::simpleView( 'comments.list', $comments->display( 10, self::$posts->tableName, $post->ID ) ) );
|
||||
}
|
||||
}
|
||||
|
||||
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 ) );
|
||||
}
|
||||
|
||||
public function search() {
|
||||
$results = [];
|
||||
if ( Input::exists( 'submit' ) ) {
|
||||
$dbResults = self::$posts->search( Input::post('searchTerm') );
|
||||
if ( ! empty( $dbResults ) ) {
|
||||
$results = $dbResults;
|
||||
}
|
||||
}
|
||||
Components::set( 'searchResults', Views::simpleView( 'blog.list', $results ) );
|
||||
Views::view( 'blog.searchResults' );
|
||||
}
|
||||
}
|
81
app/plugins/blog/forms.php
Executable file
81
app/plugins/blog/forms.php
Executable 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;
|
356
app/plugins/blog/models/posts.php
Executable file
356
app/plugins/blog/models/posts.php
Executable file
@ -0,0 +1,356 @@
|
||||
<?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\Bin\Canary as Debug;
|
||||
use TheTempusProject\Bedrock\Functions\Check;
|
||||
use TheTempusProject\Bedrock\Functions\Sanitize;
|
||||
use TheTempusProject\Classes\DatabaseModel;
|
||||
use TheTempusProject\TheTempusProject as App;
|
||||
use TheTempusProject\Canary\Classes\CustomException;
|
||||
use TheTempusProject\Houdini\Classes\Filters;
|
||||
use TheTempusProject\Plugins\Comments as CommentPlugin;
|
||||
use TheTempusProject\Models\Comments;
|
||||
|
||||
class Posts extends DatabaseModel {
|
||||
public $tableName = 'posts';
|
||||
public $searchFields = [
|
||||
'title',
|
||||
'slug',
|
||||
'content',
|
||||
];
|
||||
public static $comments = false;
|
||||
|
||||
public $databaseMatrix = [
|
||||
[ 'author', 'int', '11' ],
|
||||
[ 'created', 'int', '10' ],
|
||||
[ 'edited', 'int', '10' ],
|
||||
[ 'draft', 'int', '1' ],
|
||||
[ 'title', 'varchar', '86' ],
|
||||
[ 'slug', 'varchar', '64' ],
|
||||
[ 'content', 'text', '' ],
|
||||
];
|
||||
|
||||
public function __construct() {
|
||||
parent::__construct();
|
||||
if ( class_exists( 'TheTempusProject\Plugins\Comments' ) ) {
|
||||
$comments = new CommentPlugin;
|
||||
if ( $comments->checkEnabled() ) {
|
||||
self::$comments = new Comments;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function newPost( $title, $post, $slug, $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,
|
||||
'slug' => $slug,
|
||||
'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, $slug, $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,
|
||||
'slug' => $slug,
|
||||
'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 );
|
||||
|
||||
// Summarize
|
||||
if ( ! empty( $instance->slug ) ) {
|
||||
$identifier = $instance->slug;
|
||||
} else {
|
||||
$identifier = $instance->ID;
|
||||
}
|
||||
|
||||
$cleanPost = Sanitize::contentShort( $instance->content );
|
||||
// By Word
|
||||
$wordsArray = explode( ' ', $cleanPost );
|
||||
$wordSummary = implode( ' ', array_splice( $wordsArray, 0, 100 ) );
|
||||
// By Line
|
||||
$linesArray = explode( "\n", $cleanPost );
|
||||
$lineSummary = implode( "\n", array_splice( $linesArray, 0, 5 ) );
|
||||
|
||||
if ( strlen( $wordSummary ) < strlen( $lineSummary ) ) {
|
||||
$contentSummaryNoLink = $wordSummary;
|
||||
$contentSummary = $wordSummary . '... <a href="{ROOT_URL}blog/post/' . $identifier . '" class="text-decoration-none">Read More</a>';
|
||||
} else {
|
||||
$contentSummaryNoLink = $lineSummary;
|
||||
$contentSummary = $lineSummary . '... <a href="{ROOT_URL}blog/post/' . $identifier . '" class="text-decoration-none">Read More</a>';
|
||||
}
|
||||
|
||||
$instance->contentSummaryNoLink = $contentSummaryNoLink;
|
||||
$instance->contentSummary = $contentSummary;
|
||||
|
||||
if ( isset( $params['stripHtml'] ) && $params['stripHtml'] === true ) {
|
||||
$instance->contentSummary = strip_tags( $instance->content );
|
||||
}
|
||||
if ( $instance->draft != '0' ) {
|
||||
$draft = ' <b>Draft</b>';
|
||||
}
|
||||
$instance->isDraft = $draft;
|
||||
$instance->authorName = \ucfirst( $authorName );
|
||||
if ( self::$comments !== false ) {
|
||||
$instance->commentCount = self::$comments->count( 'blog', $instance->ID );
|
||||
} else {
|
||||
$instance->commentCount = 0;
|
||||
}
|
||||
$instance->content = Filters::applyOne( 'mentions.0', $instance->content, true );
|
||||
$instance->content = Filters::applyOne( 'hashtags.0', $instance->content, true );
|
||||
|
||||
$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->get( $this->tableName, $whereClause );
|
||||
} else {
|
||||
$postData = self::$db->get( $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->get( $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->get( $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->get( $this->tableName, $whereClause );
|
||||
if ( !$postData->count() ) {
|
||||
Debug::info( 'No Blog posts found.' );
|
||||
|
||||
return false;
|
||||
}
|
||||
return $this->filter( $postData->results() );
|
||||
}
|
||||
|
||||
public function findBySlug( $slug, $includeDraft = false ) {
|
||||
$whereClause = [];
|
||||
if ( $includeDraft !== true ) {
|
||||
$whereClause = ['draft', '=', '0', 'AND'];
|
||||
}
|
||||
|
||||
$whereClause = array_merge( $whereClause, ['slug', '=', $slug] );
|
||||
|
||||
$postData = self::$db->get( $this->tableName, $whereClause );
|
||||
if ( !$postData->count() ) {
|
||||
Debug::info( 'No Blog posts found.' );
|
||||
return false;
|
||||
}
|
||||
return $this->filter( $postData->first() );
|
||||
}
|
||||
|
||||
public function byMonth( $month, $year = 0, $includeDraft = false ) {
|
||||
if ( 0 === $year ) {
|
||||
$year = date( 'Y' );
|
||||
}
|
||||
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->get( $this->tableName, $whereClause );
|
||||
if ( !$postData->count() ) {
|
||||
Debug::info( 'No Blog posts found.' );
|
||||
|
||||
return false;
|
||||
}
|
||||
return $this->filter( $postData->results() );
|
||||
}
|
||||
}
|
49
app/plugins/blog/plugin.php
Executable file
49
app/plugins/blog/plugin.php
Executable file
@ -0,0 +1,49 @@
|
||||
<?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 TheTempusProject\Classes\Plugin;
|
||||
|
||||
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="fa fa-fw fa-font"></i> Blog',
|
||||
'url' => '{ROOT_URL}admin/blog',
|
||||
],
|
||||
];
|
||||
public $info_footer_links = [
|
||||
[
|
||||
'text' => 'Blog',
|
||||
'url' => '{ROOT_URL}blog/index',
|
||||
],
|
||||
];
|
||||
public $resourceMatrix = [
|
||||
'posts' => [
|
||||
[
|
||||
'title' => 'Welcome',
|
||||
'slug' => 'welcome',
|
||||
'content' => '<p>This is just a simple message to say thank you for installing The Tempus Project. If you have any questions you can find everything through our website <a href="https://TheTempusProject.com">here</a>.</p>',
|
||||
'author' => 1,
|
||||
'created' => '{time}',
|
||||
'edited' => '{time}',
|
||||
'draft' => 0,
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
39
app/plugins/blog/templates/blog.inc.php
Executable file
39
app/plugins/blog/templates/blog.inc.php
Executable file
@ -0,0 +1,39 @@
|
||||
<?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\Models\Posts;
|
||||
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() {
|
||||
$posts = new Posts;
|
||||
Components::set('SIDEBAR', Views::simpleView('blog.widgets.recent', $posts->recent(5)));
|
||||
Components::set('SIDEBAR2', Views::simpleView('blog.widgets.archive', $posts->archive()));
|
||||
Components::set('SIDEBARABOUT', Views::simpleView('blog.widgets.about'));
|
||||
Components::set('SIDEBARSEARCH', Views::simpleView('blog.widgets.search'));
|
||||
Components::set('BLOGFEATURES', '');
|
||||
Navigation::setCrumbComponent( 'BLOG_BREADCRUMBS', Input::get( 'url' ) );
|
||||
Components::set( 'BLOG_TEMPLATE_URL', Template::parse( '{ROOT_URL}app/plugins/comments/' ) );
|
||||
$this->addCss( '<link rel="stylesheet" href="{BLOG_TEMPLATE_URL}css/comments.css">' );
|
||||
parent::__construct();
|
||||
}
|
||||
}
|
139
app/plugins/blog/templates/blog.tpl
Executable file
139
app/plugins/blog/templates/blog.tpl
Executable file
@ -0,0 +1,139 @@
|
||||
<!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_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">
|
||||
<meta name="author" content="The Tempus Project">
|
||||
{ROBOT}
|
||||
<link rel="icon" href="{ROOT_URL}images/favicon.ico">
|
||||
<!-- Required CSS -->
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.7.1/css/all.min.css" crossorigin="anonymous" referrerpolicy="no-referrer">
|
||||
<link rel="stylesheet" href="{BOOTSTRAP_CDN}css/bootstrap.min.css" crossorigin="anonymous">
|
||||
<!-- RSS -->
|
||||
<link rel="alternate" href="{ROOT_URL}blog/rss" title="{TITLE} Feed" type="application/rss+xml">
|
||||
<!-- Custom styles for this template -->
|
||||
{TEMPLATE_CSS_INCLUDES}
|
||||
</head>
|
||||
<body>
|
||||
<!-- Navigation -->
|
||||
<header class="p-3 text-bg-dark">
|
||||
<div class="container">
|
||||
<div class="d-flex align-items-center position-relative">
|
||||
<!-- Navbar Toggler (Left) -->
|
||||
|
||||
<!-- Centered Logo (Now inside normal document flow) -->
|
||||
<a href="/" class="align-items-center text-white text-decoration-none d-flex d-md-none">
|
||||
<img src="{ROOT_URL}{LOGO}" width="40" height="32" alt="{SITENAME} Logo" class="bi">
|
||||
</a>
|
||||
|
||||
<!-- Logo (Normal Position for Large Screens) -->
|
||||
<a href="/" class="align-items-center text-white text-decoration-none d-none d-md-flex">
|
||||
<img src="{ROOT_URL}{LOGO}" width="40" height="32" alt="{SITENAME} Logo" class="bi">
|
||||
</a>
|
||||
|
||||
<div class="navbar-expand-md flex-grow-1">
|
||||
<div class="collapse navbar-collapse d-md-flex" id="mainMenu">
|
||||
<!-- Centered Navigation -->
|
||||
<div class="d-none d-md-block d-flex justify-content-center position-absolute start-50 translate-middle-x">
|
||||
{topNavLeft}
|
||||
</div>
|
||||
<div class="d-flex justify-content-center flex-grow-1 d-md-none">
|
||||
{topNavLeft}
|
||||
</div>
|
||||
|
||||
<!-- Right-Side Content (Push to End) -->
|
||||
<div class="d-flex flex-row justify-content-center align-items-center mt-3 mt-md-0 ms-md-auto">
|
||||
{topNavRight}
|
||||
</div>
|
||||
</div> <!-- End Collapse -->
|
||||
</div> <!-- End Navbar Expand -->
|
||||
|
||||
<button class="me-3 d-md-none btn btn-md btn-outline-light" type="button" data-bs-toggle="collapse" data-bs-target="#mainMenu" aria-controls="mainMenu" aria-expanded="false" aria-label="Toggle navigation">
|
||||
<i class="fa fa-bars"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div class="d-flex flex-column min-vh-100">
|
||||
<div class="flex-container flex-grow-1">
|
||||
{ISSUES}
|
||||
<div class="container pt-4">
|
||||
<div class="row">
|
||||
{ERROR}
|
||||
{NOTICE}
|
||||
{SUCCESS}
|
||||
{INFO}
|
||||
</div>
|
||||
</div>
|
||||
{/ISSUES}
|
||||
|
||||
<!-- Leading Content -->
|
||||
<div class="container">
|
||||
{BLOGFEATURES}
|
||||
</div>
|
||||
|
||||
<div class="pt-4">
|
||||
<div class="container">
|
||||
<h3 class="pb-4 mb-4 fst-italic border-bottom context-main-border">
|
||||
{SITENAME} Blog
|
||||
</h3>
|
||||
<div class="d-md-flex g-5">
|
||||
<!-- Main Content -->
|
||||
<div class="col-12 col-md-8">
|
||||
{CONTENT}
|
||||
</div>
|
||||
<!-- Sidebar Content -->
|
||||
<div class="col-12 col-md-4">
|
||||
<div class="position-sticky" style="top: 2rem;">
|
||||
<div class="ps-md-2 ps-lg-5">
|
||||
{SIDEBARABOUT}
|
||||
</div>
|
||||
<div class="ps-md-2 ps-lg-5">
|
||||
{SIDEBARSEARCH}
|
||||
</div>
|
||||
<div class="ps-md-2 ps-lg-5">
|
||||
{SIDEBAR}
|
||||
</div>
|
||||
<div class="ps-md-2 ps-lg-5">
|
||||
{SIDEBAR2}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{FOOT}
|
||||
</div>
|
||||
<!-- Bootstrap core JavaScript and jquery -->
|
||||
<script language="JavaScript" crossorigin="anonymous" type="text/javascript" src="{JQUERY_CDN}jquery.min.js"></script>
|
||||
<script language="JavaScript" crossorigin="anonymous" type="text/javascript" src="https://cdn.jsdelivr.net/npm/@popperjs/core@2.11.6/dist/umd/popper.min.js"></script>
|
||||
<script language="JavaScript" crossorigin="anonymous" type="text/javascript" src="{BOOTSTRAP_CDN}js/bootstrap.min.js"></script>
|
||||
<!-- Custom javascript for this template -->
|
||||
{TEMPLATE_JS_INCLUDES}
|
||||
</body>
|
||||
</html>
|
19
app/plugins/blog/templates/rss.inc.php
Executable file
19
app/plugins/blog/templates/rss.inc.php
Executable 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
Executable file
11
app/plugins/blog/templates/rss.tpl
Executable 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>
|
55
app/plugins/blog/views/admin/create.html
Executable file
55
app/plugins/blog/views/admin/create.html
Executable file
@ -0,0 +1,55 @@
|
||||
<div class="context-main-bg context-main p-3">
|
||||
<legend class="text-center">Add Blog Post</legend>
|
||||
<hr>
|
||||
{ADMIN_BREADCRUMBS}
|
||||
<form method="post">
|
||||
<fieldset>
|
||||
<!-- Title -->
|
||||
<div class="mb-3 row">
|
||||
<label for="title" class="col-lg-3 col-form-label text-end">Title:</label>
|
||||
<div class="col-lg-6">
|
||||
<input type="text" class="form-control" name="title" id="title" required>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Slug -->
|
||||
<div class="mb-3 row">
|
||||
<label for="slug" class="col-lg-3 col-form-label text-end">URL Slug (for pretty linking):</label>
|
||||
<div class="col-lg-6">
|
||||
<input type="text" class="form-control" name="slug" id="slug" required>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- form buttons -->
|
||||
<div class="mb-3 row">
|
||||
<div class="offset-3 col-lg-6">
|
||||
<div class="btn-group w-100">
|
||||
<button type="button" class="btn btn-sm btn-outline-primary" onclick="insertTag ('blogPost', 'c');">✔</button>
|
||||
<button type="button" class="btn btn-sm btn-outline-primary" onclick="insertTag ('blogPost', 'x');">✖</button>
|
||||
<button type="button" class="btn btn-sm btn-outline-primary" onclick="insertTag ('blogPost', '!');">❕</button>
|
||||
<button type="button" class="btn btn-sm btn-outline-primary" onclick="insertTag ('blogPost', '?');">❔</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Message -->
|
||||
<div class="mb-3 row">
|
||||
<label for="blogPost" class="col-lg-3 col-form-label text-end">Post:</label>
|
||||
<div class="col-lg-6">
|
||||
<textarea class="form-control" name="blogPost" id="blogPost" rows="6" maxlength="2000" required></textarea>
|
||||
<small class="form-text text-muted">Max: 2000 characters</small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Hidden Token -->
|
||||
<input type="hidden" name="token" value="{TOKEN}">
|
||||
</fieldset>
|
||||
|
||||
<!-- Submit Button -->
|
||||
<div class="text-center">
|
||||
<button name="submit" value="publish" type="submit" class="mx-2 btn btn-lg btn-primary">Publish</button>
|
||||
<button name="submit" value="saveDraft" type="submit" class="mx-2 btn btn-lg btn-primary">Save as Draft</button>
|
||||
<button name="submit" value="preview" type="submit" class="mx-2 btn btn-lg btn-primary">Preview</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
30
app/plugins/blog/views/admin/dashboard.html
Executable file
30
app/plugins/blog/views/admin/dashboard.html
Executable file
@ -0,0 +1,30 @@
|
||||
<legend>New Posts</legend>
|
||||
<table class="table context-main">
|
||||
<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"><i class="fa fa-fw fa-upload"></i></a></td>
|
||||
<td><a href="{ROOT_URL}admin/blog/edit/{ID}" class="btn btn-sm btn-warning"><i class="fa fa-fw fa-pencil"></i></a></td>
|
||||
<td width="30px"><a href="{ROOT_URL}admin/blog/delete/{ID}" class="btn btn-sm btn-danger"><i class="fa fa-fw fa-trash"></i></a></td>
|
||||
</tr>
|
||||
{/LOOP}
|
||||
{ALT}
|
||||
<tr>
|
||||
<td align="center" colspan="5">
|
||||
No results to show.
|
||||
</td>
|
||||
</tr>
|
||||
{/ALT}
|
||||
</tbody>
|
||||
</table>
|
55
app/plugins/blog/views/admin/edit.html
Executable file
55
app/plugins/blog/views/admin/edit.html
Executable file
@ -0,0 +1,55 @@
|
||||
<div class="context-main-bg context-main p-3">
|
||||
<legend class="text-center">Edit Blog Post</legend>
|
||||
<hr>
|
||||
{ADMIN_BREADCRUMBS}
|
||||
<form method="post">
|
||||
<fieldset>
|
||||
<!-- Title -->
|
||||
<div class="mb-3 row">
|
||||
<label for="title" class="col-lg-3 col-form-label text-end">Title:</label>
|
||||
<div class="col-lg-6">
|
||||
<input type="text" class="form-control" name="title" id="title" value="{title}" required>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Slug -->
|
||||
<div class="mb-3 row">
|
||||
<label for="slug" class="col-lg-3 col-form-label text-end">URL Slug (for pretty linking):</label>
|
||||
<div class="col-lg-6">
|
||||
<input type="text" class="form-control" name="slug" id="slug" value="{slug}" required>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- form buttons -->
|
||||
<div class="mb-3 row">
|
||||
<div class="offset-3 col-lg-6">
|
||||
<div class="btn-group w-100">
|
||||
<button type="button" class="btn btn-sm btn-outline-primary" onclick="insertTag ('blogPost', 'c');">✔</button>
|
||||
<button type="button" class="btn btn-sm btn-outline-primary" onclick="insertTag ('blogPost', 'x');">✖</button>
|
||||
<button type="button" class="btn btn-sm btn-outline-primary" onclick="insertTag ('blogPost', '!');">❕</button>
|
||||
<button type="button" class="btn btn-sm btn-outline-primary" onclick="insertTag ('blogPost', '?');">❔</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Message -->
|
||||
<div class="mb-3 row">
|
||||
<label for="blogPost" class="col-lg-3 col-form-label text-end">Post:</label>
|
||||
<div class="col-lg-6">
|
||||
<textarea class="form-control" name="blogPost" id="blogPost" rows="6" maxlength="2000" required>{content}</textarea>
|
||||
<small class="form-text text-muted">Max: 2000 characters</small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Hidden Token -->
|
||||
<input type="hidden" name="token" value="{TOKEN}">
|
||||
</fieldset>
|
||||
|
||||
<!-- Submit Button -->
|
||||
<div class="text-center">
|
||||
<button name="submit" value="publish" type="submit" class="mx-2 btn btn-lg btn-primary">Publish</button>
|
||||
<button name="submit" value="saveDraft" type="submit" class="mx-2 btn btn-lg btn-primary">Save as Draft</button>
|
||||
<button name="submit" value="preview" type="submit" class="mx-2 btn btn-lg btn-primary">Preview</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
48
app/plugins/blog/views/admin/list.html
Executable file
48
app/plugins/blog/views/admin/list.html
Executable file
@ -0,0 +1,48 @@
|
||||
<div class="context-main-bg context-main p-3">
|
||||
<legend class="text-center">Blog Posts</legend>
|
||||
<hr>
|
||||
{ADMIN_BREADCRUMBS}
|
||||
<form action="{ROOT_URL}admin/blog/delete" method="post">
|
||||
<table class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width: 30%">Title</th>
|
||||
<th style="width: 20%">Author</th>
|
||||
<th style="width: 10%">comments</th>
|
||||
<th style="width: 10%">Created</th>
|
||||
<th style="width: 10%">Updated</th>
|
||||
<th style="width: 5%"></th>
|
||||
<th style="width: 5%"></th>
|
||||
<th style="width: 10%">
|
||||
<input type="checkbox" onchange="checkAll(this)" name="check.b" value="B_[]">
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{LOOP}
|
||||
<tr>
|
||||
<td><a href="{ROOT_URL}admin/blog/view/{ID}" class="text-decoration-none">{title}</a>{isDraft}</td>
|
||||
<td>{authorName}</td>
|
||||
<td>{commentCount}</td>
|
||||
<td>{DTC}{created}{/DTC}</td>
|
||||
<td>{DTC}{edited}{/DTC}</td>
|
||||
<td><a href="{ROOT_URL}admin/blog/edit/{ID}" class="btn btn-sm btn-warning"><i class="fa fa-fw fa-pencil"></i></a></td>
|
||||
<td><a href="{ROOT_URL}admin/blog/delete/{ID}" class="btn btn-sm btn-danger"><i class="fa fa-fw fa-trash"></i></a></td>
|
||||
<td>
|
||||
<input type="checkbox" value="{ID}" name="B_[]">
|
||||
</td>
|
||||
</tr>
|
||||
{/LOOP}
|
||||
{ALT}
|
||||
<tr>
|
||||
<td colspan="7">
|
||||
No results to show.
|
||||
</td>
|
||||
</tr>
|
||||
{/ALT}
|
||||
</tbody>
|
||||
</table>
|
||||
<a href="{ROOT_URL}admin/blog/create" class="btn btn-sm btn-primary">Create</a>
|
||||
<button name="submit" value="submit" type="submit" class="btn btn-sm btn-danger"><i class="fa fa-fw fa-trash"></i></button>
|
||||
</form>
|
||||
</div>
|
40
app/plugins/blog/views/admin/preview.html
Executable file
40
app/plugins/blog/views/admin/preview.html
Executable 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 method="post" enctype="multipart/form-data">
|
||||
<legend>New Blog Post</legend>
|
||||
<div class="form-group">
|
||||
<label for="title" class="col-lg-3 control-label">Title</label>
|
||||
<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>
|
14
app/plugins/blog/views/admin/view.html
Executable file
14
app/plugins/blog/views/admin/view.html
Executable file
@ -0,0 +1,14 @@
|
||||
<div class="context-main-bg context-main p-3">
|
||||
<legend class="text-center">Blog Post: {title}</legend>
|
||||
<hr>
|
||||
{ADMIN_BREADCRUMBS}
|
||||
<p class="blog-post-meta">{DTC date}{created}{/DTC} by <a href="{ROOT_URL}admin/users/view/{author}">{authorName}</a></p>
|
||||
<div class="well">{content}</div>
|
||||
{ADMIN}
|
||||
<hr>
|
||||
<a href="{ROOT_URL}admin/blog/delete/{ID}" class="btn btn-md btn-danger"><i class="fa fa-fw fa-trash"></i></a>
|
||||
<a href="{ROOT_URL}admin/blog/edit/{ID}" class="btn btn-md btn-warning"><i class="fa fa-fw fa-pencil"></i></a>
|
||||
<a href="{ROOT_URL}admin/comments/blog/{ID}" class="btn btn-md btn-primary">View Comments</a>
|
||||
<hr>
|
||||
{/ADMIN}
|
||||
</div>
|
7
app/plugins/blog/views/largeFeature.html
Executable file
7
app/plugins/blog/views/largeFeature.html
Executable file
@ -0,0 +1,7 @@
|
||||
<div class="p-4 p-md-5 mb-4 rounded text-bg-dark">
|
||||
<div class="col-md-6 px-0">
|
||||
<h1 class="display-4 fst-italic">Title of a longer featured blog post</h1>
|
||||
<p class="lead my-3">Multiple lines of text that form the lede, informing new readers quickly and efficiently about what’s most interesting in this post’s contents.</p>
|
||||
<p class="lead mb-0"><a href="#" class="text-white fw-bold">Continue reading...</a></p>
|
||||
</div>
|
||||
</div>
|
15
app/plugins/blog/views/list.html
Executable file
15
app/plugins/blog/views/list.html
Executable file
@ -0,0 +1,15 @@
|
||||
{LOOP}
|
||||
<article class="blog-post context-main-bg p-2">
|
||||
<h2 class="blog-post-title mb-1">{title}</h2>
|
||||
<p class="blog-post-meta">{DTC date}{created}{/DTC} by <a href="{ROOT_URL}home/profile/{authorName}" class="text-decoration-none">{authorName}</a></p>
|
||||
<div class="well">
|
||||
{contentSummary}
|
||||
</div>
|
||||
</article>
|
||||
<hr>
|
||||
{/LOOP}
|
||||
{ALT}
|
||||
<div class="text-center">
|
||||
<p class="h5">No Posts Found</p>
|
||||
</div>
|
||||
{/ALT}
|
18
app/plugins/blog/views/post.html
Executable file
18
app/plugins/blog/views/post.html
Executable file
@ -0,0 +1,18 @@
|
||||
<div class="row">
|
||||
<div class="col-lg-12 col-sm-12 blog-main context-main-bg p-1 p-md-2 mb-2 rounded">
|
||||
<div class="blog-post mb-5 pb-5">
|
||||
<h2 class="blog-post-title">{title}</h2>
|
||||
<hr>
|
||||
<p class="blog-post-meta">{DTC date}{created}{/DTC} by <a href="{ROOT_URL}home/profile/{authorName}" class="text-decoration-none">{authorName}</a></p>
|
||||
{content}
|
||||
{ADMIN}
|
||||
<hr>
|
||||
<a href="{ROOT_URL}admin/blog/delete/{ID}" class="btn btn-md btn-danger"><i class="fa fa-fw fa-trash"></i></a>
|
||||
<a href="{ROOT_URL}admin/blog/edit/{ID}" class="btn btn-md btn-warning"><i class="fa fa-fw fa-pencil"></i></a>
|
||||
<hr>
|
||||
{/ADMIN}
|
||||
</div><!-- /.blog-post -->
|
||||
{COMMENTS}
|
||||
{NEWCOMMENT}
|
||||
</div><!-- /.blog-main -->
|
||||
</div><!-- /.row -->
|
10
app/plugins/blog/views/rss.html
Executable file
10
app/plugins/blog/views/rss.html
Executable 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}
|
3
app/plugins/blog/views/searchResults.html
Normal file
3
app/plugins/blog/views/searchResults.html
Normal file
@ -0,0 +1,3 @@
|
||||
<legend class="text-center my-2">Search Results</legend>
|
||||
<hr>
|
||||
{searchResults}
|
31
app/plugins/blog/views/smallFeature.html
Executable file
31
app/plugins/blog/views/smallFeature.html
Executable file
@ -0,0 +1,31 @@
|
||||
|
||||
<div class="row mb-2">
|
||||
<div class="col-md-6">
|
||||
<div class="row g-0 border rounded overflow-hidden flex-md-row mb-4 shadow-sm h-md-250 position-relative">
|
||||
<div class="col p-4 d-flex flex-column position-static">
|
||||
<strong class="d-inline-block mb-2 text-primary">World</strong>
|
||||
<h3 class="mb-0">Featured post</h3>
|
||||
<div class="mb-1 text-muted">Nov 12</div>
|
||||
<p class="card-text mb-auto">This is a wider card with supporting text below as a natural lead-in to additional content.</p>
|
||||
<a href="#" class="stretched-link">Continue reading</a>
|
||||
</div>
|
||||
<div class="col-auto d-none d-lg-block">
|
||||
<svg class="bd-placeholder-img" width="200" height="250" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="Placeholder: Thumbnail" preserveAspectRatio="xMidYMid slice" focusable="false"><title>Placeholder</title><rect width="100%" height="100%" fill="#55595c"><text x="50%" y="50%" fill="#eceeef" dy=".3em">Thumbnail</text></svg>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="row g-0 border rounded overflow-hidden flex-md-row mb-4 shadow-sm h-md-250 position-relative">
|
||||
<div class="col p-4 d-flex flex-column position-static">
|
||||
<strong class="d-inline-block mb-2 text-success">Design</strong>
|
||||
<h3 class="mb-0">Post title</h3>
|
||||
<div class="mb-1 text-muted">Nov 11</div>
|
||||
<p class="mb-auto">This is a wider card with supporting text below as a natural lead-in to additional content.</p>
|
||||
<a href="#" class="stretched-link">Continue reading</a>
|
||||
</div>
|
||||
<div class="col-auto d-none d-lg-block">
|
||||
<svg class="bd-placeholder-img" width="200" height="250" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="Placeholder: Thumbnail" preserveAspectRatio="xMidYMid slice" focusable="false"><title>Placeholder</title><rect width="100%" height="100%" fill="#55595c"><text x="50%" y="50%" fill="#eceeef" dy=".3em">Thumbnail</text></svg>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
11
app/plugins/blog/views/widgets/about.html
Normal file
11
app/plugins/blog/views/widgets/about.html
Normal file
@ -0,0 +1,11 @@
|
||||
<div class="pb-2 pb-lg-3">
|
||||
<div class="card rounded context-main-bg">
|
||||
<div class="card-body">
|
||||
<h4 class="fst-italic">About</h4>
|
||||
<p class="mb-0">
|
||||
The blog is mostly here to serve ass a simple way to link to long-form content on the site.
|
||||
There won't be any breaking news or tell-all stories here. Just good ole fashioned boring crap no one wants to read.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
17
app/plugins/blog/views/widgets/archive.html
Normal file
17
app/plugins/blog/views/widgets/archive.html
Normal file
@ -0,0 +1,17 @@
|
||||
<div class="pb-2 pb-lg-3">
|
||||
<div class="card rounded context-main-bg">
|
||||
<div class="card-header">
|
||||
<h3 class="card-title">Archives</h3>
|
||||
</div>
|
||||
<div class="card-body context-second-bg">
|
||||
<ol class="list-unstyled">
|
||||
{LOOP}
|
||||
<li>({count}) <a href="{ROOT_URL}blog/month/{month}/{year}" class="text-decoration-none">{monthText} {year}</a></li>
|
||||
{/LOOP}
|
||||
{ALT}
|
||||
<li>None To Show</li>
|
||||
{/ALT}
|
||||
</ol>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
20
app/plugins/blog/views/widgets/recent.html
Normal file
20
app/plugins/blog/views/widgets/recent.html
Normal file
@ -0,0 +1,20 @@
|
||||
<div class="pb-2 pb-lg-3">
|
||||
<div class="card rounded context-main-bg">
|
||||
<div class="card-header">
|
||||
<h3 class="card-title">Recent Posts</h3>
|
||||
</div>
|
||||
<div class="card-body context-second-bg">
|
||||
<ol class="list-unstyled">
|
||||
{LOOP}
|
||||
<li><a href="{ROOT_URL}blog/post/{ID}" class="text-decoration-none">{title}</a></li>
|
||||
{/LOOP}
|
||||
{ALT}
|
||||
<li>No Posts to show</li>
|
||||
{/ALT}
|
||||
</ol>
|
||||
</div>
|
||||
<div class="card-footer">
|
||||
<a href="{ROOT_URL}blog" class="text-decoration-none">View All</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
18
app/plugins/blog/views/widgets/search.html
Normal file
18
app/plugins/blog/views/widgets/search.html
Normal file
@ -0,0 +1,18 @@
|
||||
<div class="pb-2 pb-lg-3">
|
||||
<div class="card rounded context-main-bg">
|
||||
<div class="card-body">
|
||||
<form method="post" action="/blog/search">
|
||||
<fieldset>
|
||||
<!-- Hidden Token -->
|
||||
<input type="hidden" name="token" value="{TOKEN}">
|
||||
|
||||
<!-- Search -->
|
||||
<div class="input-group">
|
||||
<input type="text" class="form-control" aria-label="Search Terms" name="searchTerm" id="searchTerm">
|
||||
<button type="submit" class="btn btn-secondary bg-primary" name="submit" value="submit">Search</button>
|
||||
</div>
|
||||
</fieldset>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
63
app/plugins/bugreport/controllers/admin/bugreport.php
Executable file
63
app/plugins/bugreport/controllers/admin/bugreport.php
Executable 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
Executable file
52
app/plugins/bugreport/controllers/bugreport.php
Executable 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 ( false != $result ) {
|
||||
Session::flash( 'success', 'Your Bug Report has been received. We may contact you for more information at the email address you provided.' );
|
||||
Redirect::to( 'home/index' );
|
||||
} else {
|
||||
Issues::add( 'error', 'There was an unresolved error while submitting your report.' );
|
||||
return Views::view( 'bugreport.create' );
|
||||
}
|
||||
}
|
||||
}
|
51
app/plugins/bugreport/forms.php
Executable file
51
app/plugins/bugreport/forms.php
Executable 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;
|
100
app/plugins/bugreport/models/bugreport.php
Executable file
100
app/plugins/bugreport/models/bugreport.php
Executable file
@ -0,0 +1,100 @@
|
||||
<?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\Canary\Bin\Canary as Debug;
|
||||
use TheTempusProject\Canary\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();
|
||||
}
|
||||
}
|
65
app/plugins/bugreport/plugin.php
Executable file
65
app/plugins/bugreport/plugin.php
Executable file
@ -0,0 +1,65 @@
|
||||
<?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 = [
|
||||
'canSendBugReports' => [
|
||||
'pretty' => 'Can Submit Bug Reports',
|
||||
'default' => false,
|
||||
],
|
||||
];
|
||||
public $contact_footer_links = [
|
||||
[
|
||||
'text' => 'Report a Bug',
|
||||
'url' => '{ROOT_URL}bugreport',
|
||||
'filter' => 'loggedin',
|
||||
],
|
||||
];
|
||||
public $admin_links = [
|
||||
[
|
||||
'text' => '<i class="fa fa-fw fa-bug"></i> Bug Reports',
|
||||
'url' => '{ROOT_URL}admin/bugreport',
|
||||
],
|
||||
];
|
||||
}
|
45
app/plugins/bugreport/views/admin/list.html
Executable file
45
app/plugins/bugreport/views/admin/list.html
Executable file
@ -0,0 +1,45 @@
|
||||
<div class="context-main-bg context-main p-3">
|
||||
<legend class="text-center">Bug Reports</legend>
|
||||
<hr>
|
||||
{ADMIN_BREADCRUMBS}
|
||||
<form action="{ROOT_URL}admin/bugreport/delete" method="post">
|
||||
<table class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width: 5%">ID</th>
|
||||
<th style="width: 20%">Time</th>
|
||||
<th style="width: 60%">Description</th>
|
||||
<th style="width: 5%"></th>
|
||||
<th style="width: 5%"></th>
|
||||
<th style="width: 5%">
|
||||
<input type="checkbox" onchange="checkAll(this)" name="check.br" value="BR_[]">
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{LOOP}
|
||||
<tr>
|
||||
<td align="center">{ID}</td>
|
||||
<td align="center">{DTC}{time}{/DTC}</td>
|
||||
<td>{description}</td>
|
||||
<td><a href="{ROOT_URL}admin/bugreport/view/{ID}" class="btn btn-sm btn-primary"><i class="fa fa-fw fa-upload"></i></a></td>
|
||||
<td><a href="{ROOT_URL}admin/bugreport/delete/{ID}" class="btn btn-sm btn-danger"><i class="fa fa-fw fa-trash"></i></a></td>
|
||||
<td>
|
||||
<input type="checkbox" value="{ID}" name="BR_[]">
|
||||
</td>
|
||||
</tr>
|
||||
{/LOOP}
|
||||
{ALT}
|
||||
<tr>
|
||||
<td align="center" colspan="6">
|
||||
No results to show.
|
||||
</td>
|
||||
</tr>
|
||||
{/ALT}
|
||||
</tbody>
|
||||
</table>
|
||||
<button name="submit" value="submit" type="submit" class="btn btn-sm btn-danger"><i class="fa fa-fw fa-trash"></i></button>
|
||||
</form>
|
||||
<br>
|
||||
<a href="{ROOT_URL}admin/bugreport/clear">clear all</a>
|
||||
</div>
|
69
app/plugins/bugreport/views/admin/view.html
Executable file
69
app/plugins/bugreport/views/admin/view.html
Executable file
@ -0,0 +1,69 @@
|
||||
<div class="container py-4">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-8">
|
||||
{ADMIN_BREADCRUMBS}
|
||||
<div class="card shadow">
|
||||
<!-- Card Header -->
|
||||
<div class="card-header text-center bg-dark text-white">
|
||||
<h3 class="card-title mb-0">Bug Report</h3>
|
||||
</div>
|
||||
|
||||
<!-- Card Body -->
|
||||
<div class="card-body">
|
||||
<div class="row align-items-center">
|
||||
<!-- Log Details -->
|
||||
<table class="table table-borderless">
|
||||
<tbody>
|
||||
<tr>
|
||||
<th scope="row">ID:</th>
|
||||
<td>{ID}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Time submitted</th>
|
||||
<td>{DTC}{time}{/DTC}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">URL:</th>
|
||||
<td><a href="{URL}">{URL}</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Original URL</th>
|
||||
<td><a href="{OURL}">{OURL}</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Multiple occurrences?</th>
|
||||
<td>{repeatText}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">IP:</th>
|
||||
<td>{ip}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">User:</th>
|
||||
<td><a href="{ROOT_URL}admin/users/view/{userID}">{submittedBy}</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row" colspan="2">Description:</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="2">{description}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Admin Controls -->
|
||||
<div class="card-footer text-center">
|
||||
{ADMIN}
|
||||
<form action="{ROOT_URL}admin/bugreport/delete" method="post">
|
||||
<input type="hidden" name="BR_" value="{ID}">
|
||||
<input type="hidden" name="token" value="{TOKEN}">
|
||||
<button name="submit" value="submit" type="submit" class="btn btn-sm btn-danger"><i class="fa fa-fw fa-trash"></i></button>
|
||||
</form>
|
||||
{/ADMIN}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
61
app/plugins/bugreport/views/create.html
Executable file
61
app/plugins/bugreport/views/create.html
Executable file
@ -0,0 +1,61 @@
|
||||
<div class="m-2 m-lg-4">
|
||||
<div class="col-12 mx-5 col-sm-10 col-lg-8 mx-auto p-4 rounded shadow-sm context-main-bg">
|
||||
<h2 class="text-center mb-4">Report a Bug</h2>
|
||||
<hr>
|
||||
<p class="text-center text-sm-start">
|
||||
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 class="text-center text-sm-start">
|
||||
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 method="post">
|
||||
<!-- Page URL -->
|
||||
<div class="mb-3">
|
||||
<label for="url" class="form-label">Page you were trying to reach:</label>
|
||||
<input type="url" name="url" id="url" class="form-control" aria-describedby="urlHelp" required>
|
||||
<small id="urlHelp" class="form-text text-muted">
|
||||
This is the URL of the page you actually received the error.
|
||||
</small>
|
||||
</div>
|
||||
|
||||
<!-- Referrer URL -->
|
||||
<div class="mb-3">
|
||||
<label for="ourl" class="form-label">Page you were on:</label>
|
||||
<input type="url" name="ourl" id="ourl" class="form-control" aria-describedby="ourlHelp">
|
||||
<small id="ourlHelp" class="form-text text-muted">
|
||||
This is the URL of the page you were on before you received the error.
|
||||
</small>
|
||||
</div>
|
||||
|
||||
<!-- Repeat Issue -->
|
||||
<div class="mb-3">
|
||||
<label class="form-label">*Has this happened more than once?</label>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="radio" name="repeat" id="repeatNo" value="false" checked>
|
||||
<label class="form-check-label" for="repeatNo">No</label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="radio" name="repeat" id="repeatYes" value="true">
|
||||
<label class="form-check-label" for="repeatYes">Yes</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Description -->
|
||||
<div class="mb-3">
|
||||
<label for="entry" class="form-label">Describe the error you received: </label>
|
||||
<textarea class="form-control" name="entry" id="entry" rows="6" maxlength="2000" aria-describedby="descHelp" required></textarea>
|
||||
<small id="descHelp" class="form-text text-muted">
|
||||
(max: 2000 characters)
|
||||
</small>
|
||||
</div>
|
||||
|
||||
<!-- Hidden Token -->
|
||||
<input type="hidden" name="token" value="{TOKEN}">
|
||||
|
||||
<!-- Submit Button -->
|
||||
<div class="text-center">
|
||||
<button type="submit" name="submit" value="submit" class="btn btn-primary btn-lg">Submit</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
83
app/plugins/comments/controllers/admin/comments.php
Executable file
83
app/plugins/comments/controllers/admin/comments.php
Executable file
@ -0,0 +1,83 @@
|
||||
<?php
|
||||
/**
|
||||
* app/plugins/comments/controllers/admin/comments.php
|
||||
*
|
||||
* This is the comments admin controller.
|
||||
*
|
||||
* @package TP Comments
|
||||
* @version 3.0
|
||||
* @author Joey Kimsey <Joey@thetempusproject.com>
|
||||
* @link https://TheTempusProject.com
|
||||
* @license https://opensource.org/licenses/MIT [MIT LICENSE]
|
||||
*/
|
||||
namespace TheTempusProject\Controllers\Admin;
|
||||
|
||||
use TheTempusProject\Houdini\Classes\Navigation;
|
||||
use TheTempusProject\Houdini\Classes\Issues;
|
||||
use TheTempusProject\Houdini\Classes\Views;
|
||||
use TheTempusProject\Houdini\Classes\Components;
|
||||
use TheTempusProject\Bedrock\Functions\Input;
|
||||
use TheTempusProject\Bedrock\Functions\Check;
|
||||
use TheTempusProject\Classes\Forms;
|
||||
use TheTempusProject\Classes\AdminController;
|
||||
use TheTempusProject\Models\Comments as CommentsModel;
|
||||
|
||||
class Comments extends AdminController {
|
||||
protected static $comments;
|
||||
|
||||
public function __construct() {
|
||||
parent::__construct();
|
||||
self::$title = 'Admin - Comments';
|
||||
self::$comments = new CommentsModel;
|
||||
$view = Navigation::activePageSelect( 'nav.admin', '/admin/comments' );
|
||||
Components::set( 'ADMINNAV', $view );
|
||||
}
|
||||
|
||||
public function edit( $data = null ) {
|
||||
if ( !Input::exists( 'submit' ) ) {
|
||||
return Views::view( 'comments.admin.edit', self::$comments->findById( $data ) );
|
||||
}
|
||||
if ( !Forms::check( 'editComment' ) ) {
|
||||
Issues::add( 'error', [ 'There was an error with your request.' => Check::userErrors() ] );
|
||||
return Views::view( 'comments.admin.edit', self::$comments->findById( $data ) );
|
||||
}
|
||||
if ( self::$comments->update( $data, Input::post( 'comment' ) ) ) {
|
||||
Issues::add( 'success', 'Comment updated' );
|
||||
} else {
|
||||
return Views::view( 'comments.admin.edit', self::$comments->findById( $data ) );
|
||||
}
|
||||
$this->index();
|
||||
}
|
||||
|
||||
public function viewComments( $contentID = null ) {
|
||||
if ( empty( $contentID ) ) {
|
||||
Issues::add( 'error', 'Content ID not found.' );
|
||||
return $this->index();
|
||||
}
|
||||
$contentData = self::$comments->findById( $data );
|
||||
if ( empty( $contentID ) ) {
|
||||
return Views::view( 'comments.list', $commentData );
|
||||
}
|
||||
Issues::add( 'error', 'Comment not found.' );
|
||||
$this->index();
|
||||
}
|
||||
|
||||
public function delete( $data = null ) {
|
||||
if ( $data == null ) {
|
||||
if ( !Input::exists( 'C_' ) ) {
|
||||
return $this->index();
|
||||
}
|
||||
$data = Input::post( 'C_' );
|
||||
}
|
||||
if ( !self::$comments->delete( $data ) ) {
|
||||
Issues::add( 'error', 'There was an error with your request.' );
|
||||
} else {
|
||||
Issues::add( 'success', 'Comment has been deleted' );
|
||||
}
|
||||
$this->index();
|
||||
}
|
||||
|
||||
public function index() {
|
||||
Views::view( 'comments.admin.list', self::$comments->recent() );
|
||||
}
|
||||
}
|
36
app/plugins/comments/controllers/moderator.php
Executable file
36
app/plugins/comments/controllers/moderator.php
Executable file
@ -0,0 +1,36 @@
|
||||
<?php
|
||||
/**
|
||||
* app/plugins/comments/controllers/moderator.php
|
||||
*
|
||||
* This is the Moderator controller.
|
||||
*
|
||||
* @version 3.0
|
||||
* @author Joey Kimsey <Joey@thetempusproject.com>
|
||||
* @link https://TheTempusProject.com
|
||||
* @license https://opensource.org/licenses/MIT [MIT LICENSE]
|
||||
*/
|
||||
namespace TheTempusProject\Controllers;
|
||||
|
||||
use TheTempusProject\Houdini\Classes\Template;
|
||||
use TheTempusProject\Houdini\Classes\Views;
|
||||
use TheTempusProject\Houdini\Classes\Issues;
|
||||
use TheTempusProject\Classes\Controller;
|
||||
use TheTempusProject\TheTempusProject as App;
|
||||
use TheTempusProject\Hermes\Functions\Redirect;
|
||||
use TheTempusProject\Bedrock\Functions\Session;
|
||||
|
||||
class Moderator extends Controller {
|
||||
public function __construct() {
|
||||
parent::__construct();
|
||||
Template::noIndex();
|
||||
if ( !App::$isMod ) {
|
||||
Session::flash( 'error', 'You do not have permission to view this page.' );
|
||||
return Redirect::home();
|
||||
}
|
||||
}
|
||||
|
||||
public function index() {
|
||||
self::$title = 'Moderator\'s Area';
|
||||
Views::view( 'comments.moderator' );
|
||||
}
|
||||
}
|
6
app/plugins/comments/css/comments.css
Executable file
6
app/plugins/comments/css/comments.css
Executable file
@ -0,0 +1,6 @@
|
||||
/**
|
||||
* Comments
|
||||
*/
|
||||
.comments {
|
||||
margin-top: 120px;
|
||||
}
|
69
app/plugins/comments/forms.php
Executable file
69
app/plugins/comments/forms.php
Executable file
@ -0,0 +1,69 @@
|
||||
<?php
|
||||
/**
|
||||
* app/plugins/comments/forms.php
|
||||
*
|
||||
* This houses all of the form checking functions for this plugin.
|
||||
*
|
||||
* @package TP Comments
|
||||
* @version 3.0
|
||||
* @author Joey Kimsey <Joey@thetempusproject.com>
|
||||
* @link https://TheTempusProject.com
|
||||
* @license https://opensource.org/licenses/MIT [MIT LICENSE]
|
||||
*/
|
||||
namespace TheTempusProject\Plugins\Comments;
|
||||
|
||||
use TheTempusProject\Bedrock\Functions\Input;
|
||||
use TheTempusProject\Bedrock\Functions\Check;
|
||||
use TheTempusProject\Classes\Forms;
|
||||
|
||||
class CommentsForms extends Forms {
|
||||
/**
|
||||
* Adds these functions to the form list.
|
||||
*/
|
||||
public function __construct() {
|
||||
self::addHandler( 'newComment', __CLASS__, 'newComment' );
|
||||
self::addHandler( 'editComment', __CLASS__, 'editComment' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the new comment form.
|
||||
*
|
||||
* @return {bool}
|
||||
*/
|
||||
public static function newComment() {
|
||||
if ( !Input::exists( 'comment' ) ) {
|
||||
Check::addUserError( 'You cannot post a blank comment.' );
|
||||
return false;
|
||||
}
|
||||
if ( !Input::exists( 'contentId' ) ) {
|
||||
Check::addUserError( 'Content ID was missing.' );
|
||||
return false;
|
||||
}
|
||||
// these are disabled because i need to figure out a solution for pages where images are wrong
|
||||
// a missing image loads a new token and messes this up
|
||||
// if ( !Check::token() ) {
|
||||
// return false;
|
||||
// }
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the edit comment form.
|
||||
*
|
||||
* @return {bool}
|
||||
*/
|
||||
public static function editComment() {
|
||||
if ( !Input::exists( 'comment' ) ) {
|
||||
Check::addUserError( 'You cannot post a blank comment.' );
|
||||
return false;
|
||||
}
|
||||
// these are disabled because i need to figure out a solution for pages where images are wrong
|
||||
// a missing image loads a new token and messes this up
|
||||
// if ( !Check::token() ) {
|
||||
// return false;
|
||||
// }
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
new CommentsForms;
|
184
app/plugins/comments/models/comments.php
Executable file
184
app/plugins/comments/models/comments.php
Executable file
@ -0,0 +1,184 @@
|
||||
<?php
|
||||
/**
|
||||
* app/plugins/comments/models/comment.php
|
||||
*
|
||||
* This class is used for the creation, retrieval, and manipulation
|
||||
* of the comments table.
|
||||
*
|
||||
* @package TP Comments
|
||||
* @version 3.0
|
||||
* @author Joey Kimsey <Joey@thetempusproject.com>
|
||||
* @link https://TheTempusProject.com
|
||||
* @license https://opensource.org/licenses/MIT [MIT LICENSE]
|
||||
*/
|
||||
namespace TheTempusProject\Models;
|
||||
|
||||
use TheTempusProject\Bedrock\Functions\Check;
|
||||
use TheTempusProject\Canary\Bin\Canary as Debug;
|
||||
use TheTempusProject\Houdini\Classes\Views;
|
||||
use TheTempusProject\Classes\DatabaseModel;
|
||||
use TheTempusProject\TheTempusProject as App;
|
||||
use TheTempusProject\Canary\Classes\CustomException;
|
||||
use TheTempusProject\Houdini\Classes\Filters;
|
||||
|
||||
class Comments extends DatabaseModel {
|
||||
public $tableName = 'comments';
|
||||
public $databaseMatrix = [
|
||||
[ 'author', 'int', '11' ],
|
||||
[ 'contentID', 'int', '11' ],
|
||||
[ 'created', 'int', '10' ],
|
||||
[ 'edited', 'int', '10' ],
|
||||
[ 'approved', 'int', '1' ],
|
||||
[ 'contentType', 'varchar', '32' ],
|
||||
[ 'content', 'text', '' ],
|
||||
];
|
||||
|
||||
public function count( $contentType, $contentID ) {
|
||||
if ( !Check::id( $contentID ) ) {
|
||||
Debug::info( 'Comments: illegal ID.' );
|
||||
|
||||
return false;
|
||||
}
|
||||
if ( !Check::dataTitle( $contentType ) ) {
|
||||
Debug::info( 'Comments: illegal Type.' );
|
||||
|
||||
return false;
|
||||
}
|
||||
$where = ['contentType', '=', $contentType, 'AND', 'contentID', '=', $contentID];
|
||||
$data = self::$db->get( $this->tableName, $where );
|
||||
if ( !$data->count() ) {
|
||||
Debug::info( 'No comments found.' );
|
||||
|
||||
return 0;
|
||||
}
|
||||
return $data->count();
|
||||
}
|
||||
|
||||
public function display( $displayCount, $contentType, $contentID ) {
|
||||
if ( !Check::id( $contentID ) ) {
|
||||
Debug::info( 'Comments: illegal ID.' );
|
||||
|
||||
return false;
|
||||
}
|
||||
if ( !Check::dataTitle( $contentType ) ) {
|
||||
Debug::info( 'Comments: illegal Type.' );
|
||||
|
||||
return false;
|
||||
}
|
||||
$where = ['contentType', '=', $contentType, 'AND', 'contentID', '=', $contentID];
|
||||
$commentData = self::$db->get( $this->tableName, $where, 'created', 'DESC', [0, $displayCount] );
|
||||
if ( !$commentData->count() ) {
|
||||
Debug::info( 'No comments found.' );
|
||||
|
||||
return false;
|
||||
}
|
||||
return $this->filter( $commentData->results() );
|
||||
}
|
||||
|
||||
public function update( $id, $comment ) {
|
||||
if ( empty( self::$log ) ) {
|
||||
self::$log = new Log;
|
||||
}
|
||||
if ( !Check::id( $id ) ) {
|
||||
Debug::info( 'Comments: illegal ID.' );
|
||||
|
||||
return false;
|
||||
}
|
||||
$fields = [
|
||||
'edited' => time(),
|
||||
'content' => $comment,
|
||||
'approved' => 1,
|
||||
];
|
||||
if ( !self::$db->update( $this->tableName, $id, $fields ) ) {
|
||||
new CustomException( 'commentUpdate' );
|
||||
Debug::error( "Post: $id not updated: $fields" );
|
||||
|
||||
return false;
|
||||
}
|
||||
self::$log->admin( "Updated Comment: $id" );
|
||||
return true;
|
||||
}
|
||||
|
||||
public function create( $contentType, $contentID, $comment ) {
|
||||
if ( !Check::id( $contentID ) ) {
|
||||
Debug::info( 'Comments: illegal ID.' );
|
||||
|
||||
return false;
|
||||
}
|
||||
if ( !Check::dataTitle( $contentType ) ) {
|
||||
Debug::info( 'Comments: illegal Type.' );
|
||||
|
||||
return false;
|
||||
}
|
||||
$fields = [
|
||||
'author' => App::$activeUser->ID,
|
||||
'edited' => time(),
|
||||
'created' => time(),
|
||||
'content' => $comment,
|
||||
'contentType' => $contentType,
|
||||
'contentID' => $contentID,
|
||||
'approved' => 0,
|
||||
];
|
||||
if ( !self::$db->insert( $this->tableName, $fields ) ) {
|
||||
new CustomException( 'commentCreate' );
|
||||
Debug::error( "Comments: $data not created: $fields" );
|
||||
|
||||
return false;
|
||||
}
|
||||
return self::$db->lastId();
|
||||
}
|
||||
|
||||
public function filter( $data, $params = [] ) {
|
||||
foreach ( $data as $instance ) {
|
||||
if ( !is_object( $instance ) ) {
|
||||
$instance = $data;
|
||||
$end = true;
|
||||
}
|
||||
if ( App::$isAdmin || ( App::$isLoggedIn && $instance->author == App::$activeUser->ID ) ) {
|
||||
$instance->commentControl = Views::simpleView( 'comments.control', ['ID' => $instance->ID] );
|
||||
} else {
|
||||
$instance->commentControl = '';
|
||||
}
|
||||
$data = self::$db->get( $instance->contentType, ['ID', '=', $instance->contentID] )->results();
|
||||
if ( empty( $data ) ) {
|
||||
$title = 'Unknown';
|
||||
} elseif ( empty( $data[0]->title ) ) {
|
||||
$title = 'Unknown';
|
||||
} else {
|
||||
$title = $data[0]->title;
|
||||
}
|
||||
$authorName = self::$user->getUsername( $instance->author );
|
||||
$authorAvatar = self::$user->getAvatar( $instance->author );
|
||||
$instance->avatar = $authorAvatar;
|
||||
$instance->authorName = $authorName;
|
||||
$instance->contentTitle = $title;
|
||||
$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 recent( $contentType = 'all', $limit = null ) {
|
||||
if ( $contentType === 'all' ) {
|
||||
$where = ['ID', '>', '0'];
|
||||
} else {
|
||||
$where = ['contentType', '=', $contentType];
|
||||
}
|
||||
if ( empty( $limit ) ) {
|
||||
$commentData = self::$db->get( $this->tableName, $where, 'created', 'DESC' );
|
||||
} else {
|
||||
$commentData = self::$db->get( $this->tableName, $where, 'created', 'DESC', [0, $limit] );
|
||||
}
|
||||
if ( !$commentData->count() ) {
|
||||
Debug::info( 'No comments found.' );
|
||||
|
||||
return false;
|
||||
}
|
||||
return $this->filter( $commentData->results() );
|
||||
}
|
||||
}
|
154
app/plugins/comments/plugin.php
Executable file
154
app/plugins/comments/plugin.php
Executable file
@ -0,0 +1,154 @@
|
||||
<?php
|
||||
/**
|
||||
* app/plugins/comments/plugin.php
|
||||
*
|
||||
* This houses all of the main plugin info and functionality.
|
||||
*
|
||||
* @package TP Comments
|
||||
* @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\Bedrock\Functions\Check;
|
||||
use TheTempusProject\Classes\Installer;
|
||||
use TheTempusProject\Houdini\Classes\Navigation;
|
||||
use TheTempusProject\Classes\Plugin;
|
||||
use TheTempusProject\TheTempusProject as App;
|
||||
use TheTempusProject\Bedrock\Functions\Input;
|
||||
use TheTempusProject\Houdini\Classes\Components;
|
||||
use TheTempusProject\Houdini\Classes\Issues;
|
||||
use TheTempusProject\Houdini\Classes\Views;
|
||||
use TheTempusProject\Classes\Forms;
|
||||
use TheTempusProject\Hermes\Functions\Redirect;
|
||||
use TheTempusProject\Bedrock\Functions\Session;
|
||||
use TheTempusProject\Models\Comments as CommentsModel;
|
||||
|
||||
class Comments extends Plugin {
|
||||
protected static $comments;
|
||||
public $pluginName = 'TP Comments';
|
||||
public $pluginAuthor = 'JoeyK';
|
||||
public $pluginWebsite = 'https://TheTempusProject.com';
|
||||
public $modelVersion = '1.0';
|
||||
public $pluginVersion = '3.0';
|
||||
public $pluginDescription = 'A simple plugin to add user comments for other plugins.';
|
||||
public $admin_links = [
|
||||
[
|
||||
'text' => '<i class="fa fa-fw fa-comment"></i> Comments',
|
||||
'url' => '{ROOT_URL}admin/comments',
|
||||
],
|
||||
];
|
||||
public $main_links = [
|
||||
[
|
||||
'text' => 'Moderator',
|
||||
'url' => '{ROOT_URL}moderator/index',
|
||||
'filter' => 'mod',
|
||||
]
|
||||
];
|
||||
public $permissionMatrix = [
|
||||
'modAccess' => [
|
||||
'pretty' => 'Access Moderator Areas',
|
||||
'default' => false,
|
||||
],
|
||||
];
|
||||
public $resourceMatrix = [
|
||||
'groups' => [
|
||||
[
|
||||
'name' => 'Moderator',
|
||||
'permissions' => '{"adminAccess":false}',
|
||||
]
|
||||
],
|
||||
];
|
||||
|
||||
public function __construct( $load = false ) {
|
||||
if ( !empty(App::$activePerms) ) {
|
||||
App::$isMod = !empty(App::$activePerms['modAccess']);
|
||||
} else {
|
||||
App::$isMod = false;
|
||||
}
|
||||
$this->filters[] = [
|
||||
'name' => 'mod',
|
||||
'find' => '#{MOD}(.*?){/MOD}#is',
|
||||
'replace' => ( App::$isMod ? '$1' : '' ),
|
||||
'enabled' => true,
|
||||
];
|
||||
self::$comments = new CommentsModel;
|
||||
parent::__construct( $load );
|
||||
}
|
||||
|
||||
public function formPost( $type, $content, $redirect ) {
|
||||
if ( ! $this->checkEnabled() ) {
|
||||
Debug::info( 'Comments Plugin is disabled in the control panel.' );
|
||||
Issues::add( 'error', 'Comments are disabled.' );
|
||||
return false;
|
||||
}
|
||||
if ( !App::$isLoggedIn ) {
|
||||
Session::flash( 'notice', 'You must be logged in to post comments.' );
|
||||
return Redirect::to( $redirect . $content->ID );
|
||||
}
|
||||
if ( !Forms::check( 'newComment' ) ) {
|
||||
Session::flash( 'error', [ 'There was a problem with your comment form.' => Check::userErrors() ] );
|
||||
return Redirect::to( $redirect . $content->ID );
|
||||
}
|
||||
if ( !self::$comments->create( $type, $content->ID, Input::post( 'comment' ) ) ) {
|
||||
Session::flash( 'error', [ 'There was a problem posting your comment.' => Check::userErrors() ] );
|
||||
} else {
|
||||
Session::flash( 'success', 'Comment posted' );
|
||||
}
|
||||
return Redirect::to( $redirect . $content->ID );
|
||||
}
|
||||
|
||||
public function formEdit( $type, $content, $redirect ) {
|
||||
if ( ! $this->checkEnabled() ) {
|
||||
Debug::info( 'Comments Plugin is disabled in the control panel.' );
|
||||
Issues::add( 'error', 'Comments are disabled.' );
|
||||
return false;
|
||||
}
|
||||
if ( !App::$isLoggedIn ) {
|
||||
Session::flash( 'notice', 'You must be logged in to do that.' );
|
||||
return Redirect::to( $type );
|
||||
}
|
||||
if ( !App::$isAdmin && $content->author != App::$activeUser->ID ) {
|
||||
Session::flash( 'error', 'You do not have permission to edit this comment' );
|
||||
return Redirect::to( $type );
|
||||
}
|
||||
if ( !Input::exists( 'submit' ) ) {
|
||||
return Views::view( 'comments.admin.edit', $content );
|
||||
}
|
||||
if ( !Forms::check( 'editComment' ) ) {
|
||||
Issues::add( 'error', [ 'There was a problem editing your comment.' => Check::userErrors() ] );
|
||||
return Views::view( 'comments.admin.edit', $content );
|
||||
}
|
||||
if ( !self::$comments->update( $content->ID, Input::post( 'comment' ) ) ) {
|
||||
Issues::add( 'error', [ 'There was a problem editing your comment.' => Check::userErrors() ] );
|
||||
return Views::view( 'comments.admin.edit', $content );
|
||||
}
|
||||
Session::flash( 'success', 'Comment updated' );
|
||||
return Redirect::to( $redirect . $content->contentID );
|
||||
}
|
||||
|
||||
public function formDelete( $type, $content, $redirect ) {
|
||||
if ( ! $this->checkEnabled() ) {
|
||||
Debug::info( 'Comments Plugin is disabled in the control panel.' );
|
||||
Issues::add( 'error', 'Comments are disabled.' );
|
||||
return false;
|
||||
}
|
||||
if ( !App::$isLoggedIn ) {
|
||||
Session::flash( 'notice', 'You must be logged in to do that.' );
|
||||
return Redirect::to( $type );
|
||||
}
|
||||
if ( !App::$isAdmin && $content->author != App::$activeUser->ID ) {
|
||||
Session::flash( 'error', 'You do not have permission to edit this comment' );
|
||||
return Redirect::to( $type );
|
||||
}
|
||||
if ( !self::$comments->delete( (array) $content->ID ) ) {
|
||||
Session::flash( 'error', 'There was an error with your request.' );
|
||||
} else {
|
||||
Session::flash( 'success', 'Comment has been deleted' );
|
||||
}
|
||||
return Redirect::to( $redirect . $content->contentID );
|
||||
}
|
||||
}
|
28
app/plugins/comments/views/admin/dashboard.html
Executable file
28
app/plugins/comments/views/admin/dashboard.html
Executable file
@ -0,0 +1,28 @@
|
||||
<legend>New Comments</legend>
|
||||
<table class="table context-main">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width: 20%"></th>
|
||||
<th style="width: 70%"></th>
|
||||
<th style="width: 5%"></th>
|
||||
<th style="width: 5%"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{LOOP}
|
||||
<tr>
|
||||
<td>{authorName}</td>
|
||||
<td>{content}</td>
|
||||
<td><a href="{ROOT_URL}admin/comments/edit/{ID}" class="btn btn-sm btn-warning"><i class="fa fa-fw fa-pencil"></i></a></td>
|
||||
<td><a href="{ROOT_URL}admin/comments/delete/{ID}" class="btn btn-sm btn-danger"><i class="fa fa-fw fa-trash"></i></a></td>
|
||||
</tr>
|
||||
{/LOOP}
|
||||
{ALT}
|
||||
<tr>
|
||||
<td align="center" colspan="4">
|
||||
No results to show.
|
||||
</td>
|
||||
</tr>
|
||||
{/ALT}
|
||||
</tbody>
|
||||
</table>
|
25
app/plugins/comments/views/admin/edit.html
Executable file
25
app/plugins/comments/views/admin/edit.html
Executable file
@ -0,0 +1,25 @@
|
||||
<div class="mb-4 mt-4">
|
||||
<div class="offset-md-1 col-10 p-3 context-main-bg">
|
||||
<legend class="text-center">Edit Comment</legend>
|
||||
<hr>
|
||||
{ADMIN_BREADCRUMBS}
|
||||
<form method="post" class="container py-4">
|
||||
<fieldset>
|
||||
<div class="mb-3 row">
|
||||
<label for="comment" class="col-lg-5 col-form-label text-end">Comment:</label>
|
||||
<div class="col-lg-3">
|
||||
<textarea class="form-control" name="comment" maxlength="2000" rows="5" cols="50" id="comment">{content}</textarea>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Hidden Token -->
|
||||
<input type="hidden" name="token" value="{TOKEN}">
|
||||
|
||||
<!-- Submit Button -->
|
||||
<div class="text-center">
|
||||
<button type="submit" name="submit" value="submit" class="btn btn-primary btn-lg">Save</button>
|
||||
</div>
|
||||
</fieldset>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
45
app/plugins/comments/views/admin/list.html
Executable file
45
app/plugins/comments/views/admin/list.html
Executable file
@ -0,0 +1,45 @@
|
||||
<div class="context-main-bg context-main p-3">
|
||||
<legend class="text-center">Recent Comments</legend>
|
||||
<hr>
|
||||
{ADMIN_BREADCRUMBS}
|
||||
<form action="{ROOT_URL}admin/comments/delete" method="post">
|
||||
<table class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width: 20%">Author</th>
|
||||
<th style="width: 20%">Subject</th>
|
||||
<th style="width: 35%">Comment</th>
|
||||
<th style="width: 10%">Time</th>
|
||||
<th style="width: 5%"></th>
|
||||
<th style="width: 5%"></th>
|
||||
<th style="width: 5%">
|
||||
<input type="checkbox" onchange="checkAll(this)" name="check.c" value="C_[]">
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{LOOP}
|
||||
<tr>
|
||||
<td><a href="{ROOT_URL}admin/users/view/{author}">{authorName}</a></td>
|
||||
<td><a href="{ROOT_URL}admin/blog/view/{contentID}">{contentTitle}</a></td>
|
||||
<td>{content}</td>
|
||||
<td>{DTC}{created}{/DTC}</td>
|
||||
<td><a href="{ROOT_URL}admin/comments/edit/{ID}" class="btn btn-sm btn-warning"><i class="fa fa-fw fa-pencil"></i></a></td>
|
||||
<td><a href="{ROOT_URL}admin/comments/delete/{ID}" class="btn btn-sm btn-danger"><i class="fa fa-fw fa-trash"></i></a></td>
|
||||
<td>
|
||||
<input type="checkbox" value="{ID}" name="C_[]">
|
||||
</td>
|
||||
</tr>
|
||||
{/LOOP}
|
||||
{ALT}
|
||||
<tr>
|
||||
<td align="center" colspan="7">
|
||||
No results to show.
|
||||
</td>
|
||||
</tr>
|
||||
{/ALT}
|
||||
</tbody>
|
||||
</table>
|
||||
<button name="submit" value="submit" type="submit" class="btn btn-sm btn-danger"><i class="fa fa-fw fa-trash"></i></button>
|
||||
</form>
|
||||
</div>
|
8
app/plugins/comments/views/control.html
Executable file
8
app/plugins/comments/views/control.html
Executable file
@ -0,0 +1,8 @@
|
||||
<div class="action">
|
||||
<a href="{ROOT_URL}{COMMENT_TYPE}/comments/edit/{ID}" class="btn btn-warning btn-sm">
|
||||
<span class="fa fa-fw fa-pencil"></span>
|
||||
</a>
|
||||
<a href="{ROOT_URL}{COMMENT_TYPE}/comments/delete/{ID}" class="btn btn-danger btn-sm">
|
||||
<span class="fa fa-fw fa-trash"></span>
|
||||
</a>
|
||||
</div>
|
14
app/plugins/comments/views/create.html
Executable file
14
app/plugins/comments/views/create.html
Executable file
@ -0,0 +1,14 @@
|
||||
<form method="post" class="text-center mx-auto mt-4" style="max-width: 600px;">
|
||||
<div class="mb-3">
|
||||
<textarea
|
||||
class="form-control"
|
||||
name="comment"
|
||||
maxlength="2000"
|
||||
rows="4"
|
||||
id="comment"
|
||||
placeholder="Write your comment here..."></textarea>
|
||||
</div>
|
||||
<button name="submit" value="submit" type="submit" class="btn btn-lg btn-primary mb-3">Comment</button>
|
||||
<input type="hidden" name="token" value="{TOKEN}">
|
||||
<input type="hidden" name="contentId" value="{CONTENT_ID}">
|
||||
</form>
|
39
app/plugins/comments/views/list.html
Executable file
39
app/plugins/comments/views/list.html
Executable file
@ -0,0 +1,39 @@
|
||||
<div class="card">
|
||||
<div class="card-header d-flex align-items-center justify-content-between context-main-bg context-main">
|
||||
<h3 class="card-title mb-0">
|
||||
<i class="fa fa-fw fa-comment"></i> Comments
|
||||
</h3>
|
||||
<span class="badge bg-primary">{count}</span>
|
||||
</div>
|
||||
<div class="card-body context-second-bg">
|
||||
<ul class="list-group list-group-flush">
|
||||
{LOOP}
|
||||
<li class="list-group-item context-main-bg context-main mb-2">
|
||||
<div class="d-flex align-items-start">
|
||||
<div class="me-3">
|
||||
<img src="{ROOT_URL}{avatar}" class="rounded-circle" alt="User Avatar" style="width: 50px; height: 50px;">
|
||||
</div>
|
||||
<div>
|
||||
<div class="mb-1">
|
||||
<small class="text-muted">
|
||||
By: <a href="{ROOT_URL}home/profile/{author}" class="text-decoration-none">{authorName}</a> on {DTC date}{created}{/DTC}
|
||||
</small>
|
||||
</div>
|
||||
<div class="comment-text">
|
||||
{content}
|
||||
</div>
|
||||
{commentControl}
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
{/LOOP}
|
||||
{ALT}
|
||||
<li class="list-group-item context-main-bg mb-2">
|
||||
<div class="text-center">
|
||||
<p class="mb-0">Be the first to comment.</p>
|
||||
</div>
|
||||
</li>
|
||||
{/ALT}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
7
app/plugins/comments/views/moderator.html
Executable file
7
app/plugins/comments/views/moderator.html
Executable file
@ -0,0 +1,7 @@
|
||||
<h1>Moderator' Area</h1>
|
||||
<div class="jumbotron">
|
||||
<h1>Welcome!</h1>
|
||||
<p>This is the moderator section. You can give some groups permission to access this area. The menu is hidden for normal users and if they get a link to a moderator's area, the authentication system will stop them from accessing any content protected this way.</p>
|
||||
<p>You can even use this feature in-line with your views, hiding certain components from non-moderators</p>
|
||||
<p>The idea behind this role is for them to help you in policing comments as needed.</p>
|
||||
</div>
|
56
app/plugins/contact/controllers/admin/contact.php
Executable file
56
app/plugins/contact/controllers/admin/contact.php
Executable file
@ -0,0 +1,56 @@
|
||||
<?php
|
||||
/**
|
||||
* app/plugins/contact/controllers/admin/contact.php
|
||||
*
|
||||
* This is the contact admin controller.
|
||||
*
|
||||
* @package TP Contact
|
||||
* @version 3.0
|
||||
* @author Joey Kimsey <Joey@thetempusproject.com>
|
||||
* @link https://TheTempusProject.com
|
||||
* @license https://opensource.org/licenses/MIT [MIT LICENSE]
|
||||
*/
|
||||
namespace TheTempusProject\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\Contact as ContactModel;
|
||||
|
||||
class Contact extends AdminController {
|
||||
protected static $contact;
|
||||
|
||||
public function __construct() {
|
||||
parent::__construct();
|
||||
self::$title = 'Admin - Contact';
|
||||
self::$contact = new ContactModel;
|
||||
}
|
||||
|
||||
public function view( $id = null ) {
|
||||
Views::view( 'contact.admin.view', self::$contact->findById( $id ) );
|
||||
}
|
||||
|
||||
public function delete( $data = null ) {
|
||||
if ( Input::exists( 'submit' ) ) {
|
||||
$data = Input::post( 'F_' );
|
||||
}
|
||||
if ( self::$contact->delete( (array) $data ) ) {
|
||||
Issues::add( 'success', 'contact deleted' );
|
||||
} else {
|
||||
Issues::add( 'error', 'There was an error with your request.' );
|
||||
}
|
||||
$this->index();
|
||||
}
|
||||
|
||||
public function clear( $data = null ) {
|
||||
self::$contact->clear();
|
||||
$this->index();
|
||||
}
|
||||
|
||||
public function index( $data = null ) {
|
||||
Views::view( 'contact.admin.list', self::$contact->listPaginated() );
|
||||
}
|
||||
}
|
47
app/plugins/contact/controllers/contact.php
Executable file
47
app/plugins/contact/controllers/contact.php
Executable file
@ -0,0 +1,47 @@
|
||||
<?php
|
||||
/**
|
||||
* app/plugins/contact/controllers/contact.php
|
||||
*
|
||||
* This is the home controller for the contact plugin.
|
||||
*
|
||||
* @package TP Contact
|
||||
* @version 3.0
|
||||
* @author Joey Kimsey <Joey@thetempusproject.com>
|
||||
* @link https://TheTempusProject.com
|
||||
* @license https://opensource.org/licenses/MIT [MIT LICENSE]
|
||||
*/
|
||||
namespace TheTempusProject\Controllers;
|
||||
|
||||
use TheTempusProject\Houdini\Classes\Views;
|
||||
use TheTempusProject\Houdini\Classes\Issues;
|
||||
use TheTempusProject\Classes\Controller;
|
||||
use TheTempusProject\Classes\Forms;
|
||||
use TheTempusProject\Bedrock\Functions\Check;
|
||||
use TheTempusProject\Bedrock\Functions\Input;
|
||||
use TheTempusProject\Bedrock\Functions\Session;
|
||||
use TheTempusProject\Hermes\Functions\Redirect;
|
||||
use TheTempusProject\Models\Contact as ContactModel;
|
||||
|
||||
class Contact extends Controller {
|
||||
protected static $contact;
|
||||
|
||||
public function index() {
|
||||
self::$contact = new ContactModel;
|
||||
self::$title = 'Contact - {SITENAME}';
|
||||
self::$pageDescription = 'At {SITENAME}, we value our users\' input. You can provide any contact or suggestions using this form.';
|
||||
if ( !Input::exists() ) {
|
||||
return Views::view( 'contact.create' );
|
||||
}
|
||||
if ( !Forms::check( 'contact' ) ) {
|
||||
Issues::add( 'error', [ 'There was an error with your form, please check your submission and try again.' => Check::userErrors() ] );
|
||||
return Views::view( 'contact.create' );
|
||||
}
|
||||
$result = self::$contact->create( Input::post( 'name' ), Input::post( 'contactEmail' ), Input::post( 'entry' ) );
|
||||
if ( $result ) {
|
||||
Session::flash( 'success', 'Thank you! Your contact has been received.' );
|
||||
Redirect::to( 'home/index' );
|
||||
} else {
|
||||
Issues::add( 'error', [ 'There was an error with your form, please check your submission and try again.' => Check::userErrors() ] );
|
||||
}
|
||||
}
|
||||
}
|
56
app/plugins/contact/forms.php
Executable file
56
app/plugins/contact/forms.php
Executable file
@ -0,0 +1,56 @@
|
||||
<?php
|
||||
/**
|
||||
* app/plugins/contact/forms.php
|
||||
*
|
||||
* This houses all of the form checking functions for this plugin.
|
||||
*
|
||||
* @package TP Contact
|
||||
* @version 3.0
|
||||
* @author Joey Kimsey <Joey@thetempusproject.com>
|
||||
* @link https://TheTempusProject.com
|
||||
* @license https://opensource.org/licenses/MIT [MIT LICENSE]
|
||||
*/
|
||||
namespace TheTempusProject\Plugins\Contact;
|
||||
|
||||
use TheTempusProject\Bedrock\Functions\Input;
|
||||
use TheTempusProject\Bedrock\Functions\Check;
|
||||
use TheTempusProject\Classes\Forms;
|
||||
|
||||
class ContactForms extends Forms {
|
||||
/**
|
||||
* Adds these functions to the form list.
|
||||
*/
|
||||
public function __construct() {
|
||||
self::addHandler( 'contact', __CLASS__, 'contact' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the contact form.
|
||||
*
|
||||
* @return {bool}
|
||||
*/
|
||||
public static function contact() {
|
||||
if ( !Input::exists( 'name' ) ) {
|
||||
Check::addUserError( 'You must provide a name.' );
|
||||
return false;
|
||||
}
|
||||
if ( !Check::name( Input::post( 'name' ) ) ) {
|
||||
Check::addUserError( 'Invalid name.' );
|
||||
return false;
|
||||
}
|
||||
if ( !empty( Input::post( 'contactEmail' ) ) && !Check::email( Input::post( 'contactEmail' ) ) ) {
|
||||
Check::addUserError( 'Invalid Email.' );
|
||||
return false;
|
||||
}
|
||||
if ( Input::post( 'entry' ) == '' ) {
|
||||
Check::addUserError( 'Contact cannot be empty.' );
|
||||
return false;
|
||||
}
|
||||
if ( !Check::token() ) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
new ContactForms;
|
83
app/plugins/contact/models/contact.php
Executable file
83
app/plugins/contact/models/contact.php
Executable file
@ -0,0 +1,83 @@
|
||||
<?php
|
||||
/**
|
||||
* app/plugins/contact/models/contact.php
|
||||
*
|
||||
* This class is used for the manipulation of the feedback database table.
|
||||
*
|
||||
* @todo make this send a confirmation email
|
||||
*
|
||||
* @package TP Contact
|
||||
* @version 3.0
|
||||
* @author Joey Kimsey <Joey@thetempusproject.com>
|
||||
* @link https://TheTempusProject.com
|
||||
* @license https://opensource.org/licenses/MIT [MIT LICENSE]
|
||||
*/
|
||||
namespace TheTempusProject\Models;
|
||||
|
||||
use TheTempusProject\Bedrock\Functions\Check;
|
||||
use TheTempusProject\Canary\Bin\Canary as Debug;
|
||||
use TheTempusProject\Classes\DatabaseModel;
|
||||
use TheTempusProject\Plugins\Contact as Plugin;
|
||||
|
||||
class Contact extends DatabaseModel {
|
||||
public $tableName = 'feedback';
|
||||
public $databaseMatrix = [
|
||||
[ 'name', 'varchar', '128' ],
|
||||
[ 'time', 'int', '10' ],
|
||||
[ 'email', 'varchar', '128' ],
|
||||
[ 'ip', 'varchar', '64' ],
|
||||
[ 'feedback', 'text', '' ],
|
||||
];
|
||||
public $plugin;
|
||||
|
||||
/**
|
||||
* The model constructor.
|
||||
*/
|
||||
public function __construct() {
|
||||
parent::__construct();
|
||||
$this->plugin = new Plugin;
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves a contact form to the db.
|
||||
*
|
||||
* @param string $name -the name on the form
|
||||
* @param string $email -the email provided
|
||||
* @param string $feedback -contents of the feedback form.
|
||||
* @return bool
|
||||
*/
|
||||
public function create( $name, $email, $feedback ) {
|
||||
if ( !$this->plugin->checkEnabled() ) {
|
||||
Debug::info( 'Contact is disabled in the config.' );
|
||||
return false;
|
||||
}
|
||||
$fields = [
|
||||
'name' => $name,
|
||||
'email' => $email,
|
||||
'feedback' => $feedback,
|
||||
'time' => time(),
|
||||
'ip' => $_SERVER['REMOTE_ADDR'],
|
||||
];
|
||||
if ( !self::$db->insert( $this->tableName, $fields ) ) {
|
||||
Debug::info( 'Contact::create - failed to insert to db' );
|
||||
return false;
|
||||
}
|
||||
return self::$db->lastId();
|
||||
}
|
||||
|
||||
/**
|
||||
* Function to clear contact from the DB.
|
||||
*
|
||||
* @todo is there a way i could check for success here I'm pretty sure this is just a bad idea?
|
||||
* @return bool
|
||||
*/
|
||||
public function clear() {
|
||||
if ( empty( self::$log ) ) {
|
||||
self::$log = new Log;
|
||||
}
|
||||
self::$db->delete( $this->tableName, ['ID', '>=', '0'] );
|
||||
self::$log->admin( 'Contacts Cleared' );
|
||||
Debug::info( 'Contacts Cleared' );
|
||||
return true;
|
||||
}
|
||||
}
|
60
app/plugins/contact/plugin.php
Executable file
60
app/plugins/contact/plugin.php
Executable file
@ -0,0 +1,60 @@
|
||||
<?php
|
||||
/**
|
||||
* app/plugins/contact/plugin.php
|
||||
*
|
||||
* This houses all of the main plugin info and functionality.
|
||||
*
|
||||
* @package TP Contact
|
||||
* @version 3.0
|
||||
* @author Joey Kimsey <Joey@thetempusproject.com>
|
||||
* @link https://TheTempusProject.com
|
||||
* @license https://opensource.org/licenses/MIT [MIT LICENSE]
|
||||
*/
|
||||
namespace TheTempusProject\Plugins;
|
||||
|
||||
use TheTempusProject\Classes\Plugin;
|
||||
|
||||
class Contact extends Plugin {
|
||||
public $pluginName = 'TP Contact';
|
||||
public $pluginAuthor = 'JoeyK';
|
||||
public $pluginWebsite = 'https://TheTempusProject.com';
|
||||
public $modelVersion = '1.0';
|
||||
public $pluginVersion = '3.0';
|
||||
public $pluginDescription = 'A simple plugin which adds a form and management for user submitted contact forms.';
|
||||
public $configName = 'contact';
|
||||
public $configMatrix = [
|
||||
'enabled' => [
|
||||
'type' => 'radio',
|
||||
'pretty' => 'Enable User Contact.',
|
||||
'default' => true,
|
||||
],
|
||||
'sendEmail' => [
|
||||
'type' => 'radio',
|
||||
'pretty' => 'Email the user after submitting.',
|
||||
'default' => false,
|
||||
],
|
||||
'emailTemplate' => [
|
||||
'type' => 'text',
|
||||
'pretty' => 'Email Template',
|
||||
'default' => 'contactEmail',
|
||||
],
|
||||
];
|
||||
public $permissionMatrix = [
|
||||
'canUseContactForm' => [
|
||||
'pretty' => 'Can Submit Contact',
|
||||
'default' => true,
|
||||
],
|
||||
];
|
||||
public $contact_footer_links = [
|
||||
[
|
||||
'text' => 'Contact',
|
||||
'url' => '{ROOT_URL}contact',
|
||||
],
|
||||
];
|
||||
public $admin_links = [
|
||||
[
|
||||
'text' => '<i class="fa fa-fw fa-briefcase"></i> Contact',
|
||||
'url' => '{ROOT_URL}admin/contact',
|
||||
],
|
||||
];
|
||||
}
|
29
app/plugins/contact/views/admin/dashboard.html
Normal file
29
app/plugins/contact/views/admin/dashboard.html
Normal file
@ -0,0 +1,29 @@
|
||||
<table class="table context-main">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width: 5%"></th>
|
||||
<th style="width: 25%"></th>
|
||||
<th style="width: 55%"></th>
|
||||
<th style="width: 5%"></th>
|
||||
<th style="width: 5%"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{LOOP}
|
||||
<tr>
|
||||
<td>{ID}</td>
|
||||
<td>{DTC}{time}{/DTC}</td>
|
||||
<td>{feedback}</td>
|
||||
<td><a href="{ROOT_URL}admin/contact/view/{ID}" class="btn btn-sm btn-primary"><i class="fa fa-fw fa-upload"></i></a></td>
|
||||
<td><a href="{ROOT_URL}admin/contact/delete/{ID}" class="btn btn-sm btn-danger"><i class="fa fa-fw fa-trash"></i></a></td>
|
||||
</tr>
|
||||
{/LOOP}
|
||||
{ALT}
|
||||
<tr>
|
||||
<td class="text-center" colspan="5">
|
||||
No Contact forms to show.
|
||||
</td>
|
||||
</tr>
|
||||
{/ALT}
|
||||
</tbody>
|
||||
</table>
|
45
app/plugins/contact/views/admin/list.html
Executable file
45
app/plugins/contact/views/admin/list.html
Executable file
@ -0,0 +1,45 @@
|
||||
<div class="context-main-bg context-main p-3">
|
||||
<legend class="text-center">Contact Forms</legend>
|
||||
<hr>
|
||||
{ADMIN_BREADCRUMBS}
|
||||
<form action="{ROOT_URL}admin/contact/delete" method="post">
|
||||
<table class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width: 5%">ID</th>
|
||||
<th style="width: 25%">Time</th>
|
||||
<th style="width: 55%">Feedback</th>
|
||||
<th style="width: 5%"></th>
|
||||
<th style="width: 5%"></th>
|
||||
<th style="width: 5%">
|
||||
<input type="checkbox" onchange="checkAll(this)" name="check.f" value="F_[]">
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{LOOP}
|
||||
<tr>
|
||||
<td>{ID}</td>
|
||||
<td>{DTC}{time}{/DTC}</td>
|
||||
<td>{feedback}</td>
|
||||
<td><a href="{ROOT_URL}admin/contact/view/{ID}" class="btn btn-sm btn-primary"><i class="fa fa-fw fa-upload"></i></a></td>
|
||||
<td><a href="{ROOT_URL}admin/contact/delete/{ID}" class="btn btn-sm btn-danger"><i class="fa fa-fw fa-trash"></i></a></td>
|
||||
<td>
|
||||
<input type="checkbox" value="{ID}" name="F_[]">
|
||||
</td>
|
||||
</tr>
|
||||
{/LOOP}
|
||||
{ALT}
|
||||
<tr>
|
||||
<td align="center" colspan="6">
|
||||
No results to show.
|
||||
</td>
|
||||
</tr>
|
||||
{/ALT}
|
||||
</tbody>
|
||||
</table>
|
||||
<button name="submit" value="submit" type="submit" class="btn btn-sm btn-danger"><i class="fa fa-fw fa-trash"></i></button>
|
||||
</form>
|
||||
<br>
|
||||
<a href="{ROOT_URL}admin/contact/clear">clear all</a>
|
||||
</div>
|
61
app/plugins/contact/views/admin/view.html
Executable file
61
app/plugins/contact/views/admin/view.html
Executable file
@ -0,0 +1,61 @@
|
||||
<div class="container py-4">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-8">
|
||||
{ADMIN_BREADCRUMBS}
|
||||
<div class="card shadow">
|
||||
<!-- Card Header -->
|
||||
<div class="card-header text-center bg-dark text-white">
|
||||
<h3 class="card-title mb-0">Contact Form</h3>
|
||||
</div>
|
||||
|
||||
<!-- Card Body -->
|
||||
<div class="card-body">
|
||||
<div class="row align-items-center">
|
||||
<!-- Log Details -->
|
||||
<table class="table table-borderless">
|
||||
<tbody>
|
||||
<tr>
|
||||
<th scope="row">ID:</th>
|
||||
<td>{ID}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Time submitted</th>
|
||||
<td>{DTC}{time}{/DTC}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Email:</th>
|
||||
<td>{email}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Name:</th>
|
||||
<td>{name}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">IP:</th>
|
||||
<td>{ip}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row" colspan="2">Feedback:</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="2">{feedback}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Admin Controls -->
|
||||
<div class="card-footer text-center">
|
||||
{ADMIN}
|
||||
<form action="{ROOT_URL}admin/contact/delete" method="post">
|
||||
<input type="hidden" name="F_" value="{ID}">
|
||||
<input type="hidden" name="token" value="{TOKEN}">
|
||||
<button name="submit" value="submit" type="submit" class="btn btn-sm btn-danger"><i class="fa fa-fw fa-trash"></i></button>
|
||||
</form>
|
||||
{/ADMIN}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
47
app/plugins/contact/views/create.html
Executable file
47
app/plugins/contact/views/create.html
Executable file
@ -0,0 +1,47 @@
|
||||
<div class="m-2 m-lg-4">
|
||||
<div class="context-main-bg container py-2 my-2 py-lg-4 my-lg-4">
|
||||
<h2 class="text-center mb-4">Contact Us</h2>
|
||||
<div class="col-12 col-lg-6 offset-lg-3">
|
||||
<p class="text-center text-lg-start">
|
||||
Here at <strong>{SITENAME}</strong>, we highly value your feedback. We constantly strive to provide our users with the highest level of quality in everything we do.
|
||||
</p>
|
||||
<p class="text-center text-lg-start">
|
||||
If you would like to provide any suggestions or comments on our service, we ask that you please fill out the quick form below and let us know what's on your mind.
|
||||
</p>
|
||||
</div>
|
||||
<form method="post">
|
||||
<!-- Name -->
|
||||
<div class="mb-3 row">
|
||||
<label for="name" class="col-lg-3 col-form-label text-lg-end">Name:</label>
|
||||
<div class="col-lg-6">
|
||||
<input type="text" class="form-control" name="name" id="name" required>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Email (Optional) -->
|
||||
<div class="mb-3 row">
|
||||
<label for="contactEmail" class="col-lg-3 col-form-label text-lg-end">E-mail: (optional)</label>
|
||||
<div class="col-lg-6">
|
||||
<input type="email" class="form-control" name="contactEmail" id="contactEmail">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Feedback -->
|
||||
<div class="mb-3 row">
|
||||
<label for="entry" class="col-lg-3 col-form-label text-lg-end">Feedback:</label>
|
||||
<div class="col-lg-6">
|
||||
<textarea class="form-control" name="entry" id="entry" rows="6" maxlength="2000" required></textarea>
|
||||
<small class="form-text text-muted">Max: 2000 characters</small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Hidden Token -->
|
||||
<input type="hidden" name="token" value="{TOKEN}">
|
||||
|
||||
<!-- Submit Button -->
|
||||
<div class="text-center">
|
||||
<button type="submit" name="submit" value="submit" class="btn btn-primary btn-lg">Submit</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
121
app/plugins/messages/controllers/messages.php
Executable file
121
app/plugins/messages/controllers/messages.php
Executable file
@ -0,0 +1,121 @@
|
||||
<?php
|
||||
/**
|
||||
* app/controllers/messages.php
|
||||
*
|
||||
* This is the user messages controller.
|
||||
*
|
||||
* @version 3.0
|
||||
* @author Joey Kimsey <Joey@thetempusproject.com>
|
||||
* @link https://TheTempusProject.com
|
||||
* @license https://opensource.org/licenses/MIT [MIT LICENSE]
|
||||
*/
|
||||
namespace TheTempusProject\Controllers;
|
||||
|
||||
use TheTempusProject\Classes\Controller;
|
||||
use TheTempusProject\Classes\Forms as FormChecker;
|
||||
use TheTempusProject\Models\Message;
|
||||
use TheTempusProject\Houdini\Classes\Components;
|
||||
use TheTempusProject\Houdini\Classes\Views;
|
||||
use TheTempusProject\Houdini\Classes\Issues;
|
||||
use TheTempusProject\Bedrock\Functions\Check;
|
||||
use TheTempusProject\Bedrock\Functions\Input;
|
||||
use TheTempusProject\TheTempusProject as App;
|
||||
use TheTempusProject\Hermes\Functions\Redirect;
|
||||
use TheTempusProject\Bedrock\Functions\Session;
|
||||
|
||||
class Messages extends Controller {
|
||||
private static $message;
|
||||
|
||||
public function __construct() {
|
||||
parent::__construct();
|
||||
self::$title = 'Messages';
|
||||
self::$message = new Message;
|
||||
if ( ! App::$isLoggedIn ) {
|
||||
Session::flash( 'error', 'You do not have permission to access this page.' );
|
||||
return Redirect::home();
|
||||
}
|
||||
}
|
||||
|
||||
public function create() {
|
||||
self::$title .= ' - New Message';
|
||||
if ( Input::get( 'prepopuser' ) ) {
|
||||
$data = Input::get( 'prepopuser' );
|
||||
}
|
||||
if ( !empty( $data ) && self::$user->checkUsername( $data ) ) {
|
||||
Components::set( 'prepopuser', $data );
|
||||
} else {
|
||||
Components::set( 'prepopuser', '' );
|
||||
}
|
||||
if ( !Input::exists( 'submit' ) ) {
|
||||
return Views::view( 'messages.create' );
|
||||
}
|
||||
if ( !FormChecker::check( 'newMessage' ) ) {
|
||||
Issues::add( 'error', [ 'There was an problem sending your messages.' => Check::userErrors() ] );
|
||||
return Views::view( 'messages.create' );
|
||||
}
|
||||
if ( self::$message->newThread( Input::post( 'toUser' ), Input::post( 'subject' ), Input::post( 'message' ) ) ) {
|
||||
Issues::add( 'success', 'Message Sent.' );
|
||||
} else {
|
||||
Issues::add( 'notice', 'There was an problem sending your messages.' );
|
||||
}
|
||||
return $this->index();
|
||||
}
|
||||
|
||||
public function delete( $id = '' ) {
|
||||
if ( Input::exists( 'T_' ) ) {
|
||||
self::$message->delete( Input::post( 'T_' ) );
|
||||
}
|
||||
if ( Input::exists( 'F_' ) ) {
|
||||
self::$message->delete( Input::post( 'F_' ) );
|
||||
}
|
||||
if ( Input::exists( 'ID' ) ) {
|
||||
self::$message->delete( Input::get( 'ID' ) );
|
||||
}
|
||||
if ( !empty( $id ) ) {
|
||||
self::$message->delete( $id );
|
||||
}
|
||||
return $this->index();
|
||||
}
|
||||
|
||||
public function index() {
|
||||
Components::set( 'message_inbox', Views::simpleView( 'messages.inbox', self::$message->getInbox() ) );
|
||||
Components::set( 'message_outbox', Views::simpleView( 'messages.outbox', self::$message->getOutbox() ) );
|
||||
Views::view( 'messages.index' );
|
||||
}
|
||||
|
||||
public function read( $id = '' ) {
|
||||
self::$message->markRead( $id );
|
||||
return $this->index();
|
||||
}
|
||||
|
||||
public function reply() {
|
||||
if ( Input::exists( 'messageID' ) ) {
|
||||
$data = Input::post( 'messageID' );
|
||||
}
|
||||
if ( !Check::id( $data ) ) {
|
||||
Issues::add( 'error', 'There was an error with your request.' );
|
||||
return $this->index();
|
||||
}
|
||||
self::$title .= ' - Reply to: ' . self::$message->messageTitle( $data );
|
||||
if ( !Input::exists( 'message' ) ) {
|
||||
Components::set( 'messageID', $data );
|
||||
return Views::view( 'messages.reply' );
|
||||
}
|
||||
if ( !FormChecker::check( 'replyMessage' ) ) {
|
||||
Issues::add( 'error', [ 'There was an problem sending your messages.' => Check::userErrors() ] );
|
||||
Components::set( 'messageID', $data );
|
||||
return Views::view( 'messages.reply' );
|
||||
}
|
||||
if ( !self::$message->newMessageReply( $data, Input::post( 'message' ) ) ) {
|
||||
Issues::add( 'error', 'There was an error with your request.' );
|
||||
return $this->index();
|
||||
}
|
||||
Issues::add( 'success', 'Reply Sent.' );
|
||||
return $this->index();
|
||||
}
|
||||
|
||||
public function view( $id = '' ) {
|
||||
self::$title = self::$message->messageTitle( $id );
|
||||
return Views::view( 'messages.message', self::$message->getThread( $id, true ) );
|
||||
}
|
||||
}
|
454
app/plugins/messages/models/message.php
Executable file
454
app/plugins/messages/models/message.php
Executable file
@ -0,0 +1,454 @@
|
||||
<?php
|
||||
/**
|
||||
* app/models/message.php
|
||||
*
|
||||
* Houses all of the functions for the core messaging system.
|
||||
*
|
||||
* @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\Classes\DatabaseModel;
|
||||
use TheTempusProject\Houdini\Classes\Components;
|
||||
use TheTempusProject\Houdini\Classes\Views;
|
||||
use TheTempusProject\Canary\Bin\Canary as Debug;
|
||||
use TheTempusProject\Bedrock\Functions\Check;
|
||||
use TheTempusProject\Bedrock\Functions\Sanitize;
|
||||
use TheTempusProject\TheTempusProject as App;
|
||||
use TheTempusProject\Canary\Classes\CustomException;
|
||||
|
||||
class Message extends DatabaseModel {
|
||||
public $tableName = 'messages';
|
||||
public $databaseMatrix = [
|
||||
[ 'userTo', 'int', '11' ],
|
||||
[ 'userFrom', 'int', '11' ],
|
||||
[ 'parent', 'int', '11' ],
|
||||
[ 'sent', 'int', '10' ],
|
||||
[ 'lastReply', 'int', '10' ],
|
||||
[ 'senderDeleted', 'int', '1' ],
|
||||
[ 'recieverDeleted', 'int', '1' ],
|
||||
[ 'isRead', 'int', '1' ],
|
||||
[ 'message', 'text', '' ],
|
||||
[ 'subject', 'text', '' ],
|
||||
];
|
||||
private $messages;
|
||||
private $usernames;
|
||||
|
||||
/**
|
||||
* The model constructor.
|
||||
*/
|
||||
public function __construct() {
|
||||
parent::__construct();
|
||||
self::$message = $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the most recent relative message in a thread
|
||||
*
|
||||
* @param int $parent - the id of the parent message
|
||||
* @param string $user - the id of the relative user
|
||||
* @return object
|
||||
*/
|
||||
public function getLatestMessage( $parent, $user, $type = null ) {
|
||||
if ( !Check::id( $parent ) ) {
|
||||
Debug::info( 'Invalid message ID' );
|
||||
return false;
|
||||
}
|
||||
if ( !Check::id( $user ) ) {
|
||||
Debug::info( 'Invalid user ID' );
|
||||
return false;
|
||||
}
|
||||
$messageData = self::$db->get( $this->tableName, [ 'ID', '=', $parent ] );
|
||||
if ( $messageData->count() == 0 ) {
|
||||
Debug::info( 'Message not found.' );
|
||||
return false;
|
||||
}
|
||||
$message = $messageData->first();
|
||||
$params = [ 'parent', '=', $parent ];
|
||||
if ( $type !== null ) {
|
||||
$params = array_merge( $params, [ 'AND', $type, '=', $user ] );
|
||||
}
|
||||
$messageData = self::$db->get( $this->tableName, $params, 'ID', 'DESC', [ 0, 1 ] );
|
||||
if ( $messageData->count() != 0 ) {
|
||||
if ( $messageData->first()->recieverDeleted == 0 ) {
|
||||
$message = $messageData->first();
|
||||
} else {
|
||||
$message->recieverDeleted = 1;
|
||||
}
|
||||
}
|
||||
return $message;
|
||||
}
|
||||
|
||||
/**
|
||||
* This calls a view of the requested message.
|
||||
*
|
||||
* @param int $ID - The message ID you are looking for.
|
||||
* @return null
|
||||
*/
|
||||
public function getThread( $id, $markRead = false ) {
|
||||
if ( !Check::id( $id ) ) {
|
||||
Debug::info( 'Invalid ID' );
|
||||
return false;
|
||||
}
|
||||
$messageData = self::$db->get( $this->tableName, [ 'ID', '=', $id ] );
|
||||
if ( $messageData->count() == 0 ) {
|
||||
Debug::info( 'Message not found.' );
|
||||
return false;
|
||||
}
|
||||
$message = $messageData->first();
|
||||
if ( $message->userTo == App::$activeUser->ID ) {
|
||||
$permissionCheck = 1;
|
||||
if ( $message->recieverDeleted == 1 ) {
|
||||
Debug::info( 'User has already deleted this message.' );
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if ( $message->userFrom == App::$activeUser->ID ) {
|
||||
$permissionCheck = 1;
|
||||
if ( $message->senderDeleted == 1 ) {
|
||||
Debug::info( 'User has already deleted this message.' );
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if ( empty( $permissionCheck ) ) {
|
||||
Debug::info( 'You do not have permission to view this message.' );
|
||||
return false;
|
||||
}
|
||||
if ( $message->parent != 0 ) {
|
||||
$find = $message->parent;
|
||||
} else {
|
||||
$find = $message->ID;
|
||||
}
|
||||
$messageData = self::$db->get( $this->tableName, [ 'ID', '=', $find, 'OR', 'Parent', '=', $find ], 'ID', 'ASC' )->results();
|
||||
Components::set( 'PID', $find );
|
||||
|
||||
if ( $markRead == true ) {
|
||||
foreach ( $messageData as $instance ) {
|
||||
$this->markRead( $instance->ID );
|
||||
}
|
||||
}
|
||||
return $this->filter( $messageData );
|
||||
}
|
||||
|
||||
public function getInbox( $limit = null ) {
|
||||
if ( empty( $limit ) ) {
|
||||
$limit = 10;
|
||||
}
|
||||
$limit = [ 0, $limit ];
|
||||
$messageData = self::$db->get(
|
||||
$this->tableName,
|
||||
[
|
||||
'parent', '=', 0,
|
||||
'AND',
|
||||
'userFrom', '=', App::$activeUser->ID,
|
||||
'OR',
|
||||
'parent', '=', 0,
|
||||
'AND',
|
||||
'userTo', '=', App::$activeUser->ID,
|
||||
],
|
||||
'ID',
|
||||
'DESC',
|
||||
$limit
|
||||
);
|
||||
if ( $messageData->count() == 0 ) {
|
||||
Debug::info( 'getInbox: No messages found' );
|
||||
return false;
|
||||
}
|
||||
$filters = [
|
||||
'importantUser' => App::$activeUser->ID,
|
||||
'deleted' => false,
|
||||
'type' => 'userTo',
|
||||
];
|
||||
return $this->filter( $messageData->results(), $filters );
|
||||
}
|
||||
|
||||
/**
|
||||
* This function calls the view for the message outbox.
|
||||
*
|
||||
* @return null
|
||||
*/
|
||||
public function getOutbox( $limit = null ) {
|
||||
if ( empty( $limit ) ) {
|
||||
$limit = 10;
|
||||
}
|
||||
$limit = [ 0, $limit ];
|
||||
$messageData = self::$db->get(
|
||||
$this->tableName,
|
||||
[
|
||||
'parent', '=', 0,
|
||||
'AND',
|
||||
'userFrom', '=', App::$activeUser->ID,
|
||||
],
|
||||
'ID',
|
||||
'DESC',
|
||||
$limit
|
||||
);
|
||||
if ( $messageData->count() == 0 ) {
|
||||
Debug::info( 'getOutbox: No messages found' );
|
||||
return false;
|
||||
}
|
||||
$filters = [
|
||||
'importantUser' => App::$activeUser->ID,
|
||||
'deleted' => false,
|
||||
'type' => 'userFrom',
|
||||
];
|
||||
return $this->filter( $messageData->results(), $filters );
|
||||
}
|
||||
|
||||
/**
|
||||
* This function is to prep our messages for display. An array of raw messages
|
||||
* sent through this function will automatically have all the user ID's filter
|
||||
* into actual usernames.
|
||||
*
|
||||
* @param $messageArray - This is an array of messages that need to be processed.
|
||||
* @return array - It will return the same message array after being processed.
|
||||
* @todo add filtering for BB-code.
|
||||
*/
|
||||
public function filter( $messageArray, $filters = [] ) {
|
||||
$out = [];
|
||||
foreach ( $messageArray as $message ) {
|
||||
if ( !is_object( $message ) ) {
|
||||
$message = $messageArray;
|
||||
$end = true;
|
||||
}
|
||||
if ( isset( $filters['type'] ) && isset( $filters['importantUser'] ) ) {
|
||||
$type = $filters['type'];
|
||||
} else {
|
||||
$type = null;
|
||||
}
|
||||
if ( isset( $filters['importantUser'] ) ) {
|
||||
$user = $filters['importantUser'];
|
||||
} else {
|
||||
$user = App::$activeUser->ID;
|
||||
}
|
||||
if ( $message->parent == 0 ) {
|
||||
$last = $this->getLatestMessage( $message->ID, $user, $type );
|
||||
} else {
|
||||
$last = $message;
|
||||
}
|
||||
if ( $type != null && $message->$type != $user && $last->$type != $user ) {
|
||||
continue;
|
||||
}
|
||||
if ( isset( $filters['deleted'] ) && $filters['deleted'] == false ) {
|
||||
if ( $type == 'userFrom' ) {
|
||||
if ( $last->senderDeleted == 1 ) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if ( $type == 'userTo' ) {
|
||||
if ( $last->recieverDeleted == 1 ) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
$messageOut = (array) $message;
|
||||
$short = explode( ' ', Sanitize::contentShort( $message->message ) );
|
||||
$summary = implode( ' ', array_splice( $short, 0, 25 ) );
|
||||
if ( count( $short, 1 ) >= 25 ) {
|
||||
$messageOut['summary'] = $summary . '...';
|
||||
} else {
|
||||
$messageOut['summary'] = $summary;
|
||||
}
|
||||
if ( $last->isRead == 0 ) {
|
||||
$messageOut['unreadBadge'] = Views::simpleView( 'messages.unreadBadge' );
|
||||
} else {
|
||||
$messageOut['unreadBadge'] = '';
|
||||
}
|
||||
$messageOut['fromAvatar'] = self::$user->getAvatar( $message->userFrom );
|
||||
$messageOut['userTo'] = self::$user->getUsername( $message->userTo );
|
||||
$messageOut['userToPretty'] = \ucfirst( $messageOut['userTo'] );
|
||||
$messageOut['userFrom'] = self::$user->getUsername( $message->userFrom );
|
||||
$messageOut['userFromPretty'] = \ucfirst( $messageOut['userFrom'] );
|
||||
$out[] = (object) $messageOut;
|
||||
if ( !empty( $end ) ) {
|
||||
$out = $out[0];
|
||||
break;
|
||||
}
|
||||
}
|
||||
return $out;
|
||||
}
|
||||
|
||||
/**
|
||||
* Function to check input and save messages to the DB.
|
||||
*
|
||||
* @param string $data - Username of the person receiving the sent message.
|
||||
* @return function
|
||||
*/
|
||||
public function newThread( $to, $subject, $message ) {
|
||||
if ( !self::$user->usernameExists( $to ) ) {
|
||||
Debug::info( 'Message->sendMessage: User not found.' );
|
||||
return false;
|
||||
}
|
||||
$fields = [
|
||||
'userTo' => self::$user->getID( $to ),
|
||||
'userFrom' => App::$activeUser->ID,
|
||||
'parent' => 0,
|
||||
'sent' => time(),
|
||||
'lastReply' => time(),
|
||||
'senderDeleted' => 0,
|
||||
'recieverDeleted' => 0,
|
||||
'isRead' => 0,
|
||||
'subject' => $subject,
|
||||
'message' => $message,
|
||||
];
|
||||
if ( !self::$db->insert( $this->tableName, $fields ) ) {
|
||||
new CustomException( 'messageSend' );
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public function getUnreadCount( $userId ) {
|
||||
$result = self::$db->get(
|
||||
$this->tableName,
|
||||
[
|
||||
'userTo', '=', $userId,
|
||||
'AND',
|
||||
'isRead', '=', 0,
|
||||
'AND',
|
||||
'parent', '=', 0,
|
||||
'AND',
|
||||
'recieverDeleted', '=', 0,
|
||||
]
|
||||
);
|
||||
return $result->count();
|
||||
}
|
||||
|
||||
public function unreadCount() {
|
||||
if ( empty( App::$activeUser->ID ) ) {
|
||||
return 0;
|
||||
}
|
||||
return $this->getUnreadCount( App::$activeUser->ID );
|
||||
}
|
||||
|
||||
public function hasPermission( $id ) {
|
||||
if ( !Check::id( $id ) ) {
|
||||
Debug::info( 'Invalid ID' );
|
||||
return false;
|
||||
}
|
||||
$messageData = self::$db->get( 'messages', [ 'ID', '=', $id ] );
|
||||
if ( $messageData->count() == 0 ) {
|
||||
Debug::info( 'Message not found.' );
|
||||
return false;
|
||||
}
|
||||
$message = $messageData->first();
|
||||
if ( $message->userTo != App::$activeUser->ID && $message->userFrom != App::$activeUser->ID ) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Marks a message as read. This is setup to only work
|
||||
* if the message was sent to the active user.
|
||||
*
|
||||
* @param int - The message ID you are marking as read.
|
||||
* @return bool
|
||||
*/
|
||||
public function markRead( $id ) {
|
||||
if ( !Check::id( $id ) ) {
|
||||
Debug::info( 'Invalid ID' );
|
||||
return false;
|
||||
}
|
||||
$result = self::$db->get( $this->tableName, [ 'ID', '=', $id, 'AND', 'userTo', '=', App::$activeUser->ID, 'AND', 'isRead', '=', '0' ] );
|
||||
if ( $result->count() == 0 ) {
|
||||
Debug::info( 'Failed to mark message as read.' );
|
||||
return false;
|
||||
}
|
||||
if ( !self::$db->update( $this->tableName, $id, [ 'isRead' => 1 ] ) ) {
|
||||
Debug::error( 'Failed to mark message as read.' );
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public function newMessageReply( $id, $message ) {
|
||||
if ( !$this->hasPermission( $id ) ) {
|
||||
Debug::info( 'Permission Denied.' );
|
||||
return false;
|
||||
}
|
||||
$messageData = self::$db->get( $this->tableName, [ 'ID', '=', $id ] )->first();
|
||||
if ( $messageData->userTo == App::$activeUser->ID ) {
|
||||
$recipient = $messageData->userFrom;
|
||||
} else {
|
||||
$recipient = $messageData->userTo;
|
||||
}
|
||||
if ( $recipient === App::$activeUser->ID ) {
|
||||
Debug::info( 'Cannot send messages to yourself' );
|
||||
return false;
|
||||
}
|
||||
if ( !self::$db->update( $this->tableName, $messageData->ID, [ 'lastReply' => time() ] ) ) {
|
||||
new CustomException( 'messagesReplyUpdate' );
|
||||
return false;
|
||||
}
|
||||
$fields = [
|
||||
'senderDeleted' => 0,
|
||||
'recieverDeleted' => 0,
|
||||
'isRead' => 0,
|
||||
'userTo' => $recipient,
|
||||
'userFrom' => App::$activeUser->ID,
|
||||
'message' => $message,
|
||||
'subject' => 're: ' . $messageData->subject,
|
||||
'sent' => time(),
|
||||
'parent' => $messageData->ID,
|
||||
'lastReply' => time(),
|
||||
];
|
||||
if ( !self::$db->insert( $this->tableName, $fields ) ) {
|
||||
new CustomException( 'messagesReplySend' );
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public function messageTitle( $id ) {
|
||||
if ( !$this->hasPermission( $id ) ) {
|
||||
Debug::info( 'Permission Denied.' );
|
||||
return false;
|
||||
}
|
||||
$message = self::$db->get( $this->tableName, [ 'ID', '=', $id ] )->first();
|
||||
return $message->subject;
|
||||
}
|
||||
|
||||
/**
|
||||
* Function to delete messages from the DB.
|
||||
*
|
||||
* @param int $data - The ID of the message you are trying to delete.
|
||||
* @todo - done at 5 am after no sleep. This can be simplified a lot, i just wanted a working solution ASAP
|
||||
* @return bool
|
||||
*/
|
||||
public function delete( $data ) {
|
||||
if ( !is_array( $data ) ) {
|
||||
$data = [ $data ];
|
||||
}
|
||||
foreach ( $data as $instance ) {
|
||||
if ( !Check::id( $instance ) ) {
|
||||
$error = true;
|
||||
}
|
||||
if ( !$this->hasPermission( $instance ) ) {
|
||||
Debug::info( 'Permission Denied.' );
|
||||
return false;
|
||||
}
|
||||
$message = self::$db->get( $this->tableName, [ 'ID', '=', $instance ] )->first();
|
||||
if ( $message->userTo == App::$activeUser->ID ) {
|
||||
$fields = [ 'recieverDeleted' => '1' ];
|
||||
} else {
|
||||
$fields = [ 'senderDeleted' => '1' ];
|
||||
}
|
||||
if ( !self::$db->update( $this->tableName, $instance, $fields ) ) {
|
||||
$error = true;
|
||||
}
|
||||
Debug::info( "message Deleted: $instance" );
|
||||
if ( !empty( $end ) ) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if ( !empty( $error ) ) {
|
||||
Debug::info( 'There was an error deleting one or more messages.' );
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
57
app/plugins/messages/plugin.php
Executable file
57
app/plugins/messages/plugin.php
Executable file
@ -0,0 +1,57 @@
|
||||
<?php
|
||||
/**
|
||||
* app/plugins/messages/plugin.php
|
||||
*
|
||||
* This houses all of the main plugin info and functionality.
|
||||
*
|
||||
* @package TP Messages
|
||||
* @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\TheTempusProject as App;
|
||||
use TheTempusProject\Classes\Plugin;
|
||||
use TheTempusProject\Models\Message;
|
||||
use TheTempusProject\Houdini\Classes\Components;
|
||||
use TheTempusProject\Houdini\Classes\Views;
|
||||
|
||||
class Messages extends Plugin {
|
||||
public $pluginName = 'TP Messages';
|
||||
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 messaging system.';
|
||||
public $permissionMatrix = [
|
||||
'sendMessages' => [
|
||||
'pretty' => 'Can send Messages',
|
||||
'default' => false,
|
||||
],
|
||||
];
|
||||
private static $loaded = false;
|
||||
|
||||
public function __construct() {
|
||||
if ( ! self::$loaded ) {
|
||||
$messages = new Message;
|
||||
Components::set( 'MESSAGE_COUNT', $messages->unreadCount() );
|
||||
if ( $messages->unreadCount() > 0 ) {
|
||||
$messageBadge = Views::simpleView( 'messages.badge' );
|
||||
} else {
|
||||
$messageBadge = '';
|
||||
}
|
||||
Components::set( 'MBADGE', $messageBadge );
|
||||
if ( App::$isLoggedIn ) {
|
||||
Components::set( 'RECENT_MESSAGES', Views::simpleView( 'messages.nav.recentMessagesDropdown', $messages->getInbox( 5 ) ) );
|
||||
} else {
|
||||
Components::set( 'RECENT_MESSAGES', '' );
|
||||
}
|
||||
App::$topNavRight .= '{RECENT_MESSAGES}';
|
||||
App::$topNavRightDropdown .= '<li><a href="{ROOT_URL}messages" class="dropdown-item"><i class="fa fa-fw fa-envelope"></i> Inbox {MBADGE}</a></li>';
|
||||
self::$loaded = true;
|
||||
}
|
||||
parent::__construct();
|
||||
}
|
||||
}
|
1
app/plugins/messages/views/badge.html
Executable file
1
app/plugins/messages/views/badge.html
Executable file
@ -0,0 +1 @@
|
||||
<span class="badge bg-danger rounded-pill">{MESSAGE_COUNT}</span>
|
73
app/plugins/messages/views/create.html
Executable file
73
app/plugins/messages/views/create.html
Executable file
@ -0,0 +1,73 @@
|
||||
<div class="m-2 m-lg-4">
|
||||
<div class="col-12 mx-5 col-sm-10 col-lg-8 mx-auto p-4 rounded shadow-sm context-main-bg">
|
||||
<nav aria-label="breadcrumb">
|
||||
<ol class="breadcrumb">
|
||||
<li class="breadcrumb-item">
|
||||
<a href="{ROOT_URL}messages" class="text-decoration-none">
|
||||
Messages
|
||||
</a>
|
||||
</li>
|
||||
<li class="breadcrumb-item active" aria-current="page">
|
||||
Create
|
||||
</li>
|
||||
</ol>
|
||||
</nav>
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-6 col-lg-6">
|
||||
<form method="post" class="needs-validation">
|
||||
<legend class="mb-3">New Message</legend>
|
||||
<fieldset>
|
||||
<!-- To User Field -->
|
||||
<div class="mb-3 row">
|
||||
<label for="toUser" class="col-sm-6 col-form-label">To:</label>
|
||||
<div class="col-sm-6">
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
name="toUser"
|
||||
id="toUser"
|
||||
value="{prepopuser}"
|
||||
required>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Subject Field -->
|
||||
<div class="mb-3 row">
|
||||
<label for="subject" class="col-sm-6 col-form-label">Subject:</label>
|
||||
<div class="col-sm-6">
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
name="subject"
|
||||
id="subject"
|
||||
required>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Message Field -->
|
||||
<div class="mb-3">
|
||||
<label for="message" class="form-label">Message:</label>
|
||||
<textarea
|
||||
class="form-control"
|
||||
name="message"
|
||||
id="message"
|
||||
rows="6"
|
||||
maxlength="2000"
|
||||
required></textarea>
|
||||
</div>
|
||||
</fieldset>
|
||||
<input type="hidden" name="token" value="{TOKEN}">
|
||||
<div class="text-center text-md-start">
|
||||
<button
|
||||
type="submit"
|
||||
name="submit"
|
||||
value="submit"
|
||||
class="btn btn-primary btn-lg">
|
||||
Send
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
40
app/plugins/messages/views/inbox.html
Executable file
40
app/plugins/messages/views/inbox.html
Executable file
@ -0,0 +1,40 @@
|
||||
<h2>Inbox</h2>
|
||||
<form action="{ROOT_URL}messages/delete" method="post">
|
||||
<table class="table table-striped text-center">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width: 15%">From</th>
|
||||
<th style="width: 40%">Subject</th>
|
||||
<th style="width: 20%">Last Reply</th>
|
||||
<th style="width: 10%"></th>
|
||||
<th style="width: 10%"></th>
|
||||
<th style="width: 5%">
|
||||
<INPUT type="checkbox" onchange="checkAll(this)" name="check.t" value="T_[]"/>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{LOOP}
|
||||
<tr {unreadBadge}>
|
||||
<td><a href="{ROOT_URL}home/profile/{userFrom}" class="text-decoration-none">{userFromPretty}</a></td>
|
||||
<td><a href="{ROOT_URL}messages/view/{ID}" class="text-decoration-none">{subject}</a></td>
|
||||
<td>{DTC date}{lastReply}{/DTC}</td>
|
||||
<td><a href="{ROOT_URL}messages/read/{ID}" class="btn btn-sm btn-info"><i class="fa-solid fa-envelope-open"></i></a></td>
|
||||
<td><a href="{ROOT_URL}messages/delete/{ID}" class="btn btn-sm btn-danger"><i class="fa fa-fw fa-trash"></i></a></td>
|
||||
<td>
|
||||
<input type="checkbox" value="{ID}" name="T_[]">
|
||||
</td>
|
||||
</tr>
|
||||
{/LOOP}
|
||||
{ALT}
|
||||
<tr>
|
||||
<td align="center" colspan="6">
|
||||
No Messages.
|
||||
</td>
|
||||
</tr>
|
||||
{/ALT}
|
||||
</tbody>
|
||||
</table>
|
||||
<button name="submit" value="submit" type="submit" class="btn btn-sm btn-danger"><i class="fa fa-fw fa-trash"></i></button>
|
||||
<a href="{ROOT_URL}messages/create" class="btn btn-sm btn-primary">New message</a>
|
||||
</form>
|
10
app/plugins/messages/views/index.html
Normal file
10
app/plugins/messages/views/index.html
Normal file
@ -0,0 +1,10 @@
|
||||
<div class="m-2 m-lg-4">
|
||||
<div class="col-12 col-sm-10 col-lg-8 mx-auto p-md-2 p-lg-4 rounded shadow-sm context-main-bg context-main">
|
||||
<div class="my-3 p-3">
|
||||
{message_inbox}
|
||||
</div>
|
||||
<div class="my-3 p-3">
|
||||
{message_outbox}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
53
app/plugins/messages/views/message.html
Executable file
53
app/plugins/messages/views/message.html
Executable file
@ -0,0 +1,53 @@
|
||||
<div class="m-2 m-lg-4">
|
||||
<div class="col-12 mx-5 col-sm-10 col-lg-8 mx-auto p-4 rounded shadow-sm context-main-bg context-main">
|
||||
<nav aria-label="breadcrumb">
|
||||
<ol class="breadcrumb">
|
||||
<li class="breadcrumb-item">
|
||||
<a href="{ROOT_URL}messages" class="text-decoration-none">
|
||||
Messages
|
||||
</a>
|
||||
</li>
|
||||
<li class="breadcrumb-item active" aria-current="page">
|
||||
View
|
||||
</li>
|
||||
</ol>
|
||||
</nav>
|
||||
<div class="card panel-primary col-lg-8 mx-auto text-center">
|
||||
{LOOP}
|
||||
{SINGLE}
|
||||
<div class="card-header context-second-bg">
|
||||
<p class="card-title context-main text-center h3">{subject}</p>
|
||||
</div>
|
||||
{/SINGLE}
|
||||
<div class="card-body context-other-bg">
|
||||
<div class="row">
|
||||
<div class="col-4 col-lg-3 text-center">
|
||||
<a href="{ROOT_URL}home/profile/{userFrom}" class="text-decoration-none">{userFromPretty}</a><br>
|
||||
<img alt="User Pic" src="{ROOT_URL}{fromAvatar}" class="img-circle img-fluid mt-2">
|
||||
</div>
|
||||
<div class="col-8 col-lg-9 text-start">
|
||||
{message}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-footer context-second-bg">
|
||||
{ADMIN}
|
||||
<div class="d-flex justify-content-between">
|
||||
<div class="">
|
||||
{ID}
|
||||
</div>
|
||||
<div class="">
|
||||
{DTC}{sent}{/DTC}
|
||||
</div>
|
||||
</div>
|
||||
{/ADMIN}
|
||||
</div>
|
||||
{/LOOP}
|
||||
</div>
|
||||
<form action="{ROOT_URL}messages/reply" method="post">
|
||||
<input type="hidden" name="token" value="{TOKEN}">
|
||||
<input type="hidden" name="messageID" value="{PID}">
|
||||
<button name="submit" value="reply" type="submit" class="btn btn-md btn-primary my-4">Reply</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
46
app/plugins/messages/views/nav/recentMessagesDropdown.html
Executable file
46
app/plugins/messages/views/nav/recentMessagesDropdown.html
Executable file
@ -0,0 +1,46 @@
|
||||
<div class="dropdown nav-item mx-2 my-2 my-lg-0">
|
||||
<a
|
||||
href="#"
|
||||
class="d-flex align-items-center text-white text-decoration-none dropdown-toggle"
|
||||
id="messagesDropdown"
|
||||
data-bs-toggle="dropdown"
|
||||
aria-haspopup="true"
|
||||
aria-expanded="false">
|
||||
<i class="fa fa-fw fa-envelope"></i><span class="">{MBADGE}</span>
|
||||
</a>
|
||||
<ul class="dropdown-menu dropdown-menu-dark dropdown-menu-end text-small shadow" aria-labelledby="messagesDropdown">
|
||||
{LOOP}
|
||||
<!-- Message Item -->
|
||||
<li>
|
||||
<a href="{ROOT_URL}messages/view/{ID}" class="dropdown-item">
|
||||
<div class="d-flex">
|
||||
<h5 class="media-heading text-start">
|
||||
<img class="" style="width: 40px;" src="{ROOT_URL}{fromAvatar}" alt="">
|
||||
<strong>{userFrom}</strong>
|
||||
</h5>
|
||||
<div class="text-end">
|
||||
<div class="media-body">
|
||||
<p class="small text-muted mb-1"><i class="fa fa-clock-o me-1"></i> {DTC}{lastReply}{/DTC}</p>
|
||||
<span>{summary}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</li>
|
||||
{/LOOP}
|
||||
{ALT}
|
||||
<li class="px-3 text-center">
|
||||
<strong>No Messages</strong>
|
||||
</li>
|
||||
{/ALT}
|
||||
<!-- Footer -->
|
||||
<li>
|
||||
<hr class="dropdown-divider">
|
||||
</li>
|
||||
<li>
|
||||
<a href="/messages" class="dropdown-item text-center">
|
||||
Read All New Messages
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
37
app/plugins/messages/views/outbox.html
Executable file
37
app/plugins/messages/views/outbox.html
Executable file
@ -0,0 +1,37 @@
|
||||
<h2>Outbox</h2>
|
||||
<form action="{ROOT_URL}messages/delete" method="post">
|
||||
<table class="table table-striped text-center">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width: 20%">To</th>
|
||||
<th style="width: 40%">Subject</th>
|
||||
<th style="width: 20%">Sent</th>
|
||||
<th style="width: 10%"></th>
|
||||
<th style="width: 10%">
|
||||
<INPUT type="checkbox" onchange="checkAll(this)" name="check.e" value="F_[]"/>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{LOOP}
|
||||
<tr>
|
||||
<td><a href="{ROOT_URL}home/profile/{userTo}" class="text-decoration-none">{userToPretty}</a></td>
|
||||
<td><a href="{ROOT_URL}messages/view/{ID}" class="text-decoration-none">{subject}</a></td>
|
||||
<td>{DTC date}{sent}{/DTC}</td>
|
||||
<td><a href="{ROOT_URL}messages/delete/{ID}" class="btn btn-sm btn-danger"><i class="fa fa-fw fa-trash"></i></a></td>
|
||||
<td>
|
||||
<input type="checkbox" value="{ID}" name="F_[]">
|
||||
</td>
|
||||
</tr>
|
||||
{/LOOP}
|
||||
{ALT}
|
||||
<tr>
|
||||
<td align="center" colspan="6">
|
||||
No Messages
|
||||
</td>
|
||||
</tr>
|
||||
{/ALT}
|
||||
</tbody>
|
||||
</table>
|
||||
<button name="submit" value="submit" type="submit" class="btn btn-sm btn-danger"><i class="fa fa-fw fa-trash"></i></button>
|
||||
</form>
|
18
app/plugins/messages/views/reply.html
Executable file
18
app/plugins/messages/views/reply.html
Executable file
@ -0,0 +1,18 @@
|
||||
<div class="context-main context-main-bg col-8 offset-2 my-3 p-3">
|
||||
<form method="post">
|
||||
<legend class="text-center">Reply</legend>
|
||||
<fieldset>
|
||||
<div class="form-group">
|
||||
<div class="col-6 offset-3">
|
||||
<label for="message" class="control-label">Message:</label>
|
||||
<textarea class="form-control" name="message" maxlength="2000" rows="10" cols="50" id="message"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
<input type="hidden" name="messageID" value="{messageID}">
|
||||
<input type="hidden" name="token" value="{TOKEN}">
|
||||
<div class="text-center">
|
||||
<button name="submit" value="submit" type="submit" class="btn btn-lg btn-primary center-block my-3">Send</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
1
app/plugins/messages/views/unreadBadge.html
Executable file
1
app/plugins/messages/views/unreadBadge.html
Executable file
@ -0,0 +1 @@
|
||||
class="bg-info"
|
85
app/plugins/notifications/controllers/admin/notifications.php
Executable file
85
app/plugins/notifications/controllers/admin/notifications.php
Executable file
@ -0,0 +1,85 @@
|
||||
<?php
|
||||
/**
|
||||
* app/plugins/notifications/controllers/admin/notifications.php
|
||||
*
|
||||
* This is the notifications admin controller.
|
||||
*
|
||||
* @package TP Notifications
|
||||
* @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\Notification as NotificationsModel;
|
||||
use TheTempusProject\Models\Group;
|
||||
use TheTempusProject\Models\User;
|
||||
use TheTempusProject\Houdini\Classes\Forms;
|
||||
use TheTempusProject\Classes\Forms as FormChecker;
|
||||
|
||||
class Notifications extends AdminController {
|
||||
protected static $notifications;
|
||||
public static $user;
|
||||
public static $group;
|
||||
|
||||
public function __construct() {
|
||||
parent::__construct();
|
||||
self::$title = 'Admin - Notifications';
|
||||
self::$notifications = new NotificationsModel;
|
||||
self::$user = new User;
|
||||
self::$group = new Group;
|
||||
$view = Navigation::activePageSelect( 'nav.admin', '/admin/Notifications' );
|
||||
Components::set( 'ADMINNAV', $view );
|
||||
}
|
||||
|
||||
public function index( $data = null ) {
|
||||
// this is just a simple form to allow admins to send notifications
|
||||
return $this->create();
|
||||
}
|
||||
|
||||
public function create() {
|
||||
$select = Forms::getSelectHtml(
|
||||
'groupSelect',
|
||||
self::$group->listGroupsSimple( true )
|
||||
);
|
||||
Components::set( 'groupSelect', $select );
|
||||
if ( ! Input::exists( 'submit' ) ) {
|
||||
return Views::view( 'notifications.admin.send' );
|
||||
}
|
||||
if ( ! FormChecker::check( 'createNotification' ) ) {
|
||||
Issues::add( 'error', [ 'There was an error with your request.' => Check::userErrors() ] );
|
||||
return Views::view( 'notifications.admin.send' );
|
||||
}
|
||||
if ( Input::exists( 'expires' ) ) {
|
||||
$expiresAt = time() + intval( Input::post( 'expires' ) );
|
||||
} else {
|
||||
$expiresAt = 0;
|
||||
}
|
||||
|
||||
if ( Input::exists( 'groupSelect' ) && Input::post( 'groupSelect' ) != 0 && Input::post( 'groupSelect' ) != 'all' ) {
|
||||
$list = self::$group->listMembers( Input::post( 'groupSelect' ) );
|
||||
} else {
|
||||
$list = self::$user->userList();
|
||||
}
|
||||
|
||||
$results = [];
|
||||
foreach ( $list as $recipient ) {
|
||||
$results[] = self::$notifications->create( Input::post( 'notification' ), 'Admin Issued', $recipient->ID, $expiresAt );
|
||||
}
|
||||
|
||||
// $result = self::$notifications->create( Input::post( 'notification' ), 'Admin Issued', XXXXXXX_user_idsXXXXXX, $expiresAt );
|
||||
if ( ! empty( $results ) ) {
|
||||
Issues::add( 'success', 'Your notification has been sent.' );
|
||||
} else {
|
||||
Issues::add( 'error', [ 'There was an unknown error submitting your data.' => Check::userErrors() ] );
|
||||
}
|
||||
Views::view( 'notifications.admin.send' );
|
||||
}
|
||||
}
|
79
app/plugins/notifications/controllers/notifications.php
Executable file
79
app/plugins/notifications/controllers/notifications.php
Executable file
@ -0,0 +1,79 @@
|
||||
<?php
|
||||
/**
|
||||
* app/plugins/notifications/controllers/notifications.php
|
||||
*
|
||||
* This is the home controller for the notifications plugin.
|
||||
*
|
||||
* @package TP Notifications
|
||||
* @version 3.0
|
||||
* @author Joey Kimsey <Joey@thetempusproject.com>
|
||||
* @link https://TheTempusProject.com
|
||||
* @license https://opensource.org/licenses/MIT [MIT LICENSE]
|
||||
*/
|
||||
namespace TheTempusProject\Controllers;
|
||||
|
||||
use TheTempusProject\Houdini\Classes\Views;
|
||||
use TheTempusProject\Houdini\Classes\Issues;
|
||||
use TheTempusProject\Classes\Controller;
|
||||
use TheTempusProject\Models\Notification as NotificationsModel;
|
||||
use TheTempusProject\TheTempusProject as App;
|
||||
use TheTempusProject\Hermes\Functions\Redirect;
|
||||
use TheTempusProject\Bedrock\Functions\Session;
|
||||
|
||||
class Notifications extends Controller {
|
||||
protected static $notifications;
|
||||
|
||||
public function __construct() {
|
||||
parent::__construct();
|
||||
self::$notifications = new NotificationsModel;
|
||||
self::$title = 'Notifications - {SITENAME}';
|
||||
self::$pageDescription = 'Your recent notifications';
|
||||
if ( ! App::$isLoggedIn ) {
|
||||
Session::flash( 'error', 'You do not have permission to access this page.' );
|
||||
return Redirect::home();
|
||||
}
|
||||
}
|
||||
|
||||
public function index() {
|
||||
$notifications = self::$notifications->getByUser( 10 );
|
||||
Views::view( 'notifications.list', $notifications );
|
||||
}
|
||||
|
||||
public function markRead( $id = null ) {
|
||||
$notification = self::$notifications->findById( $id );
|
||||
if ( $notification == false ) {
|
||||
Issues::add( 'error', 'Notification not found.' );
|
||||
return $this->index();
|
||||
}
|
||||
if ( $notification->userID != App::$activeUser->ID ) {
|
||||
Issues::add( 'error', 'You do not have permission to modify this notification.' );
|
||||
return $this->index();
|
||||
}
|
||||
$result = self::$notifications->markSeen( $id );
|
||||
if ( $result == true ) {
|
||||
Issues::add( 'success', 'Notification marked as read.' );
|
||||
} else {
|
||||
Issues::add( 'notice', 'There was an problem updating your notification.' );
|
||||
}
|
||||
return $this->index();
|
||||
}
|
||||
|
||||
public function delete( $id = null ) {
|
||||
$notification = self::$notifications->findById( $id );
|
||||
if ( $notification == false ) {
|
||||
Issues::add( 'error', 'Notification not found.' );
|
||||
return $this->index();
|
||||
}
|
||||
if ( $notification->userID != App::$activeUser->ID ) {
|
||||
Issues::add( 'error', 'You do not have permission to modify this notification.' );
|
||||
return $this->index();
|
||||
}
|
||||
$result = self::$notifications->delete( $id );
|
||||
if ( $result == true ) {
|
||||
Issues::add( 'success', 'Notification deleted.' );
|
||||
} else {
|
||||
Issues::add( 'notice', 'There was an problem deleting your notification.' );
|
||||
}
|
||||
return $this->index();
|
||||
}
|
||||
}
|
41
app/plugins/notifications/forms.php
Executable file
41
app/plugins/notifications/forms.php
Executable file
@ -0,0 +1,41 @@
|
||||
<?php
|
||||
/**
|
||||
* app/plugins/notifications/forms.php
|
||||
*
|
||||
* This houses all of the form checking functions for this plugin.
|
||||
*
|
||||
* @package TP Notifications
|
||||
* @version 3.0
|
||||
* @author Joey Kimsey <Joey@thetempusproject.com>
|
||||
* @link https://TheTempusProject.com
|
||||
* @license https://opensource.org/licenses/MIT [MIT LICENSE]
|
||||
*/
|
||||
namespace TheTempusProject\Plugins\Notifications;
|
||||
|
||||
use TheTempusProject\Bedrock\Functions\Input;
|
||||
use TheTempusProject\Bedrock\Functions\Check;
|
||||
use TheTempusProject\Classes\Forms;
|
||||
|
||||
class NotificationForms extends Forms {
|
||||
/**
|
||||
* Adds these functions to the form list.
|
||||
*/
|
||||
public function __construct() {
|
||||
self::addHandler( 'createNotification', __CLASS__, 'createNotification' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the createNotification form.
|
||||
*
|
||||
* @return {bool}
|
||||
*/
|
||||
public static function createNotification() {
|
||||
if ( ! Input::exists( 'notification' ) ) {
|
||||
Check::addUserError( 'You must provide a notification.' );
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
new NotificationForms;
|
146
app/plugins/notifications/models/notification.php
Executable file
146
app/plugins/notifications/models/notification.php
Executable file
@ -0,0 +1,146 @@
|
||||
<?php
|
||||
/**
|
||||
* app/plugins/notifications/models/notification.php
|
||||
*
|
||||
* This class is used for the manipulation of the notifications database table.
|
||||
*
|
||||
* @package TP Notifications
|
||||
* @version 3.0
|
||||
* @author Joey Kimsey <Joey@thetempusproject.com>
|
||||
* @link https://TheTempusProject.com
|
||||
* @license https://opensource.org/licenses/MIT [MIT LICENSE]
|
||||
*/
|
||||
namespace TheTempusProject\Models;
|
||||
|
||||
use TheTempusProject\Bedrock\Functions\Check;
|
||||
use TheTempusProject\Canary\Bin\Canary as Debug;
|
||||
use TheTempusProject\Classes\DatabaseModel;
|
||||
use TheTempusProject\TheTempusProject as App;
|
||||
use TheTempusProject\Houdini\Classes\Views;
|
||||
use TheTempusProject\Canary\Classes\CustomException;
|
||||
|
||||
class Notification extends DatabaseModel {
|
||||
public $tableName = 'notifications';
|
||||
public $databaseMatrix = [
|
||||
[ 'notification', 'text', '' ],
|
||||
[ 'origin', 'varchar', '128' ],
|
||||
[ 'userID', 'int', '11' ],
|
||||
[ 'createdAt', 'int', '11' ],
|
||||
[ 'expiresAt', 'int', '11' ],
|
||||
[ 'deletedAt', 'int', '11' ],
|
||||
[ 'seenAt', 'int', '11' ],
|
||||
];
|
||||
|
||||
public function getUnreadCount( $userID ) {
|
||||
$result = self::$db->get(
|
||||
$this->tableName,
|
||||
[
|
||||
'userID', '=', $userID,
|
||||
'AND',
|
||||
'deletedAt', '=', '0',
|
||||
'AND',
|
||||
'seenAt', '=', '0',
|
||||
'AND',
|
||||
'expiresAt', '<', time(),
|
||||
]
|
||||
);
|
||||
return $result->count();
|
||||
}
|
||||
|
||||
public function create( $notification, $origin, $userID, $expiresAt = '0', $deletedAt = '0', $seenAt = '0' ) {
|
||||
$fields = [
|
||||
'notification' => $notification,
|
||||
'origin' => $origin,
|
||||
'userID' => $userID,
|
||||
'createdAt' => time(),
|
||||
'expiresAt' => $expiresAt,
|
||||
'deletedAt' => $deletedAt,
|
||||
'seenAt' => $seenAt,
|
||||
];
|
||||
if ( !self::$db->insert( $this->tableName, $fields ) ) {
|
||||
Debug::info( 'Events::create - failed to insert to db' );
|
||||
return false;
|
||||
}
|
||||
return self::$db->lastId();
|
||||
}
|
||||
|
||||
public function getByUser( $limit = 0 ) {
|
||||
$whereClause = [
|
||||
'userID', '=', App::$activeUser->ID,
|
||||
'AND',
|
||||
'deletedAt', '=', '0',
|
||||
'AND',
|
||||
'expiresAt', '<', time(),
|
||||
];
|
||||
if ( empty( $limit ) ) {
|
||||
$notifications = self::$db->get( $this->tableName, $whereClause );
|
||||
} else {
|
||||
$notifications = self::$db->get( $this->tableName, $whereClause, 'ID', 'DESC', [0, $limit] );
|
||||
}
|
||||
if ( !$notifications->count() ) {
|
||||
Debug::info( 'Notification:getByUser No Notifications found' );
|
||||
return false;
|
||||
}
|
||||
return $this->filter( $notifications->results() );
|
||||
}
|
||||
|
||||
public function markSeen( $id ) {
|
||||
if ( !Check::id( $id ) ) {
|
||||
Debug::info( 'Notifications: illegal ID.' );
|
||||
return false;
|
||||
}
|
||||
$fields = [
|
||||
'seenAt' => time(),
|
||||
];
|
||||
$notification = self::findById( $id );
|
||||
if ( ! $notification ) {
|
||||
Debug::error( "Notifications: $id not updated" );
|
||||
return false;
|
||||
}
|
||||
if ( !self::$db->update( $this->tableName, $id, $fields ) ) {
|
||||
new CustomException( 'notificationUpdate' );
|
||||
Debug::error( "Notifications: $id not updated" );
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public function delete( $id ) {
|
||||
if ( !Check::id( $id ) ) {
|
||||
Debug::info( 'Notifications: illegal ID.' );
|
||||
return false;
|
||||
}
|
||||
$fields = [
|
||||
'deletedAt' => time(),
|
||||
];
|
||||
if ( !self::$db->update( $this->tableName, $id, $fields ) ) {
|
||||
new CustomException( 'notificationDelete' );
|
||||
Debug::error( "Notifications: $id not updated" );
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public function filter( $messageArray, $filters = [] ) {
|
||||
$out = [];
|
||||
foreach ( $messageArray as $message ) {
|
||||
if ( !is_object( $message ) ) {
|
||||
$message = $messageArray;
|
||||
$end = true;
|
||||
}
|
||||
if ( $message->seenAt == 0 ) {
|
||||
$message->unseenBadge = Views::simpleView( 'notifications.unseenBadge' );
|
||||
$message->markReadLink = '<a href="{ROOT_URL}notifications/markRead/'.$message->ID.'" class="btn btn-sm btn-primary"><i class="fa-solid fa-fw fa-envelope-open"></i></a>';
|
||||
} else {
|
||||
$message->unseenBadge = '';
|
||||
$message->markReadLink = '';
|
||||
}
|
||||
$out[] = (object) $message;
|
||||
if ( !empty( $end ) ) {
|
||||
$out = $out[0];
|
||||
break;
|
||||
}
|
||||
}
|
||||
return $out;
|
||||
}
|
||||
}
|
64
app/plugins/notifications/plugin.php
Executable file
64
app/plugins/notifications/plugin.php
Executable file
@ -0,0 +1,64 @@
|
||||
<?php
|
||||
/**
|
||||
* app/plugins/notifications/plugin.php
|
||||
*
|
||||
* This houses all of the main plugin info and functionality.
|
||||
*
|
||||
* @package TP Notifications
|
||||
* @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\TheTempusProject as App;
|
||||
use TheTempusProject\Classes\Plugin;
|
||||
use TheTempusProject\Models\Notification;
|
||||
use TheTempusProject\Houdini\Classes\Components;
|
||||
use TheTempusProject\Houdini\Classes\Views;
|
||||
|
||||
class Notifications extends Plugin {
|
||||
public $pluginName = 'TP Notifications';
|
||||
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 notification system.';
|
||||
public $permissionMatrix = [
|
||||
'sendNotifications' => [
|
||||
'pretty' => 'Can send notifications',
|
||||
'default' => false,
|
||||
],
|
||||
];
|
||||
public $admin_links = [
|
||||
[
|
||||
'text' => '<i class="fa fa-fw fa-bell"></i> Notify',
|
||||
'url' => '{ROOT_URL}admin/notifications',
|
||||
],
|
||||
];
|
||||
private static $loaded = false;
|
||||
public function __construct( $load = false ) {
|
||||
parent::__construct( $load );
|
||||
if ( $this->checkEnabled() && App::$isLoggedIn ) {
|
||||
$notifications = new Notification;
|
||||
Components::set( 'notificationCount', $notifications->getUnreadCount( App::$activeUser->ID ) );
|
||||
if ( $notifications->getUnreadCount( App::$activeUser->ID ) > 0 ) {
|
||||
$messageBadge = Views::simpleView( 'notifications.badge' );
|
||||
} else {
|
||||
$messageBadge = '';
|
||||
}
|
||||
Components::set( 'NBADGE', $messageBadge );
|
||||
if ( ! self::$loaded ) {
|
||||
if ( App::$isLoggedIn ) {
|
||||
Components::set( 'recentNotifications', Views::simpleView( 'notifications.nav.recentNotificationsDropdown', $notifications->getByUser( 10 ) ) );
|
||||
} else {
|
||||
Components::set( 'recentNotifications', '' );
|
||||
}
|
||||
App::$topNavRight .= '{recentNotifications}';
|
||||
App::$topNavRightDropdown .= '<li><a href="{ROOT_URL}notifications" class="dropdown-item"><i class="fa fa-fw fa-bell"></i> Notifications {NBADGE}</a></li>';
|
||||
self::$loaded = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
61
app/plugins/notifications/views/admin/send.html
Executable file
61
app/plugins/notifications/views/admin/send.html
Executable file
@ -0,0 +1,61 @@
|
||||
<div class="context-main-bg context-main p-3">
|
||||
<legend class="text-center">Send Notification</legend>
|
||||
<hr>
|
||||
{ADMIN_BREADCRUMBS}
|
||||
<form method="post" enctype="multipart/form-data">
|
||||
<fieldset>
|
||||
<!-- Group -->
|
||||
<div class="mb-3 row">
|
||||
<label for="groupSelect" class="col-lg-3 col-form-label text-end">Group:</label>
|
||||
<div class="col-lg-6">
|
||||
{groupSelect}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Recipients -->
|
||||
<div class="mb-3 row">
|
||||
<label for="expiration" class="col-lg-3 col-form-label text-end">Expiration:</label>
|
||||
<div class="col-lg-6">
|
||||
<select class="form-control" name="expiration" id="expiration">
|
||||
<option value="0">forever</option>
|
||||
<option value="1800">30 Minutes</option>
|
||||
<option value="3600">60 Minutes</option>
|
||||
<option value="14400">4 hours</option>
|
||||
<option value="28800">8 hours</option>
|
||||
<option value="43200">12 hours</option>
|
||||
<option value="86400">24 hours</option>
|
||||
<option value="172800">2 days</option>
|
||||
<option value="259200">3 days</option>
|
||||
<option value="432000">5 days</option>
|
||||
<option value="604800">7 days</option>
|
||||
<option value="1209600">2 weeks</option>
|
||||
<option value="1814400">3 weeks</option>
|
||||
<option value="2419200">4 weeks</option>
|
||||
<option value="2592000">1 month</option>
|
||||
<option value="5184000">2 months</option>
|
||||
<option value="7776000">3 months</option>
|
||||
<option value="15552000">6 months</option>
|
||||
<option value="31536000">12 months</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Notification -->
|
||||
<div class="mb-3 row">
|
||||
<label for="notification" class="col-lg-3 col-form-label text-end">Notification:</label>
|
||||
<div class="col-lg-6">
|
||||
<textarea class="form-control" name="notification" id="notification" rows="6" maxlength="2000" required></textarea>
|
||||
<small class="form-text text-muted">Max: 2000 characters</small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Hidden Token -->
|
||||
<input type="hidden" name="token" value="{TOKEN}">
|
||||
</fieldset>
|
||||
|
||||
<!-- Submit Button -->
|
||||
<div class="text-center">
|
||||
<button type="submit" name="submit" value="submit" class="btn btn-primary btn-lg center-block">Send</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
1
app/plugins/notifications/views/badge.html
Executable file
1
app/plugins/notifications/views/badge.html
Executable file
@ -0,0 +1 @@
|
||||
<span class="badge bg-danger rounded-pill">{notificationCount}</span>
|
41
app/plugins/notifications/views/list.html
Executable file
41
app/plugins/notifications/views/list.html
Executable file
@ -0,0 +1,41 @@
|
||||
|
||||
<div class="m-2 m-lg-4">
|
||||
<div class="col-12 mx-5 col-sm-10 col-lg-8 mx-auto p-4 rounded shadow-sm context-main-bg">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-8">
|
||||
<legend class="text-center">Notifications</legend>
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width: 90%"></th>
|
||||
<th style="width: 5%"></th>
|
||||
<th style="width: 5%"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{LOOP}
|
||||
<tr {unseenBadge}>
|
||||
<td class=" context-main">{notification}</td>
|
||||
<td>
|
||||
{markReadLink}
|
||||
</td>
|
||||
<td>
|
||||
<a href="{ROOT_URL}notifications/delete/{ID}" class="btn btn-sm btn-danger">
|
||||
<i class="fa fa-fw fa-trash"></i>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
{/LOOP}
|
||||
{ALT}
|
||||
<tr>
|
||||
<td colspan="3" class="text-center context-main">
|
||||
No Notifications
|
||||
</td>
|
||||
</tr>
|
||||
{/ALT}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
41
app/plugins/notifications/views/nav/recentNotificationsDropdown.html
Executable file
41
app/plugins/notifications/views/nav/recentNotificationsDropdown.html
Executable file
@ -0,0 +1,41 @@
|
||||
<!-- Notifications Dropdown -->
|
||||
<div class="dropdown nav-item mx-2 my-2 my-lg-0">
|
||||
<a
|
||||
href="#"
|
||||
class="d-flex align-items-center text-white text-decoration-none dropdown-toggle"
|
||||
id="notificationsDropdown"
|
||||
data-bs-toggle="dropdown"
|
||||
aria-haspopup="true"
|
||||
aria-expanded="false">
|
||||
<i class="fa fa-fw fa-bell"></i><span class="">{NBADGE}</span>
|
||||
</a>
|
||||
<ul class="dropdown-menu dropdown-menu-dark dropdown-menu-end text-small shadow" aria-labelledby="notificationsDropdown">
|
||||
{LOOP}
|
||||
<!-- Notification Item -->
|
||||
<li>
|
||||
<a href="{ROOT_URL}notifications" class="dropdown-item">
|
||||
<div class="media">
|
||||
<div class="media-body">
|
||||
<p class="small text-muted mb-1"><i class="fa fa-clock-o me-1"></i> {DTC}{createdAt}{/DTC}</p>
|
||||
<span>{notification}</span>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</li>
|
||||
{/LOOP}
|
||||
{ALT}
|
||||
<li class="px-3 text-center">
|
||||
<strong>No Notifications</strong>
|
||||
</li>
|
||||
{/ALT}
|
||||
<!-- Footer -->
|
||||
<li>
|
||||
<hr class="dropdown-divider">
|
||||
</li>
|
||||
<li>
|
||||
<a href="/notifications" class="dropdown-item text-center">
|
||||
See All Notifications
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
1
app/plugins/notifications/views/unseenBadge.html
Executable file
1
app/plugins/notifications/views/unseenBadge.html
Executable file
@ -0,0 +1 @@
|
||||
class="bg-info"
|
65
app/plugins/subscribe/controllers/admin/subscriptions.php
Executable file
65
app/plugins/subscribe/controllers/admin/subscriptions.php
Executable file
@ -0,0 +1,65 @@
|
||||
<?php
|
||||
/**
|
||||
* app/plugins/subscribe/controllers/admin/subscriptions.php
|
||||
*
|
||||
* This is the subscriptions admin controller.
|
||||
*
|
||||
* @package TP Subscribe
|
||||
* @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\Bedrock\Functions\Check;
|
||||
use TheTempusProject\Houdini\Classes\Issues;
|
||||
use TheTempusProject\Houdini\Classes\Views;
|
||||
use TheTempusProject\Classes\AdminController;
|
||||
use TheTempusProject\Models\Subscribe;
|
||||
use TheTempusProject\Classes\Forms;
|
||||
|
||||
class Subscriptions extends AdminController {
|
||||
public static $subscribe;
|
||||
|
||||
public function __construct() {
|
||||
parent::__construct();
|
||||
self::$title = 'Admin - Email Subscribers';
|
||||
self::$subscribe = new Subscribe;
|
||||
}
|
||||
|
||||
public function delete( $data = null ) {
|
||||
if ( $data == null ) {
|
||||
if ( Input::exists( 'submit' ) ) {
|
||||
$data = Input::post( 'S_' );
|
||||
}
|
||||
}
|
||||
if ( !self::$subscribe->delete( (array) $data ) ) {
|
||||
Issues::add( 'error', 'There was an error with your request, please try again.' );
|
||||
} else {
|
||||
Issues::add( 'success', 'Subscriber removed.' );
|
||||
}
|
||||
$this->index();
|
||||
}
|
||||
|
||||
public function add( $data = null ) {
|
||||
if ( !Input::exists( 'submit' ) ) {
|
||||
return Views::view( 'subscribe.admin.add' );
|
||||
}
|
||||
if ( !Forms::check( 'subscribe' ) ) {
|
||||
Issues::add( 'error', [ 'There was an error with your request.' => Check::userErrors() ] );
|
||||
return $this->index();
|
||||
}
|
||||
if ( !self::$subscribe->add( Input::post( 'email' ) ) ) {
|
||||
Issues::add( 'error', 'There was an error with your request, please try again.' );
|
||||
return $this->index();
|
||||
}
|
||||
Issues::add( 'success', 'Subscriber added.' );
|
||||
$this->index();
|
||||
}
|
||||
|
||||
public function index( $data = null ) {
|
||||
Views::view( 'subscribe.admin.list', self::$subscribe->listPaginated() );
|
||||
}
|
||||
}
|
79
app/plugins/subscribe/controllers/subscribe.php
Executable file
79
app/plugins/subscribe/controllers/subscribe.php
Executable file
@ -0,0 +1,79 @@
|
||||
<?php
|
||||
/**
|
||||
* app/plugins/subscribe/controllers/subscribe.php
|
||||
*
|
||||
* This is the home controller for the subscribe plugin.
|
||||
*
|
||||
* @package TP Subscribe
|
||||
* @version 3.0
|
||||
* @author Joey Kimsey <Joey@thetempusproject.com>
|
||||
* @link https://TheTempusProject.com
|
||||
* @license https://opensource.org/licenses/MIT [MIT LICENSE]
|
||||
*/
|
||||
namespace TheTempusProject\Controllers;
|
||||
|
||||
use TheTempusProject\Classes\Controller;
|
||||
use TheTempusProject\Classes\Forms;
|
||||
use TheTempusProject\Hermes\Functions\Redirect;
|
||||
use TheTempusProject\Bedrock\Functions\Session;
|
||||
use TheTempusProject\Bedrock\Functions\Input;
|
||||
use TheTempusProject\Bedrock\Functions\Check;
|
||||
use TheTempusProject\Classes\Email;
|
||||
use TheTempusProject\Houdini\Classes\Issues;
|
||||
use TheTempusProject\Houdini\Classes\Views;
|
||||
use TheTempusProject\Models\Subscribe as SubscribeModel;
|
||||
|
||||
class Subscribe extends Controller {
|
||||
private static $subscribe;
|
||||
|
||||
public function __construct() {
|
||||
parent::__construct();
|
||||
self::$subscribe = new SubscribeModel;
|
||||
}
|
||||
|
||||
public function index() {
|
||||
self::$title = 'Subscribe - {SITENAME}';
|
||||
self::$pageDescription = 'We are always publishing great content and keeping our members up to date. If you would like to join our list, you can subscribe here.';
|
||||
if ( !Input::exists( 'email' ) ) {
|
||||
return Views::view( 'subscribe.subscribe' );
|
||||
}
|
||||
if ( !Forms::check( 'subscribe' ) ) {
|
||||
Issues::add( 'error', [ 'There was an error with your form.' => Check::userErrors() ] );
|
||||
return Views::view( 'subscribe.subscribe' );
|
||||
}
|
||||
if ( !self::$subscribe->add( Input::post( 'email' ) ) ) {
|
||||
Issues::add( 'error', 'There was an error with your request, please try again.' );
|
||||
return Views::view( 'subscribe.subscribe' );
|
||||
}
|
||||
$data = self::$subscribe->get( Input::post( 'email' ) );
|
||||
Email::send( Input::post( 'email' ), 'subscribe', $data->confirmationCode, ['template' => true] );
|
||||
Issues::add( 'success', 'You have successfully been subscribed to our mailing list.' );
|
||||
}
|
||||
|
||||
public function unsubscribe( $email = null, $code = null ) {
|
||||
self::$title = '{SITENAME}';
|
||||
self::$pageDescription = '';
|
||||
if ( !empty( $email ) && !empty( $code ) ) {
|
||||
if ( self::$subscribe->unsubscribe( $email, $code ) ) {
|
||||
return Issues::add( 'success', 'You have been successfully unsubscribed from receiving further mailings.' );
|
||||
}
|
||||
Issues::add( 'error', 'There was an error with your request.' );
|
||||
return Views::view( 'subscribe.unsubscribe' );
|
||||
}
|
||||
if ( !Input::exists( 'submit' ) ) {
|
||||
return Views::view( 'subscribe.unsubscribe' );
|
||||
}
|
||||
if ( !Forms::check( 'unsubscribe' ) ) {
|
||||
Issues::add( 'error', [ 'There was an error with your request.' => Check::userErrors() ] );
|
||||
return Views::view( 'subscribe.unsubscribe' );
|
||||
}
|
||||
$data = self::$subscribe->get( Input::post( 'email' ) );
|
||||
if ( empty( $data ) ) {
|
||||
Issues::add( 'notice', 'There was an error with your request.' );
|
||||
return Views::view( 'subscribe.unsubscribe' );
|
||||
}
|
||||
Email::send( Input::post( 'email' ), 'unsubInstructions', $data->confirmationCode, ['template' => true] );
|
||||
Session::flash( 'success', 'An email with instructions on how to unsubscribe has been sent to your email.' );
|
||||
Redirect::to( 'home/index' );
|
||||
}
|
||||
}
|
73
app/plugins/subscribe/forms.php
Executable file
73
app/plugins/subscribe/forms.php
Executable file
@ -0,0 +1,73 @@
|
||||
<?php
|
||||
/**
|
||||
* app/plugins/subscribe/forms.php
|
||||
*
|
||||
* This houses all of the form checking functions for this plugin.
|
||||
*
|
||||
* @package TP Subscribe
|
||||
* @version 3.0
|
||||
* @author Joey Kimsey <Joey@thetempusproject.com>
|
||||
* @link https://TheTempusProject.com
|
||||
* @license https://opensource.org/licenses/MIT [MIT LICENSE]
|
||||
*/
|
||||
namespace TheTempusProject\Plugins\Subscribe;
|
||||
|
||||
use TheTempusProject\Bedrock\Functions\Input;
|
||||
use TheTempusProject\Classes\Forms;
|
||||
|
||||
class SubscribeForms extends Forms {
|
||||
/**
|
||||
* Adds these functions to the form list.
|
||||
*/
|
||||
public function __construct() {
|
||||
self::addHandler( 'subscribe', __CLASS__, 'subscribe' );
|
||||
self::addHandler( 'unsubscribe', __CLASS__, 'unsubscribe' );
|
||||
self::addHandler( 'newSubscription', __CLASS__, 'newSubscription' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the subscribe form.
|
||||
*
|
||||
* @return {bool}
|
||||
*/
|
||||
public static function subscribe() {
|
||||
if ( !self::email( Input::post( 'email' ) ) ) {
|
||||
self::addUserError( 'Invalid email.' );
|
||||
return false;
|
||||
}
|
||||
if ( !self::token() ) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the unsubscribe form.
|
||||
*
|
||||
* @return {bool}
|
||||
*/
|
||||
public static function unsubscribe() {
|
||||
if ( !self::email( Input::post( 'email' ) ) ) {
|
||||
self::addUserError( 'Invalid email.' );
|
||||
return false;
|
||||
}
|
||||
if ( !self::token() ) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the new subscription form.
|
||||
*
|
||||
* @return {bool}
|
||||
*/
|
||||
public static function newSubscription() {
|
||||
if ( !self::token() ) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
new SubscribeForms;
|
97
app/plugins/subscribe/models/subscribe.php
Executable file
97
app/plugins/subscribe/models/subscribe.php
Executable file
@ -0,0 +1,97 @@
|
||||
<?php
|
||||
/**
|
||||
* app/plugins/subscribe/models/subscribe.php
|
||||
*
|
||||
* This class is used for the manipulation of the subscribers database table.
|
||||
*
|
||||
* @package TP Subscribe
|
||||
* @version 3.0
|
||||
* @author Joey Kimsey <Joey@thetempusproject.com>
|
||||
* @link https://TheTempusProject.com
|
||||
* @license https://opensource.org/licenses/MIT [MIT LICENSE]
|
||||
*/
|
||||
namespace TheTempusProject\Models;
|
||||
|
||||
use TheTempusProject\Bedrock\Functions\Check;
|
||||
use TheTempusProject\Bedrock\Functions\Code;
|
||||
use TheTempusProject\Canary\Bin\Canary as Debug;
|
||||
use TheTempusProject\Classes\DatabaseModel;
|
||||
|
||||
class Subscribe extends DatabaseModel {
|
||||
public $tableName = 'subscribers';
|
||||
public $databaseMatrix = [
|
||||
[ 'confirmed', 'int', '1' ],
|
||||
[ 'subscribed', 'int', '10' ],
|
||||
[ 'confirmationCode', 'varchar', '80' ],
|
||||
[ 'email', 'varchar', '75' ],
|
||||
];
|
||||
|
||||
/**
|
||||
* Adds an email to the subscribers database.
|
||||
*
|
||||
* @param string $email - the email you are trying to add.
|
||||
* @return bool
|
||||
*/
|
||||
public function add( $email ) {
|
||||
if ( !Check::email( $email ) ) {
|
||||
return false;
|
||||
}
|
||||
$alreadyExists = self::$db->get( $this->tableName, ['email', '=', $email] );
|
||||
|
||||
if ( $alreadyExists->error() ) {
|
||||
Debug::info( 'Error querying database: ' . $alreadyExists->errorMessage() );
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( $alreadyExists->count() ) {
|
||||
Debug::info( 'email already subscribed.' );
|
||||
return false;
|
||||
}
|
||||
$fields = [
|
||||
'email' => $email,
|
||||
'confirmationCode' => Code::genConfirmation(),
|
||||
'confirmed' => 0,
|
||||
'subscribed' => time(),
|
||||
];
|
||||
self::$db->insert( $this->tableName, $fields );
|
||||
return self::$db->lastId();
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes an email from the subscribers database.
|
||||
*
|
||||
* @param string $email - The email you are trying to remove.
|
||||
* @param string $code - The confirmation code to unsubscribe.
|
||||
* @return boolean
|
||||
*/
|
||||
public function unsubscribe( $email, $code ) {
|
||||
if ( !Check::email( $email ) ) {
|
||||
return false;
|
||||
}
|
||||
$user = self::$db->get( $this->tableName, ['email', '=', $email, 'AND', 'confirmationCode', '=', $code] );
|
||||
if ( !$user->count() ) {
|
||||
Debug::info( __METHOD__ . ' - Cannot find subscriber with that email and code' );
|
||||
return false;
|
||||
}
|
||||
self::$db->delete( $this->tableName, ['ID', '=', $user->first()->ID] );
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a subscriber object for the provided email address.
|
||||
*
|
||||
* @param string $email - An email address to look for.
|
||||
* @return bool|object - Depending on success.
|
||||
*/
|
||||
public function get( $email ) {
|
||||
if ( !Check::email( $email ) ) {
|
||||
return false;
|
||||
}
|
||||
$data = self::$db->get( $this->tableName, ['email', '=', $email] );
|
||||
if ( !$data->count() ) {
|
||||
Debug::info( __METHOD__ . ' - Email not found' );
|
||||
return false;
|
||||
}
|
||||
return (object) $data->first();
|
||||
}
|
||||
}
|
47
app/plugins/subscribe/plugin.php
Executable file
47
app/plugins/subscribe/plugin.php
Executable file
@ -0,0 +1,47 @@
|
||||
<?php
|
||||
/**
|
||||
* app/plugins/subscribe/plugin.php
|
||||
*
|
||||
* This houses all of the main plugin info and functionality.
|
||||
*
|
||||
* @package TP Subscribe
|
||||
* @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\Houdini\Classes\Components;
|
||||
use TheTempusProject\Houdini\Classes\Views;
|
||||
use TheTempusProject\TheTempusProject as App;
|
||||
|
||||
class Subscribe extends Plugin {
|
||||
private static $loaded = false;
|
||||
public $pluginName = 'TP Subscribe';
|
||||
public $pluginAuthor = 'JoeyK';
|
||||
public $pluginWebsite = 'https://TheTempusProject.com';
|
||||
public $modelVersion = '1.0';
|
||||
public $pluginVersion = '3.0';
|
||||
public $pluginDescription = 'A simple plugin to add a method for users to share their email.';
|
||||
public $admin_links = [
|
||||
[
|
||||
'text' => '<i class="fa fa-fw fa-address-book"></i> Subscriptions',
|
||||
'url' => '{ROOT_URL}admin/subscriptions',
|
||||
],
|
||||
];
|
||||
|
||||
public function __construct( $load = false ) {
|
||||
parent::__construct( $load );
|
||||
if ( ! self::$loaded ) {
|
||||
if ( $this->checkEnabled() ) {
|
||||
Components::append( 'FOOTER_RIGHT', Views::simpleView( 'subscribe.footer.right') );
|
||||
}
|
||||
self::$loaded = true;
|
||||
}
|
||||
}
|
||||
}
|
24
app/plugins/subscribe/views/admin/add.html
Executable file
24
app/plugins/subscribe/views/admin/add.html
Executable file
@ -0,0 +1,24 @@
|
||||
<div class="context-main-bg context-main p-3">
|
||||
<legend class="text-center">Add Subscriber</legend>
|
||||
<hr>
|
||||
{ADMIN_BREADCRUMBS}
|
||||
<form method="post">
|
||||
<fieldset>
|
||||
<!-- Subject -->
|
||||
<div class="mb-3 row">
|
||||
<label for="email" class="col-lg-5 col-form-label text-end">Email:</label>
|
||||
<div class="col-lg-3">
|
||||
<input type="email" class="form-control" name="email" id="email" required>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Hidden Token -->
|
||||
<input type="hidden" name="token" value="{TOKEN}">
|
||||
</fieldset>
|
||||
|
||||
<!-- Submit Button -->
|
||||
<div class="text-center">
|
||||
<button type="submit" name="submit" value="submit" class="btn btn-primary btn-lg center-block">Submit</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
40
app/plugins/subscribe/views/admin/list.html
Executable file
40
app/plugins/subscribe/views/admin/list.html
Executable file
@ -0,0 +1,40 @@
|
||||
<div class="context-main-bg context-main p-3">
|
||||
<legend class="text-center">Subscribers</legend>
|
||||
<hr>
|
||||
{ADMIN_BREADCRUMBS}
|
||||
<form action="{ROOT_URL}admin/subscriptions/delete" method="post">
|
||||
<table class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width: 5%">ID</th>
|
||||
<th style="width: 85%">email</th>
|
||||
<th style="width: 5%"></th>
|
||||
<th style="width: 5%">
|
||||
<input type="checkbox" onchange="checkAll(this)" name="check.s" value="S_[]">
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{LOOP}
|
||||
<tr>
|
||||
<td align="center">{ID}</td>
|
||||
<td>{EMAIL}</td>
|
||||
<td><a href="{ROOT_URL}admin/subscriptions/delete/{ID}" class="btn btn-sm btn-danger"><i class="fa fa-fw fa-trash"></i></a></td>
|
||||
<td>
|
||||
<input type="checkbox" value="{ID}" name="S_[]">
|
||||
</td>
|
||||
</tr>
|
||||
{/LOOP}
|
||||
{ALT}
|
||||
<tr>
|
||||
<td align="center" colspan="6">
|
||||
No results to show.
|
||||
</td>
|
||||
</tr>
|
||||
{/ALT}
|
||||
</tbody>
|
||||
</table>
|
||||
<a href="{ROOT_URL}admin/subscriptions/add" class="btn btn-sm btn-primary">Add</a>
|
||||
<button name="submit" value="submit" type="submit" class="btn btn-sm btn-danger"><i class="fa fa-fw fa-trash"></i></button>
|
||||
</form>
|
||||
</div>
|
11
app/plugins/subscribe/views/footer/right.html
Executable file
11
app/plugins/subscribe/views/footer/right.html
Executable file
@ -0,0 +1,11 @@
|
||||
<div class="col-12 col-sm-6 col-md-3 offset-0 offset-lg-2 mb-3 text-center">
|
||||
<h5 class="">Subscribe</h5>
|
||||
<div class="d-flex flex-column w-75 w-sm-100 gap-2 justify-content-center mx-auto">
|
||||
<form action="{ROOT_URL}subscribe/home" method="post">
|
||||
<label for="email" class="visually-hidden">Email address</label>
|
||||
<input name="email" id="email" type="email" class="form-control my-2" placeholder="Email address" autocomplete="email">
|
||||
<input type="hidden" name="token" value="{TOKEN}">
|
||||
<button class="btn btn-primary my-2 w-100" name="submit" value="submit" type="submit">Subscribe</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
5
app/plugins/subscribe/views/subscribe.html
Executable file
5
app/plugins/subscribe/views/subscribe.html
Executable file
@ -0,0 +1,5 @@
|
||||
<form action="{ROOT_URL}subscribe/home" method="post">
|
||||
<input type="email" placeholder="Email" id="email" name="email" autocomplete="email">
|
||||
<input type="hidden" name="token" value="{TOKEN}">
|
||||
<button name="submit" value="submit" type="submit" class="btn btn-lg btn-primary center-block">Submit</button><br>
|
||||
</form>
|
5
app/plugins/subscribe/views/unsubscribe.html
Executable file
5
app/plugins/subscribe/views/unsubscribe.html
Executable file
@ -0,0 +1,5 @@
|
||||
<form action="{ROOT_URL}home/unsubscribe" method="post">
|
||||
<input type="email" placeholder="Email" id="email" name="email">
|
||||
<input type="hidden" name="token" value="{TOKEN}">
|
||||
<button name="submit" value="submit" type="submit" class="btn btn-lg btn-primary center-block">Submit</button><br>
|
||||
</form>
|
Reference in New Issue
Block a user