<?php

use app\services\AbstractKanban;
use app\services\tasks\TasksKanban;

defined('BASEPATH') or exit('No direct script access allowed');

class Tasks_model extends App_Model {
    const STATUS_NOT_STARTED = 1;
    const STATUS_AWAITING_FEEDBACK = 2;
    const STATUS_TESTING = 3;
    const STATUS_IN_PROGRESS = 4;
    const STATUS_COMPLETE = 5;

    public function __construct() {
        parent::__construct();

        $this->load->model('projects_model');
        $this->load->model('staff_model');
    }

    // Not used?
    public function get_user_tasks_assigned($playground = false) {
        $this->db->where('id IN (SELECT taskid FROM ' . db_prefix() . ($playground ? 'playground_' : '') . 'task_assigned WHERE staffid = ' . get_staff_user_id() . ')');
        $this->db->where('status !=', 5);
        $this->db->order_by('duedate', 'asc');
        return $this->db->get(db_prefix() . ($playground ? 'playground_' : '') . 'tasks')->result_array();
    }

    public function get_statuses($playground = false) {
        $statuses = hooks()->apply_filters('before_get_task_statuses', [
            [
                'id' => static ::STATUS_NOT_STARTED,
                'color' => '#64748b',
                'name' => _l('task_status_1'),
                'order' => 1,
                'filter_default' => true,
            ], [
                'id' => static ::STATUS_IN_PROGRESS,
                'color' => '#3b82f6',
                'name' => _l('task_status_4'),
                'order' => 2, 'filter_default' => true,
            ], [
                'id' => static ::STATUS_TESTING,
                'color' => '#0284c7',
                'name' => _l('task_status_3'),
                'order' => 3,
                'filter_default' => true, 
            ], [
                'id' => static ::STATUS_AWAITING_FEEDBACK,
                'color' => '#84cc16',
                'name' => _l('task_status_2'),
                'order' => 4, 'filter_default' => true,
            ], [
                'id' => static ::STATUS_COMPLETE,
                'color' => '#22c55e',
                'name' => _l('task_status_5'),
                'order' => 100,
                'filter_default' => false,
            ],
        ]);
        usort($statuses, function ($a, $b) {
            return $a['order'] - $b['order'];
        });
        return $statuses;
    }

    /**
     * Get task by id
     * @param  mixed $id task id
     * @return object
     */
    public function get($id, $where = [], $playground = false) {
        $is_admin = is_admin();
        $this->db->where('id', $id);
        $this->db->where($where);
        $task = $this->db->get(db_prefix() . ($playground ? 'playground_' : '') . 'tasks')->row();
        if ($task) {
            $task->comments = $this->get_task_comments($id, $playground);
            $task->assignees = $this->get_task_assignees($id, $playground);
            $task->assignees_ids = [];
            foreach ($task->assignees as $follower) {
                array_push($task->assignees_ids, $follower['assigneeid']);
            }
            $task->followers = $this->get_task_followers($id, $playground);
            $task->followers_ids = [];
            foreach ($task->followers as $follower) {
                array_push($task->followers_ids, $follower['followerid']);
            }
            $task->attachments = $this->get_task_attachments($id, [], $playground);
            $task->timesheets = $this->get_timesheeets($id, $playground);
            $task->checklist_items = $this->get_checklist_items($id, $playground);
            if (is_staff_logged_in()) {
                $task->current_user_is_assigned = $this->is_task_assignee(get_staff_user_id(), $id, $playground);
                $task->current_user_is_creator = $this->is_task_creator(get_staff_user_id(), $id, $playground);
            }
            $task->milestone_name = '';
            if ($task->rel_type == 'project') {
                $task->project_data = $this->projects_model->get($task->rel_id, $playground);
                if ($task->milestone != 0) {
                    $milestone = $this->misc_model->get_milestone($task->milestone, $playground);
                    if ($milestone) {
                        $task->hide_milestone_from_customer = $milestone->hide_from_customer;
                        $task->milestone_name = $milestone->name;
                    }
                }
            }
        }
        return hooks()->apply_filters('get_task', $task);
    }
    
    public function get_tasks($id, $playground = false) {
        if (is_numeric($id)) {
            return $this->get($id, [], $playground);
        } else {
            $result = [];
            $tasks = $this->db->get(db_prefix() . ($playground ? 'playground_' : '') . 'tasks')->result_array();
            foreach ($tasks as $task) {
                $result[] = (array)$this->get($task['id'], [], $playground);
            }
            return $result;
        }
    }

    public function update_order($data, $playground = false) {
        AbstractKanban::updateOrder($data['order'], 'kanban_order', ($playground ? 'playground_' : '') . 'tasks', $data['status']);
    }

    public function get_distinct_tasks_years($get_from, $playground = false) {
        return $this->db->query('SELECT DISTINCT(YEAR(' . $this->db->escape_str($get_from) . ')) as year FROM ' . db_prefix() . ($playground ? 'playground_' : '') . 'tasks WHERE ' . $this->db->escape_str($get_from) . ' IS NOT NULL ORDER BY year DESC')->result_array();
    }

    public function is_task_billed($id, $playground = false) {
        return (total_rows(db_prefix() . ($playground ? 'playground_' : '') . 'tasks', ['id' => $id, 'billed' => 1, ]) > 0 ? true : false);
    }

    public function copy($data, $overwrites = [], $playground = false) {
        $task = $this->get($data['copy_from']);
        $fields_tasks = $this->db->list_fields(db_prefix() . ($playground ? 'playground_' : '') . 'tasks');
        $_new_task_data = [];
        foreach ($fields_tasks as $field) {
            if (isset($task->$field)) {
                $_new_task_data[$field] = $task->$field;
            }
        }
        unset($_new_task_data['id']);
        if (isset($data['copy_task_status']) && is_numeric($data['copy_task_status'])) {
            $_new_task_data['status'] = $data['copy_task_status'];
        } else {
            // fallback in case no status is provided
            $_new_task_data['status'] = 1;
        }
        $_new_task_data['dateadded'] = date('Y-m-d H:i:s');
        $_new_task_data['startdate'] = date('Y-m-d');
        $_new_task_data['deadline_notified'] = 0;
        $_new_task_data['billed'] = 0;
        $_new_task_data['invoice_id'] = 0;
        $_new_task_data['total_cycles'] = 0;
        $_new_task_data['is_recurring_from'] = null;
        if (is_staff_logged_in()) {
            $_new_task_data['addedfrom'] = get_staff_user_id();
        }
        if (!empty($task->duedate)) {
            $dStart = new DateTime($task->startdate);
            $dEnd = new DateTime($task->duedate);
            $dDiff = $dStart->diff($dEnd);
            $_new_task_data['duedate'] = date('Y-m-d', strtotime(date('Y-m-d', strtotime('+' . $dDiff->days . 'DAY'))));
        }
        // Overwrite data options
        if (count($overwrites) > 0) {
            foreach ($overwrites as $key => $val) {
                $_new_task_data[$key] = $val;
            }
        }
        unset($_new_task_data['datefinished']);
        $_new_task_data = hooks()->apply_filters('before_add_task', $_new_task_data);
        $this->db->insert(db_prefix() . ($playground ? 'playground_' : '') . 'tasks', $_new_task_data);
        $insert_id = $this->db->insert_id();
        $this->load->model('misc_model');
        if ($insert_id) {
            $tags = get_tags_in($data['copy_from'], ($playground ? 'playground_' : '') . 'task');
            $this->misc_model->handle_tags_save($tags, $insert_id, ($playground ? 'playground_' : '') . 'task');
            if (isset($data['copy_task_assignees']) && $data['copy_task_assignees'] == 'true') {
                $this->copy_task_assignees($data['copy_from'], $insert_id, $playground);
            }
            if (isset($data['copy_task_followers']) && $data['copy_task_followers'] == 'true') {
                $this->copy_task_followers($data['copy_from'], $insert_id, $playground);
            }
            if (isset($data['copy_task_checklist_items']) && $data['copy_task_checklist_items'] == 'true') {
                $this->copy_task_checklist_items($data['copy_from'], $insert_id, $playground);
            }
            if (isset($data['copy_task_attachments']) && $data['copy_task_attachments'] == 'true') {
                $attachments = $this->get_task_attachments($data['copy_from'], [], $playground);
                if (is_dir($this->misc_model->get_upload_path_by_type('task', $playground) . $data['copy_from'])) {
                    xcopy($this->misc_model->get_upload_path_by_type('task', $playground) . $data['copy_from'], $this->misc_model->get_upload_path_by_type('task', $playground) . $insert_id);
                }
                foreach ($attachments as $at) {
                    $_at = [];
                    $_at[] = $at;
                    $external = false;
                    if (!empty($at['external'])) {
                        $external = $at['external'];
                        $_at[0]['name'] = $at['file_name'];
                        $_at[0]['link'] = $at['external_link'];
                        if (!empty($at['thumbnail_link'])) {
                            $_at[0]['thumbnailLink'] = $at['thumbnail_link'];
                        }
                    }
                    $this->add_attachment_to_database($insert_id, $_at, $external, false, $playground);
                }
            }
            $this->copy_task_custom_fields($data['copy_from'], $insert_id, $playground);
            hooks()->do_action('after_add_task', $insert_id);
            return $insert_id;
        }
        return false;
    }

    public function copy_task_followers($from_task, $to_task, $playground = false) {
        $followers = $this->get_task_followers($from_task, $playground);
        foreach ($followers as $follower) {
            $this->db->insert(db_prefix() . ($playground ? 'playground_' : '') . 'task_followers', ['taskid' => $to_task, 'staffid' => $follower['followerid'], ]);
        }
    }

    public function copy_task_assignees($from_task, $to_task, $playground = false) {
        $assignees = $this->get_task_assignees($from_task, $playground);
        foreach ($assignees as $assignee) {
            $this->db->insert(db_prefix() . ($playground ? 'playground_' : '') . 'task_assigned', ['taskid' => $to_task, 'staffid' => $assignee['assigneeid'], 'assigned_from' => get_staff_user_id(), ]);
        }
    }

    public function copy_task_checklist_items($from_task, $to_task, $playground = false) {
        $checklists = $this->get_checklist_items($from_task, $playground);
        foreach ($checklists as $list) {
            $this->db->insert(db_prefix() . ($playground ? 'playground_' : '') . 'task_checklist_items', ['taskid' => $to_task, 'finished' => 0, 'description' => $list['description'], 'dateadded' => date('Y-m-d H:i:s'), 'addedfrom' => $list['addedfrom'], 'list_order' => $list['list_order'], 'assigned' => $list['assigned'], ]);
        }
    }

