<?php
/**
 * The model file of todo module of ZDOO.
 *
 * @copyright   Copyright 2009-2018 青岛易软天创网络科技有限公司(QingDao Nature Easy Soft Network Technology Co,LTD, www.cnezsoft.com)
 * @license     ZPL (http://zpl.pub/page/zplv12.html)
 * @author      Chunsheng Wang <chunsheng@cnezsoft.com>
 * @package     todo
 * @version     $Id: model.php 5035 2013-07-06 05:21:58Z wyd621@gmail.com $
 * @link        http://www.zdoo.com
 */
?>
<?php
class todoModel extends model
{
    /**
     * Create a todo.
     *
     * @access public
     * @return void
     */
    public function create()
    {
        $objectType = $this->post->type;
        $hasObject  = in_array($objectType, $this->config->todo->moduleList);

        $todo = fixer::input('post')
            ->add('account', $this->app->user->account)
            ->setDefault('assignedBy', $this->app->user->account)
            ->setDefault('assignedDate', helper::now())
            ->setDefault('type', 'custom')
            ->setDefault('pri', '3')
            ->setDefault('status', 'wait')
            ->cleanInt('pri, begin, end, private')
            ->setIF($hasObject && $objectType, 'idvalue', $this->post->idvalue)
            ->setIF($this->post->date == false, 'date', '0000-00-00')
            ->setIF($this->post->begin == false, 'begin', '2400')
            ->setIF($this->post->begin == false or $this->post->end == false, 'end', '2400')
            ->setIF($this->post->status == 'done', 'finishedBy', $this->app->user->account)
            ->setIF($this->post->status == 'done', 'finishedDate', helper::now())
            ->stripTags($this->config->todo->editor->create['id'], $this->config->allowedTags)
            ->remove(implode(',', $this->config->todo->moduleList) . ',uid')
            ->get();

        if($todo->end < $todo->begin)
        {
            dao::$errors[] = sprintf($this->lang->error->gt, $this->lang->todo->end, $this->lang->todo->begin);
            return false;
        }

        if(empty($todo->cycle)) unset($todo->config);
        if(!empty($todo->cycle))
        {
            $todo->date = date('Y-m-d');

            $todo->config['begin'] = $todo->date;
            if($todo->config['type'] == 'day')
            {
                unset($todo->config['week']);
                unset($todo->config['month']);
                if(!isset($todo->config['day']) && $todo->config['dayType'] == 'every')
                {
                    dao::$errors['everyInput'] = sprintf($this->lang->error->notempty, $this->lang->todo->cycleDaysLabel);
                    return false;
                }

                if(isset($todo->config['day']) && $todo->config['day'] <= 0)
                {
                    dao::$errors['everyInput'] = sprintf($this->lang->error->gt, $this->lang->todo->cycleDaysLabel, 0);
                    return false;
                }

                if(isset($todo->config['day']) && !validater::checkInt($todo->config['day']))
                {
                    dao::$errors['everyInput'] = sprintf($this->lang->error->int[0], $this->lang->todo->cycleDaysLabel);
                    return false;
                }

                if($todo->config['dayType'] == 'specified') $todo->config['specifiedDate'] = 1;
            }
            if($todo->config['type'] == 'week')
            {
                unset($todo->config['day']);
                unset($todo->config['month']);
                $todo->config['week'] = join(',', $todo->config['week']);
            }
            if($todo->config['type'] == 'month')
            {
                unset($todo->config['day']);
                unset($todo->config['week']);
                $todo->config['month'] = join(',', $todo->config['month']);
            }

            $beforeDays = $todo->config['beforeDays'];

            if($beforeDays and !validater::checkInt($beforeDays))
            {
                dao::$errors['beforeDays'] = sprintf($this->lang->error->int[0], $this->lang->todo->beforeDaysLabel);
                return false;
            }
            if($beforeDays && ($beforeDays < 0 || $beforeDays > 30))
            {
                dao::$errors['beforeDays'] = sprintf($this->lang->error->int[2], $this->lang->todo->beforeDaysLabel, 0 , 30);
                return false;
            }
            $todo->config['beforeDays'] = (int)$beforeDays;
            if(isset($todo->config['dayType'])) unset($todo->config['dayType']);
            $todo->config = json_encode($todo->config);
            $todo->type   = 'cycle';
        }

        if($todo->type != 'custom' && isset($todo->idvalue) && in_array($todo->type, $this->lang->todo->typeList))
        {
            $type = $todo->type;
            $app  = 'crm';
            if($type == 'task') $app = 'sys';
            $object = $this->loadModel($type, $app)->getByID($this->post->$type);
            if(isset($object->name))  $todo->name = $object->name;
            if(isset($object->title)) $todo->name = $object->title;
        }

        $todo = $this->loadModel('file')->processImgURL($todo, $this->config->todo->editor->create['id'], $this->post->uid);
        $this->dao->insert(TABLE_TODO)->data($todo)
            ->autoCheck()
            ->checkIF(!$hasObject, $this->config->todo->require->create, 'notempty')
            ->checkIF($hasObject && $todo->idvalue == 0, 'idvalue', 'notempty')
            ->exec();

        if(dao::isError()) return false;

        $todoID = $this->dao->lastInsertID();
        $this->loadModel('action')->create('todo', $todoID, 'created');
        $this->file->updateObjectID($this->post->uid, $todoID, 'todo');
        if(!empty($todo->cycle)) $this->createByCycle(array($todoID => $todo));

        return $todoID;
    }

