Initial commit

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

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

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

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

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

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

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

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

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

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

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