Code Coverage
 
Classes and Traits
Functions and Methods
Lines
Total
0.00% covered (danger)
0.00%
0 / 1
33.33% covered (danger)
33.33%
24 / 72
CRAP
38.11% covered (warning)
38.11%
117 / 307
Task
0.00% covered (danger)
0.00%
0 / 1
33.33% covered (danger)
33.33%
24 / 72
6000.12
38.11% covered (warning)
38.11%
117 / 307
 init
100.00% covered (success)
100.00%
1 / 1
3
100.00% covered (success)
100.00%
9 / 9
 afterFind
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
3 / 3
 updateState
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
2 / 2
 scenarios
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
3 / 3
 tableName
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
1 / 1
 __get
0.00% covered (danger)
0.00%
0 / 1
2.15
66.67% covered (warning)
66.67%
2 / 3
 __set
100.00% covered (success)
100.00%
1 / 1
2
100.00% covered (success)
100.00%
4 / 4
 getContentName
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
1 / 1
 getContentDescription
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
1 / 1
 getIcon
0.00% covered (danger)
0.00%
0 / 1
2
0.00% covered (danger)
0.00%
0 / 1
 rules
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
4 / 4
 anonymous function
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
2 / 2
 validateTaskList
0.00% covered (danger)
0.00%
0 / 1
3.07
80.00% covered (success)
80.00%
4 / 5
 attributeLabels
0.00% covered (danger)
0.00%
0 / 1
2
0.00% covered (danger)
0.00%
0 / 19
 getUrl
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
1 / 1
 getList
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
1 / 1
 findUnsorted
0.00% covered (danger)
0.00%
0 / 1
2
0.00% covered (danger)
0.00%
0 / 1
 findUnsortedCompleted
0.00% covered (danger)
0.00%
0 / 1
2
0.00% covered (danger)
0.00%
0 / 1
 findUserTasks
0.00% covered (danger)
0.00%
0 / 1
30
0.00% covered (danger)
0.00%
0 / 12
 findPendingTasks
0.00% covered (danger)
0.00%
0 / 1
2
0.00% covered (danger)
0.00%
0 / 4
 findReadable
0.00% covered (danger)
0.00%
0 / 1
2
0.00% covered (danger)
0.00%
0 / 4
 beforeSave
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
2 / 2
 beforeDelete
0.00% covered (danger)
0.00%
0 / 1
20
0.00% covered (danger)
0.00%
0 / 7
 afterSave
0.00% covered (danger)
0.00%
0 / 1
74.76
19.23% covered (danger)
19.23%
5 / 26
 afterMove
0.00% covered (danger)
0.00%
0 / 1
12
0.00% covered (danger)
0.00%
0 / 5
 getTaskAssignedUsers
100.00% covered (success)
100.00%
1 / 1
2
100.00% covered (success)
100.00%
5 / 5
 isTaskAssigned
0.00% covered (danger)
0.00%
0 / 1
4.05
85.71% covered (success)
85.71%
6 / 7
 addTaskAssigned
0.00% covered (danger)
0.00%
0 / 1
4.10
81.82% covered (success)
81.82%
9 / 11
 afterDelete
0.00% covered (danger)
0.00%
0 / 1
6
0.00% covered (danger)
0.00%
0 / 4
 getTaskUsers
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
1 / 1
 getAssignedTaskUsers
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
1 / 1
 hasTaskAssigned
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
1 / 1
 getUsers
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
1 / 1
 getTaskResponsible
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
1 / 1
 hasTaskResponsible
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
1 / 1
 getTaskResponsibleUsers
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
1 / 1
 isTaskResponsible
0.00% covered (danger)
0.00%
0 / 1
4.25
75.00% covered (success)
75.00%
3 / 4
 isOwner
0.00% covered (danger)
0.00%
0 / 1
4.13
80.00% covered (success)
80.00%
4 / 5
 addTaskResponsible
0.00% covered (danger)
0.00%
0 / 1
4.10
81.82% covered (success)
81.82%
9 / 11
 isOverdue
0.00% covered (danger)
0.00%
0 / 1
2
0.00% covered (danger)
0.00%
0 / 1
 getTaskReminder
0.00% covered (danger)
0.00%
0 / 1
2
0.00% covered (danger)
0.00%
0 / 2
 getItems
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
1 / 1
 hasItems
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
1 / 1
 saveNewItems
0.00% covered (danger)
0.00%
0 / 1
12
0.00% covered (danger)
0.00%
0 / 6
 addItem
