<commit_before><?php

/*
 * Copyright (C) 2006-2011 <NAME>, <NAME>, Cyber IT Solutions
 * Pty. Ltd.
 * 
 * This file is part of the allocPSA application <<EMAIL>>.
 * 
 * allocPSA is free software: you can redistribute it and/or modify it
 * under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or (at
 * your option) any later version.
 * 
 * allocPSA is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
 * License for more details.
 * 
 * You should have received a copy of the GNU Affero General Public License
 * along with allocPSA. If not, see <http://www.gnu.org/licenses/>.
*/

define("PERM_PROJECT_READ_TASK_DETAIL", 256);

class task extends db_entity {
  public $classname = "task";
  public $data_table = "task";
  public $display_field_name = "taskName";
  public $key_field = "taskID";
  public $data_fields = array("taskName" => array("audit"=>true)
                             ,"taskDescription" => array("audit"=>true)
                             ,"creatorID"
                             ,"closerID"
                             ,"priority" => array("audit"=>true)
                             ,"timeLimit" => array("audit"=>true)
                             ,"timeBest" => array("audit"=>true)
                             ,"timeWorst" => array("audit"=>true)
                             ,"timeExpected" => array("audit"=>true)
                             ,"dateCreated"
                             ,"dateAssigned"
                             ,"dateClosed"
                             ,"dateTargetStart" => array("audit"=>true)
                             ,"dateTargetCompletion" => array("audit"=>true)
                             ,"dateActualStart" => array("audit"=>true)
                             ,"dateActualCompletion" => array("audit"=>true)
                             ,"taskComments"
                             ,"taskStatus" => array("audit"=>true)
                             ,"taskModifiedUser"
                             ,"projectID" => array("audit"=>true)
                             ,"parentTaskID" => array("audit"=>true)
                             ,"taskTypeID" => array("audit"=>true)
                             ,"personID" => array("audit"=>true)
                             ,"managerID" => array("audit"=>true)
                             ,"estimatorID" => array("audit"=>true)
                             ,"duplicateTaskID" => array("audit"=>true)
                             );
  public $permissions = array(PERM_PROJECT_READ_TASK_DETAIL => "read details");

  function save() {
    global $current_user, $TPL;

    // The data prior to the save
    $old = $this->all_row_fields;

    // Set the task creator
    $this->get_value("creatorID") || $this->set_value("creatorID",$current_user->get_id());

    // Set the task's status and sub-status
    list($taskStatus, $taskSubStatus) = explode("_",$this->get_value("taskStatus"));

    // Edge-case for people who open tasks with a dateActualCompletion set, i.e. simultaneous task create and close.
    if (!$this->get_id() && $this->get_value("dateActualCompletion")) {
      if ($taskStatus != "closed" && $taskStatus != "close") {
        $taskStatus = "close";
        $taskSubStatus = "complete";
      }
    }

    if (!$this->post_save_hook && in_array($taskStatus,array("closed","close","open","pending"))) {
      $this->$taskStatus($taskSubStatus);
    }

    // If a dateActualCompletion has just been entered
    if (!$this->post_save_hook && !$old["dateActualCompletion"] && $this->get_value("dateActualCompletion")) {
      $this->close("complete");

    // Else if there was a dateActualCompletion and they have just *unset* it...
    } else if (!$this->post_save_hook && $old["dateActualCompletion"] && !$this->get_value("dateActualCompletion")) {
      $this->open("inprogress");
    }

    // If they've just plugged a dateActualStart in and the task is notstarted, then change the status to Open: In Progress
    if (!$this->post_save_hook && !$old["dateActualStart"] && $this->get_value("dateActualStart") && $old["taskStatus"] == "open_notstarted") {
      $this->open("inprogress");
    }

    // If task exists and the personID has changed, update the dateAssigned
    if ($this->get_id()) {
      if (sprintf("%d",$this->get_value("personID")) != sprintf("%d",$old["personID"])) {
        $this->set_value("dateAssigned",date("Y-m-d H:i:s"));
      }
    // Else if task doesn't exist and there is a personID set, set the dateAssigned as well
    } else if ($this->get_value("personID")) {
      $this->set_value("dateAssigned",date("Y-m-d H:i:s"));
    }

    $this->get_value("taskDescription") and $this->set_value("taskDescription",rtrim($this->get_value("taskDescription")));


    // Copy the taskExpected over to the taskLimit if we're creating the task and taskLimit isn't set
    if (!$this->get_id() && !imp($this->get_value("timeLimit")) && imp($this->get_value("timeExpected"))) {
      $this->set_value("timeLimit",$this->get_value("timeExpected"));
    }

    // If we don't have a taskLimit try and inherit the project's defaultTaskLimit
    if (!$this->get_id() && !imp($this->get_value("timeLimit"))) {
      $project = $this->get_foreign_object("project");
      if ($project && imp($project->get_value("defaultTaskLimit"))) {
        $this->set_value("timeLimit",$project->get_value("defaultTaskLimit"));
      }
    }

    // If there are no estimates, then wipe the estimatorID person setting
    if (!imp($this->get_value("timeBest")) && !imp($this->get_value("timeWorst")) && !imp($this->get_value("timeExpected"))) {
      $this->set_value("estimatorID","");
    } else if (!$this->get_value("estimatorID")) {
      $this->set_value("estimatorID",$current_user->get_id());
    }

    $rtn = parent::save();

    // If the task has just been closed, opened, pending or duplicated, then audit the change.
    if ($this->post_save_hook) {
      $psh = $this->post_save_hook;
      $this->$psh();
    }

    return $rtn;
  }

  function delete() {
    if ($this->can_be_deleted()) {
      return parent::delete();
    }
  }

  function validate() {
    // Validate/coerce the fields
    $this->get_value("taskStatus") == "inprogress" && $this->set_value("taskStatus","open_inprogress");
    $this->get_value("taskStatus") == "notstarted" && $this->set_value("taskStatus","open_notstarted");
    $this->get_value("taskStatus") == "info"       && $this->set_value("taskStatus","pending_info");
    $this->get_value("taskStatus") == "client"     && $this->set_value("taskStatus","pending_client");
    $this->get_value("taskStatus") == "manager"    && $this->set_value("taskStatus","pending_manager");
    $this->get_value("taskStatus") == "invalid"    && $this->set_value("taskStatus","closed_invalid");
    $this->get_value("taskStatus") == "duplicate"  && $this->set_value("taskStatus","closed_duplicate");
    $this->get_value("taskStatus") == "incomplete" && $this->set_value("taskStatus","closed_incomplete");
    $this->get_value("taskStatus") == "complete"   && $this->set_value("taskStatus","closed_complete");
    $this->get_value("taskStatus") == "open"       && $this->set_value("taskStatus","open_inprogress");
    $this->get_value("taskStatus") == "pending"    && $this->set_value("taskStatus","pending_info");
    $this->get_value("taskStatus") == "close"      && $this->set_value("taskStatus","closed_complete");
    $this->get_value("taskStatus") == "closed"     && $this->set_value("taskStatus","closed_complete");
    $this->get_value("taskStatus") || $this->set_value("taskStatus","open_notstarted");

    $this->get_value("priority") || $this->set_value("priority",3);
    in_array($this->get_value("priority"),array(1,2,3,4,5)) or $err[] = "Invalid priority.";

    $this->get_value("taskTypeID") || $this->set_value("taskTypeID","Task");
    in_array(ucwords($this->get_value("taskTypeID")),array("Task","Fault","Message","Milestone","Parent")) or $err[] = "Invalid Task Type.";

    $this->get_value("taskName") or $err[] = "Please enter a name for the Task.";
    $this->get_value("dateCreated") || $this->set_value("dateCreated",date("Y-m-d H:i:s"));

    $this->get_value("personID") || $this->set_value("personID","");
    $this->get_value("managerID") || $this->set_value("managerID","");

    return parent::validate($err);
  }

  function closed($t="complete",$closeChildren=true) { return $this->close($t,$closeChildren); }  // wrapper
 
  function close($taskSubStatus = "complete",$closeChildren=true) {
    global $current_user;
    $old = $this->all_row_fields;
    $cur_status = $old["taskStatus"].$old["duplicateTaskID"];
    $new_status = "closed_".$taskSubStatus.$this->get_value("duplicateTaskID");

    if ($cur_status != $new_status) { 
      $this->post_save_hook = "mark_closed";
      $taskSubStatus == "duplicate" and $this->post_save_hook = "mark_dupe";
      $this->get_value("dateActualStart")      || $this->set_value("dateActualStart", date("Y-m-d"));
      $this->get_value("dateActualCompletion") || $this->set_value("dateActualCompletion", date("Y-m-d"));
      $this->get_value("closerID")             || $this->set_value("closerID", $current_user->get_id());
      $this->get_value("dateClosed")           || $this->set_value("dateClosed",date("Y-m-d H:i:s"));           
      $this->set_value("taskStatus","closed_".$taskSubStatus);
      if ($closeChildren) {
        $this->close_off_children_recursive($taskSubStatus);
      }
    }
  }

  function open($taskSubStatus = "inprogress") {
    $old = $this->all_row_fields;
    $cur_status = $old["taskStatus"];
    $new_status = "open_".$taskSubStatus;

    if ($cur_status != $new_status) { 
      if (!$this->get_value("taskStatus") || substr($this->get_value("taskStatus"),0,4)!="open") {
        $this->post_save_hook = "mark_reopened";
      }
      $this->set_value("closerID",null);
      $this->set_value("dateClosed","");
      $this->set_value("dateActualCompletion","");
      $this->set_value("duplicateTaskID","");
      $this->set_value("taskStatus","open_".$taskSubStatus);
      if (!$this->get_value("dateActualStart") && $taskSubStatus == "inprogress") {
        $this->set_value("dateActualStart",date("Y-m-d"));
      }
    }
  }