    /**
     * Create batch todo
     *
     * @access public
     * @return void
     */
    public function batchCreate()
    {
        $this->loadModel('action');

        $todos = fixer::input('post')->cleanInt('date')->get();
        for($i = 0; $i < $this->config->todo->batchCreate; $i++)
        {
            if(empty($todos->names[$i]) && empty($todos->idvalues[$i + 1])) continue;

            $todo             = new stdclass();
            $todo->account    = $this->app->user->account;
            $todo->date       = $this->post->date ? $this->post->date : '0000-00-00';
            $todo->type       = $todos->types[$i];
            $todo->pri        = $todos->pris[$i];
            $todo->assignedTo = $todos->assignedTo[$i];
            $todo->name       = empty($todos->names[$i]) ? '' : $todos->names[$i];
            $todo->desc       = nl2br($todos->descs[$i]);
            $todo->begin      = empty($todos->begins[$i]) ? 2400 : $todos->begins[$i];
            $todo->end        = empty($todos->ends[$i]) ? 2400 : $todos->ends[$i];
            $todo->status     = "wait";
            $todo->private    = 0;
            $todo->idvalue    = empty($todos->idvalues[$i + 1]) ? 0 : $todos->idvalues[$i + 1];

            if(!$todo->name && !$todo->idvalue) continue;

            if(!empty($todo->assignedTo))
            {
                $todo->assignedBy   = $this->app->user->account;
                $todo->assignedDate = helper::now();
            }

            $this->dao->insert(TABLE_TODO)->data($todo)->autoCheck()->exec();
            if(dao::isError()) return false;

            $todoID = $this->dao->lastInsertID();

            $this->action->create('todo', $todoID, 'created');
        }
        return !dao::isError();
    }

