1
0
Fork 0
mirror of https://github.com/ThomasGsp/HyperProxmox.git synced 2025-03-09 15:40:18 +00:00

first commit

This commit is contained in:
thomas.guiseppin 2017-10-21 22:04:42 +02:00
commit 5352a2b94a
396 changed files with 10008 additions and 0 deletions

View file

@ -0,0 +1 @@
CREATE DATABASE IF NOT EXISTS `huge`;

View file

@ -0,0 +1,28 @@
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',
`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(64) 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_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_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(40) COLLATE utf8_unicode_ci DEFAULT NULL COMMENT 'user''s email verification hash string',
`user_password_reset_hash` char(40) 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=2 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci COMMENT='user data';
INSERT INTO `huge`.`users` (`user_id`, `user_name`, `user_password_hash`, `user_email`, `user_active`, `user_account_type`,
`user_has_avatar`, `user_remember_me_token`, `user_creation_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, 'demo', '$2y$10$OvprunjvKOOhM1h9bzMPs.vuwGIsOqZbw88rzSyGCTJTcE61g5WXi', 'demo@demo.com', 1, 1, 0, NULL, 1422205178,
1422209189, 0, NULL, NULL, NULL, NULL, 'DEFAULT');

View 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';

View file

@ -0,0 +1,129 @@
<?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);
/**
* 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' => '127.0.0.1',
'DB_NAME' => 'huge',
'DB_USER' => 'root',
'DB_PASS' => '12345678',
'DB_PORT' => '3306',
'DB_CHARSET' => 'utf8',
/**
* Configuration for: Additional login providers: Facebook
* CURRENTLY REMOVED (as Facebook has removed support for the used API version).
* Another, better and up-to-date implementation might come soon.
*/
'FACEBOOK_LOGIN' => false,
/**
* 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_RUNTIME' => 1209600,
'COOKIE_PATH' => '/',
/**
* 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: 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' => 'login/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: ',
);

View file

@ -0,0 +1,73 @@
<?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_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_PASSWORD_WRONG" => "Password was wrong.",
"FEEDBACK_USER_DOES_NOT_EXIST" => "This user does not exist.",
"FEEDBACK_LOGIN_FAILED" => "Login failed.",
"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_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...",
"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_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.",
"FEEDBACK_FACEBOOK_LOGIN_NOT_REGISTERED" => "Sorry, you don't have an account here. Please register first.",
"FEEDBACK_FACEBOOK_EMAIL_NEEDED" => "Sorry, but you need to allow us to see your email address to register.",
"FEEDBACK_FACEBOOK_UID_ALREADY_EXISTS" => "Sorry, but you have already registered here (your Facebook ID exists in our database).",
"FEEDBACK_FACEBOOK_EMAIL_ALREADY_EXISTS" => "Sorry, but you have already registered here (your Facebook email exists in our database).",
"FEEDBACK_FACEBOOK_USERNAME_ALREADY_EXISTS" => "Sorry, but you have already registered here (your Facebook username exists in our database).",
"FEEDBACK_FACEBOOK_REGISTER_SUCCESSFUL" => "You have been successfully registered with Facebook.",
"FEEDBACK_FACEBOOK_OFFLINE" => "We could not reach the Facebook servers. Maybe Facebook is offline (that really happens sometimes).",
);

View 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');
}
}

View file

@ -0,0 +1,25 @@
<?php
/**
* Class Error
* This controller simply shows a page that will be displayed when a controller/method is not found. Simple 404.
*/
class ErrorController extends Controller
{
/**
* Construct this object by extending the basic Controller class
*/
public function __construct()
{
parent::__construct();
}
/**
* This method controls what happens / what the user sees when a page does not exist (404)
*/
public function index()
{
header('HTTP/1.0 404 Not Found');
$this->View->render('error/index');
}
}

View 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');
}
}

View file

