diff options
Diffstat (limited to 'app/class/issue.class.php')
-rw-r--r-- | app/class/issue.class.php | 267 |
1 files changed, 267 insertions, 0 deletions
diff --git a/app/class/issue.class.php b/app/class/issue.class.php new file mode 100644 index 0000000..47adfa1 --- /dev/null +++ b/app/class/issue.class.php @@ -0,0 +1,267 @@ +<?php + +/* + * SCROTT IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * For more information, please refer to UNLICENSE + */ + +require_once "class/obj.class.php"; +require_once "class/stage.class.php"; +require_once "class/user.class.php"; +require_once "class/mesg.class.php"; + +/* + * This class models Scrott issues. Issues represent units of work, track + * messages, can be assigned to users, and advance through a pipeline. + */ +class issue extends obj +{ + /* + * Constructor + */ + public function __construct(?string $guid = NULL) + { + $this->fields['issues'] = array( + "guid", + "numb", + "mesg", + "closer", + "closed", + "due", + "tags", + ); + + parent::__construct($guid); + $this->expectType("issue"); + } + + /* + * Initialize a new issue object with the given message, parent, + * and owner. The issue's name is taken from the message's name. + * The given message object is updated to parent the new issue. + */ + public static function initNew(mesg $mesg, user $owner, stage $parent) : issue + { + $pad = $parent->getParent(); + $numb = $pad->issueNumb++; + $pad->saveObj(); + + $issue = new issue(); + $issue->setOwner($owner); + $issue->setParent($parent); + $issue->name = $mesg->name; + $issue->objtype = "issue"; + $issue->numb = $numb; + $issue->mesg = $mesg->guid; + $issue->saveObj(); + + $mesg->setParent($issue); + + return $issue; + } + + /* + * Get all assignees. Assignees are returned as an array of + * stdClass instances. Objects contain data from the 'assignees' + * database table with no defined operations. Pointers to users + * will be followed and an instanciated user object will be + * in their place. Limit of zero returns all assignees. + */ + public function getAssignees(int $limit = 0) : array + { + $assign = array(); + $query = "SELECT * FROM assignees WHERE guid = '" . + database::esc($this->guid) . "'"; + + if ($limit != 0) + $query .= " LIMIT " . database::esc($limit); + + $res = database::query($query); + + foreach ($res as $a) + { + $obj = new stdClass(); + + foreach ($a as $k => $v) + $obj->{$k} = $v; + + $obj->assignee = new user($obj->assignee); + $obj->assigner = new user($obj->assigner); + + if (isset($obj->dismisser) && $obj->dismisser != "") + $obj->dismisser = new user($obj->dismisser); + + $assign[] = $obj; + } + + return $assign; + } + + /* + * Add an assignee to this issue. Returns false if user is + * already assigned, or if another error occurs; true + * otherwise. + */ + public function addAssignee(user $assignee, user $assigner) : bool + { + if ($assignee->isAssignedTo($this) || !isset($assignee->guid) + || !isset($assigner->guid)) + return false; + + $query = "INSERT INTO assignees (guid, assignee, assigner, assigned)" . + " VALUES ('" . database::esc($this->guid) . "', '" . + database::esc($assignee->guid) . "', '" . database::esc($assigner->guid) . + "', '" . database::esc(self::getCurrentTimestamp()) . "')"; + + database::query($query); + return true; + } + + /* + * Dismiss an assignee, effectively unassigning them. Returns + * false if user is not already an active assignee, or if another + * error occurs; true otherwise. + */ + public function dismissAssignee(user $assignee, user $dismisser) : bool + { + if (!$assignee->isAssignedTo($this) || !isset($assignee->guid) + || !isset($dismisser->guid)) + return false; + + $query = "UPDATE assignees SET dismisser = '" . database::esc($dismisser->guid) . + "', dismissed = '" . database::esc(self::getCurrentTimestamp()) . + "' WHERE guid = '" . database::esc($this->guid) . "' AND assignee = '" . + database::esc($assignee->guid) . "'"; + + database::query($query); + return true; + } + + /* + * Signoff an assignee's portion of this issue. Returns false + * if user is not already an active assignee, or if another + * error occurs; true otherwise. + */ + public function signoffAssignee(user $assignee) : bool + { + if (!$assignee->isAssignedTo($this) || !isset($assignee->guid)) + return false; + + $query = "UPDATE assignees SET signedoff = '" . + database::esc(self::getCurrentTimestamp()) . "' WHERE guid = '" . + database::esc($this->guid) . "' AND assignee = '" . + database::esc($assignee->guid) . "'"; + + database::query($query); + return true; + } + + /* + * Get the OP message for this issue. + */ + public function getOPMesg() : mesg + { + return new mesg($this->mesg); + } + + /* + * Get all messages on this issue. The OP message is filtered + * from results. Messages are sorted by date created. + */ + public function getMesgs_ordByDatetime() : array + { + $mesgs = parent::getMesgs_ordByDatetime(); + $i = -1; + + foreach ($mesgs as $k => $m) + { + if ($m->guid == $this->mesg) + { + $i = $k; + break; + } + } + + if ($i != -1) + { + unset($mesgs[$i]); + $mesgs = array_values($mesgs); + } + + return $mesgs; + } + + /* + * Get the user that closed this issue. If the issue is still + * open, NULL is returned. + */ + public function getCloser() : ?user + { + if (!isset($this->closer) || $this->closer == "") + return NULL; + + return new user($this->closer); + } + + /* + * Get the pad this issue exists under + */ + public function getPad() : pad + { + $parent = $this->getParent(); + + if ($parent->objtype == "pad") + return $parent; + + return $parent->getParent(); + } + + /* + * Advance this issue in the pipeline, closing it if already in the + * last stage. A closer is needed incase a close takes place. + */ + public function advance(user $closer) : void + { + $stage = $this->getParent(); + + if ($stage->objtype != "stage") + return; + + if (!($next = $stage->getNext())) + $this->close($closer); + else + $this->setParent($next); + } + + /* + * Mark this issue as completed and closed. + */ + public function close(user $closer) : void + { + $pad = $this->getParent()->getParent(); + + if ($pad) + { + $this->closer = $closer->guid; + $this->closed = self::getCurrentTimestamp(); + $this->setParent($pad); + } + } + + /* + * Check whether issue is currently open or closed. + */ + public function isOpen() : bool + { + return self::typeOf($this->parent) != "pad"; + } +} + +?> |