    /**
     * update a todo.
     *
     * @param  int    $todoID
     * @access public
     * @return void
     */
    public function update($todoID)
    {
        $oldTodo = $this->dao->findById((int)$todoID)->from(TABLE_TODO)->fetch();

        $objectType = $this->post->type;
        $hasObject  = in_array($objectType, $this->config->todo->moduleList);
        $todo = fixer::input('post')
            ->cleanInt('pri, begin, end, private')
            ->setIF($hasObject && $objectType,  'idvalue', $this->post->idvalue)
            ->setIF($this->post->date  == false, 'date', '0000-00-00')
            ->setIF($this->post->begin == false, 'begin', '2400')
            ->setIF($this->post->end   == false, 'end', '2400')
            ->setDefault('private', 0)
            ->setDefault('type', $oldTodo->type)
            ->stripTags($this->config->todo->editor->edit['id'], $this->config->allowedTags)
            ->remove(implode(',', $this->config->todo->moduleList) . ',uid')
            ->get();

        if($hasObject) $todo->idvalue = $oldTodo->idvalue;

        if($todo->end < $todo->begin)
        {
            dao::$errors[] = sprintf($this->lang->error->gt, $this->lang->todo->end, $this->lang->todo->begin);
            return false;
        }

        if(!empty($oldTodo->cycle))
        {
            $todo->date = date('Y-m-d');

            $todo->config['begin'] = $todo->date;
            if($todo->config['type'] == 'day')
            {
                unset($todo->config['week']);
                unset($todo->config['month']);
                if(!isset($todo->config['day']) && $todo->config['dayType'] == 'every')
                {
                    dao::$errors['everyInput'] = sprintf($this->lang->error->notempty, $this->lang->todo->cycleDaysLabel);
                    return false;
                }

                if(isset($todo->config['day']) && $todo->config['day'] <= 0)
                {
                    dao::$errors['everyInput'] = sprintf($this->lang->error->gt, $this->lang->todo->cycleDaysLabel, 0);
                    return false;
                }

                if(isset($todo->config['day']) && !validater::checkInt($todo->config['day']))
                {
                    dao::$errors['everyInput'] = sprintf($this->lang->error->int[0], $this->lang->todo->cycleDaysLabel);
                    return false;
                }

                if($todo->config['dayType'] == 'specified') $todo->config['specifiedDate'] = 1;
            }
            if($todo->config['type'] == 'week')
            {
                unset($todo->config['day']);
                unset($todo->config['month']);
                $todo->config['week'] = join(',', $todo->config['week']);
            }
            if($todo->config['type'] == 'month')
            {
                unset($todo->config['day']);
                unset($todo->config['week']);
                $todo->config['month'] = join(',', $todo->config['month']);
            }

            $beforeDays = $todo->config['beforeDays'];

            if($beforeDays and !validater::checkInt($beforeDays))
            {
                dao::$errors['beforeDays'] = sprintf($this->lang->error->int[0], $this->lang->todo->beforeDaysLabel);
                return false;
            }
            if($beforeDays && ($beforeDays < 0 || $beforeDays > 30))
            {
                dao::$errors['beforeDays'] = sprintf($this->lang->error->int[2], $this->lang->todo->beforeDaysLabel, 0 , 30);
                return false;
            }
            $todo->config['beforeDays'] = (int)$beforeDays;
            if(isset($todo->config['dayType'])) unset($todo->config['dayType']);
            $todo->config = json_encode($todo->config);
        }

        $todo = $this->loadModel('file')->processImgURL($todo, $this->config->todo->editor->edit['id'], $this->post->uid);
        $this->dao->update(TABLE_TODO)->data($todo)
            ->autoCheck()
            ->checkIF($todo->type == 'custom', $this->config->todo->require->edit, 'notempty')
            ->checkIF($hasObject && $todo->idvalue == 0, 'idvalue', 'notempty')
            ->where('id')->eq($todoID)
            ->exec();

        if(dao::isError()) return false;

        $this->file->updateObjectID($this->post->uid, $todoID, 'todo');

        return commonModel::createChanges($oldTodo, $todo);
    }

    /**
     * Batch update todos.
     *
     * @access public
     * @return bool
     */
    public function batchUpdate()
    {
        $this->loadModel('action');
        foreach($this->post->names as $id => $name)
        {
            if(empty($name) && empty($this->post->idvalues[$id])) continue;

            $oldTodo = $this->getById($id);

            $todo = new stdclass();
            $todo->name       = $name;
            $todo->type       = $this->post->types[$id];
            $todo->pri        = $this->post->pris[$id];
            $todo->assignedTo = $this->post->assignedTo[$id];
            $todo->desc       = $this->post->descs[$id];
            $todo->date       = isset($this->post->dates[$id])    ? $this->post->dates[$id]    : '0000-00-00';
            $todo->begin      = isset($this->post->begins[$id])   ? $this->post->begins[$id]   : 2400;
            $todo->end        = isset($this->post->ends[$id])     ? $this->post->ends[$id]     : 2400;
            $todo->idvalue    = isset($this->post->idvalues[$id]) ? $this->post->idvalues[$id] : $oldTodo->idvalue;

            $this->dao->update(TABLE_TODO)->data($todo)
                ->autoCheck()
                ->checkIF($todo->type == 'custom', $this->config->todo->require->edit, 'notempty')
                ->where('id')->eq($id)
                ->exec();

            if(dao::isError()) return false;

            $todo->date = str_replace('-', '', $todo->date);

            $changes = commonModel::createChanges($oldTodo, $todo);

            if($changes)
            {
                $actionID = $this->action->create('todo', $id, 'edited');
                $this->action->logHistory($actionID, $changes);
            }
        }

        return !dao::isError();
    }

    /**
     * Change the status of a todo.
     *
     * @param  string $todoID
     * @access public
     * @return bool
     */
    public function close($todoID)
    {
        $this->dao->update(TABLE_TODO)
            ->set('status')->eq('closed')
            ->set('closedBy')->eq($this->app->user->account)
            ->set('closedDate')->eq(helper::now())
            ->where('id')->eq((int)$todoID)
            ->exec();
        $this->loadModel('action')->create('todo', $todoID, 'closed', '', 'closed');
        return !dao::isError();
    }

    /**
     * Change the status of a todo.
     *
     * @param  string $todoID
     * @access public
     * @return bool
     */
    public function activate($todoID)
    {
        $this->dao->update(TABLE_TODO)
            ->set('status')->eq('wait')
            ->where('id')->eq((int)$todoID)
            ->exec();
        $this->loadModel('action')->create('todo', $todoID, 'activated', '', 'closed');
        return true;
    }