    public function copy_task_custom_fields($from_task, $to_task, $playground = false) {
        $this->load->model('custom_fields_model');
        $custom_fields = $this->custom_fields_model->get_custom_fields('tasks', [], false, $playground);
        foreach ($custom_fields as $field) {
            $value = get_custom_field_value($from_task, $field['id'], 'tasks', false, $playground);
            if ($value != '') {
                $this->db->insert(db_prefix() . ($playground ? 'playground_' : '') . 'customfieldsvalues', ['relid' => $to_task, 'fieldid' => $field['id'], 'fieldto' => 'tasks', 'value' => $value, ]);
            }
        }
    }

    public function get_billable_tasks($customer_id = false, $project_id = '', $playground = false) {
        $has_permission_view = staff_can('view', 'tasks');
        $noPermissionsQuery = get_tasks_where_string(false, $playground);
        $this->db->where('billable', 1);
        $this->db->where('billed', 0);
        if ($project_id == '') {
            $this->db->where('rel_type != "project"');
        } else {
            $this->db->where('rel_type', 'project');
            $this->db->where('rel_id', $project_id);
        }
        if ($customer_id != false && $project_id == '') {
            $this->db->where('(
                (rel_id IN (SELECT id FROM ' . db_prefix() . ($playground ? 'playground_' : '') . 'invoices WHERE clientid=' . $this->db->escape_str($customer_id) . ') AND rel_type="invoice")
                OR
                (rel_id IN (SELECT id FROM ' . db_prefix() . ($playground ? 'playground_' : '') . 'estimates WHERE clientid=' . $this->db->escape_str($customer_id) . ') AND rel_type="estimate")
                OR
                (rel_id IN (SELECT id FROM ' . db_prefix() . ($playground ? 'playground_' : '') . 'contracts WHERE client=' . $this->db->escape_str($customer_id) . ') AND rel_type="contract")
                OR
                ( rel_id IN (SELECT ticketid FROM ' . db_prefix() . ($playground ? 'playground_' : '') . 'tickets WHERE userid=' . $this->db->escape_str($customer_id) . ') AND rel_type="ticket")
                OR
                (rel_id IN (SELECT id FROM ' . db_prefix() . ($playground ? 'playground_' : '') . 'expenses WHERE clientid=' . $this->db->escape_str($customer_id) . ') AND rel_type="expense")
                OR
                (rel_id IN (SELECT id FROM ' . db_prefix() . ($playground ? 'playground_' : '') . 'proposals WHERE rel_id=' . $this->db->escape_str($customer_id) . ' AND rel_type="customer") AND rel_type="proposal")
                OR
                (rel_id IN (SELECT userid FROM ' . db_prefix() . ($playground ? 'playground_' : '') . 'clients WHERE userid=' . $this->db->escape_str($customer_id) . ') AND rel_type="customer")
            )');
        }
        if (!$has_permission_view) {
            $this->db->where($noPermissionsQuery);
        }
        $tasks = $this->db->get(db_prefix() . ($playground ? 'playground_' : '') . 'tasks')->result_array();
        $i = 0;
        foreach ($tasks as $task) {
            $task_rel_data = get_relation_data($task['rel_type'], $task['rel_id']);
            $task_rel_value = get_relation_values($task_rel_data, $task['rel_type']);
            $tasks[$i]['rel_name'] = $task_rel_value['name'];
            if (total_rows(db_prefix() . ($playground ? 'playground_' : '') . 'taskstimers', ['task_id' => $task['id'], 'end_time' => null, ]) > 0) {
                $tasks[$i]['started_timers'] = true;
            } else {
                $tasks[$i]['started_timers'] = false;
            }
            $i++;
        }
        return $tasks;
    }

    public function get_billable_amount($taskId, $playground = false) {
        $data = $this->get_billable_task_data($taskId, $playground);
        return app_format_number($data->total_hours * $data->hourly_rate);
    }

    public function get_billable_task_data($task_id, $playground = false) {
        $this->db->where('id', $task_id);
        $data = $this->db->get(db_prefix() . ($playground ? 'playground_' : '') . 'tasks')->row();
        if ($data->rel_type == 'project') {
            $this->db->select('billing_type,project_rate_per_hour,name');
            $this->db->where('id', $data->rel_id);
            $project = $this->db->get(db_prefix() . ($playground ? 'playground_' : '') . 'projects')->row();
            $billing_type = $this->projects_model->get_project_billing_type($data->rel_id, $playground);
            if ($project->billing_type == 2) {
                $data->hourly_rate = $project->project_rate_per_hour;
            }
            $data->name = $project->name . ' - ' . $data->name;
        }
        $total_seconds = task_timer_round($this->calc_task_total_time($task_id));
        $data->total_hours = sec2qty($total_seconds);
        $data->total_seconds = $total_seconds;
        return $data;
    }

    public function get_tasks_by_staff_id($id, $where = [], $playground = false) {
        $this->db->where($where);
        $this->db->where('(id IN (SELECT taskid FROM ' . db_prefix() . ($playground ? 'playground_' : '') . 'task_assigned WHERE staffid=' . $this->db->escape_str($id) . '))');
        return $this->db->get(db_prefix() . ($playground ? 'playground_' : '') . 'tasks')->result_array();
    }

    /**
     * Add new staff task
     * @param array $data task $_POST data
     * @return mixed
     */
    public function add($data, $clientRequest = false, $playground = false) {
        $fromTicketId = null;
        if (isset($data['ticket_to_task'])) {
            $fromTicketId = $data['ticket_to_task'];
            unset($data['ticket_to_task']);
        }
        $data['startdate'] = to_sql_date($data['startdate']);
        $data['duedate'] = to_sql_date($data['duedate']);
        $data['dateadded'] = date('Y-m-d H:i:s');
        $data['addedfrom'] = $clientRequest == false ? get_staff_user_id() : get_contact_user_id();
        $data['is_added_from_contact'] = $clientRequest == false ? 0 : 1;
        $checklistItems = [];
        if (isset($data['checklist_items']) && count($data['checklist_items']) > 0) {
            $checklistItems = $data['checklist_items'];
            unset($data['checklist_items']);
        }
        if ($clientRequest == false) {
            $defaultStatus = get_option('default_task_status');
            if ($defaultStatus == 'auto') {
                if (date('Y-m-d') >= $data['startdate']) {
                    $data['status'] = 4;
                } else {
                    $data['status'] = 1;
                }
            } else {
                $data['status'] = $defaultStatus;
            }
        } else {
            // When client create task the default status is NOT STARTED
            // After staff will get the task will change the status
            $data['status'] = 1;
        }
        if (isset($data['custom_fields'])) {
            $custom_fields = $data['custom_fields'];
            unset($data['custom_fields']);
        }
        if (isset($data['is_public'])) {
            $data['is_public'] = 1;
        } else {
            $data['is_public'] = 0;
        }
        if (isset($data['repeat_every']) && $data['repeat_every'] != '') {
            $data['recurring'] = 1;
            if ($data['repeat_every'] == 'custom') {
                $data['repeat_every'] = $data['repeat_every_custom'];
                $data['recurring_type'] = $data['repeat_type_custom'];
                $data['custom_recurring'] = 1;
            } else {
                $_temp = explode('-', $data['repeat_every']);
                $data['recurring_type'] = $_temp[1];
                $data['repeat_every'] = $_temp[0];
                $data['custom_recurring'] = 0;
            }
        } else {
            $data['recurring'] = 0;
            $data['repeat_every'] = null;
        }
        if (isset($data['repeat_type_custom']) && isset($data['repeat_every_custom'])) {
            unset($data['repeat_type_custom']);
            unset($data['repeat_every_custom']);
        }
        if (is_client_logged_in() || $clientRequest) {
            $data['visible_to_client'] = 1;
        } else {
            if (isset($data['visible_to_client'])) {
                $data['visible_to_client'] = 1;
            } else {
                $data['visible_to_client'] = 0;
            }
        }
        if (isset($data['billable'])) {
            $data['billable'] = 1;
        } else {
            $data['billable'] = 0;
        }
        if ((!isset($data['milestone']) || $data['milestone'] == '') || (isset($data['milestone']) && $data['milestone'] == '')) {
            $data['milestone'] = 0;
        } else {
            if ($data['rel_type'] != 'project') {
                $data['milestone'] = 0;
            }
        }
        if (empty($data['rel_type'])) {
            unset($data['rel_type']);
            unset($data['rel_id']);
        } else {
            if (empty($data['rel_id'])) {
                unset($data['rel_type']);
                unset($data['rel_id']);
            }
        }
        $withDefaultAssignee = true;
        if (isset($data['withDefaultAssignee'])) {
            $withDefaultAssignee = $data['withDefaultAssignee'];
            unset($data['withDefaultAssignee']);
        }
        $data = hooks()->apply_filters('before_add_task', $data);
        $tags = '';
        if (isset($data['tags'])) {
            $tags = $data['tags'];
            unset($data['tags']);
        }
        if (isset($data['assignees'])) {
            $assignees = $data['assignees'];
            unset($data['assignees']);
        }
        if (isset($data['followers'])) {
            $followers = $data['followers'];
            unset($data['followers']);
        }
        $this->db->insert(db_prefix() . ($playground ? 'playground_' : '') . 'tasks', $data);
        $insert_id = $this->db->insert_id();
        $this->load->model('misc_model');
        if ($insert_id) {
            foreach ($checklistItems as $key => $chkID) {
                if ($chkID != '') {
                    $itemTemplate = $this->get_checklist_template($chkID, $playground);
                    $this->db->insert(db_prefix() . ($playground ? 'playground_' : '') . 'task_checklist_items', ['description' => $itemTemplate->description, 'taskid' => $insert_id, 'dateadded' => date('Y-m-d H:i:s'), 'addedfrom' => get_staff_user_id(), 'list_order' => $key, ]);
                }
            }
            $this->misc_model->handle_tags_save($tags, $insert_id, 'task');
            if (isset($custom_fields)) {
                $this->load->model('custom_fields_model');
                $this->custom_fields_model->handle_custom_fields_post($insert_id, $custom_fields, false, $playground);
            }
            if (isset($data['rel_type']) && $data['rel_type'] == 'lead') {
                $this->load->model('leads_model');
                $this->leads_model->log_lead_activity($data['rel_id'], 'not_activity_new_task_created', false, serialize(['<a href="' . admin_url('tasks/view/' . $insert_id) . '" onclick="init_task_modal(' . $insert_id . ');return false;">' . $data['name'] . '</a>', ]));
            }
            if ($clientRequest == false) {
                if (isset($assignees)) {
                    foreach ($assignees as $staff_id) {
                        $this->add_task_assignees(['taskid' => $insert_id, 'assignee' => $staff_id, ], false, false, $playground);
                    }
                }
                // else {
                //     $new_task_auto_assign_creator = (get_option('new_task_auto_assign_current_member') == '1' ? true : false);
                //     if ( isset($data['rel_type'])
                //         && $data['rel_type'] == 'project'
                //         && !$this->projects_model->is_member($data['rel_id'])
                //         || !$withDefaultAssignee
                //         ) {
                //         $new_task_auto_assign_creator = false;
                //     }
                //     if ($new_task_auto_assign_creator == true) {
                //         $this->db->insert(db_prefix() . ($playground ? 'playground_' : '') . 'task_assigned', [
                //             'taskid'        => $insert_id,
                //             'staffid'       => get_staff_user_id(),
                //             'assigned_from' => get_staff_user_id(),
                //         ]);
                //     }
                // }
                if (isset($followers)) {
                    foreach ($followers as $staff_id) {
                        $this->add_task_followers(['taskid' => $insert_id, 'follower' => $staff_id, ], $playground);
                    }
                }
                //  else {
                //     if (get_option('new_task_auto_follower_current_member') == '1') {
                //         $this->db->insert(db_prefix() . ($playground ? 'playground_' : '') . 'task_followers', [
                //             'taskid'  => $insert_id,
                //             'staffid' => get_staff_user_id(),
                //         ]);
                //     }
                // }
                if ($fromTicketId !== null) {
                    $ticket_attachments = $this->db->query('SELECT * FROM ' . db_prefix() . ($playground ? 'playground_' : '') . 'ticket_attachments WHERE ticketid=' . $this->db->escape_str($fromTicketId) . ' OR (ticketid=' . $this->db->escape_str($fromTicketId) . ' AND replyid IN (SELECT id FROM ' . db_prefix() . ($playground ? 'playground_' : '') . 'ticket_replies WHERE ticketid=' . $this->db->escape_str($fromTicketId) . '))')->result_array();
                    if (count($ticket_attachments) > 0) {
                        $task_path = $this->misc_model->get_upload_path_by_type('task', $playground) . $insert_id . '/';
                        _maybe_create_upload_path($task_path);
                        foreach ($ticket_attachments as $ticket_attachment) {
                            $path = $this->misc_model->get_upload_path_by_type('ticket', $playground) . $fromTicketId . '/' . $ticket_attachment['file_name'];
                            if (file_exists($path)) {
                                $f = fopen($path, FOPEN_READ);
                                if ($f) {
                                    $filename = unique_filename($task_path, $ticket_attachment['file_name']);
                                    $fpt = fopen($task_path . $filename, 'w');
                                    if ($fpt && fwrite($fpt, stream_get_contents($f))) {
                                        $this->db->insert(db_prefix() . ($playground ? 'playground_' : '') . 'files', ['rel_id' => $insert_id, 'rel_type' => 'task', 'file_name' => $filename, 'filetype' => $ticket_attachment['filetype'], 'staffid' => get_staff_user_id(), 'dateadded' => date('Y-m-d H:i:s'), 'attachment_key' => app_generate_hash(), ]);
                                    }
                                    if ($fpt) {
                                        fclose($fpt);
                                    }
                                    fclose($f);
                                }
                            }
                        }
                    }
                }
            }
            log_activity('New Task Added [ID:' . $insert_id . ', Name: ' . $data['name'] . ']');
            hooks()->do_action('after_add_task', $insert_id);
            return $insert_id;
        }
        return false;
    }

