コード例 #1
0
ファイル: user.php プロジェクト: nikkiczx/phproject
 public function dashboard($f3, $params)
 {
     $issue = new \Model\Issue\Detail();
     // Add user's group IDs to owner filter
     $owner_ids = array($this->_userId);
     $groups = new \Model\User\Group();
     foreach ($groups->find(array("user_id = ?", $this->_userId)) as $r) {
         $owner_ids[] = $r->group_id;
     }
     $owner_ids = implode(",", $owner_ids);
     $order = "priority DESC, has_due_date ASC, due_date ASC";
     $projects = $issue->find(array("owner_id IN ({$owner_ids}) AND type_id=:type AND deleted_date IS NULL AND closed_date IS NULL AND status_closed = 0", ":type" => $f3->get("issue_type.project")), array("order" => $order));
     $subprojects = array();
     foreach ($projects as $i => $project) {
         if ($project->parent_id) {
             $subprojects[] = $project;
             unset($projects[$i]);
         }
     }
     $f3->set("projects", $projects);
     $f3->set("subprojects", $subprojects);
     $f3->set("bugs", $issue->find(array("owner_id IN ({$owner_ids}) AND type_id=:type AND deleted_date IS NULL AND closed_date IS NULL AND status_closed = 0", ":type" => $f3->get("issue_type.bug")), array("order" => $order)));
     $f3->set("repeat_issues", $issue->find(array("owner_id IN ({$owner_ids}) AND deleted_date IS NULL AND closed_date IS NULL AND status_closed = 0 AND repeat_cycle NOT IN ('none', '')", ":type" => $f3->get("issue_type.bug")), array("order" => $order)));
     $watchlist = new \Model\Issue\Watcher();
     $f3->set("watchlist", $watchlist->findby_watcher($this->_userId, $order));
     $tasks = new \Model\Issue\Detail();
     $f3->set("tasks", $tasks->find(array("owner_id IN ({$owner_ids}) AND type_id=:type AND deleted_date IS NULL AND closed_date IS NULL AND status_closed = 0", ":type" => $f3->get("issue_type.task")), array("order" => $order)));
     // Get current sprint if there is one
     $sprint = new \Model\Sprint();
     $sprint->load("NOW() BETWEEN start_date AND end_date");
     $f3->set("sprint", $sprint);
     $f3->set("menuitem", "index");
     $this->_render("user/dashboard.html");
 }
コード例 #2
0
ファイル: user.php プロジェクト: Rayne/phproject
 /**
  * GET /user/dashboard User dashboard
  *
  * @param \Base $f3
  * @throws \Exception
  */
 public function dashboard($f3)
 {
     $dashboard = $f3->get("user_obj")->option("dashboard");
     if (!$dashboard) {
         $dashboard = array("left" => array("projects", "subprojects", "bugs", "repeat_work", "watchlist"), "right" => array("tasks"));
     }
     // Load dashboard widget data
     $allWidgets = array("projects", "subprojects", "tasks", "bugs", "repeat_work", "watchlist", "my_comments", "recent_comments", "open_comments", "issue_tree");
     $helper = \Helper\Dashboard::instance();
     foreach ($dashboard as $widgets) {
         foreach ($widgets as $widget) {
             if (is_callable(array($helper, $widget))) {
                 $f3->set($widget, $helper->{$widget}());
             } else {
                 $f3->set("error", "Widget '{$widget}' is not available.");
             }
             unset($allWidgets[array_search($widget, $allWidgets)]);
         }
     }
     $f3->set("unused_widgets", $allWidgets);
     // Get current sprint if there is one
     $sprint = new \Model\Sprint();
     $localDate = date('Y-m-d', \Helper\View::instance()->utc2local());
     $sprint->load(array("? BETWEEN start_date AND end_date", $localDate));
     $f3->set("sprint", $sprint);
     $f3->set("dashboard", $dashboard);
     $f3->set("menuitem", "index");
     $this->_render("user/dashboard.html");
 }