    /**
     * Change the status of a todo.
     *
     * @param  string $todoID
     * @param  string $status
     * @access public
     * @return bool
     */
    public function finish($todoID)
    {
        $todo = $this->getById($todoID);

        $data = new stdclass();
        $data->status       = 'done';
        $data->finishedBy   = $this->app->user->account;
        $data->finishedDate = helper::now();
        if($todo->date == '00000000') $data->date = helper::today();

        $this->dao->update(TABLE_TODO)->data($data)->where('id')->eq((int)$todoID)->exec();
        if(dao::isError()) return false;

        $this->loadModel('action')->create('todo', $todoID, 'finished', '', 'done');

        return true;
    }

    /**
     * Assign to.
     *
     * @param  int    $todoID
     * @access public
     * @return array|bool
     */
    public function assignTo($todoID)
    {
        $oldTodo = $this->getById($todoID);
        $todo = fixer::input('post')
            ->remove('account')
            ->add('assignedBy', $this->app->user->account)
            ->add('assignedDate', helper::now())
            ->setIF($this->post->date == false,  'date', '0000-00-00')
            ->setIF($this->post->begin == false, 'begin', '2400')
            ->setIF(($this->post->end == false || $this->post->begin == false), 'end', '2400')
            ->get();

        $this->dao->update(TABLE_TODO)->data($todo)
            ->autoCheck()
            ->where('id')->eq($todoID)
            ->exec();

        if(!dao::isError()) return commonModel::createChanges($oldTodo, $todo);
        return false;
    }

    /**
     * Get info of a todo.
     *
     * @param  int    $todoID
     * @access public
     * @return object|bool
     */
    public function getById($todoID)
    {
        $todo = $this->dao->findById((int)$todoID)->from(TABLE_TODO)->fetch();
        if(!$todo) return false;
        if($todo->type == 'task') $todo->name = $this->dao->findById($todo->idvalue)->from(TABLE_TASK)->fetch('name');
        if($todo->type == 'customer') $todo->name = $this->dao->findById($todo->idvalue)->from(TABLE_CUSTOMER)->fetch('name');
        if($todo->type == 'order')
        {
            $order = $this->dao->select('c.name, o.createdDate')
                ->from(TABLE_ORDER)->alias('o')
                ->leftJoin(TABLE_CUSTOMER)->alias('c')->on('o.customer=c.id')
                ->where('o.id')->eq($todo->idvalue)
                ->fetch();
            $todo->name = $order->name . '|' . date('Y-m-d', strtotime($order->createdDate));
        }

        $zentaoEntryList = $this->dao->select('*')->from(TABLE_ENTRY)->where('zentao')->eq(1)->fetchAll();
        foreach($zentaoEntryList as $zentaoEntry)
        {
            if(strpos($todo->type, $zentaoEntry->code . '_') !== false)
            {
                if(empty($zentaoTodoList)) $zentaoTodoList = $this->loadModel('sso')->getZentaoTodoList($zentaoEntry->code, $this->app->user->account);
                $type = substr($todo->type, strpos($todo->type, '_') + 1);
                $todo->name = isset($zentaoTodoList[$type][$todo->idvalue]) ? $zentaoTodoList[$type][$todo->idvalue] : '';
            }
        }
        $todo->date = str_replace('-', '', $todo->date);
        $todo = $this->loadModel('file')->replaceImgURL($todo, 'desc');
        return $todo;
    }

    /**
     * Get todos by id list.
     *
     * @param  int    $todoIDList
     * @access public
     * @return array
     */
    public function getByIdList($todoIDList)
    {
        $todos     = $this->dao->select('*')->from(TABLE_TODO)->where('id')->in($todoIDList)->fetchAll('id');
        $tasks     = $this->dao->select('id, name')->from(TABLE_TASK)->fetchPairs();
        $customers = $this->loadModel('customer')->getPairs();
        $orders    = $this->dao->select('o.id, o.createdDate, c.name')
            ->from(TABLE_ORDER)->alias('o')
            ->leftJoin(TABLE_CUSTOMER)->alias('c')->on('o.customer=c.id')
            ->fetchAll('id');
        $zentaoEntryList = $this->dao->select('*')->from(TABLE_ENTRY)->where('zentao')->eq(1)->fetchAll();

        $this->loadModel('sso');
        foreach($todos as $todo)
        {
            if($todo->type == 'task') $todo->name = zget($tasks, $todo->idvalue);
            if($todo->type == 'customer') $todo->name = zget($customers, $todo->idvalue);
            if($todo->type == 'order')
            {
                $order = zget($orders, $todo->idvalue, '');
                if($order) $todo->name = $order->name . '|' . date('Y-m-d', strtotime($order->createdDate));
            }

            foreach($zentaoEntryList as $zentaoEntry)
            {
                if(strpos($todo->type, $zentaoEntry->code . '_') !== false)
                {
                    if(empty($zentaoTodoList)) $zentaoTodoList = $this->sso->getZentaoTodoList($zentaoEntry->code, $this->app->user->account);
                    $type = substr($todo->type, strpos($todo->type, '_') + 1);
                    $todo->name = $zentaoTodoList[$type][$todo->idvalue];
                }
            }
        }

        return $todos;
    }

