427 lines
10 KiB
PHP
427 lines
10 KiB
PHP
<?php
|
|
/**
|
|
* TaskFreak! Time Tracker
|
|
*
|
|
* @package taskfreak_tt
|
|
* @author Stan Ozier <taskfreak@gmail.com>
|
|
* @version 0.5
|
|
* @copyright GNU General Public License (GPL) version 3
|
|
*/
|
|
|
|
/**
|
|
* Task
|
|
*
|
|
* Class representing a task
|
|
* @since 0.1
|
|
*/
|
|
class TaskModel extends Model {
|
|
|
|
public function __construct() {
|
|
parent::__construct('task');
|
|
$this->addProperties(array(
|
|
'id' => 'UID',
|
|
'title' => 'STR',
|
|
'note' => 'BBS',
|
|
'priority' => 'NUM,'.json_encode($GLOBALS['config']['task']['priority']),
|
|
'begin' => 'DTE',
|
|
'deadline' => 'DTE',
|
|
'status' => 'NUM,{"options":["todo","done","valid"],"default":0}',
|
|
'archived' => 'BOL',
|
|
'member_id' => 'NUM'
|
|
));
|
|
}
|
|
|
|
/**
|
|
* check submitted data before saving task
|
|
*/
|
|
public function checkUid($usrId) {
|
|
if ($this->isEmpty('begin')) {
|
|
$this->set('begin','9999-00-00');
|
|
}
|
|
if ($this->isEmpty('deadline')) {
|
|
$this->set('deadline','9999-00-00');
|
|
}
|
|
if ($this->isEmpty('member_id') && APP_SETUP_USER_MODEL) {
|
|
$this->set('member_id', $usrId);
|
|
}
|
|
return parent::check('title');
|
|
}
|
|
|
|
/**
|
|
* parse a single line string for task params
|
|
* @return a task object
|
|
*/
|
|
public static function parse($str, &$def, &$dte) {
|
|
if (!preg_match('/^(\* |\*{2,3})?([+|-][0-9]{0,2}|[0-9]{2}\/[0-9]{2})?( ?[0-9]+\))?(.+)?$/',$str, $arr)) {
|
|
return false;
|
|
}
|
|
ArrayHelper::arrayTrim($arr);
|
|
$obj = new TaskModel();
|
|
$tst = (empty($arr[1]))?substr($arr[2],0,1):'*';
|
|
switch ($tst) {
|
|
case '*': // multiple
|
|
if ($arr[1] == '**') {
|
|
// reset default date (**)
|
|
$dte = '';
|
|
} else if ($arr[1] == '***') {
|
|
// reset default title (***)
|
|
$def = '';
|
|
} else {
|
|
if ($arr[2]) {
|
|
// remember date
|
|
if ($tst == '+') {
|
|
if ($n = substr($arr[2], 1)) {
|
|
// number of days later
|
|
$obj->set('deadline',$arr[2].' days');
|
|
} else {
|
|
// no number, means today
|
|
$obj->set('deadline', APP_SQL_TODAY);
|
|
}
|
|
} else {
|
|
$dte = $arr[2];
|
|
}
|
|
}
|
|
if ($arr[4]) {
|
|
// remember title
|
|
$def = $arr[4];
|
|
}
|
|
}
|
|
return false;
|
|
break;
|
|
case '-': // no deadline
|
|
$obj->set('deadline','9999-00-00');
|
|
break;
|
|
case '+': // specify deadline
|
|
if ($n = substr($arr[2], 1)) {
|
|
// number of days later
|
|
$obj->set('deadline',$arr[2].' days');
|
|
} else {
|
|
// no number, means today
|
|
$obj->set('deadline', date_format(new DateTime('now', $GLOBALS['config']['datetime']['timezone_user']), 'Y-m-d'));
|
|
}
|
|
break;
|
|
default:
|
|
if ($arr[2]) {
|
|
// specific date set
|
|
$obj->set('deadline', $arr[2]);
|
|
$dte = $obj->get('deadline');
|
|
} else if (!empty($dte)) {
|
|
// use default date (from batch)
|
|
$obj->set('deadline', $dte);
|
|
} else if ($GLOBALS['config']['task']['date']) {
|
|
// use default date (config)
|
|
$obj->set('deadline', date_format(new DateTime($GLOBALS['config']['task']['date'], $GLOBALS['config']['datetime']['timezone_user']), 'Y-m-d'));
|
|
// $dte = $obj->get('deadline');
|
|
} else {
|
|
// no date by default (config)
|
|
$dte = '';
|
|
}
|
|
}
|
|
$prio = $GLOBALS['config']['task']['priority']['default']; // default priority
|
|
if ($arr[3]) {
|
|
// priority ?
|
|
$prio = intval(substr($arr[3],0,-1));
|
|
}
|
|
$obj->set('priority',$prio);
|
|
$title = $arr[4];
|
|
if ($def) {
|
|
$title = $def.' : '.$title;
|
|
}
|
|
$obj->set('title',$title);
|
|
return $obj;
|
|
}
|
|
|
|
/*
|
|
* set archive status for multiple tasks
|
|
*/
|
|
public static function updateManyAtOnce($action, $arr) {
|
|
if (!count($arr)) {
|
|
return false;
|
|
}
|
|
$sta = '';
|
|
switch ($action) {
|
|
case 'report':
|
|
$sta = 'deadline = DATE_ADD(deadline, INTERVAL 1 DAY)';
|
|
break;
|
|
case 'open':
|
|
$sta = 'status=0';
|
|
break;
|
|
case 'close':
|
|
$sta = 'status=1';
|
|
break;
|
|
case 'valid':
|
|
$sta = 'status=2';
|
|
break;
|
|
case 'archive':
|
|
$sta = 'archived=1';
|
|
break;
|
|
case 'unarchive':
|
|
$sta = 'archived=0';
|
|
break;
|
|
}
|
|
if ($sta) {
|
|
$filter = 'id IN ('.implode(',',$arr).')';
|
|
DbConnector::query('UPDATE task SET '.$sta.' WHERE '.$filter);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
}
|
|
|
|
class TaskSummary extends TaskModel {
|
|
|
|
public function __construct() {
|
|
parent::__construct();
|
|
$this->addProperties(array(
|
|
'start' => 'DTM',
|
|
'stop' => 'DTM',
|
|
'spent' => 'NUM',
|
|
'timers' => 'NUM'
|
|
));
|
|
}
|
|
|
|
public function htmlPriority() {
|
|
$arr = $this->getPropertyOptions('priority');
|
|
$st = $this->get('priority');
|
|
return $st.') '.TR::html('priority',$arr['options'][$st]);
|
|
}
|
|
|
|
public function htmlStatus() {
|
|
$arr = $this->getPropertyOptions('status');
|
|
$str = $this->get('status');
|
|
$str = TR::html('task',$arr['options'][$str]);
|
|
if ($this->get('archived')) {
|
|
$str .= ' ('.TR::html('task','archived').')';
|
|
}
|
|
return $str;
|
|
}
|
|
|
|
public function htmlTimes() {
|
|
return $this->html('start',APP_DATETIME_SHT).' > '.$this->html('stop','%H:%M');
|
|
}
|
|
|
|
public function htmlBegin() {
|
|
if ($this->isEmpty('start')) {
|
|
if ($this->isEmpty('begin')) {
|
|
return '-';
|
|
} else {
|
|
return $this->html('begin',APP_DATE);
|
|
}
|
|
} else {
|
|
return $this->html('start',APP_DATETIME);
|
|
}
|
|
}
|
|
|
|
public function getEnd() {
|
|
if ($this->isEmpty('stop')) {
|
|
if ($this->isEmpty('deadline')) {
|
|
return '';
|
|
} else {
|
|
return $this->get('deadline');
|
|
}
|
|
} else {
|
|
return $this->get('stop');
|
|
}
|
|
}
|
|
|
|
public function htmlEnd($expanded=false, $default='-') {
|
|
if ($this->isEmpty('stop')) {
|
|
return $default;
|
|
} else {
|
|
return $this->html('stop',APP_DATETIME);
|
|
}
|
|
}
|
|
|
|
public function htmlDeadline() {
|
|
if ($this->isEmpty('deadline')) {
|
|
return '-';
|
|
} else {
|
|
switch ($this->_diff) {
|
|
case 9999:
|
|
return '-';
|
|
case -1:
|
|
return TR::html('date','yesterday');
|
|
case 0:
|
|
return TR::html('date','today');
|
|
case 1:
|
|
return TR::html('date','tomorrow');
|
|
default:
|
|
return $this->html('deadline',APP_DATE);
|
|
}
|
|
}
|
|
}
|
|
|
|
public function getTimeSpent() {
|
|
return $this->htmlTime($this->get('spent'), $this->isEmpty('start'));
|
|
}
|
|
|
|
public function getRealSpentSecs() {
|
|
return APP_NOW - strtotime($this->get('start'));
|
|
}
|
|
|
|
public function getRealSpent() {
|
|
$spent = $this->getRealSpentSecs();
|
|
$h = floor($spent / 3600);
|
|
$m = floor($spent / 60) - ($h*60);
|
|
$s = $spent - ($h*3600 + $m*60);
|
|
return str_pad($h, 2, '0',STR_PAD_LEFT)
|
|
.':'.str_pad($m, 2, '0',STR_PAD_LEFT)
|
|
.':'.str_pad($s, 2, '0',STR_PAD_LEFT);
|
|
}
|
|
|
|
public function chkDeadline() {
|
|
if ($this->isEmpty('deadline')) {
|
|
$this->_diff = 9999;
|
|
} else {
|
|
$dead = date_timestamp_get(new DateTime($this->get('deadline'), $GLOBALS['config']['datetime']['timezone_user']));
|
|
// -TODO- optimize ! maybe using DateTime diff ?
|
|
$usernow = date_timestamp_get(new DateTime('now'));
|
|
$this->_diff = ceil(($dead - $usernow) / 3600 / 24);
|
|
}
|
|
}
|
|
|
|
public function isOpened($user_id) {
|
|
// -TODO- if no "validate" option, do not allow on closed tasks
|
|
return ($this->get('status') < 2 && (!$this->get('archived')) && ($this->get('member_id') == $user_id));
|
|
}
|
|
|
|
public function curCss($default='') {
|
|
$arr = array();
|
|
if ($this->_diff < 0) {
|
|
$arr[] = 'overdue';
|
|
} else if ($this->_diff == 0) {
|
|
$arr[] = 'today';
|
|
} else {
|
|
$arr[] = 'future';
|
|
}
|
|
if ($default) {
|
|
$arr[] = $default;
|
|
}
|
|
if (count($arr)) {
|
|
return ' class="'.implode(' ',$arr).'"';
|
|
} else {
|
|
return '';
|
|
}
|
|
}
|
|
|
|
public function htmlDate() {
|
|
$str = $this->html('end_date',APP_DATE_FRM,'no_date');
|
|
if ($css = $this->curCss()) {
|
|
return '<span'.$css.'>'.$str.'</span>';
|
|
} else {
|
|
return $str;
|
|
}
|
|
}
|
|
|
|
public static function htmlTime($spent, $stopped=true) {
|
|
if (empty($spent)) {
|
|
if ($stopped) {
|
|
return '--:--';
|
|
} else {
|
|
return TR::html('task','running');
|
|
}
|
|
}
|
|
$h = floor($spent / 60);
|
|
$m = $spent - ($h*60);
|
|
return str_pad($h, 2, '0',STR_PAD_LEFT).':'.str_pad($m, 2, '0',STR_PAD_LEFT);
|
|
}
|
|
|
|
/**
|
|
* export data in array for the mobile version (ajax requests)
|
|
*/
|
|
public function exportData($method='html') {
|
|
// prepare general info
|
|
$arrInfo = array();
|
|
$arr = $this->getFields();
|
|
foreach ($arr as $key => $type) {
|
|
$arrInfo[$key] = $this->$method($key);
|
|
}
|
|
|
|
// prepare timer history and totals
|
|
$total = 0;
|
|
$arrSpent = array();
|
|
if ($this->get('spent')) {
|
|
do {
|
|
$total += $this->get('spent');
|
|
// start and stop times
|
|
$times = $this->htmlTimes();
|
|
$spent = $this->getTimeSpent();
|
|
$arrSpent[$times] = $spent;
|
|
} while ($this->next());
|
|
}
|
|
|
|
$arrInfo['total'] = $this->htmlTime($total);
|
|
|
|
return array('info' => $arrInfo, 'spent' => $arrSpent);
|
|
// return $arrInfo;
|
|
}
|
|
|
|
/**
|
|
* update current task status
|
|
*/
|
|
public function updateStatus($status) {
|
|
$this->connectDb();
|
|
$this->set('status',$status);
|
|
$this->fields('status');
|
|
return parent::update();
|
|
}
|
|
|
|
/**
|
|
* override load function
|
|
*/
|
|
public function load($filter='') {
|
|
$this->select('id, title, begin, deadline, start, stop, status, archived, '
|
|
.'CEIL(spent/60) as spent');
|
|
$this->from('task');
|
|
$this->leftJoin('timer','task.id=timer.task_id');
|
|
return parent::load($filter, false);
|
|
}
|
|
|
|
/**
|
|
* load current running timer
|
|
*/
|
|
public static function loadCurrent($id=0) {
|
|
$obj = new TaskSummary();
|
|
$obj->connectDb();
|
|
if ($id) {
|
|
$obj->setUid($id);
|
|
if ($obj->load()) {
|
|
return $obj;
|
|
}
|
|
} else {
|
|
$ftr = "stop='0000-00-00 00:00:00'";
|
|
if (!empty($_SESSION['appUserId'])) {
|
|
$ftr .= " AND member_id='".$_SESSION['appUserId']."'";
|
|
}
|
|
if ($obj->load($ftr)) {
|
|
return $obj;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
public function loadCompactList() {
|
|
/*
|
|
SELECT task.*, MIN(start) as start, MAX(stop) as stop, SUM(CEIL(spent/60)) as spent
|
|
FROM `task`
|
|
LEFT JOIN timer ON task.id = timer.task_id
|
|
WHERE status < 2
|
|
GROUP BY id
|
|
*/
|
|
$this->select('task.*, MIN(start) as start, MAX(stop) as stop, '
|
|
.'SUM(CEIL(spent/60)) as spent, COUNT(timer.task_id) AS timers');
|
|
$this->from('task');
|
|
$this->leftJoin('timer','task.id=timer.task_id');
|
|
$this->groupBy('id');
|
|
return parent::loadList(false);
|
|
}
|
|
|
|
public function loadExpandList() {
|
|
$this->select('task.*, start, stop, CEIL(spent/60) as spent');
|
|
$this->from('task');
|
|
$this->leftJoin('timer','task.id=timer.task_id');
|
|
return parent::loadList(false);
|
|
}
|
|
|
|
}
|