summaryrefslogtreecommitdiff
path: root/autoload/Todo
diff options
context:
space:
mode:
Diffstat (limited to 'autoload/Todo')
-rw-r--r--autoload/Todo/Lists.php141
-rw-r--r--autoload/Todo/Model/TodoList.php100
-rw-r--r--autoload/Todo/Model/User.php66
-rw-r--r--autoload/Todo/SessionHelper.php49
-rw-r--r--autoload/Todo/UserManager.php203
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);
+ }
+ }
+}