diff options
Diffstat (limited to 'autoload/Todo')
-rw-r--r-- | autoload/Todo/Lists.php | 141 | ||||
-rw-r--r-- | autoload/Todo/Model/TodoList.php | 100 | ||||
-rw-r--r-- | autoload/Todo/Model/User.php | 66 | ||||
-rw-r--r-- | autoload/Todo/SessionHelper.php | 49 | ||||
-rw-r--r-- | autoload/Todo/UserManager.php | 203 |
5 files changed, 559 insertions, 0 deletions
diff --git a/autoload/Todo/Lists.php b/autoload/Todo/Lists.php new file mode 100644 index 0000000..9f951a3 --- /dev/null +++ b/autoload/Todo/Lists.php @@ -0,0 +1,141 @@ +<?php + +namespace Todo; + +class Lists extends SessionHelper { + public function home($f3) { + $f3->set('content', ($this->user === NULL) ? 'login.html' : 'noListSelected.html'); + } + + private function populateListData($f3, $l) { + $f3->set('listid', $l->id); + $f3->set('listname', $l->name); + $f3->set('listdata', $l->itemsToArray()); + } + + private function getList($f3, $id) { + $l = new \Todo\Model\TodoList($f3->get('DB')); + if((!$l->byID($id)) || $l->user !== $this->user->id) { + $f3->set('content', 'blank.html'); + $f3->set('error', 'List not found'); + $f3->error(404); # TODO: Meh, this stops F3 from rendering the template.... + return NULL; + } + return $l; + } + + public function deleteList($f3, $args) { + if($this->needLogin($f3)) { + return; + } + + if(!($l = $this->getList($f3, $args['list']))) { + return; + } + + if(($f3->get('VERB') === 'POST') && ($f3->get('POST.confirm') === 'OK')) { + $l->deleteList(); + + $f3->set('success', 'List deleted'); + $f3->set('content', 'blank.html'); + } else { + $this->populateListData($f3, $l); + $f3->set('content', 'deleteList.html'); + } + } + + public function newList($f3) { + if($this->needLogin($f3)) { + return; + } + + if($f3->get('VERB') !== 'POST') { + return; + } + + if($f3->get('POST.listname') === '') { + $f3->set('error', 'List name must not be empty'); + $f3->set('content', 'blank.html'); + return; + } + + $l = new \Todo\Model\TodoList($f3->get('DB')); + $l->user = $this->user->id; + $l->name = $f3->get('POST.listname'); + $l->save(); + + $f3->set('success', 'List created!'); + + $f3->set('content', 'list.html'); + $this->populateListData($f3, $l); + } + + public function showList($f3, $args) { + if($this->needLogin($f3)) { + return; + } + + $f3->set('content', 'list.html'); + + if(!($l = $this->getList($f3, $args['list']))) { + return; + } + + if($f3->get('VERB') === 'POST') { + $ok = true; + switch($f3->get('POST.action')) { + case 'setname': + if($f3->get('POST.name') === '') { + $f3->set('error', 'List name must not be empty'); + $ok = false; + } else { + $l->name = $f3->get('POST.name'); + $l->save(); + } + break; + case 'additem': + if($f3->get('POST.itemtext') === '') { + $f3->set('error', 'Can not add empty list item'); + $ok = false; + } else { + $l->addItem($f3->get('POST.itemtext')); + } + break; + case 'delitem': + $l->delItem($f3->get('POST.id')); + break; + case 'setchecked': + $it = $l->getItem($f3->get('POST.id')); + if($it === NULL) { + $f3->set('error', 'Invalid item ID'); + $ok = false; + } else { + $it->checked = ($f3->get('POST.checked') === 'y'); + $it->save(); + } + break; + case 'moveup': + $l->moveItem($f3->get('POST.id'), -1); + break; + case 'movedown': + $l->moveItem($f3->get('POST.id'), 1); + break; + case 'movex': + $l->moveItem($f3->get('POST.id'), $f3->get('POST.move')); + break; + case 'delchecked': + $l->deleteChecked(); + break; + default: + $ok = false; + $f3->set('error', 'Unknown list action.'); + } + + if($ok) { + $f3->set('success', 'List updated'); + } + } + + $this->populateListData($f3, $l); + } +}
\ No newline at end of file diff --git a/autoload/Todo/Model/TodoList.php b/autoload/Todo/Model/TodoList.php new file mode 100644 index 0000000..4470c34 --- /dev/null +++ b/autoload/Todo/Model/TodoList.php @@ -0,0 +1,100 @@ +<?php + +namespace Todo\Model; + +class TodoList extends \DB\SQL\Mapper { + public function __construct($db) { + parent::__construct($db, 'lists'); + } + + public function byID($id) { + $this->load(array('id=?', $id)); + return !$this->dry(); + } + + public function itemsToArray() { + $items = array(); + $it = new \DB\SQL\Mapper($this->db, 'items'); + for($it->load(array('list=?', $this->id), array('order' => 'ord ASC')); !$it->dry(); $it->next()) { + $items[] = array( + 'id' => $it->id, + 'ord' => $it->ord, + 'text' => $it->text, + 'date' => $it->date, + 'checked' => $it->checked, + ); + } + + return $items; + } + + public function countItems() { + $it = new \DB\SQL\Mapper($this->db, 'items'); + return $it->count(array('list=?', $this->id)); + } + + public function getItem($id) { + $it = new \DB\SQL\Mapper($this->db, 'items'); + $it->load(array('list=? AND id=?', $this->id, $id)); + return $it->dry() ? NULL : $it; + } + + public function moveItem($id, $movement) { + $it = new \DB\SQL\Mapper($this->db, 'items'); + $it->load(array('list=? AND id=?', $this->id, $id)); + if($it->dry()) { + return; + } + $ordOld = $it->ord; + $it->reset(); + + $ordNew = min(max(0, $ordOld + $movement), $this->countItems() - 1); + if($ordNew === $ordOld) { + return; + } + + $moveSgn = ($movement > 0) ? 1 : -1; + + if($moveSgn === 1) { + $it->load(array('list=? AND ord>? AND ord<=?', $this->id, $ordOld, $ordNew)); + } else { + $it->load(array('list=? AND ord>=? AND ord<?', $this->id, $ordNew, $ordOld)); + } + for(;!$it->dry(); $it->next()) { + $it->ord -= $moveSgn; + $it->save(); + } + + $it->reset(); + $it->load(array('list=? AND id=?', $this->id, $id)); + $it->ord = $ordNew; + $it->save(); + } + + public function addItem($text) { + $n = $this->countItems(); + + $now = new \DateTime('now'); + + $it = new \DB\SQL\Mapper($this->db, 'items'); + $it->text = $text; + $it->ord = $n; + $it->date = $now->format('Y-m-d H:i:s'); + $it->checked = false; + $it->list = $this->id; + $it->save(); + } + + public function delItem($id) { + $this->db->exec('DELETE FROM `items` WHERE `list` = :l AND `id` = :i', array(':l' => $this->id, ':i' => $id)); + } + + public function deleteList() { + $this->db->exec('DELETE FROM `items` WHERE `list` = :l', array(':l' => $this->id)); + $this->erase(); + } + + public function deleteChecked() { + $this->db->exec('DELETE FROM `items` WHERE `list` = :l AND `checked` = :c', array(':l' => $this->id, ':c' => true)); + } +}
\ No newline at end of file diff --git a/autoload/Todo/Model/User.php b/autoload/Todo/Model/User.php new file mode 100644 index 0000000..53c8f67 --- /dev/null +++ b/autoload/Todo/Model/User.php @@ -0,0 +1,66 @@ +<?php + +namespace Todo\Model; + +class User extends \DB\SQL\Mapper { + public function __construct(\DB\SQL $db) { + parent::__construct($db, 'users'); + } + + public function makeCode() { + $alphabet = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; + $len = strlen($alphabet); + $this->code = ''; + for($i = 0; $i < 16; $i++) { + $this->code .= substr($alphabet, mt_rand(0, $len-1), 1); + } + } + + public function register($name, $email, $password) { + $this->load(array('name=? OR email=?', $name, $email)); + if(!$this->dry()) { + $this->reset(); + return false; + } + + $this->name = $name; + $this->email = $email; + $this->pwhash = \Bcrypt::instance()->hash($password); + $this->active = false; + $this->makeCode(); + $this->save(); + return true; + } + + public function byName($name) { + $this->load(array('name=?', $name)); + return !$this->dry(); + } + + public function byID($id) { + $this->load(array('id=?', $id)); + return !$this->dry(); + } + + public function byEmail($email) { + $this->load(array('email=?', $email)); + return !$this->dry(); + } + + public function verifyPass($password) { + return \Bcrypt::instance()->verify($password, $this->pwhash); + } + + public function deleteUser() { + $this->db->exec('DELETE FROM `items` WHERE `list` IN (SELECT `id` FROM `lists` WHERE `user` = :u)', array(':u' => $this->id)); + $this->db->exec('DELETE FROM `lists` WHERE `user` = :u', array(':u' => $this->id)); + $this->erase(); + } + + # Returns a TodoList object that can be iterated with ->next(). + public function lists() { + $l = new \Todo\Model\TodoList($this->db); + $l->load(array('user=?', $this->id), array('order' => 'name ASC')); + return $l; + } +}
\ No newline at end of file diff --git a/autoload/Todo/SessionHelper.php b/autoload/Todo/SessionHelper.php new file mode 100644 index 0000000..b4811ab --- /dev/null +++ b/autoload/Todo/SessionHelper.php @@ -0,0 +1,49 @@ +<?php + +namespace Todo; + +abstract class SessionHelper { + protected $user = NULL; + + private function populateLists($f3) { + $lists = array(); + for($l = $this->user->lists(); !$l->dry(); $l->next()) { + $lists[] = array( + 'id' => $l->id, + 'name' => $l->name, + 'active' => ($l->id === $f3->get('listid')), + ); + } + $f3->set('lists', $lists); + } + + public function beforeRoute($f3) { + $id = $f3->get('SESSION.userID'); + if(is_numeric($id) && ($id > 0)) { + $this->user = new \Todo\Model\User($f3->get('DB')); + if((!$this->user->byID($id)) || (!$this->user->active)) { + $this->user = NULL; + } + } + $f3->set('listid', -1); + } + + public function afterRoute($f3) { + $f3->set('SESSION.userID', ($this->user === NULL) ? -1 : $this->user->id); + if($this->user !== NULL) { + $f3->set('user', $this->user->name); + $this->populateLists($f3); + } else { + $f3->set('user', ''); + } + } + + protected function needLogin($f3) { + if($this->user === NULL) { + $f3->set('error', 'You must be logged in to do that'); + $f3->set('content', 'blank.html'); + return true; + } + return false; + } +}
\ No newline at end of file diff --git a/autoload/Todo/UserManager.php b/autoload/Todo/UserManager.php new file mode 100644 index 0000000..306eb02 --- /dev/null +++ b/autoload/Todo/UserManager.php @@ -0,0 +1,203 @@ +<?php + +namespace Todo; + +class UserManager extends \Todo\SessionHelper { + public function login($f3, $args) { + if($this->user !== NULL) { + $f3->set('info', 'You are already logged in'); + $f3->set('content', 'blank.html'); + return; + } + $f3->set('content', 'login.html'); + + if($f3->get('VERB') !== "POST") { + return; + } + + $this->user = new \Todo\Model\User($f3->get('DB')); + if($this->user->byName($f3->get('POST.name')) && $this->user->verifyPass($f3->get('POST.password')) && $this->user->active) { + $f3->set('success', 'Login Successful!'); + $f3->set('content', 'noListSelected.html'); + } else { + $this->user = NULL; + $f3->set('error', 'Username or password is wrong or account is not active.'); + } + } + + public function logout($f3, $args) { + $this->user = NULL; + $f3->set('success', 'Logout Successful!'); + $f3->set('content', 'login.html'); + } + + public function register($f3, $args) { + if($this->user !== NULL) { + $f3->set('error', 'You already have an account!'); + $f3->set('content', 'blank.html'); + return; + } + $f3->set('content', 'register.html'); + + if($f3->get('VERB') !== 'POST') { + return; + } + + if(($f3->get('POST.password') === '') || ($f3->get('POST.name') === '') || ($f3->get('POST.email') === '')) { + $f3->set('error', 'All form inputs must be filled out'); + return; + } + + if($f3->get('POST.password') !== $f3->get('POST.repeatPassword')) { + $f3->set('error', 'Passwords do not match'); + return; + } + + $u = new \Todo\Model\User($f3->get('DB')); + if($u->register($f3->get('POST.name'), $f3->get('POST.email'), $f3->get('POST.password'))) { + $f3->set('username', $u->name); + $f3->set('activationurl', 'http://'.$f3->get('HOST').$f3->get('BASE').'/activate/'.$u->id.'/'.$u->code); + mail($u->email, 'Activation code for ' . $f3->get('appname'), \Template::instance()->render('mails/activationcode', 'text/plain'), 'From:'.$f3->get('mail_from')); + + $f3->set('success', 'New account created. Check your mails for the activation link.'); + $f3->set('content', 'blank.html'); + } else { + $f3->set('error', 'Name or E-Mail already in use.'); + } + } + + public function delete($f3, $args) { + if($this->needLogin($f3)) { + return; + } + + $f3->set('content', 'deleteAccount.html'); + + if(($f3->get('VERB') == 'POST') && ($f3->get('POST.confirm') == 'OK')) { + $this->user->deleteUser(); + $this->user = NULL; + $f3->set('success', 'Account deleted!'); + $f3->set('content', 'blank.html'); + } + } + + public function activate($f3, $args) { + $f3->set('content', 'blank.html'); + + $u = new \Todo\Model\User($f3->get('DB')); + if(!$u->byID($args['user'])) { + $f3->set('error', 'Unknown user.'); + return; + } + + if($u->active) { + $f3->set('info', 'Account already activated'); + return; + } + + if($u->code !== $args['code']) { + $f3->set('error', 'Wrong activation code!'); + return; + } + + $u->active = true; + $u->makeCode(); # set a new random code to prevent double usage + $u->save(); + + $f3->set('success', 'Account activated!'); + } + + public function initResetpw($f3, $args) { + $f3->set('content', 'pwresetRequest.html'); + + if($f3->get('VERB') !== 'POST') { + return; + } + + $u = new \Todo\Model\User($f3->get('DB')); + if(!$u->byEmail($f3->get('POST.email'))) { + $f3->set('error', 'No account with this address registered.'); + return; + } + + $u->makeCode(); + $u->save(); + + $f3->set('username', $u->name); + $f3->set('reseturl', 'http://'.$f3->get('HOST').$f3->get('BASE').'/pwreset/'.$u->id.'/'.$u->code); + mail($u->email, 'Password reset for ' . $f3->get('appname'), \Template::instance()->render('mails/pwreset', 'text/plain'), 'From:'.$f3->get('mail_from')); + + $f3->set('success', 'Password reset link was sent to your E-Mail address.'); + $f3->set('content', 'blank.html'); + } + + public function resetpw($f3, $args) { + $u = new \Todo\Model\User($f3->get('DB')); + if((!$u->byID($args['user'])) || ($u->code !== $args['code'])) { + $f3->set('error', 'Invalid password reset link.'); + $f3->set('content', 'blank.html'); + return; + } + + $f3->set('content', 'pwreset.html'); + if($f3->get('VERB') !== 'POST') { + return; + } + + if($f3->get('POST.password') !== $f3->get('POST.repeatPassword')) { + $f3->set('error', 'Passwords do not match'); + return; + } + + $u->pwhash = \Bcrypt::instance()->hash($f3->get('POST.password')); + $u->makeCode(); + $u->save(); + + $f3->set('success', 'Password changed'); + $f3->set('content', 'blank.html'); + } + + public function settings($f3, $args) { + if($this->needLogin($f3)) { + return; + } + + $f3->set('content', 'settings.html'); + $f3->set('email', $this->user->email); + + if($f3->get('VERB') !== 'POST') { + return; + } + + $ok = array(); + $error = array(); + + if(($this->user->email !== $f3->get('POST.email')) && ($f3->get('POST.email') !== '')) { + $this->user->email = $f3->get('POST.email'); + $f3->set('email', $this->user->email); + $ok[] = 'E-Mail address changed.'; + } + + if($f3->get('POST.password') !== '') { + if($f3->get('POST.password') === $f3->get('POST.repeatPassword')) { + $this->user->pwhash = \Bcrypt::instance()->hash($f3->get('POST.password')); + $ok[] = 'Password changed.'; + } else { + $error[] = 'Passwords do not match.'; + } + } + + if(!empty($ok)) { + $this->user->save(); + } + + $text = array_reduce(array_merge($ok, $error), function($a, $b) { return $a . ' ' . $b; }); + if((!empty($ok)) && (!empty($error))) { + $f3->set('info', $text); + } else if(!empty($ok)) { + $f3->set('success', $text); + } else if(!empty($error)) { + $f3->set('error', $text); + } + } +} |