    /**
     * Update task data
     * @param  array $data task data $_POST
     * @param  mixed $id   task id
     * @return boolean
     */
    public function update($data, $id, $clientRequest = false, $playground = false) {
        $affectedRows = 0;
        $data['startdate'] = to_sql_date($data['startdate']);
        $data['duedate'] = to_sql_date($data['duedate']);
        $checklistItems = [];
        if (isset($data['checklist_items']) && count($data['checklist_items']) > 0) {
            $checklistItems = $data['checklist_items'];
            unset($data['checklist_items']);
        }
        if (isset($data['datefinished'])) {
            $data['datefinished'] = to_sql_date($data['datefinished'], true);
        }
        if ($clientRequest == false) {
            $data['cycles'] = !isset($data['cycles']) ? 0 : $data['cycles'];
            $original_task = $this->get($id);
            // Recurring task set to NO, Cancelled
            if ($original_task->repeat_every != '' && $data['repeat_every'] == '') {
                $data['cycles'] = 0;
                $data['total_cycles'] = 0;
                $data['last_recurring_date'] = null;
            }
            if ($data['repeat_every'] != '') {
                $data['recurring'] = 1;
                if ($data['repeat_every'] == 'custom') {
                    $data['repeat_every'] = $data['repeat_every_custom'];
                    $data['recurring_type'] = $data['repeat_type_custom'];
                    $data['custom_recurring'] = 1;
                } else {
                    $_temp = explode('-', $data['repeat_every']);
                    $data['recurring_type'] = $_temp[1];
                    $data['repeat_every'] = $_temp[0];
                    $data['custom_recurring'] = 0;
                }
            } else {
                $data['recurring'] = 0;
            }
            if (isset($data['repeat_type_custom']) && isset($data['repeat_every_custom'])) {
                unset($data['repeat_type_custom']);
                unset($data['repeat_every_custom']);
            }
            if (isset($data['is_public'])) {
                $data['is_public'] = 1;
            } else {
                $data['is_public'] = 0;
            }
            if (isset($data['billable'])) {
                $data['billable'] = 1;
            } else {
                $data['billable'] = 0;
            }
            if (isset($data['visible_to_client'])) {
                $data['visible_to_client'] = 1;
            } else {
                $data['visible_to_client'] = 0;
            }
        }
        if ((!isset($data['milestone']) || $data['milestone'] == '') || (isset($data['milestone']) && $data['milestone'] == '')) {
            $data['milestone'] = 0;
        } else {
            if ($data['rel_type'] != 'project') {
                $data['milestone'] = 0;
            }
        }
        if (empty($data['rel_type'])) {
            $data['rel_id'] = null;
            $data['rel_type'] = null;
        } else {
            if (empty($data['rel_id'])) {
                $data['rel_id'] = null;
                $data['rel_type'] = null;
            }
        }
        $data = hooks()->apply_filters('before_update_task', $data, $id);
        if (isset($data['custom_fields'])) {
            $custom_fields = $data['custom_fields'];
            $this->load->model('custom_fields_model');
            if ($this->custom_fields_model->handle_custom_fields_post($id, $custom_fields, false, $playground)) {
                $affectedRows++;
            }
            unset($data['custom_fields']);
        }
        if (isset($data['tags'])) {
            $this->load->model('misc_model');
            if ($this->misc_model->handle_tags_save($data['tags'], $id, 'task')) {
                $affectedRows++;
            }
            unset($data['tags']);
        }
        foreach ($checklistItems as $key => $chkID) {
            $itemTemplate = $this->get_checklist_template($chkID, $playground);
            $this->db->insert(db_prefix() . ($playground ? 'playground_' : '') . 'task_checklist_items', ['description' => $itemTemplate->description, 'taskid' => $id, 'dateadded' => date('Y-m-d H:i:s'), 'addedfrom' => get_staff_user_id(), 'list_order' => $key, ]);
            $affectedRows++;
        }
        $this->db->where('id', $id);
        $this->db->update(db_prefix() . ($playground ? 'playground_' : '') . 'tasks', $data);
        if ($this->db->affected_rows() > 0) {
            $affectedRows++;
        }
        if ($affectedRows > 0) {
            hooks()->do_action('after_update_task', $id);
            log_activity('Task Updated [ID:' . $id . ', Name: ' . $data['name'] . ']');
            return true;
        }
        return false;
    }

    public function get_checklist_item($id, $playground = false) {
        $this->db->where('id', $id);
        return $this->db->get(db_prefix() . ($playground ? 'playground_' : '') . 'task_checklist_items')->row();
    }

    public function get_checklist_items($taskid, $playground = false) {
        $this->db->where('taskid', $taskid);
        $this->db->order_by('list_order', 'asc');
        return $this->db->get(db_prefix() . ($playground ? 'playground_' : '') . 'task_checklist_items')->result_array();
    }

    public function add_checklist_template($description, $playground = false) {
        $this->db->insert(db_prefix() . ($playground ? 'playground_' : '') . 'tasks_checklist_templates', ['description' => $description, ]);
        return $this->db->insert_id();
    }

    public function remove_checklist_item_template($id, $playground = false) {
        $this->db->where('id', $id);
        $this->db->delete(db_prefix() . ($playground ? 'playground_' : '') . 'tasks_checklist_templates');
        if ($this->db->affected_rows() > 0) {
            return true;
        }
        return false;
    }

    public function get_checklist_templates($playground = false) {
        $this->db->order_by('description', 'asc');
        return $this->db->get(db_prefix() . ($playground ? 'playground_' : '') . 'tasks_checklist_templates')->result_array();
    }

    public function get_checklist_template($id, $playground = false) {
        $this->db->where('id', $id);
        return $this->db->get(db_prefix() . ($playground ? 'playground_' : '') . 'tasks_checklist_templates')->row();
    }

    /**
     * Add task new blank check list item
     * @param mixed $data $_POST data with taxid
     */
    public function add_checklist_item($data, $playground = false) {
        $this->db->insert(db_prefix() . ($playground ? 'playground_' : '') . 'task_checklist_items', ['taskid' => $data['taskid'], 'description' => $data['description'], 'dateadded' => date('Y-m-d H:i:s'), 'addedfrom' => get_staff_user_id(), 'list_order' => $data['list_order']??0, ]);
        $insert_id = $this->db->insert_id();
        if ($insert_id) {
            hooks()->do_action('task_checklist_item_created', ['task_id' => $data['taskid'], 'checklist_id' => $insert_id]);
            return true;
        }
        return false;
    }

    public function delete_checklist_item($id, $playground = false) {
        $this->db->where('id', $id);
        $this->db->delete(db_prefix() . ($playground ? 'playground_' : '') . 'task_checklist_items');
        if ($this->db->affected_rows() > 0) {
            return true;
        }
        return false;
    }

    public function update_checklist_order($data, $playground = false) {
        foreach ($data['order'] as $order) {
            $this->db->where('id', $order[0]);
            $this->db->update(db_prefix() . ($playground ? 'playground_' : '') . 'task_checklist_items', ['list_order' => $order[1], ]);
        }
    }