コード例 #3
0
ファイル: backlog.php プロジェクト: nikkiczx/phproject
 public function index_old($f3)
 {
     $sprint_model = new \Model\Sprint();
     $sprints = $sprint_model->find(array("end_date < ?", $this->now(false)), array("order" => "start_date ASC"));
     $issue = new \Model\Issue\Detail();
     $sprint_details = array();
     foreach ($sprints as $sprint) {
         $projects = $issue->find(array("deleted_date IS NULL AND sprint_id = ? AND type_id = ?", $sprint->id, $f3->get("issue_type.project")));
         $sprint_details[] = $sprint->cast() + array("projects" => $projects);
     }
     $f3->set("sprints", $sprint_details);
     $f3->set("title", $f3->get("dict.backlog"));
     $f3->set("menuitem", "backlog");
     $this->_render("backlog/old.html");
 }
コード例 #4
0
ファイル: admin.php プロジェクト: nikkiczx/phproject
 public function sprint_edit($f3, $params)
 {
     $f3->set("title", $f3->get("dict.sprints"));
     $sprint = new \Model\Sprint();
     $sprint->load($params["id"]);
     if (!$sprint->id) {
         $f3->error(404);
         return;
     }
     if ($post = $f3->get("POST")) {
         if (empty($post["start_date"]) || empty($post["end_date"])) {
             $f3->set("error", "Start and end date are required");
             $this->_render("admin/sprints/edit.html");
             return;
         }
         $start = strtotime($post["start_date"]);
         $end = strtotime($post["end_date"]);
         if ($end <= $start) {
             $f3->set("error", "End date must be after start date");
             $this->_render("admin/sprints/edit.html");
             return;
         }
         $sprint->name = trim($post["name"]);
         $sprint->start_date = date("Y-m-d", $start);
         $sprint->end_date = date("Y-m-d", $end);
         $sprint->save();
         $f3->reroute("/admin/sprints");
         return;
     }
     $f3->set("sprint", $sprint);
     $this->_render("admin/sprints/edit.html");
 }
コード例 #5
0
ファイル: taskboard.php プロジェクト: nikkiczx/phproject
 /**
  * Load the burndown chart data
  */
 public function burndown($f3, $params)
 {
     $sprint = new \Model\Sprint();
     $sprint->load($params["id"]);
     if (!$sprint->id) {
         $f3->error(404);
         return;
     }
     $visible_tasks = explode(",", $params["tasks"]);
     // Visible tasks must have at least one key
     if (empty($visible_tasks)) {
         $visible_tasks = array(0);
     }
     // Get today's date
     $today = date('Y-m-d');
     $today = $today . " 23:59:59";
     // Check to see if the sprint is completed
     if ($today < strtotime($sprint->end_date . ' + 1 day')) {
         $burnComplete = 0;
         $burnDates = $this->_createDateRangeArray($sprint->start_date, $today);
         $remainingDays = $this->_createDateRangeArray($today, $sprint->end_date);
     } else {
         $burnComplete = 1;
         $burnDates = $this->_createDateRangeArray($sprint->start_date, $sprint->end_date);
         $remainingDays = array();
     }
     $burnDays = array();
     $burnDatesCount = count($burnDates);
     $db = $f3->get("db.instance");
     $visible_tasks_str = implode(",", $visible_tasks);
     $query_initial = "SELECT SUM(IFNULL(i.hours_total, i.hours_remaining)) AS remaining\n\t\t\t\tFROM issue i\n\t\t\t\tWHERE i.created_date < :date\n\t\t\t\tAND i.id IN (" . implode(",", $visible_tasks) . ")";
     $query_daily = "SELECT SUM(IF(f.id IS NULL, IFNULL(i.hours_total, i.hours_remaining), f.new_value)) AS remaining\n\t\t\t\tFROM issue_update_field f\n\t\t\t\tJOIN issue_update u ON u.id = f.issue_update_id\n\t\t\t\tJOIN (\n\t\t\t\t\tSELECT MAX(u.id) AS max_id\n\t\t\t\t\tFROM issue_update u\n\t\t\t\t\tJOIN issue_update_field f ON f.issue_update_id = u.id\n\t\t\t\t\tWHERE f.field = 'hours_remaining'\n\t\t\t\t\tAND u.created_date < :date\n\t\t\t\t\tAND u.issue_id IN ({$visible_tasks_str})\n\t\t\t\t\tGROUP BY u.issue_id\n\t\t\t\t) a ON a.max_id = u.id\n\t\t\t\tRIGHT JOIN issue i ON i.id = u.issue_id\n\t\t\t\tWHERE (f.field = 'hours_remaining' OR f.field IS NULL)\n\t\t\t\tAND i.created_date < :date\n\t\t\t\tAND i.id IN ({$visible_tasks_str})";
     $i = 1;
     foreach ($burnDates as $date) {
         // Get total_hours, which is the initial amount entered on each task, and cache this query
         if ($i == 1) {
             $result = $db->exec($query_initial, array(":date" => $sprint->start_date), 2592000);
             $burnDays[$date] = $result[0];
         } elseif ($i < $burnDatesCount - 1 || $burnComplete) {
             $result = $db->exec($query_daily, array(":date" => $date . " 23:59:59"), 2592000);
             $burnDays[$date] = $result[0];
         } else {
             $result = $db->exec($query_daily, array(":date" => $date . " 23:59:59"));
             $burnDays[$date] = $result[0];
         }
         $i++;
     }
     // Add in empty days
     if (!$burnComplete) {
         $i = 0;
         foreach ($remainingDays as $day) {
             if ($i != 0) {
                 $burnDays[$day] = NULL;
             }
             $i++;
         }
     }
     // Reformat the date and remove weekends
     $i = 0;
     foreach ($burnDays as $burnKey => $burnDay) {
         $weekday = date("D", strtotime($burnKey));
         $weekendDays = array("Sat", "Sun");
         if (!in_array($weekday, $weekendDays)) {
             $newDate = date("M j", strtotime($burnKey));
             $burnDays[$newDate] = $burnDays[$burnKey];
             unset($burnDays[$burnKey]);
         } else {
             // Remove weekend days
             unset($burnDays[$burnKey]);
         }
         $i++;
     }
     $this->_printJson($burnDays);
 }