@ -0,0 +1,313 @@
<?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 {
$this->View->render('login/index');
}
}
/**
* The login action, when you do login/login
*/
public function login()
{
// 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 login/showProfile, if false, then to login form again
if ($login_successful) {
Redirect::to('login/showProfile');
} else {
Redirect::to('login/index');
}
}
/**
* The logout action
* Perform logout, redirect user to main-page
*/
public function logout()
{
LoginModel::logout();
Redirect::home();
}
/**
* 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 user's PRIVATE profile
* Auth::checkAuthentication() makes sure that only logged in users can use this action and see this page
*/
public function showProfile()
{
Auth::checkAuthentication();
$this->View->render('login/showProfile', 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
* Auth::checkAuthentication() makes sure that only logged in users can use this action and see this page
*/
public function editUsername()
{
Auth::checkAuthentication();
$this->View->render('login/editUsername');
}
/**
* Edit user name (perform the real action after form has been submitted)
* Auth::checkAuthentication() makes sure that only logged in users can use this action
*/
public function editUsername_action()
{
Auth::checkAuthentication();
UserModel::editUserName(Request::post('user_name'));
Redirect::to('login/index');
}
/**
* Show edit-my-user-email page
* Auth::checkAuthentication() makes sure that only logged in users can use this action and see this page
*/
public function editUserEmail()
{
Auth::checkAuthentication();
$this->View->render('login/editUserEmail');
}
/**
* Edit user email (perform the real action after form has been submitted)
* Auth::checkAuthentication() makes sure that only logged in users can use this action and see this page
*/
// make this POST
public function editUserEmail_action()
{
Auth::checkAuthentication();
UserModel::editUserEmail(Request::post('user_email'));
Redirect::to('login/editUserEmail');
}
/**
* Edit avatar
* Auth::checkAuthentication() makes sure that only logged in users can use this action and see this page
*/
public function editAvatar()
{
Auth::checkAuthentication();
$this->View->render('login/editAvatar', array(
'avatar_file_path' => AvatarModel::getPublicUserAvatarFilePathByUserId(Session::get('user_id'))
));
}
/**
* Perform the upload of the avatar
* Auth::checkAuthentication() makes sure that only logged in users can use this action and see this page
* POST-request
*/
public function uploadAvatar_action()
{
Auth::checkAuthentication();
AvatarModel::createAvatar();
Redirect::to('login/editAvatar');
}
/**
* Delete the current user's avatar
* Auth::checkAuthentication() makes sure that only logged in users can use this action and see this page
*/
public function deleteAvatar_action()
{
Auth::checkAuthentication();
AvatarModel::deleteAvatar(Session::get("user_id"));
Redirect::to('login/editAvatar');
}
/**
* Show the change-account-type page
* Auth::checkAuthentication() makes sure that only logged in users can use this action and see this page
*/
public function changeUserRole()
{
Auth::checkAuthentication();
$this->View->render('login/changeUserRole');
}
/**
* Perform the account-type changing
* Auth::checkAuthentication() makes sure that only logged in users can use this action
* POST-request
*/
public function changeUserRole_action()
{
Auth::checkAuthentication();
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('login/changeUserRole');
}
/**
* Register page
* Show the register form, but redirect to main-page if user is already logged-in
*/
public function register()
{
if (LoginModel::isUserLoggedIn()) {
Redirect::home();
} else {
$this->View->render('login/register');
}
}
/**
* 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('login/register');
}
}
/**
* 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('login/verify');
} else {
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'));
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/changePassword', 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');
}
/**
* 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();
}
}

View 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');
}
}

View 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();
}
}
}

View file

@ -0,0 +1,100 @@
<?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 for method: does such a method exist in the controller ?
if (method_exists($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 {
header('location: ' . Config::get('URL') . 'error');
}
} else {
header('location: ' . Config::get('URL') . 'error');
}
}
/**
* 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';
}
}

View file

@ -0,0 +1,28 @@
<?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
{
public static function checkAuthentication()
{
// initialize the session (if not initialized yet)
Session::init();
// if user is not logged in...
if (!Session::userIsLoggedIn()) {
// ... 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();
}
}
}

View 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];
}
}

View file

@ -0,0 +1,31 @@
<?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();
*/
function __construct()
{
// always initialize a session
Session::init();
// 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();
}
}

View file

@ -0,0 +1,46 @@
<?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) {
$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
);
}
return $this->database;
}
}

View 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");
}
}

View file

@ -0,0 +1,115 @@
<?php
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;
// 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
$mail->Send();
if ($mail) {
return true;
} else {
// if not successful, copy errors into Mail's error property
$this->error = $mail->ErrorInfo;
return false;
}
}
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();
}
}
public function getError()
{
return $this->error;
}
}

View file

@ -0,0 +1,27 @@
<?php
/**
* Class Redirect
*
* Simple abstraction for redirecting the user to a certain page
*/
class Redirect
{
/**
* To the homepage
*/
public static function home()
{
header("location: " . Config::get('URL'));
}
/**
* To the defined page
*
* @param $path
*/
public static function to($path)
{
header("location: " . Config::get('URL') . $path);
}
}

View file

@ -0,0 +1,53 @@
<?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];
}
}
/**
* 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];
}
}
}

View file

@ -0,0 +1,75 @@
<?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])) {
return $_SESSION[$key];
}
}
/**
* 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();
}
/**
* Checks if the user is logged in or not
*
* @return bool user's login status
*/
public static function userIsLoggedIn()
{
return (Session::get('user_logged_in') ? true : false);
}
}

View file