0.00% covered (danger)
0.00%
0 / 1
6
0.00% covered (danger)
0.00%
0 / 7
 setNewItems
0.00% covered (danger)
0.00%
0 / 1
2
0.00% covered (danger)
0.00%
0 / 2
 setEditItems
0.00% covered (danger)
0.00%
0 / 1
2
0.00% covered (danger)
0.00%
0 / 2
 confirm
0.00% covered (danger)
0.00%
0 / 1
12
0.00% covered (danger)
0.00%
0 / 6
 completeItems
0.00% covered (danger)
0.00%
0 / 1
2
0.00% covered (danger)
0.00%
0 / 2
 getConfirmedCount
0.00% covered (danger)
0.00%
0 / 1
2
0.00% covered (danger)
0.00%
0 / 1
 isPending
0.00% covered (danger)
0.00%
0 / 1
2
0.00% covered (danger)
0.00%
0 / 1
 isInProgress
0.00% covered (danger)
0.00%
0 / 1
2
0.00% covered (danger)
0.00%
0 / 1
 isPendingReview
0.00% covered (danger)
0.00%
0 / 1
2
0.00% covered (danger)
0.00%
0 / 1
 isCompleted
0.00% covered (danger)
0.00%
0 / 1
2
0.00% covered (danger)
0.00%
0 / 1
 canReview
0.00% covered (danger)
0.00%
0 / 1
6
0.00% covered (danger)
0.00%
0 / 1
 canEdit
0.00% covered (danger)
0.00%
0 / 1
12
0.00% covered (danger)
0.00%
0 / 5
 filterResponsibleAssigned
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
5 / 5
 deleteOldNotifications
0.00% covered (danger)
0.00%
0 / 1
4.03
87.50% covered (success)
87.50%
7 / 8
 getTitle
0.00% covered (danger)
0.00%
0 / 1
2
0.00% covered (danger)
0.00%
0 / 1
 getBadge
0.00% covered (danger)
0.00%
0 / 1
6
0.00% covered (danger)
0.00%
0 / 3
 getSearchAttributes
0.00% covered (danger)
0.00%
0 / 1
2.04
77.78% covered (success)
77.78%
7 / 9
 canProcess
0.00% covered (danger)
0.00%
0 / 1
5.05
87.50% covered (success)
87.50%
7 / 8
 canCheckItems
0.00% covered (danger)
0.00%
0 / 1
20
0.00% covered (danger)
0.00%
0 / 1
 canReviewTask
0.00% covered (danger)
0.00%
0 / 1
2
0.00% covered (danger)
0.00%
0 / 1
 canResetTask
0.00% covered (danger)
0.00%
0 / 1
6
0.00% covered (danger)
0.00%
0 / 1
 canResortItems
0.00% covered (danger)
0.00%
0 / 1
6
0.00% covered (danger)
0.00%
0 / 1
 getPercent
0.00% covered (danger)
0.00%
0 / 1
110
0.00% covered (danger)
0.00%
0 / 16
 getLabels
0.00% covered (danger)
0.00%
0 / 1
42
0.00% covered (danger)
0.00%
0 / 17
 moveItemIndex
0.00% covered (danger)
0.00%
0 / 1
2
0.00% covered (danger)
0.00%
0 / 2
 beforeRemoveUser
0.00% covered (danger)
0.00%
0 / 1
6
0.00% covered (danger)
0.00%
0 / 4
 removeUser
0.00% covered (danger)
0.00%
0 / 1
30
0.00% covered (danger)
0.00%
0 / 9
 getColor
0.00% covered (danger)
0.00%
0 / 1
12
0.00% covered (danger)
0.00%
0 / 3
<?php
/**
 * @link https://www.humhub.org/
 * @copyright Copyright (c) 2018 HumHub GmbH & Co. KG
 * @license https://www.humhub.com/licences
 *
 */
