/** * @param \Base $f3 * @throws \Exception */ public function edit($f3) { $post = $f3->get("POST"); $issue = new \Model\Issue(); $issue->load($post["itemId"]); $issue->sprint_id = empty($post["reciever"]["receiverId"]) ? null : $post["reciever"]["receiverId"]; $issue->save(); $this->_printJson($issue); }
public function recent_comments() { $f3 = \Base::instance(); $issue = new \Model\Issue(); $ownerString = implode(",", $this->getOwnerIds()); $issues = $issue->find(array("owner_id IN ({$ownerString}) OR author_id = ?", $f3->get("user.id"))); $ids = array(); foreach ($issues as $item) { $ids[] = $item->id; } $comment = new \Model\Issue\Comment\Detail(); $issueIds = implode(",", $ids); return $comment->find(array("issue_id IN ({$issueIds}) AND user_id != ?", $f3->get("user.id")), array("order" => "created_date DESC", "limit" => 15)); }
/** * Reassign assigned issues * @param int $user_id * @return int Number of issues affected */ public function reassignIssues($user_id) { if (!$this->id) { throw new \Exception("User is not initialized."); } $issue_model = new \Model\Issue(); $issues = $issue_model->find(array("owner_id = ? AND deleted_date IS NULL AND closed_date IS NULL", $this->id)); foreach ($issues as $issue) { $issue->owner_id = $user_id; $issue->save(); } return count($issues); }
/** * Send an email to watchers with the file info * @param int $issue_id * @param int $file_id */ public function issue_file($issue_id, $file_id) { $f3 = \Base::instance(); if ($f3->get("mail.from")) { $log = new \Log("mail.log"); // Get issue and comment data $issue = new \Model\Issue(); $issue->load($issue_id); $file = new \Model\Issue\File\Detail(); $file->load($file_id); // This should catch a bug I can't currently find the source of. --Alan if ($file->issue_id != $issue->id) { return; } // Get issue parent if set if ($issue->parent_id) { $parent = new \Model\Issue(); $parent->load($issue->parent_id); $f3->set("parent", $parent); } // Get recipient list and remove current user $recipients = $this->_issue_watchers($issue_id); $recipients = array_diff($recipients, array($file->user_email)); // Render message body $f3->set("issue", $issue); $f3->set("file", $file); $text = $this->_render("notification/file.txt"); $body = $this->_render("notification/file.html"); $subject = "[#{$issue->id}] - {$file->user_name} attached a file to {$issue->name}"; // Send to recipients foreach ($recipients as $recipient) { $this->utf8mail($recipient, $subject, $body, $text); $log->write("Sent file notification to: " . $recipient); } } }
/** * Update an existing task */ public function edit($f3, $params) { $post = $f3->get("POST"); $issue = new \Model\Issue(); $issue->load($post["taskId"]); if (!empty($post["receiver"])) { if ($post["receiver"]["story"]) { $issue->parent_id = $post["receiver"]["story"]; } $issue->status = $post["receiver"]["status"]; $status = new \Model\Issue\Status(); $status->load($issue->status); if ($status->closed) { if (!$issue->closed_date) { $issue->closed_date = $this->now(); } } else { $issue->closed_date = null; } } else { $issue->name = $post["title"]; $issue->description = $post["description"]; $issue->owner_id = $post["assigned"]; $issue->hours_remaining = $post["hours"]; $issue->hours_spent += $post["hours_spent"]; if (!empty($post["hours_spent"]) && !empty($post["burndown"])) { $issue->hours_remaining -= $post["hours_spent"]; } if ($issue->hours_remaining < 0) { $issue->hours_remaining = 0; } if (!empty($post["dueDate"])) { $issue->due_date = date("Y-m-d", strtotime($post["dueDate"])); } else { $issue->due_date = null; } if (!empty($post["repeat_cycle"])) { $issue->repeat_cycle = $post["repeat_cycle"]; } $issue->priority = $post["priority"]; if (!empty($post["storyId"])) { $issue->parent_id = $post["storyId"]; } $issue->title = $post["title"]; } if (!empty($post["comment"])) { $comment = new \Model\Issue\Comment(); $comment->user_id = $this->_userId; $comment->issue_id = $issue->id; if (!empty($post["hours_spent"])) { $comment->text = trim($post["comment"]) . sprintf(" (%s %s spent)", $post["hours_spent"], $post["hours_spent"] == 1 ? "hour" : "hours"); } else { $comment->text = $post["comment"]; } $comment->created_date = $this->now(); $comment->save(); $issue->update_comment = $comment->id; } $issue->save(); $this->_printJson($issue->cast() + array("taskId" => $issue->id)); }
} $to_user = new \Model\User(); foreach ($header->to as $to_email) { $to = $to_email->mailbox . "@" . $to_email->host; $to_user->load(array('email = ? AND deleted_date IS NULL', $to)); if (!empty($to_user->id)) { $owner = $to_user->id; break; } else { $owner = $from_user->id; } } // Find issue IDs in subject preg_match("/\\[#([0-9]+)\\] -/", $header->subject, $matches); // Get issue instance $issue = new \Model\Issue(); if (!empty($matches[1])) { $issue->load(intval($matches[1])); } if (!$issue->id) { $subject = trim(preg_replace("/^((Re|Fwd?):\\s)*/i", "", $header->subject)); $issue->load(array('name=? AND deleted_date IS NULL AND closed_date IS NULL', $subject)); } if ($issue->id) { if (trim($text)) { $comment = \Model\Issue\Comment::create(array('user_id' => $from_user->id, 'issue_id' => $issue->id, 'text' => $text)); $log->write(sprintf("Added comment %s on issue #%s - %s", $comment->id, $issue->id, $issue->name)); } } else { $issue = \Model\Issue::create(array('name' => $header->subject, 'description' => $text, 'author_id' => $from_user->id, 'owner_id' => $owner, 'status' => 1, 'type_id' => 1)); $log->write(sprintf("Created issue #%s - %s", $issue->id, $issue->name));
<?php /** * Tests the Issue model's core functionality * @package Test * @author Alan Hardman <*****@*****.**> */ require_once "base.php"; $test = new Test(); $issue = new Model\Issue(); $test->expect($issue->load(1) && $issue->id == 1, "Issue->load() by Integer"); $test->expect($issue->load(array('id = ?', 1)) && $issue->id == 1, "Issue->load() by String"); $test->expect(is_array($issue->getChildren()), "Issue->getChildren()"); $test->expect(is_array($issue->getAncestors()), "Issue->getAncestors()"); $test->expect($issue->save(false) && $issue->id, "Issue->save() without notifications"); // Output results showResults($test);
if (!empty($user->id)) { $author = $user->id; // find an owner from the recipients foreach ($header->to as $owner_email) { $user->reset(); $to = $owner_email->mailbox . "@" . $owner_email->host; $user->load(array('email=?', $to)); if (!empty($user->id)) { $owner = $user->id; break; } else { $owner = $author; } } preg_match("/\\[#([0-9]+)\\] -/", $header->subject, $matches); $issue = new \Model\Issue(); !empty($matches[1]) ? $issue->load($matches[1]) : ''; // post a comment if replying to an issue if (!empty($issue->id)) { if (!empty($message)) { $comment = new \Model\Issue\Comment(); $comment->user_id = $author; $comment->issue_id = $issue->id; $comment->text = html_entity_decode(strip_tags($message)); $comment->created_date = date("Y-m-d H:i:s"); $comment->save(); $notification = \Helper\Notification::instance(); $notification->issue_comment($issue->id, $comment->id); } } else { if (!empty($header->subject)) {
/** * GET /issues/parent_ajax * Load all matching issues * * @param \Base $f3 */ public function parent_ajax($f3) { if (!$f3->get("AJAX")) { $f3->error(400); } $term = trim($f3->get('GET.q')); $results = array(); $issue = new \Model\Issue(); if (substr($term, 0, 1) == '#' && is_numeric(substr($term, 1))) { $id = (int) substr($term, 1); $issues = $issue->find(array('id LIKE ?', $id . '%'), array('limit' => 20)); foreach ($issues as $row) { $results[] = array('id' => $row->get('id'), 'text' => $row->get('name')); } } elseif (is_numeric($term)) { $id = (int) $term; $issues = $issue->find(array('(id LIKE ?) OR (name LIKE ?)', $id . '%', '%' . $id . '%'), array('limit' => 20)); foreach ($issues as $row) { $results[] = array('id' => $row->get('id'), 'text' => $row->get('name')); } } else { $issues = $issue->find(array('name LIKE ?', '%' . addslashes($term) . '%'), array('limit' => 20)); foreach ($issues as $row) { $results[] = array('id' => $row->get('id'), 'text' => $row->get('name')); } } $this->_printJson(array('results' => $results)); }
$start = strtotime($sprint->start_date); $end = strtotime($sprint->end_date); $tasks_for_this_sprint = array(); // Find tasks that fit into this sprint foreach ($tasks as $task) { echo "Using task {$task->id}\n"; $due = strtotime($task->due_date); if ($due >= $start && $due <= $end) { echo "Task marked for move {$task->id}\n"; $tasks_for_this_sprint[] = $task; } } // Create sprint project if (count($tasks_for_this_sprint)) { echo "Creating project for sprint {$sprint->id}\n"; $sprint_project = new \Model\Issue(); $sprint_project->type_id = $issue_type_project; $sprint_project->parent_id = $project->id; $sprint_project->sprint_id = $sprint->id; $sprint_project->author_id = $project->author_id; $sprint_project->owner_id = $project->owner_id; if ($sprint->name) { $sprint_project->name = $project->name . " - " . $sprint->name . " - " . date("n/j", strtotime($sprint->start_date)) . "-" . date("n/j", strtotime($sprint->start_date)); } else { $sprint_project->name = $project->name . " - " . date("n/j", strtotime($sprint->start_date)) . "-" . date("n/j", strtotime($sprint->start_date)); } $sprint_project->description = "This is an automatically generated project for breaking large projects into sprints."; $sprint_project->created_date = date("Y-m-d H:i:s"); $sprint_project->save(); // Move tasks into sprint project foreach ($tasks_for_this_sprint as $task) {
public function single_comments_post($f3, $params) { $issue = new \Model\Issue(); $issue->load($params["id"]); if (!$issue->id) { $f3->error(404); return; } $data = array("issue_id" => $issue->id, "user_id" => $this->_userId, "text" => $f3->get("POST.text")); $comment = \Model\Issue\Comment::create($data); $this->_printJson($comment->cast()); }
/** * Convert an issue ID to a name * @param int $id * @return string */ public function convertIssueId($id) { if (isset($this->cache['issue.' . $id])) { $issue = $this->cache['issue.' . $id]; } else { $issue = new \Model\Issue(); $issue->load($id); $this->cache['issue.' . $id] = $issue; } return $issue->name; }
/** * Log issue update, send notifications * @param boolean $notify * @return Issue */ public function save($notify = true) { $f3 = \Base::instance(); // Catch empty sprint at the lowest level here if ($this->get("sprint_id") === 0) { $this->set("sprint_id", null); } // Censor credit card numbers if enabled if ($f3->get("security.block_ccs")) { if (preg_match("/([0-9]{3,4}-){3}[0-9]{3,4}/", $this->get("description"))) { $this->set("description", preg_replace("/([0-9]{3,4}-){3}([0-9]{3,4})/", "************\$2", $this->get("description"))); } } // Make dates correct if ($this->due_date) { $this->due_date = date("Y-m-d", strtotime($this->due_date)); } else { $this->due_date = null; } if ($this->start_date) { $this->start_date = date("Y-m-d", strtotime($this->start_date)); } else { $this->start_date = null; } // Check if updating or inserting if ($this->query) { // Save issue updates and send notifications $update = $this->_saveUpdate($notify); $issue = parent::save(); if ($notify && $update && $update->id && $update->notify) { $notification = \Helper\Notification::instance(); $notification->issue_update($this->get("id"), $update->id); } } else { // Move task to a sprint if the parent is in a sprint if ($this->get("parent_id") && !$this->get("sprint_id")) { $parent = new \Model\Issue(); $parent->load($this->get("parent_id")); if ($parent->sprint_id) { $this->set("sprint_id", $parent->sprint_id); } } // Save issue and send notifications $issue = parent::save(); if ($notify) { $notification = \Helper\Notification::instance(); $notification->issue_create($issue->id); } return $issue; } $this->saveTags(); return empty($issue) ? parent::save() : $issue; }
/** * Repeat an issue by generating a minimal copy and setting new due date * @param boolean $notify * @return Issue */ public function repeat($notify = true) { $repeat_issue = new \Model\Issue(); $repeat_issue->name = $this->name; $repeat_issue->type_id = $this->type_id; $repeat_issue->parent_id = $this->parent_id; $repeat_issue->author_id = $this->author_id; $repeat_issue->owner_id = $this->owner_id; $repeat_issue->description = $this->description; $repeat_issue->priority = $this->priority; $repeat_issue->repeat_cycle = $this->repeat_cycle; $repeat_issue->hours_total = $this->hours_total; $repeat_issue->hours_remaining = $this->hours_total; $repeat_issue->created_date = date("Y-m-d H:i:s"); // Find a due date in the future switch ($repeat_issue->repeat_cycle) { case 'daily': $repeat_issue->start_date = $this->start_date ? date("Y-m-d", strtotime("tomorrow")) : NULL; $repeat_issue->due_date = date("Y-m-d", strtotime("tomorrow")); break; case 'weekly': $repeat_issue->start_date = $this->start_date ? date("Y-m-d", strtotime($this->start_date . " +1 week")) : NULL; $repeat_issue->due_date = date("Y-m-d", strtotime($this->due_date . " +1 week")); break; case 'monthly': $repeat_issue->start_date = $this->start_date ? date("Y-m-d", strtotime($this->start_date . " +1 month")) : NULL; $repeat_issue->due_date = date("Y-m-d", strtotime($this->due_date . " +1 month")); break; case 'sprint': $sprint = new \Model\Sprint(); $sprint->load(array("start_date > NOW()"), array('order' => 'start_date')); $repeat_issue->start_date = $this->start_date ? $sprint->start_date : NULL; $repeat_issue->due_date = $sprint->end_date; break; default: $repeat_issue->repeat_cycle = 'none'; } // If the issue was in a sprint before, put it in a sprint again. if ($this->sprint_id) { $sprint = new \Model\Sprint(); $sprint->load(array("end_date >= ? AND start_date <= ?", $repeat_issue->due_date, $repeat_issue->due_date), array('order' => 'start_date')); $repeat_issue->sprint_id = $sprint->id; } $repeat_issue->save(); if ($notify) { $notification = \Helper\Notification::instance(); $notification->issue_create($repeat_issue->id); } return $repeat_issue; }
/** * Log and save an issue update * @param boolean $notify * @return Issue\Update */ protected function _saveUpdate($notify = true) { $f3 = \Base::instance(); // Ensure issue is not tied to itself as a parent if ($this->get("id") == $this->get("parent_id")) { $this->set("parent_id", $this->_getPrev("parent_id")); } // Log update $update = new \Model\Issue\Update(); $update->issue_id = $this->id; $update->user_id = $f3->get("user.id"); $update->created_date = date("Y-m-d H:i:s"); if ($f3->exists('update_comment')) { $update->comment_id = $f3->get('update_comment')->id; if ($notify) { $update->notify = 1; } } else { $update->notify = 0; } $update->save(); // Set hours_total to the hours_remaining value if it's 0 or null if ($this->get("hours_remaining") && !$this->get("hours_total")) { $this->set("hours_total", $this->get("hours_remaining")); } // Set hours remaining to 0 if the issue has been closed if ($this->get("closed_date") && $this->get("hours_remaining")) { $this->set("hours_remaining", 0); } // Create a new issue if repeating if ($this->get("closed_date") && $this->get("repeat_cycle") && $this->get("repeat_cycle") != "none") { $repeat_issue = new \Model\Issue(); $repeat_issue->name = $this->get("name"); $repeat_issue->type_id = $this->get("type_id"); $repeat_issue->parent_id = $this->get("parent_id"); $repeat_issue->author_id = $this->get("author_id"); $repeat_issue->owner_id = $this->get("owner_id"); $repeat_issue->description = $this->get("description"); $repeat_issue->priority = $this->get("priority"); $repeat_issue->repeat_cycle = $this->get("repeat_cycle"); $repeat_issue->hours_total = $this->get("hours_total"); $repeat_issue->hours_remaining = $this->get("hours_total"); // Reset hours remaining to start hours $repeat_issue->created_date = date("Y-m-d H:i:s"); // Find a due date in the future switch ($repeat_issue->repeat_cycle) { case 'daily': $repeat_issue->start_date = $this->get("start_date") ? date("Y-m-d", strtotime("tomorrow")) : NULL; $repeat_issue->due_date = date("Y-m-d", strtotime("tomorrow")); break; case 'weekly': $repeat_issue->start_date = $this->get("start_date") ? date("Y-m-d", strtotime($this->get("start_date") . " +1 week")) : NULL; $repeat_issue->due_date = date("Y-m-d", strtotime($this->get("due_date") . " +1 week")); break; case 'monthly': $repeat_issue->start_date = $this->get("start_date") ? date("Y-m-d", strtotime($this->get("start_date") . " +1 month")) : NULL; $repeat_issue->due_date = date("Y-m-d", strtotime($this->get("due_date") . " +1 month")); break; case 'sprint': $sprint = new \Model\Sprint(); $sprint->load(array("start_date > NOW()"), array('order' => 'start_date')); $repeat_issue->start_date = $this->get("start_date") ? $sprint->start_date : NULL; $repeat_issue->due_date = $sprint->end_date; break; default: $repeat_issue->repeat_cycle = 'none'; } // If the issue was in a sprint before, put it in a sprint again. if ($this->get("sprint_id")) { $sprint = new \Model\Sprint(); $sprint->load(array("end_date >= ? AND start_date <= ?", $repeat_issue->due_date, $repeat_issue->due_date), array('order' => 'start_date')); $repeat_issue->sprint_id = $sprint->id; } $repeat_issue->save(); $notification = \Helper\Notification::instance(); $notification->issue_create($repeat_issue->id); $this->set("repeat_cycle", null); } // Log updated fields $updated = 0; $important_changes = 0; $important_fields = array('status', 'name', 'description', 'owner_id', 'priority', 'due_date'); foreach ($this->fields as $key => $field) { if ($field["changed"] && $field["value"] != $this->_getPrev($key)) { $update_field = new \Model\Issue\Update\Field(); $update_field->issue_update_id = $update->id; $update_field->field = $key; $update_field->old_value = $this->_getPrev($key); $update_field->new_value = $field["value"]; $update_field->save(); $updated++; if ($key == 'sprint_id') { $this->resetTaskSprints(); } if (in_array($key, $important_fields)) { $important_changes++; } } } // Delete update if no fields were changed if (!$updated) { $update->delete(); } // Set notify flag if important changes occurred if ($notify && $important_changes) { $update->notify = 1; $update->save(); } // Send back the update return $update->id ? $update : false; }
/** * Replaces IDs with links to their corresponding issues * @param string $str * @return string */ protected function _parseIds($str) { $url = \Base::instance()->get("site.url"); // Find all IDs $count = preg_match_all("/(?<=[^a-z\\/&]#|^#)[0-9]+(?=[^a-z\\/]|\$)/i", $str, $matches); if (!$count) { return $str; } // Load IDs $ids = array(); foreach ($matches[0] as $match) { $ids[] = $match; } $idsStr = implode(",", array_unique($ids)); $issue = new \Model\Issue(); $issues = $issue->find(array("id IN ({$idsStr})")); return preg_replace_callback("/(?<=[^a-z\\/&]|^)#[0-9]+(?=[^a-z\\/]|\$)/i", function ($matches) use($url, $issues) { $id = ltrim($matches[0], "#"); foreach ($issues as $i) { if ($i->id == $id) { $issue = $i; } } if ($issue) { if ($issue->deleted_date) { $f3 = \Base::instance(); if ($f3->get("user.role") == "admin" || $f3->get("user.rank") >= \Model\User::RANK_MANAGER || $f3->get("user.id") == $issue->author_id) { return "<a href=\"{$url}issues/{$id}\" style=\"text-decoration: line-through;\">#{$id} – " . htmlspecialchars($issue->name) . "</a>"; } else { return "#{$id}"; } } return "<a href=\"{$url}issues/{$id}\">#{$id} – " . htmlspecialchars($issue->name) . "</a>"; } return "<a href=\"{$url}issues/{$id}\">#{$id}</a>"; }, $str); }