@ -0,0 +1,27 @@
<?php
class Text
{
private static $texts;
public static function get($key)
{
// if not $key
if (!$key) {
return null;
}
// 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];
}
}

View file

@ -0,0 +1,164 @@
<?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)
{
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;
}
}

View file

@ -0,0 +1,254 @@
<?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 (AvatarModel::isAvatarFolderWritable() AND AvatarModel::validateImageFile()) {
// create a jpg file in the avatar folder, write marker to database
$target_file_path = Config::get('PATH_AVATARS') . Session::get('user_id');
AvatarModel::resizeAvatarImage($_FILES['avatar_file']['tmp_name'], $target_file_path, Config::get('AVATAR_SIZE'), Config::get('AVATAR_SIZE'), Config::get('AVATAR_JPEG_QUALITY'));
AvatarModel::writeAvatarToDatabase(Session::get('user_id'));
Session::set('user_avatar_file', AvatarModel::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
* TODO totally decouple
*
* @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 ($_FILES['avatar_file']['size'] > 5000000) {
// if input file too big (>5MB)
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
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 (!($image_proportions['mime'] == 'image/jpeg')) {
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 sexy)
*
* 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.
* @param int $quality The quality of the JPG to produce 1 - 100
*
* TODO currently we just allow .jpg
*
* @return bool success state
*/
public static function resizeAvatarImage($source_image, $destination, $final_width = 44, $final_height = 44, $quality = 85)
{
list($width, $height) = getimagesize($source_image);
if (!$width || !$height) {
return false;
}
//saving the image into memory (for manipulation with GD Library)
$myImage = imagecreatefromjpeg($source_image);
// calculating the part of the image to use for thumbnail
if ($width > $height) {
$y = 0;
$x = ($width - $height) / 2;
$smallestSide = $height;
} else {
$x = 0;
$y = ($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, $x, $y, $final_width, $final_height, $smallestSide, $smallestSide);
// add '.jpg' to file path, save it as a .jpg file with our $destination_filename parameter
$destination .= '.jpg';
imagejpeg($thumb, $destination, $quality);
// delete "working copy"
imagedestroy($thumb);
if (file_exists($destination)) {
return true;
}
// default return
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 $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;
}
}

View 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 ($captcha == Session::get('captcha')) {
return true;
}
return false;
}
}

View file

@ -0,0 +1,270 @@
<?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);
if (!$result) {
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)
{
// 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.
if (!$result) {
Session::add('feedback_negative', Text::get('FEEDBACK_LOGIN_FAILED'));
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);
// we say "password wrong" here, but less details like "login failed" would be better (= less information)
Session::add('feedback_negative', Text::get('FEEDBACK_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;
}
return $result;
}
/**
* 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)
{
if (!$cookie) {
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);
if ($hash !== hash('sha256', $user_id . ':' . $token) OR empty($token)) {
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 ($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);
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()
{
self::deleteCookie();
Session::destroy();
}
/**
* 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();
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);
}
/**
* 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
$cookie_string_first_part = $user_id . ':' . $random_token_string;
$cookie_string_hash = hash('sha256', $cookie_string_first_part);
$cookie_string = $cookie_string_first_part . ':' . $cookie_string_hash;
// set cookie
setcookie('remember_me', $cookie_string, time() + Config::get('COOKIE_RUNTIME'), Config::get('COOKIE_PATH'));
}
/**
* 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
*/
public static function deleteCookie()
{
setcookie('remember_me', false, time() - (3600 * 24 * 3650), Config::get('COOKIE_PATH'));
}
/**
* Returns the current state of the user's login
*
* @return bool user's login status
*/
public static function isUserLoggedIn()
{
return Session::userIsLoggedIn();
}
}

View 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;
}
}

View file