namespace humhub\modules\tasks\models;
use humhub\modules\content\components\ContentContainerPermissionManager;
use humhub\modules\space\modules\manage\models\MembershipSearch;
use humhub\modules\tasks\helpers\TaskUrl;
use humhub\modules\tasks\permissions\CreateTask;
use humhub\modules\tasks\permissions\ProcessUnassignedTasks;
use humhub\modules\user\components\ActiveQueryUser;
use Yii;
use yii\db\ActiveQuery;
use yii\db\Expression;
use humhub\modules\notification\models\Notification;
use humhub\modules\tasks\models\checklist\TaskCheckList;
use humhub\modules\tasks\models\checklist\TaskItem;
use humhub\modules\tasks\models\lists\TaskList;
use humhub\modules\tasks\models\scheduling\TaskReminder;
use humhub\modules\tasks\models\scheduling\TaskScheduling;
use humhub\modules\tasks\models\state\TaskState;
use humhub\modules\tasks\models\user\TaskUser;
use humhub\libs\DbDateValidator;
use humhub\modules\content\components\ContentContainerActiveRecord;
use humhub\modules\content\components\ContentActiveRecord;
use humhub\modules\user\models\User;
use humhub\modules\search\interfaces\Searchable;
use humhub\widgets\Label;
use humhub\modules\tasks\widgets\WallEntry;
use humhub\modules\tasks\permissions\ManageTasks;
/**
 * This is the model class for table "task".
 *
 * The followings are the available columns in table 'task':
 * @property integer $id
 * @property string $title
 * @property string $color
 * @property string $description
 * @property integer $review
 * @property integer $request_sent
 * @property integer $scheduling
 * @property integer $all_day
 * @property string $start_datetime
 * @property string $end_datetime
 * @property integer $status
 * @property integer $cal_mode
 * @property integer $task_list_id
 * @property string $time_zone The timeZone this entry was saved, note the dates itself are always saved in app timeZone
 *
 * @property TaskReminder[] $taskReminder
 * @property TaskItem[] $items
 * @property TaskList $list
 * @property User[] $taskResponsibleUsers
 * @property User[] $taskAssignedUsers
 */