    /**
     * Get todo list of a user.
     *
     * @param  string $mode     all|self|assignedToOther|assignedToMe
     * @param  string $account
     * @param  date   $date     all|today|thisweek|lastweek|before, or a date.
     * @param  string $status
     * @param  string $orderBy
     * @param  object $pager
     * @access public
     * @return void
     */
    public function getList($mode = 'self', $account = '', $date = 'all',  $status = 'all', $orderBy="date, status, begin", $pager = null)
    {
        if($account == '') $account = $this->app->user->account;
        $mode = strtolower($mode);
        $this->app->loadClass('date');
        $todos = array();
        if(!is_array($date)) $date = strtolower($date);

        if(is_array($date))
        {
            $begin = strtolower($date['begin']);
            $end   = strtolower($date['end']);
        }
        elseif($date == 'today')
        {
            $begin = date::today();
            $end   = $begin;
        }
        elseif($date == 'yesterday')
        {
            $begin = date::yesterday();
            $end   = $begin;
        }
        elseif($date == 'thisweek')
        {
            extract(date::getThisWeek());
        }
        elseif($date == 'lastweek')
        {
            extract(date::getLastWeek());
        }
        elseif($date == 'thismonth')
        {
            extract(date::getThisMonth());
        }
        elseif($date == 'lastmonth')
        {
            extract(date::getLastMonth());
        }
        elseif($date == 'thisseason')
        {
            extract(date::getThisSeason());
        }
        elseif($date == 'thisyear')
        {
            extract(date::getThisYear());
        }
        elseif($date == 'future')
        {
            $begin = '0000-00-00';
            $end   = '0000-00-00';
        }
        elseif($date == 'all')
        {
            $begin = '0000-00-00';
            $end   = '2109-01-01';
        }
        elseif($date == 'before')
        {
            $begin = '1970-01-01';
            $end   = date::yesterday();
        }
        else
        {
            $begin = $end = $date;
        }

        $stmt = $this->dao->select('*')->from(TABLE_TODO)
            ->where('1=1')
            ->beginIF($mode == 'self')->andWhere('account', true)->eq($account)->orWhere('assignedTo')->eq($account)->orWhere('finishedBy')->eq($account)->markRight(1)->fi()
            ->beginIF($mode == 'assignedtoother')->andWhere('account')->eq($account)->andWhere('assignedTo')->ne($account)->andWhere('assignedTo')->ne('')->fi()
            ->beginIF($mode == 'assignedtome')->andWhere('assignedTo')->eq($account)->fi()
            ->beginIF($mode == 'cycle')->andWhere('cycle')->eq('1')->fi()
            ->beginIF($mode == 'cycle')->andWhere('account')->eq($account)->fi()
            ->beginIF($mode != 'cycle')->andWhere('cycle')->eq('0')->fi()
            ->andWhere('`date`')->ge($begin)
            ->andWhere('`date`')->le($end)
            ->beginIF(strpos('all,undone,unclosed,custom', $status) === false)->andWhere('status')->in($status)->fi()
            ->beginIF($status == 'undone')->andWhere('status')->notin('done,closed')->fi()
            ->beginIF($status == 'custom')->andWhere('status')->notin('done,closed')->fi()
            ->beginIF($status == 'unclosed')->andWhere('status')->ne('closed')->fi()
            ->orderBy($orderBy)
            ->page($pager)
            ->query();

        /* Set session. */
        $sql = explode('WHERE', $this->dao->get());
        $sql = explode('ORDER', $sql[1]);
        $this->session->set('todoReportCondition', $sql[0]);

        while($todo = $stmt->fetch())
        {
            if($todo->type == 'task' && $todo->idvalue) $todo->name = $this->dao->findById($todo->idvalue)->from(TABLE_TASK)->fetch('name');
            if($todo->type == 'customer' && $todo->idvalue) $todo->name = $this->dao->findById($todo->idvalue)->from(TABLE_CUSTOMER)->fetch('name');
            if($todo->type == 'order' && $todo->idvalue)
            {
                $order = $this->dao->select('c.name, o.createdDate')
                    ->from(TABLE_ORDER)->alias('o')
                    ->leftJoin(TABLE_CUSTOMER)->alias('c')->on('o.customer=c.id')
                    ->where('o.id')->eq($todo->idvalue)
                    ->fetch();
                $todo->name = $order->name . '|' . date('Y-m-d', strtotime($order->createdDate));
            }

            $todo->begin = date::formatTime($todo->begin);
            $todo->end   = date::formatTime($todo->end);

            /* If is private, change the title to private. */
            if($todo->private and $this->app->user->account != $todo->account) $todo->name = $this->lang->todo->thisIsPrivate;
            $todos[$todo->id] = $todo;
        }

        $zentaoEntryList = $this->dao->select('*')->from(TABLE_ENTRY)->where('zentao')->eq(1)->fetchAll();
        foreach($zentaoEntryList as $zentaoEntry)
        {
            $this->lang->todo->typeList["{$zentaoEntry->code}_task"] = $zentaoEntry->name . $this->lang->todo->task;
            $this->lang->todo->typeList["{$zentaoEntry->code}_bug"]  = $zentaoEntry->name . $this->lang->todo->bug;

            static $zentaoTodoList = array();
            $this->loadModel('sso');
            foreach($todos as $todo)
            {
                if(strpos($todo->type, $zentaoEntry->code . '_') !== false)
                {
                    if(empty($zentaoTodoList)) $zentaoTodoList = $this->sso->getZentaoTodoList($zentaoEntry->code, $this->app->user->account);
                    $type = substr($todo->type, strpos($todo->type, '_') + 1);
                    $todo->name = !empty($todo->name) ? $todo->name : (isset($zentaoTodoList[$type][$todo->idvalue]) ? $zentaoTodoList[$type][$todo->idvalue] : $this->lang->todo->typeList["{$todo->type}"] . $todo->idvalue);
                }
            }
        }

        return $todos;
    }