コード例 #6
0
ファイル: issues.php プロジェクト: Rayne/phproject
 /**
  * GET /issues/@id
  * View an issue
  *
  * @param \Base $f3
  * @param array $params
  * @throws \Exception
  */
 public function single($f3, $params)
 {
     $issue = new \Model\Issue\Detail();
     $issue->load(array("id=?", $params["id"]));
     $user = $f3->get("user_obj");
     if (!$issue->id || $issue->deleted_date && !($user->role == 'admin' || $user->rank >= \Model\User::RANK_MANAGER || $issue->author_id == $user->id)) {
         $f3->error(404);
         return;
     }
     $type = new \Model\Issue\Type();
     $type->load($issue->type_id);
     $f3->set("title", $type->name . " #" . $issue->id . ": " . $issue->name);
     $f3->set("menuitem", "browse");
     $author = new \Model\User();
     $author->load($issue->author_id);
     $owner = new \Model\User();
     if ($issue->owner_id) {
         $owner->load($issue->owner_id);
     }
     $files = new \Model\Issue\File\Detail();
     $f3->set("files", $files->find(array("issue_id = ? AND deleted_date IS NULL", $issue->id)));
     if ($issue->sprint_id) {
         $sprint = new \Model\Sprint();
         $sprint->load($issue->sprint_id);
         $f3->set("sprint", $sprint);
     }
     $watching = new \Model\Issue\Watcher();
     $watching->load(array("issue_id = ? AND user_id = ?", $issue->id, $this->_userId));
     $f3->set("watching", !!$watching->id);
     $f3->set("issue", $issue);
     $f3->set("ancestors", $issue->getAncestors());
     $f3->set("type", $type);
     $f3->set("author", $author);
     $f3->set("owner", $owner);
     $comments = new \Model\Issue\Comment\Detail();
     $f3->set("comments", $comments->find(array("issue_id = ?", $issue->id), array("order" => "created_date DESC")));
     // Extra data needed for inline edit form
     $status = new \Model\Issue\Status();
     $f3->set("statuses", $status->find(null, null, $f3->get("cache_expire.db")));
     $priority = new \Model\Issue\Priority();
     $f3->set("priorities", $priority->find(null, array("order" => "value DESC"), $f3->get("cache_expire.db")));
     $sprint = new \Model\Sprint();
     $f3->set("sprints", $sprint->find(array("end_date >= ? OR id = ?", $this->now(false), $issue->sprint_id), array("order" => "start_date ASC")));
     $users = new \Model\User();
     $f3->set("users", $users->find("deleted_date IS NULL AND role != 'group'", array("order" => "name ASC")));
     $f3->set("groups", $users->find("deleted_date IS NULL AND role = 'group'", array("order" => "name ASC")));
     $this->_render("issues/single.html");
 }