  function pending($taskSubStatus = "info") {
    $old = $this->all_row_fields;
    $cur_status = $old["taskStatus"];
    $new_status = "pending_".$taskSubStatus;

    if ($cur_status != $new_status) { 
      $this->post_save_hook = "mark_pending";
      $this->set_value("dateActualCompletion", "");
      $this->set_value("duplicateTaskID","");
      $this->set_value("closerID",null);
      $this->set_value("dateClosed","");
      $this->set_value("taskStatus","pending_".$taskSubStatus);
    }
  }

  function close_off_children_recursive($taskSubStatus='') {
    // mark all children as complete
    global $current_user;
    if ($this->get_id() && $this->get_value("taskTypeID") == "Parent") {
      $db = new db_alloc;
      $query = sprintf("SELECT * FROM task WHERE parentTaskID = %d",$this->get_id());
      $db->query($query);
                                                                                                                               
      while ($db->next_record()) {
        $task = new task;
        $task->read_db_record($db);
        $task->close($taskSubStatus);
        $task->save();
      }
    }
  }

  function create_task_reminder() {
    // Create a reminder for this task based on the priority.
    global $current_user;

    // Get the task type
    $taskTypeName = $this->get_value("taskTypeID");
    $label = $this->get_priority_label();
    $reminderInterval = "Day";
    $intervalValue = $this->get_value("priority");
    $taskTypeName == "Parent" and $taskTypeName.= " Task";

    $subject = $taskTypeName." Reminder: ".$this->get_id()." ".$this->get_name()." [".$label."]";
    $message = "\n\n".$subject;
    $message.= "\n\n".$this->get_url(true);
    $this->get_value("taskDescription") and $message.= "\n\n".$this->get_value("taskDescription");
    $message.= "\n\n-- \nReminder created by ".$current_user->get_name()." at ".date("Y-m-d H:i:s");
    $people[] = $this->get_value("personID");
    $this->create_reminder(null, $message, $reminderInterval, $intervalValue, REMINDER_METAPERSON_TASK_ASSIGNEE, $subject);
  }

  function create_reminders($people, $message, $reminderInterval, $intervalValue) {
    if (is_array($people)) {
      foreach($people as $personID) {
        $person = new person;
        $person->set_id($personID);
        $person->select();
        if ($person->get_value("emailAddress")) {
          $this->create_reminder($personID, $message, $reminderInterval, $intervalValue);
        }
      }
    }
  }

  function create_reminder($personID=null, $message, $reminderInterval, $intervalValue, $metaPerson=null, $subject="") {
    $label = $this->get_priority_label();

    $reminder = new reminder;
    $reminder->set_value('reminderType', "task");
    $reminder->set_value('reminderLinkID', $this->get_id());
    $reminder->set_value('reminderRecuringInterval', $reminderInterval);
    $reminder->set_value('reminderRecuringValue', $intervalValue);
    $reminder->set_value('reminderSubject', $subject);
    $reminder->set_value('reminderContent', $message);

    $reminder->set_value('reminderAdvNoticeSent', "0");
    $reminder->set_value('reminderAdvNoticeInterval', "No");
    $reminder->set_value('reminderAdvNoticeValue', "0");

    $reminder->set_value('reminderTime', date("Y-m-d H:i:s"));
    if ($personID) {
      $reminder->set_value('personID', $personID);
    } else if ($metaPerson) {
      $reminder->set_value('metaPerson', $metaPerson);
    }
    $reminder->save();
  }

  function is_owner($person = "") {

    if (!is_object($person)) {
      return false;
    }

    // A user owns a task if they 'own' the project
    if ($this->get_id()) {
      // Check for existing task
      $p = $this->get_foreign_object("project");
    } else if ($_POST["projectID"]) {
      // Or maybe they are creating a new task
      $p = new project;
      $p->set_id($_POST["projectID"]);
    }

    // if this task doesn't exist (no ID) 
    // OR the person has isManager or canEditTasks for this tasks project 
    // OR if this person is the Creator of this task.
    // OR if this person is the For Person of this task.
    // OR if this person has super 'manage' perms
    // OR if we're skipping the perms checking because i.e. we're having our task status updated by a timesheet
    if (
       !$this->get_id() 
    || (is_object($p) && ($p->has_project_permission($person, array("isManager", "canEditTasks"))) 
    || $this->get_value("creatorID") == $person->get_id()
    || $this->get_value("personID") == $person->get_id()
    || $person->have_role("manage")
    || $this->skip_perms_check
    )) {
      return true;
    }
  }

  function has_attachment_permission($person) {
    return $this->is_owner($person);
  }

  function has_attachment_permission_delete($person) {
    return $this->is_owner($person);
  }

  function update_children($field,$value="") {
    $q = sprintf("SELECT * FROM task WHERE parentTaskID = %d",$this->get_id());
    $db = new db_alloc();
    $db->query($q);
    while ($db->row()) {
      $t = new task;
      $t->read_db_record($db);
      $t->set_value($field,$value);
      $t->save();
      if ($t->get_value("taskTypeID") == "Parent") {
        $t->update_children($field,$value);
      }
    }
  }