    /**
     * getCalendarData
     *
     * @param  string $date
     * @param  int    $showContactNum
     * @access public
     * @return object
     */
    public function getCalendarData($date, $showContactNum = 0)
    {
        $begin     = date('Y-m-01', strtotime($date));
        $weekday   = date('w', strtotime($begin));
        $beginDays = $weekday == 0 ? 6 : $weekday - 1;
        $begin     = date('Y-m-d', strtotime("$begin -$beginDays days"));
        /* Make sure the calendar will display 6 rows.*/
        $end       = date('Y-m-d', strtotime("$begin +42 days"));

        $dateLimit = array();
        $dateLimit['begin'] = $begin;
        $dateLimit['end']   = $end;

        $account       = $this->app->user->account;
        $calendars     = $this->lang->todo->typeList;
        $todos         = $this->getList('self', $account, $dateLimit);
        $calendarList  = array();
        $todoList      = array();
        $newDatingList = array();

        if($showContactNum)
        {
            $datingList   = $this->dao->select('date, COUNT(DISTINCT(customer)) AS number')
                ->from(TABLE_DATING)
                ->where('account')->eq($account)
                ->andWhere('`date`')->ge($begin)
                ->andWhere('`date`')->le($end)
                ->andWhere('status')->eq('wait')
                ->groupBy('date')
                ->fetchPairs();

            foreach($datingList as $date => $number)
            {
                $data           = new stdclass();
                $data->title    = sprintf($this->lang->todo->nextContact, $number);
                $data->allDay   = true;
                $data->start    = strtotime($date) * 1000;
                $data->end      = $data->start;
                $data->calendar = 'contact';

                $data->data = new stdclass();
                $data->data->account    = $account;
                $data->data->status     = 'contact';
                $data->data->assignedTo = $account;

                $newDatingList[] = $data;
            }
        }

        foreach($calendars as $key => $calendar)
        {
            $data = new stdclass();
            $data->name  = $key;
            $data->title = $calendar;
            $data->desc  = $calendar;
            $data->color = zget($this->config->todo->calendarColor, $key, $this->config->todo->calendarColor['custom']);
            $calendarList[] = $data;
        }

        foreach($todos as $todo)
        {
            $data = new stdclass();
            $data->id       = $todo->id;
            $data->title    = $todo->name;
            $data->desc     = $todo->name;
            $data->allDay   = ($todo->begin == '' and $todo->end == '');
            $data->start    = strtotime($todo->date . ' ' . $todo->begin) * 1000;
            $data->end      = strtotime($todo->date . ' ' . $todo->end) * 1000;
            $data->calendar = $todo->type;

            $data->data = new stdclass();
            $data->data->account    = $todo->account;
            $data->data->status     = $todo->status;
            $data->data->assignedTo = $todo->assignedTo;
            $data->data->assignedBy = $todo->assignedBy;
            $data->data->pri        = $todo->pri;

            $data->click = new stdclass();
            $data->click->title  = $this->lang->todo->view;
            $data->click->width  = '1060px';
            $data->click->remote = helper::createLink('todo', 'view', "id=$todo->id", 'html');
            $todoList[] = $data;
        }

        $data = new stdclass();
        $data->calendars = $calendarList;
        $data->events    = array_merge($todoList, $newDatingList);
        return $data;
    }