class Task extends ContentActiveRecord implements Searchable
{
    const SCENARIO_EDIT = 'edit';
    /**
     * @inheritdocs
     */
    public $canMove = true;
    /**
     * @inheritdocs
     */
    public $moduleId = 'tasks';
    /**
     * @inheritdocs
     */
    public $wallEntryClass = WallEntry::class;
    public $assignedUsers;
    public $responsibleUsers;
    public $selectedReminders;
    public $newItems;
    public $editItems;
    /**
     * Status
     */
    const STATUS_PENDING = 1;
    const STATUS_IN_PROGRESS = 2;
    const STATUS_PENDING_REVIEW = 3;
    const STATUS_COMPLETED = 5;
    const STATUS_ALL = 4;
    /**
     * @deprecated
     */
    const STATUS_OPEN = 1;
    /**
     * @var array all given statuses as array
     */
    public static $statuses = [
        self::STATUS_PENDING,
        self::STATUS_IN_PROGRESS,
        self::STATUS_PENDING_REVIEW,
        self::STATUS_COMPLETED
    ];
    /**
     * User Types
     */
    const USER_ASSIGNED = 0;
    const USER_RESPONSIBLE = 1;
    /**
     * @var TaskState
     */
    public $state;
    /**
     * @var TaskScheduling
     */
    public $schedule;
    /**
     * @var TaskCheckList
     */
    public $checklist;
    public function init()
    {
        $this->schedule = new TaskScheduling(['task' => $this]);
        $this->checklist = new TaskCheckList(['task' => $this]);
        if($this->status == null) {
            $this->status = static::STATUS_PENDING;
        }
        $this->updateState();
        if(!$this->all_day) {
            $this->all_day = 1;
        }
        parent::init();
    }
    public function afterFind()
    {
        $this->updateState();
        parent::afterFind();
    }
    protected function updateState()
    {
        $this->state = TaskState::getState($this);
    }
    public function scenarios()
    {
        $scenarios = parent::scenarios();
        $scenarios[self::SCENARIO_EDIT] = $scenarios[self::SCENARIO_DEFAULT];
        return $scenarios;
    }
    /**
     * @return string the associated database table name
     */
    public static function tableName()
    {
        return 'task';
    }
    /**
     * @inheritdoc
     */
    public function __get($name)
    {
        if ($name == 'deadline') {
            return parent::__get('end_datetime');
        }
        return parent::__get($name);
    }
    public function __set($name, $value)
    {
        parent::__set($name,$value);
        if($name == 'status') {
            $this->updateState();
        }
    }
    /**
     * @inheritdoc
     */
    public function getContentName()
    {
        return Yii::t('TasksModule.base', 'Task');
    }
    /**
     * @inheritdoc
     */
    public function getContentDescription()
    {
        return $this->title;
    }
    /**
     * @inheritdoc
     */
    public function getIcon()
    {
        return 'fa-tasks';
    }
    /**
     * @inheritdoc
     */
    public function rules()
    {
        return [
            [['title'], 'required'],
            [['color'], 'string', 'max' => 7],
            [['start_datetime', 'end_datetime'], 'required', 'when' => function ($model) {
                return $model->scheduling == 1;
            }, 'whenClient' => "function (attribute, value) {
                return $('#task-scheduling').val() == 1;
            }"],
            [['start_datetime'], 'default', 'value' => null],
            [['end_datetime'], 'default', 'value' => null],
            [['start_datetime'], DbDateValidator::class],
            [['end_datetime'], DbDateValidator::class],
            [['all_day', 'scheduling', 'review', 'request_sent'], 'integer'],
            [['cal_mode'], 'in', 'range' => TaskScheduling::$calModes],
            [['assignedUsers', 'description', 'responsibleUsers', 'selectedReminders'], 'safe'],
            [['title'], 'string', 'max' => 255],
            [['task_list_id'], 'validateTaskList']
        ];
    }
    public function validateTaskList($attribute, $params)
    {
        if($this->task_list_id) {
            $taskList = TaskList::findByContainer($this->content->container)->where(['id' => $this->task_list_id]);
            if(!$taskList) {
                $this->addError('task_list_id',  Yii::t('TasksModule.base', 'Invalid task list selection.'));
            }
        }
    }
    /**
     * @inheritdoc
     */
    public function attributeLabels()
    {
        return [
            'id' => 'ID',
            'title' => Yii::t('TasksModule.models_task', 'Title'),
            'color' => Yii::t('TasksModule.models_task', 'Color'),
            'description' => Yii::t('TasksModule.models_task', 'Description'),
            'review' => Yii::t('TasksModule.models_task', 'Review by responsible user required'),
            'request_sent' => Yii::t('TasksModule.models_task', 'Extend deadline request'),
            'scheduling' => Yii::t('TasksModule.models_task', 'Scheduling'),
            'all_day' => Yii::t('TasksModule.models_task', 'All Day'),
            'start_datetime' => Yii::t('TasksModule.models_task', 'Start'),
            'end_datetime' => Yii::t('TasksModule.models_task', 'End'),
            'status' => Yii::t('TasksModule.models_task', 'Status'),
            'cal_mode' => Yii::t('TasksModule.models_task', 'Add schedule to the space calendar'),
            'parent_task_id' => Yii::t('TasksModule.models_task', 'Parent Task'),
            'newItems' => Yii::t('TasksModule.models_task', 'Checklist Items'),
            'editItems' => Yii::t('TasksModule.models_task', 'Checklist Items'),
            'assignedUsers' => Yii::t('TasksModule.models_task', 'Assigned user(s)'),
            'responsibleUsers' => Yii::t('TasksModule.models_task', 'Responsible user(s)'),
            'selectedReminders' => Yii::t('TasksModule.models_task', 'Reminders'),
            'task_list_id' => Yii::t('TasksModule.base', 'List'),
        ];
    }
    /**
     * @inheritdoc
     */
    public function getUrl()
    {
        return TaskUrl::viewTask($this);
    }
    public function getList()
    {
        return $this->hasOne(TaskList::class, ['id' => 'task_list_id']);
    }
    /**
     * @param ContentContainerActiveRecord $contentContainer
     * @return \humhub\modules\content\components\ActiveQueryContent
     */
    public static function findUnsorted(ContentContainerActiveRecord $contentContainer)
    {
        return static::find()->contentContainer($contentContainer)->where('task_list_id IS NULL')->readable();
    }
    /**
     * @param ContentContainerActiveRecord $contentContainer
     * @return \humhub\modules\content\components\ActiveQueryContent
     */
    public static function findUnsortedCompleted(ContentContainerActiveRecord $contentContainer)
    {
        return static::findUnsorted($contentContainer)->andWhere(['task.status' => Task::STATUS_COMPLETED]);
    }
    /**
     * @param User|null $user
     * @param ContentActiveRecord|null $container
     * @param int $limit
     * @return array|Notification[]|Task[]|\yii\db\ActiveRecord[]
     * @throws \Throwable
     * @throws \yii\base\Exception
     */
    public static function findUserTasks(User $user = null, $container = null, $limit = 5)
    {
        if (!$user && !Yii::$app->user->isGuest) {
            $user = Yii::$app->user->getIdentity();
        } else if (!$user) {
            return [];
        }
        $query = ($container) ? self::find()->contentContainer($container) : self::find();
        return $query
            ->leftJoin('task_user', 'task.id=task_user.task_id', [])
            ->where(['task_user.user_id' => $user->id])
            ->andWhere(['!=', 'task.status', Task::STATUS_COMPLETED])
            ->orderBy([new Expression('-task.end_datetime DESC')])
            ->readable()
            ->limit($limit)
            ->all();
    }
    public static function findPendingTasks(ContentContainerActiveRecord $container)
    {
        return self::find()
            ->contentContainer($container)
            //->orderBy([new Expression('-task.end_datetime DESC')])
            ->readable()
            ->andWhere(['!=', 'task.status', Task::STATUS_COMPLETED]);
    }
    public static function findReadable(ContentContainerActiveRecord $container)
    {
        return self::find()
            ->contentContainer($container)
            ->orderBy(['task.end_datetime' => SORT_DESC])
            ->readable();
    }
    /**
     * @inheritdoc
     */
    public function beforeSave($insert)
    {
        $this->schedule->beforeSave();
        return parent::beforeSave($insert);
    }
    /**
     * @inheritdoc
     */
    public function beforeDelete()
    {
        foreach (TaskItem::findAll(['task_id' => $this->id]) as $item) {
            $item->delete();
        }
        foreach (TaskUser::findAll(['task_id' => $this->id]) as $taskUser) {
            $taskUser->delete();
        }
        foreach (TaskReminder::findAll(['task_id' => $this->id]) as $taskReminder) {
            $taskReminder->delete();
        }
        return parent::beforeDelete();
    }
    /**
     * Saves new items (if set) and updates items given edititems (if set)
     *
     * @param boolean $insert
     * @param array $changedAttributes
     * @return boolean
     */
    public function afterSave($insert, $changedAttributes)
    {
        parent::afterSave($insert, $changedAttributes);
        if($this->scenario === self::SCENARIO_EDIT) {
            $oldTaskUsers = $this->taskUsers;
            TaskUser::deleteAll(['task_id' => $this->id]);
            if (!empty($this->assignedUsers)) {
                foreach ($this->assignedUsers as $guid) {
                    $user = User::findOne(['guid' => $guid]);
                    if(!$user) {
                        continue;
                    }
                    $oldAssigned = array_filter($oldTaskUsers, function($taskUser) use ($user) {
                        /** @var $taskUser TaskUser */
                        return $taskUser->user_id === $user->id && $taskUser->user_type === Task::USER_ASSIGNED;
                    });
                    $this->addTaskAssigned($guid, empty($oldAssigned));
                }
            }
            if (!empty($this->responsibleUsers)) {
                foreach ($this->responsibleUsers as $guid) {
                    $user = User::findOne(['guid' => $guid]);
                    if(!$user) {
                        continue;
                    }
                    $oldResponsible = array_filter($oldTaskUsers, function($taskUser) use ($user) {
                        /** @var $taskUser TaskUser */
                        return $taskUser->user_id === $user->id && $taskUser->user_type === Task::USER_RESPONSIBLE;
                    });
                    $this->addTaskResponsible($guid, empty($oldResponsible));
                }
            }
            $this->checklist->afterSave($insert);
            $this->schedule->afterSave($insert, $changedAttributes);
            $this->saveNewItems();
        }
        if($this->list) {
            $this->list->setAttributes(['updated_at' => new Expression('NOW()')]);
        }
    }
    public function afterMove(ContentContainerActiveRecord $container = null)
    {
        foreach ($this->taskUsers as $taskUser) {
            if(!$container->isMember($taskUser->user_id)) {
                $taskUser->delete();
            }
        }
        $this->updateAttributes(['task_list_id' => null]);
    }
    /**
     * Returns an ActiveQuery for all assigned user models of this task.
     *
     * @return \yii\db\ActiveQuery
     */
    public function getTaskAssignedUsers($filterOutResponsibleUsers = false)
    {
        $query = $this->hasMany(User::class, ['id' => 'user_id'])->via('assignedTaskUsers');
        if($filterOutResponsibleUsers) {
            $responsible = $this->getTaskResponsibleUsers()->select(['id']);
            $query->where(['not in', 'user.id', $responsible]);
        }
        return $query;
    }
    public function isTaskAssigned($user = null)
    {
        if (!$user && !Yii::$app->user->isGuest) {
            $user = Yii::$app->user->getIdentity();
        } else if (!$user) {
            return false;
        }
        $taskAssigned = array_filter($this->assignedTaskUsers, function (TaskUser $p) use ($user) {
            return $p->user_id == $user->id;
        });
        return !empty($taskAssigned);
    }
    public function addTaskAssigned($user, $sendNotification = true)
    {
        $user = (is_string($user)) ? User::findOne(['guid' => $user]) : $user;
        if (!$user) {
            return false;
        }
        if (!$this->isTaskAssigned($user)) {
            $taskAssigned = new TaskUser([
                'task_id' => $this->id,
                'user_id' => $user->id,
                'user_type' => self::USER_ASSIGNED,
                'sendNotificationOnCreation' => $sendNotification,
            ]);
            return $taskAssigned->save();
        }
        return false;
    }
    /**
     * @inheritdoc
     */
    public function afterDelete()
    {
        if($this->list) {
            $this->list->setAttributes(['updated_at' => new Expression('NOW()')]);
        }
        parent::afterDelete();
    }
    // ###########  handle assigned users  ###########
    public function getTaskUsers()
    {
        return $this->hasMany(TaskUser::class, ['task_id' => 'id']);
    }
    /**
     * Returns an ActiveQuery for all assigned task users of this task.
     *
     * @return \yii\db\ActiveQuery
     */
    public function getAssignedTaskUsers()
    {
        return $this->getTaskUsers()->andOnCondition(['user_type' => self::USER_ASSIGNED]);
    }
    public function hasTaskAssigned()
    {
        return !empty($this->assignedTaskUsers);
    }
    public function getUsers()
    {
        return $this->hasMany(User::class, ['id' => 'user_id'])->via('taskUsers');
    }
    // ###########  handle responsible users  ###########
    /**
     * Returns an ActiveQuery for all responsible task users of this task.
     *
     * @return \yii\db\ActiveQuery
     */
    public function getTaskResponsible()
    {
        return $this->hasMany(TaskUser::class, ['task_id' => 'id'])->andOnCondition(['user_type' => self::USER_RESPONSIBLE]);
    }
    public function hasTaskResponsible()
    {
        return !empty($this->taskResponsible);
    }
    /**
     * Returns an ActiveQuery for all responsible user models of this task.
     *
     * @return \yii\db\ActiveQuery
     */
    public function getTaskResponsibleUsers()
    {
        return $this->hasMany(User::class, ['id' => 'user_id'])->via('taskResponsible');
    }
    public function isTaskResponsible($user = null)
    {
        if (!$user && !Yii::$app->user->isGuest) {
            $user = Yii::$app->user->getIdentity();
        } else if (!$user) {
            return false;
        }
        $taskResponsible = array_filter($this->taskResponsible, function (TaskUser $p) use ($user) {
            return $p->user_id == $user->id;
        });
        return !empty($taskResponsible);
    }
    public function isOwner($user = null)
    {
        if (!$user && !Yii::$app->user->isGuest) {
            $user = Yii::$app->user->getIdentity();
        } else if (!$user) {
            return false;
        }
        return $this->content->created_by === $user->getId();
    }
    public function addTaskResponsible($user, $sendNotification = true)
    {
        $user = (is_string($user)) ? User::findOne(['guid' => $user]) : $user;
        if (!$user) {
            return false;
        }
        if (!$this->isTaskResponsible($user)) {
            $taskResponsible = new TaskUser([
                'task_id' => $this->id,
                'user_id' => $user->id,
                'user_type' => self::USER_RESPONSIBLE,
                'sendNotificationOnCreation' => $sendNotification
            ]);
            return $taskResponsible->save();
        }
        return false;
    }
    public function isOverdue()
    {
        return $this->schedule->isOverdue();
    }
    /**
     * Returns an ActiveQuery for all assigned task users of this task.
     *
     * @return \yii\db\ActiveQuery
     */
    public function getTaskReminder()
    {
        $query = $this->hasMany(TaskReminder::className(), ['task_id' => 'id']);
        return $query;
    }
    // ###########  handle task items  ###########
    /**
     * Returns an ActiveQuery for all task items of this task.
     *
     * @return ActiveQuery
     */
    public function getItems()
    {
        return $this->hasMany(TaskItem::class, ['task_id' => 'id'])->orderBy(['sort_order' => SORT_ASC]);
    }
    public function hasItems()
    {
        // Todo check task_items and subtask-Items
        return !empty($this->items);
    }
    public function saveNewItems()
    {
        if ($this->newItems == null) {
            return;
        }
        foreach ($this->newItems as $itemText) {
            $this->addItem($itemText);
        }
        // Reset cached items
        unset($this->items);
    }
    public function addItem($itemText)
    {
        if (trim($itemText) === '') {
            return;
        }
        $item = new TaskItem();
        $item->task_id = $this->id;
        $item->title = $itemText;
        $item->save();
        return $item;
    }
    /**
     * Sets the newItems array, which is used for creating and updating (afterSave)
     * the task, by saving all valid item title contained in the given array.
     * @param array $newItemArr
     */
    public function setNewItems($newItemArr)
    {
        $this->newItems = TaskItem::filterValidItems($newItemArr);
    }
    /**
     * Sets the editItems array, which is used for updating (afterSave)
     * the task. The given array has to contain task item ids as key and an title
     * as values.
     * @param array $editItemArr
     */
    public function setEditItems($editItemArr)
    {
        $this->editItems = TaskItem::filterValidItems($editItemArr);
    }
    /**
     * @param array $items
     * @throws \yii\db\Exception
     */
    public function confirm($items = [])
    {
        foreach ($items as $itemID) {
            $item = TaskItem::findOne(['id' => $itemID, 'task_id' => $this->id]);
            if ($item) {
                $item->completed = 1;
                $item->save();
            }
        }
    }
    /**
     * @throws \yii\db\Exception
     */
    public function completeItems()
    {
        TaskItem::updateAll(['completed' => 1], ['task_id' => $this->id]);
    }
    /**
     * Returns the total number of confirmed users got this message
     *
     * @return int
     */
    public function getConfirmedCount()
    {
        return $this->getItems()->where(['completed' => true])->count();
    }
    public function isPending()
    {
        return $this->state->isPending();
    }
    public function isInProgress()
    {
        return $this->state->isInProgress();
    }
    public function isPendingReview()
    {
        return $this->state->isPendingReview();
    }
    public function isCompleted()
    {
        return $this->state->isCompleted();
    }
    public function canReview($user = null)
    {
        return $this->review && $this->isTaskResponsible($user);
    }
    /**
     * Additional canEdit check for responsible users.
     * @return bool
     * @see Content::canEdit()
     */
    public function canEdit()
    {
        if($this->isNewRecord) {
            return $this->content->container->can([CreateTask::class, ManageTasks::class]);
        } else if(!$this->hasTaskResponsible()) {
            return  $this->content->container->can([ManageTasks::class]);
        }
        return  $this->isTaskResponsible();
    }
    // ###########  handle notifications  ###########
    /**
     * Filters responsible users from the list of assigned users
     *
     * @return array|User[]
     */
    public function filterResponsibleAssigned()
    {
        $responsible = $this->getTaskResponsibleUsers()->select(['id']);
        $filteredAssigned = $this->getTaskAssignedUsers()
            ->where(['not in', 'id', $responsible])
            ->all();
        return $filteredAssigned;
    }
    public function deleteOldNotifications($notificationClassName = null)
    {
        if (!$notificationClassName) {
            // delete all old notifications - used for reset
            $notifications = Notification::find()->where(['source_class' => self::class, 'source_pk' => $this->id, 'space_id' => $this->content->container->id])->all();
            foreach ($notifications as $notification) {
                $notification->delete();
            }
        }
        else {
            // delete specific old notifications
            $notifications = Notification::find()->where(['class' => $notificationClassName, 'source_class' => self::class, 'source_pk' => $this->id, 'space_id' => $this->content->container->id])->all();
            foreach ($notifications as $notification) {
                $notification->delete();
            }
        }
    }
    /**
     * Access url of the source content or other view
     *
     * @return string the timezone this item was originally saved, note this is
     */
    public function getTitle()
    {
        return $this->title;
    }
    /**
     * Returns a badge for the snippet
     *
     * @return string the timezone this item was originally saved, note this is
     */
    public function getBadge()
    {
        if ($this->schedule->isOverdue())
            return Label::danger(Yii::t('TasksModule.views_index_index', 'Overdue'))->icon('fa fa-exclamation-triangle')->options(['style' => 'margin-right: 3px;'])->right();
        return null;
    }
    /**
     * @inheritdoc
     */
    public function getSearchAttributes()
    {
        $itemTitles = "";
        $itemDescriptions = "";
        foreach ($this->items as $item) {
            $itemTitles .= $item->title;
            $itemDescriptions .= $item->description;
        }
        return [
            'title' => $this->title,
            'description' => $this->description,
            'itemTitles' => $itemTitles,
            'itemDescriptions' => $itemDescriptions
        ];
    }
    /**
     * handle task specific permissions
     * @return bool
     */
    public function canProcess($user = null)
    {
        if(!$user && Yii::$app->user->isGuest) {
            return false;
        }
        if(!$user) {
            $user = Yii::$app->user->getIdentity();
        }
        $permissionManager = new ContentContainerPermissionManager([
            'contentContainer' => $this->content->container,
            'subject' => $user
        ]);
        return (!$this->hasTaskAssigned() && $permissionManager->can(ProcessUnassignedTasks::class));
    }
    /**
     * handle task specific permissions
     * @return bool
     */
    public function canCheckItems()
    {
        return (($this->isTaskResponsible() || $this->isTaskAssigned() || $this->canProcess()) && (!($this->isCompleted())));
    }
    /**
     * Only responsible users can review task
     * @return bool
     */
    public function canReviewTask()
    {
        return (self::isTaskResponsible());
    }
    /**
     * handle task specific permissions
     * @return bool
     */
    public function canResetTask()
    {
        return (self::isTaskResponsible() && (self::isCompleted()));
    }
    /**
     * handle task specific permissions
     * @return bool
     */
    public function canResortItems()
    {
        return (self::isTaskResponsible() && (!self::isCompleted()));
    }
    // ###########  handle view-specific  ###########
    /**
     * Returns the percentage of task
     *
     * @return int
     */
    public function getPercent()
    {
//        $denominator = TaskItem::find()->where(['task_id' => $this->id])->count();
        $denominator = $this->getItems()->count();
        // add STATUS_IN_PROGRESS and STATUS_COMPLETED
        $denominator += 2;
        // handle special status STATUS_PENDING_REVIEW
        if ($this->review) {
            $denominator += 1;
        }
        if ($denominator == 0)
            return 0;
        $counter = $this->getConfirmedCount();
        if (self::isInProgress())
            $counter += 1;
        elseif (self::isCompleted() && !$this->review)
            $counter += 2;
        elseif (self::isPendingReview() && $this->review)
            $counter += 2;
        elseif (self::isCompleted() && $this->review)
            $counter += 3;
        return $counter / $denominator * 100;
    }
    /**
     * Returns additional labels
     *
     * @param array $labels
     * @param bool $includeContentName
     * @return Label[]|string[]
     */
    public function getLabels($labels = [], $includeContentName = true)
    {
        switch ($this->status) {
            case self::STATUS_PENDING :
                $labels[] = Label::defaultType(Yii::t('TasksModule.views_index_index', 'Pending'))->icon('fa fa-info-circle')->sortOrder(350);
                break;
            case self::STATUS_IN_PROGRESS :
                $labels[] = Label::info(Yii::t('TasksModule.views_index_index', 'In Progress'))->icon('fa fa-edit')->sortOrder(350);
                break;
            case self::STATUS_PENDING_REVIEW :
                $labels[] = Label::warning(Yii::t('TasksModule.views_index_index', 'Pending Review'))->icon('fa fa-exclamation-triangle')->sortOrder(350);
                break;
            case self::STATUS_COMPLETED :
                $labels[] = Label::success(Yii::t('TasksModule.views_index_index', 'Completed'))->icon('fa fa-check-square')->sortOrder(350);
                break;
            default:
                break;
        }
        if ($this->schedule->isOverdue())
            $labels[] = Label::danger(Yii::t('TasksModule.views_index_index', 'Overdue'))->icon('fa fa-exclamation-triangle')->sortOrder(360);
        return parent::getLabels($labels, $includeContentName);
    }
    public function moveItemIndex($itemId, $newIndex)
    {
        $this->checklist->moveItemIndex($itemId, $newIndex);
    }
    /**
     * @throws \Exception
     * @throws \yii\db\StaleObjectException
     */
    public function beforeRemoveUser()
    {
        $notifications = Notification::find()->where(['source_class' => self::className(), 'source_pk' => $this->id, 'space_id' => $this->content->contentContainer->id])->all();
        foreach ($notifications as $notification) {
            $notification->delete();
        }
    }
    /**
     * @param $userId
     * @return boolw
     * @throws \Exception
     * @throws \yii\db\StaleObjectException
     */
    public function removeUser($userId)
    {
        if (empty($userId) || !isset($userId)) {
            return false;
        }
        $taskAssigned = $this->getAssignedTaskUsers()->where(['task_user.user_id' => $userId])->all();
        foreach ($taskAssigned as $assigned) {
            $assigned->delete();
        }
        $taskResponsible = $this->getTaskResponsible()->where(['task_user.user_id' => $userId])->all();
        foreach ($taskResponsible as $responsible) {
            $responsible->delete();
        }
    }
    public function getColor()
    {
        if($this->task_list_id && $this->list) {
            return $this->list->getColor();
        }
    }
}