  function get_parent_task_select($projectID="") {
    global $TPL;
    
    if (is_object($this)) {
      $projectID = $this->get_value("projectID");
      $parentTaskID = $this->get_value("parentTaskID");
    }

    $projectID or $projectID = $_GET["projectID"];
    $parentTaskID or $parentTaskID = $_GET["parentTaskID"];

    $db = new db_alloc;
    if ($projectID) {
      list($ts_open,$ts_pending,$ts_closed) = task::get_task_status_in_set_sql();
      // Status may be closed_<something>
      $query = sprintf("SELECT taskID AS value, taskName AS label
                        FROM task 
                        WHERE projectID= '%d' 
                        AND taskTypeID = 'Parent'
                        AND (taskStatus NOT IN (".$ts_closed.") OR taskID = %d)
                        ORDER BY taskName", $projectID, $parentTaskID);
      $options = page::select_options($query, $parentTaskID,70);
    }
    return "<select name=\"parentTaskID\"><option value=\"\">".$options."</select>";
  }

  function get_task_cc_list_select($projectID="") {
    $interestedParty = array();
    $interestedPartyOptions = array();
    
    if (is_object($this)) {
      $interestedPartyOptions = $this->get_all_parties($projectID);
    } else {
      $interestedPartyOptions = task::get_all_parties($projectID);
    }

    #echo "<pre>".print_r($interestedPartyOptions,1)."</pre>";
  
    if (is_array($interestedPartyOptions)) {

      foreach ($interestedPartyOptions as $email => $info) {
        $name = $info["name"];
        $identifier = $info["identifier"];

        if ($info["role"] == "interested" && $info["selected"]) {
          $interestedParty[] = $identifier;
        }

        if ($email) {
          $name = trim($name);
          $str = trim(page::htmlentities($name." <".$email.">"));
          $options[$identifier] = $str;
        }
      }
    }
    $str = "<select name=\"interestedParty[]\" multiple=\"true\">".page::select_options($options,$interestedParty,100,false)."</select>";
    return $str;
  }

  function get_all_parties($projectID="") {
    $db = new db_alloc;
    $interestedPartyOptions = array();
  
    if ($_GET["projectID"]) {
      $projectID = $_GET["projectID"];
    } else if (!$projectID && is_object($this)) {
      $projectID = $this->get_value("projectID");
    }

    if ($projectID) {
      $interestedPartyOptions = project::get_all_parties($projectID);
    }

    $extra_interested_parties = config::get_config_item("defaultInterestedParties") or $extra_interested_parties=array();
    foreach ($extra_interested_parties as $name => $email) {
      $interestedPartyOptions[$email] = array("name"=>$name);
    }

    if (is_object($this)) {
      if ($this->get_value("creatorID")) {
        $p = new person;
        $p->set_id($this->get_value("creatorID"));
        $p->select();
        $p->get_value("emailAddress") and $interestedPartyOptions[$p->get_value("emailAddress")] = array("name"=>$p->get_name(), "role"=>"creator", "personID"=>$this->get_value("creatorID"));
      }
      if ($this->get_value("personID")) {
        $p = new person;
        $p->set_id($this->get_value("personID"));
        $p->select();
        $p->get_value("emailAddress") and $interestedPartyOptions[$p->get_value("emailAddress")] = array("name"=>$p->get_name(), "role"=>"assignee", "selected"=>true, "personID"=>$this->get_value("personID"));
      }
      if ($this->get_value("managerID")) {
        $p = new person;
        $p->set_id($this->get_value("managerID"));
        $p->select();
        $p->get_value("emailAddress") and $interestedPartyOptions[$p->get_value("emailAddress")] = array("name"=>$p->get_name(), "role"=>"manager", "selected"=>true, "personID"=>$this->get_value("managerID"));
      }
      $this_id = $this->get_id();
    }
    // return an aggregation of the current task/proj/client parties + the existing interested parties
    $interestedPartyOptions = interestedParty::get_interested_parties("task",$this_id,$interestedPartyOptions);
    return $interestedPartyOptions;
  }

  function get_encoded_interested_party_identifier($info=array()) {
    return urlencode(base64_encode(serialize($info)));
  }

  function get_decoded_interested_party_identifier($blob) {
    return unserialize(base64_decode(urldecode($blob)));
  }

  function get_personList_dropdown($projectID,$field,$taskID=false) {
    global $current_user;
 
    $db = new db_alloc;

    if ($_GET["timeSheetID"]) {
      $ts_query = sprintf("SELECT * FROM timeSheet WHERE timeSheetID = %d",$_GET["timeSheetID"]);
      $db->query($ts_query);
      $db->next_record();
      $owner = $db->f("personID");

    } else if (is_object($this) && $this->get_value($field)) {
      $owner = $this->get_value($field);

    } else if ($taskID) {
      $t = new task;
      $t->set_id($taskID);
      $t->select();
      $owner = $t->get_value($field);

    } else if (!is_object($this) || !$this->get_id()) {
      $owner = $current_user->get_id();
    }

    $peoplenames = person::get_username_list($owner);

    if ($projectID) {
      $q = sprintf("SELECT * 
                      FROM projectPerson 
                 LEFT JOIN person ON person.personID = projectPerson.personID 
                     WHERE person.personActive = 1 
                       AND projectID = %d
                  ORDER BY firstName, username
                   ",$projectID);
      $db->query($q);
      while ($row = $db->row()) {
        $ops[$row["personID"]] = $peoplenames[$row["personID"]];
      }
    } else {
      $ops = $peoplenames;
    }

    $ops[$owner] or $ops[$owner] = $peoplenames[$owner];
   
    $str = page::select_options($ops, $owner);
    return $str;
  }

  function get_project_options($projectID="") {
    $projectID or $projectID = $_GET["projectID"];
    // Project Options - Select all projects 
    $db = new db_alloc;
    $query = sprintf("SELECT projectID AS value, projectName AS label 
                        FROM project 
                       WHERE projectStatus IN ('Current', 'Potential') OR projectID = %d
                    ORDER BY projectName",$projectID);
    $str = page::select_options($query, $projectID, 60);
    return $str;
  }

  function set_option_tpl_values() {
    // Set template values to provide options for edit selects
    global $TPL, $current_user, $isMessage;
    $db = new db_alloc;
    $projectID = $_GET["projectID"] or $projectID = $this->get_value("projectID");
    $TPL["personOptions"] = "<select name=\"personID\"><option value=\"\">".task::get_personList_dropdown($projectID, "personID")."</select>";
    $TPL["managerPersonOptions"] = "<select name=\"managerID\"><option value=\"\">".task::get_personList_dropdown($projectID, "managerID")."</select>";
    $TPL["estimatorPersonOptions"] = "<select name=\"estimatorID\"><option value=\"\">".task::get_personList_dropdown($projectID, "estimatorID")."</select>";

    // TaskType Options
    $taskType = new meta("taskType");
    $taskType_array = $taskType->get_assoc_array("taskTypeID","taskTypeID");
    $TPL["taskTypeOptions"] = page::select_options($taskType_array,$this->get_value("taskTypeID"));

    // Project dropdown
    $TPL["projectOptions"] = task::get_project_options($projectID);
    
    $priority = $this->get_value("priority") or $priority = 3;
    $taskPriorities = config::get_config_item("taskPriorities") or $taskPriorities = array();
    foreach ($taskPriorities as $k => $v) {
      $tp[$k] = $v["label"];
    }
    $TPL["priorityOptions"] = page::select_options($tp,$priority);
    $priority and $TPL["priorityLabel"] = " <div style=\"display:inline; color:".$taskPriorities[$priority]["colour"]."\">[".$this->get_priority_label()."]</div>";

    // We're building these two with the <select> tags because they will be
    // replaced by an AJAX created dropdown when the projectID changes.
    $TPL["parentTaskOptions"] = $this->get_parent_task_select();
    $TPL["interestedPartyOptions"] = $this->get_task_cc_list_select();

    $db->query(sprintf("SELECT fullName,emailAddress
                          FROM interestedParty
                         WHERE entity='task' 
                           AND entityID = %d
                           AND interestedPartyActive = 1
                      ORDER BY fullName",$this->get_id()));
    while ($db->next_record()) {
      $str = trim(page::htmlentities($db->f("fullName")." <".$db->f("emailAddress").">"));
      $value = interestedParty::get_encoded_interested_party_identifier($db->f("fullName"), $db->f("emailAddress"));
      $TPL["interestedParty_hidden"].= $commar.$str."<input type=\"hidden\" name=\"interestedParty[]\" value=\"".$value."\">";
      $TPL["interestedParty_text"].= $commar.$str;
      $commar = "<br>";
    }

    $TPL["task_taskStatusLabel"] = $this->get_task_status("label");
    $TPL["task_taskStatusColour"] = $this->get_task_status("colour");
    $TPL["task_taskStatusValue"] = $this->get_value("taskStatus");
    $TPL["task_taskStatusOptions"] = page::select_options($this->get_task_statii_array(true),$this->get_value("taskStatus"));

    // If we're viewing the printer friendly view
    if ($_GET["media"] == "print") {
      // Parent Task label
      $t = new task;
      $t->set_id($this->get_value("parentTaskID"));
      $t->select();
      $TPL["parentTask"] = $t->get_display_value();

      // Task Type label
      $TPL["taskType"] = $this->get_value("taskTypeID"); 

      // Priority
      $TPL["priority"] = $this->get_value("priority");

      // Assignee label
      $p = new person;
      $p->set_id($this->get_value("personID"));
      $p->select();
      $TPL["person"] = $p->get_display_value();
  
      // Project label
      $p = new project;
      $p->set_id($this->get_value("projectID"));
      $p->select();
      $TPL["projectName"] = $p->get_display_value();
    }

  }

  function get_task_comments_array() {
    $rows = comment::util_get_comments_array("task",$this->get_id());
    $rows or $rows = array();
    return $rows;
  }

  function get_task_link($_FORM=array()) {
    $_FORM["return"] or $_FORM["return"] = "html";
    $rtn = "<a href=\"".$this->get_url()."\">";
    $rtn.= $this->get_name($_FORM);
    $rtn.= "</a>";
    return $rtn;
  }

  function get_task_image() {
    global $TPL;
    return "<img class=\"taskType\" alt=\"".$this->get_value("taskTypeID")."\" title=\"".$this->get_value("taskTypeID")."\" src=\"".$TPL["url_alloc_images"]."taskType_".$this->get_value("taskTypeID").".gif\">";
  }

  function get_name($_FORM=array()) {

    $_FORM["prefixTaskID"] and $id = $this->get_id()." ";

    if ($this->get_value("taskTypeID") == "Parent" && $_FORM["return"] == "html") {
      $rtn = "<strong>".$id.$this->get_value("taskName",DST_HTML_DISPLAY)."</strong>";
    } else if ($_FORM["return"] == "html") {
      $rtn = $id.$this->get_value("taskName",DST_HTML_DISPLAY);
    } else {
      $rtn = $id.$this->get_value("taskName");
    }
    return $rtn;
  }

  function get_url($absolute=false) {
    global $sess;
    $sess or $sess = new Session;

    $url = "task/task.php?taskID=".$this->get_id();

    if ($sess->Started() && !$absolute) {
      $url = $sess->url(SCRIPT_PATH.$url);

    // This for urls that are emailed
    } else {
      static $prefix;
      $prefix or $prefix = config::get_config_item("allocURL");
      $url = $prefix.$url;
    }
    return $url;
  }

  function get_task_statii_array($flat=false) {
    // This gets an array that is useful for building the two types of dropdown lists that taskStatus uses
    $taskStatii = task::get_task_statii();
    if ($flat) {
      $m = new meta("taskStatus");
      $taskStatii = $m->get_assoc_array();
      foreach ($taskStatii as $status => $arr) {
        $taskStatiiArray[$status] = task::get_task_status_thing("label",$status);
      }
    } else {
      $taskStatiiArray[""] = ""; // blank entry
      foreach ($taskStatii as $status => $sub) {
        $taskStatiiArray[$status] = ucwords($status);
        foreach ($sub as $subStatus => $arr) {
          $taskStatiiArray[$status."_".$subStatus] = "&nbsp;&nbsp;&nbsp;&nbsp;".$arr["label"];
        }
      }
    } 

    return $taskStatiiArray;
  }

  function get_task_statii() {
    // looks like:
    //$arr["open"]["notstarted"] = array("label"=>"Not Started","colour"=>"#ffffff");
    //$arr["open"]["inprogress"] = array("label"=>"In Progress","colour"=>"#ffffff");
    //etc
    static $rows;
    if (!$rows) {
      $m = new meta("taskStatus");
      $rows = $m->get_assoc_array();
    }
    foreach ($rows as $taskStatusID => $arr) {
      list($s,$ss) = explode("_",$taskStatusID);
      $rtn[$s][$ss] = array("label"=>$arr["taskStatusLabel"],"colour"=>$arr["taskStatusColour"]);
    }
    return $rtn;
  }

  function get_task_status($thing="") {
    return task::get_task_status_thing($thing,$this->get_value("taskStatus"));
  }

  function get_task_status_thing($thing="",$status="") {
    list($taskStatus,$taskSubStatus) = explode("_",$status);
    $arr = task::get_task_statii();
    if ($thing && $arr[$taskStatus][$taskSubStatus][$thing]) {
      return $arr[$taskStatus][$taskSubStatus][$thing];
    }
  }

  function get_task_status_in_set_sql() {
    $m = new meta("taskStatus");
    $arr = $m->get_assoc_array();
    foreach ($arr as $taskStatusID => $r) {
      $id = strtolower(substr($taskStatusID,0,4));
      if ($id == "open") {
        $sql_open.= $commar1.'"'.$taskStatusID.'"';
        $commar1 = ",";
      } else if ($id == "clos") {
        $sql_clos.= $commar2.'"'.$taskStatusID.'"';
        $commar2 = ",";
      } else if ($id == "pend") {
        $sql_pend.= $commar3.'"'.$taskStatusID.'"';
        $commar3 = ",";
      }
    }
    return array($sql_open,$sql_pend,$sql_clos);
  }

  function get_taskStatus_sql($s) {
    if ($s) {
      if (is_array($s)) {
        $taskStatusArray = $s;
      } else {
        $taskStatusArray[] = $s;
      }
      $subsql = array();
      foreach ($taskStatusArray as $status) {
        list($taskStatus,$taskSubStatus) = explode("_",$status);
        if($taskSubStatus) {
          $subsql[] = sprintf("(task.taskStatus = '%s')",db_esc($status));
        } else {
          $subsql[] = sprintf("(SUBSTRING(task.taskStatus,1,%d) = '%s')",strlen($status),db_esc($status));
        }
      }
      return '('.implode(" OR ",$subsql).')';
    }
  }

  function get_list_filter($filter=array()) {

    // This takes care of projectID singular and plural
    $projectIDs = project::get_projectID_sql($filter);
    $projectIDs and $sql["projectIDs"] = $projectIDs;

    list($ts_open,$ts_pending,$ts_closed) = task::get_task_status_in_set_sql();

    // New Tasks
    if ($filter["taskDate"] == "new") {
      $past = date("Y-m-d", mktime(0, 0, 0, date("m"), date("d") - 2, date("Y")))." 00:00:00";
      date("D") == "Mon" and $past = date("Y-m-d", mktime(0, 0, 0, date("m"), date("d") - 4, date("Y")))." 00:00:00";
      $sql[] = sprintf("(task.taskStatus NOT IN (".$ts_closed.") AND task.dateCreated >= '".$past."')");

    // Due Today
    } else if ($filter["taskDate"] == "due_today") {
      $sql[] = "(task.taskStatus NOT IN (".$ts_closed.") AND task.dateTargetCompletion = '".date("Y-m-d")."')";

    // Overdue
    } else if ($filter["taskDate"] == "overdue") {
      $sql[] = "(task.taskStatus NOT IN (".$ts_closed.")
                AND 
                (task.dateTargetCompletion IS NOT NULL AND task.dateTargetCompletion != '' AND '".date("Y-m-d")."' > task.dateTargetCompletion))";
  
    // Date Created
    } else if ($filter["taskDate"] == "d_created") {
      $filter["dateOne"] and $sql[] = sprintf("(task.dateCreated >= '%s')",db_esc($filter["dateOne"]));
      $filter["dateTwo"] and $sql[] = sprintf("(task.dateCreated <= '%s 23:59:59')",db_esc($filter["dateTwo"]));

    // Date Assigned
    } else if ($filter["taskDate"] == "d_assigned") {
      $filter["dateOne"] and $sql[] = sprintf("(task.dateAssigned >= '%s')",db_esc($filter["dateOne"]));
      $filter["dateTwo"] and $sql[] = sprintf("(task.dateAssigned <= '%s 23:59:59')",db_esc($filter["dateTwo"]));

    // Date Target Start
    } else if ($filter["taskDate"] == "d_targetStart") {
      $filter["dateOne"] and $sql[] = sprintf("(task.dateTargetStart >= '%s')",db_esc($filter["dateOne"]));
      $filter["dateTwo"] and $sql[] = sprintf("(task.dateTargetStart <= '%s')",db_esc($filter["dateTwo"]));

    // Date Target Completion
    } else if ($filter["taskDate"] == "d_targetCompletion") {
      $filter["dateOne"] and $sql[] = sprintf("(task.dateTargetCompletion >= '%s')",db_esc($filter["dateOne"]));
      $filter["dateTwo"] and $sql[] = sprintf("(task.dateTargetCompletion <= '%s')",db_esc($filter["dateTwo"]));

    // Date Actual Start
    } else if ($filter["taskDate"] == "d_actualStart") {
      $filter["dateOne"] and $sql[] = sprintf("(task.dateActualStart >= '%s')",db_esc($filter["dateOne"]));
      $filter["dateTwo"] and $sql[] = sprintf("(task.dateActualStart <= '%s')",db_esc($filter["dateTwo"]));

    // Date Actual Completion
    } else if ($filter["taskDate"] == "d_actualCompletion") {
      $filter["dateOne"] and $sql[] = sprintf("(task.dateActualCompletion >= '%s')",db_esc($filter["dateOne"]));
      $filter["dateTwo"] and $sql[] = sprintf("(task.dateActualCompletion <= '%s')",db_esc($filter["dateTwo"]));
    }

    // Task status filtering
    $filter["taskStatus"] and $sql[] = task::get_taskStatus_sql($filter["taskStatus"]);

    // Unset if they've only selected the topmost empty task type
    if (is_array($filter["taskTypeID"]) && count($filter["taskTypeID"])>=1 && !$filter["taskTypeID"][0]) {
      unset($filter["taskTypeID"][0]);
    }

    // If many create an SQL taskTypeID in (set) 
    if (is_array($filter["taskTypeID"]) && count($filter["taskTypeID"])) {
      $sql[] = "(task.taskTypeID in ('".implode("','",$filter["taskTypeID"])."'))";
    
    // Else if only one taskTypeID
    } else if ($filter["taskTypeID"]) {
      $sql[] = sprintf("(task.taskTypeID = '%s')",$filter["taskTypeID"]);
    }

    // Filter on taskID
    if ($filter["taskID"]) {     
      $sql[] = sprintf("(task.taskID = %d)", db_esc($filter["taskID"]));
    }
    // Filter on %taskName%
    if ($filter["taskName"]) {     
      $sql[] = sprintf("(task.taskName LIKE '%%%s%%')", db_esc($filter["taskName"]));
    }

    // If personID filter
    if ($filter["personID"] == "NULL") {
      $sql["personID"] = "(task.personID IS NULL)";
    } else if ($filter["personID"]) {
      $sql["personID"] = sprintf("(task.personID = %d)",$filter["personID"]);
    }

    // If creatorID filter
    if ($filter["creatorID"]) {
      $sql["creatorID"] = sprintf("(task.creatorID = %d)",$filter["creatorID"]);
    }
    // If managerID filter
    if ($filter["managerID"]) {
      $sql["managerID"] = sprintf("(task.managerID = %d)",$filter["managerID"]);
    }

    // These filters are for the time sheet dropdown list
    if ($filter["taskTimeSheetStatus"] == "open") {
      unset($sql["personID"]);
      $sql[] = sprintf("(task.taskStatus NOT IN (".$ts_closed."))");

    } else if ($filter["taskTimeSheetStatus"] == "not_assigned"){ 
      unset($sql["personID"]);
      $sql[] = sprintf("((task.taskStatus NOT IN (".$ts_closed.")) AND personID != %d)",$filter["personID"]);

    } else if ($filter["taskTimeSheetStatus"] == "recent_closed"){
      unset($sql["personID"]);
      $sql[] = sprintf("(task.dateActualCompletion >= DATE_SUB(CURDATE(),INTERVAL 14 DAY))");

    } else if ($filter["taskTimeSheetStatus"] == "all") {
    }

    $filter["parentTaskID"] and $sql["parentTaskID"] = sprintf("(parentTaskID = %d)",$filter["parentTaskID"]);
    return $sql;
  }

  function get_recursive_child_tasks($taskID_of_parent, $rows=array(), $padding=0) {
    $rtn = array();
    $rows or $rows = array();
    foreach($rows as $taskID => $v) {
      $parentTaskID = $v["parentTaskID"];
      $row = $v["row"];

      if ($taskID_of_parent == $parentTaskID) {
        $row["padding"] = $padding;
        $rtn[$taskID]["row"] = $row;
        unset($rows[$taskID]);
        $padding+=1;
        $children = task::get_recursive_child_tasks($taskID,$rows,$padding);
        $padding-=1;

        if (count($children)) {
          $rtn[$taskID]["children"] = $children;
        }
      }
    }
    return $rtn;
  }

  function build_recursive_task_list($t=array(),$_FORM=array()) {
    $tasks or $tasks = array();
    foreach ($t as $r) {
      $row = $r["row"];
      $done[$row["taskID"]] = true; // To track orphans
      $tasks += array($row["taskID"]=>$row);

      if ($r["children"]) {
        list($t,$d) = task::build_recursive_task_list($r["children"],$_FORM);
        $t and $tasks += $t;
        $d and $done += $d;
      }
    }
    return array($tasks,$done);
  }

  function get_list($_FORM) {
    global $current_user;

    /*
     * This is the definitive method of getting a list of tasks that need a sophisticated level of filtering
     *
     */
 
    $filter = task::get_list_filter($_FORM);

    $debug = $_FORM["debug"];
    $debug and print "\n<pre>_FORM: ".print_r($_FORM,1)."</pre>";
    $debug and print "\n<pre>filter: ".print_r($filter,1)."</pre>";

    // Zero is a valid limit
    if ($_FORM["limit"] || $_FORM["limit"] === 0 || $_FORM["limit"] === "0") {
      $limit = sprintf("limit %d",$_FORM["limit"]); 
    }<commit_msg>Make task::get_list() default to prioritised.<commit_after><?php

/*
 * Copyright (C) 2006-2011 <NAME>, <NAME>, Cyber IT Solutions
 * Pty. Ltd.
 * 
 * This file is part of the allocPSA application <<EMAIL>>.
 * 
 * allocPSA is free software: you can redistribute it and/or modify it
 * under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or (at
 * your option) any later version.
 * 
 * allocPSA is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
 * License for more details.
 * 
 * You should have received a copy of the GNU Affero General Public License
 * along with allocPSA. If not, see <http://www.gnu.org/licenses/>.
*/

define("PERM_PROJECT_READ_TASK_DETAIL", 256);

class task extends db_entity {
  public $classname = "task";
  public $data_table = "task";
  public $display_field_name = "taskName";
  public $key_field = "taskID";
  public $data_fields = array("taskName" => array("audit"=>true)
                             ,"taskDescription" => array("audit"=>true)
                             ,"creatorID"
                             ,"closerID"
                             ,"priority" => array("audit"=>true)
                             ,"timeLimit" => array("audit"=>true)
                             ,"timeBest" => array("audit"=>true)
                             ,"timeWorst" => array("audit"=>true)
                             ,"timeExpected" => array("audit"=>true)
                             ,"dateCreated"
                             ,"dateAssigned"
                             ,"dateClosed"
                             ,"dateTargetStart" => array("audit"=>true)
                             ,"dateTargetCompletion" => array("audit"=>true)
                             ,"dateActualStart" => array("audit"=>true)
                             ,"dateActualCompletion" => array("audit"=>true)
                             ,"taskComments"
                             ,"taskStatus" => array("audit"=>true)
                             ,"taskModifiedUser"
                             ,"projectID" => array("audit"=>true)
                             ,"parentTaskID" => array("audit"=>true)
                             ,"taskTypeID" => array("audit"=>true)
                             ,"personID" => array("audit"=>true)
                             ,"managerID" => array("audit"=>true)
                             ,"estimatorID" => array("audit"=>true)
                             ,"duplicateTaskID" => array("audit"=>true)
                             );
  public $permissions = array(PERM_PROJECT_READ_TASK_DETAIL => "read details");

  function save() {
    global $current_user, $TPL;

    // The data prior to the save
    $old = $this->all_row_fields;

    // Set the task creator
    $this->get_value("creatorID") || $this->set_value("creatorID",$current_user->get_id());

    // Set the task's status and sub-status
    list($taskStatus, $taskSubStatus) = explode("_",$this->get_value("taskStatus"));

    // Edge-case for people who open tasks with a dateActualCompletion set, i.e. simultaneous task create and close.
    if (!$this->get_id() && $this->get_value("dateActualCompletion")) {
      if ($taskStatus != "closed" && $taskStatus != "close") {
        $taskStatus = "close";
        $taskSubStatus = "complete";
      }
    }

    if (!$this->post_save_hook && in_array($taskStatus,array("closed","close","open","pending"))) {
      $this->$taskStatus($taskSubStatus);
    }

    // If a dateActualCompletion has just been entered
    if (!$this->post_save_hook && !$old["dateActualCompletion"] && $this->get_value("dateActualCompletion")) {
      $this->close("complete");

    // Else if there was a dateActualCompletion and they have just *unset* it...
    } else if (!$this->post_save_hook && $old["dateActualCompletion"] && !$this->get_value("dateActualCompletion")) {
      $this->open("inprogress");
    }

    // If they've just plugged a dateActualStart in and the task is notstarted, then change the status to Open: In Progress
    if (!$this->post_save_hook && !$old["dateActualStart"] && $this->get_value("dateActualStart") && $old["taskStatus"] == "open_notstarted") {
      $this->open("inprogress");
    }

    // If task exists and the personID has changed, update the dateAssigned
    if ($this->get_id()) {
      if (sprintf("%d",$this->get_value("personID")) != sprintf("%d",$old["personID"])) {
        $this->set_value("dateAssigned",date("Y-m-d H:i:s"));
      }
    // Else if task doesn't exist and there is a personID set, set the dateAssigned as well
    } else if ($this->get_value("personID")) {
      $this->set_value("dateAssigned",date("Y-m-d H:i:s"));
    }

    $this->get_value("taskDescription") and $this->set_value("taskDescription",rtrim($this->get_value("taskDescription")));


    // Copy the taskExpected over to the taskLimit if we're creating the task and taskLimit isn't set
    if (!$this->get_id() && !imp($this->get_value("timeLimit")) && imp($this->get_value("timeExpected"))) {
      $this->set_value("timeLimit",$this->get_value("timeExpected"));
    }

    // If we don't have a taskLimit try and inherit the project's defaultTaskLimit
    if (!$this->get_id() && !imp($this->get_value("timeLimit"))) {
      $project = $this->get_foreign_object("project");
      if ($project && imp($project->get_value("defaultTaskLimit"))) {
        $this->set_value("timeLimit",$project->get_value("defaultTaskLimit"));
      }
    }

    // If there are no estimates, then wipe the estimatorID person setting
    if (!imp($this->get_value("timeBest")) && !imp($this->get_value("timeWorst")) && !imp($this->get_value("timeExpected"))) {
      $this->set_value("estimatorID","");
    } else if (!$this->get_value("estimatorID")) {
      $this->set_value("estimatorID",$current_user->get_id());
    }

    $rtn = parent::save();

    // If the task has just been closed, opened, pending or duplicated, then audit the change.
    if ($this->post_save_hook) {
      $psh = $this->post_save_hook;
      $this->$psh();
    }

    return $rtn;
  }

  function delete() {
    if ($this->can_be_deleted()) {
      return parent::delete();
    }
  }

  function validate() {
    // Validate/coerce the fields
    $this->get_value("taskStatus") == "inprogress" && $this->set_value("taskStatus","open_inprogress");
    $this->get_value("taskStatus") == "notstarted" && $this->set_value("taskStatus","open_notstarted");
    $this->get_value("taskStatus") == "info"       && $this->set_value("taskStatus","pending_info");
    $this->get_value("taskStatus") == "client"     && $this->set_value("taskStatus","pending_client");
    $this->get_value("taskStatus") == "manager"    && $this->set_value("taskStatus","pending_manager");
    $this->get_value("taskStatus") == "invalid"    && $this->set_value("taskStatus","closed_invalid");
    $this->get_value("taskStatus") == "duplicate"  && $this->set_value("taskStatus","closed_duplicate");
    $this->get_value("taskStatus") == "incomplete" && $this->set_value("taskStatus","closed_incomplete");
    $this->get_value("taskStatus") == "complete"   && $this->set_value("taskStatus","closed_complete");
    $this->get_value("taskStatus") == "open"       && $this->set_value("taskStatus","open_inprogress");
    $this->get_value("taskStatus") == "pending"    && $this->set_value("taskStatus","pending_info");
    $this->get_value("taskStatus") == "close"      && $this->set_value("taskStatus","closed_complete");
    $this->get_value("taskStatus") == "closed"     && $this->set_value("taskStatus","closed_complete");
    $this->get_value("taskStatus") || $this->set_value("taskStatus","open_notstarted");

    $this->get_value("priority") || $this->set_value("priority",3);
    in_array($this->get_value("priority"),array(1,2,3,4,5)) or $err[] = "Invalid priority.";

    $this->get_value("taskTypeID") || $this->set_value("taskTypeID","Task");
    in_array(ucwords($this->get_value("taskTypeID")),array("Task","Fault","Message","Milestone","Parent")) or $err[] = "Invalid Task Type.";

    $this->get_value("taskName") or $err[] = "Please enter a name for the Task.";
    $this->get_value("dateCreated") || $this->set_value("dateCreated",date("Y-m-d H:i:s"));

    $this->get_value("personID") || $this->set_value("personID","");
    $this->get_value("managerID") || $this->set_value("managerID","");

    return parent::validate($err);
  }

  function closed($t="complete",$closeChildren=true) { return $this->close($t,$closeChildren); }  // wrapper
 
  function close($taskSubStatus = "complete",$closeChildren=true) {
    global $current_user;
    $old = $this->all_row_fields;
    $cur_status = $old["taskStatus"].$old["duplicateTaskID"];
    $new_status = "closed_".$taskSubStatus.$this->get_value("duplicateTaskID");

    if ($cur_status != $new_status) { 
      $this->post_save_hook = "mark_closed";
      $taskSubStatus == "duplicate" and $this->post_save_hook = "mark_dupe";
      $this->get_value("dateActualStart")      || $this->set_value("dateActualStart", date("Y-m-d"));
      $this->get_value("dateActualCompletion") || $this->set_value("dateActualCompletion", date("Y-m-d"));
      $this->get_value("closerID")             || $this->set_value("closerID", $current_user->get_id());
      $this->get_value("dateClosed")           || $this->set_value("dateClosed",date("Y-m-d H:i:s"));           
      $this->set_value("taskStatus","closed_".$taskSubStatus);
      if ($closeChildren) {
        $this->close_off_children_recursive($taskSubStatus);
      }
    }
  }

  function open($taskSubStatus = "inprogress") {
    $old = $this->all_row_fields;
    $cur_status = $old["taskStatus"];
    $new_status = "open_".$taskSubStatus;

    if ($cur_status != $new_status) { 
      if (!$this->get_value("taskStatus") || substr($this->get_value("taskStatus"),0,4)!="open") {
        $this->post_save_hook = "mark_reopened";
      }
      $this->set_value("closerID",null);
      $this->set_value("dateClosed","");
      $this->set_value("dateActualCompletion","");
      $this->set_value("duplicateTaskID","");
      $this->set_value("taskStatus","open_".$taskSubStatus);
      if (!$this->get_value("dateActualStart") && $taskSubStatus == "inprogress") {
        $this->set_value("dateActualStart",date("Y-m-d"));
      }
    }
  }

  function pending($taskSubStatus = "info") {
    $old = $this->all_row_fields;
    $cur_status = $old["taskStatus"];
    $new_status = "pending_".$taskSubStatus;

    if ($cur_status != $new_status) { 
      $this->post_save_hook = "mark_pending";
      $this->set_value("dateActualCompletion", "");
      $this->set_value("duplicateTaskID","");
      $this->set_value("closerID",null);
      $this->set_value("dateClosed","");
      $this->set_value("taskStatus","pending_".$taskSubStatus);
    }
  }

  function close_off_children_recursive($taskSubStatus='') {
    // mark all children as complete
    global $current_user;
    if ($this->get_id() && $this->get_value("taskTypeID") == "Parent") {
      $db = new db_alloc;
      $query = sprintf("SELECT * FROM task WHERE parentTaskID = %d",$this->get_id());
      $db->query($query);
                                                                                                                               
      while ($db->next_record()) {
        $task = new task;
        $task->read_db_record($db);
        $task->close($taskSubStatus);
        $task->save();
      }
    }
  }

  function create_task_reminder() {
    // Create a reminder for this task based on the priority.
    global $current_user;

    // Get the task type
    $taskTypeName = $this->get_value("taskTypeID");
    $label = $this->get_priority_label();
    $reminderInterval = "Day";
    $intervalValue = $this->get_value("priority");
    $taskTypeName == "Parent" and $taskTypeName.= " Task";

    $subject = $taskTypeName." Reminder: ".$this->get_id()." ".$this->get_name()." [".$label."]";
    $message = "\n\n".$subject;
    $message.= "\n\n".$this->get_url(true);
    $this->get_value("taskDescription") and $message.= "\n\n".$this->get_value("taskDescription");
    $message.= "\n\n-- \nReminder created by ".$current_user->get_name()." at ".date("Y-m-d H:i:s");
    $people[] = $this->get_value("personID");
    $this->create_reminder(null, $message, $reminderInterval, $intervalValue, REMINDER_METAPERSON_TASK_ASSIGNEE, $subject);
  }

  function create_reminders($people, $message, $reminderInterval, $intervalValue) {
    if (is_array($people)) {
      foreach($people as $personID) {
        $person = new person;
        $person->set_id($personID);
        $person->select();
        if ($person->get_value("emailAddress")) {
          $this->create_reminder($personID, $message, $reminderInterval, $intervalValue);
        }
      }
    }
  }

  function create_reminder($personID=null, $message, $reminderInterval, $intervalValue, $metaPerson=null, $subject="") {
    $label = $this->get_priority_label();

    $reminder = new reminder;
    $reminder->set_value('reminderType', "task");
    $reminder->set_value('reminderLinkID', $this->get_id());
    $reminder->set_value('reminderRecuringInterval', $reminderInterval);
    $reminder->set_value('reminderRecuringValue', $intervalValue);
    $reminder->set_value('reminderSubject', $subject);
    $reminder->set_value('reminderContent', $message);

    $reminder->set_value('reminderAdvNoticeSent', "0");
    $reminder->set_value('reminderAdvNoticeInterval', "No");
    $reminder->set_value('reminderAdvNoticeValue', "0");

    $reminder->set_value('reminderTime', date("Y-m-d H:i:s"));
    if ($personID) {
      $reminder->set_value('personID', $personID);
    } else if ($metaPerson) {
      $reminder->set_value('metaPerson', $metaPerson);
    }
    $reminder->save();
  }

  function is_owner($person = "") {

    if (!is_object($person)) {
      return false;
    }

    // A user owns a task if they 'own' the project
    if ($this->get_id()) {
      // Check for existing task
      $p = $this->get_foreign_object("project");
    } else if ($_POST["projectID"]) {
      // Or maybe they are creating a new task
      $p = new project;
      $p->set_id($_POST["projectID"]);
    }

    // if this task doesn't exist (no ID) 
    // OR the person has isManager or canEditTasks for this tasks project 
    // OR if this person is the Creator of this task.
    // OR if this person is the For Person of this task.
    // OR if this person has super 'manage' perms
    // OR if we're skipping the perms checking because i.e. we're having our task status updated by a timesheet
    if (
       !$this->get_id() 
    || (is_object($p) && ($p->has_project_permission($person, array("isManager", "canEditTasks"))) 
    || $this->get_value("creatorID") == $person->get_id()
    || $this->get_value("personID") == $person->get_id()
    || $person->have_role("manage")
    || $this->skip_perms_check
    )) {
      return true;
    }
  }

  function has_attachment_permission($person) {
    return $this->is_owner($person);
  }

  function has_attachment_permission_delete($person) {
    return $this->is_owner($person);
  }

  function update_children($field,$value="") {
    $q = sprintf("SELECT * FROM task WHERE parentTaskID = %d",$this->get_id());
    $db = new db_alloc();
    $db->query($q);
    while ($db->row()) {
      $t = new task;
      $t->read_db_record($db);
      $t->set_value($field,$value);
      $t->save();
      if ($t->get_value("taskTypeID") == "Parent") {
        $t->update_children($field,$value);
      }
    }
  }

  function get_parent_task_select($projectID="") {
    global $TPL;
    
    if (is_object($this)) {
      $projectID = $this->get_value("projectID");
      $parentTaskID = $this->get_value("parentTaskID");
    }

    $projectID or $projectID = $_GET["projectID"];
    $parentTaskID or $parentTaskID = $_GET["parentTaskID"];

    $db = new db_alloc;
    if ($projectID) {
      list($ts_open,$ts_pending,$ts_closed) = task::get_task_status_in_set_sql();
      // Status may be closed_<something>
      $query = sprintf("SELECT taskID AS value, taskName AS label
                        FROM task 
                        WHERE projectID= '%d' 
                        AND taskTypeID = 'Parent'
                        AND (taskStatus NOT IN (".$ts_closed.") OR taskID = %d)
                        ORDER BY taskName", $projectID, $parentTaskID);
      $options = page::select_options($query, $parentTaskID,70);
    }
    return "<select name=\"parentTaskID\"><option value=\"\">".$options."</select>";
  }

  function get_task_cc_list_select($projectID="") {
    $interestedParty = array();
    $interestedPartyOptions = array();
    
    if (is_object($this)) {
      $interestedPartyOptions = $this->get_all_parties($projectID);
    } else {
      $interestedPartyOptions = task::get_all_parties($projectID);
    }

    #echo "<pre>".print_r($interestedPartyOptions,1)."</pre>";
  
    if (is_array($interestedPartyOptions)) {

      foreach ($interestedPartyOptions as $email => $info) {
        $name = $info["name"];
        $identifier = $info["identifier"];

        if ($info["role"] == "interested" && $info["selected"]) {
          $interestedParty[] = $identifier;
        }

        if ($email) {
          $name = trim($name);
          $str = trim(page::htmlentities($name." <".$email.">"));
          $options[$identifier] = $str;
        }
      }
    }
    $str = "<select name=\"interestedParty[]\" multiple=\"true\">".page::select_options($options,$interestedParty,100,false)."</select>";
    return $str;
  }

  function get_all_parties($projectID="") {
    $db = new db_alloc;
    $interestedPartyOptions = array();
  
    if ($_GET["projectID"]) {
      $projectID = $_GET["projectID"];
    } else if (!$projectID && is_object($this)) {
      $projectID = $this->get_value("projectID");
    }

    if ($projectID) {
      $interestedPartyOptions = project::get_all_parties($projectID);
    }

    $extra_interested_parties = config::get_config_item("defaultInterestedParties") or $extra_interested_parties=array();
    foreach ($extra_interested_parties as $name => $email) {
      $interestedPartyOptions[$email] = array("name"=>$name);
    }

    if (is_object($this)) {
      if ($this->get_value("creatorID")) {
        $p = new person;
        $p->set_id($this->get_value("creatorID"));
        $p->select();
        $p->get_value("emailAddress") and $interestedPartyOptions[$p->get_value("emailAddress")] = array("name"=>$p->get_name(), "role"=>"creator", "personID"=>$this->get_value("creatorID"));
      }
      if ($this->get_value("personID")) {
        $p = new person;
        $p->set_id($this->get_value("personID"));
        $p->select();
        $p->get_value("emailAddress") and $interestedPartyOptions[$p->get_value("emailAddress")] = array("name"=>$p->get_name(), "role"=>"assignee", "selected"=>true, "personID"=>$this->get_value("personID"));
      }
      if ($this->get_value("managerID")) {
        $p = new person;
        $p->set_id($this->get_value("managerID"));
        $p->select();
        $p->get_value("emailAddress") and $interestedPartyOptions[$p->get_value("emailAddress")] = array("name"=>$p->get_name(), "role"=>"manager", "selected"=>true, "personID"=>$this->get_value("managerID"));
      }
      $this_id = $this->get_id();
    }
    // return an aggregation of the current task/proj/client parties + the existing interested parties
    $interestedPartyOptions = interestedParty::get_interested_parties("task",$this_id,$interestedPartyOptions);
    return $interestedPartyOptions;
  }

  function get_encoded_interested_party_identifier($info=array()) {
    return urlencode(base64_encode(serialize($info)));
  }

  function get_decoded_interested_party_identifier($blob) {
    return unserialize(base64_decode(urldecode($blob)));
  }

  function get_personList_dropdown($projectID,$field,$taskID=false) {
    global $current_user;
 
    $db = new db_alloc;

    if ($_GET["timeSheetID"]) {
      $ts_query = sprintf("SELECT * FROM timeSheet WHERE timeSheetID = %d",$_GET["timeSheetID"]);
      $db->query($ts_query);
      $db->next_record();
      $owner = $db->f("personID");

    } else if (is_object($this) && $this->get_value($field)) {
      $owner = $this->get_value($field);

    } else if ($taskID) {
      $t = new task;
      $t->set_id($taskID);
      $t->select();
      $owner = $t->get_value($field);

    } else if (!is_object($this) || !$this->get_id()) {
      $owner = $current_user->get_id();
    }

    $peoplenames = person::get_username_list($owner);

    if ($projectID) {
      $q = sprintf("SELECT * 
                      FROM projectPerson 
                 LEFT JOIN person ON person.personID = projectPerson.personID 
                     WHERE person.personActive = 1 
                       AND projectID = %d
                  ORDER BY firstName, username
                   ",$projectID);
      $db->query($q);
      while ($row = $db->row()) {
        $ops[$row["personID"]] = $peoplenames[$row["personID"]];
      }
    } else {
      $ops = $peoplenames;
    }

    $ops[$owner] or $ops[$owner] = $peoplenames[$owner];
   
    $str = page::select_options($ops, $owner);
    return $str;
  }

  function get_project_options($projectID="") {
    $projectID or $projectID = $_GET["projectID"];
    // Project Options - Select all projects 
    $db = new db_alloc;
    $query = sprintf("SELECT projectID AS value, projectName AS label 
                        FROM project 
                       WHERE projectStatus IN ('Current', 'Potential') OR projectID = %d
                    ORDER BY projectName",$projectID);
    $str = page::select_options($query, $projectID, 60);
    return $str;
  }

  function set_option_tpl_values() {
    // Set template values to provide options for edit selects
    global $TPL, $current_user, $isMessage;
    $db = new db_alloc;
    $projectID = $_GET["projectID"] or $projectID = $this->get_value("projectID");
    $TPL["personOptions"] = "<select name=\"personID\"><option value=\"\">".task::get_personList_dropdown($projectID, "personID")."</select>";
    $TPL["managerPersonOptions"] = "<select name=\"managerID\"><option value=\"\">".task::get_personList_dropdown($projectID, "managerID")."</select>";
    $TPL["estimatorPersonOptions"] = "<select name=\"estimatorID\"><option value=\"\">".task::get_personList_dropdown($projectID, "estimatorID")."</select>";

    // TaskType Options
    $taskType = new meta("taskType");
    $taskType_array = $taskType->get_assoc_array("taskTypeID","taskTypeID");
    $TPL["taskTypeOptions"] = page::select_options($taskType_array,$this->get_value("taskTypeID"));

    // Project dropdown
    $TPL["projectOptions"] = task::get_project_options($projectID);
    
    $priority = $this->get_value("priority") or $priority = 3;
    $taskPriorities = config::get_config_item("taskPriorities") or $taskPriorities = array();
    foreach ($taskPriorities as $k => $v) {
      $tp[$k] = $v["label"];
    }
    $TPL["priorityOptions"] = page::select_options($tp,$priority);
    $priority and $TPL["priorityLabel"] = " <div style=\"display:inline; color:".$taskPriorities[$priority]["colour"]."\">[".$this->get_priority_label()."]</div>";

    // We're building these two with the <select> tags because they will be
    // replaced by an AJAX created dropdown when the projectID changes.
    $TPL["parentTaskOptions"] = $this->get_parent_task_select();
    $TPL["interestedPartyOptions"] = $this->get_task_cc_list_select();

    $db->query(sprintf("SELECT fullName,emailAddress
                          FROM interestedParty
                         WHERE entity='task' 
                           AND entityID = %d
                           AND interestedPartyActive = 1
                      ORDER BY fullName",$this->get_id()));
    while ($db->next_record()) {
      $str = trim(page::htmlentities($db->f("fullName")." <".$db->f("emailAddress").">"));
      $value = interestedParty::get_encoded_interested_party_identifier($db->f("fullName"), $db->f("emailAddress"));
      $TPL["interestedParty_hidden"].= $commar.$str."<input type=\"hidden\" name=\"interestedParty[]\" value=\"".$value."\">";
      $TPL["interestedParty_text"].= $commar.$str;
      $commar = "<br>";
    }

    $TPL["task_taskStatusLabel"] = $this->get_task_status("label");
    $TPL["task_taskStatusColour"] = $this->get_task_status("colour");
    $TPL["task_taskStatusValue"] = $this->get_value("taskStatus");
    $TPL["task_taskStatusOptions"] = page::select_options($this->get_task_statii_array(true),$this->get_value("taskStatus"));

    // If we're viewing the printer friendly view
    if ($_GET["media"] == "print") {
      // Parent Task label
      $t = new task;
      $t->set_id($this->get_value("parentTaskID"));
      $t->select();
      $TPL["parentTask"] = $t->get_display_value();

      // Task Type label
      $TPL["taskType"] = $this->get_value("taskTypeID"); 

      // Priority
      $TPL["priority"] = $this->get_value("priority");

      // Assignee label
      $p = new person;
      $p->set_id($this->get_value("personID"));
      $p->select();
      $TPL["person"] = $p->get_display_value();
  
      // Project label
      $p = new project;
      $p->set_id($this->get_value("projectID"));
      $p->select();
      $TPL["projectName"] = $p->get_display_value();
    }

  }

  function get_task_comments_array() {
    $rows = comment::util_get_comments_array("task",$this->get_id());
    $rows or $rows = array();
    return $rows;
  }

  function get_task_link($_FORM=array()) {
    $_FORM["return"] or $_FORM["return"] = "html";
    $rtn = "<a href=\"".$this->get_url()."\">";
    $rtn.= $this->get_name($_FORM);
    $rtn.= "</a>";
    return $rtn;
  }

  function get_task_image() {
    global $TPL;
    return "<img class=\"taskType\" alt=\"".$this->get_value("taskTypeID")."\" title=\"".$this->get_value("taskTypeID")."\" src=\"".$TPL["url_alloc_images"]."taskType_".$this->get_value("taskTypeID").".gif\">";
  }

  function get_name($_FORM=array()) {

    $_FORM["prefixTaskID"] and $id = $this->get_id()." ";

    if ($this->get_value("taskTypeID") == "Parent" && $_FORM["return"] == "html") {
      $rtn = "<strong>".$id.$this->get_value("taskName",DST_HTML_DISPLAY)."</strong>";
    } else if ($_FORM["return"] == "html") {
      $rtn = $id.$this->get_value("taskName",DST_HTML_DISPLAY);
    } else {
      $rtn = $id.$this->get_value("taskName");
    }
    return $rtn;
  }

  function get_url($absolute=false) {
    global $sess;
    $sess or $sess = new Session;

    $url = "task/task.php?taskID=".$this->get_id();

    if ($sess->Started() && !$absolute) {
      $url = $sess->url(SCRIPT_PATH.$url);

    // This for urls that are emailed
    } else {
      static $prefix;
      $prefix or $prefix = config::get_config_item("allocURL");
      $url = $prefix.$url;
    }
    return $url;
  }

  function get_task_statii_array($flat=false) {
    // This gets an array that is useful for building the two types of dropdown lists that taskStatus uses
    $taskStatii = task::get_task_statii();
    if ($flat) {
      $m = new meta("taskStatus");
      $taskStatii = $m->get_assoc_array();
      foreach ($taskStatii as $status => $arr) {
        $taskStatiiArray[$status] = task::get_task_status_thing("label",$status);
      }
    } else {
      $taskStatiiArray[""] = ""; // blank entry
      foreach ($taskStatii as $status => $sub) {
        $taskStatiiArray[$status] = ucwords($status);
        foreach ($sub as $subStatus => $arr) {
          $taskStatiiArray[$status."_".$subStatus] = "&nbsp;&nbsp;&nbsp;&nbsp;".$arr["label"];
        }
      }
    } 

    return $taskStatiiArray;
  }

  function get_task_statii() {
    // looks like:
    //$arr["open"]["notstarted"] = array("label"=>"Not Started","colour"=>"#ffffff");
    //$arr["open"]["inprogress"] = array("label"=>"In Progress","colour"=>"#ffffff");
    //etc
    static $rows;
    if (!$rows) {
      $m = new meta("taskStatus");
      $rows = $m->get_assoc_array();
    }
    foreach ($rows as $taskStatusID => $arr) {
      list($s,$ss) = explode("_",$taskStatusID);
      $rtn[$s][$ss] = array("label"=>$arr["taskStatusLabel"],"colour"=>$arr["taskStatusColour"]);
    }
    return $rtn;
  }

  function get_task_status($thing="") {
    return task::get_task_status_thing($thing,$this->get_value("taskStatus"));
  }

  function get_task_status_thing($thing="",$status="") {
    list($taskStatus,$taskSubStatus) = explode("_",$status);
    $arr = task::get_task_statii();
    if ($thing && $arr[$taskStatus][$taskSubStatus][$thing]) {
      return $arr[$taskStatus][$taskSubStatus][$thing];
    }
  }

  function get_task_status_in_set_sql() {
    $m = new meta("taskStatus");
    $arr = $m->get_assoc_array();
    foreach ($arr as $taskStatusID => $r) {
      $id = strtolower(substr($taskStatusID,0,4));
      if ($id == "open") {
        $sql_open.= $commar1.'"'.$taskStatusID.'"';
        $commar1 = ",";
      } else if ($id == "clos") {
        $sql_clos.= $commar2.'"'.$taskStatusID.'"';
        $commar2 = ",";
      } else if ($id == "pend") {
        $sql_pend.= $commar3.'"'.$taskStatusID.'"';
        $commar3 = ",";
      }
    }
    return array($sql_open,$sql_pend,$sql_clos);
  }

  function get_taskStatus_sql($s) {
    if ($s) {
      if (is_array($s)) {
        $taskStatusArray = $s;
      } else {
        $taskStatusArray[] = $s;
      }
      $subsql = array();
      foreach ($taskStatusArray as $status) {
        list($taskStatus,$taskSubStatus) = explode("_",$status);
        if($taskSubStatus) {
          $subsql[] = sprintf("(task.taskStatus = '%s')",db_esc($status));
        } else {
          $subsql[] = sprintf("(SUBSTRING(task.taskStatus,1,%d) = '%s')",strlen($status),db_esc($status));
        }
      }
      return '('.implode(" OR ",$subsql).')';
    }
  }

  function get_list_filter($filter=array()) {

    // This takes care of projectID singular and plural
    $projectIDs = project::get_projectID_sql($filter);
    $projectIDs and $sql["projectIDs"] = $projectIDs;

    list($ts_open,$ts_pending,$ts_closed) = task::get_task_status_in_set_sql();

    // New Tasks
    if ($filter["taskDate"] == "new") {
      $past = date("Y-m-d", mktime(0, 0, 0, date("m"), date("d") - 2, date("Y")))." 00:00:00";
      date("D") == "Mon" and $past = date("Y-m-d", mktime(0, 0, 0, date("m"), date("d") - 4, date("Y")))." 00:00:00";
      $sql[] = sprintf("(task.taskStatus NOT IN (".$ts_closed.") AND task.dateCreated >= '".$past."')");

    // Due Today
    } else if ($filter["taskDate"] == "due_today") {
      $sql[] = "(task.taskStatus NOT IN (".$ts_closed.") AND task.dateTargetCompletion = '".date("Y-m-d")."')";

    // Overdue
    } else if ($filter["taskDate"] == "overdue") {
      $sql[] = "(task.taskStatus NOT IN (".$ts_closed.")
                AND 
                (task.dateTargetCompletion IS NOT NULL AND task.dateTargetCompletion != '' AND '".date("Y-m-d")."' > task.dateTargetCompletion))";
  
    // Date Created
    } else if ($filter["taskDate"] == "d_created") {
      $filter["dateOne"] and $sql[] = sprintf("(task.dateCreated >= '%s')",db_esc($filter["dateOne"]));
      $filter["dateTwo"] and $sql[] = sprintf("(task.dateCreated <= '%s 23:59:59')",db_esc($filter["dateTwo"]));

    // Date Assigned
    } else if ($filter["taskDate"] == "d_assigned") {
      $filter["dateOne"] and $sql[] = sprintf("(task.dateAssigned >= '%s')",db_esc($filter["dateOne"]));
      $filter["dateTwo"] and $sql[] = sprintf("(task.dateAssigned <= '%s 23:59:59')",db_esc($filter["dateTwo"]));

    // Date Target Start
    } else if ($filter["taskDate"] == "d_targetStart") {
      $filter["dateOne"] and $sql[] = sprintf("(task.dateTargetStart >= '%s')",db_esc($filter["dateOne"]));
      $filter["dateTwo"] and $sql[] = sprintf("(task.dateTargetStart <= '%s')",db_esc($filter["dateTwo"]));

    // Date Target Completion
    } else if ($filter["taskDate"] == "d_targetCompletion") {
      $filter["dateOne"] and $sql[] = sprintf("(task.dateTargetCompletion >= '%s')",db_esc($filter["dateOne"]));
      $filter["dateTwo"] and $sql[] = sprintf("(task.dateTargetCompletion <= '%s')",db_esc($filter["dateTwo"]));

    // Date Actual Start
    } else if ($filter["taskDate"] == "d_actualStart") {
      $filter["dateOne"] and $sql[] = sprintf("(task.dateActualStart >= '%s')",db_esc($filter["dateOne"]));
      $filter["dateTwo"] and $sql[] = sprintf("(task.dateActualStart <= '%s')",db_esc($filter["dateTwo"]));

    // Date Actual Completion
    } else if ($filter["taskDate"] == "d_actualCompletion") {
      $filter["dateOne"] and $sql[] = sprintf("(task.dateActualCompletion >= '%s')",db_esc($filter["dateOne"]));
      $filter["dateTwo"] and $sql[] = sprintf("(task.dateActualCompletion <= '%s')",db_esc($filter["dateTwo"]));
    }

    // Task status filtering
    $filter["taskStatus"] and $sql[] = task::get_taskStatus_sql($filter["taskStatus"]);

    // Unset if they've only selected the topmost empty task type
    if (is_array($filter["taskTypeID"]) && count($filter["taskTypeID"])>=1 && !$filter["taskTypeID"][0]) {
      unset($filter["taskTypeID"][0]);
    }

    // If many create an SQL taskTypeID in (set) 
    if (is_array($filter["taskTypeID"]) && count($filter["taskTypeID"])) {
      $sql[] = "(task.taskTypeID in ('".implode("','",$filter["taskTypeID"])."'))";
    
    // Else if only one taskTypeID
    } else if ($filter["taskTypeID"]) {
      $sql[] = sprintf("(task.taskTypeID = '%s')",$filter["taskTypeID"]);
    }

    // Filter on taskID
    if ($filter["taskID"]) {     
      $sql[] = sprintf("(task.taskID = %d)", db_esc($filter["taskID"]));
    }
    // Filter on %taskName%
    if ($filter["taskName"]) {     
      $sql[] = sprintf("(task.taskName LIKE '%%%s%%')", db_esc($filter["taskName"]));
    }

    // If personID filter
    if ($filter["personID"] == "NULL") {
      $sql["personID"] = "(task.personID IS NULL)";
    } else if ($filter["personID"]) {
      $sql["personID"] = sprintf("(task.personID = %d)",$filter["personID"]);
    }

    // If creatorID filter
    if ($filter["creatorID"]) {
      $sql["creatorID"] = sprintf("(task.creatorID = %d)",$filter["creatorID"]);
    }
    // If managerID filter
    if ($filter["managerID"]) {
      $sql["managerID"] = sprintf("(task.managerID = %d)",$filter["managerID"]);
    }

    // These filters are for the time sheet dropdown list
    if ($filter["taskTimeSheetStatus"] == "open") {
      unset($sql["personID"]);
      $sql[] = sprintf("(task.taskStatus NOT IN (".$ts_closed."))");

    } else if ($filter["taskTimeSheetStatus"] == "not_assigned"){ 
      unset($sql["personID"]);
      $sql[] = sprintf("((task.taskStatus NOT IN (".$ts_closed.")) AND personID != %d)",$filter["personID"]);

    } else if ($filter["taskTimeSheetStatus"] == "recent_closed"){
      unset($sql["personID"]);
      $sql[] = sprintf("(task.dateActualCompletion >= DATE_SUB(CURDATE(),INTERVAL 14 DAY))");

    } else if ($filter["taskTimeSheetStatus"] == "all") {
    }

    $filter["parentTaskID"] and $sql["parentTaskID"] = sprintf("(parentTaskID = %d)",$filter["parentTaskID"]);
    return $sql;
  }

  function get_recursive_child_tasks($taskID_of_parent, $rows=array(), $padding=0) {
    $rtn = array();
    $rows or $rows = array();
    foreach($rows as $taskID => $v) {
      $parentTaskID = $v["parentTaskID"];
      $row = $v["row"];

      if ($taskID_of_parent == $parentTaskID) {
        $row["padding"] = $padding;
        $rtn[$taskID]["row"] = $row;
        unset($rows[$taskID]);
        $padding+=1;
        $children = task::get_recursive_child_tasks($taskID,$rows,$padding);
        $padding-=1;

        if (count($children)) {
          $rtn[$taskID]["children"] = $children;
        }
      }
    }
    return $rtn;
  }

  function build_recursive_task_list($t=array(),$_FORM=array()) {
    $tasks or $tasks = array();
    foreach ($t as $r) {
      $row = $r["row"];
      $done[$row["taskID"]] = true; // To track orphans
      $tasks += array($row["taskID"]=>$row);

      if ($r["children"]) {
        list($t,$d) = task::build_recursive_task_list($r["children"],$_FORM);
        $t and $tasks += $t;
        $d and $done += $d;
      }
    }
    return array($tasks,$done);
  }

  function get_list($_FORM) {
    global $current_user;

    /*
     * This is the definitive method of getting a list of tasks that need a sophisticated level of filtering
     *
     */
 
    $filter = task::get_list_filter($_FORM);

    $debug = $_FORM["debug"];
    $debug and print "\n<pre>_FORM: ".print_r($_FORM,1)."</pre>";
    $debug and print "\n<pre>filter: ".print_r($filter,1)."</pre>";

    $_FORM["taskView"] or $_FORM["taskView"] = 'prioritised';

    // Zero is a valid limit
    if ($_FORM["limit"] || $_FORM["limit"] === 0 || $_FORM["limit"] === "0") {
      $limit = sprintf("limit %d",$_FORM["limit"]); 
    }
{'fraction_non_alphanumeric': 0.19195676491596922, 'fraction_numerical': 0.0024259087049690697, 'mean_word_length': 3.2346764068028766, 'pattern_counts': {'":': 0, '<': 93, '<?xml version=': 0, '>': 781, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 10, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3999594', 'n_tokens_mistral': 24277, 'n_tokens_neox': 23696, 'n_words': 4338}