@ -0,0 +1,251 @@
<?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
*
* @return bool success status
*/
public static function requestPasswordReset($user_name_or_email)
{
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 char string)
$temporary_timestamp = time();
$user_password_reset_hash = sha1(uniqid(mt_rand(), true));
// set token (= a random hash string and a timestamp) into database ...
$token_set = PasswordResetModel::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 = PasswordResetModel::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::validateNewPassword($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 (PasswordResetModel::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 validateNewPassword($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;
}
}

View file

@ -0,0 +1,278 @@
<?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()
{
// TODO this could be written simpler and cleaner
// clean the input
$user_name = strip_tags(Request::post('user_name'));
$user_email = strip_tags(Request::post('user_email'));
$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 = RegistrationModel::registrationInputValidation(Request::post('captcha'), $user_name, $user_password_new, $user_password_repeat, $user_email);
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);
// 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;
}
// generate random hash for email verification (40 char string)
$user_activation_hash = sha1(uniqid(mt_rand(), true));
// write user data to database
if (!RegistrationModel::writeNewUserToDatabase($user_name, $user_password_hash, $user_email, time(), $user_activation_hash)) {
Session::add('feedback_negative', Text::get('FEEDBACK_ACCOUNT_CREATION_FAILED'));
}
// 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 (RegistrationModel::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
RegistrationModel::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
*
* @return bool
*/
public static function registrationInputValidation($captcha, $user_name, $user_password_new, $user_password_repeat, $user_email)
{
// 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
if (self::validateUserName($user_name) AND self::validateUserEmail($user_email) AND self::validateUserPassword($user_password_new, $user_password_repeat)) {
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
* @return bool
*/
public static function validateUserEmail($user_email)
{
if (empty($user_email)) {
Session::add('feedback_negative', Text::get('FEEDBACK_EMAIL_FIELD_EMPTY'));
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;
}
}

View file

@ -0,0 +1,331 @@
<?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 FROM users";
$query = $database->prepare($sql);
$query->execute();
$all_users_profiles = array();
foreach ($query->fetchAll() as $user) {
$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_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
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'));
}
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 (UserModel::doesUsernameAlreadyExist($new_user_name)) {
Session::add('feedback_negative', Text::get('FEEDBACK_USERNAME_ALREADY_TAKEN'));
return false;
}
$status_of_action = UserModel::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 (UserModel::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 (UserModel::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_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();
}
}

View 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;
}
}

View 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>';
}
}

View 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>

View file

@ -0,0 +1,66 @@
<!doctype html>
<html>
<head>
<!-- META -->
<meta charset="utf-8">
<!-- 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, "overview")) { 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, "login/register")) { echo ' class="active" '; } ?> >
<a href="<?php echo Config::get('URL'); ?>login/register">Register</a>
</li>
<?php } ?>
</ul>
<!-- my account -->
<ul class="navigation right">
<?php if (Session::userIsLoggedIn()) : ?>
<li <?php if (View::checkForActiveController($filename, "login")) { echo ' class="active" '; } ?> >
<a href="<?php echo Config::get('URL'); ?>login/showprofile">My Account</a>
<ul class="navigation-submenu">
<li <?php if (View::checkForActiveController($filename, "login")) { echo ' class="active" '; } ?> >
<a href="<?php echo Config::get('URL'); ?>login/changeUserRole">Change account type</a>
</li>
<li <?php if (View::checkForActiveController($filename, "login")) { echo ' class="active" '; } ?> >
<a href="<?php echo Config::get('URL'); ?>login/editAvatar">Edit your avatar</a>
</li>
<li <?php if (View::checkForActiveController($filename, "login")) { echo ' class="active" '; } ?> >
<a href="<?php echo Config::get('URL'); ?>login/editusername">Edit my username</a>
</li>
<li <?php if (View::checkForActiveController($filename, "login")) { echo ' class="active" '; } ?> >
<a href="<?php echo Config::get('URL'); ?>login/edituseremail">Edit my email</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 endif; ?>
</ul>

View 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>

View file

@ -0,0 +1,6 @@
<div class="container">
<h1>Page not found</h1>
<div class="box">
<p class="red-text">This page does not exist.</p>
</div>
</div>

View 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 /login/register) 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>

View file

@ -0,0 +1,27 @@
<div class="container">
<h1>LoginController/changePassword</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>

View file

@ -0,0 +1,31 @@
<div class="container">
<h1>LoginController/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'); ?>login/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>

View 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'); ?>login/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'); ?>login/deleteAvatar_action">Delete your avatar</a>
</div>
</div>

View file

@ -0,0 +1,17 @@
<div class="container">
<h1>LoginController/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'); ?>login/editUserEmail_action" method="post">
<label>
New email address: <input type="text" name="user_email" required />
</label>
<input type="submit" value="Submit" />
</form>
</div>
</div>

View file

@ -0,0 +1,18 @@
<div class="container">
<h1>LoginController/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'); ?>login/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>
<input type="submit" value="Submit" />
</form>
</div>
</div>

View file

@ -0,0 +1,34 @@
<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>
<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'); ?>login/register">Register</a>
</div>
</div>
</div>
</div>

View file

@ -0,0 +1,37 @@
<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'); ?>login/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="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'); ?>login/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'); ?>login/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/login/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>

View file

@ -0,0 +1,18 @@
<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>
Enter your username or email and you'll get a mail with instructions:
<input type="text" name="user_name_or_email" required />
</label>
<input type="submit" value="Send me a password-reset mail" />
</form>
</div>
</div>

View file

@ -0,0 +1,21 @@
<div class="container">
<h1>LoginController/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>

View 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>

View 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>

View file

@ -0,0 +1,43 @@
<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><?= 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>

View 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>

View 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>