initial commit
This commit is contained in:
1
application/_installation/01-create-database.sql
Normal file
1
application/_installation/01-create-database.sql
Normal file
@@ -0,0 +1 @@
|
||||
CREATE DATABASE IF NOT EXISTS `huge`;
|
||||
31
application/_installation/02-create-table-users.sql
Normal file
31
application/_installation/02-create-table-users.sql
Normal file
@@ -0,0 +1,31 @@
|
||||
CREATE TABLE IF NOT EXISTS `huge`.`users` (
|
||||
`user_id` int(11) NOT NULL AUTO_INCREMENT COMMENT 'auto incrementing user_id of each user, unique index',
|
||||
`session_id` varchar(48) DEFAULT NULL COMMENT 'stores session cookie id to prevent session concurrency',
|
||||
`user_name` varchar(64) COLLATE utf8_unicode_ci NOT NULL COMMENT 'user''s name, unique',
|
||||
`user_password_hash` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL COMMENT 'user''s password in salted and hashed format',
|
||||
`user_email` varchar(254) COLLATE utf8_unicode_ci NOT NULL COMMENT 'user''s email, unique',
|
||||
`user_active` tinyint(1) NOT NULL DEFAULT '0' COMMENT 'user''s activation status',
|
||||
`user_deleted` tinyint(1) NOT NULL DEFAULT '0' COMMENT 'user''s deletion status',
|
||||
`user_account_type` tinyint(1) NOT NULL DEFAULT '1' COMMENT 'user''s account type (basic, premium, etc)',
|
||||
`user_has_avatar` tinyint(1) NOT NULL DEFAULT '0' COMMENT '1 if user has a local avatar, 0 if not',
|
||||
`user_remember_me_token` varchar(64) COLLATE utf8_unicode_ci DEFAULT NULL COMMENT 'user''s remember-me cookie token',
|
||||
`user_creation_timestamp` bigint(20) DEFAULT NULL COMMENT 'timestamp of the creation of user''s account',
|
||||
`user_suspension_timestamp` bigint(20) DEFAULT NULL COMMENT 'Timestamp till the end of a user suspension',
|
||||
`user_last_login_timestamp` bigint(20) DEFAULT NULL COMMENT 'timestamp of user''s last login',
|
||||
`user_failed_logins` tinyint(1) NOT NULL DEFAULT '0' COMMENT 'user''s failed login attempts',
|
||||
`user_last_failed_login` int(10) DEFAULT NULL COMMENT 'unix timestamp of last failed login attempt',
|
||||
`user_activation_hash` varchar(80) COLLATE utf8_unicode_ci DEFAULT NULL COMMENT 'user''s email verification hash string',
|
||||
`user_password_reset_hash` char(80) COLLATE utf8_unicode_ci DEFAULT NULL COMMENT 'user''s password reset code',
|
||||
`user_password_reset_timestamp` bigint(20) DEFAULT NULL COMMENT 'timestamp of the password reset request',
|
||||
`user_provider_type` text COLLATE utf8_unicode_ci,
|
||||
PRIMARY KEY (`user_id`),
|
||||
UNIQUE KEY `user_name` (`user_name`),
|
||||
UNIQUE KEY `user_email` (`user_email`)
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci COMMENT='user data';
|
||||
|
||||
INSERT INTO `huge`.`users` (`user_id`, `session_id`, `user_name`, `user_password_hash`, `user_email`, `user_active`, `user_deleted`, `user_account_type`,
|
||||
`user_has_avatar`, `user_remember_me_token`, `user_creation_timestamp`, `user_suspension_timestamp`, `user_last_login_timestamp`,
|
||||
`user_failed_logins`, `user_last_failed_login`, `user_activation_hash`, `user_password_reset_hash`,
|
||||
`user_password_reset_timestamp`, `user_provider_type`) VALUES
|
||||
(1, NULL, 'demo', '$2y$10$OvprunjvKOOhM1h9bzMPs.vuwGIsOqZbw88rzSyGCTJTcE61g5WXi', 'demo@demo.com', 1, 0, 7, 0, NULL, 1422205178, NULL, 1422209189, 0, NULL, NULL, NULL, NULL, 'DEFAULT'),
|
||||
(2, NULL, 'demo2', '$2y$10$OvprunjvKOOhM1h9bzMPs.vuwGIsOqZbw88rzSyGCTJTcE61g5WXi', 'demo2@demo.com', 1, 0, 1, 0, NULL, 1422205178, NULL, 1422209189, 0, NULL, NULL, NULL, NULL, 'DEFAULT');
|
||||
6
application/_installation/03-create-table-notes.sql
Normal file
6
application/_installation/03-create-table-notes.sql
Normal file
@@ -0,0 +1,6 @@
|
||||
CREATE TABLE IF NOT EXISTS `huge`.`notes` (
|
||||
`note_id` int(11) unsigned NOT NULL AUTO_INCREMENT,
|
||||
`note_text` text NOT NULL,
|
||||
`user_id` int(11) unsigned NOT NULL,
|
||||
PRIMARY KEY (`note_id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci COMMENT='user notes';
|
||||
156
application/config/config.development.php
Normal file
156
application/config/config.development.php
Normal file
@@ -0,0 +1,156 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Configuration for DEVELOPMENT environment
|
||||
* To create another configuration set just copy this file to config.production.php etc. You get the idea :)
|
||||
*/
|
||||
|
||||
/**
|
||||
* Configuration for: Error reporting
|
||||
* Useful to show every little problem during development, but only show hard / no errors in production.
|
||||
* It's a little bit dirty to put this here, but who cares. For development purposes it's totally okay.
|
||||
*/
|
||||
error_reporting(E_ALL);
|
||||
ini_set("display_errors", 1);
|
||||
|
||||
/**
|
||||
* Configuration for cookie security
|
||||
* Quote from PHP manual: Marks the cookie as accessible only through the HTTP protocol. This means that the cookie
|
||||
* won't be accessible by scripting languages, such as JavaScript. This setting can effectively help to reduce identity
|
||||
* theft through XSS attacks (although it is not supported by all browsers).
|
||||
*
|
||||
* @see php.net/manual/en/session.configuration.php#ini.session.cookie-httponly
|
||||
*/
|
||||
ini_set('session.cookie_httponly', 1);
|
||||
|
||||
/**
|
||||
* Returns the full configuration.
|
||||
* This is used by the core/Config class.
|
||||
*/
|
||||
return array(
|
||||
/**
|
||||
* Configuration for: Base URL
|
||||
* This detects your URL/IP incl. sub-folder automatically. You can also deactivate auto-detection and provide the
|
||||
* URL manually. This should then look like 'http://192.168.33.44/' ! Note the slash in the end.
|
||||
*/
|
||||
'URL' => 'http://' . $_SERVER['HTTP_HOST'] . str_replace('public', '', dirname($_SERVER['SCRIPT_NAME'])),
|
||||
/**
|
||||
* Configuration for: Folders
|
||||
* Usually there's no reason to change this.
|
||||
*/
|
||||
'PATH_CONTROLLER' => realpath(dirname(__FILE__) . '/../../') . '/application/controller/',
|
||||
'PATH_VIEW' => realpath(dirname(__FILE__) . '/../../') . '/application/view/',
|
||||
/**
|
||||
* Configuration for: Avatar paths
|
||||
* Internal path to save avatars. Make sure this folder is writable. The slash at the end is VERY important!
|
||||
*/
|
||||
'PATH_AVATARS' => realpath(dirname(__FILE__) . '/../../') . '/public/avatars/',
|
||||
'PATH_AVATARS_PUBLIC' => 'avatars/',
|
||||
/**
|
||||
* Configuration for: Default controller and action
|
||||
*/
|
||||
'DEFAULT_CONTROLLER' => 'index',
|
||||
'DEFAULT_ACTION' => 'index',
|
||||
/**
|
||||
* Configuration for: Database
|
||||
* DB_TYPE The used database type. Note that other types than "mysql" might break the db construction currently.
|
||||
* DB_HOST The mysql hostname, usually localhost or 127.0.0.1
|
||||
* DB_NAME The database name
|
||||
* DB_USER The username
|
||||
* DB_PASS The password
|
||||
* DB_PORT The mysql port, 3306 by default (?), find out via phpinfo() and look for mysqli.default_port.
|
||||
* DB_CHARSET The charset, necessary for security reasons. Check Database.php class for more info.
|
||||
*/
|
||||
'DB_TYPE' => 'mysql',
|
||||
'DB_HOST' => 'localhost',
|
||||
'DB_NAME' => 'huge',
|
||||
'DB_USER' => 'root',
|
||||
'DB_PASS' => 'root',
|
||||
'DB_PORT' => '3306',
|
||||
'DB_CHARSET' => 'utf8',
|
||||
/**
|
||||
* Configuration for: Captcha size
|
||||
* The currently used Captcha generator (https://github.com/Gregwar/Captcha) also runs without giving a size,
|
||||
* so feel free to use ->build(); inside CaptchaModel.
|
||||
*/
|
||||
'CAPTCHA_WIDTH' => 359,
|
||||
'CAPTCHA_HEIGHT' => 100,
|
||||
/**
|
||||
* Configuration for: Cookies
|
||||
* 1209600 seconds = 2 weeks
|
||||
* COOKIE_PATH is the path the cookie is valid on, usually "/" to make it valid on the whole domain.
|
||||
* @see http://stackoverflow.com/q/9618217/1114320
|
||||
* @see php.net/manual/en/function.setcookie.php
|
||||
*
|
||||
* COOKIE_DOMAIN: The domain where the cookie is valid for. Usually this does not work with "localhost",
|
||||
* ".localhost", "127.0.0.1", or ".127.0.0.1". If so, leave it as empty string, false or null.
|
||||
* When using real domains make sure you have a dot (!) in front of the domain, like ".mydomain.com". This is
|
||||
* strange, but explained here:
|
||||
* @see http://stackoverflow.com/questions/2285010/php-setcookie-domain
|
||||
* @see http://stackoverflow.com/questions/1134290/cookies-on-localhost-with-explicit-domain
|
||||
* @see http://php.net/manual/en/function.setcookie.php#73107
|
||||
*
|
||||
* COOKIE_SECURE: If the cookie will be transferred through secured connection(SSL). It's highly recommended to set it to true if you have secured connection.
|
||||
* COOKIE_HTTP: If set to true, Cookies that can't be accessed by JS - Highly recommended!
|
||||
* SESSION_RUNTIME: How long should a session cookie be valid by seconds, 604800 = 1 week.
|
||||
*/
|
||||
'COOKIE_RUNTIME' => 1209600,
|
||||
'COOKIE_PATH' => '/',
|
||||
'COOKIE_DOMAIN' => "",
|
||||
'COOKIE_SECURE' => false,
|
||||
'COOKIE_HTTP' => true,
|
||||
'SESSION_RUNTIME' => 604800,
|
||||
/**
|
||||
* Configuration for: Avatars/Gravatar support
|
||||
* Set to true if you want to use "Gravatar(s)", a service that automatically gets avatar pictures via using email
|
||||
* addresses of users by requesting images from the gravatar.com API. Set to false to use own locally saved avatars.
|
||||
* AVATAR_SIZE set the pixel size of avatars/gravatars (will be 44x44 by default). Avatars are always squares.
|
||||
* AVATAR_DEFAULT_IMAGE is the default image in public/avatars/
|
||||
*/
|
||||
'USE_GRAVATAR' => false,
|
||||
'GRAVATAR_DEFAULT_IMAGESET' => 'mm',
|
||||
'GRAVATAR_RATING' => 'pg',
|
||||
'AVATAR_SIZE' => 44,
|
||||
'AVATAR_JPEG_QUALITY' => 85,
|
||||
'AVATAR_DEFAULT_IMAGE' => 'default.jpg',
|
||||
/**
|
||||
* Configuration for: Encryption Keys
|
||||
* ENCRYPTION_KEY, HMAC_SALT: Currently used to encrypt and decrypt publicly visible values, like the user id in
|
||||
* the cookie. Change these values for increased security, but don't touch if you have no idea what this means.
|
||||
*/
|
||||
'ENCRYPTION_KEY' => '6#x0gÊìf^25cL1f$08&',
|
||||
'HMAC_SALT' => '8qk9c^4L6d#15tM8z7n0%',
|
||||
/**
|
||||
* Configuration for: Email server credentials
|
||||
*
|
||||
* Here you can define how you want to send emails.
|
||||
* If you have successfully set up a mail server on your linux server and you know
|
||||
* what you do, then you can skip this section. Otherwise please set EMAIL_USE_SMTP to true
|
||||
* and fill in your SMTP provider account data.
|
||||
*
|
||||
* EMAIL_USED_MAILER: Check Mail class for alternatives
|
||||
* EMAIL_USE_SMTP: Use SMTP or not
|
||||
* EMAIL_SMTP_AUTH: leave this true unless your SMTP service does not need authentication
|
||||
*/
|
||||
'EMAIL_USED_MAILER' => 'phpmailer',
|
||||
'EMAIL_USE_SMTP' => false,
|
||||
'EMAIL_SMTP_HOST' => 'yourhost',
|
||||
'EMAIL_SMTP_AUTH' => true,
|
||||
'EMAIL_SMTP_USERNAME' => 'yourusername',
|
||||
'EMAIL_SMTP_PASSWORD' => 'yourpassword',
|
||||
'EMAIL_SMTP_PORT' => 465,
|
||||
'EMAIL_SMTP_ENCRYPTION' => 'ssl',
|
||||
/**
|
||||
* Configuration for: Email content data
|
||||
*/
|
||||
'EMAIL_PASSWORD_RESET_URL' => 'login/verifypasswordreset',
|
||||
'EMAIL_PASSWORD_RESET_FROM_EMAIL' => 'no-reply@example.com',
|
||||
'EMAIL_PASSWORD_RESET_FROM_NAME' => 'My Project',
|
||||
'EMAIL_PASSWORD_RESET_SUBJECT' => 'Password reset for PROJECT XY',
|
||||
'EMAIL_PASSWORD_RESET_CONTENT' => 'Please click on this link to reset your password: ',
|
||||
'EMAIL_VERIFICATION_URL' => 'register/verify',
|
||||
'EMAIL_VERIFICATION_FROM_EMAIL' => 'no-reply@example.com',
|
||||
'EMAIL_VERIFICATION_FROM_NAME' => 'My Project',
|
||||
'EMAIL_VERIFICATION_SUBJECT' => 'Account activation for PROJECT XY',
|
||||
'EMAIL_VERIFICATION_CONTENT' => 'Please click on this link to activate your account: ',
|
||||
);
|
||||
75
application/config/texts.php
Normal file
75
application/config/texts.php
Normal file
@@ -0,0 +1,75 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Texts used in the application.
|
||||
* These texts are used via Text::get('FEEDBACK_USERNAME_ALREADY_TAKEN').
|
||||
* Could be extended to i18n etc.
|
||||
*/
|
||||
return array(
|
||||
"FEEDBACK_UNKNOWN_ERROR" => "Unknown error occurred!",
|
||||
"FEEDBACK_DELETED" => "Your account has been deleted.",
|
||||
"FEEDBACK_ACCOUNT_SUSPENDED" => "Account Suspended for ",
|
||||
"FEEDBACK_ACCOUNT_SUSPENSION_DELETION_STATUS" => "This user's suspension / deletion status has been edited.",
|
||||
"FEEDBACK_ACCOUNT_CANT_DELETE_SUSPEND_OWN" => "You can not delete or suspend your own account.",
|
||||
"FEEDBACK_ACCOUNT_USER_SUCCESSFULLY_KICKED" => "The selected user has been successfully kicked out of the system (by resetting this user's session)",
|
||||
"FEEDBACK_PASSWORD_WRONG_3_TIMES" => "You have typed in a wrong password 3 or more times already. Please wait 30 seconds to try again.",
|
||||
"FEEDBACK_ACCOUNT_NOT_ACTIVATED_YET" => "Your account is not activated yet. Please click on the confirm link in the mail.",
|
||||
"FEEDBACK_USERNAME_OR_PASSWORD_WRONG" => "The username or password is incorrect. Please try again.",
|
||||
"FEEDBACK_USER_DOES_NOT_EXIST" => "This user does not exist.",
|
||||
"FEEDBACK_LOGIN_FAILED" => "Login failed.",
|
||||
"FEEDBACK_LOGIN_FAILED_3_TIMES" => "Login failed 3 or more times already. Please wait 30 seconds to try again.",
|
||||
"FEEDBACK_USERNAME_FIELD_EMPTY" => "Username field was empty.",
|
||||
"FEEDBACK_PASSWORD_FIELD_EMPTY" => "Password field was empty.",
|
||||
"FEEDBACK_USERNAME_OR_PASSWORD_FIELD_EMPTY" => "Username or password field was empty.",
|
||||
"FEEDBACK_USERNAME_EMAIL_FIELD_EMPTY" => "Username / email field was empty.",
|
||||
"FEEDBACK_EMAIL_FIELD_EMPTY" => "Email field was empty.",
|
||||
"FEEDBACK_EMAIL_REPEAT_WRONG" => "Email and email repeat are not the same",
|
||||
"FEEDBACK_EMAIL_AND_PASSWORD_FIELDS_EMPTY" => "Email and password fields were empty.",
|
||||
"FEEDBACK_USERNAME_SAME_AS_OLD_ONE" => "Sorry, that username is the same as your current one. Please choose another one.",
|
||||
"FEEDBACK_USERNAME_ALREADY_TAKEN" => "Sorry, that username is already taken. Please choose another one.",
|
||||
"FEEDBACK_USER_EMAIL_ALREADY_TAKEN" => "Sorry, that email is already in use. Please choose another one.",
|
||||
"FEEDBACK_USERNAME_CHANGE_SUCCESSFUL" => "Your username has been changed successfully.",
|
||||
"FEEDBACK_USERNAME_AND_PASSWORD_FIELD_EMPTY" => "Username and password fields were empty.",
|
||||
"FEEDBACK_USERNAME_DOES_NOT_FIT_PATTERN" => "Username does not fit the name pattern: only a-Z and numbers are allowed, 2 to 64 characters.",
|
||||
"FEEDBACK_EMAIL_DOES_NOT_FIT_PATTERN" => "Sorry, your chosen email does not fit into the email naming pattern.",
|
||||
"FEEDBACK_EMAIL_SAME_AS_OLD_ONE" => "Sorry, that email address is the same as your current one. Please choose another one.",
|
||||
"FEEDBACK_EMAIL_CHANGE_SUCCESSFUL" => "Your email address has been changed successfully.",
|
||||
"FEEDBACK_CAPTCHA_WRONG" => "The entered captcha security characters were wrong.",
|
||||
"FEEDBACK_PASSWORD_REPEAT_WRONG" => "Password and password repeat are not the same.",
|
||||
"FEEDBACK_PASSWORD_TOO_SHORT" => "Password has a minimum length of 6 characters.",
|
||||
"FEEDBACK_USERNAME_TOO_SHORT_OR_TOO_LONG" => "Username cannot be shorter than 2 or longer than 64 characters.",
|
||||
"FEEDBACK_ACCOUNT_SUCCESSFULLY_CREATED" => "Your account has been created successfully and we have sent you an email. Please click the VERIFICATION LINK within that mail.",
|
||||
"FEEDBACK_VERIFICATION_MAIL_SENDING_FAILED" => "Sorry, we could not send you an verification mail. Your account has NOT been created.",
|
||||
"FEEDBACK_ACCOUNT_CREATION_FAILED" => "Sorry, your registration failed. Please go back and try again.",
|
||||
"FEEDBACK_VERIFICATION_MAIL_SENDING_ERROR" => "Verification mail could not be sent due to: ",
|
||||
"FEEDBACK_VERIFICATION_MAIL_SENDING_SUCCESSFUL" => "A verification mail has been sent successfully.",
|
||||
"FEEDBACK_ACCOUNT_ACTIVATION_SUCCESSFUL" => "Activation was successful! You can now log in.",
|
||||
"FEEDBACK_ACCOUNT_ACTIVATION_FAILED" => "Sorry, no such id/verification code combination here! It might be possible that your mail provider (Yahoo? Hotmail?) automatically visits links in emails for anti-scam scanning, so this activation link might been clicked without your action. Please try to log in on the main page.",
|
||||
"FEEDBACK_AVATAR_UPLOAD_SUCCESSFUL" => "Avatar upload was successful.",
|
||||
"FEEDBACK_AVATAR_UPLOAD_WRONG_TYPE" => "Only JPEG and PNG files are supported.",
|
||||
"FEEDBACK_AVATAR_UPLOAD_TOO_SMALL" => "Avatar source file's width/height is too small. Needs to be 100x100 pixel minimum.",
|
||||
"FEEDBACK_AVATAR_UPLOAD_TOO_BIG" => "Avatar source file is too big. 5 Megabyte is the maximum.",
|
||||
"FEEDBACK_AVATAR_FOLDER_DOES_NOT_EXIST_OR_NOT_WRITABLE" => "Avatar folder does not exist or is not writable. Please change this via chmod 775 or 777.",
|
||||
"FEEDBACK_AVATAR_IMAGE_UPLOAD_FAILED" => "Something went wrong with the image upload.",
|
||||
"FEEDBACK_AVATAR_IMAGE_DELETE_SUCCESSFUL" => "You successfully deleted your avatar.",
|
||||
"FEEDBACK_AVATAR_IMAGE_DELETE_NO_FILE" => "You don't have a custom avatar.",
|
||||
"FEEDBACK_AVATAR_IMAGE_DELETE_FAILED" => "Something went wrong while deleting your avatar.",
|
||||
"FEEDBACK_PASSWORD_RESET_TOKEN_FAIL" => "Could not write token to database.",
|
||||
"FEEDBACK_PASSWORD_RESET_TOKEN_MISSING" => "No password reset token.",
|
||||
"FEEDBACK_PASSWORD_RESET_MAIL_SENDING_ERROR" => "Password reset mail could not be sent due to: ",
|
||||
"FEEDBACK_PASSWORD_RESET_MAIL_SENDING_SUCCESSFUL" => "A password reset mail has been sent successfully.",
|
||||
"FEEDBACK_PASSWORD_RESET_LINK_EXPIRED" => "Your reset link has expired. Please use the reset link within one hour.",
|
||||
"FEEDBACK_PASSWORD_RESET_COMBINATION_DOES_NOT_EXIST" => "Username/Verification code combination does not exist.",
|
||||
"FEEDBACK_PASSWORD_RESET_LINK_VALID" => "Password reset validation link is valid. Please change the password now.",
|
||||
"FEEDBACK_PASSWORD_CHANGE_SUCCESSFUL" => "Password successfully changed.",
|
||||
"FEEDBACK_PASSWORD_CHANGE_FAILED" => "Sorry, your password changing failed.",
|
||||
"FEEDBACK_PASSWORD_NEW_SAME_AS_CURRENT" => "New password is the same as the current password.",
|
||||
"FEEDBACK_PASSWORD_CURRENT_INCORRECT" => "Current password entered was incorrect.",
|
||||
"FEEDBACK_ACCOUNT_TYPE_CHANGE_SUCCESSFUL" => "Account type change successful",
|
||||
"FEEDBACK_ACCOUNT_TYPE_CHANGE_FAILED" => "Account type change failed",
|
||||
"FEEDBACK_NOTE_CREATION_FAILED" => "Note creation failed.",
|
||||
"FEEDBACK_NOTE_EDITING_FAILED" => "Note editing failed.",
|
||||
"FEEDBACK_NOTE_DELETION_FAILED" => "Note deletion failed.",
|
||||
"FEEDBACK_COOKIE_INVALID" => "Your remember-me-cookie is invalid.",
|
||||
"FEEDBACK_COOKIE_LOGIN_SUCCESSFUL" => "You were successfully logged in via the remember-me-cookie.",
|
||||
);
|
||||
35
application/controller/AdminController.php
Normal file
35
application/controller/AdminController.php
Normal file
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
class AdminController extends Controller
|
||||
{
|
||||
/**
|
||||
* Construct this object by extending the basic Controller class
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
// special authentication check for the entire controller: Note the check-ADMIN-authentication!
|
||||
// All methods inside this controller are only accessible for admins (= users that have role type 7)
|
||||
Auth::checkAdminAuthentication();
|
||||
}
|
||||
|
||||
/**
|
||||
* This method controls what happens when you move to /admin or /admin/index in your app.
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
$this->View->render('admin/index', array(
|
||||
'users' => UserModel::getPublicProfilesOfAllUsers())
|
||||
);
|
||||
}
|
||||
|
||||
public function actionAccountSettings()
|
||||
{
|
||||
AdminModel::setAccountSuspensionAndDeletionStatus(
|
||||
Request::post('suspension'), Request::post('softDelete'), Request::post('user_id')
|
||||
);
|
||||
|
||||
Redirect::to("admin");
|
||||
}
|
||||
}
|
||||
26
application/controller/DashboardController.php
Normal file
26
application/controller/DashboardController.php
Normal file
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This controller shows an area that's only visible for logged in users (because of Auth::checkAuthentication(); in line 16)
|
||||
*/
|
||||
class DashboardController extends Controller
|
||||
{
|
||||
/**
|
||||
* Construct this object by extending the basic Controller class
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
// this entire controller should only be visible/usable by logged in users, so we put authentication-check here
|
||||
Auth::checkAuthentication();
|
||||
}
|
||||
|
||||
/**
|
||||
* This method controls what happens when you move to /dashboard/index in your app.
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
$this->View->render('dashboard/index');
|
||||
}
|
||||
}
|
||||
28
application/controller/ErrorController.php
Normal file
28
application/controller/ErrorController.php
Normal file
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Class Error
|
||||
* This controller simply contains some methods that can be used to give proper feedback in certain error scenarios,
|
||||
* like a proper 404 response with an additional HTML page behind when something does not exist.
|
||||
*/
|
||||
class ErrorController extends Controller
|
||||
{
|
||||
/**
|
||||
* Construct this object by extending the basic Controller class
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
/**
|
||||
* Use this when something is not found. Gives back a proper 404 header response plus a normal page (where you could
|
||||
* show a well-designed error message or something more useful for your users).
|
||||
* You can see this in action in action in /core/Application.php -> __construct
|
||||
*/
|
||||
public function error404()
|
||||
{
|
||||
header('HTTP/1.0 404 Not Found', true, 404);
|
||||
$this->View->render('error/404');
|
||||
}
|
||||
}
|
||||
21
application/controller/IndexController.php
Normal file
21
application/controller/IndexController.php
Normal file
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
class IndexController extends Controller
|
||||
{
|
||||
/**
|
||||
* Construct this object by extending the basic Controller class
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles what happens when user moves to URL/index/index - or - as this is the default controller, also
|
||||
* when user moves to /index or enter your application at base level
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
$this->View->render('index/index');
|
||||
}
|
||||
}
|
||||
148
application/controller/LoginController.php
Normal file
148
application/controller/LoginController.php
Normal file
@@ -0,0 +1,148 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* LoginController
|
||||
* Controls everything that is authentication-related
|
||||
*/
|
||||
class LoginController extends Controller
|
||||
{
|
||||
/**
|
||||
* Construct this object by extending the basic Controller class. The parent::__construct thing is necessary to
|
||||
* put checkAuthentication in here to make an entire controller only usable for logged-in users (for sure not
|
||||
* needed in the LoginController).
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
/**
|
||||
* Index, default action (shows the login form), when you do login/index
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
// if user is logged in redirect to main-page, if not show the view
|
||||
if (LoginModel::isUserLoggedIn()) {
|
||||
Redirect::home();
|
||||
} else {
|
||||
$data = array('redirect' => Request::get('redirect') ? Request::get('redirect') : null);
|
||||
$this->View->render('login/index', $data);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The login action, when you do login/login
|
||||
*/
|
||||
public function login()
|
||||
{
|
||||
// check if csrf token is valid
|
||||
if (!Csrf::isTokenValid()) {
|
||||
LoginModel::logout();
|
||||
Redirect::home();
|
||||
exit();
|
||||
}
|
||||
|
||||
// perform the login method, put result (true or false) into $login_successful
|
||||
$login_successful = LoginModel::login(
|
||||
Request::post('user_name'), Request::post('user_password'), Request::post('set_remember_me_cookie')
|
||||
);
|
||||
|
||||
// check login status: if true, then redirect user to user/index, if false, then to login form again
|
||||
if ($login_successful) {
|
||||
if (Request::post('redirect')) {
|
||||
Redirect::toPreviousViewedPageAfterLogin(ltrim(urldecode(Request::post('redirect')), '/'));
|
||||
} else {
|
||||
Redirect::to('user/index');
|
||||
}
|
||||
} else {
|
||||
if (Request::post('redirect')) {
|
||||
Redirect::to('login?redirect=' . ltrim(urlencode(Request::post('redirect')), '/'));
|
||||
} else {
|
||||
Redirect::to('login/index');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The logout action
|
||||
* Perform logout, redirect user to main-page
|
||||
*/
|
||||
public function logout()
|
||||
{
|
||||
LoginModel::logout();
|
||||
Redirect::home();
|
||||
exit();
|
||||
}
|
||||
|
||||
/**
|
||||
* Login with cookie
|
||||
*/
|
||||
public function loginWithCookie()
|
||||
{
|
||||
// run the loginWithCookie() method in the login-model, put the result in $login_successful (true or false)
|
||||
$login_successful = LoginModel::loginWithCookie(Request::cookie('remember_me'));
|
||||
|
||||
// if login successful, redirect to dashboard/index ...
|
||||
if ($login_successful) {
|
||||
Redirect::to('dashboard/index');
|
||||
} else {
|
||||
// if not, delete cookie (outdated? attack?) and route user to login form to prevent infinite login loops
|
||||
LoginModel::deleteCookie();
|
||||
Redirect::to('login/index');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the request-password-reset page
|
||||
*/
|
||||
public function requestPasswordReset()
|
||||
{
|
||||
$this->View->render('login/requestPasswordReset');
|
||||
}
|
||||
|
||||
/**
|
||||
* The request-password-reset action
|
||||
* POST-request after form submit
|
||||
*/
|
||||
public function requestPasswordReset_action()
|
||||
{
|
||||
PasswordResetModel::requestPasswordReset(Request::post('user_name_or_email'), Request::post('captcha'));
|
||||
Redirect::to('login/index');
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify the verification token of that user (to show the user the password editing view or not)
|
||||
* @param string $user_name username
|
||||
* @param string $verification_code password reset verification token
|
||||
*/
|
||||
public function verifyPasswordReset($user_name, $verification_code)
|
||||
{
|
||||
// check if this the provided verification code fits the user's verification code
|
||||
if (PasswordResetModel::verifyPasswordReset($user_name, $verification_code)) {
|
||||
// pass URL-provided variable to view to display them
|
||||
$this->View->render('login/resetPassword', array(
|
||||
'user_name' => $user_name,
|
||||
'user_password_reset_hash' => $verification_code
|
||||
));
|
||||
} else {
|
||||
Redirect::to('login/index');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the new password
|
||||
* Please note that this happens while the user is not logged in. The user identifies via the data provided by the
|
||||
* password reset link from the email, automatically filled into the <form> fields. See verifyPasswordReset()
|
||||
* for more. Then (regardless of result) route user to index page (user will get success/error via feedback message)
|
||||
* POST request !
|
||||
* TODO this is an _action
|
||||
*/
|
||||
public function setNewPassword()
|
||||
{
|
||||
PasswordResetModel::setNewPassword(
|
||||
Request::post('user_name'), Request::post('user_password_reset_hash'),
|
||||
Request::post('user_password_new'), Request::post('user_password_repeat')
|
||||
);
|
||||
Redirect::to('login/index');
|
||||
}
|
||||
}
|
||||
77
application/controller/NoteController.php
Normal file
77
application/controller/NoteController.php
Normal file
@@ -0,0 +1,77 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* The note controller: Just an example of simple create, read, update and delete (CRUD) actions.
|
||||
*/
|
||||
class NoteController extends Controller
|
||||
{
|
||||
/**
|
||||
* Construct this object by extending the basic Controller class
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
// VERY IMPORTANT: All controllers/areas that should only be usable by logged-in users
|
||||
// need this line! Otherwise not-logged in users could do actions. If all of your pages should only
|
||||
// be usable by logged-in users: Put this line into libs/Controller->__construct
|
||||
Auth::checkAuthentication();
|
||||
}
|
||||
|
||||
/**
|
||||
* This method controls what happens when you move to /note/index in your app.
|
||||
* Gets all notes (of the user).
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
$this->View->render('note/index', array(
|
||||
'notes' => NoteModel::getAllNotes()
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* This method controls what happens when you move to /dashboard/create in your app.
|
||||
* Creates a new note. This is usually the target of form submit actions.
|
||||
* POST request.
|
||||
*/
|
||||
public function create()
|
||||
{
|
||||
NoteModel::createNote(Request::post('note_text'));
|
||||
Redirect::to('note');
|
||||
}
|
||||
|
||||
/**
|
||||
* This method controls what happens when you move to /note/edit(/XX) in your app.
|
||||
* Shows the current content of the note and an editing form.
|
||||
* @param $note_id int id of the note
|
||||
*/
|
||||
public function edit($note_id)
|
||||
{
|
||||
$this->View->render('note/edit', array(
|
||||
'note' => NoteModel::getNote($note_id)
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* This method controls what happens when you move to /note/editSave in your app.
|
||||
* Edits a note (performs the editing after form submit).
|
||||
* POST request.
|
||||
*/
|
||||
public function editSave()
|
||||
{
|
||||
NoteModel::updateNote(Request::post('note_id'), Request::post('note_text'));
|
||||
Redirect::to('note');
|
||||
}
|
||||
|
||||
/**
|
||||
* This method controls what happens when you move to /note/delete(/XX) in your app.
|
||||
* Deletes a note. In a real application a deletion via GET/URL is not recommended, but for demo purposes it's
|
||||
* totally okay.
|
||||
* @param int $note_id id of the note
|
||||
*/
|
||||
public function delete($note_id)
|
||||
{
|
||||
NoteModel::deleteNote($note_id);
|
||||
Redirect::to('note');
|
||||
}
|
||||
}
|
||||
39
application/controller/ProfileController.php
Normal file
39
application/controller/ProfileController.php
Normal file
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
class ProfileController extends Controller
|
||||
{
|
||||
/**
|
||||
* Construct this object by extending the basic Controller class
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
/**
|
||||
* This method controls what happens when you move to /overview/index in your app.
|
||||
* Shows a list of all users.
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
$this->View->render('profile/index', array(
|
||||
'users' => UserModel::getPublicProfilesOfAllUsers())
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method controls what happens when you move to /overview/showProfile in your app.
|
||||
* Shows the (public) details of the selected user.
|
||||
* @param $user_id int id the the user
|
||||
*/
|
||||
public function showProfile($user_id)
|
||||
{
|
||||
if (isset($user_id)) {
|
||||
$this->View->render('profile/showProfile', array(
|
||||
'user' => UserModel::getPublicProfileOfUser($user_id))
|
||||
);
|
||||
} else {
|
||||
Redirect::home();
|
||||
}
|
||||
}
|
||||
}
|
||||
74
application/controller/RegisterController.php
Normal file
74
application/controller/RegisterController.php
Normal file
@@ -0,0 +1,74 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* RegisterController
|
||||
* Register new user
|
||||
*/
|
||||
class RegisterController extends Controller
|
||||
{
|
||||
/**
|
||||
* Construct this object by extending the basic Controller class. The parent::__construct thing is necessary to
|
||||
* put checkAuthentication in here to make an entire controller only usable for logged-in users (for sure not
|
||||
* needed in the RegisterController).
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
/**
|
||||
* Register page
|
||||
* Show the register form, but redirect to main-page if user is already logged-in
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
if (LoginModel::isUserLoggedIn()) {
|
||||
Redirect::home();
|
||||
} else {
|
||||
$this->View->render('register/index');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Register page action
|
||||
* POST-request after form submit
|
||||
*/
|
||||
public function register_action()
|
||||
{
|
||||
$registration_successful = RegistrationModel::registerNewUser();
|
||||
|
||||
if ($registration_successful) {
|
||||
Redirect::to('login/index');
|
||||
} else {
|
||||
Redirect::to('register/index');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify user after activation mail link opened
|
||||
* @param int $user_id user's id
|
||||
* @param string $user_activation_verification_code user's verification token
|
||||
*/
|
||||
public function verify($user_id, $user_activation_verification_code)
|
||||
{
|
||||
if (isset($user_id) && isset($user_activation_verification_code)) {
|
||||
RegistrationModel::verifyNewUser($user_id, $user_activation_verification_code);
|
||||
$this->View->render('register/verify');
|
||||
} else {
|
||||
Redirect::to('login/index');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a captcha, write the characters into $_SESSION['captcha'] and returns a real image which will be used
|
||||
* like this: <img src="......./login/showCaptcha" />
|
||||
* IMPORTANT: As this action is called via <img ...> AFTER the real application has finished executing (!), the
|
||||
* SESSION["captcha"] has no content when the application is loaded. The SESSION["captcha"] gets filled at the
|
||||
* moment the end-user requests the <img .. >
|
||||
* Maybe refactor this sometime.
|
||||
*/
|
||||
public function showCaptcha()
|
||||
{
|
||||
CaptchaModel::generateAndShowCaptcha();
|
||||
}
|
||||
}
|
||||
157
application/controller/UserController.php
Normal file
157
application/controller/UserController.php
Normal file
@@ -0,0 +1,157 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* UserController
|
||||
* Controls everything that is user-related
|
||||
*/
|
||||
class UserController extends Controller
|
||||
{
|
||||
/**
|
||||
* Construct this object by extending the basic Controller class.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
// VERY IMPORTANT: All controllers/areas that should only be usable by logged-in users
|
||||
// need this line! Otherwise not-logged in users could do actions.
|
||||
Auth::checkAuthentication();
|
||||
}
|
||||
|
||||
/**
|
||||
* Show user's PRIVATE profile
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
$this->View->render('user/index', array(
|
||||
'user_name' => Session::get('user_name'),
|
||||
'user_email' => Session::get('user_email'),
|
||||
'user_gravatar_image_url' => Session::get('user_gravatar_image_url'),
|
||||
'user_avatar_file' => Session::get('user_avatar_file'),
|
||||
'user_account_type' => Session::get('user_account_type')
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* Show edit-my-username page
|
||||
*/
|
||||
public function editUsername()
|
||||
{
|
||||
$this->View->render('user/editUsername');
|
||||
}
|
||||
|
||||
/**
|
||||
* Edit user name (perform the real action after form has been submitted)
|
||||
*/
|
||||
public function editUsername_action()
|
||||
{
|
||||
// check if csrf token is valid
|
||||
if (!Csrf::isTokenValid()) {
|
||||
LoginModel::logout();
|
||||
Redirect::home();
|
||||
exit();
|
||||
}
|
||||
|
||||
UserModel::editUserName(Request::post('user_name'));
|
||||
Redirect::to('user/editUsername');
|
||||
}
|
||||
|
||||
/**
|
||||
* Show edit-my-user-email page
|
||||
*/
|
||||
public function editUserEmail()
|
||||
{
|
||||
$this->View->render('user/editUserEmail');
|
||||
}
|
||||
|
||||
/**
|
||||
* Edit user email (perform the real action after form has been submitted)
|
||||
*/
|
||||
// make this POST
|
||||
public function editUserEmail_action()
|
||||
{
|
||||
UserModel::editUserEmail(Request::post('user_email'));
|
||||
Redirect::to('user/editUserEmail');
|
||||
}
|
||||
|
||||
/**
|
||||
* Edit avatar
|
||||
*/
|
||||
public function editAvatar()
|
||||
{
|
||||
$this->View->render('user/editAvatar', array(
|
||||
'avatar_file_path' => AvatarModel::getPublicUserAvatarFilePathByUserId(Session::get('user_id'))
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform the upload of the avatar
|
||||
* POST-request
|
||||
*/
|
||||
public function uploadAvatar_action()
|
||||
{
|
||||
AvatarModel::createAvatar();
|
||||
Redirect::to('user/editAvatar');
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete the current user's avatar
|
||||
*/
|
||||
public function deleteAvatar_action()
|
||||
{
|
||||
AvatarModel::deleteAvatar(Session::get("user_id"));
|
||||
Redirect::to('user/editAvatar');
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the change-account-type page
|
||||
*/
|
||||
public function changeUserRole()
|
||||
{
|
||||
$this->View->render('user/changeUserRole');
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform the account-type changing
|
||||
* POST-request
|
||||
*/
|
||||
public function changeUserRole_action()
|
||||
{
|
||||
if (Request::post('user_account_upgrade')) {
|
||||
// "2" is quick & dirty account type 2, something like "premium user" maybe. you got the idea :)
|
||||
UserRoleModel::changeUserRole(2);
|
||||
}
|
||||
|
||||
if (Request::post('user_account_downgrade')) {
|
||||
// "1" is quick & dirty account type 1, something like "basic user" maybe.
|
||||
UserRoleModel::changeUserRole(1);
|
||||
}
|
||||
|
||||
Redirect::to('user/changeUserRole');
|
||||
}
|
||||
|
||||
/**
|
||||
* Password Change Page
|
||||
*/
|
||||
public function changePassword()
|
||||
{
|
||||
$this->View->render('user/changePassword');
|
||||
}
|
||||
|
||||
/**
|
||||
* Password Change Action
|
||||
* Submit form, if retured positive redirect to index, otherwise show the changePassword page again
|
||||
*/
|
||||
public function changePassword_action()
|
||||
{
|
||||
$result = PasswordResetModel::changePassword(
|
||||
Session::get('user_name'), Request::post('user_password_current'),
|
||||
Request::post('user_password_new'), Request::post('user_password_repeat')
|
||||
);
|
||||
|
||||
if($result)
|
||||
Redirect::to('user/index');
|
||||
else
|
||||
Redirect::to('user/changePassword');
|
||||
}
|
||||
}
|
||||
106
application/core/Application.php
Normal file
106
application/core/Application.php
Normal file
@@ -0,0 +1,106 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Class Application
|
||||
* The heart of the application
|
||||
*/
|
||||
class Application
|
||||
{
|
||||
/** @var mixed Instance of the controller */
|
||||
private $controller;
|
||||
|
||||
/** @var array URL parameters, will be passed to used controller-method */
|
||||
private $parameters = array();
|
||||
|
||||
/** @var string Just the name of the controller, useful for checks inside the view ("where am I ?") */
|
||||
private $controller_name;
|
||||
|
||||
/** @var string Just the name of the controller's method, useful for checks inside the view ("where am I ?") */
|
||||
private $action_name;
|
||||
|
||||
/**
|
||||
* Start the application, analyze URL elements, call according controller/method or relocate to fallback location
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
// create array with URL parts in $url
|
||||
$this->splitUrl();
|
||||
|
||||
// creates controller and action names (from URL input)
|
||||
$this->createControllerAndActionNames();
|
||||
|
||||
// does such a controller exist ?
|
||||
if (file_exists(Config::get('PATH_CONTROLLER') . $this->controller_name . '.php')) {
|
||||
|
||||
// load this file and create this controller
|
||||
// example: if controller would be "car", then this line would translate into: $this->car = new car();
|
||||
require Config::get('PATH_CONTROLLER') . $this->controller_name . '.php';
|
||||
$this->controller = new $this->controller_name();
|
||||
|
||||
// check are controller and method existing and callable?
|
||||
if (is_callable(array($this->controller, $this->action_name))) {
|
||||
if (!empty($this->parameters)) {
|
||||
// call the method and pass arguments to it
|
||||
call_user_func_array(array($this->controller, $this->action_name), $this->parameters);
|
||||
} else {
|
||||
// if no parameters are given, just call the method without parameters, like $this->index->index();
|
||||
$this->controller->{$this->action_name}();
|
||||
}
|
||||
} else {
|
||||
// load 404 error page
|
||||
require Config::get('PATH_CONTROLLER') . 'ErrorController.php';
|
||||
$this->controller = new ErrorController;
|
||||
$this->controller->error404();
|
||||
}
|
||||
} else {
|
||||
// load 404 error page
|
||||
require Config::get('PATH_CONTROLLER') . 'ErrorController.php';
|
||||
$this->controller = new ErrorController;
|
||||
$this->controller->error404();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get and split the URL
|
||||
*/
|
||||
private function splitUrl()
|
||||
{
|
||||
if (Request::get('url')) {
|
||||
|
||||
// split URL
|
||||
$url = trim(Request::get('url'), '/');
|
||||
$url = filter_var($url, FILTER_SANITIZE_URL);
|
||||
$url = explode('/', $url);
|
||||
|
||||
// put URL parts into according properties
|
||||
$this->controller_name = isset($url[0]) ? $url[0] : null;
|
||||
$this->action_name = isset($url[1]) ? $url[1] : null;
|
||||
|
||||
// remove controller name and action name from the split URL
|
||||
unset($url[0], $url[1]);
|
||||
|
||||
// rebase array keys and store the URL parameters
|
||||
$this->parameters = array_values($url);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if controller and action names are given. If not, default values are put into the properties.
|
||||
* Also renames controller to usable name.
|
||||
*/
|
||||
private function createControllerAndActionNames()
|
||||
{
|
||||
// check for controller: no controller given ? then make controller = default controller (from config)
|
||||
if (!$this->controller_name) {
|
||||
$this->controller_name = Config::get('DEFAULT_CONTROLLER');
|
||||
}
|
||||
|
||||
// check for action: no action given ? then make action = default action (from config)
|
||||
if (!$this->action_name OR (strlen($this->action_name) == 0)) {
|
||||
$this->action_name = Config::get('DEFAULT_ACTION');
|
||||
}
|
||||
|
||||
// rename controller name to real controller class/file name ("index" to "IndexController")
|
||||
$this->controller_name = ucwords($this->controller_name) . 'Controller';
|
||||
}
|
||||
}
|
||||
82
application/core/Auth.php
Normal file
82
application/core/Auth.php
Normal file
@@ -0,0 +1,82 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Class Auth
|
||||
* Checks if user is logged in, if not then sends the user to "yourdomain.com/login".
|
||||
* Auth::checkAuthentication() can be used in the constructor of a controller (to make the
|
||||
* entire controller only visible for logged-in users) or inside a controller-method to make only this part of the
|
||||
* application available for logged-in users.
|
||||
*/
|
||||
class Auth
|
||||
{
|
||||
/**
|
||||
* The normal authentication flow, just check if the user is logged in (by looking into the session).
|
||||
* If user is not, then he will be redirected to login page and the application is hard-stopped via exit().
|
||||
*/
|
||||
public static function checkAuthentication()
|
||||
{
|
||||
// initialize the session (if not initialized yet)
|
||||
Session::init();
|
||||
|
||||
// self::checkSessionConcurrency();
|
||||
|
||||
// if user is NOT logged in...
|
||||
// (if user IS logged in the application will not run the code below and therefore just go on)
|
||||
if (!Session::userIsLoggedIn()) {
|
||||
|
||||
// ... then treat user as "not logged in", destroy session, redirect to login page
|
||||
Session::destroy();
|
||||
|
||||
// send the user to the login form page, but also add the current page's URI (the part after the base URL)
|
||||
// as a parameter argument, making it possible to send the user back to where he/she came from after a
|
||||
// successful login
|
||||
header('location: ' . Config::get('URL') . 'login?redirect=' . urlencode($_SERVER['REQUEST_URI']));
|
||||
|
||||
// to prevent fetching views via cURL (which "ignores" the header-redirect above) we leave the application
|
||||
// the hard way, via exit(). @see https://github.com/panique/php-login/issues/453
|
||||
// this is not optimal and will be fixed in future releases
|
||||
exit();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The admin authentication flow, just check if the user is logged in (by looking into the session) AND has
|
||||
* user role type 7 (currently there's only type 1 (normal user), type 2 (premium user) and 7 (admin)).
|
||||
* If user is not, then he will be redirected to login page and the application is hard-stopped via exit().
|
||||
* Using this method makes only sense in controllers that should only be used by admins.
|
||||
*/
|
||||
public static function checkAdminAuthentication()
|
||||
{
|
||||
// initialize the session (if not initialized yet)
|
||||
Session::init();
|
||||
|
||||
// self::checkSessionConcurrency();
|
||||
|
||||
// if user is not logged in or is not an admin (= not role type 7)
|
||||
if (!Session::userIsLoggedIn() || Session::get("user_account_type") != 7) {
|
||||
|
||||
// ... then treat user as "not logged in", destroy session, redirect to login page
|
||||
Session::destroy();
|
||||
header('location: ' . Config::get('URL') . 'login');
|
||||
|
||||
// to prevent fetching views via cURL (which "ignores" the header-redirect above) we leave the application
|
||||
// the hard way, via exit(). @see https://github.com/panique/php-login/issues/453
|
||||
// this is not optimal and will be fixed in future releases
|
||||
exit();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Detects if there is concurrent session (i.e. another user logged in with the same current user credentials),
|
||||
* If so, then logout.
|
||||
*/
|
||||
public static function checkSessionConcurrency(){
|
||||
if(Session::userIsLoggedIn()){
|
||||
if(Session::isConcurrentSessionExists()){
|
||||
LoginModel::logout();
|
||||
Redirect::home();
|
||||
exit();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
23
application/core/Config.php
Normal file
23
application/core/Config.php
Normal file
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
class Config
|
||||
{
|
||||
// this is public to allow better Unit Testing
|
||||
public static $config;
|
||||
|
||||
public static function get($key)
|
||||
{
|
||||
if (!self::$config) {
|
||||
|
||||
$config_file = '../application/config/config.' . Environment::get() . '.php';
|
||||
|
||||
if (!file_exists($config_file)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
self::$config = require $config_file;
|
||||
}
|
||||
|
||||
return self::$config[$key];
|
||||
}
|
||||
}
|
||||
34
application/core/Controller.php
Normal file
34
application/core/Controller.php
Normal file
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This is the "base controller class". All other "real" controllers extend this class.
|
||||
* Whenever a controller is created, we also
|
||||
* 1. initialize a session
|
||||
* 2. check if the user is not logged in anymore (session timeout) but has a cookie
|
||||
*/
|
||||
class Controller
|
||||
{
|
||||
/** @var View View The view object */
|
||||
public $View;
|
||||
|
||||
/**
|
||||
* Construct the (base) controller. This happens when a real controller is constructed, like in
|
||||
* the constructor of IndexController when it says: parent::__construct();
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
// always initialize a session
|
||||
Session::init();
|
||||
|
||||
// check session concurrency
|
||||
Auth::checkSessionConcurrency();
|
||||
|
||||
// user is not logged in but has remember-me-cookie ? then try to login with cookie ("remember me" feature)
|
||||
if (!Session::userIsLoggedIn() AND Request::cookie('remember_me')) {
|
||||
header('location: ' . Config::get('URL') . 'login/loginWithCookie');
|
||||
}
|
||||
|
||||
// create a view object to be able to use it inside a controller, like $this->View->render();
|
||||
$this->View = new View();
|
||||
}
|
||||
}
|
||||
60
application/core/Csrf.php
Normal file
60
application/core/Csrf.php
Normal file
@@ -0,0 +1,60 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Cross Site Request Forgery Class
|
||||
*
|
||||
*/
|
||||
|
||||
/**
|
||||
* Instructions:
|
||||
*
|
||||
* At your form, before the submit button put:
|
||||
* <input type="hidden" name="csrf_token" value="<?= Csrf::makeToken(); ?>" />
|
||||
*
|
||||
* This validation needed in the controller action method to validate CSRF token submitted with the form:
|
||||
*
|
||||
* if (!Csrf::isTokenValid()) {
|
||||
* LoginModel::logout();
|
||||
* Redirect::home();
|
||||
* exit();
|
||||
* }
|
||||
*
|
||||
* To get simpler code it might be better to put the logout, redirect, exit into an own (static) method.
|
||||
*/
|
||||
class Csrf
|
||||
{
|
||||
/**
|
||||
* get CSRF token and generate a new one if expired
|
||||
*
|
||||
* @access public
|
||||
* @static static method
|
||||
* @return string
|
||||
*/
|
||||
public static function makeToken()
|
||||
{
|
||||
// token is valid for 1 day
|
||||
$max_time = 60 * 60 * 24;
|
||||
$stored_time = Session::get('csrf_token_time');
|
||||
$csrf_token = Session::get('csrf_token');
|
||||
|
||||
if ($max_time + $stored_time <= time() || empty($csrf_token)) {
|
||||
Session::set('csrf_token', md5(uniqid(rand(), true)));
|
||||
Session::set('csrf_token_time', time());
|
||||
}
|
||||
|
||||
return Session::get('csrf_token');
|
||||
}
|
||||
|
||||
/**
|
||||
* checks if CSRF token in session is same as in the form submitted
|
||||
*
|
||||
* @access public
|
||||
* @static static method
|
||||
* @return bool
|
||||
*/
|
||||
public static function isTokenValid()
|
||||
{
|
||||
$token = Request::post('csrf_token');
|
||||
return $token === Session::get('csrf_token') && !empty($token);
|
||||
}
|
||||
}
|
||||
64
application/core/DatabaseFactory.php
Normal file
64
application/core/DatabaseFactory.php
Normal file
@@ -0,0 +1,64 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Class DatabaseFactory
|
||||
*
|
||||
* Use it like this:
|
||||
* $database = DatabaseFactory::getFactory()->getConnection();
|
||||
*
|
||||
* That's my personal favourite when creating a database connection.
|
||||
* It's a slightly modified version of Jon Raphaelson's excellent answer on StackOverflow:
|
||||
* http://stackoverflow.com/questions/130878/global-or-singleton-for-database-connection
|
||||
*
|
||||
* Full quote from the answer:
|
||||
*
|
||||
* "Then, in 6 months when your app is super famous and getting dugg and slashdotted and you decide you need more than
|
||||
* a single connection, all you have to do is implement some pooling in the getConnection() method. Or if you decide
|
||||
* that you want a wrapper that implements SQL logging, you can pass a PDO subclass. Or if you decide you want a new
|
||||
* connection on every invocation, you can do do that. It's flexible, instead of rigid."
|
||||
*
|
||||
* Thanks! Big up, mate!
|
||||
*/
|
||||
class DatabaseFactory
|
||||
{
|
||||
private static $factory;
|
||||
private $database;
|
||||
|
||||
public static function getFactory()
|
||||
{
|
||||
if (!self::$factory) {
|
||||
self::$factory = new DatabaseFactory();
|
||||
}
|
||||
return self::$factory;
|
||||
}
|
||||
|
||||
public function getConnection() {
|
||||
if (!$this->database) {
|
||||
|
||||
/**
|
||||
* Check DB connection in try/catch block. Also when PDO is not constructed properly,
|
||||
* prevent to exposing database host, username and password in plain text as:
|
||||
* PDO->__construct('mysql:host=127....', 'root', '12345678', Array)
|
||||
* by throwing custom error message
|
||||
*/
|
||||
try {
|
||||
$options = array(PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_OBJ, PDO::ATTR_ERRMODE => PDO::ERRMODE_WARNING);
|
||||
$this->database = new PDO(
|
||||
Config::get('DB_TYPE') . ':host=' . Config::get('DB_HOST') . ';dbname=' .
|
||||
Config::get('DB_NAME') . ';port=' . Config::get('DB_PORT') . ';charset=' . Config::get('DB_CHARSET'),
|
||||
Config::get('DB_USER'), Config::get('DB_PASS'), $options
|
||||
);
|
||||
} catch (PDOException $e) {
|
||||
|
||||
// Echo custom message. Echo error code gives you some info.
|
||||
echo 'Database connection can not be estabilished. Please try again later.' . '<br>';
|
||||
echo 'Error code: ' . $e->getCode();
|
||||
|
||||
// Stop application :(
|
||||
// No connection, reached limit connections etc. so no point to keep it running
|
||||
exit;
|
||||
}
|
||||
}
|
||||
return $this->database;
|
||||
}
|
||||
}
|
||||
146
application/core/Encryption.php
Normal file
146
application/core/Encryption.php
Normal file
@@ -0,0 +1,146 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Encryption and Decryption Class
|
||||
*
|
||||
*/
|
||||
class Encryption
|
||||
{
|
||||
|
||||
/**
|
||||
* Cipher algorithm
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const CIPHER = 'aes-256-cbc';
|
||||
|
||||
/**
|
||||
* Hash function
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const HASH_FUNCTION = 'sha256';
|
||||
|
||||
/**
|
||||
* constructor for Encryption object.
|
||||
*
|
||||
* @access private
|
||||
*/
|
||||
private function __construct()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Encrypt a string.
|
||||
*
|
||||
* @access public
|
||||
* @static static method
|
||||
* @param string $plain
|
||||
* @return string
|
||||
* @throws Exception If functions don't exists
|
||||
*/
|
||||
public static function encrypt($plain)
|
||||
{
|
||||
if (!function_exists('openssl_cipher_iv_length') ||
|
||||
!function_exists('openssl_random_pseudo_bytes') ||
|
||||
!function_exists('openssl_encrypt')) {
|
||||
|
||||
throw new Exception('Encryption function doesn\'t exist');
|
||||
}
|
||||
|
||||
// generate initialization vector,
|
||||
// this will make $iv different every time,
|
||||
// so, encrypted string will be also different.
|
||||
$iv_size = openssl_cipher_iv_length(self::CIPHER);
|
||||
$iv = openssl_random_pseudo_bytes($iv_size);
|
||||
|
||||
// generate key for authentication using ENCRYPTION_KEY & HMAC_SALT
|
||||
$key = mb_substr(hash(self::HASH_FUNCTION, Config::get('ENCRYPTION_KEY') . Config::get('HMAC_SALT')), 0, 32, '8bit');
|
||||
|
||||
// append initialization vector
|
||||
$encrypted_string = openssl_encrypt($plain, self::CIPHER, $key, OPENSSL_RAW_DATA, $iv);
|
||||
$ciphertext = $iv . $encrypted_string;
|
||||
|
||||
// apply the HMAC
|
||||
$hmac = hash_hmac('sha256', $ciphertext, $key);
|
||||
|
||||
return $hmac . $ciphertext;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decrypted a string.
|
||||
*
|
||||
* @access public
|
||||
* @static static method
|
||||
* @param string $ciphertext
|
||||
* @return string
|
||||
* @throws Exception If $ciphertext is empty, or If functions don't exists
|
||||
*/
|
||||
public static function decrypt($ciphertext)
|
||||
{
|
||||
if (empty($ciphertext)) {
|
||||
throw new Exception('The String to decrypt can\'t be empty');
|
||||
}
|
||||
|
||||
if (!function_exists('openssl_cipher_iv_length') ||
|
||||
!function_exists('openssl_decrypt')) {
|
||||
|
||||
throw new Exception('Encryption function doesn\'t exist');
|
||||
}
|
||||
|
||||
// generate key used for authentication using ENCRYPTION_KEY & HMAC_SALT
|
||||
$key = mb_substr(hash(self::HASH_FUNCTION, Config::get('ENCRYPTION_KEY') . Config::get('HMAC_SALT')), 0, 32, '8bit');
|
||||
|
||||
// split cipher into: hmac, cipher & iv
|
||||
$macSize = 64;
|
||||
$hmac = mb_substr($ciphertext, 0, $macSize, '8bit');
|
||||
$iv_cipher = mb_substr($ciphertext, $macSize, null, '8bit');
|
||||
|
||||
// generate original hmac & compare it with the one in $ciphertext
|
||||
$originalHmac = hash_hmac('sha256', $iv_cipher, $key);
|
||||
if (!self::hashEquals($hmac, $originalHmac)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// split out the initialization vector and cipher
|
||||
$iv_size = openssl_cipher_iv_length(self::CIPHER);
|
||||
$iv = mb_substr($iv_cipher, 0, $iv_size, '8bit');
|
||||
$cipher = mb_substr($iv_cipher, $iv_size, null, '8bit');
|
||||
|
||||
return openssl_decrypt($cipher, self::CIPHER, $key, OPENSSL_RAW_DATA, $iv);
|
||||
}
|
||||
|
||||
/**
|
||||
* A timing attack resistant comparison.
|
||||
*
|
||||
* @access private
|
||||
* @static static method
|
||||
* @param string $hmac The hmac from the ciphertext being decrypted.
|
||||
* @param string $compare The comparison hmac.
|
||||
* @return bool
|
||||
* @see https://github.com/sarciszewski/php-future/blob/bd6c91fb924b2b35a3e4f4074a642868bd051baf/src/Security.php#L36
|
||||
*/
|
||||
private static function hashEquals($hmac, $compare)
|
||||
{
|
||||
if (function_exists('hash_equals')) {
|
||||
return hash_equals($hmac, $compare);
|
||||
}
|
||||
|
||||
// if hash_equals() is not available,
|
||||
// then use the following snippet.
|
||||
// It's equivalent to hash_equals() in PHP 5.6.
|
||||
$hashLength = mb_strlen($hmac, '8bit');
|
||||
$compareLength = mb_strlen($compare, '8bit');
|
||||
|
||||
if ($hashLength !== $compareLength) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$result = 0;
|
||||
for ($i = 0; $i < $hashLength; $i++) {
|
||||
$result |= (ord($hmac[$i]) ^ ord($compare[$i]));
|
||||
}
|
||||
|
||||
return $result === 0;
|
||||
}
|
||||
}
|
||||
18
application/core/Environment.php
Normal file
18
application/core/Environment.php
Normal file
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Class Environment
|
||||
*
|
||||
* Extremely simple way to get the environment, everywhere inside your application.
|
||||
* Extend this the way you want.
|
||||
*/
|
||||
class Environment
|
||||
{
|
||||
public static function get()
|
||||
{
|
||||
// if APPLICATION_ENV constant exists (set in Apache configs)
|
||||
// then return content of APPLICATION_ENV
|
||||
// else return "development"
|
||||
return (getenv('APPLICATION_ENV') ? getenv('APPLICATION_ENV') : "development");
|
||||
}
|
||||
}
|
||||
77
application/core/Filter.php
Normal file
77
application/core/Filter.php
Normal file
@@ -0,0 +1,77 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Class Filter
|
||||
*
|
||||
* This is the place to put filters, usually methods that cleans, sorts and, well, filters stuff.
|
||||
*/
|
||||
class Filter
|
||||
{
|
||||
/**
|
||||
* The XSS filter: This simply removes "code" from any data, used to prevent Cross-Site Scripting Attacks.
|
||||
*
|
||||
* A very simple introduction: Let's say an attackers changes its username from "John" to these lines:
|
||||
* "<script>var http = new XMLHttpRequest(); http.open('POST', 'example.com/my_account/delete.php', true);</script>"
|
||||
* This means, every user's browser would render "John" anymore, instead interpreting this JavaScript code, calling
|
||||
* the delete.php, in this case inside the project, in worse scenarios something like performing a bank transaction
|
||||
* or sending your cookie data (containing your remember-me-token) to somebody else.
|
||||
*
|
||||
* What is XSS ?
|
||||
* @see http://phpsecurity.readthedocs.org/en/latest/Cross-Site-Scripting-%28XSS%29.html
|
||||
*
|
||||
* Deeper information:
|
||||
* @see https://www.owasp.org/index.php/XSS_Filter_Evasion_Cheat_Sheet
|
||||
*
|
||||
* XSSFilter expects a value, checks if the value is a string, and if so, encodes typical script tag chars to
|
||||
* harmless HTML (you'll see the code, it wil not be interpreted). Then the method checks if the value is an array,
|
||||
* or an object and if so, makes sure all its string content is encoded (recursive call on its values).
|
||||
* Note that this method uses reference to the assed variable, not a copy, meaning you can use this methods like this:
|
||||
*
|
||||
* CORRECT: Filter::XSSFilter($myVariable);
|
||||
* WRONG: $myVariable = Filter::XSSFilter($myVariable);
|
||||
*
|
||||
* This works like some other popular PHP functions, for example sort().
|
||||
* @see http://php.net/manual/en/function.sort.php
|
||||
*
|
||||
* @see http://stackoverflow.com/questions/1676897/what-does-it-mean-to-start-a-php-function-with-an-ampersand
|
||||
* @see http://php.net/manual/en/language.references.pass.php
|
||||
*
|
||||
* FYI: htmlspecialchars() does this (from PHP docs):
|
||||
*
|
||||
* '&' (ampersand) becomes '&'
|
||||
* '"' (double quote) becomes '"' when ENT_NOQUOTES is not set.
|
||||
* "'" (single quote) becomes ''' (or ') only when ENT_QUOTES is set.
|
||||
* '<' (less than) becomes '<'
|
||||
* '>' (greater than) becomes '>'
|
||||
*
|
||||
* @see http://www.php.net/manual/en/function.htmlspecialchars.php
|
||||
*
|
||||
* @param $value The value to be filtered
|
||||
* @return mixed
|
||||
*/
|
||||
public static function XSSFilter(&$value)
|
||||
{
|
||||
// if argument is a string, filters that string
|
||||
if (is_string($value)) {
|
||||
$value = htmlspecialchars($value, ENT_QUOTES, 'UTF-8');
|
||||
|
||||
// if argument is an array or an object,
|
||||
// recursivly filters its content
|
||||
} else if (is_array($value) || is_object($value)) {
|
||||
|
||||
/**
|
||||
* Make sure the element is passed by reference,
|
||||
* In PHP 7, foreach does not use the internal array pointer.
|
||||
* In order to be able to directly modify array elements within the loop
|
||||
* precede $value with &. In that case the value will be assigned by reference.
|
||||
* @see http://php.net/manual/en/control-structures.foreach.php
|
||||
*/
|
||||
foreach ($value as &$valueInValue) {
|
||||
self::XSSFilter($valueInValue);
|
||||
}
|
||||
}
|
||||
|
||||
// other types are untouched
|
||||
return $value;
|
||||
}
|
||||
}
|
||||
154
application/core/Mail.php
Normal file
154
application/core/Mail.php
Normal file
@@ -0,0 +1,154 @@
|
||||
<?php
|
||||
|
||||
/* Using PHPMailer's namespace */
|
||||
use PHPMailer\PHPMailer\PHPMailer;
|
||||
|
||||
/**
|
||||
* Class Mail
|
||||
*
|
||||
* Handles everything regarding mail-sending.
|
||||
*/
|
||||
class Mail
|
||||
{
|
||||
/** @var mixed variable to collect errors */
|
||||
private $error;
|
||||
|
||||
/**
|
||||
* Try to send a mail by using PHP's native mail() function.
|
||||
* Please note that not PHP itself will send a mail, it's just a wrapper for Linux's sendmail or other mail tools
|
||||
*
|
||||
* Good guideline on how to send mails natively with mail():
|
||||
* @see http://stackoverflow.com/a/24644450/1114320
|
||||
* @see http://www.php.net/manual/en/function.mail.php
|
||||
*/
|
||||
public function sendMailWithNativeMailFunction()
|
||||
{
|
||||
// no code yet, so we just return something to make IDEs and code analyzer tools happy
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to send a mail by using SwiftMailer.
|
||||
* Make sure you have loaded SwiftMailer via Composer.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function sendMailWithSwiftMailer()
|
||||
{
|
||||
// no code yet, so we just return something to make IDEs and code analyzer tools happy
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to send a mail by using PHPMailer.
|
||||
* Make sure you have loaded PHPMailer via Composer.
|
||||
* Depending on your EMAIL_USE_SMTP setting this will work via SMTP credentials or via native mail()
|
||||
*
|
||||
* @param $user_email
|
||||
* @param $from_email
|
||||
* @param $from_name
|
||||
* @param $subject
|
||||
* @param $body
|
||||
*
|
||||
* @return bool
|
||||
* @throws Exception
|
||||
* @throws phpmailerException
|
||||
*/
|
||||
public function sendMailWithPHPMailer($user_email, $from_email, $from_name, $subject, $body)
|
||||
{
|
||||
$mail = new PHPMailer;
|
||||
|
||||
// you should use UTF-8 to avoid encoding issues
|
||||
$mail->CharSet = 'UTF-8';
|
||||
|
||||
// if you want to send mail via PHPMailer using SMTP credentials
|
||||
if (Config::get('EMAIL_USE_SMTP')) {
|
||||
|
||||
// set PHPMailer to use SMTP
|
||||
$mail->IsSMTP();
|
||||
|
||||
// 0 = off, 1 = commands, 2 = commands and data, perfect to see SMTP errors
|
||||
$mail->SMTPDebug = 0;
|
||||
|
||||
// enable SMTP authentication
|
||||
$mail->SMTPAuth = Config::get('EMAIL_SMTP_AUTH');
|
||||
|
||||
// encryption
|
||||
if (Config::get('EMAIL_SMTP_ENCRYPTION')) {
|
||||
$mail->SMTPSecure = Config::get('EMAIL_SMTP_ENCRYPTION');
|
||||
}
|
||||
|
||||
// set SMTP provider's credentials
|
||||
$mail->Host = Config::get('EMAIL_SMTP_HOST');
|
||||
$mail->Username = Config::get('EMAIL_SMTP_USERNAME');
|
||||
$mail->Password = Config::get('EMAIL_SMTP_PASSWORD');
|
||||
$mail->Port = Config::get('EMAIL_SMTP_PORT');
|
||||
|
||||
} else {
|
||||
|
||||
$mail->IsMail();
|
||||
}
|
||||
|
||||
// fill mail with data
|
||||
$mail->From = $from_email;
|
||||
$mail->FromName = $from_name;
|
||||
$mail->AddAddress($user_email);
|
||||
$mail->Subject = $subject;
|
||||
$mail->Body = $body;
|
||||
|
||||
// try to send mail, put result status (true/false into $wasSendingSuccessful)
|
||||
// I'm unsure if mail->send really returns true or false every time, tis method in PHPMailer is quite complex
|
||||
$wasSendingSuccessful = $mail->Send();
|
||||
|
||||
if ($wasSendingSuccessful) {
|
||||
return true;
|
||||
|
||||
} else {
|
||||
|
||||
// if not successful, copy errors into Mail's error property
|
||||
$this->error = $mail->ErrorInfo;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The main mail sending method, this simply calls a certain mail sending method depending on which mail provider
|
||||
* you've selected in the application's config.
|
||||
*
|
||||
* @param $user_email string email
|
||||
* @param $from_email string sender's email
|
||||
* @param $from_name string sender's name
|
||||
* @param $subject string subject
|
||||
* @param $body string full mail body text
|
||||
* @return bool the success status of the according mail sending method
|
||||
*/
|
||||
public function sendMail($user_email, $from_email, $from_name, $subject, $body)
|
||||
{
|
||||
if (Config::get('EMAIL_USED_MAILER') == "phpmailer") {
|
||||
|
||||
// returns true if successful, false if not
|
||||
return $this->sendMailWithPHPMailer(
|
||||
$user_email, $from_email, $from_name, $subject, $body
|
||||
);
|
||||
}
|
||||
|
||||
if (Config::get('EMAIL_USED_MAILER') == "swiftmailer") {
|
||||
return $this->sendMailWithSwiftMailer();
|
||||
}
|
||||
|
||||
if (Config::get('EMAIL_USED_MAILER') == "native") {
|
||||
return $this->sendMailWithNativeMailFunction();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The different mail sending methods write errors to the error property $this->error,
|
||||
* this method simply returns this error / error array.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function getError()
|
||||
{
|
||||
return $this->error;
|
||||
}
|
||||
}
|
||||
48
application/core/Redirect.php
Normal file
48
application/core/Redirect.php
Normal file
@@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Class Redirect
|
||||
*
|
||||
* Simple abstraction for redirecting the user to a certain page
|
||||
*/
|
||||
class Redirect
|
||||
{
|
||||
/**
|
||||
* To the last visited page before user logged in (useful when people are on a certain page inside your application
|
||||
* and then want to log in (to edit or comment something for example) and don't to be redirected to the main page).
|
||||
*
|
||||
* This is just a bulletproof version of Redirect::to(), redirecting to an ABSOLUTE URL path like
|
||||
* "http://www.mydomain.com/user/profile", useful as people had problems with the RELATIVE URL path generated
|
||||
* by Redirect::to() when using HUGE inside sub-folders.
|
||||
*
|
||||
* @param $path string
|
||||
*/
|
||||
public static function toPreviousViewedPageAfterLogin($path)
|
||||
{
|
||||
header('location: http://' . $_SERVER['HTTP_HOST'] . '/' . $path);
|
||||
}
|
||||
|
||||
/**
|
||||
* To the homepage
|
||||
*/
|
||||
public static function home()
|
||||
{
|
||||
header("location: " . Config::get('URL'));
|
||||
}
|
||||
|
||||
/**
|
||||
* To the defined page, uses a relative path (like "user/profile")
|
||||
*
|
||||
* Redirects to a RELATIVE path, like "user/profile" (which works very fine unless you are using HUGE inside tricky
|
||||
* sub-folder structures)
|
||||
*
|
||||
* @see https://github.com/panique/huge/issues/770
|
||||
* @see https://github.com/panique/huge/issues/754
|
||||
*
|
||||
* @param $path string
|
||||
*/
|
||||
public static function to($path)
|
||||
{
|
||||
header("location: " . Config::get('URL') . $path);
|
||||
}
|
||||
}
|
||||
64
application/core/Request.php
Normal file
64
application/core/Request.php
Normal file
@@ -0,0 +1,64 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This is under development. Expect changes!
|
||||
* Class Request
|
||||
* Abstracts the access to $_GET, $_POST and $_COOKIE, preventing direct access to these super-globals.
|
||||
* This makes PHP code quality analyzer tools very happy.
|
||||
* @see http://php.net/manual/en/reserved.variables.request.php
|
||||
*/
|
||||
class Request
|
||||
{
|
||||
/**
|
||||
* Gets/returns the value of a specific key of the POST super-global.
|
||||
* When using just Request::post('x') it will return the raw and untouched $_POST['x'], when using it like
|
||||
* Request::post('x', true) then it will return a trimmed and stripped $_POST['x'] !
|
||||
*
|
||||
* @param mixed $key key
|
||||
* @param bool $clean marker for optional cleaning of the var
|
||||
* @return mixed the key's value or nothing
|
||||
*/
|
||||
public static function post($key, $clean = false)
|
||||
{
|
||||
if (isset($_POST[$key])) {
|
||||
// we use the Ternary Operator here which saves the if/else block
|
||||
// @see http://davidwalsh.name/php-shorthand-if-else-ternary-operators
|
||||
return ($clean) ? trim(strip_tags($_POST[$key])) : $_POST[$key];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the state of a checkbox.
|
||||
*
|
||||
* @param mixed $key key
|
||||
* @return mixed state of the checkbox
|
||||
*/
|
||||
public static function postCheckbox($key)
|
||||
{
|
||||
return isset($_POST[$key]) ? 1 : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* gets/returns the value of a specific key of the GET super-global
|
||||
* @param mixed $key key
|
||||
* @return mixed the key's value or nothing
|
||||
*/
|
||||
public static function get($key)
|
||||
{
|
||||
if (isset($_GET[$key])) {
|
||||
return $_GET[$key];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* gets/returns the value of a specific key of the COOKIE super-global
|
||||
* @param mixed $key key
|
||||
* @return mixed the key's value or nothing
|
||||
*/
|
||||
public static function cookie($key)
|
||||
{
|
||||
if (isset($_COOKIE[$key])) {
|
||||
return $_COOKIE[$key];
|
||||
}
|
||||
}
|
||||
}
|
||||
135
application/core/Session.php
Normal file
135
application/core/Session.php
Normal file
@@ -0,0 +1,135 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Session class
|
||||
*
|
||||
* handles the session stuff. creates session when no one exists, sets and gets values, and closes the session
|
||||
* properly (=logout). Not to forget the check if the user is logged in or not.
|
||||
*/
|
||||
class Session
|
||||
{
|
||||
/**
|
||||
* starts the session
|
||||
*/
|
||||
public static function init()
|
||||
{
|
||||
// if no session exist, start the session
|
||||
if (session_id() == '') {
|
||||
session_start();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* sets a specific value to a specific key of the session
|
||||
*
|
||||
* @param mixed $key key
|
||||
* @param mixed $value value
|
||||
*/
|
||||
public static function set($key, $value)
|
||||
{
|
||||
$_SESSION[$key] = $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* gets/returns the value of a specific key of the session
|
||||
*
|
||||
* @param mixed $key Usually a string, right ?
|
||||
* @return mixed the key's value or nothing
|
||||
*/
|
||||
public static function get($key)
|
||||
{
|
||||
if (isset($_SESSION[$key])) {
|
||||
$value = $_SESSION[$key];
|
||||
|
||||
// filter the value for XSS vulnerabilities
|
||||
return Filter::XSSFilter($value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* adds a value as a new array element to the key.
|
||||
* useful for collecting error messages etc
|
||||
*
|
||||
* @param mixed $key
|
||||
* @param mixed $value
|
||||
*/
|
||||
public static function add($key, $value)
|
||||
{
|
||||
$_SESSION[$key][] = $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* deletes the session (= logs the user out)
|
||||
*/
|
||||
public static function destroy()
|
||||
{
|
||||
session_destroy();
|
||||
}
|
||||
|
||||
/**
|
||||
* update session id in database
|
||||
*
|
||||
* @access public
|
||||
* @static static method
|
||||
* @param string $userId
|
||||
* @param string $sessionId
|
||||
*/
|
||||
public static function updateSessionId($userId, $sessionId = null)
|
||||
{
|
||||
$database = DatabaseFactory::getFactory()->getConnection();
|
||||
$sql = "UPDATE users SET session_id = :session_id WHERE user_id = :user_id";
|
||||
|
||||
$query = $database->prepare($sql);
|
||||
$query->execute(array(':session_id' => $sessionId, ":user_id" => $userId));
|
||||
}
|
||||
|
||||
/**
|
||||
* checks for session concurrency
|
||||
*
|
||||
* This is done as the following:
|
||||
* UserA logs in with his session id('123') and it will be stored in the database.
|
||||
* Then, UserB logs in also using the same email and password of UserA from another PC,
|
||||
* and also store the session id('456') in the database
|
||||
*
|
||||
* Now, Whenever UserA performs any action,
|
||||
* You then check the session_id() against the last one stored in the database('456'),
|
||||
* If they don't match then log both of them out.
|
||||
*
|
||||
* @access public
|
||||
* @static static method
|
||||
* @return bool
|
||||
* @see Session::updateSessionId()
|
||||
* @see http://stackoverflow.com/questions/6126285/php-stop-concurrent-user-logins
|
||||
*/
|
||||
public static function isConcurrentSessionExists()
|
||||
{
|
||||
$session_id = session_id();
|
||||
$userId = Session::get('user_id');
|
||||
|
||||
if (isset($userId) && isset($session_id)) {
|
||||
|
||||
$database = DatabaseFactory::getFactory()->getConnection();
|
||||
$sql = "SELECT session_id FROM users WHERE user_id = :user_id LIMIT 1";
|
||||
|
||||
$query = $database->prepare($sql);
|
||||
$query->execute(array(":user_id" => $userId));
|
||||
|
||||
$result = $query->fetch();
|
||||
$userSessionId = !empty($result)? $result->session_id: null;
|
||||
|
||||
return $session_id !== $userSessionId;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the user is logged in or not
|
||||
*
|
||||
* @return bool user's login status
|
||||
*/
|
||||
public static function userIsLoggedIn()
|
||||
{
|
||||
return (self::get('user_logged_in') ? true : false);
|
||||
}
|
||||
}
|
||||
32
application/core/Text.php
Normal file
32
application/core/Text.php
Normal file
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
class Text
|
||||
{
|
||||
private static $texts;
|
||||
|
||||
public static function get($key, $data = null)
|
||||
{
|
||||
// if not $key
|
||||
if (!$key) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ($data) {
|
||||
foreach ($data as $var => $value) {
|
||||
${$var} = $value;
|
||||
}
|
||||
}
|
||||
|
||||
// load config file (this is only done once per application lifecycle)
|
||||
if (!self::$texts) {
|
||||
self::$texts = require('../application/config/texts.php');
|
||||
}
|
||||
|
||||
// check if array key exists
|
||||
if (!array_key_exists($key, self::$texts)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return self::$texts[$key];
|
||||
}
|
||||
}
|
||||
177
application/core/View.php
Normal file
177
application/core/View.php
Normal file
@@ -0,0 +1,177 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Class View
|
||||
* The part that handles all the output
|
||||
*/
|
||||
class View
|
||||
{
|
||||
/**
|
||||
* simply includes (=shows) the view. this is done from the controller. In the controller, you usually say
|
||||
* $this->view->render('help/index'); to show (in this example) the view index.php in the folder help.
|
||||
* Usually the Class and the method are the same like the view, but sometimes you need to show different views.
|
||||
* @param string $filename Path of the to-be-rendered view, usually folder/file(.php)
|
||||
* @param array $data Data to be used in the view
|
||||
*/
|
||||
public function render($filename, $data = null)
|
||||
{
|
||||
if ($data) {
|
||||
foreach ($data as $key => $value) {
|
||||
$this->{$key} = $value;
|
||||
}
|
||||
}
|
||||
|
||||
require Config::get('PATH_VIEW') . '_templates/header.php';
|
||||
require Config::get('PATH_VIEW') . $filename . '.php';
|
||||
require Config::get('PATH_VIEW') . '_templates/footer.php';
|
||||
}
|
||||
|
||||
/**
|
||||
* Similar to render, but accepts an array of separate views to render between the header and footer. Use like
|
||||
* the following: $this->view->renderMulti(array('help/index', 'help/banner'));
|
||||
* @param array $filenames Array of the paths of the to-be-rendered view, usually folder/file(.php) for each
|
||||
* @param array $data Data to be used in the view
|
||||
* @return bool
|
||||
*/
|
||||
public function renderMulti($filenames, $data = null)
|
||||
{
|
||||
if (!is_array($filenames)) {
|
||||
self::render($filenames, $data);
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($data) {
|
||||
foreach ($data as $key => $value) {
|
||||
$this->{$key} = $value;
|
||||
}
|
||||
}
|
||||
|
||||
require Config::get('PATH_VIEW') . '_templates/header.php';
|
||||
|
||||
foreach($filenames as $filename) {
|
||||
require Config::get('PATH_VIEW') . $filename . '.php';
|
||||
}
|
||||
|
||||
require Config::get('PATH_VIEW') . '_templates/footer.php';
|
||||
}
|
||||
|
||||
/**
|
||||
* Same like render(), but does not include header and footer
|
||||
* @param string $filename Path of the to-be-rendered view, usually folder/file(.php)
|
||||
* @param mixed $data Data to be used in the view
|
||||
*/
|
||||
public function renderWithoutHeaderAndFooter($filename, $data = null)
|
||||
{
|
||||
if ($data) {
|
||||
foreach ($data as $key => $value) {
|
||||
$this->{$key} = $value;
|
||||
}
|
||||
}
|
||||
|
||||
require Config::get('PATH_VIEW') . $filename . '.php';
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders pure JSON to the browser, useful for API construction
|
||||
* @param $data
|
||||
*/
|
||||
public function renderJSON($data)
|
||||
{
|
||||
header("Content-Type: application/json");
|
||||
echo json_encode($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* renders the feedback messages into the view
|
||||
*/
|
||||
public function renderFeedbackMessages()
|
||||
{
|
||||
// echo out the feedback messages (errors and success messages etc.),
|
||||
// they are in $_SESSION["feedback_positive"] and $_SESSION["feedback_negative"]
|
||||
require Config::get('PATH_VIEW') . '_templates/feedback.php';
|
||||
|
||||
// delete these messages (as they are not needed anymore and we want to avoid to show them twice
|
||||
Session::set('feedback_positive', null);
|
||||
Session::set('feedback_negative', null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the passed string is the currently active controller.
|
||||
* Useful for handling the navigation's active/non-active link.
|
||||
*
|
||||
* @param string $filename
|
||||
* @param string $navigation_controller
|
||||
*
|
||||
* @return bool Shows if the controller is used or not
|
||||
*/
|
||||
public static function checkForActiveController($filename, $navigation_controller)
|
||||
{
|
||||
$split_filename = explode("/", $filename);
|
||||
$active_controller = $split_filename[0];
|
||||
|
||||
if ($active_controller == $navigation_controller) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the passed string is the currently active controller-action (=method).
|
||||
* Useful for handling the navigation's active/non-active link.
|
||||
*
|
||||
* @param string $filename
|
||||
* @param string $navigation_action
|
||||
*
|
||||
* @return bool Shows if the action/method is used or not
|
||||
*/
|
||||
public static function checkForActiveAction($filename, $navigation_action)
|
||||
{
|
||||
$split_filename = explode("/", $filename);
|
||||
$active_action = $split_filename[1];
|
||||
|
||||
if ($active_action == $navigation_action) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the passed string is the currently active controller and controller-action.
|
||||
* Useful for handling the navigation's active/non-active link.
|
||||
*
|
||||
* @param string $filename
|
||||
* @param string $navigation_controller_and_action
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function checkForActiveControllerAndAction($filename, $navigation_controller_and_action)
|
||||
{
|
||||
$split_filename = explode("/", $filename);
|
||||
$active_controller = $split_filename[0];
|
||||
$active_action = $split_filename[1];
|
||||
|
||||
$split_filename = explode("/", $navigation_controller_and_action);
|
||||
$navigation_controller = $split_filename[0];
|
||||
$navigation_action = $split_filename[1];
|
||||
|
||||
if ($active_controller == $navigation_controller AND $active_action == $navigation_action) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts characters to HTML entities
|
||||
* This is important to avoid XSS attacks, and attempts to inject malicious code in your page.
|
||||
*
|
||||
* @param string $str The string.
|
||||
* @return string
|
||||
*/
|
||||
public function encodeHTML($str)
|
||||
{
|
||||
return htmlentities($str, ENT_QUOTES, 'UTF-8');
|
||||
}
|
||||
}
|
||||
95
application/model/AdminModel.php
Normal file
95
application/model/AdminModel.php
Normal file
@@ -0,0 +1,95 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Handles all data manipulation of the admin part
|
||||
*/
|
||||
class AdminModel
|
||||
{
|
||||
/**
|
||||
* Sets the deletion and suspension values
|
||||
*
|
||||
* @param $suspensionInDays
|
||||
* @param $softDelete
|
||||
* @param $userId
|
||||
*/
|
||||
public static function setAccountSuspensionAndDeletionStatus($suspensionInDays, $softDelete, $userId)
|
||||
{
|
||||
|
||||
// Prevent to suspend or delete own account.
|
||||
// If admin suspend or delete own account will not be able to do any action.
|
||||
if ($userId == Session::get('user_id')) {
|
||||
Session::add('feedback_negative', Text::get('FEEDBACK_ACCOUNT_CANT_DELETE_SUSPEND_OWN'));
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($suspensionInDays > 0) {
|
||||
$suspensionTime = time() + ($suspensionInDays * 60 * 60 * 24);
|
||||
} else {
|
||||
$suspensionTime = null;
|
||||
}
|
||||
|
||||
// FYI "on" is what a checkbox delivers by default when submitted. Didn't know that for a long time :)
|
||||
if ($softDelete == "on") {
|
||||
$delete = 1;
|
||||
} else {
|
||||
$delete = 0;
|
||||
}
|
||||
|
||||
// write the above info to the database
|
||||
self::writeDeleteAndSuspensionInfoToDatabase($userId, $suspensionTime, $delete);
|
||||
|
||||
// if suspension or deletion should happen, then also kick user out of the application instantly by resetting
|
||||
// the user's session :)
|
||||
if ($suspensionTime != null OR $delete = 1) {
|
||||
self::resetUserSession($userId);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Simply write the deletion and suspension info for the user into the database, also puts feedback into session
|
||||
*
|
||||
* @param $userId
|
||||
* @param $suspensionTime
|
||||
* @param $delete
|
||||
* @return bool
|
||||
*/
|
||||
private static function writeDeleteAndSuspensionInfoToDatabase($userId, $suspensionTime, $delete)
|
||||
{
|
||||
$database = DatabaseFactory::getFactory()->getConnection();
|
||||
|
||||
$query = $database->prepare("UPDATE users SET user_suspension_timestamp = :user_suspension_timestamp, user_deleted = :user_deleted WHERE user_id = :user_id LIMIT 1");
|
||||
$query->execute(array(
|
||||
':user_suspension_timestamp' => $suspensionTime,
|
||||
':user_deleted' => $delete,
|
||||
':user_id' => $userId
|
||||
));
|
||||
|
||||
if ($query->rowCount() == 1) {
|
||||
Session::add('feedback_positive', Text::get('FEEDBACK_ACCOUNT_SUSPENSION_DELETION_STATUS'));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Kicks the selected user out of the system instantly by resetting the user's session.
|
||||
* This means, the user will be "logged out".
|
||||
*
|
||||
* @param $userId
|
||||
* @return bool
|
||||
*/
|
||||
private static function resetUserSession($userId)
|
||||
{
|
||||
$database = DatabaseFactory::getFactory()->getConnection();
|
||||
|
||||
$query = $database->prepare("UPDATE users SET session_id = :session_id WHERE user_id = :user_id LIMIT 1");
|
||||
$query->execute(array(
|
||||
':session_id' => null,
|
||||
':user_id' => $userId
|
||||
));
|
||||
|
||||
if ($query->rowCount() == 1) {
|
||||
Session::add('feedback_positive', Text::get('FEEDBACK_ACCOUNT_USER_SUCCESSFULLY_KICKED'));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
258
application/model/AvatarModel.php
Normal file
258
application/model/AvatarModel.php
Normal file
@@ -0,0 +1,258 @@
|
||||
<?php
|
||||
|
||||
class AvatarModel
|
||||
{
|
||||
/**
|
||||
* Gets a gravatar image link from given email address
|
||||
*
|
||||
* Gravatar is the #1 (free) provider for email address based global avatar hosting.
|
||||
* The URL (or image) returns always a .jpg file ! For deeper info on the different parameter possibilities:
|
||||
* @see http://gravatar.com/site/implement/images/
|
||||
* @source http://gravatar.com/site/implement/images/php/
|
||||
*
|
||||
* This method will return something like http://www.gravatar.com/avatar/79e2e5b48aec07710c08d50?s=80&d=mm&r=g
|
||||
* Note: the url does NOT have something like ".jpg" ! It works without.
|
||||
*
|
||||
* Set the configs inside the application/config/ files.
|
||||
*
|
||||
* @param string $email The email address
|
||||
* @return string
|
||||
*/
|
||||
public static function getGravatarLinkByEmail($email)
|
||||
{
|
||||
return 'http://www.gravatar.com/avatar/' .
|
||||
md5(strtolower(trim($email))) .
|
||||
'?s=' . Config::get('AVATAR_SIZE') . '&d=' . Config::get('GRAVATAR_DEFAULT_IMAGESET') . '&r=' . Config::get('GRAVATAR_RATING');
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the user's avatar file path
|
||||
* @param int $user_has_avatar Marker from database
|
||||
* @param int $user_id User's id
|
||||
* @return string Avatar file path
|
||||
*/
|
||||
public static function getPublicAvatarFilePathOfUser($user_has_avatar, $user_id)
|
||||
{
|
||||
if ($user_has_avatar) {
|
||||
return Config::get('URL') . Config::get('PATH_AVATARS_PUBLIC') . $user_id . '.jpg';
|
||||
}
|
||||
|
||||
return Config::get('URL') . Config::get('PATH_AVATARS_PUBLIC') . Config::get('AVATAR_DEFAULT_IMAGE');
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the user's avatar file path
|
||||
* @param $user_id integer The user's id
|
||||
* @return string avatar picture path
|
||||
*/
|
||||
public static function getPublicUserAvatarFilePathByUserId($user_id)
|
||||
{
|
||||
$database = DatabaseFactory::getFactory()->getConnection();
|
||||
|
||||
$query = $database->prepare("SELECT user_has_avatar FROM users WHERE user_id = :user_id LIMIT 1");
|
||||
$query->execute(array(':user_id' => $user_id));
|
||||
|
||||
if ($query->fetch()->user_has_avatar) {
|
||||
return Config::get('URL') . Config::get('PATH_AVATARS_PUBLIC') . $user_id . '.jpg';
|
||||
}
|
||||
|
||||
return Config::get('URL') . Config::get('PATH_AVATARS_PUBLIC') . Config::get('AVATAR_DEFAULT_IMAGE');
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an avatar picture (and checks all necessary things too)
|
||||
* TODO decouple
|
||||
* TODO total rebuild
|
||||
*/
|
||||
public static function createAvatar()
|
||||
{
|
||||
// check avatar folder writing rights, check if upload fits all rules
|
||||
if (self::isAvatarFolderWritable() AND self::validateImageFile()) {
|
||||
|
||||
// create a jpg file in the avatar folder, write marker to database
|
||||
$target_file_path = Config::get('PATH_AVATARS') . Session::get('user_id');
|
||||
self::resizeAvatarImage($_FILES['avatar_file']['tmp_name'], $target_file_path, Config::get('AVATAR_SIZE'), Config::get('AVATAR_SIZE'));
|
||||
self::writeAvatarToDatabase(Session::get('user_id'));
|
||||
Session::set('user_avatar_file', self::getPublicUserAvatarFilePathByUserId(Session::get('user_id')));
|
||||
Session::add('feedback_positive', Text::get('FEEDBACK_AVATAR_UPLOAD_SUCCESSFUL'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the avatar folder exists and is writable
|
||||
*
|
||||
* @return bool success status
|
||||
*/
|
||||
public static function isAvatarFolderWritable()
|
||||
{
|
||||
if (is_dir(Config::get('PATH_AVATARS')) AND is_writable(Config::get('PATH_AVATARS'))) {
|
||||
return true;
|
||||
}
|
||||
|
||||
Session::add('feedback_negative', Text::get('FEEDBACK_AVATAR_FOLDER_DOES_NOT_EXIST_OR_NOT_WRITABLE'));
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the image
|
||||
* Only accepts gif, jpg, png types
|
||||
* @see http://php.net/manual/en/function.image-type-to-mime-type.php
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function validateImageFile()
|
||||
{
|
||||
if (!isset($_FILES['avatar_file'])) {
|
||||
Session::add('feedback_negative', Text::get('FEEDBACK_AVATAR_IMAGE_UPLOAD_FAILED'));
|
||||
return false;
|
||||
}
|
||||
|
||||
// if input file too big (>5MB)
|
||||
if ($_FILES['avatar_file']['size'] > 5000000) {
|
||||
Session::add('feedback_negative', Text::get('FEEDBACK_AVATAR_UPLOAD_TOO_BIG'));
|
||||
return false;
|
||||
}
|
||||
|
||||
// get the image width, height and mime type
|
||||
$image_proportions = getimagesize($_FILES['avatar_file']['tmp_name']);
|
||||
|
||||
// if input file too small, [0] is the width, [1] is the height
|
||||
if ($image_proportions[0] < Config::get('AVATAR_SIZE') OR $image_proportions[1] < Config::get('AVATAR_SIZE')) {
|
||||
Session::add('feedback_negative', Text::get('FEEDBACK_AVATAR_UPLOAD_TOO_SMALL'));
|
||||
return false;
|
||||
}
|
||||
|
||||
// if file type is not jpg, gif or png
|
||||
if (!in_array($image_proportions['mime'], array('image/jpeg', 'image/gif', 'image/png'))) {
|
||||
Session::add('feedback_negative', Text::get('FEEDBACK_AVATAR_UPLOAD_WRONG_TYPE'));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes marker to database, saying user has an avatar now
|
||||
*
|
||||
* @param $user_id
|
||||
*/
|
||||
public static function writeAvatarToDatabase($user_id)
|
||||
{
|
||||
$database = DatabaseFactory::getFactory()->getConnection();
|
||||
|
||||
$query = $database->prepare("UPDATE users SET user_has_avatar = TRUE WHERE user_id = :user_id LIMIT 1");
|
||||
$query->execute(array(':user_id' => $user_id));
|
||||
}
|
||||
|
||||
/**
|
||||
* Resize avatar image (while keeping aspect ratio and cropping it off in a clean way).
|
||||
* Only works with gif, jpg and png file types. If you want to change this also have a look into
|
||||
* method validateImageFile() inside this model.
|
||||
*
|
||||
* TROUBLESHOOTING: You don't see the new image ? Press F5 or CTRL-F5 to refresh browser cache.
|
||||
*
|
||||
* @param string $source_image The location to the original raw image
|
||||
* @param string $destination The location to save the new image
|
||||
* @param int $final_width The desired width of the new image
|
||||
* @param int $final_height The desired height of the new image
|
||||
*
|
||||
* @return bool success state
|
||||
*/
|
||||
public static function resizeAvatarImage($source_image, $destination, $final_width = 44, $final_height = 44)
|
||||
{
|
||||
$imageData = getimagesize($source_image);
|
||||
$width = $imageData[0];
|
||||
$height = $imageData[1];
|
||||
$mimeType = $imageData['mime'];
|
||||
|
||||
if (!$width || !$height) {
|
||||
return false;
|
||||
}
|
||||
|
||||
switch ($mimeType) {
|
||||
case 'image/jpeg': $myImage = imagecreatefromjpeg($source_image); break;
|
||||
case 'image/png': $myImage = imagecreatefrompng($source_image); break;
|
||||
case 'image/gif': $myImage = imagecreatefromgif($source_image); break;
|
||||
default: return false;
|
||||
}
|
||||
|
||||
// calculating the part of the image to use for thumbnail
|
||||
if ($width > $height) {
|
||||
$verticalCoordinateOfSource = 0;
|
||||
$horizontalCoordinateOfSource = ($width - $height) / 2;
|
||||
$smallestSide = $height;
|
||||
} else {
|
||||
$horizontalCoordinateOfSource = 0;
|
||||
$verticalCoordinateOfSource = ($height - $width) / 2;
|
||||
$smallestSide = $width;
|
||||
}
|
||||
|
||||
// copying the part into thumbnail, maybe edit this for square avatars
|
||||
$thumb = imagecreatetruecolor($final_width, $final_height);
|
||||
imagecopyresampled($thumb, $myImage, 0, 0, $horizontalCoordinateOfSource, $verticalCoordinateOfSource, $final_width, $final_height, $smallestSide, $smallestSide);
|
||||
|
||||
// add '.jpg' to file path, save it as a .jpg file with our $destination_filename parameter
|
||||
imagejpeg($thumb, $destination . '.jpg', Config::get('AVATAR_JPEG_QUALITY'));
|
||||
imagedestroy($thumb);
|
||||
|
||||
if (file_exists($destination)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a user's avatar
|
||||
*
|
||||
* @param int $userId
|
||||
* @return bool success
|
||||
*/
|
||||
public static function deleteAvatar($userId)
|
||||
{
|
||||
if (!ctype_digit($userId)) {
|
||||
Session::add("feedback_negative", Text::get("FEEDBACK_AVATAR_IMAGE_DELETE_FAILED"));
|
||||
return false;
|
||||
}
|
||||
|
||||
// try to delete image, but still go on regardless of file deletion result
|
||||
self::deleteAvatarImageFile($userId);
|
||||
|
||||
$database = DatabaseFactory::getFactory()->getConnection();
|
||||
|
||||
$sth = $database->prepare("UPDATE users SET user_has_avatar = 0 WHERE user_id = :user_id LIMIT 1");
|
||||
$sth->bindValue(":user_id", (int)$userId, PDO::PARAM_INT);
|
||||
$sth->execute();
|
||||
|
||||
if ($sth->rowCount() == 1) {
|
||||
Session::set('user_avatar_file', self::getPublicUserAvatarFilePathByUserId($userId));
|
||||
Session::add("feedback_positive", Text::get("FEEDBACK_AVATAR_IMAGE_DELETE_SUCCESSFUL"));
|
||||
return true;
|
||||
} else {
|
||||
Session::add("feedback_negative", Text::get("FEEDBACK_AVATAR_IMAGE_DELETE_FAILED"));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the avatar image file from the filesystem
|
||||
*
|
||||
* @param integer $userId
|
||||
* @return bool
|
||||
*/
|
||||
public static function deleteAvatarImageFile($userId)
|
||||
{
|
||||
// Check if file exists
|
||||
if (!file_exists(Config::get('PATH_AVATARS') . $userId . ".jpg")) {
|
||||
Session::add("feedback_negative", Text::get("FEEDBACK_AVATAR_IMAGE_DELETE_NO_FILE"));
|
||||
return false;
|
||||
}
|
||||
|
||||
// Delete avatar file
|
||||
if (!unlink(Config::get('PATH_AVATARS') . $userId . ".jpg")) {
|
||||
Session::add("feedback_negative", Text::get("FEEDBACK_AVATAR_IMAGE_DELETE_FAILED"));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
46
application/model/CaptchaModel.php
Normal file
46
application/model/CaptchaModel.php
Normal file
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Class CaptchaModel
|
||||
*
|
||||
* This model class handles all the captcha stuff.
|
||||
* Currently this uses the excellent Captcha generator lib from https://github.com/Gregwar/Captcha
|
||||
* Have a look there for more options etc.
|
||||
*/
|
||||
class CaptchaModel
|
||||
{
|
||||
/**
|
||||
* Generates the captcha, "returns" a real image, this is why there is header('Content-type: image/jpeg')
|
||||
* Note: This is a very special method, as this is echoes out binary data.
|
||||
*/
|
||||
public static function generateAndShowCaptcha()
|
||||
{
|
||||
// create a captcha with the CaptchaBuilder lib (loaded via Composer)
|
||||
$captcha = new Gregwar\Captcha\CaptchaBuilder;
|
||||
$captcha->build(
|
||||
Config::get('CAPTCHA_WIDTH'),
|
||||
Config::get('CAPTCHA_HEIGHT')
|
||||
);
|
||||
|
||||
// write the captcha character into session
|
||||
Session::set('captcha', $captcha->getPhrase());
|
||||
|
||||
// render an image showing the characters (=the captcha)
|
||||
header('Content-type: image/jpeg');
|
||||
$captcha->output();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the entered captcha is the same like the one from the rendered image which has been saved in session
|
||||
* @param $captcha string The captcha characters
|
||||
* @return bool success of captcha check
|
||||
*/
|
||||
public static function checkCaptcha($captcha)
|
||||
{
|
||||
if (Session::get('captcha') && ($captcha == Session::get('captcha'))) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
382
application/model/LoginModel.php
Normal file
382
application/model/LoginModel.php
Normal file
@@ -0,0 +1,382 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* LoginModel
|
||||
*
|
||||
* The login part of the model: Handles the login / logout stuff
|
||||
*/
|
||||
class LoginModel
|
||||
{
|
||||
/**
|
||||
* Login process (for DEFAULT user accounts).
|
||||
*
|
||||
* @param $user_name string The user's name
|
||||
* @param $user_password string The user's password
|
||||
* @param $set_remember_me_cookie mixed Marker for usage of remember-me cookie feature
|
||||
*
|
||||
* @return bool success state
|
||||
*/
|
||||
public static function login($user_name, $user_password, $set_remember_me_cookie = null)
|
||||
{
|
||||
// we do negative-first checks here, for simplicity empty username and empty password in one line
|
||||
if (empty($user_name) OR empty($user_password)) {
|
||||
Session::add('feedback_negative', Text::get('FEEDBACK_USERNAME_OR_PASSWORD_FIELD_EMPTY'));
|
||||
return false;
|
||||
}
|
||||
|
||||
// checks if user exists, if login is not blocked (due to failed logins) and if password fits the hash
|
||||
$result = self::validateAndGetUser($user_name, $user_password);
|
||||
|
||||
// check if that user exists. We don't give back a cause in the feedback to avoid giving an attacker details.
|
||||
if (!$result) {
|
||||
//No Need to give feedback here since whole validateAndGetUser controls gives a feedback
|
||||
return false;
|
||||
}
|
||||
|
||||
// stop the user's login if account has been soft deleted
|
||||
if ($result->user_deleted == 1) {
|
||||
Session::add('feedback_negative', Text::get('FEEDBACK_DELETED'));
|
||||
return false;
|
||||
}
|
||||
|
||||
// stop the user from logging in if user has a suspension, display how long they have left in the feedback.
|
||||
if ($result->user_suspension_timestamp != null && $result->user_suspension_timestamp - time() > 0) {
|
||||
$suspensionTimer = Text::get('FEEDBACK_ACCOUNT_SUSPENDED') . round(abs($result->user_suspension_timestamp - time())/60/60, 2) . " hours left";
|
||||
Session::add('feedback_negative', $suspensionTimer);
|
||||
return false;
|
||||
}
|
||||
|
||||
// reset the failed login counter for that user (if necessary)
|
||||
if ($result->user_last_failed_login > 0) {
|
||||
self::resetFailedLoginCounterOfUser($result->user_name);
|
||||
}
|
||||
|
||||
// save timestamp of this login in the database line of that user
|
||||
self::saveTimestampOfLoginOfUser($result->user_name);
|
||||
|
||||
// if user has checked the "remember me" checkbox, then write token into database and into cookie
|
||||
if ($set_remember_me_cookie) {
|
||||
self::setRememberMeInDatabaseAndCookie($result->user_id);
|
||||
}
|
||||
|
||||
// successfully logged in, so we write all necessary data into the session and set "user_logged_in" to true
|
||||
self::setSuccessfulLoginIntoSession(
|
||||
$result->user_id, $result->user_name, $result->user_email, $result->user_account_type
|
||||
);
|
||||
|
||||
// return true to make clear the login was successful
|
||||
// maybe do this in dependence of setSuccessfulLoginIntoSession ?
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the inputs of the users, checks if password is correct etc.
|
||||
* If successful, user is returned
|
||||
*
|
||||
* @param $user_name
|
||||
* @param $user_password
|
||||
*
|
||||
* @return bool|mixed
|
||||
*/
|
||||
private static function validateAndGetUser($user_name, $user_password)
|
||||
{
|
||||
// brute force attack mitigation: use session failed login count and last failed login for not found users.
|
||||
// block login attempt if somebody has already failed 3 times and the last login attempt is less than 30sec ago
|
||||
// (limits user searches in database)
|
||||
if (Session::get('failed-login-count') >= 3 AND (Session::get('last-failed-login') > (time() - 30))) {
|
||||
Session::add('feedback_negative', Text::get('FEEDBACK_LOGIN_FAILED_3_TIMES'));
|
||||
return false;
|
||||
}
|
||||
|
||||
// get all data of that user (to later check if password and password_hash fit)
|
||||
$result = UserModel::getUserDataByUsername($user_name);
|
||||
|
||||
// check if that user exists. We don't give back a cause in the feedback to avoid giving an attacker details.
|
||||
// brute force attack mitigation: reset failed login counter because of found user
|
||||
if (!$result) {
|
||||
|
||||
// increment the user not found count, helps mitigate user enumeration
|
||||
self::incrementUserNotFoundCounter();
|
||||
|
||||
// user does not exist, but we won't to give a potential attacker this details, so we just use a basic feedback message
|
||||
Session::add('feedback_negative', Text::get('FEEDBACK_USERNAME_OR_PASSWORD_WRONG'));
|
||||
return false;
|
||||
}
|
||||
|
||||
// block login attempt if somebody has already failed 3 times and the last login attempt is less than 30sec ago
|
||||
if (($result->user_failed_logins >= 3) AND ($result->user_last_failed_login > (time() - 30))) {
|
||||
Session::add('feedback_negative', Text::get('FEEDBACK_PASSWORD_WRONG_3_TIMES'));
|
||||
return false;
|
||||
}
|
||||
|
||||
// if hash of provided password does NOT match the hash in the database: +1 failed-login counter
|
||||
if (!password_verify($user_password, $result->user_password_hash)) {
|
||||
self::incrementFailedLoginCounterOfUser($result->user_name);
|
||||
Session::add('feedback_negative', Text::get('FEEDBACK_USERNAME_OR_PASSWORD_WRONG'));
|
||||
return false;
|
||||
}
|
||||
|
||||
// if user is not active (= has not verified account by verification mail)
|
||||
if ($result->user_active != 1) {
|
||||
Session::add('feedback_negative', Text::get('FEEDBACK_ACCOUNT_NOT_ACTIVATED_YET'));
|
||||
return false;
|
||||
}
|
||||
|
||||
// reset the user not found counter
|
||||
self::resetUserNotFoundCounter();
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the failed-login-count to 0.
|
||||
* Reset the last-failed-login to an empty string.
|
||||
*/
|
||||
private static function resetUserNotFoundCounter()
|
||||
{
|
||||
Session::set('failed-login-count', 0);
|
||||
Session::set('last-failed-login', '');
|
||||
}
|
||||
|
||||
/**
|
||||
* Increment the failed-login-count by 1.
|
||||
* Add timestamp to last-failed-login.
|
||||
*/
|
||||
private static function incrementUserNotFoundCounter()
|
||||
{
|
||||
// Username enumeration prevention: set session failed login count and last failed login for users not found
|
||||
Session::set('failed-login-count', Session::get('failed-login-count') + 1);
|
||||
Session::set('last-failed-login', time());
|
||||
}
|
||||
|
||||
/**
|
||||
* performs the login via cookie (for DEFAULT user account, FACEBOOK-accounts are handled differently)
|
||||
* TODO add throttling here ?
|
||||
*
|
||||
* @param $cookie string The cookie "remember_me"
|
||||
*
|
||||
* @return bool success state
|
||||
*/
|
||||
public static function loginWithCookie($cookie)
|
||||
{
|
||||
// do we have a cookie ?
|
||||
if (!$cookie) {
|
||||
Session::add('feedback_negative', Text::get('FEEDBACK_COOKIE_INVALID'));
|
||||
return false;
|
||||
}
|
||||
|
||||
// before list(), check it can be split into 3 strings.
|
||||
if (count (explode(':', $cookie)) !== 3) {
|
||||
Session::add('feedback_negative', Text::get('FEEDBACK_COOKIE_INVALID'));
|
||||
return false;
|
||||
}
|
||||
|
||||
// check cookie's contents, check if cookie contents belong together or token is empty
|
||||
list ($user_id, $token, $hash) = explode(':', $cookie);
|
||||
|
||||
// decrypt user id
|
||||
$user_id = Encryption::decrypt($user_id);
|
||||
|
||||
if ($hash !== hash('sha256', $user_id . ':' . $token) OR empty($token) OR empty($user_id)) {
|
||||
Session::add('feedback_negative', Text::get('FEEDBACK_COOKIE_INVALID'));
|
||||
return false;
|
||||
}
|
||||
|
||||
// get data of user that has this id and this token
|
||||
$result = UserModel::getUserDataByUserIdAndToken($user_id, $token);
|
||||
|
||||
// if user with that id and exactly that cookie token exists in database
|
||||
if ($result) {
|
||||
|
||||
// successfully logged in, so we write all necessary data into the session and set "user_logged_in" to true
|
||||
self::setSuccessfulLoginIntoSession($result->user_id, $result->user_name, $result->user_email, $result->user_account_type);
|
||||
|
||||
// save timestamp of this login in the database line of that user
|
||||
self::saveTimestampOfLoginOfUser($result->user_name);
|
||||
|
||||
// NOTE: we don't set another remember_me-cookie here as the current cookie should always
|
||||
// be invalid after a certain amount of time, so the user has to login with username/password
|
||||
// again from time to time. This is good and safe ! ;)
|
||||
|
||||
Session::add('feedback_positive', Text::get('FEEDBACK_COOKIE_LOGIN_SUCCESSFUL'));
|
||||
return true;
|
||||
} else {
|
||||
Session::add('feedback_negative', Text::get('FEEDBACK_COOKIE_INVALID'));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Log out process: delete cookie, delete session
|
||||
*/
|
||||
public static function logout()
|
||||
{
|
||||
$user_id = Session::get('user_id');
|
||||
|
||||
self::deleteCookie($user_id);
|
||||
|
||||
Session::destroy();
|
||||
Session::updateSessionId($user_id);
|
||||
}
|
||||
|
||||
/**
|
||||
* The real login process: The user's data is written into the session.
|
||||
* Cheesy name, maybe rename. Also maybe refactoring this, using an array.
|
||||
*
|
||||
* @param $user_id
|
||||
* @param $user_name
|
||||
* @param $user_email
|
||||
* @param $user_account_type
|
||||
*/
|
||||
public static function setSuccessfulLoginIntoSession($user_id, $user_name, $user_email, $user_account_type)
|
||||
{
|
||||
Session::init();
|
||||
|
||||
// remove old and regenerate session ID.
|
||||
// It's important to regenerate session on sensitive actions,
|
||||
// and to avoid fixated session.
|
||||
// e.g. when a user logs in
|
||||
session_regenerate_id(true);
|
||||
$_SESSION = array();
|
||||
|
||||
Session::set('user_id', $user_id);
|
||||
Session::set('user_name', $user_name);
|
||||
Session::set('user_email', $user_email);
|
||||
Session::set('user_account_type', $user_account_type);
|
||||
Session::set('user_provider_type', 'DEFAULT');
|
||||
|
||||
// get and set avatars
|
||||
Session::set('user_avatar_file', AvatarModel::getPublicUserAvatarFilePathByUserId($user_id));
|
||||
Session::set('user_gravatar_image_url', AvatarModel::getGravatarLinkByEmail($user_email));
|
||||
|
||||
// finally, set user as logged-in
|
||||
Session::set('user_logged_in', true);
|
||||
|
||||
// update session id in database
|
||||
Session::updateSessionId($user_id, session_id());
|
||||
|
||||
// set session cookie setting manually,
|
||||
// Why? because you need to explicitly set session expiry, path, domain, secure, and HTTP.
|
||||
// @see https://www.owasp.org/index.php/PHP_Security_Cheat_Sheet#Cookies
|
||||
setcookie(session_name(), session_id(), time() + Config::get('SESSION_RUNTIME'), Config::get('COOKIE_PATH'),
|
||||
Config::get('COOKIE_DOMAIN'), Config::get('COOKIE_SECURE'), Config::get('COOKIE_HTTP'));
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Increments the failed-login counter of a user
|
||||
*
|
||||
* @param $user_name
|
||||
*/
|
||||
public static function incrementFailedLoginCounterOfUser($user_name)
|
||||
{
|
||||
$database = DatabaseFactory::getFactory()->getConnection();
|
||||
|
||||
$sql = "UPDATE users
|
||||
SET user_failed_logins = user_failed_logins+1, user_last_failed_login = :user_last_failed_login
|
||||
WHERE user_name = :user_name OR user_email = :user_name
|
||||
LIMIT 1";
|
||||
$sth = $database->prepare($sql);
|
||||
$sth->execute(array(':user_name' => $user_name, ':user_last_failed_login' => time() ));
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the failed-login counter of a user back to 0
|
||||
*
|
||||
* @param $user_name
|
||||
*/
|
||||
public static function resetFailedLoginCounterOfUser($user_name)
|
||||
{
|
||||
$database = DatabaseFactory::getFactory()->getConnection();
|
||||
|
||||
$sql = "UPDATE users
|
||||
SET user_failed_logins = 0, user_last_failed_login = NULL
|
||||
WHERE user_name = :user_name AND user_failed_logins != 0
|
||||
LIMIT 1";
|
||||
$sth = $database->prepare($sql);
|
||||
$sth->execute(array(':user_name' => $user_name));
|
||||
}
|
||||
|
||||
/**
|
||||
* Write timestamp of this login into database (we only write a "real" login via login form into the database,
|
||||
* not the session-login on every page request
|
||||
*
|
||||
* @param $user_name
|
||||
*/
|
||||
public static function saveTimestampOfLoginOfUser($user_name)
|
||||
{
|
||||
$database = DatabaseFactory::getFactory()->getConnection();
|
||||
|
||||
$sql = "UPDATE users SET user_last_login_timestamp = :user_last_login_timestamp
|
||||
WHERE user_name = :user_name LIMIT 1";
|
||||
$sth = $database->prepare($sql);
|
||||
$sth->execute(array(':user_name' => $user_name, ':user_last_login_timestamp' => time()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Write remember-me token into database and into cookie
|
||||
* Maybe splitting this into database and cookie part ?
|
||||
*
|
||||
* @param $user_id
|
||||
*/
|
||||
public static function setRememberMeInDatabaseAndCookie($user_id)
|
||||
{
|
||||
$database = DatabaseFactory::getFactory()->getConnection();
|
||||
|
||||
// generate 64 char random string
|
||||
$random_token_string = hash('sha256', mt_rand());
|
||||
|
||||
// write that token into database
|
||||
$sql = "UPDATE users SET user_remember_me_token = :user_remember_me_token WHERE user_id = :user_id LIMIT 1";
|
||||
$sth = $database->prepare($sql);
|
||||
$sth->execute(array(':user_remember_me_token' => $random_token_string, ':user_id' => $user_id));
|
||||
|
||||
// generate cookie string that consists of user id, random string and combined hash of both
|
||||
// never expose the original user id, instead, encrypt it.
|
||||
$cookie_string_first_part = Encryption::encrypt($user_id) . ':' . $random_token_string;
|
||||
$cookie_string_hash = hash('sha256', $user_id . ':' . $random_token_string);
|
||||
$cookie_string = $cookie_string_first_part . ':' . $cookie_string_hash;
|
||||
|
||||
// set cookie, and make it available only for the domain created on (to avoid XSS attacks, where the
|
||||
// attacker could steal your remember-me cookie string and would login itself).
|
||||
// If you are using HTTPS, then you should set the "secure" flag (the second one from right) to true, too.
|
||||
// @see http://www.php.net/manual/en/function.setcookie.php
|
||||
setcookie('remember_me', $cookie_string, time() + Config::get('COOKIE_RUNTIME'), Config::get('COOKIE_PATH'),
|
||||
Config::get('COOKIE_DOMAIN'), Config::get('COOKIE_SECURE'), Config::get('COOKIE_HTTP'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the cookie
|
||||
* It's necessary to split deleteCookie() and logout() as cookies are deleted without logging out too!
|
||||
* Sets the remember-me-cookie to ten years ago (3600sec * 24 hours * 365 days * 10).
|
||||
* that's obviously the best practice to kill a cookie @see http://stackoverflow.com/a/686166/1114320
|
||||
*
|
||||
* @param string $user_id
|
||||
*/
|
||||
public static function deleteCookie($user_id = null)
|
||||
{
|
||||
// is $user_id was set, then clear remember_me token in database
|
||||
if (isset($user_id)) {
|
||||
|
||||
$database = DatabaseFactory::getFactory()->getConnection();
|
||||
|
||||
$sql = "UPDATE users SET user_remember_me_token = :user_remember_me_token WHERE user_id = :user_id LIMIT 1";
|
||||
$sth = $database->prepare($sql);
|
||||
$sth->execute(array(':user_remember_me_token' => null, ':user_id' => $user_id));
|
||||
}
|
||||
|
||||
// delete remember_me cookie in browser
|
||||
setcookie('remember_me', false, time() - (3600 * 24 * 3650), Config::get('COOKIE_PATH'),
|
||||
Config::get('COOKIE_DOMAIN'), Config::get('COOKIE_SECURE'), Config::get('COOKIE_HTTP'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current state of the user's login
|
||||
*
|
||||
* @return bool user's login status
|
||||
*/
|
||||
public static function isUserLoggedIn()
|
||||
{
|
||||
return Session::userIsLoggedIn();
|
||||
}
|
||||
}
|
||||
120
application/model/NoteModel.php
Normal file
120
application/model/NoteModel.php
Normal file
@@ -0,0 +1,120 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* NoteModel
|
||||
* This is basically a simple CRUD (Create/Read/Update/Delete) demonstration.
|
||||
*/
|
||||
class NoteModel
|
||||
{
|
||||
/**
|
||||
* Get all notes (notes are just example data that the user has created)
|
||||
* @return array an array with several objects (the results)
|
||||
*/
|
||||
public static function getAllNotes()
|
||||
{
|
||||
$database = DatabaseFactory::getFactory()->getConnection();
|
||||
|
||||
$sql = "SELECT user_id, note_id, note_text FROM notes WHERE user_id = :user_id";
|
||||
$query = $database->prepare($sql);
|
||||
$query->execute(array(':user_id' => Session::get('user_id')));
|
||||
|
||||
// fetchAll() is the PDO method that gets all result rows
|
||||
return $query->fetchAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a single note
|
||||
* @param int $note_id id of the specific note
|
||||
* @return object a single object (the result)
|
||||
*/
|
||||
public static function getNote($note_id)
|
||||
{
|
||||
$database = DatabaseFactory::getFactory()->getConnection();
|
||||
|
||||
$sql = "SELECT user_id, note_id, note_text FROM notes WHERE user_id = :user_id AND note_id = :note_id LIMIT 1";
|
||||
$query = $database->prepare($sql);
|
||||
$query->execute(array(':user_id' => Session::get('user_id'), ':note_id' => $note_id));
|
||||
|
||||
// fetch() is the PDO method that gets a single result
|
||||
return $query->fetch();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a note (create a new one)
|
||||
* @param string $note_text note text that will be created
|
||||
* @return bool feedback (was the note created properly ?)
|
||||
*/
|
||||
public static function createNote($note_text)
|
||||
{
|
||||
if (!$note_text || strlen($note_text) == 0) {
|
||||
Session::add('feedback_negative', Text::get('FEEDBACK_NOTE_CREATION_FAILED'));
|
||||
return false;
|
||||
}
|
||||
|
||||
$database = DatabaseFactory::getFactory()->getConnection();
|
||||
|
||||
$sql = "INSERT INTO notes (note_text, user_id) VALUES (:note_text, :user_id)";
|
||||
$query = $database->prepare($sql);
|
||||
$query->execute(array(':note_text' => $note_text, ':user_id' => Session::get('user_id')));
|
||||
|
||||
if ($query->rowCount() == 1) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// default return
|
||||
Session::add('feedback_negative', Text::get('FEEDBACK_NOTE_CREATION_FAILED'));
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update an existing note
|
||||
* @param int $note_id id of the specific note
|
||||
* @param string $note_text new text of the specific note
|
||||
* @return bool feedback (was the update successful ?)
|
||||
*/
|
||||
public static function updateNote($note_id, $note_text)
|
||||
{
|
||||
if (!$note_id || !$note_text) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$database = DatabaseFactory::getFactory()->getConnection();
|
||||
|
||||
$sql = "UPDATE notes SET note_text = :note_text WHERE note_id = :note_id AND user_id = :user_id LIMIT 1";
|
||||
$query = $database->prepare($sql);
|
||||
$query->execute(array(':note_id' => $note_id, ':note_text' => $note_text, ':user_id' => Session::get('user_id')));
|
||||
|
||||
if ($query->rowCount() == 1) {
|
||||
return true;
|
||||
}
|
||||
|
||||
Session::add('feedback_negative', Text::get('FEEDBACK_NOTE_EDITING_FAILED'));
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a specific note
|
||||
* @param int $note_id id of the note
|
||||
* @return bool feedback (was the note deleted properly ?)
|
||||
*/
|
||||
public static function deleteNote($note_id)
|
||||
{
|
||||
if (!$note_id) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$database = DatabaseFactory::getFactory()->getConnection();
|
||||
|
||||
$sql = "DELETE FROM notes WHERE note_id = :note_id AND user_id = :user_id LIMIT 1";
|
||||
$query = $database->prepare($sql);
|
||||
$query->execute(array(':note_id' => $note_id, ':user_id' => Session::get('user_id')));
|
||||
|
||||
if ($query->rowCount() == 1) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// default return
|
||||
Session::add('feedback_negative', Text::get('FEEDBACK_NOTE_DELETION_FAILED'));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
365
application/model/PasswordResetModel.php
Normal file
365
application/model/PasswordResetModel.php
Normal file
@@ -0,0 +1,365 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Class PasswordResetModel
|
||||
*
|
||||
* Handles all the stuff that is related to the password-reset process
|
||||
*/
|
||||
class PasswordResetModel
|
||||
{
|
||||
/**
|
||||
* Perform the necessary actions to send a password reset mail
|
||||
*
|
||||
* @param $user_name_or_email string Username or user's email
|
||||
* @param $captcha string Captcha string
|
||||
*
|
||||
* @return bool success status
|
||||
*/
|
||||
public static function requestPasswordReset($user_name_or_email, $captcha)
|
||||
{
|
||||
if (!CaptchaModel::checkCaptcha($captcha)) {
|
||||
Session::add('feedback_negative', Text::get('FEEDBACK_CAPTCHA_WRONG'));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (empty($user_name_or_email)) {
|
||||
Session::add('feedback_negative', Text::get('FEEDBACK_USERNAME_EMAIL_FIELD_EMPTY'));
|
||||
return false;
|
||||
}
|
||||
|
||||
// check if that username exists
|
||||
$result = UserModel::getUserDataByUserNameOrEmail($user_name_or_email);
|
||||
if (!$result) {
|
||||
Session::add('feedback_negative', Text::get('FEEDBACK_USER_DOES_NOT_EXIST'));
|
||||
return false;
|
||||
}
|
||||
|
||||
// generate integer-timestamp (to see when exactly the user (or an attacker) requested the password reset mail)
|
||||
// generate random hash for email password reset verification (40 bytes)
|
||||
$temporary_timestamp = time();
|
||||
$user_password_reset_hash = bin2hex(random_bytes(40));
|
||||
|
||||
// set token (= a random hash string and a timestamp) into database ...
|
||||
$token_set = self::setPasswordResetDatabaseToken($result->user_name, $user_password_reset_hash, $temporary_timestamp);
|
||||
if (!$token_set) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// ... and send a mail to the user, containing a link with username and token hash string
|
||||
$mail_sent = self::sendPasswordResetMail($result->user_name, $user_password_reset_hash, $result->user_email);
|
||||
if ($mail_sent) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// default return
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set password reset token in database (for DEFAULT user accounts)
|
||||
*
|
||||
* @param string $user_name username
|
||||
* @param string $user_password_reset_hash password reset hash
|
||||
* @param int $temporary_timestamp timestamp
|
||||
*
|
||||
* @return bool success status
|
||||
*/
|
||||
public static function setPasswordResetDatabaseToken($user_name, $user_password_reset_hash, $temporary_timestamp)
|
||||
{
|
||||
$database = DatabaseFactory::getFactory()->getConnection();
|
||||
|
||||
$sql = "UPDATE users
|
||||
SET user_password_reset_hash = :user_password_reset_hash, user_password_reset_timestamp = :user_password_reset_timestamp
|
||||
WHERE user_name = :user_name AND user_provider_type = :provider_type LIMIT 1";
|
||||
$query = $database->prepare($sql);
|
||||
$query->execute(array(
|
||||
':user_password_reset_hash' => $user_password_reset_hash, ':user_name' => $user_name,
|
||||
':user_password_reset_timestamp' => $temporary_timestamp, ':provider_type' => 'DEFAULT'
|
||||
));
|
||||
|
||||
// check if exactly one row was successfully changed
|
||||
if ($query->rowCount() == 1) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// fallback
|
||||
Session::add('feedback_negative', Text::get('FEEDBACK_PASSWORD_RESET_TOKEN_FAIL'));
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send the password reset mail
|
||||
*
|
||||
* @param string $user_name username
|
||||
* @param string $user_password_reset_hash password reset hash
|
||||
* @param string $user_email user email
|
||||
*
|
||||
* @return bool success status
|
||||
*/
|
||||
public static function sendPasswordResetMail($user_name, $user_password_reset_hash, $user_email)
|
||||
{
|
||||
// create email body
|
||||
$body = Config::get('EMAIL_PASSWORD_RESET_CONTENT') . ' ' . Config::get('URL') .
|
||||
Config::get('EMAIL_PASSWORD_RESET_URL') . '/' . urlencode($user_name) . '/' . urlencode($user_password_reset_hash);
|
||||
|
||||
// create instance of Mail class, try sending and check
|
||||
$mail = new Mail;
|
||||
$mail_sent = $mail->sendMail($user_email, Config::get('EMAIL_PASSWORD_RESET_FROM_EMAIL'),
|
||||
Config::get('EMAIL_PASSWORD_RESET_FROM_NAME'), Config::get('EMAIL_PASSWORD_RESET_SUBJECT'), $body
|
||||
);
|
||||
|
||||
if ($mail_sent) {
|
||||
Session::add('feedback_positive', Text::get('FEEDBACK_PASSWORD_RESET_MAIL_SENDING_SUCCESSFUL'));
|
||||
return true;
|
||||
}
|
||||
|
||||
Session::add('feedback_negative', Text::get('FEEDBACK_PASSWORD_RESET_MAIL_SENDING_ERROR') . $mail->getError() );
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies the password reset request via the verification hash token (that's only valid for one hour)
|
||||
* @param string $user_name Username
|
||||
* @param string $verification_code Hash token
|
||||
* @return bool Success status
|
||||
*/
|
||||
public static function verifyPasswordReset($user_name, $verification_code)
|
||||
{
|
||||
$database = DatabaseFactory::getFactory()->getConnection();
|
||||
|
||||
// check if user-provided username + verification code combination exists
|
||||
$sql = "SELECT user_id, user_password_reset_timestamp
|
||||
FROM users
|
||||
WHERE user_name = :user_name
|
||||
AND user_password_reset_hash = :user_password_reset_hash
|
||||
AND user_provider_type = :user_provider_type
|
||||
LIMIT 1";
|
||||
$query = $database->prepare($sql);
|
||||
$query->execute(array(
|
||||
':user_password_reset_hash' => $verification_code, ':user_name' => $user_name,
|
||||
':user_provider_type' => 'DEFAULT'
|
||||
));
|
||||
|
||||
// if this user with exactly this verification hash code does NOT exist
|
||||
if ($query->rowCount() != 1) {
|
||||
Session::add('feedback_negative', Text::get('FEEDBACK_PASSWORD_RESET_COMBINATION_DOES_NOT_EXIST'));
|
||||
return false;
|
||||
}
|
||||
|
||||
// get result row (as an object)
|
||||
$result_user_row = $query->fetch();
|
||||
|
||||
// 3600 seconds are 1 hour
|
||||
$timestamp_one_hour_ago = time() - 3600;
|
||||
|
||||
// if password reset request was sent within the last hour (this timeout is for security reasons)
|
||||
if ($result_user_row->user_password_reset_timestamp > $timestamp_one_hour_ago) {
|
||||
|
||||
// verification was successful
|
||||
Session::add('feedback_positive', Text::get('FEEDBACK_PASSWORD_RESET_LINK_VALID'));
|
||||
return true;
|
||||
} else {
|
||||
Session::add('feedback_negative', Text::get('FEEDBACK_PASSWORD_RESET_LINK_EXPIRED'));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes the new password to the database
|
||||
*
|
||||
* @param string $user_name username
|
||||
* @param string $user_password_hash
|
||||
* @param string $user_password_reset_hash
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function saveNewUserPassword($user_name, $user_password_hash, $user_password_reset_hash)
|
||||
{
|
||||
$database = DatabaseFactory::getFactory()->getConnection();
|
||||
|
||||
$sql = "UPDATE users SET user_password_hash = :user_password_hash, user_password_reset_hash = NULL,
|
||||
user_password_reset_timestamp = NULL
|
||||
WHERE user_name = :user_name AND user_password_reset_hash = :user_password_reset_hash
|
||||
AND user_provider_type = :user_provider_type LIMIT 1";
|
||||
$query = $database->prepare($sql);
|
||||
$query->execute(array(
|
||||
':user_password_hash' => $user_password_hash, ':user_name' => $user_name,
|
||||
':user_password_reset_hash' => $user_password_reset_hash, ':user_provider_type' => 'DEFAULT'
|
||||
));
|
||||
|
||||
// if one result exists, return true, else false. Could be written even shorter btw.
|
||||
return ($query->rowCount() == 1 ? true : false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the new password (for DEFAULT user, FACEBOOK-users don't have a password)
|
||||
* Please note: At this point the user has already pre-verified via verifyPasswordReset() (within one hour),
|
||||
* so we don't need to check again for the 60min-limit here. In this method we authenticate
|
||||
* via username & password-reset-hash from (hidden) form fields.
|
||||
*
|
||||
* @param string $user_name
|
||||
* @param string $user_password_reset_hash
|
||||
* @param string $user_password_new
|
||||
* @param string $user_password_repeat
|
||||
*
|
||||
* @return bool success state of the password reset
|
||||
*/
|
||||
public static function setNewPassword($user_name, $user_password_reset_hash, $user_password_new, $user_password_repeat)
|
||||
{
|
||||
// validate the password
|
||||
if (!self::validateResetPassword($user_name, $user_password_reset_hash, $user_password_new, $user_password_repeat)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// crypt the password (with the PHP 5.5+'s password_hash() function, result is a 60 character hash string)
|
||||
$user_password_hash = password_hash($user_password_new, PASSWORD_DEFAULT);
|
||||
|
||||
// write the password to database (as hashed and salted string), reset user_password_reset_hash
|
||||
if (self::saveNewUserPassword($user_name, $user_password_hash, $user_password_reset_hash)) {
|
||||
Session::add('feedback_positive', Text::get('FEEDBACK_PASSWORD_CHANGE_SUCCESSFUL'));
|
||||
return true;
|
||||
} else {
|
||||
Session::add('feedback_negative', Text::get('FEEDBACK_PASSWORD_CHANGE_FAILED'));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate the password submission
|
||||
*
|
||||
* @param $user_name
|
||||
* @param $user_password_reset_hash
|
||||
* @param $user_password_new
|
||||
* @param $user_password_repeat
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function validateResetPassword($user_name, $user_password_reset_hash, $user_password_new, $user_password_repeat)
|
||||
{
|
||||
if (empty($user_name)) {
|
||||
Session::add('feedback_negative', Text::get('FEEDBACK_USERNAME_FIELD_EMPTY'));
|
||||
return false;
|
||||
} else if (empty($user_password_reset_hash)) {
|
||||
Session::add('feedback_negative', Text::get('FEEDBACK_PASSWORD_RESET_TOKEN_MISSING'));
|
||||
return false;
|
||||
} else if (empty($user_password_new) || empty($user_password_repeat)) {
|
||||
Session::add('feedback_negative', Text::get('FEEDBACK_PASSWORD_FIELD_EMPTY'));
|
||||
return false;
|
||||
} else if ($user_password_new !== $user_password_repeat) {
|
||||
Session::add('feedback_negative', Text::get('FEEDBACK_PASSWORD_REPEAT_WRONG'));
|
||||
return false;
|
||||
} else if (strlen($user_password_new) < 6) {
|
||||
Session::add('feedback_negative', Text::get('FEEDBACK_PASSWORD_TOO_SHORT'));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Writes the new password to the database
|
||||
*
|
||||
* @param string $user_name
|
||||
* @param string $user_password_hash
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function saveChangedPassword($user_name, $user_password_hash)
|
||||
{
|
||||
$database = DatabaseFactory::getFactory()->getConnection();
|
||||
|
||||
$sql = "UPDATE users SET user_password_hash = :user_password_hash
|
||||
WHERE user_name = :user_name
|
||||
AND user_provider_type = :user_provider_type LIMIT 1";
|
||||
$query = $database->prepare($sql);
|
||||
$query->execute(array(
|
||||
':user_password_hash' => $user_password_hash, ':user_name' => $user_name,
|
||||
':user_provider_type' => 'DEFAULT'
|
||||
));
|
||||
|
||||
// if one result exists, return true, else false. Could be written even shorter btw.
|
||||
return ($query->rowCount() == 1 ? true : false);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Validates fields, hashes new password, saves new password
|
||||
*
|
||||
* @param string $user_name
|
||||
* @param string $user_password_current
|
||||
* @param string $user_password_new
|
||||
* @param string $user_password_repeat
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function changePassword($user_name, $user_password_current, $user_password_new, $user_password_repeat)
|
||||
{
|
||||
// validate the passwords
|
||||
if (!self::validatePasswordChange($user_name, $user_password_current, $user_password_new, $user_password_repeat)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// crypt the password (with the PHP 5.5+'s password_hash() function, result is a 60 character hash string)
|
||||
$user_password_hash = password_hash($user_password_new, PASSWORD_DEFAULT);
|
||||
|
||||
// write the password to database (as hashed and salted string)
|
||||
if (self::saveChangedPassword($user_name, $user_password_hash)) {
|
||||
Session::add('feedback_positive', Text::get('FEEDBACK_PASSWORD_CHANGE_SUCCESSFUL'));
|
||||
return true;
|
||||
} else {
|
||||
Session::add('feedback_negative', Text::get('FEEDBACK_PASSWORD_CHANGE_FAILED'));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Validates current and new passwords
|
||||
*
|
||||
* @param string $user_name
|
||||
* @param string $user_password_current
|
||||
* @param string $user_password_new
|
||||
* @param string $user_password_repeat
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function validatePasswordChange($user_name, $user_password_current, $user_password_new, $user_password_repeat)
|
||||
{
|
||||
$database = DatabaseFactory::getFactory()->getConnection();
|
||||
|
||||
$sql = "SELECT user_password_hash, user_failed_logins FROM users WHERE user_name = :user_name LIMIT 1;";
|
||||
$query = $database->prepare($sql);
|
||||
$query->execute(array(
|
||||
':user_name' => $user_name
|
||||
));
|
||||
|
||||
$user = $query->fetch();
|
||||
|
||||
if ($query->rowCount() == 1) {
|
||||
$user_password_hash = $user->user_password_hash;
|
||||
} else {
|
||||
Session::add('feedback_negative', Text::get('FEEDBACK_USER_DOES_NOT_EXIST'));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!password_verify($user_password_current, $user_password_hash)) {
|
||||
Session::add('feedback_negative', Text::get('FEEDBACK_PASSWORD_CURRENT_INCORRECT'));
|
||||
return false;
|
||||
} else if (empty($user_password_new) || empty($user_password_repeat)) {
|
||||
Session::add('feedback_negative', Text::get('FEEDBACK_PASSWORD_FIELD_EMPTY'));
|
||||
return false;
|
||||
} else if ($user_password_new !== $user_password_repeat) {
|
||||
Session::add('feedback_negative', Text::get('FEEDBACK_PASSWORD_REPEAT_WRONG'));
|
||||
return false;
|
||||
} else if (strlen($user_password_new) < 6) {
|
||||
Session::add('feedback_negative', Text::get('FEEDBACK_PASSWORD_TOO_SHORT'));
|
||||
return false;
|
||||
} else if ($user_password_current == $user_password_new){
|
||||
Session::add('feedback_negative', Text::get('FEEDBACK_PASSWORD_NEW_SAME_AS_CURRENT'));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
293
application/model/RegistrationModel.php
Normal file
293
application/model/RegistrationModel.php
Normal file
@@ -0,0 +1,293 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Class RegistrationModel
|
||||
*
|
||||
* Everything registration-related happens here.
|
||||
*/
|
||||
class RegistrationModel
|
||||
{
|
||||
/**
|
||||
* Handles the entire registration process for DEFAULT users (not for people who register with
|
||||
* 3rd party services, like facebook) and creates a new user in the database if everything is fine
|
||||
*
|
||||
* @return boolean Gives back the success status of the registration
|
||||
*/
|
||||
public static function registerNewUser()
|
||||
{
|
||||
// clean the input
|
||||
$user_name = strip_tags(Request::post('user_name'));
|
||||
$user_email = strip_tags(Request::post('user_email'));
|
||||
$user_email_repeat = strip_tags(Request::post('user_email_repeat'));
|
||||
$user_password_new = Request::post('user_password_new');
|
||||
$user_password_repeat = Request::post('user_password_repeat');
|
||||
|
||||
// stop registration flow if registrationInputValidation() returns false (= anything breaks the input check rules)
|
||||
$validation_result = self::registrationInputValidation(Request::post('captcha'), $user_name, $user_password_new, $user_password_repeat, $user_email, $user_email_repeat);
|
||||
if (!$validation_result) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// crypt the password with the PHP 5.5's password_hash() function, results in a 60 character hash string.
|
||||
// @see php.net/manual/en/function.password-hash.php for more, especially for potential options
|
||||
$user_password_hash = password_hash($user_password_new, PASSWORD_DEFAULT);
|
||||
|
||||
// make return a bool variable, so both errors can come up at once if needed
|
||||
$return = true;
|
||||
|
||||
// check if username already exists
|
||||
if (UserModel::doesUsernameAlreadyExist($user_name)) {
|
||||
Session::add('feedback_negative', Text::get('FEEDBACK_USERNAME_ALREADY_TAKEN'));
|
||||
$return = false;
|
||||
}
|
||||
|
||||
// check if email already exists
|
||||
if (UserModel::doesEmailAlreadyExist($user_email)) {
|
||||
Session::add('feedback_negative', Text::get('FEEDBACK_USER_EMAIL_ALREADY_TAKEN'));
|
||||
$return = false;
|
||||
}
|
||||
|
||||
// if Username or Email were false, return false
|
||||
if (!$return) return false;
|
||||
|
||||
// generate random hash for email verification (40 bytes)
|
||||
$user_activation_hash = bin2hex(random_bytes(40));
|
||||
|
||||
// write user data to database
|
||||
if (!self::writeNewUserToDatabase($user_name, $user_password_hash, $user_email, time(), $user_activation_hash)) {
|
||||
Session::add('feedback_negative', Text::get('FEEDBACK_ACCOUNT_CREATION_FAILED'));
|
||||
return false; // no reason not to return false here
|
||||
}
|
||||
|
||||
// get user_id of the user that has been created, to keep things clean we DON'T use lastInsertId() here
|
||||
$user_id = UserModel::getUserIdByUsername($user_name);
|
||||
|
||||
if (!$user_id) {
|
||||
Session::add('feedback_negative', Text::get('FEEDBACK_UNKNOWN_ERROR'));
|
||||
return false;
|
||||
}
|
||||
|
||||
// send verification email
|
||||
if (self::sendVerificationEmail($user_id, $user_email, $user_activation_hash)) {
|
||||
Session::add('feedback_positive', Text::get('FEEDBACK_ACCOUNT_SUCCESSFULLY_CREATED'));
|
||||
return true;
|
||||
}
|
||||
|
||||
// if verification email sending failed: instantly delete the user
|
||||
self::rollbackRegistrationByUserId($user_id);
|
||||
Session::add('feedback_negative', Text::get('FEEDBACK_VERIFICATION_MAIL_SENDING_FAILED'));
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the registration input
|
||||
*
|
||||
* @param $captcha
|
||||
* @param $user_name
|
||||
* @param $user_password_new
|
||||
* @param $user_password_repeat
|
||||
* @param $user_email
|
||||
* @param $user_email_repeat
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function registrationInputValidation($captcha, $user_name, $user_password_new, $user_password_repeat, $user_email, $user_email_repeat)
|
||||
{
|
||||
$return = true;
|
||||
|
||||
// perform all necessary checks
|
||||
if (!CaptchaModel::checkCaptcha($captcha)) {
|
||||
Session::add('feedback_negative', Text::get('FEEDBACK_CAPTCHA_WRONG'));
|
||||
$return = false;
|
||||
}
|
||||
|
||||
// if username, email and password are all correctly validated, but make sure they all run on first sumbit
|
||||
if (self::validateUserName($user_name) AND self::validateUserEmail($user_email, $user_email_repeat) AND self::validateUserPassword($user_password_new, $user_password_repeat) AND $return) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// otherwise, return false
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the username
|
||||
*
|
||||
* @param $user_name
|
||||
* @return bool
|
||||
*/
|
||||
public static function validateUserName($user_name)
|
||||
{
|
||||
if (empty($user_name)) {
|
||||
Session::add('feedback_negative', Text::get('FEEDBACK_USERNAME_FIELD_EMPTY'));
|
||||
return false;
|
||||
}
|
||||
|
||||
// if username is too short (2), too long (64) or does not fit the pattern (aZ09)
|
||||
if (!preg_match('/^[a-zA-Z0-9]{2,64}$/', $user_name)) {
|
||||
Session::add('feedback_negative', Text::get('FEEDBACK_USERNAME_DOES_NOT_FIT_PATTERN'));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the email
|
||||
*
|
||||
* @param $user_email
|
||||
* @param $user_email_repeat
|
||||
* @return bool
|
||||
*/
|
||||
public static function validateUserEmail($user_email, $user_email_repeat)
|
||||
{
|
||||
if (empty($user_email)) {
|
||||
Session::add('feedback_negative', Text::get('FEEDBACK_EMAIL_FIELD_EMPTY'));
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($user_email !== $user_email_repeat) {
|
||||
Session::add('feedback_negative', Text::get('FEEDBACK_EMAIL_REPEAT_WRONG'));
|
||||
return false;
|
||||
}
|
||||
|
||||
// validate the email with PHP's internal filter
|
||||
// side-fact: Max length seems to be 254 chars
|
||||
// @see http://stackoverflow.com/questions/386294/what-is-the-maximum-length-of-a-valid-email-address
|
||||
if (!filter_var($user_email, FILTER_VALIDATE_EMAIL)) {
|
||||
Session::add('feedback_negative', Text::get('FEEDBACK_EMAIL_DOES_NOT_FIT_PATTERN'));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the password
|
||||
*
|
||||
* @param $user_password_new
|
||||
* @param $user_password_repeat
|
||||
* @return bool
|
||||
*/
|
||||
public static function validateUserPassword($user_password_new, $user_password_repeat)
|
||||
{
|
||||
if (empty($user_password_new) OR empty($user_password_repeat)) {
|
||||
Session::add('feedback_negative', Text::get('FEEDBACK_PASSWORD_FIELD_EMPTY'));
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($user_password_new !== $user_password_repeat) {
|
||||
Session::add('feedback_negative', Text::get('FEEDBACK_PASSWORD_REPEAT_WRONG'));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (strlen($user_password_new) < 6) {
|
||||
Session::add('feedback_negative', Text::get('FEEDBACK_PASSWORD_TOO_SHORT'));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes the new user's data to the database
|
||||
*
|
||||
* @param $user_name
|
||||
* @param $user_password_hash
|
||||
* @param $user_email
|
||||
* @param $user_creation_timestamp
|
||||
* @param $user_activation_hash
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function writeNewUserToDatabase($user_name, $user_password_hash, $user_email, $user_creation_timestamp, $user_activation_hash)
|
||||
{
|
||||
$database = DatabaseFactory::getFactory()->getConnection();
|
||||
|
||||
// write new users data into database
|
||||
$sql = "INSERT INTO users (user_name, user_password_hash, user_email, user_creation_timestamp, user_activation_hash, user_provider_type)
|
||||
VALUES (:user_name, :user_password_hash, :user_email, :user_creation_timestamp, :user_activation_hash, :user_provider_type)";
|
||||
$query = $database->prepare($sql);
|
||||
$query->execute(array(':user_name' => $user_name,
|
||||
':user_password_hash' => $user_password_hash,
|
||||
':user_email' => $user_email,
|
||||
':user_creation_timestamp' => $user_creation_timestamp,
|
||||
':user_activation_hash' => $user_activation_hash,
|
||||
':user_provider_type' => 'DEFAULT'));
|
||||
$count = $query->rowCount();
|
||||
if ($count == 1) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the user from users table. Currently used to rollback a registration when verification mail sending
|
||||
* was not successful.
|
||||
*
|
||||
* @param $user_id
|
||||
*/
|
||||
public static function rollbackRegistrationByUserId($user_id)
|
||||
{
|
||||
$database = DatabaseFactory::getFactory()->getConnection();
|
||||
|
||||
$query = $database->prepare("DELETE FROM users WHERE user_id = :user_id");
|
||||
$query->execute(array(':user_id' => $user_id));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends the verification email (to confirm the account).
|
||||
* The construction of the mail $body looks weird at first, but it's really just a simple string.
|
||||
*
|
||||
* @param int $user_id user's id
|
||||
* @param string $user_email user's email
|
||||
* @param string $user_activation_hash user's mail verification hash string
|
||||
*
|
||||
* @return boolean gives back true if mail has been sent, gives back false if no mail could been sent
|
||||
*/
|
||||
public static function sendVerificationEmail($user_id, $user_email, $user_activation_hash)
|
||||
{
|
||||
$body = Config::get('EMAIL_VERIFICATION_CONTENT') . Config::get('URL') . Config::get('EMAIL_VERIFICATION_URL')
|
||||
. '/' . urlencode($user_id) . '/' . urlencode($user_activation_hash);
|
||||
|
||||
$mail = new Mail;
|
||||
$mail_sent = $mail->sendMail($user_email, Config::get('EMAIL_VERIFICATION_FROM_EMAIL'),
|
||||
Config::get('EMAIL_VERIFICATION_FROM_NAME'), Config::get('EMAIL_VERIFICATION_SUBJECT'), $body
|
||||
);
|
||||
|
||||
if ($mail_sent) {
|
||||
Session::add('feedback_positive', Text::get('FEEDBACK_VERIFICATION_MAIL_SENDING_SUCCESSFUL'));
|
||||
return true;
|
||||
} else {
|
||||
Session::add('feedback_negative', Text::get('FEEDBACK_VERIFICATION_MAIL_SENDING_ERROR') . $mail->getError() );
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* checks the email/verification code combination and set the user's activation status to true in the database
|
||||
*
|
||||
* @param int $user_id user id
|
||||
* @param string $user_activation_verification_code verification token
|
||||
*
|
||||
* @return bool success status
|
||||
*/
|
||||
public static function verifyNewUser($user_id, $user_activation_verification_code)
|
||||
{
|
||||
$database = DatabaseFactory::getFactory()->getConnection();
|
||||
|
||||
$sql = "UPDATE users SET user_active = 1, user_activation_hash = NULL
|
||||
WHERE user_id = :user_id AND user_activation_hash = :user_activation_hash LIMIT 1";
|
||||
$query = $database->prepare($sql);
|
||||
$query->execute(array(':user_id' => $user_id, ':user_activation_hash' => $user_activation_verification_code));
|
||||
|
||||
if ($query->rowCount() == 1) {
|
||||
Session::add('feedback_positive', Text::get('FEEDBACK_ACCOUNT_ACTIVATION_SUCCESSFUL'));
|
||||
return true;
|
||||
}
|
||||
|
||||
Session::add('feedback_negative', Text::get('FEEDBACK_ACCOUNT_ACTIVATION_FAILED'));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
343
application/model/UserModel.php
Normal file
343
application/model/UserModel.php
Normal file
@@ -0,0 +1,343 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* UserModel
|
||||
* Handles all the PUBLIC profile stuff. This is not for getting data of the logged in user, it's more for handling
|
||||
* data of all the other users. Useful for display profile information, creating user lists etc.
|
||||
*/
|
||||
class UserModel
|
||||
{
|
||||
/**
|
||||
* Gets an array that contains all the users in the database. The array's keys are the user ids.
|
||||
* Each array element is an object, containing a specific user's data.
|
||||
* The avatar line is built using Ternary Operators, have a look here for more:
|
||||
* @see http://davidwalsh.name/php-shorthand-if-else-ternary-operators
|
||||
*
|
||||
* @return array The profiles of all users
|
||||
*/
|
||||
public static function getPublicProfilesOfAllUsers()
|
||||
{
|
||||
$database = DatabaseFactory::getFactory()->getConnection();
|
||||
|
||||
$sql = "SELECT user_id, user_name, user_email, user_active, user_has_avatar, user_deleted FROM users";
|
||||
$query = $database->prepare($sql);
|
||||
$query->execute();
|
||||
|
||||
$all_users_profiles = array();
|
||||
|
||||
foreach ($query->fetchAll() as $user) {
|
||||
|
||||
// all elements of array passed to Filter::XSSFilter for XSS sanitation, have a look into
|
||||
// application/core/Filter.php for more info on how to use. Removes (possibly bad) JavaScript etc from
|
||||
// the user's values
|
||||
array_walk_recursive($user, 'Filter::XSSFilter');
|
||||
|
||||
$all_users_profiles[$user->user_id] = new stdClass();
|
||||
$all_users_profiles[$user->user_id]->user_id = $user->user_id;
|
||||
$all_users_profiles[$user->user_id]->user_name = $user->user_name;
|
||||
$all_users_profiles[$user->user_id]->user_email = $user->user_email;
|
||||
$all_users_profiles[$user->user_id]->user_active = $user->user_active;
|
||||
$all_users_profiles[$user->user_id]->user_deleted = $user->user_deleted;
|
||||
$all_users_profiles[$user->user_id]->user_avatar_link = (Config::get('USE_GRAVATAR') ? AvatarModel::getGravatarLinkByEmail($user->user_email) : AvatarModel::getPublicAvatarFilePathOfUser($user->user_has_avatar, $user->user_id));
|
||||
}
|
||||
|
||||
return $all_users_profiles;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a user's profile data, according to the given $user_id
|
||||
* @param int $user_id The user's id
|
||||
* @return mixed The selected user's profile
|
||||
*/
|
||||
public static function getPublicProfileOfUser($user_id)
|
||||
{
|
||||
$database = DatabaseFactory::getFactory()->getConnection();
|
||||
|
||||
$sql = "SELECT user_id, user_name, user_email, user_active, user_has_avatar, user_deleted
|
||||
FROM users WHERE user_id = :user_id LIMIT 1";
|
||||
$query = $database->prepare($sql);
|
||||
$query->execute(array(':user_id' => $user_id));
|
||||
|
||||
$user = $query->fetch();
|
||||
|
||||
if ($query->rowCount() == 1) {
|
||||
if (Config::get('USE_GRAVATAR')) {
|
||||
$user->user_avatar_link = AvatarModel::getGravatarLinkByEmail($user->user_email);
|
||||
} else {
|
||||
$user->user_avatar_link = AvatarModel::getPublicAvatarFilePathOfUser($user->user_has_avatar, $user->user_id);
|
||||
}
|
||||
} else {
|
||||
Session::add('feedback_negative', Text::get('FEEDBACK_USER_DOES_NOT_EXIST'));
|
||||
}
|
||||
|
||||
// all elements of array passed to Filter::XSSFilter for XSS sanitation, have a look into
|
||||
// application/core/Filter.php for more info on how to use. Removes (possibly bad) JavaScript etc from
|
||||
// the user's values
|
||||
array_walk_recursive($user, 'Filter::XSSFilter');
|
||||
|
||||
return $user;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $user_name_or_email
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public static function getUserDataByUserNameOrEmail($user_name_or_email)
|
||||
{
|
||||
$database = DatabaseFactory::getFactory()->getConnection();
|
||||
|
||||
$query = $database->prepare("SELECT user_id, user_name, user_email FROM users
|
||||
WHERE (user_name = :user_name_or_email OR user_email = :user_name_or_email)
|
||||
AND user_provider_type = :provider_type LIMIT 1");
|
||||
$query->execute(array(':user_name_or_email' => $user_name_or_email, ':provider_type' => 'DEFAULT'));
|
||||
|
||||
return $query->fetch();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a username is already taken
|
||||
*
|
||||
* @param $user_name string username
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function doesUsernameAlreadyExist($user_name)
|
||||
{
|
||||
$database = DatabaseFactory::getFactory()->getConnection();
|
||||
|
||||
$query = $database->prepare("SELECT user_id FROM users WHERE user_name = :user_name LIMIT 1");
|
||||
$query->execute(array(':user_name' => $user_name));
|
||||
if ($query->rowCount() == 0) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a email is already used
|
||||
*
|
||||
* @param $user_email string email
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function doesEmailAlreadyExist($user_email)
|
||||
{
|
||||
$database = DatabaseFactory::getFactory()->getConnection();
|
||||
|
||||
$query = $database->prepare("SELECT user_id FROM users WHERE user_email = :user_email LIMIT 1");
|
||||
$query->execute(array(':user_email' => $user_email));
|
||||
if ($query->rowCount() == 0) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes new username to database
|
||||
*
|
||||
* @param $user_id int user id
|
||||
* @param $new_user_name string new username
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function saveNewUserName($user_id, $new_user_name)
|
||||
{
|
||||
$database = DatabaseFactory::getFactory()->getConnection();
|
||||
|
||||
$query = $database->prepare("UPDATE users SET user_name = :user_name WHERE user_id = :user_id LIMIT 1");
|
||||
$query->execute(array(':user_name' => $new_user_name, ':user_id' => $user_id));
|
||||
if ($query->rowCount() == 1) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes new email address to database
|
||||
*
|
||||
* @param $user_id int user id
|
||||
* @param $new_user_email string new email address
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function saveNewEmailAddress($user_id, $new_user_email)
|
||||
{
|
||||
$database = DatabaseFactory::getFactory()->getConnection();
|
||||
|
||||
$query = $database->prepare("UPDATE users SET user_email = :user_email WHERE user_id = :user_id LIMIT 1");
|
||||
$query->execute(array(':user_email' => $new_user_email, ':user_id' => $user_id));
|
||||
$count = $query->rowCount();
|
||||
if ($count == 1) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Edit the user's name, provided in the editing form
|
||||
*
|
||||
* @param $new_user_name string The new username
|
||||
*
|
||||
* @return bool success status
|
||||
*/
|
||||
public static function editUserName($new_user_name)
|
||||
{
|
||||
// new username same as old one ?
|
||||
if ($new_user_name == Session::get('user_name')) {
|
||||
Session::add('feedback_negative', Text::get('FEEDBACK_USERNAME_SAME_AS_OLD_ONE'));
|
||||
return false;
|
||||
}
|
||||
|
||||
// username cannot be empty and must be azAZ09 and 2-64 characters
|
||||
if (!preg_match("/^[a-zA-Z0-9]{2,64}$/", $new_user_name)) {
|
||||
Session::add('feedback_negative', Text::get('FEEDBACK_USERNAME_DOES_NOT_FIT_PATTERN'));
|
||||
return false;
|
||||
}
|
||||
|
||||
// clean the input, strip usernames longer than 64 chars (maybe fix this ?)
|
||||
$new_user_name = substr(strip_tags($new_user_name), 0, 64);
|
||||
|
||||
// check if new username already exists
|
||||
if (self::doesUsernameAlreadyExist($new_user_name)) {
|
||||
Session::add('feedback_negative', Text::get('FEEDBACK_USERNAME_ALREADY_TAKEN'));
|
||||
return false;
|
||||
}
|
||||
|
||||
$status_of_action = self::saveNewUserName(Session::get('user_id'), $new_user_name);
|
||||
if ($status_of_action) {
|
||||
Session::set('user_name', $new_user_name);
|
||||
Session::add('feedback_positive', Text::get('FEEDBACK_USERNAME_CHANGE_SUCCESSFUL'));
|
||||
return true;
|
||||
} else {
|
||||
Session::add('feedback_negative', Text::get('FEEDBACK_UNKNOWN_ERROR'));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Edit the user's email
|
||||
*
|
||||
* @param $new_user_email
|
||||
*
|
||||
* @return bool success status
|
||||
*/
|
||||
public static function editUserEmail($new_user_email)
|
||||
{
|
||||
// email provided ?
|
||||
if (empty($new_user_email)) {
|
||||
Session::add('feedback_negative', Text::get('FEEDBACK_EMAIL_FIELD_EMPTY'));
|
||||
return false;
|
||||
}
|
||||
|
||||
// check if new email is same like the old one
|
||||
if ($new_user_email == Session::get('user_email')) {
|
||||
Session::add('feedback_negative', Text::get('FEEDBACK_EMAIL_SAME_AS_OLD_ONE'));
|
||||
return false;
|
||||
}
|
||||
|
||||
// user's email must be in valid email format, also checks the length
|
||||
// @see http://stackoverflow.com/questions/21631366/php-filter-validate-email-max-length
|
||||
// @see http://stackoverflow.com/questions/386294/what-is-the-maximum-length-of-a-valid-email-address
|
||||
if (!filter_var($new_user_email, FILTER_VALIDATE_EMAIL)) {
|
||||
Session::add('feedback_negative', Text::get('FEEDBACK_EMAIL_DOES_NOT_FIT_PATTERN'));
|
||||
return false;
|
||||
}
|
||||
|
||||
// strip tags, just to be sure
|
||||
$new_user_email = substr(strip_tags($new_user_email), 0, 254);
|
||||
|
||||
// check if user's email already exists
|
||||
if (self::doesEmailAlreadyExist($new_user_email)) {
|
||||
Session::add('feedback_negative', Text::get('FEEDBACK_USER_EMAIL_ALREADY_TAKEN'));
|
||||
return false;
|
||||
}
|
||||
|
||||
// write to database, if successful ...
|
||||
// ... then write new email to session, Gravatar too (as this relies to the user's email address)
|
||||
if (self::saveNewEmailAddress(Session::get('user_id'), $new_user_email)) {
|
||||
Session::set('user_email', $new_user_email);
|
||||
Session::set('user_gravatar_image_url', AvatarModel::getGravatarLinkByEmail($new_user_email));
|
||||
Session::add('feedback_positive', Text::get('FEEDBACK_EMAIL_CHANGE_SUCCESSFUL'));
|
||||
return true;
|
||||
}
|
||||
|
||||
Session::add('feedback_negative', Text::get('FEEDBACK_UNKNOWN_ERROR'));
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the user's id
|
||||
*
|
||||
* @param $user_name
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public static function getUserIdByUsername($user_name)
|
||||
{
|
||||
$database = DatabaseFactory::getFactory()->getConnection();
|
||||
|
||||
$sql = "SELECT user_id FROM users WHERE user_name = :user_name AND user_provider_type = :provider_type LIMIT 1";
|
||||
$query = $database->prepare($sql);
|
||||
|
||||
// DEFAULT is the marker for "normal" accounts (that have a password etc.)
|
||||
// There are other types of accounts that don't have passwords etc. (FACEBOOK)
|
||||
$query->execute(array(':user_name' => $user_name, ':provider_type' => 'DEFAULT'));
|
||||
|
||||
// return one row (we only have one result or nothing)
|
||||
return $query->fetch()->user_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the user's data
|
||||
*
|
||||
* @param $user_name string User's name
|
||||
*
|
||||
* @return mixed Returns false if user does not exist, returns object with user's data when user exists
|
||||
*/
|
||||
public static function getUserDataByUsername($user_name)
|
||||
{
|
||||
$database = DatabaseFactory::getFactory()->getConnection();
|
||||
|
||||
$sql = "SELECT user_id, user_name, user_email, user_password_hash, user_active,user_deleted, user_suspension_timestamp, user_account_type,
|
||||
user_failed_logins, user_last_failed_login
|
||||
FROM users
|
||||
WHERE (user_name = :user_name OR user_email = :user_name)
|
||||
AND user_provider_type = :provider_type
|
||||
LIMIT 1";
|
||||
$query = $database->prepare($sql);
|
||||
|
||||
// DEFAULT is the marker for "normal" accounts (that have a password etc.)
|
||||
// There are other types of accounts that don't have passwords etc. (FACEBOOK)
|
||||
$query->execute(array(':user_name' => $user_name, ':provider_type' => 'DEFAULT'));
|
||||
|
||||
// return one row (we only have one result or nothing)
|
||||
return $query->fetch();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the user's data by user's id and a token (used by login-via-cookie process)
|
||||
*
|
||||
* @param $user_id
|
||||
* @param $token
|
||||
*
|
||||
* @return mixed Returns false if user does not exist, returns object with user's data when user exists
|
||||
*/
|
||||
public static function getUserDataByUserIdAndToken($user_id, $token)
|
||||
{
|
||||
$database = DatabaseFactory::getFactory()->getConnection();
|
||||
|
||||
// get real token from database (and all other data)
|
||||
$query = $database->prepare("SELECT user_id, user_name, user_email, user_password_hash, user_active,
|
||||
user_account_type, user_has_avatar, user_failed_logins, user_last_failed_login
|
||||
FROM users
|
||||
WHERE user_id = :user_id
|
||||
AND user_remember_me_token = :user_remember_me_token
|
||||
AND user_remember_me_token IS NOT NULL
|
||||
AND user_provider_type = :provider_type LIMIT 1");
|
||||
$query->execute(array(':user_id' => $user_id, ':user_remember_me_token' => $token, ':provider_type' => 'DEFAULT'));
|
||||
|
||||
// return one row (we only have one result or nothing)
|
||||
return $query->fetch();
|
||||
}
|
||||
}
|
||||
65
application/model/UserRoleModel.php
Normal file
65
application/model/UserRoleModel.php
Normal file
@@ -0,0 +1,65 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Class UserRoleModel
|
||||
*
|
||||
* This class contains everything that is related to up- and downgrading accounts.
|
||||
*/
|
||||
class UserRoleModel
|
||||
{
|
||||
/**
|
||||
* Upgrades / downgrades the user's account. Currently it's just the field user_account_type in the database that
|
||||
* can be 1 or 2 (maybe "basic" or "premium"). Put some more complex stuff in here, maybe a pay-process or whatever
|
||||
* you like.
|
||||
*
|
||||
* @param $type
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function changeUserRole($type)
|
||||
{
|
||||
if (!$type) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// save new role to database
|
||||
if (self::saveRoleToDatabase($type)) {
|
||||
Session::add('feedback_positive', Text::get('FEEDBACK_ACCOUNT_TYPE_CHANGE_SUCCESSFUL'));
|
||||
return true;
|
||||
} else {
|
||||
Session::add('feedback_negative', Text::get('FEEDBACK_ACCOUNT_TYPE_CHANGE_FAILED'));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes the new account type marker to the database and to the session
|
||||
*
|
||||
* @param $type
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function saveRoleToDatabase($type)
|
||||
{
|
||||
// if $type is not 1 or 2
|
||||
if (!in_array($type, [1, 2])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$database = DatabaseFactory::getFactory()->getConnection();
|
||||
|
||||
$query = $database->prepare("UPDATE users SET user_account_type = :new_type WHERE user_id = :user_id LIMIT 1");
|
||||
$query->execute(array(
|
||||
':new_type' => $type,
|
||||
':user_id' => Session::get('user_id')
|
||||
));
|
||||
|
||||
if ($query->rowCount() == 1) {
|
||||
// set account type in session
|
||||
Session::set('user_account_type', $type);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
19
application/view/_templates/feedback.php
Normal file
19
application/view/_templates/feedback.php
Normal file
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
// get the feedback (they are arrays, to make multiple positive/negative messages possible)
|
||||
$feedback_positive = Session::get('feedback_positive');
|
||||
$feedback_negative = Session::get('feedback_negative');
|
||||
|
||||
// echo out positive messages
|
||||
if (isset($feedback_positive)) {
|
||||
foreach ($feedback_positive as $feedback) {
|
||||
echo '<div class="feedback success">'.$feedback.'</div>';
|
||||
}
|
||||
}
|
||||
|
||||
// echo out negative messages
|
||||
if (isset($feedback_negative)) {
|
||||
foreach ($feedback_negative as $feedback) {
|
||||
echo '<div class="feedback error">'.$feedback.'</div>';
|
||||
}
|
||||
}
|
||||
7
application/view/_templates/footer.php
Normal file
7
application/view/_templates/footer.php
Normal file
@@ -0,0 +1,7 @@
|
||||
<div class="footer"></div>
|
||||
</div><!-- close class="wrapper" -->
|
||||
|
||||
<!-- the support button on the top right -->
|
||||
<a class="support-button" href="https://affiliates.a2hosting.com/idevaffiliate.php?id=4471&url=579" target="_blank"></a>
|
||||
</body>
|
||||
</html>
|
||||
79
application/view/_templates/header.php
Normal file
79
application/view/_templates/header.php
Normal file
@@ -0,0 +1,79 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<title>HUGE</title>
|
||||
<!-- META -->
|
||||
<meta charset="utf-8">
|
||||
<!-- send empty favicon fallback to prevent user's browser hitting the server for lots of favicon requests resulting in 404s -->
|
||||
<link rel="icon" href="data:;base64,=">
|
||||
<!-- CSS -->
|
||||
<link rel="stylesheet" href="<?php echo Config::get('URL'); ?>css/style.css" />
|
||||
</head>
|
||||
<body>
|
||||
<!-- wrapper, to center website -->
|
||||
<div class="wrapper">
|
||||
|
||||
<!-- logo -->
|
||||
<div class="logo"></div>
|
||||
|
||||
<!-- navigation -->
|
||||
<ul class="navigation">
|
||||
<li <?php if (View::checkForActiveController($filename, "index")) { echo ' class="active" '; } ?> >
|
||||
<a href="<?php echo Config::get('URL'); ?>index/index">Index</a>
|
||||
</li>
|
||||
<li <?php if (View::checkForActiveController($filename, "profile")) { echo ' class="active" '; } ?> >
|
||||
<a href="<?php echo Config::get('URL'); ?>profile/index">Profiles</a>
|
||||
</li>
|
||||
<?php if (Session::userIsLoggedIn()) { ?>
|
||||
<li <?php if (View::checkForActiveController($filename, "dashboard")) { echo ' class="active" '; } ?> >
|
||||
<a href="<?php echo Config::get('URL'); ?>dashboard/index">Dashboard</a>
|
||||
</li>
|
||||
<li <?php if (View::checkForActiveController($filename, "note")) { echo ' class="active" '; } ?> >
|
||||
<a href="<?php echo Config::get('URL'); ?>note/index">My Notes</a>
|
||||
</li>
|
||||
<?php } else { ?>
|
||||
<!-- for not logged in users -->
|
||||
<li <?php if (View::checkForActiveControllerAndAction($filename, "login/index")) { echo ' class="active" '; } ?> >
|
||||
<a href="<?php echo Config::get('URL'); ?>login/index">Login</a>
|
||||
</li>
|
||||
<li <?php if (View::checkForActiveControllerAndAction($filename, "register/index")) { echo ' class="active" '; } ?> >
|
||||
<a href="<?php echo Config::get('URL'); ?>register/index">Register</a>
|
||||
</li>
|
||||
<?php } ?>
|
||||
</ul>
|
||||
|
||||
<!-- my account -->
|
||||
<ul class="navigation right">
|
||||
<?php if (Session::userIsLoggedIn()) : ?>
|
||||
<li <?php if (View::checkForActiveController($filename, "user")) { echo ' class="active" '; } ?> >
|
||||
<a href="<?php echo Config::get('URL'); ?>user/index">My Account</a>
|
||||
<ul class="navigation-submenu">
|
||||
<li <?php if (View::checkForActiveController($filename, "user")) { echo ' class="active" '; } ?> >
|
||||
<a href="<?php echo Config::get('URL'); ?>user/changeUserRole">Change account type</a>
|
||||
</li>
|
||||
<li <?php if (View::checkForActiveController($filename, "user")) { echo ' class="active" '; } ?> >
|
||||
<a href="<?php echo Config::get('URL'); ?>user/editAvatar">Edit your avatar</a>
|
||||
</li>
|
||||
<li <?php if (View::checkForActiveController($filename, "user")) { echo ' class="active" '; } ?> >
|
||||
<a href="<?php echo Config::get('URL'); ?>user/editusername">Edit my username</a>
|
||||
</li>
|
||||
<li <?php if (View::checkForActiveController($filename, "user")) { echo ' class="active" '; } ?> >
|
||||
<a href="<?php echo Config::get('URL'); ?>user/edituseremail">Edit my email</a>
|
||||
</li>
|
||||
<li <?php if (View::checkForActiveController($filename, "user")) { echo ' class="active" '; } ?> >
|
||||
<a href="<?php echo Config::get('URL'); ?>user/changePassword">Change Password</a>
|
||||
</li>
|
||||
<li <?php if (View::checkForActiveController($filename, "login")) { echo ' class="active" '; } ?> >
|
||||
<a href="<?php echo Config::get('URL'); ?>login/logout">Logout</a>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<?php if (Session::get("user_account_type") == 7) : ?>
|
||||
<li <?php if (View::checkForActiveController($filename, "admin")) {
|
||||
echo ' class="active" ';
|
||||
} ?> >
|
||||
<a href="<?php echo Config::get('URL'); ?>admin/">Admin</a>
|
||||
</li>
|
||||
<?php endif; ?>
|
||||
<?php endif; ?>
|
||||
</ul>
|
||||
57
application/view/admin/index.php
Normal file
57
application/view/admin/index.php
Normal file
@@ -0,0 +1,57 @@
|
||||
<div class="container">
|
||||
<h1>Admin/index</h1>
|
||||
|
||||
<div class="box">
|
||||
|
||||
<!-- echo out the system feedback (error and success messages) -->
|
||||
<?php $this->renderFeedbackMessages(); ?>
|
||||
|
||||
<h3>What happens here ?</h3>
|
||||
|
||||
<div>
|
||||
This controller/action/view shows a list of all users in the system. with the ability to soft delete a user
|
||||
or suspend a user.
|
||||
</div>
|
||||
<div>
|
||||
<table class="overview-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<td>Id</td>
|
||||
<td>Avatar</td>
|
||||
<td>Username</td>
|
||||
<td>User's email</td>
|
||||
<td>Activated ?</td>
|
||||
<td>Link to user's profile</td>
|
||||
<td>suspension Time in days</td>
|
||||
<td>Soft delete</td>
|
||||
<td>Submit</td>
|
||||
</tr>
|
||||
</thead>
|
||||
<?php foreach ($this->users as $user) { ?>
|
||||
<tr class="<?= ($user->user_active == 0 ? 'inactive' : 'active'); ?>">
|
||||
<td><?= $user->user_id; ?></td>
|
||||
<td class="avatar">
|
||||
<?php if (isset($user->user_avatar_link)) { ?>
|
||||
<img src="<?= $user->user_avatar_link; ?>"/>
|
||||
<?php } ?>
|
||||
</td>
|
||||
<td><?= $user->user_name; ?></td>
|
||||
<td><?= $user->user_email; ?></td>
|
||||
<td><?= ($user->user_active == 0 ? 'No' : 'Yes'); ?></td>
|
||||
<td>
|
||||
<a href="<?= Config::get('URL') . 'profile/showProfile/' . $user->user_id; ?>">Profile</a>
|
||||
</td>
|
||||
<form action="<?= config::get("URL"); ?>admin/actionAccountSettings" method="post">
|
||||
<td><input type="number" name="suspension" /></td>
|
||||
<td><input type="checkbox" name="softDelete" <?php if ($user->user_deleted) { ?> checked <?php } ?> /></td>
|
||||
<td>
|
||||
<input type="hidden" name="user_id" value="<?= $user->user_id; ?>" />
|
||||
<input type="submit" />
|
||||
</td>
|
||||
</form>
|
||||
</tr>
|
||||
<?php } ?>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
15
application/view/dashboard/index.php
Normal file
15
application/view/dashboard/index.php
Normal file
@@ -0,0 +1,15 @@
|
||||
<div class="container">
|
||||
<h1>DashboardController/index</h1>
|
||||
<div class="box">
|
||||
|
||||
<!-- echo out the system feedback (error and success messages) -->
|
||||
<?php $this->renderFeedbackMessages(); ?>
|
||||
|
||||
<h3>What happens here ?</h3>
|
||||
<p>
|
||||
This is an area that's only visible for logged in users. Try to log out, an go to /dashboard/ again. You'll
|
||||
be redirected to /index/ as you are not logged in. You can protect a whole section in your app within the
|
||||
according controller by placing <i>Auth::handleLogin();</i> into the constructor.
|
||||
<p>
|
||||
</div>
|
||||
</div>
|
||||
6
application/view/error/404.php
Normal file
6
application/view/error/404.php
Normal file
@@ -0,0 +1,6 @@
|
||||
<div class="container">
|
||||
<h1>404 - Page not found</h1>
|
||||
<div class="box">
|
||||
<p class="red-text">This page does not exist.</p>
|
||||
</div>
|
||||
</div>
|
||||
18
application/view/index/index.php
Normal file
18
application/view/index/index.php
Normal file
@@ -0,0 +1,18 @@
|
||||
<div class="container">
|
||||
<h1>IndexController/index</h1>
|
||||
<div class="box">
|
||||
|
||||
<!-- echo out the system feedback (error and success messages) -->
|
||||
<?php $this->renderFeedbackMessages(); ?>
|
||||
|
||||
<h3>What happens here ?</h3>
|
||||
<p>
|
||||
This is the homepage. As no real URL-route (like /register/index) is provided, the app uses the default
|
||||
controller and the default action, defined in application/config/config.php, by default it's
|
||||
IndexController and index()-method. So, the app will load application/controller/IndexController.php and
|
||||
run index() from that file. Easy. That index()-method (= the action) has just one line of code inside
|
||||
($this->view->render('index/index');) that loads application/view/index/index.php, which is basically
|
||||
this text you are reading right now.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
50
application/view/login/index.php
Normal file
50
application/view/login/index.php
Normal file
@@ -0,0 +1,50 @@
|
||||
<div class="container">
|
||||
|
||||
<!-- echo out the system feedback (error and success messages) -->
|
||||
<?php $this->renderFeedbackMessages(); ?>
|
||||
|
||||
<div class="login-page-box">
|
||||
<div class="table-wrapper">
|
||||
|
||||
<!-- login box on left side -->
|
||||
<div class="login-box">
|
||||
<h2>Login here</h2>
|
||||
<form action="<?php echo Config::get('URL'); ?>login/login" method="post">
|
||||
<input type="text" name="user_name" placeholder="Username or email" required />
|
||||
<input type="password" name="user_password" placeholder="Password" required />
|
||||
<label for="set_remember_me_cookie" class="remember-me-label">
|
||||
<input type="checkbox" name="set_remember_me_cookie" class="remember-me-checkbox" />
|
||||
Remember me for 2 weeks
|
||||
</label>
|
||||
<!-- when a user navigates to a page that's only accessible for logged a logged-in user, then
|
||||
the user is sent to this page here, also having the page he/she came from in the URL parameter
|
||||
(have a look). This "where did you came from" value is put into this form to sent the user back
|
||||
there after being logged in successfully.
|
||||
Simple but powerful feature, big thanks to @tysonlist. -->
|
||||
<?php if (!empty($this->redirect)) { ?>
|
||||
<input type="hidden" name="redirect" value="<?php echo $this->encodeHTML($this->redirect); ?>" />
|
||||
<?php } ?>
|
||||
<!--
|
||||
set CSRF token in login form, although sending fake login requests mightn't be interesting gap here.
|
||||
If you want to get deeper, check these answers:
|
||||
1. natevw's http://stackoverflow.com/questions/6412813/do-login-forms-need-tokens-against-csrf-attacks?rq=1
|
||||
2. http://stackoverflow.com/questions/15602473/is-csrf-protection-necessary-on-a-sign-up-form?lq=1
|
||||
3. http://stackoverflow.com/questions/13667437/how-to-add-csrf-token-to-login-form?lq=1
|
||||
-->
|
||||
<input type="hidden" name="csrf_token" value="<?= Csrf::makeToken(); ?>" />
|
||||
<input type="submit" class="login-submit-button" value="Log in"/>
|
||||
</form>
|
||||
<div class="link-forgot-my-password">
|
||||
<a href="<?php echo Config::get('URL'); ?>login/requestPasswordReset">I forgot my password</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- register box on right side -->
|
||||
<div class="register-box">
|
||||
<h2>No account yet ?</h2>
|
||||
<a href="<?php echo Config::get('URL'); ?>register/index">Register</a>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
35
application/view/login/requestPasswordReset.php
Normal file
35
application/view/login/requestPasswordReset.php
Normal file
@@ -0,0 +1,35 @@
|
||||
<div class="container">
|
||||
<h1>Request a password reset</h1>
|
||||
<div class="box">
|
||||
|
||||
<!-- echo out the system feedback (error and success messages) -->
|
||||
<?php $this->renderFeedbackMessages(); ?>
|
||||
|
||||
<!-- request password reset form box -->
|
||||
<form method="post" action="<?php echo Config::get('URL'); ?>login/requestPasswordReset_action">
|
||||
<label for="user_name_or_email">
|
||||
Enter your username or email and you'll get a mail with instructions:
|
||||
<input type="text" name="user_name_or_email" required />
|
||||
</label>
|
||||
|
||||
<!-- show the captcha by calling the login/showCaptcha-method in the src attribute of the img tag -->
|
||||
<img id="captcha" src="<?php echo Config::get('URL'); ?>register/showCaptcha" /><br/>
|
||||
<input type="text" name="captcha" placeholder="Enter captcha above" required />
|
||||
|
||||
<!-- quick & dirty captcha reloader -->
|
||||
<a href="#" style="display: block; font-size: 11px; margin: 5px 0 15px 0;"
|
||||
onclick="document.getElementById('captcha').src = '<?php echo Config::get('URL'); ?>register/showCaptcha?' + Math.random(); return false">Reload Captcha</a>
|
||||
|
||||
<input type="submit" value="Send me a password-reset mail" />
|
||||
</form>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div class="container">
|
||||
<p style="display: block; font-size: 11px; color: #999;">
|
||||
Please note: This captcha will be generated when the img tag requests the captcha-generation
|
||||
(= a real image) from YOURURL/register/showcaptcha. As this is a client-side triggered request, a
|
||||
$_SESSION["captcha"] dump will not show the captcha characters. The captcha generation
|
||||
happens AFTER the request that generates THIS page has been finished.
|
||||
</p>
|
||||
</div>
|
||||
27
application/view/login/resetPassword.php
Normal file
27
application/view/login/resetPassword.php
Normal file
@@ -0,0 +1,27 @@
|
||||
<div class="container">
|
||||
<h1>LoginController/resetPassword</h1>
|
||||
|
||||
<!-- echo out the system feedback (error and success messages) -->
|
||||
<?php $this->renderFeedbackMessages(); ?>
|
||||
|
||||
<div class="box">
|
||||
<h2>Set new password</h2>
|
||||
|
||||
<p>FYI: ... Idenfitication process works via password-reset-token (hidden input field)</p>
|
||||
|
||||
<!-- new password form box -->
|
||||
<form method="post" action="<?php echo Config::get('URL'); ?>login/setNewPassword" name="new_password_form">
|
||||
<input type='hidden' name='user_name' value='<?php echo $this->user_name; ?>' />
|
||||
<input type='hidden' name='user_password_reset_hash' value='<?php echo $this->user_password_reset_hash; ?>' />
|
||||
<label for="reset_input_password_new">New password (min. 6 characters)</label>
|
||||
<input id="reset_input_password_new" class="reset_input" type="password"
|
||||
name="user_password_new" pattern=".{6,}" required autocomplete="off" />
|
||||
<label for="reset_input_password_repeat">Repeat new password</label>
|
||||
<input id="reset_input_password_repeat" class="reset_input" type="password"
|
||||
name="user_password_repeat" pattern=".{6,}" required autocomplete="off" />
|
||||
<input type="submit" name="submit_new_password" value="Submit new password" />
|
||||
</form>
|
||||
|
||||
<a href="<?php echo Config::get('URL'); ?>login/index">Back to Login Page</a>
|
||||
</div>
|
||||
</div>
|
||||
22
application/view/note/edit.php
Normal file
22
application/view/note/edit.php
Normal file
@@ -0,0 +1,22 @@
|
||||
<div class="container">
|
||||
<h1>NoteController/edit/:note_id</h1>
|
||||
|
||||
<div class="box">
|
||||
<h2>Edit a note</h2>
|
||||
|
||||
<!-- echo out the system feedback (error and success messages) -->
|
||||
<?php $this->renderFeedbackMessages(); ?>
|
||||
|
||||
<?php if ($this->note) { ?>
|
||||
<form method="post" action="<?php echo Config::get('URL'); ?>note/editSave">
|
||||
<label>Change text of note: </label>
|
||||
<!-- we use htmlentities() here to prevent user input with " etc. break the HTML -->
|
||||
<input type="hidden" name="note_id" value="<?php echo htmlentities($this->note->note_id); ?>" />
|
||||
<input type="text" name="note_text" value="<?php echo htmlentities($this->note->note_text); ?>" />
|
||||
<input type="submit" value='Change' />
|
||||
</form>
|
||||
<?php } else { ?>
|
||||
<p>This note does not exist.</p>
|
||||
<?php } ?>
|
||||
</div>
|
||||
</div>
|
||||
44
application/view/note/index.php
Normal file
44
application/view/note/index.php
Normal file
@@ -0,0 +1,44 @@
|
||||
<div class="container">
|
||||
<h1>NoteController/index</h1>
|
||||
<div class="box">
|
||||
|
||||
<!-- echo out the system feedback (error and success messages) -->
|
||||
<?php $this->renderFeedbackMessages(); ?>
|
||||
|
||||
<h3>What happens here ?</h3>
|
||||
<p>
|
||||
This is just a simple CRUD implementation. Creating, reading, updating and deleting things.
|
||||
</p>
|
||||
<p>
|
||||
<form method="post" action="<?php echo Config::get('URL');?>note/create">
|
||||
<label>Text of new note: </label><input type="text" name="note_text" />
|
||||
<input type="submit" value='Create this note' autocomplete="off" />
|
||||
</form>
|
||||
</p>
|
||||
|
||||
<?php if ($this->notes) { ?>
|
||||
<table class="note-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<td>Id</td>
|
||||
<td>Note</td>
|
||||
<td>EDIT</td>
|
||||
<td>DELETE</td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach($this->notes as $key => $value) { ?>
|
||||
<tr>
|
||||
<td><?= $value->note_id; ?></td>
|
||||
<td><?= htmlentities($value->note_text); ?></td>
|
||||
<td><a href="<?= Config::get('URL') . 'note/edit/' . $value->note_id; ?>">Edit</a></td>
|
||||
<td><a href="<?= Config::get('URL') . 'note/delete/' . $value->note_id; ?>">Delete</a></td>
|
||||
</tr>
|
||||
<?php } ?>
|
||||
</tbody>
|
||||
</table>
|
||||
<?php } else { ?>
|
||||
<div>No notes yet. Create some !</div>
|
||||
<?php } ?>
|
||||
</div>
|
||||
</div>
|
||||
44
application/view/profile/index.php
Normal file
44
application/view/profile/index.php
Normal file
@@ -0,0 +1,44 @@
|
||||
<div class="container">
|
||||
<h1>ProfileController/index</h1>
|
||||
<div class="box">
|
||||
|
||||
<!-- echo out the system feedback (error and success messages) -->
|
||||
<?php $this->renderFeedbackMessages(); ?>
|
||||
|
||||
<h3>What happens here ?</h3>
|
||||
<div>
|
||||
This controller/action/view shows a list of all users in the system. You could use the underlying code to
|
||||
build things that use profile information of one or multiple/all users.
|
||||
</div>
|
||||
<div>
|
||||
<table class="overview-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<td>Id</td>
|
||||
<td>Avatar</td>
|
||||
<td>Username</td>
|
||||
<td>User's email</td>
|
||||
<td>Activated ?</td>
|
||||
<td>Link to user's profile</td>
|
||||
</tr>
|
||||
</thead>
|
||||
<?php foreach ($this->users as $user) { ?>
|
||||
<tr class="<?= ($user->user_active == 0 ? 'inactive' : 'active'); ?>">
|
||||
<td><?= $user->user_id; ?></td>
|
||||
<td class="avatar">
|
||||
<?php if (isset($user->user_avatar_link)) { ?>
|
||||
<img src="<?= $user->user_avatar_link; ?>" />
|
||||
<?php } ?>
|
||||
</td>
|
||||
<td><?= $user->user_name; ?></td>
|
||||
<td><?= $user->user_email; ?></td>
|
||||
<td><?= ($user->user_active == 0 ? 'No' : 'Yes'); ?></td>
|
||||
<td>
|
||||
<a href="<?= Config::get('URL') . 'profile/showProfile/' . $user->user_id; ?>">Profile</a>
|
||||
</td>
|
||||
</tr>
|
||||
<?php } ?>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
41
application/view/profile/showProfile.php
Normal file
41
application/view/profile/showProfile.php
Normal file
@@ -0,0 +1,41 @@
|
||||
<div class="container">
|
||||
<h1>ProfileController/showProfile/:id</h1>
|
||||
<div class="box">
|
||||
|
||||
<!-- echo out the system feedback (error and success messages) -->
|
||||
<?php $this->renderFeedbackMessages(); ?>
|
||||
|
||||
<h3>What happens here ?</h3>
|
||||
<div>This controller/action/view shows all public information about a certain user.</div>
|
||||
|
||||
<?php if ($this->user) { ?>
|
||||
<div>
|
||||
<table class="overview-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<td>Id</td>
|
||||
<td>Avatar</td>
|
||||
<td>Username</td>
|
||||
<td>User's email</td>
|
||||
<td>Activated ?</td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr class="<?= ($this->user->user_active == 0 ? 'inactive' : 'active'); ?>">
|
||||
<td><?= $this->user->user_id; ?></td>
|
||||
<td class="avatar">
|
||||
<?php if (isset($this->user->user_avatar_link)) { ?>
|
||||
<img src="<?= $this->user->user_avatar_link; ?>" />
|
||||
<?php } ?>
|
||||
</td>
|
||||
<td><?= $this->user->user_name; ?></td>
|
||||
<td><?= $this->user->user_email; ?></td>
|
||||
<td><?= ($this->user->user_active == 0 ? 'No' : 'Yes'); ?></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<?php } ?>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
38
application/view/register/index.php
Normal file
38
application/view/register/index.php
Normal file
@@ -0,0 +1,38 @@
|
||||
<div class="container">
|
||||
|
||||
<!-- echo out the system feedback (error and success messages) -->
|
||||
<?php $this->renderFeedbackMessages(); ?>
|
||||
|
||||
<!-- login box on left side -->
|
||||
<div class="login-box" style="width: 50%; display: block;">
|
||||
<h2>Register a new account</h2>
|
||||
|
||||
<!-- register form -->
|
||||
<form method="post" action="<?php echo Config::get('URL'); ?>register/register_action">
|
||||
<!-- the user name input field uses a HTML5 pattern check -->
|
||||
<input type="text" pattern="[a-zA-Z0-9]{2,64}" name="user_name" placeholder="Username (letters/numbers, 2-64 chars)" required />
|
||||
<input type="text" name="user_email" placeholder="email address (a real address)" required />
|
||||
<input type="text" name="user_email_repeat" placeholder="repeat email address (to prevent typos)" required />
|
||||
<input type="password" name="user_password_new" pattern=".{6,}" placeholder="Password (6+ characters)" required autocomplete="off" />
|
||||
<input type="password" name="user_password_repeat" pattern=".{6,}" required placeholder="Repeat your password" autocomplete="off" />
|
||||
|
||||
<!-- show the captcha by calling the login/showCaptcha-method in the src attribute of the img tag -->
|
||||
<img id="captcha" src="<?php echo Config::get('URL'); ?>register/showCaptcha" />
|
||||
<input type="text" name="captcha" placeholder="Please enter above characters" required />
|
||||
|
||||
<!-- quick & dirty captcha reloader -->
|
||||
<a href="#" style="display: block; font-size: 11px; margin: 5px 0 15px 0; text-align: center"
|
||||
onclick="document.getElementById('captcha').src = '<?php echo Config::get('URL'); ?>register/showCaptcha?' + Math.random(); return false">Reload Captcha</a>
|
||||
|
||||
<input type="submit" value="Register" />
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div class="container">
|
||||
<p style="display: block; font-size: 11px; color: #999;">
|
||||
Please note: This captcha will be generated when the img tag requests the captcha-generation
|
||||
(= a real image) from YOURURL/register/showcaptcha. As this is a client-side triggered request, a
|
||||
$_SESSION["captcha"] dump will not show the captcha characters. The captcha generation
|
||||
happens AFTER the request that generates THIS page has been finished.
|
||||
</p>
|
||||
</div>
|
||||
12
application/view/register/verify.php
Normal file
12
application/view/register/verify.php
Normal file
@@ -0,0 +1,12 @@
|
||||
<div class="container">
|
||||
|
||||
<h1>Verification</h1>
|
||||
<div class="box">
|
||||
|
||||
<!-- echo out the system feedback (error and success messages) -->
|
||||
<?php $this->renderFeedbackMessages(); ?>
|
||||
|
||||
<a href="<?php echo Config::get('URL'); ?>">Go back to home page</a>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
25
application/view/user/changePassword.php
Normal file
25
application/view/user/changePassword.php
Normal file
@@ -0,0 +1,25 @@
|
||||
<div class="container">
|
||||
<h1>UserController/changePassword</h1>
|
||||
|
||||
<!-- echo out the system feedback (error and success messages) -->
|
||||
<?php $this->renderFeedbackMessages(); ?>
|
||||
|
||||
<div class="box">
|
||||
<h2>Set new password</h2>
|
||||
|
||||
<!-- new password form box -->
|
||||
<form method="post" action="<?php echo Config::get('URL'); ?>user/changePassword_action" name="new_password_form">
|
||||
<label for="change_input_password_current">Enter Current Password:</label>
|
||||
<p><input id="change_input_password_current" class="reset_input" type='password'
|
||||
name='user_password_current' pattern=".{6,}" required autocomplete="off" /></p>
|
||||
<label for="change_input_password_new">New password (min. 6 characters)</label>
|
||||
<p><input id="change_input_password_new" class="reset_input" type="password"
|
||||
name="user_password_new" pattern=".{6,}" required autocomplete="off" /></p>
|
||||
<label for="change_input_password_repeat">Repeat new password</label>
|
||||
<p><input id="change_input_password_repeat" class="reset_input" type="password"
|
||||
name="user_password_repeat" pattern=".{6,}" required autocomplete="off" /></p>
|
||||
<input type="submit" name="submit_new_password" value="Submit new password" />
|
||||
</form>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
31
application/view/user/changeUserRole.php
Normal file
31
application/view/user/changeUserRole.php
Normal file
@@ -0,0 +1,31 @@
|
||||
<div class="container">
|
||||
<h1>UserController/changeUserRole</h1>
|
||||
|
||||
<!-- echo out the system feedback (error and success messages) -->
|
||||
<?php $this->renderFeedbackMessages(); ?>
|
||||
|
||||
<div class="box">
|
||||
<h2>Change account type</h2>
|
||||
<p>
|
||||
This page is a basic implementation of the upgrade-process.
|
||||
User can click on that button to upgrade their accounts from
|
||||
"basic account" to "premium account". This script simple offers
|
||||
a click-able button that will upgrade/downgrade the account instantly.
|
||||
In a real world application you would implement something like a
|
||||
pay-process.
|
||||
</p>
|
||||
<p>
|
||||
Please note: This whole process has been renamed from AccountType (v3.0) to UserRole (v3.1).
|
||||
</p>
|
||||
|
||||
<h2>Currently your account type is: <?php echo Session::get('user_account_type'); ?></h2>
|
||||
<!-- basic implementation for two account types: type 1 and type 2 -->
|
||||
<form action="<?php echo Config::get('URL'); ?>user/changeUserRole_action" method="post">
|
||||
<?php if (Session::get('user_account_type') == 1) { ?>
|
||||
<input type="submit" name="user_account_upgrade" value="Upgrade my account (to Premium User)" />
|
||||
<?php } else if (Session::get('user_account_type') == 2) { ?>
|
||||
<input type="submit" name="user_account_downgrade" value="Downgrade my account (to Basic User)" />
|
||||
<?php } ?>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
28
application/view/user/editAvatar.php
Normal file
28
application/view/user/editAvatar.php
Normal file
@@ -0,0 +1,28 @@
|
||||
<div class="container">
|
||||
<h1>Edit your avatar</h1>
|
||||
|
||||
<!-- echo out the system feedback (error and success messages) -->
|
||||
<?php $this->renderFeedbackMessages(); ?>
|
||||
|
||||
<div class="box">
|
||||
<h3>Upload an Avatar</h3>
|
||||
|
||||
<div class="feedback info">
|
||||
If you still see the old picture after uploading a new one: Hard-Reload the page with F5! Your browser doesn't
|
||||
realize there's a new image as new and old one have the same filename.
|
||||
</div>
|
||||
|
||||
<form action="<?php echo Config::get('URL'); ?>user/uploadAvatar_action" method="post" enctype="multipart/form-data">
|
||||
<label for="avatar_file">Select an avatar image from your hard-disk (will be scaled to 44x44 px, only .jpg currently):</label>
|
||||
<input type="file" name="avatar_file" required />
|
||||
<!-- max size 5 MB (as many people directly upload high res pictures from their digital cameras) -->
|
||||
<input type="hidden" name="MAX_FILE_SIZE" value="5000000" />
|
||||
<input type="submit" value="Upload image" />
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="box">
|
||||
<h3>Delete your avatar</h3>
|
||||
<p>Click this link to delete your (local) avatar: <a href="<?php echo Config::get('URL'); ?>user/deleteAvatar_action">Delete your avatar</a>
|
||||
</div>
|
||||
</div>
|
||||
17
application/view/user/editUserEmail.php
Normal file
17
application/view/user/editUserEmail.php
Normal file
@@ -0,0 +1,17 @@
|
||||
<div class="container">
|
||||
<h1>UserController/editUserEmail</h1>
|
||||
|
||||
<!-- echo out the system feedback (error and success messages) -->
|
||||
<?php $this->renderFeedbackMessages(); ?>
|
||||
|
||||
<div class="box">
|
||||
<h2>Change your email address</h2>
|
||||
|
||||
<form action="<?php echo Config::get('URL'); ?>user/editUserEmail_action" method="post">
|
||||
<label>
|
||||
New email address: <input type="text" name="user_email" required />
|
||||
</label>
|
||||
<input type="submit" value="Submit" />
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
20
application/view/user/editUsername.php
Normal file
20
application/view/user/editUsername.php
Normal file
@@ -0,0 +1,20 @@
|
||||
<div class="container">
|
||||
<h1>UserController/editUsername</h1>
|
||||
|
||||
<!-- echo out the system feedback (error and success messages) -->
|
||||
<?php $this->renderFeedbackMessages(); ?>
|
||||
|
||||
<div class="box">
|
||||
<h2>Change your username</h2>
|
||||
|
||||
<form action="<?php echo Config::get('URL'); ?>user/editUserName_action" method="post">
|
||||
<!-- btw http://stackoverflow.com/questions/774054/should-i-put-input-tag-inside-label-tag -->
|
||||
<label>
|
||||
New username: <input type="text" name="user_name" required />
|
||||
</label>
|
||||
<!-- set CSRF token at the end of the form -->
|
||||
<input type="hidden" name="csrf_token" value="<?= Csrf::makeToken(); ?>" />
|
||||
<input type="submit" value="Submit" />
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
21
application/view/user/index.php
Normal file
21
application/view/user/index.php
Normal file
@@ -0,0 +1,21 @@
|
||||
<div class="container">
|
||||
<h1>UserController/showProfile</h1>
|
||||
|
||||
<div class="box">
|
||||
<h2>Your profile</h2>
|
||||
|
||||
<!-- echo out the system feedback (error and success messages) -->
|
||||
<?php $this->renderFeedbackMessages(); ?>
|
||||
|
||||
<div>Your username: <?= $this->user_name; ?></div>
|
||||
<div>Your email: <?= $this->user_email; ?></div>
|
||||
<div>Your avatar image:
|
||||
<?php if (Config::get('USE_GRAVATAR')) { ?>
|
||||
Your gravatar pic (on gravatar.com): <img src='<?= $this->user_gravatar_image_url; ?>' />
|
||||
<?php } else { ?>
|
||||
Your avatar pic (saved locally): <img src='<?= $this->user_avatar_file; ?>' />
|
||||
<?php } ?>
|
||||
</div>
|
||||
<div>Your account type is: <?= $this->user_account_type; ?></div>
|
||||
</div>
|
||||
</div>
|
||||
Reference in New Issue
Block a user