コード例 #7
0
/**
 * ~SprintBreaker~
 * Alanaktion's insane attempt at automated sprint management.
 *
 * Moves tasks under projects into sprints by due date
 *
 * Tested, but still a bit scary. Also unnecessary now that
 * sprint tasks are dynamically loaded by due date.
 *
 * Use with caution and pizza.
 *
 **/
require_once "base.php";
$issue_type_project = $f3->get("issue_type.project");
// Get all current and future sprints
$sprint = new \Model\Sprint();
$sprints = $sprint->find(array("start_date >= :now OR end_date <= :now", ":now" => date("Y-m-d")));
echo "Using " . count($sprints) . " sprints.\n";
// Get all top level projects
$project_model = new \Model\Issue();
$projects = $project_model->find(array("type_id = ? AND parent_id IS NULL AND sprint_id IS NULL", $issue_type_project));
if ($projects && $sprints) {
    foreach ($projects as $project) {
        echo "\nBreaking project {$project->id}:\n";
        // Get all tasks with due dates directly under project
        $due_date_tasks = new \Model\Issue();
        $tasks = $due_date_tasks->find(array("parent_id = :project AND due_date >= :now AND type_id != :type", ":project" => $project->id, ":now" => date("Y-m-d"), ":type" => $issue_type_project));
        if ($tasks) {
            foreach ($sprints as $sprint) {
                echo "Using sprint {$sprint->id}\n";
                $start = strtotime($sprint->start_date);
コード例 #8
0
ファイル: issues.php プロジェクト: nikkiczx/phproject
 public function sprints_old($f3)
 {
     $sprint_model = new \Model\Sprint();
     $sprints = $sprint_model->find(array("end_date < ?", $this->now(false)), array("order" => "start_date ASC"));
     $return = array();
     foreach ($sprints as $sprint) {
         $return[] = $sprint->cast();
     }
     $this->_printJson($return);
 }
コード例 #9
0
ファイル: update.php プロジェクト: phemmyster/phproject
 /**
  * Convert a sprint ID to a name/date
  * @param int $id
  * @return string
  */
 public function convertSprintId($id)
 {
     if (isset($this->cache['sprint.' . $id])) {
         $sprint = $this->cache['sprint.' . $id];
     } else {
         $sprint = new \Model\Sprint();
         $sprint->load($id);
         $this->cache['sprint.' . $id] = $sprint;
     }
     return $sprint->name . " - " . date('n/j', strtotime($sprint->start_date)) . "-" . date('n/j', strtotime($sprint->end_date));
 }
コード例 #10
0
ファイル: issue.php プロジェクト: svenbw/phproject
 /**
  * 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;
 }
コード例 #11
0
ファイル: issues.php プロジェクト: phemmyster/phproject
 /**
  * @param \Base $f3
  * @param array $params
  * @throws \Exception
  */
 public function single($f3, $params)
 {
     $issue = new \Model\Issue\Detail();
     $issue->load(array("id=?", $f3->get("PARAMS.id")));
     $user = $f3->get("user_obj");
     if (!$issue->id || $issue->deleted_date && !($user->role == 'admin' || $user->rank >= \Model\User::RANK_MANAGER || $issue->author_id == $user->id)) {
         $f3->error(404);
         return;
     }
     $type = new \Model\Issue\Type();
     $type->load($issue->type_id);
     // Run actions if passed
     $post = $f3->get("POST");
     if (!empty($post)) {
         switch ($post["action"]) {
             case "add_watcher":
                 $watching = new \Model\Issue\Watcher();
                 // Loads just in case the user is already a watcher
                 $watching->load(array("issue_id = ? AND user_id = ?", $issue->id, $post["user_id"]));
                 if (!$watching->id) {
                     $watching->issue_id = $issue->id;
                     $watching->user_id = $post["user_id"];
                     $watching->save();
                 }
                 if ($f3->get("AJAX")) {
                     return;
                 }
                 break;
             case "remove_watcher":
                 $watching = new \Model\Issue\Watcher();
                 $watching->load(array("issue_id = ? AND user_id = ?", $issue->id, $post["user_id"]));
                 $watching->delete();
                 if ($f3->get("AJAX")) {
                     return;
                 }
                 break;
             case "add_dependency":
                 $dependencies = new \Model\Issue\Dependency();
                 // Loads just in case the task  is already a dependency
                 $dependencies->load(array("issue_id = ? AND dependency_id = ?", $issue->id, $post["id"]));
                 $dependencies->issue_id = $issue->id;
                 $dependencies->dependency_id = $post["id"];
                 $dependencies->dependency_type = $post["type_id"];
                 $dependencies->save();
                 if ($f3->get("AJAX")) {
                     return;
                 }
                 break;
             case "add_dependent":
                 $dependencies = new \Model\Issue\Dependency();
                 // Loads just in case the task  is already a dependency
                 $dependencies->load(array("issue_id = ? AND dependency_id = ?", $post["id"], $issue->id));
                 $dependencies->dependency_id = $issue->id;
                 $dependencies->issue_id = $post["id"];
                 $dependencies->dependency_type = $post["type_id"];
                 $dependencies->save();
                 if ($f3->get("AJAX")) {
                     return;
                 }
                 break;
             case "remove_dependency":
                 $dependencies = new \Model\Issue\Dependency();
                 $dependencies->load($post["id"]);
                 $dependencies->delete();
                 if ($f3->get("AJAX")) {
                     return;
                 }
                 break;
         }
     }
     $f3->set("title", $type->name . " #" . $issue->id . ": " . $issue->name);
     $f3->set("menuitem", "browse");
     $author = new \Model\User();
     $author->load($issue->author_id);
     $owner = new \Model\User();
     if ($issue->owner_id) {
         $owner->load($issue->owner_id);
     }
     $files = new \Model\Issue\File\Detail();
     $f3->set("files", $files->find(array("issue_id = ? AND deleted_date IS NULL", $issue->id)));
     if ($issue->sprint_id) {
         $sprint = new \Model\Sprint();
         $sprint->load($issue->sprint_id);
         $f3->set("sprint", $sprint);
     }
     $watching = new \Model\Issue\Watcher();
     $watching->load(array("issue_id = ? AND user_id = ?", $issue->id, $this->_userId));
     $f3->set("watching", !!$watching->id);
     $f3->set("issue", $issue);
     $f3->set("ancestors", $issue->getAncestors());
     $f3->set("type", $type);
     $f3->set("author", $author);
     $f3->set("owner", $owner);
     $comments = new \Model\Issue\Comment\Detail();
     $f3->set("comments", $comments->find(array("issue_id = ?", $issue->id), array("order" => "created_date DESC")));
     // Extra data needed for inline edit form
     $status = new \Model\Issue\Status();
     $f3->set("statuses", $status->find(null, null, $f3->get("cache_expire.db")));
     $priority = new \Model\Issue\Priority();
     $f3->set("priorities", $priority->find(null, array("order" => "value DESC"), $f3->get("cache_expire.db")));
     $sprint = new \Model\Sprint();
     $f3->set("sprints", $sprint->find(array("end_date >= ? OR id = ?", $this->now(false), $issue->sprint_id), array("order" => "start_date ASC")));
     $users = new \Model\User();
     $f3->set("users", $users->find("deleted_date IS NULL AND role != 'group'", array("order" => "name ASC")));
     $f3->set("groups", $users->find("deleted_date IS NULL AND role = 'group'", array("order" => "name ASC")));
     $this->_render("issues/single.html");
 }
コード例 #12
0
ファイル: issue.php プロジェクト: Rayne/phproject
 /**
  * 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;
 }