    /**
     * Update checklist item
     * @param  mixed $id          check list id
     * @param  mixed $description checklist description
     * @return void
     */
    public function update_checklist_item($id, $description, $playground = false) {
        $description = strip_tags($description, '<br>,<br/>');
        if ($description === '') {
            $this->db->where('id', $id);
            $this->db->delete(db_prefix() . ($playground ? 'playground_' : '') . 'task_checklist_items');
        } else {
            $this->db->where('id', $id);
            $this->db->update(db_prefix() . ($playground ? 'playground_' : '') . 'task_checklist_items', ['description' => nl2br($description), ]);
        }
    }

    /**
     * Make task public
     * @param  mixed $task_id task id
     * @return boolean
     */
    public function make_public($task_id, $playground = false) {
        $this->db->where('id', $task_id);
        $this->db->update(db_prefix() . ($playground ? 'playground_' : '') . 'tasks', ['is_public' => 1, ]);
        if ($this->db->affected_rows() > 0) {
            return true;
        }
        return false;
    }

    /**
     * Get task creator id
     * @param  mixed $taskid task id
     * @return mixed
     */
    public function get_task_creator_id($taskid, $playground = false) {
        $this->db->select('addedfrom');
        $this->db->where('id', $taskid);
        return $this->db->get(db_prefix() . ($playground ? 'playground_' : '') . 'tasks')->row()->addedfrom;
    }

    /**
     * Add new task comment
     * @param array $data comment $_POST data
     * @return boolean
     */
    public function add_task_comment($data, $playground = false) {
        if (is_client_logged_in()) {
            $data['staffid'] = 0;
            $data['contact_id'] = get_contact_user_id();
        } else {
            $data['staffid'] = get_staff_user_id();
            $data['contact_id'] = 0;
        }
        $this->db->insert(db_prefix() . ($playground ? 'playground_' : '') . 'task_comments', ['taskid' => $data['taskid'], 'content' => is_client_logged_in() ? _strip_tags($data['content']) : $data['content'], 'staffid' => $data['staffid'], 'contact_id' => $data['contact_id'], 'dateadded' => date('Y-m-d H:i:s'), ]);
        $insert_id = $this->db->insert_id();
        if ($insert_id) {
            $this->db->select('rel_type,rel_id,name,visible_to_client');
            $this->db->where('id', $data['taskid']);
            $task = $this->db->get(db_prefix() . ($playground ? 'playground_' : '') . 'tasks')->row();
            $description = 'not_task_new_comment';
            $additional_data = serialize([$task->name, ]);
            if ($task->rel_type == 'project') {
                $this->projects_model->log_activity($task->rel_id, 'project_activity_new_task_comment', $task->name, $task->visible_to_client, $playground);
            }
            $regex = "/data\-mention\-id\=\"(\d+)\"/";
            if (preg_match_all($regex, $data['content'], $mentionedStaff, PREG_PATTERN_ORDER)) {
                $this->_send_task_mentioned_users_notification($description, $data['taskid'], $mentionedStaff[1], 'task_new_comment_to_staff', $additional_data, $insert_id, $playground);
            } else {
                $this->_send_task_responsible_users_notification($description, $data['taskid'], false, 'task_new_comment_to_staff', $additional_data, $insert_id, $playground);
                $this->db->where('project_id', $task->rel_id);
                $this->db->where('name', 'view_task_comments');
                $project_settings = $this->db->get(db_prefix() . ($playground ? 'playground_' : '') . 'project_settings')->row();
                if ($project_settings && $project_settings->value == 1) {
                    $this->_send_customer_contacts_notification($data['taskid'], 'task_new_comment_to_customer', $playground);
                }
            }
            hooks()->do_action('task_comment_added', ['task_id' => $data['taskid'], 'comment_id' => $insert_id]);
            return $insert_id;
        }
        return false;
    }

    /**
    * Get project name by passed id
    * @param  mixed $id
    * @return string
    */
    public function get_task_subject_by_id($id, $playground = false)
    {
        $this->db->select('name');
        $this->db->where('id', $id);
        $task = $this->db->get(db_prefix() . ($playground ? 'playground_' : '') . 'tasks')->row();
        if ($task) {
            return $task->name;
        }

        return '';
    }

    /**
     * Add task followers
     * @param array $data followers $_POST data
     * @return boolean
     */
    public function add_task_followers($data, $playground = false) {
        $this->db->insert(db_prefix() . ($playground ? 'playground_' : '') . 'task_followers', ['taskid' => $data['taskid'], 'staffid' => $data['follower'], ]);
        if ($this->db->affected_rows() > 0) {
            $taskName = $this->get_task_subject_by_id($data['taskid'], $playground);
            if (get_staff_user_id() != $data['follower']) {
                $notified = add_notification(['description' => 'not_task_added_you_as_follower', 'touserid' => $data['follower'], 'link' => '#taskid=' . $data['taskid'], 'additional_data' => serialize([$taskName, ]), ], $playground);
                if ($notified) {
                    pusher_trigger_notification([$data['follower']]);
                }
                $member = $this->staff_model->get($data['follower'], [], $playground);
                send_mail_template('task_added_as_follower_to_staff', $member->email, $data['follower'], $data['taskid']);
            }
            $description = 'not_task_added_someone_as_follower';
            $additional_notification_data = serialize([$this->staff_model->get_staff_full_name($data['follower'], $playground), $taskName, ]);
            if ($data['follower'] == get_staff_user_id()) {
                $additional_notification_data = serialize([$taskName, ]);
                $description = 'not_task_added_himself_as_follower';
            }
            $this->_send_task_responsible_users_notification($description, $data['taskid'], $data['follower'], '', $additional_notification_data, false, $playground);
            hooks()->do_action('task_follower_added', ['staff_id' => $data['follower'], 'task_id' => $data['taskid'], ]);
            return true;
        }
        return false;
    }

    /**
     * Assign task to staff
     * @param array $data task assignee $_POST data
     * @return boolean
     */
    public function add_task_assignees($data, $cronOrIntegration = false, $clientRequest = false, $playground = false) {
        $assignData = ['taskid' => $data['taskid'], 'staffid' => $data['assignee'], ];
        if ($cronOrIntegration) {
            $assignData['assigned_from'] = $data['assignee'];
        } else if ($clientRequest) {
            $assignData['is_assigned_from_contact'] = 1;
            $assignData['assigned_from'] = get_contact_user_id();
        } else {
            $assignData['assigned_from'] = get_staff_user_id();
        }
        $this->db->insert(db_prefix() . ($playground ? 'playground_' : '') . 'task_assigned', $assignData);
        $assigneeId = $this->db->insert_id();
        if ($assigneeId) {
            $this->db->select('name,visible_to_client,rel_id,rel_type');
            $this->db->where('id', $data['taskid']);
            $task = $this->db->get(db_prefix() . ($playground ? 'playground_' : '') . 'tasks')->row();
            if (get_staff_user_id() != $data['assignee'] || $clientRequest) {
                $notification_data = ['description' => ($cronOrIntegration == false ? 'not_task_assigned_to_you' : 'new_task_assigned_non_user'), 'touserid' => $data['assignee'], 'link' => '#taskid=' . $data['taskid'], ];
                $notification_data['additional_data'] = serialize([$task->name, ]);
                if ($cronOrIntegration) {
                    $notification_data['fromcompany'] = 1;
                }
                if ($clientRequest) {
                    $notification_data['fromclientid'] = get_contact_user_id();
                }
                if (add_notification($notification_data)) {
                    pusher_trigger_notification([$data['assignee']]);
                }
                $member = $this->staff_model->get($data['assignee'], [], $playground);
                send_mail_template('task_assigned_to_staff', $member->email, $data['assignee'], $data['taskid'], $playground);
            }
            $description = 'not_task_assigned_someone';
            $additional_notification_data = serialize([$this->staff_model->get_staff_full_name($data['assignee'], $playground), $task->name, ]);
            if ($data['assignee'] == get_staff_user_id()) {
                $description = 'not_task_will_do_user';
                $additional_notification_data = serialize([$task->name, ]);
            }
            if ($task->rel_type == 'project') {
                $this->projects_model->log_activity($task->rel_id, 'project_activity_new_task_assignee', $task->name . ' - ' . $this->staff_model->get_staff_full_name($data['assignee'], $playground), $task->visible_to_client, $playground);
            }
            $this->_send_task_responsible_users_notification($description, $data['taskid'], $data['assignee'], '', $additional_notification_data, false, $playground);
            hooks()->do_action('task_assignee_added', ['staff_id' => $assigneeId, 'task_id' => $data['taskid'], ]);
            return $assigneeId;
        }
        return false;
    }

    /**
     * Get all task attachments
     * @param  mixed $taskid taskid
     * @return array
     */
    public function get_task_attachments($taskid, $where = [], $playground = false) {
        $this->db->select(implode(', ', prefixed_table_fields_array(db_prefix() . ($playground ? 'playground_' : '') . 'files')) . ', ' . db_prefix() . ($playground ? 'playground_' : '') . 'task_comments.id as comment_file_id');
        $this->db->where(db_prefix() . ($playground ? 'playground_' : '') . 'files.rel_id', $taskid);
        $this->db->where(db_prefix() . ($playground ? 'playground_' : '') . 'files.rel_type', 'task');
        if ((is_array($where) && count($where) > 0) || (is_string($where) && $where != '')) {
            $this->db->where($where);
        }
        $this->db->join(db_prefix() . ($playground ? 'playground_' : '') . 'task_comments', db_prefix() . ($playground ? 'playground_' : '') . 'task_comments.file_id = ' . db_prefix() . ($playground ? 'playground_' : '') . 'files.id', 'left');
        $this->db->join(db_prefix() . ($playground ? 'playground_' : '') . 'tasks', db_prefix() . ($playground ? 'playground_' : '') . 'tasks.id = ' . db_prefix() . ($playground ? 'playground_' : '') . 'files.rel_id');
        $this->db->order_by(db_prefix() . ($playground ? 'playground_' : '') . 'files.dateadded', 'desc');
        return $this->db->get(db_prefix() . ($playground ? 'playground_' : '') . 'files')->result_array();
    }