    /**
     * Judge an action is clickable or not.
     *
     * @param  object    $todo
     * @param  string    $action
     * @access public
     * @return bool
     */
    public static function isClickable($todo, $action)
    {
        $action = strtolower($action);

        if($action == 'finish') return strpos('done,closed', $todo->status) === false;
        if($action == 'close') return $todo->status == 'done';
        if($action == 'activate') return strpos('done,closed', $todo->status) !== false;

        return true;
    }

    /**
     * Check privilage of todo.
     *
     * @param  object $task
     * @param  string $action
     * @access public
     * @return bool
     */
    public function checkPriv($todo, $action)
    {
        $account = $this->app->user->account;
        $action  = strtolower($action);
        if(strpos('view,edit,assignto,close,activate,finish,delete', $action) !== false)
        {
            if($todo->private == 0 or $todo->account == $account or $todo->assignedTo == $account) return true;
        }
        return false;
    }

    /**
     * build board list.
     *
     * @param  array  $items
     * @access public
     * @return string
     */
    public function buildBoardList($items, $type, $index = 0)
    {
        $div = '';
        $index = $index ? $index + 1 : 1;

        $boardListContent = '';
        foreach($items as $id => $name)
        {
            $boardListContent .= "<li class='todo-item' title='$name' data-todo-name='$name' data-id='$id' data-type='$type' data-action='create' data-index='$index' data-toggle='droppable' data-target='.day'>\r\n";
            $boardListContent .= "$name\r\n";
            $boardListContent .= "</li>\r\n";
            $index += 1;
        }
        return $boardListContent;
    }

    /**
     * Insert the record of the notification that the user didn't create any todo today to the queue.
     *
     * @access public
     * @return void
     */
    public function notCreateTodo()
    {
        $noTodoUsers = array();
        $sessionKey  = 'notCreateTodoUsers' . date('Ymd');
        if($this->session->$sessionKey) $noTodoUsers = $this->session->$sessionKey;

        $allUsers = $this->dao->select('id, account')->from(TABLE_USER)
            ->where('clientStatus')->eq('online')
            ->beginIF(!empty($noTodoUsers))->andWhere('account')->notin($noTodoUsers)->fi()
            ->fetchPairs();
        if(empty($allUsers)) return true;

        $todoUsers = $this->dao->select('account')->from(TABLE_TODO)->where('account')->in($allUsers)->andWhere('date')->eq(date('Y-m-d'))->fetchPairs();
        $target    = array_diff($allUsers, $todoUsers);
        if(empty($target)) return true;

        $this->session->set($sessionKey, array_merge($noTodoUsers, $target));

        $lang = isset($this->config->xuanxuan->xxbLang) ? $this->config->xuanxuan->xxbLang : 'zh-cn';
        $this->app->setClientLang($lang);
        $this->app->loadLang('todo');

        $target  = array_keys($target);
        $subject = $this->lang->todo->emptyTodo; 
        $content = '';
        $link    = commonModel::getSysURL() . helper::createLink('sys.todo', 'calendar');
        $actions = array('label' => $this->lang->todo->create, 'url' => $link, 'type' => 'normal');

        $sender = new stdclass();
        $sender->id       = 'zdoo_todo';
        $sender->realname = $this->lang->zdoo . $this->lang->zdooTitle;
        $sender->avatar   = commonModel::getSysURL() . $this->app->getWebRoot() . '/favicon.ico';
        $this->loadModel('im')->messageCreateNotify($target, $subject, '', $content, 'text', $link, $actions, $sender);
    }

