* @package todo * @link https://www.zentao.net */ class todoModel extends model { /** * 创建待办。 * Create todo data. * * @param object $todo * @access public * @return int|false */ public function create(object $todo): int|false { $todoID = $this->todoTao->insert($todo); if(dao::isError()) return false; return $todoID; } /** * 批量创建待办。 * Batch create todo. * * @param object $todos * @access public * @return array|false */ public function batchCreate(array $todos): array|false { $this->loadModel('action'); $this->loadModel('score'); $todoIdList = array(); foreach($todos as $todo) { $todoID = $this->todoTao->insert($todo); if(!$todoID) return false; $todoIdList[] = $todoID; $this->score->create('todo', 'create', $todoID); $this->action->create('todo', $todoID, 'opened'); } return $todoIdList; } /** * 更新待办数据。 * update a todo. * * @param int $todoID * @param object $todo * @access public * @return array|false */ public function update(int $todoID, object $todo): array|false { $oldTodo = $this->dao->findByID($todoID)->from(TABLE_TODO)->fetch(); if(!$this->todoTao->updateRow($todoID, $todo)) return false; if(!empty($todo->uid)) $this->loadModel('file')->updateObjectID($todo->uid, $todoID, 'todo'); if(!empty($oldTodo->cycle)) $this->createByCycle(array($todoID => $todo)); if($this->config->edition != 'open' && $todo->type == 'feedback' && $todo->objectID) $this->loadModel('feedback')->updateStatus('todo', $todo->objectID, $todo->status, '', $todoID); return common::createChanges($oldTodo, (array)$todo); } /** * 更新批量编辑待办数据。 * Update batch edit todo data. * * @param array $todos * @param array $todoIdList * @access public * @return array|int */ public function batchUpdate(array $todos, array $todoIdList): array|int { if(empty($todos)) return $todos; $this->loadModel('action'); if($this->config->edition != 'open') $this->loadModel('feedback'); /* Initialize todos from the post data. */ $oldTodos = $this->getTodosByIdList($todoIdList); $allChanges = array(); foreach($todos as $todoID => $todo) { $oldTodo = $oldTodos[$todoID]; if(in_array($todo->type, $this->config->todo->moduleList)) { $table = $this->config->objectTables[$todo->type]; $nameField = $this->config->action->objectNameFields[$todo->type]; $todo->name = $this->dao->select($nameField)->from($table)->where('id')->eq($todo->objectID)->fetch($nameField); } if(!empty($oldTodo->private) && !isset($todo->private)) $todo->assignedTo = $oldTodo->assignedTo; $this->todoTao->updateRow($todoID, $todo); if($oldTodo->status != 'done' and $todo->status == 'done') $this->loadModel('action')->create('todo', $todoID, 'finished', '', 'done'); if(!dao::isError()) { if($this->config->edition != 'open' && $todo->type == 'feedback' && $todo->objectID && !isset($feedbacks[$todo->objectID])) { $feedbacks[$todo->objectID] = $todo->objectID; $this->feedback->updateStatus('todo', $todo->objectID, $todo->status, '', $todoID); } /* Create changes of one object. */ $allChanges[$todoID] = common::createChanges($oldTodo, $todo); } else { dao::$errors[] = 'todo#' . $todoID . dao::getError(true); } } return $allChanges; } /** * 开始一个待办事项。 * Start a todo. * * @param int $todoID * @access public * @return bool */ public function start(int $todoID): bool { $this->dao->update(TABLE_TODO)->set('status')->eq('doing')->where('id')->eq($todoID)->exec(); $this->loadModel('action')->create('todo', $todoID, 'started'); return !dao::isError(); } /** * 完成一个待办。 * Finish one todo. * * @param int $todoID * @access public * @return bool */ public function finish(int $todoID): bool { $todo = new stdclass(); $todo->id = $todoID; $todo->status = 'done'; $todo->finishedBy = $this->app->user->account; $todo->finishedDate = helper::now(); $this->todoTao->updateRow($todoID, $todo); if(dao::isError()) return false; $this->loadModel('action')->create('todo', $todoID, 'finished', '', 'done'); if($this->config->edition != 'open') { $todo = $this->todoTao->fetch($todoID); $feedbackID = $todo->objectID ? $todo->objectID : '' ; if($feedbackID) $this->loadModel('feedback')->updateStatus('todo', $feedbackID, 'done', '', $todoID); } return true; } /** * 批量完成待办。 * Batch finish todos. * * @param int[] $todoIdList * @access public * @return bool */ public function batchFinish(array $todoIdList): bool { foreach($todoIdList as $todoID) { $isFinished = $this->finish($todoID); if(!$isFinished) return $isFinished; } return true; } /** * 获取待办事项详情数据。 * Get info of a todo. * * @param int $todoID * @param bool $setImgSize true|false * @access public * @return object|false */ public function getByID(int $todoID, $setImgSize = false): object|false { $todo = $this->dao->findByID($todoID)->from(TABLE_TODO)->fetch(); if(!$todo) return false; $todo = $this->loadModel('file')->replaceImgURL((object)$todo, 'desc'); if($setImgSize) $todo->desc = $this->file->setImgSize($todo->desc); $todo->date = str_replace('-', '', $todo->date); return $this->todoTao->setTodoNameByType($todo); } /** * 获取用户的待办事项列表。 * Get todo list of a user. * * @param string $type * @param string $account * @param string|array $status all|today|thisweek|lastweek|before, or a date. * @param int $limit * @param object $pager * @param string $orderBy * @access public * @return array */ public function getList(string $type = 'today', string $account = '', string|array $status = 'all', int $limit = 0, ?object $pager = null, string $orderBy="date,status,begin"): array { $this->app->loadClass('date', true); $type = strtolower($type); $dateRange = $this->dateRange($type); if(empty($account)) $account = $this->app->user->account; $todos = $this->todoTao->getListBy($type, $account, $status, (string)$dateRange['begin'], (string)$dateRange['end'], $limit, $orderBy, $pager); /* Set session. */ $sql = explode('WHERE', $this->dao->get()); $sql = explode('ORDER', $sql[1]); $this->session->set('todoReportCondition', $sql[0]); foreach($todos as $todo) { $todo = $this->todoTao->setTodoNameByType($todo); $todo->begin = date::formatTime($todo->begin); $todo->end = date::formatTime($todo->end); if($todo->private and $this->app->user->account != $todo->account) $todo->name = $this->lang->todo->thisIsPrivate; } return $todos; } /** * 根据类型获取时间范围。 * Date range. * * @param string $type * @access protected * @return array */ protected function dateRange(string $type): array { if($type == 'future') return array('begin' => '2030-01-01', 'end' => '2030-01-01'); if(is_numeric($type)) { $date = date('Y-m-d', strtotime($type)); return array('begin' => $date . ' 00:00:00', 'end' => $date . ' 23:59:59'); } if(strpos(',before,today,yesterday,thisweek,lastweek,thismonth,lastmonth,thisseason,thisyear,', ",$type,") === false) return array('begin' => '', 'end' => ''); if($type == 'before') return array('begin' => '', 'end' => date::yesterday()); $funcName = ($type == 'today' || $type == 'yesterday') ? $type : 'get' . ucfirst($type); $dateRange = date::$funcName(); if($type == 'today' || $type == 'yesterday') return array('begin' => $dateRange, 'end' => $dateRange); return $dateRange; } /** * 通过包含一个或多个待办ID的列表获取待办列表,这个列表是以todoID为key、以todo对象为value的数组。 * 如果待办ID列表为空则返回所有待办。 * Get a array with todos which todoID as key, todo object as value by todo id list. * Return all todos if the todo id list is empty. * * @param array|string $todoIdList * @access public * @return array */ public function getByList(array|string $todoIdList = ''): array { return $this->dao->select('*')->from(TABLE_TODO) ->beginIF($todoIdList)->where('id')->in($todoIdList)->fi() ->fetchAll('id', false); } /** * 判断当前动作是否可以点击。 * Judge an action is clickable or not. * * @param object $todo * @param string $action * @access public * @return bool */ public static function isClickable(object $todo, string $action): bool { global $app; $action = strtolower($action); if($todo->private && $app->user->account != $todo->account) return false; if($action == 'start') return $todo->status == 'wait' && !$todo->cycle; if($action == 'activate') return $todo->status == 'done' or $todo->status == 'closed'; if($action == 'close') return $todo->status == 'done'; if($action == 'assignto') return !$todo->private && $todo->status != 'done' && $todo->status != 'closed'; if($action == 'finish') return $todo->status != 'done' && $todo->status != 'closed' && !$todo->cycle; return true; } /** * 根据周期待办创建待办。 * Create todo by cycle. * * @param array $todoList * @access public * @return void */ public function createByCycle(array $todoList): void { if(empty($todoList)) return; $this->loadModel('action'); $today = helper::today(); $now = helper::now(); $cycleList = $this->todoTao->getCycleList($todoList); $validUsers = $this->dao->select('account')->from(TABLE_USER)->where('deleted')->eq(0)->fetchPairs('account', 'account'); foreach($todoList as $todoID => $todo) { if(!isset($validUsers[$todo->account])) continue; $todo->config = json_decode($todo->config); $begin = $todo->config->begin; $end = zget($todo->config, 'end', ''); $beforeDays = (int)$todo->config->beforeDays; if(!empty($beforeDays) && $beforeDays > 0) $begin = date('Y-m-d', strtotime($begin) - $beforeDays * 24 * 3600); if($today < $begin || (!empty($end) && $today > $end)) continue; if(empty($todo->id)) $todo->id = $todoID; $newTodo = $this->todoTao->buildCycleTodo($todo); if(isset($todo->assignedTo) && $todo->assignedTo) $newTodo->assignedDate = $now; $start = strtotime($begin); $finish = strtotime("$today +{$beforeDays} days"); foreach(range($start, $finish, 86400) as $today) { $today = date('Y-m-d', $today); $lastCycle = zget($cycleList, $todoID, ''); $date = $this->todoTao->getCycleTodoDate($todo, $lastCycle, $today); if($date === false) continue; if(!$date) continue; if($date < $todo->config->begin) continue; if($date < date('Y-m-d')) continue; if($date > date('Y-m-d', $finish)) continue; if(!empty($end) && $date > $end) continue; if($lastCycle and ($date == $lastCycle->date)) continue; $newTodo->date = $date; $todoID = $this->todoTao->insert($newTodo); if($todoID) $this->action->create('todo', $todoID, 'opened', '', '', $newTodo->account); } } } /** * 激活待办事项。 * Activate a todo. * * @param int $todoID * @access public * @return bool */ public function activate(int $todoID): bool { $this->dao->update(TABLE_TODO)->set('status')->eq('wait')->where('id')->eq($todoID)->exec(); $todo = $this->fetchByID($todoID); $this->loadModel('action')->create('todo', $todoID, 'activated', '', 'wait'); if($this->config->edition != 'open' && $todo->type == 'feedback' && $todo->objectID) $this->loadModel('feedback')->updateStatus('todo', $todo->objectID, $todo->status, '', $todoID); return !dao::isError(); } /** * 关闭待办。如果是企业版或旗舰版则更新关联的反馈。 * Close todo. Update related feedback if edition is biz or max. * * @param int $todoID * @access public * @return bool */ public function close(int $todoID): bool { $isClosed = $this->todoTao->closeTodo($todoID); if(!$isClosed) return false; $this->loadModel('action')->create('todo', $todoID, 'closed', '', 'closed'); /* Update status of feedback if the closed todo type is feedback. */ if($this->config->edition != 'open') { $feedbackID = $this->dao->select('objectID')->from(TABLE_TODO)->where('id')->eq($todoID)->andWhere('type')->eq('feedback')->fetch('objectID'); if($feedbackID) $this->loadModel('feedback')->updateStatus('todo', $feedbackID, 'closed', '', $todoID); } return true; } /** * 指派待办。 * Assign todo. * * @param object $todo * @access public * @return bool */ public function assignTo(object $todo): bool { $todoID = (int)$todo->id; $result = $this->todoTao->updateRow($todoID, $todo); if(!$result) return false; $this->loadModel('action')->create('todo', $todoID, 'assigned', '', $todo->assignedTo); return !dao::isError(); } /** * 获取待办事项的数量。 * Get todo count. * * @param string $account * @access public * @return int */ public function getCount(string $account = ''): int { if(empty($account)) $account = $this->app->user->account; $count = $this->todoTao->getCountByAccount($account, $this->config->vision); if(dao::isError()) return 0; return $count; } /** * 根据类型获取各个模块的列表。 * Gets the project ID of the to-do object. * * @param array $todoList * @access public * @return array */ public function getTodoProjects(array $todoList): array { $projectIDList = array(); $projectModules = $this->config->todo->project; foreach($todoList as $type => $todos) { $todoIdList = array_keys($todos); if(empty($todoIdList)) { $projectIDList[$type] = array(); continue; } $todoIdList = array_unique($todoIdList); if(isset($projectModules[$type])) $projectIDList[$type] = $this->todoTao->getProjectList($projectModules[$type], $todoIdList); if(dao::isError()) return array(); } return $projectIDList; } /** * 修改待办事项的时间。 * Edit the date of todo. * * @param array $todoIdList * @param string $date * @access public * @return bool */ public function editDate(array $todoIdList, string $date): bool { return $this->todoTao->updateDate($todoIdList, $date); } /** * 获取导出的待办数据。 * Get data for export todo. * * @param string $orderBy * @param string $queryCondition * @param string $checkedItem * @access public * @return array */ public function getByExportList(string $orderBy, string $queryCondition, string $checkedItem): array { return $this->dao->select('*')->from(TABLE_TODO) ->where($queryCondition) ->beginIF($checkedItem)->andWhere('id')->in($checkedItem)->fi() ->orderBy($orderBy)->fetchAll('id', false); } /** * 根据待办ID获取多条待办。 * Get todo data by id list. * * @param array $todoIdList * @access public * @return array */ public function getTodosByIdList(array $todoIdList): array { return $this->dao->select('*')->from(TABLE_TODO)->where('id')->in(array_values($todoIdList))->fetchAll('id', false); } /** * 获取所有有效的周期待办列表。 * Get valid cycle todo list. * * @access public * @return array */ public function getValidCycleList(): array { return $this->dao->select('*')->from(TABLE_TODO)->where('cycle')->eq(1)->andWhere('deleted')->eq(0)->fetchAll('id', false); } /** * 根据待办类型获取优先级。 * Get pri by todo type. * * @param string $todoType * @param int $todoObjectID * @access public * @return int */ public function getPriByTodoType(string $todoType, int $todoObjectID): int { if(!isset($this->config->objectTables[$todoType])) return $this->config->todo->defaultPri; $pri = $this->dao->select('pri')->from($this->config->objectTables[$todoType])->where('id')->eq($todoObjectID)->fetch('pri'); return $pri ?: $this->config->todo->defaultPri; } }