    /**
     * Remove task attachment from server and database
     * @param  mixed $id attachmentid
     * @return boolean
     */
    public function remove_task_attachment($id, $playground = false) {
        $comment_removed = false;
        $deleted = false;
        // Get the attachment
        $this->db->where('id', $id);
        $attachment = $this->db->get(db_prefix() . ($playground ? 'playground_' : '') . 'files')->row();
        $this->load->model('misc_model');
        if ($attachment) {
            if (empty($attachment->external)) {
                $relPath = $this->misc_model->get_upload_path_by_type('task', $playground) . $attachment->rel_id . '/';
                $fullPath = $relPath . $attachment->file_name;
                if (file_exists($fullPath)) {
                    unlink($fullPath);
                    $fname = pathinfo($fullPath, PATHINFO_FILENAME);
                    $fext = pathinfo($fullPath, PATHINFO_EXTENSION);
                    $thumbPath = $relPath . $fname . '_thumb.' . $fext;
                    if (file_exists($thumbPath)) {
                        unlink($thumbPath);
                    }
                }
            }
            $this->db->where('id', $attachment->id);
            $this->db->delete(db_prefix() . ($playground ? 'playground_' : '') . 'files');
            if ($this->db->affected_rows() > 0) {
                $deleted = true;
                log_activity('Task Attachment Deleted [TaskID: ' . $attachment->rel_id . ']');
            }
            if (is_dir($this->misc_model->get_upload_path_by_type('task', $playground) . $attachment->rel_id)) {
                // Check if no attachments left, so we can delete the folder also
                $other_attachments = list_files($this->misc_model->get_upload_path_by_type('task', $playground) . $attachment->rel_id);
                if (count($other_attachments) == 0) {
                    // okey only index.html so we can delete the folder also
                    delete_dir($this->misc_model->get_upload_path_by_type('task', $playground) . $attachment->rel_id);
                }
            }
        }
        if ($deleted) {
            if ($attachment->task_comment_id != 0) {
                $total_comment_files = total_rows(db_prefix() . ($playground ? 'playground_' : '') . 'files', ['task_comment_id' => $attachment->task_comment_id]);
                if ($total_comment_files == 0) {
                    $this->db->where('id', $attachment->task_comment_id);
                    $comment = $this->db->get(db_prefix() . ($playground ? 'playground_' : '') . 'task_comments')->row();
                    if ($comment) {
                        // Comment is empty and uploaded only with attachments
                        // Now all attachments are deleted, we need to delete the comment too
                        if (empty($comment->content) || $comment->content === '[task_attachment]') {
                            $this->db->where('id', $attachment->task_comment_id);
                            $this->db->delete(db_prefix() . ($playground ? 'playground_' : '') . 'task_comments');
                            $comment_removed = $comment->id;
                        } else {
                            $this->db->query('UPDATE ' . db_prefix() . "task_comments
                                SET content = REPLACE(content, '[task_attachment]', '')
                                WHERE id = " . $attachment->task_comment_id);
                        }
                    }
                }
            }
            $this->db->where('file_id', $id);
            $comment_attachment = $this->db->get(db_prefix() . ($playground ? 'playground_' : '') . 'task_comments')->row();
            if ($comment_attachment) {
                $this->remove_comment($comment_attachment->id, false, $playground);
            }
        }
        return ['success' => $deleted, 'comment_removed' => $comment_removed];
    }

    /**
     * Add uploaded attachments to database
     * @since  Version 1.0.1
     * @param mixed $taskid     task id
     * @param array $attachment attachment data
     */
    public function add_attachment_to_database($rel_id, $attachment, $external = false, $notification = true, $playground = false) {
        $file_id = $this->misc_model->add_attachment_to_database($rel_id, 'task', $attachment, $external, $playground);
        if ($file_id) {
            $this->db->select('rel_type,rel_id,name,visible_to_client');
            $this->db->where('id', $rel_id);
            $task = $this->db->get(db_prefix() . ($playground ? 'playground_' : '') . 'tasks')->row();
            if ($task->rel_type == 'project') {
                $this->projects_model->log_activity($task->rel_id, 'project_activity_new_task_attachment', $task->name, $task->visible_to_client, $playground);
            }
            if ($notification == true) {
                $description = 'not_task_new_attachment';
                $this->_send_task_responsible_users_notification($description, $rel_id, false, 'task_new_attachment_to_staff', '', false, $playground);
                $this->_send_customer_contacts_notification($rel_id, 'task_new_attachment_to_customer', $playground);
            }
            $task_attachment_as_comment = hooks()->apply_filters('add_task_attachment_as_comment', 'true');
            if ($task_attachment_as_comment == 'true') {
                $file = $this->misc_model->get_file($file_id);
                $this->db->insert(db_prefix() . ($playground ? 'playground_' : '') . 'task_comments', ['content' => '[task_attachment]', 'taskid' => $rel_id, 'staffid' => $file->staffid, 'contact_id' => $file->contact_id, 'file_id' => $file_id, 'dateadded' => date('Y-m-d H:i:s'), ]);
            }
            return true;
        }
        return false;
    }

    /**
     * Get all task followers
     * @param  mixed $id task id
     * @return array
     */
    public function get_task_followers($id, $playground = false) {
        $this->db->select('id,' . db_prefix() . ($playground ? 'playground_' : '') . 'task_followers.staffid as followerid, CONCAT(firstname, " ", lastname) as full_name');
        $this->db->from(db_prefix() . ($playground ? 'playground_' : '') . 'task_followers');
        $this->db->join(db_prefix() . ($playground ? 'playground_' : '') . 'staff', db_prefix() . ($playground ? 'playground_' : '') . 'staff.staffid = ' . db_prefix() . ($playground ? 'playground_' : '') . 'task_followers.staffid');
        $this->db->where('taskid', $id);
        return $this->db->get()->result_array();
    }

    /**
     * Get all task assigneed
     * @param  mixed $id task id
     * @return array
     */
    public function get_task_assignees($id, $playground = false) {
        $this->db->select('id,' . db_prefix() . ($playground ? 'playground_' : '') . 'task_assigned.staffid as assigneeid,assigned_from,firstname,lastname,CONCAT(firstname, " ", lastname) as full_name,is_assigned_from_contact');
        $this->db->from(db_prefix() . ($playground ? 'playground_' : '') . 'task_assigned');
        $this->db->join(db_prefix() . ($playground ? 'playground_' : '') . 'staff', db_prefix() . ($playground ? 'playground_' : '') . 'staff.staffid = ' . db_prefix() . ($playground ? 'playground_' : '') . 'task_assigned.staffid');
        $this->db->where('taskid', $id);
        $this->db->order_by('firstname', 'asc');
        return $this->db->get()->result_array();
    }

    /**
     * Get task comment
     * @param  mixed $id task id
     * @return array
     */
    public function get_task_comments($id, $playground = false) {
        $task_comments_order = hooks()->apply_filters('task_comments_order', 'DESC');
        $this->db->select('id,dateadded,content,' . db_prefix() . ($playground ? 'playground_' : '') . 'staff.firstname,' . db_prefix() . ($playground ? 'playground_' : '') . 'staff.lastname,' . db_prefix() . ($playground ? 'playground_' : '') . 'task_comments.staffid,' . db_prefix() . ($playground ? 'playground_' : '') . 'task_comments.contact_id as contact_id,file_id,CONCAT(firstname, " ", lastname) as staff_full_name');
        $this->db->from(db_prefix() . ($playground ? 'playground_' : '') . 'task_comments');
        $this->db->join(db_prefix() . ($playground ? 'playground_' : '') . 'staff', db_prefix() . ($playground ? 'playground_' : '') . 'staff.staffid = ' . db_prefix() . ($playground ? 'playground_' : '') . 'task_comments.staffid', 'left');
        $this->db->where('taskid', $id);
        $this->db->order_by('dateadded', $task_comments_order);
        $comments = $this->db->get()->result_array();
        $ids = [];
        foreach ($comments as $key => $comment) {
            array_push($ids, $comment['id']);
            $comments[$key]['attachments'] = [];
        }
        if (count($ids) > 0) {
            $allAttachments = $this->get_task_attachments($id, 'task_comment_id IN (' . implode(',', $ids) . ')', $playground);
            foreach ($comments as $key => $comment) {
                foreach ($allAttachments as $attachment) {
                    if ($comment['id'] == $attachment['task_comment_id']) {
                        $comments[$key]['attachments'][] = $attachment;
                    }
                }
            }
        }
        return $comments;
    }

    public function edit_comment($data, $playground = false) {
        // Check if user really creator
        $this->db->where('id', $data['id']);
        $comment = $this->db->get(db_prefix() . ($playground ? 'playground_' : '') . 'task_comments')->row();
        if ($comment->staffid == get_staff_user_id() || staff_can('edit', 'tasks') || $comment->contact_id == get_contact_user_id()) {
            $comment_added = strtotime($comment->dateadded);
            $minus_1_hour = strtotime('-1 hours');
            if (get_option('client_staff_add_edit_delete_task_comments_first_hour') == 0 || (get_option('client_staff_add_edit_delete_task_comments_first_hour') == 1 && $comment_added >= $minus_1_hour) || is_admin()) {
                if (total_rows(db_prefix() . ($playground ? 'playground_' : '') . 'files', ['task_comment_id' => $comment->id]) > 0) {
                    $data['content'].= '[task_attachment]';
                }
                $this->db->where('id', $data['id']);
                $this->db->update(db_prefix() . ($playground ? 'playground_' : '') . 'task_comments', ['content' => $data['content'], ]);
                if ($this->db->affected_rows() > 0) {
                    hooks()->do_action('task_comment_updated', ['comment_id' => $comment->id, 'task_id' => $comment->taskid, ]);
                    return true;
                }
            } else {
                return false;
            }
            return false;
        }
    }

    /**
     * Remove task comment from database
     * @param  mixed $id task id
     * @return boolean
     */
    public function remove_comment($id, $force = false, $playground = false) {
        // Check if user really creator
        $this->db->where('id', $id);
        $comment = $this->db->get(db_prefix() . ($playground ? 'playground_' : '') . 'task_comments')->row();
        if (!$comment) {
            return true;
        }
        if ($comment->staffid == get_staff_user_id() || staff_can('delete', 'tasks') || $comment->contact_id == get_contact_user_id() || $force === true) {
            $comment_added = strtotime($comment->dateadded);
            $minus_1_hour = strtotime('-1 hours');
            if (get_option('client_staff_add_edit_delete_task_comments_first_hour') == 0 || (get_option('client_staff_add_edit_delete_task_comments_first_hour') == 1 && $comment_added >= $minus_1_hour) || (is_admin() || $force === true)) {
                $this->db->where('id', $id);
                $this->db->delete(db_prefix() . ($playground ? 'playground_' : '') . 'task_comments');
                if ($this->db->affected_rows() > 0) {
                    if ($comment->file_id != 0) {
                        $this->remove_task_attachment($comment->file_id, $playground);
                    }
                    $commentAttachments = $this->get_task_attachments($comment->taskid, 'task_comment_id=' . $id, $playground);
                    foreach ($commentAttachments as $attachment) {
                        $this->remove_task_attachment($attachment['id'], $playground);
                    }
                    hooks()->do_action('task_comment_deleted', ['task_id' => $comment->taskid, 'comment_id' => $id]);
                    return true;
                }
            } else {
                return false;
            }
        }
        return false;
    }

    /**
     * Remove task assignee from database
     * @param  mixed $id     assignee id
     * @param  mixed $taskid task id
     * @return boolean
     */
    public function remove_assignee($id, $taskid, $playground = false) {
        $this->db->select('rel_type,rel_id,name,visible_to_client');
        $this->db->where('id', $taskid);
        $task = $this->db->get(db_prefix() . ($playground ? 'playground_' : '') . 'tasks')->row();
        $this->db->where('id', $id);
        $assignee_data = $this->db->get(db_prefix() . ($playground ? 'playground_' : '') . 'task_assigned')->row();
        // Delete timers
        //   $this->db->where('task_id', $taskid);
        ////   $this->db->where('staff_id', $assignee_data->staffid);
        ///   $this->db->delete(db_prefix().'taskstimers');
        // Stop all timers
        $this->db->where('task_id', $taskid);
        $this->db->where('staff_id', $assignee_data->staffid);
        $this->db->where('end_time IS NULL');
        $this->db->update(db_prefix() . ($playground ? 'playground_' : '') . 'taskstimers', ['end_time' => time() ]);
        $this->db->where('id', $id);
        $this->db->delete(db_prefix() . ($playground ? 'playground_' : '') . 'task_assigned');
        if ($this->db->affected_rows() > 0) {
            if ($task->rel_type == 'project') {
                $this->projects_model->log_activity($task->rel_id, 'project_activity_task_assignee_removed', $task->name . ' - ' . $this->staff_model->get_staff_full_name($assignee_data->staffid, $playground), $task->visible_to_client, $playground);
            }
            return true;
        }
        return false;
    }

    /**
     * Remove task follower from database
     * @param  mixed $id     followerid
     * @param  mixed $taskid task id
     * @return boolean
     */
    public function remove_follower($id, $taskid, $playground = false) {
        $this->db->where('id', $id);
        $this->db->delete(db_prefix() . ($playground ? 'playground_' : '') . 'task_followers');
        if ($this->db->affected_rows() > 0) {
            return true;
        }
        return false;
    }

    /**
     * Change task status
     * @param  mixed $status  task status
     * @param  mixed $task_id task id
     * @return boolean
     */
    public function mark_as($status, $task_id, $playground = false) {
        $this->db->select('rel_type,rel_id,name,visible_to_client,status');
        $this->db->where('id', $task_id);
        $task = $this->db->get(db_prefix() . ($playground ? 'playground_' : '') . 'tasks')->row();
        if ($task->status == static ::STATUS_COMPLETE) {
            return $this->unmark_complete($task_id, $status, $playground);
        }
        $update = ['status' => $status];
        if ($status == static ::STATUS_COMPLETE) {
            $update['datefinished'] = date('Y-m-d H:i:s');
        }
        $this->db->where('id', $task_id);
        $this->db->update(db_prefix() . ($playground ? 'playground_' : '') . 'tasks', $update);
        if ($this->db->affected_rows() > 0) {
            $description = 'not_task_status_changed';
            $not_data = [$task->name, format_task_status($status, false, true), ];
            if ($status == static ::STATUS_COMPLETE) {
                $description = 'not_task_marked_as_complete';
                unset($not_data[1]);
                $this->db->where('end_time IS NULL');
                $this->db->where('task_id', $task_id);
                $this->db->update(db_prefix() . ($playground ? 'playground_' : '') . 'taskstimers', ['end_time' => time(), ]);
            }
            if ($task->rel_type == 'project') {
                $project_activity_log = $status == static ::STATUS_COMPLETE ? 'project_activity_task_marked_complete' : 'not_project_activity_task_status_changed';
                $project_activity_desc = $task->name;
                if ($status != static ::STATUS_COMPLETE) {
                    $project_activity_desc.= ' - ' . format_task_status($status);
                }
                $this->projects_model->log_activity($task->rel_id, $project_activity_log, $project_activity_desc, $task->visible_to_client, $playground);
            }
            $this->_send_task_responsible_users_notification($description, $task_id, false, 'task_status_changed_to_staff', serialize($not_data), false, $playground);
            $this->_send_customer_contacts_notification($task_id, 'task_status_changed_to_customer', $playground);
            hooks()->do_action('task_status_changed', ['status' => $status, 'task_id' => $task_id]);
            return true;
        }
        return false;
    }

    /**
     * Unmark task as complete
     * @param  mixed $id task id
     * @return boolean
     */
    public function unmark_complete($id, $force_to_status = false, $playground = false) {
        if ($force_to_status != false) {
            $status = $force_to_status;
        } else {
            $status = 1;
            $this->db->select('startdate');
            $this->db->where('id', $id);
            $_task = $this->db->get(db_prefix() . ($playground ? 'playground_' : '') . 'tasks')->row();
            if (date('Y-m-d') > date('Y-m-d', strtotime($_task->startdate))) {
                $status = 4;
            }
        }
        $this->db->where('id', $id);
        $this->db->update(db_prefix() . ($playground ? 'playground_' : '') . 'tasks', ['datefinished' => null, 'status' => $status, ]);
        if ($this->db->affected_rows() > 0) {
            $this->db->select('rel_type,rel_id,name,visible_to_client');
            $this->db->where('id', $id);
            $task = $this->db->get(db_prefix() . ($playground ? 'playground_' : '') . 'tasks')->row();
            if ($task->rel_type == 'project') {
                $this->projects_model->log_activity($task->rel_id, 'project_activity_task_unmarked_complete', $task->name, $task->visible_to_client, $playground);
            }
            $description = 'not_task_unmarked_as_complete';
            $this->_send_task_responsible_users_notification('not_task_unmarked_as_complete', $id, false, 'task_status_changed_to_staff', serialize([$task->name, ]), false, $playground);
            hooks()->do_action('task_status_changed', ['status' => $status, 'task_id' => $id]);
            return true;
        }
        return false;
    }

    /**
     * Delete task and all connections
     * @param  mixed $id taskid
     * @return boolean
     */
    public function delete_task($id, $log_activity = true, $playground = false) {
        $this->db->select('rel_type,rel_id,name,visible_to_client');
        $this->db->where('id', $id);
        $task = $this->db->get(db_prefix() . ($playground ? 'playground_' : '') . 'tasks')->row();
        $this->db->where('id', $id);
        $this->db->delete(db_prefix() . ($playground ? 'playground_' : '') . 'tasks');
        $this->load->model('misc_model');
        if ($this->db->affected_rows() > 0) {
            // Log activity only if task is deleted indivudual not when deleting all projects
            if ($task->rel_type == 'project' && $log_activity == true) {
                $this->projects_model->log_activity($task->rel_id, 'project_activity_task_deleted', $task->name, $task->visible_to_client, $playground);
            }
            $this->db->where('taskid', $id);
            $this->db->delete(db_prefix() . ($playground ? 'playground_' : '') . 'task_followers');
            $this->db->where('taskid', $id);
            $this->db->delete(db_prefix() . ($playground ? 'playground_' : '') . 'task_assigned');
            $this->db->where('taskid', $id);
            $comments = $this->db->get(db_prefix() . ($playground ? 'playground_' : '') . 'task_comments')->result_array();
            foreach ($comments as $comment) {
                $this->remove_comment($comment['id'], true, $playground);
            }
            $this->db->where('taskid', $id);
            $this->db->delete(db_prefix() . ($playground ? 'playground_' : '') . 'task_checklist_items');
            // Delete the custom field values
            $this->db->where('relid', $id);
            $this->db->where('fieldto', 'tasks');
            $this->db->delete(db_prefix() . ($playground ? 'playground_' : '') . 'customfieldsvalues');
            $this->db->where('task_id', $id);
            $this->db->delete(db_prefix() . ($playground ? 'playground_' : '') . 'taskstimers');
            $this->db->where('rel_id', $id);
            $this->db->where('rel_type', 'task');
            $this->db->delete(db_prefix() . ($playground ? 'playground_' : '') . 'taggables');
            $this->db->where('rel_type', 'task');
            $this->db->where('rel_id', $id);
            $this->db->delete(db_prefix() . ($playground ? 'playground_' : '') . 'reminders');
            $this->db->where('rel_id', $id);
            $this->db->where('rel_type', 'task');
            $attachments = $this->db->get(db_prefix() . ($playground ? 'playground_' : '') . 'files')->result_array();
            foreach ($attachments as $at) {
                $this->remove_task_attachment($at['id'], $playground);
            }
            $this->db->where('rel_id', $id);
            $this->db->where('rel_type', 'task');
            $this->db->delete(db_prefix() . ($playground ? 'playground_' : '') . 'related_items');
            if (is_dir($this->misc_model->get_upload_path_by_type('task', $playground) . $id)) {
                delete_dir($this->misc_model->get_upload_path_by_type('task', $playground) . $id);
            }
            $this->db->where('meta_key', 'task-hide-completed-items-' . $id);
            $this->db->delete(db_prefix() . ($playground ? 'playground_' : '') . 'user_meta');
            hooks()->do_action('task_deleted', $id);
            return true;
        }
        return false;
    }

    /**
     * Send notification on task activity to creator,follower/s,assignee/s
     * @param  string  $description notification description
     * @param  mixed  $taskid      task id
     * @param  boolean $excludeid   excluded staff id to not send the notifications
     * @return boolean
     */
    private function _send_task_responsible_users_notification($description, $taskid, $excludeid = false, $email_template = '', $additional_notification_data = '', $comment_id = false, $playground = false) {
        $staff = $this->staff_model->get('', ['active' => 1], $playground);
        $notifiedUsers = [];
        foreach ($staff as $member) {
            if (is_numeric($excludeid)) {
                if ($excludeid == $member['staffid']) {
                    continue;
                }
            }
            if (!is_client_logged_in()) {
                if ($member['staffid'] == get_staff_user_id()) {
                    continue;
                }
            }
            if ($this->should_staff_receive_notification($member['staffid'], $taskid, $playground)) {
                $link = '#taskid=' . $taskid;
                if ($comment_id) {
                    $link.= '#comment_' . $comment_id;
                }
                $notified = add_notification(['description' => $description, 'touserid' => $member['staffid'], 'link' => $link, 'additional_data' => $additional_notification_data, ]);
                if ($notified) {
                    array_push($notifiedUsers, $member['staffid']);
                }
                if ($email_template != '') {
                    send_mail_template($email_template, $member['email'], $member['staffid'], $taskid);
                }
            }
        }
        pusher_trigger_notification($notifiedUsers);
    }

    public function _send_customer_contacts_notification($taskid, $template_name, $playground = false) {
        $this->db->select('rel_id,visible_to_client,rel_type');
        $this->db->from(db_prefix() . ($playground ? 'playground_' : '') . 'tasks');
        $this->db->where('id', $taskid);
        $task = $this->db->get()->row();
        if ($task->rel_type == 'project') {
            $this->db->where('project_id', $task->rel_id);
            $this->db->where('name', 'view_tasks');
            $project_settings = $this->db->get(db_prefix() . ($playground ? 'playground_' : '') . 'project_settings')->row();
            if ($project_settings) {
                if ($project_settings->value == 1 && $task->visible_to_client == 1) {
                    $this->load->model('clients_model');
                    $contacts = $this->clients_model->get_contacts_for_project_notifications($project_settings->project_id, 'task_emails', $playground);
                    foreach ($contacts as $contact) {
                        if (is_client_logged_in() && get_contact_user_id() == $contact['id']) {
                            continue;
                        }
                        send_mail_template($template_name, $contact['email'], $contact['userid'], $contact['id'], $taskid);
                    }
                }
            }
        }
    }

    /**
     * Check if user has commented on task
     * @param  mixed $userid staff id to check
     * @param  mixed $taskid task id
     * @return boolean
     */
    public function staff_has_commented_on_task($userid, $taskid, $playground = false) {
        return total_rows(db_prefix() . ($playground ? 'playground_' : '') . 'task_comments', ['staffid' => $userid, 'taskid' => $taskid, ]) > 0;
    }

    /**
     * Check is user is task follower
     * @param  mixed  $userid staff id
     * @param  mixed  $taskid taskid
     * @return boolean
     */
    public function is_task_follower($userid, $taskid, $playground = false) {
        return total_rows(db_prefix() . ($playground ? 'playground_' : '') . 'task_followers', ['staffid' => $userid, 'taskid' => $taskid, ]) > 0;
    }

    /**
     * Check is user is task assignee
     * @param  mixed  $userid staff id
     * @param  mixed  $taskid taskid
     * @return boolean
     */
    public function is_task_assignee($userid, $taskid, $playground = false) {
        return total_rows(db_prefix() . ($playground ? 'playground_' : '') . 'task_assigned', ['staffid' => $userid, 'taskid' => $taskid, ]) > 0;
    }

    /**
     * Check is user is task creator
     * @param  mixed  $userid staff id
     * @param  mixed  $taskid taskid
     * @return boolean
     */
    public function is_task_creator($userid, $taskid, $playground = false) {
        return total_rows(db_prefix() . ($playground ? 'playground_' : '') . 'tasks', ['addedfrom' => $userid, 'id' => $taskid, 'is_added_from_contact' => 0, ]) > 0;
    }

    /**
     * Timer action, START/STOP Timer
     * @param  mixed  $task_id   task id
     * @param  mixed  $timer_id  timer_id to stop the timer
     * @param  string  $note      note for timer
     * @param  boolean $adminStop is admin want to stop timer from another staff member
     * @return boolean
     */
    public function timer_tracking($task_id = '', $timer_id = '', $note = '', $adminStop = false, $playground = false) {
        if ($task_id == '' && $timer_id == '') {
            return false;
        }
        if ($task_id !== '0' && $adminStop == false) {
            if (!$this->is_task_assignee(get_staff_user_id(), $task_id, $playground)) {
                return false;
            } else if ($this->is_task_billed($task_id, $playground)) {
                return false;
            }
        }
        $timer = $this->get_task_timer(['id' => $timer_id, ], $playground);
        $newTimer = false;
        if ($timer == null) {
            $newTimer = true;
        }
        if ($newTimer) {
            $this->db->select('hourly_rate');
            $this->db->from(db_prefix() . ($playground ? 'playground_' : '') . 'staff');
            $this->db->where('staffid', get_staff_user_id());
            $hourly_rate = $this->db->get()->row()->hourly_rate;
            $this->db->insert(db_prefix() . ($playground ? 'playground_' : '') . 'taskstimers', ['start_time' => time(), 'staff_id' => get_staff_user_id(), 'task_id' => $task_id, 'hourly_rate' => $hourly_rate, 'note' => ($note != '' ? $note : null), ]);
            $_new_timer_id = $this->db->insert_id();
            if (get_option('auto_stop_tasks_timers_on_new_timer') == 1) {
                $this->db->where('id !=', $_new_timer_id);
                $this->db->where('end_time IS NULL');
                $this->db->where('task_id !=', '0');
                $this->db->where('staff_id', get_staff_user_id());
                $this->db->update(db_prefix() . ($playground ? 'playground_' : '') . 'taskstimers', ['end_time' => time(), 'note' => ($note != '' ? $note : null), ]);
            }
            if ($task_id != '0' && get_option('timer_started_change_status_in_progress') == '1' && total_rows(db_prefix() . ($playground ? 'playground_' : '') . 'tasks', ['id' => $task_id, 'status' => 1]) > 0) {
                $this->mark_as(static ::STATUS_IN_PROGRESS, $task_id, $playground);
            }
            hooks()->do_action('task_timer_started', ['task_id' => $task_id, 'timer_id' => $_new_timer_id]);
            return true;
        }
        if ($timer) {
            // time already ended
            if ($timer->end_time != null) {
                return false;
            }
            $end_time = hooks()->apply_filters('before_task_timer_stopped', time(), ['timer' => $timer, 'task_id' => $task_id, 'note' => $note, ]);
            $this->db->where('id', $timer_id);
            $this->db->update(db_prefix() . ($playground ? 'playground_' : '') . 'taskstimers', ['end_time' => $end_time, 'task_id' => $task_id, 'note' => ($note != '' ? $note : null), ]);
        }
        return true;
    }

    public function timesheet($data, $playground = false) {
        if (isset($data['timesheet_duration']) && $data['timesheet_duration'] != '') {
            $duration_array = explode(':', $data['timesheet_duration']);
            $hour = $duration_array[0];
            $minutes = $duration_array[1];
            $end_time = time();
            $start_time = strtotime('-' . $hour . ' hour -' . $minutes . ' minutes');
        } else {
            $start_time = to_sql_date($data['start_time'], true);
            $end_time = to_sql_date($data['end_time'], true);
            $start_time = strtotime($start_time);
            $end_time = strtotime($end_time);
        }
        if ($end_time < $start_time) {
            return ['end_time_smaller' => true, ];
        }
        $timesheet_staff_id = get_staff_user_id();
        if (isset($data['timesheet_staff_id']) && $data['timesheet_staff_id'] != '') {
            $timesheet_staff_id = $data['timesheet_staff_id'];
        }
        if (!isset($data['timer_id']) || (isset($data['timer_id']) && $data['timer_id'] == '')) {
            // Stop all other timesheets when adding new timesheet
            $this->db->where('task_id', $data['timesheet_task_id']);
            $this->db->where('staff_id', $timesheet_staff_id);
            $this->db->where('end_time IS NULL');
            $this->db->update(db_prefix() . ($playground ? 'playground_' : '') . 'taskstimers', ['end_time' => time(), ]);
            $this->db->select('hourly_rate');
            $this->db->from(db_prefix() . ($playground ? 'playground_' : '') . 'staff');
            $this->db->where('staffid', $timesheet_staff_id);
            $hourly_rate = $this->db->get()->row()->hourly_rate;
            $this->db->insert(db_prefix() . ($playground ? 'playground_' : '') . 'taskstimers', ['start_time' => $start_time, 'end_time' => $end_time, 'staff_id' => $timesheet_staff_id, 'task_id' => $data['timesheet_task_id'], 'hourly_rate' => $hourly_rate, 'note' => (isset($data['note']) && $data['note'] != '' ? nl2br($data['note']) : null), ]);
            $insert_id = $this->db->insert_id();
            $tags = '';
            if (isset($data['tags'])) {
                $tags = $data['tags'];
            }
            $this->load->model('misc_model');
            $this->misc_model->handle_tags_save($tags, $insert_id, 'timesheet');
            if ($insert_id) {
                $this->db->select('rel_type,rel_id,name,visible_to_client');
                $this->db->where('id', $data['timesheet_task_id']);
                $task = $this->db->get(db_prefix() . ($playground ? 'playground_' : '') . 'tasks')->row();
                if ($task->rel_type == 'project') {
                    $total = $end_time - $start_time;
                    $additional = '<seconds>' . $total . '</seconds>';
                    $additional.= '<br />';
                    $additional.= '<lang>project_activity_task_name</lang> ' . $task->name;
                    $this->projects_model->log_activity($task->rel_id, 'project_activity_recorded_timesheet', $additional, $task->visible_to_client, $playground);
                }
                return true;
            }
            return false;
        }
        $affectedRows = 0;
        $this->db->where('id', $data['timer_id']);
        $this->db->update(db_prefix() . ($playground ? 'playground_' : '') . 'taskstimers', ['start_time' => $start_time, 'end_time' => $end_time, 'staff_id' => $timesheet_staff_id, 'task_id' => $data['timesheet_task_id'], 'note' => (isset($data['note']) && $data['note'] != '' ? nl2br($data['note']) : null), ]);
        if ($this->db->affected_rows() > 0) {
            $affectedRows++;
        }
        if (isset($data['tags'])) {
            $this->load->model('misc_model');
            if ($this->misc_model->handle_tags_save($data['tags'], $data['timer_id'], 'timesheet')) {
                $affectedRows++;
            }
        }
        return ($affectedRows > 0 ? true : false);
    }

    public function get_timers($task_id, $where = [], $playground = false) {
        $this->db->where($where);
        $this->db->where('task_id', $task_id);
        $this->db->order_by('start_time', 'DESC');
        return $this->db->get(db_prefix() . ($playground ? 'playground_' : '') . 'taskstimers')->result_array();
    }

    public function get_task_timer($where, $playground = false) {
        $this->db->where($where);
        return $this->db->get(db_prefix() . ($playground ? 'playground_' : '') . 'taskstimers')->row();
    }

    public function is_timer_started($task_id, $staff_id = '', $playground = false) {
        if ($staff_id == '') {
            $staff_id = get_staff_user_id();
        }
        $timer = $this->get_last_timer($task_id, $staff_id, $playground);
        if (!$timer || $timer->end_time != null) {
            return false;
        }
        return $timer;
    }

    public function is_timer_started_for_task($id, $where = [], $playground = false) {
        $this->db->where('task_id', $id);
        $this->db->where('end_time IS NULL');
        $this->db->where($where);
        $results = $this->db->count_all_results(db_prefix() . ($playground ? 'playground_' : '') . 'taskstimers');
        return $results > 0;
    }

    public function get_last_timer($task_id, $staff_id = '', $playground = false) {
        if ($staff_id == '') {
            $staff_id = get_staff_user_id();
        }
        $this->db->where('staff_id', $staff_id);
        $this->db->where('task_id', $task_id);
        $this->db->order_by('id', 'desc');
        $this->db->limit(1);
        $timer = $this->db->get(db_prefix() . ($playground ? 'playground_' : '') . 'taskstimers')->row();
        return $timer;
    }

    public function task_tracking_stats($id, $playground = false) {
        $loggers = $this->db->query('SELECT DISTINCT(staff_id) FROM ' . db_prefix() . ($playground ? 'playground_' : '') . 'taskstimers WHERE task_id=' . $this->db->escape_str($id))->result_array();
        $labels = [];
        $labels_ids = [];
        foreach ($loggers as $assignee) {
            array_push($labels, $this->staff_model->get_staff_full_name($assignee['staff_id'], $playground));
            array_push($labels_ids, $assignee['staff_id']);
        }
        $chart = ['labels' => $labels, 'datasets' => [['label' => _l('task_stats_logged_hours'), 'data' => [], ], ], ];
        $i = 0;
        foreach ($labels_ids as $staffid) {
            $chart['datasets'][0]['data'][$i] = sec2qty($this->calc_task_total_time($id, ' AND staff_id=' . $staffid));
            $i++;
        }
        return $chart;
    }

    public function get_timesheeets($task_id, $playground = false) {
        $task_id = $this->db->escape_str($task_id);
        return $this->db->query("SELECT id,note,start_time,end_time,task_id,staff_id, CONCAT(firstname, ' ', lastname) as full_name,
            end_time - start_time time_spent FROM " . db_prefix() . ($playground ? 'playground_' : '') . 'taskstimers JOIN ' . db_prefix() . ($playground ? 'playground_' : '') . 'staff ON ' . db_prefix() . ($playground ? 'playground_' : '') . 'staff.staffid=' . db_prefix() . "taskstimers.staff_id WHERE task_id = '$task_id' ORDER BY start_time DESC")->result_array();
    }

    public function get_time_spent($seconds) {
        $minutes = $seconds / 60;
        $hours = $minutes / 60;
        if ($minutes >= 60) {
            return round($hours, 2);
        } else if ($seconds > 60) {
            return round($minutes, 2);
        }
        return $seconds;
    }

    public function calc_task_total_time($task_id, $where = '') {
        $sql = get_sql_calc_task_logged_time($task_id) . $where;
        $result = $this->db->query($sql)->row();
        if ($result) {
            return $result->total_logged_time;
        }
        return 0;
    }

    public function get_unique_member_logged_task_ids($staff_id, $where = '', $playground = false) {
        $sql = 'SELECT DISTINCT(task_id) FROM ' . db_prefix() . ($playground ? 'playground_' : '') . 'taskstimers WHERE staff_id =' . $staff_id . $where;
        return $this->db->query($sql)->result();
    }

    /**
     * @deprecated
     */
    private function _cal_total_logged_array_from_timers($timers, $playground = false) {
        $total = [];
        foreach ($timers as $key => $timer) {
            $_tspent = 0;
            if (is_null($timer->end_time)) {
                $_tspent = time() - $timer->start_time;
            } else {
                $_tspent = $timer->end_time - $timer->start_time;
            }
            $total[] = $_tspent;
        }
        return array_sum($total);
    }

    public function delete_timesheet($id, $playground = false) {
        $this->db->where('id', $id);
        $timesheet = $this->db->get(db_prefix() . ($playground ? 'playground_' : '') . 'taskstimers')->row();
        $this->db->where('id', $id);
        $this->db->delete(db_prefix() . ($playground ? 'playground_' : '') . 'taskstimers');
        if ($this->db->affected_rows() > 0) {
            $this->db->where('rel_id', $id);
            $this->db->where('rel_type', 'timesheet');
            $this->db->delete(db_prefix() . ($playground ? 'playground_' : '') . 'taggables');
            $this->db->select('rel_type,rel_id,name,visible_to_client');
            $this->db->where('id', $timesheet->task_id);
            $task = $this->db->get(db_prefix() . ($playground ? 'playground_' : '') . 'tasks')->row();
            if ($task->rel_type == 'project') {
                $additional_data = $task->name;
                $total = $timesheet->end_time - $timesheet->start_time;
                $additional_data.= '<br /><seconds>' . $total . '</seconds>';
                $this->projects_model->log_activity($task->rel_id, 'project_activity_task_timesheet_deleted', $additional_data, $task->visible_to_client, $playground);
            }
            hooks()->do_action('task_timer_deleted', $timesheet);
            log_activity('Timesheet Deleted [' . $id . ']');
            return true;
        }
        return false;
    }

    public function get_reminders($task_id, $playground = false) {
        $this->db->where('rel_id', $task_id);
        $this->db->where('rel_type', 'task');
        $this->db->order_by('isnotified,date', 'ASC');
        return $this->db->get(db_prefix() . ($playground ? 'playground_' : '') . 'reminders')->result_array();
    }

    // get timesheets data
    public function get_timesheets($id ='', $playground = false)
    {
        $this->db->select('*');
        $this->db->from(db_prefix() . ($playground ? 'playground_' : '') . 'taskstimers');
        if ($id >0) {
            $this->db->where('id', $id);
        }
        return $this->db->get()->result_array();
    }

    // get timesheets data
    public function timesheets($data, $playground = false)
    {
        $data = [
            'task_id' =>  $this->input->post('task_id'),
            'start_time' =>  $this->input->post('start_time'),
            'end_time' =>  $this->input->post('end_time'),
            'staff_id' =>  $this->input->post('staff_id'),
            'hourly_rate' => $this->input->post('hourly_rate'),
            'note' => $this->input->post('note')
        ];
 
        $this->db->insert(db_prefix() . ($playground ? 'playground_' : '') . 'taskstimers', $data);
        $insert_id = $this->db->insert_id();

        if ($insert_id) {
            return true;
        }
        return false;     
    }

    //  update timesheets data
    public function update_timesheet($data, $playground = false)
    {
        if (isset($data['id'])){
            $this->db->where('id', $data['id']);
            $event = $this->db->get(db_prefix() . ($playground ? 'playground_' : '') . 'taskstimers')->row();
            if (!$event){
                return false;
            }
            $data = hooks()->apply_filters('event_update_data', $data, $data['id']);
            $this->db->where('id', $data['id']);
            $this->db->update(db_prefix() . ($playground ? 'playground_' : '') . 'taskstimers', $data);
            if ($this->db->affected_rows() > 0){
                return true;
            }
            return false;
        }
    }

    /**
     * Check whether the given staff can access the given task
     *
     * @param  int $staff_id
     * @param  int $task_id
     *
     * @return boolean
     */
    public function can_staff_access_task($staff_id, $task_id, $playground = false) {
        $retVal = false;
        $staffCanAccessTasks = $this->get_staff_members_that_can_access_task($task_id, $playground);
        foreach ($staffCanAccessTasks as $staff) {
            if ($staff['staffid'] == $staff_id) {
                $retVal = true;
                break;
            }
        }
        return $retVal;
    }

    /**
     * Get the staff members that can view the given task
     *
     * @param  int $taskId
     *
     * @return array
     */
    public function get_staff_members_that_can_access_task($taskId, $playground = false) {
        $taskId = $this->db->escape_str($taskId);
        $prefix = db_prefix() . ($playground ? 'playground_' : '');
        return $this->db->query("SELECT * FROM {$prefix}staff WHERE (
            admin=1
            OR staffid IN (SELECT staffid FROM {$prefix}task_assigned WHERE taskid=$taskId)
            OR staffid IN (SELECT staffid FROM {$prefix}task_followers WHERE taskid=$taskId)
            OR staffid IN (SELECT addedfrom FROM {$prefix}tasks WHERE id=$taskId AND is_added_from_contact=0)
            OR staffid IN(SELECT staff_id FROM {$prefix}staff_permissions WHERE feature = 'tasks' AND capability='view')
        ) AND active=1")->result_array();
    }

    /**
     * Check whether the given staff should receive notification for
     * the given task
     *
     * @param  int $staffid
     * @param  int $taskid  [description]
     *
     * @return boolean
     */
    private function should_staff_receive_notification($staffid, $taskid, $playground = false) {
        if (!$this->can_staff_access_task($staffid, $taskid, $playground)) {
            return false;
        }
        return hooks()->apply_filters('should_staff_receive_task_notification', ($this->is_task_assignee($staffid, $taskid, $playground) || $this->is_task_follower($staffid, $taskid, $playground) || $this->is_task_creator($staffid, $taskid, $playground) || $this->staff_has_commented_on_task($staffid, $taskid, $playground)), ['staff_id' => $staffid, 'task_id' => $taskid]);
    }

    /**
     * Send notifications on new task comment
     *
     * @param  string $description
     * @param  int $taskid
     * @param  array $staff
     * @param  string $email_template
     * @param  array $notification_data
     * @param  int $comment_id
     *
     * @return void
     */
    private function _send_task_mentioned_users_notification($description, $taskid, $staff, $email_template, $notification_data, $comment_id, $playground = false) {
        $staff = array_unique($staff, SORT_NUMERIC);
        $notifiedUsers = [];
        foreach ($staff as $staffId) {
            if (!is_client_logged_in()) {
                if ($staffId == get_staff_user_id()) {
                    continue;
                }
            }
            $member = $this->staff_model->get($staffId, [], $playground);
            $link = '#taskid=' . $taskid;
            if ($comment_id) {
                $link.= '#comment_' . $comment_id;
            }
            $notified = add_notification(['description' => $description, 'touserid' => $member->staffid, 'link' => $link, 'additional_data' => $notification_data, ]);
            if ($notified) {
                array_push($notifiedUsers, $member->staffid);
            }
            if ($email_template != '') {
                send_mail_template($email_template, $member->email, $member->staffid, $taskid);
            }
        }
        pusher_trigger_notification($notifiedUsers);
    }

    public function update_checklist_assigned_staff($data, $playground = false) {
        $assigned = $this->db->escape_str($data['assigned']);
        if (!is_numeric($assigned) || $assigned == 0) {
            $assigned = null;
        }
        $this->db->where('id', $data['checklistId']);
        $this->db->update(db_prefix() . ($playground ? 'playground_' : '') . 'task_checklist_items', ['assigned' => $assigned, ]);
    }

    public function do_kanban_query($status, $search = '', $page = 1, $count = false, $where = [], $playground = false) {
        _deprecated_function('Tasks_model::do_kanban_query', '2.9.2', 'TasksKanban class');
        $kanBan = (new TasksKanban($status))->search($search)->page($page)->sortBy($sort['sort']??null, $sort['sort_by']??null);
        if ($where) {
            $kanBan->tapQuery(function ($status, $ci) use ($where) {
                $ci->db->where($where);
            });
        }
        if ($count) {
            return $kanBan->countAll();
        }
        return $kanBan->get();
    }
}