    /**
     * CreateByCycle
     *
     * @param  array  $todoList
     * @access public
     * @return void
     */
    public function createByCycle($todoList)
    {
        $this->loadModel('action');
        $this->loadModel('attend', 'oa');
        $today = helper::today();
        $now   = helper::now();
        $lastCycleList = $this->dao->select('*')->from(TABLE_TODO)->where('type')->eq('cycle')->andWhere('idvalue')->in(array_keys($todoList))->orderBy('date_asc')->fetchAll('idvalue');
        $activedUsers  = $this->loadModel('user')->getPairs('nodeleted,noforbidden');
        foreach($todoList as $todoID => $todo)
        {
            if(!isset($activedUsers[$todo->account])) continue;
            if(!empty($todo->assignedTo) && !isset($activedUsers[$todo->assignedTo])) continue;

            $todo->config = json_decode($todo->config);
            $begin      = $todo->config->begin;
            $end        = $todo->config->end;
            $beforeDays = (int)$todo->config->beforeDays;
            if(!empty($beforeDays) && $beforeDays > 0) $begin = date('Y-m-d', strtotime("$begin -{$beforeDays} days"));
            if($today < $begin or (!empty($end) && $today > $end)) continue;

            $newTodo = new stdclass();
            $newTodo->account    = $todo->account;
            $newTodo->begin      = $todo->begin;
            $newTodo->end        = $todo->end;
            $newTodo->type       = 'cycle';
            $newTodo->idvalue    = $todoID;
            $newTodo->pri        = $todo->pri;
            $newTodo->name       = $todo->name;
            $newTodo->desc       = $todo->desc;
            $newTodo->status     = 'wait';
            $newTodo->private    = isset($todo->private) ? $todo->private : '';
            $newTodo->assignedTo = isset($todo->assignedTo) ? $todo->assignedTo : '';
            $newTodo->assignedBy = isset($todo->assignedBy) ? $todo->assignedBy : '';
            if(isset($todo->assignedTo) and $todo->assignedTo) $newTodo->assignedDate = $now;

            $start    = strtotime($begin);
            $finish   = strtotime("$today +{$beforeDays} days");
            $restdays = 0;
            foreach(range($start, $finish, 86400) as $todoDate)
            {
                $todoDate  = date('Y-m-d', $todoDate);
                $lastCycle = zget($lastCycleList, $todoID, '');
                $date      = '';

                if($todo->config->type == 'day')
                {
                    if(isset($todo->config->day))
                    {
                        $day = (int)$todo->config->day;
                        if($day <= 0) continue;

                        /* If no data, judge the interval from the begin time. */
                        if(empty($lastCycle))
                        {
                            $isRestDay = false;
                            if(isset($todo->config->skipNoWork) && $this->attend->isRestDay($todoDate)) continue;
                            $date = $todoDate;
                        }

                        /* If have data, judge the interval from the last cycle time. */
                        if(!empty($lastCycle->date))
                        {
                            if($todoDate < $lastCycle->date) continue;
                            $isRestDay = false;
                            $interval  = (new DateTime($todoDate))->diff(new DateTime($lastCycle->date))->days;

                            if(isset($todo->config->skipNoWork) && $this->attend->isRestDay($todoDate))
                            {
                                $restdays++;
                                continue;
                            }

                            if($interval < $day + $restdays) continue;
                            $restdays = 0;
                            $date     = $todoDate;
                        }
                    }
                    if(isset($todo->config->specifiedDate))
                    {
                        $date          = $todoDate;
                        $specifiedDate = $todo->config->specify->month + 1 . '-' . $todo->config->specify->day;

                        /* If not set cycle every year and have data, continue. */
                        if(!empty($lastCycle) and !isset($todo->config->cycleYear)) continue;

                        /* If set specified date, only judge month and day. */
                        if(date('n-j', strtotime($date)) != $specifiedDate) continue;
                    }
                }
                elseif($todo->config->type == 'week')
                {
                    $week = date('w', strtotime($todoDate));
                    if(strpos(",{$todo->config->week},", ",{$week},") !== false)
                    {
                        if(empty($lastCycle))         $date = $todoDate;
                        if($lastCycle and $lastCycle->date < $todoDate) $date = $todoDate;
                    }
                }
                elseif($todo->config->type == 'month')
                {
                    $day = date('j', strtotime($todoDate));
                    if(strpos(",{$todo->config->month},", ",{$day},") !== false)
                    {
                        if(empty($lastCycle))         $date = $todoDate;
                        if($lastCycle and $lastCycle->date < $todoDate) $date = $todoDate;
                    }
                }

                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;
                if($date > date('Y-m-d', strtotime('+ 30 days'))) continue;

                $newTodo->date = $date;

                $this->dao->insert(TABLE_TODO)->data($newTodo)->exec();
                $this->action->create('todo', $this->dao->lastInsertID(), 'created', '', '', 'system');
                $lastCycleList[$todoID] = $newTodo;
            }
        }
    }
}
