summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--README.txt12
-rw-r--r--app/class/agent.class.php7
-rw-r--r--app/class/globals.php2
-rw-r--r--app/class/group.class.php11
-rw-r--r--app/class/image.php18
-rw-r--r--app/class/obj.class.php3
-rw-r--r--app/class/pad.class.php18
-rw-r--r--app/class/user.class.php9
-rw-r--r--app/index.php6
-rw-r--r--app/model/issue.php66
-rw-r--r--app/model/pad.php2
-rw-r--r--app/view/datalsts.php4
-rw-r--r--app/view/issue.php66
-rw-r--r--app/view/pad.php12
-rw-r--r--app/view/pad_closed.php68
15 files changed, 263 insertions, 41 deletions
diff --git a/README.txt b/README.txt
index 85441e6..3d0c354 100644
--- a/README.txt
+++ b/README.txt
@@ -3,12 +3,14 @@ SCROTT - The Secure Centralized Robust Online Ticketing Tool
This is the alpha version of Scrott, which is currently only a very simplistic
and generic issue tracker, supporting multiple users and project boards (called
-'pads'). The alpha will constitute the v0.1.x series of releases. Expect some
-upcoming quality-of-life changes to the alpha, as well as bug fixes.
+'pads').
-The v0.2 series of releases and on are reserved for the beta, which I am
-planning a full rewrite for soon. This progress can be tracked on the 'dev'
-source branch.
+Development on the beta version has begun. I have planned a full rewrite and
+will be introducing some design changes to a few core components of Scrott.
+Follow development on the 'dev' source branch.
+
+In the meantime, expect the alpha to receive no more than bug fixes and, perhaps,
+only small features as updates.
INSTALLATION
diff --git a/app/class/agent.class.php b/app/class/agent.class.php
index 4c75f0b..4651a10 100644
--- a/app/class/agent.class.php
+++ b/app/class/agent.class.php
@@ -90,6 +90,13 @@ abstract class agent extends obj
}
/*
+ * Get all contained users. For users, this is an array containing
+ * only $this. For groups, this is an array containing the owner
+ * and all members.
+ */
+ public abstract function getContainedUsers() : array;
+
+ /*
* Send an email message to this agent using stored configuration
* parameters. If config is not established, delivery is not
* attempted. Return status.
diff --git a/app/class/globals.php b/app/class/globals.php
index 74635d7..8a6efd7 100644
--- a/app/class/globals.php
+++ b/app/class/globals.php
@@ -19,7 +19,7 @@ require_once "class/obj.class.php";
* These are utility functions and constants for the Scrott application.
*/
-define("__VERSION__", "v0.1");
+define("__VERSION__", "v0.2");
/*
* These global variables are arrays of strings logged by Scrott business
diff --git a/app/class/group.class.php b/app/class/group.class.php
index 1191d71..600fb6d 100644
--- a/app/class/group.class.php
+++ b/app/class/group.class.php
@@ -64,6 +64,17 @@ class group extends agent
}
/*
+ * Get all contained users. This is an array of all members and
+ * the group owner.
+ */
+ public function getContainedUsers() : array
+ {
+ $cus = $this->getMembers();
+ $cus[] = $this->getOwner();
+ return $cus;
+ }
+
+ /*
* Send an email message to this group using stored configuration
* parameters. If config is not established, delivery is not
* attempted. Return status. If any delivery attempts fail, the
diff --git a/app/class/image.php b/app/class/image.php
index 6b73cae..62999aa 100644
--- a/app/class/image.php
+++ b/app/class/image.php
@@ -20,13 +20,19 @@
/*
* Mappings from image MIME types to PHP open/write functions
*/
-$_IMG_OPEN_FUNCS['image/jpeg'] = "imagecreatefromjpeg";
-$_IMG_OPEN_FUNCS['image/jpg'] = "imagecreatefromjpeg";
-$_IMG_OPEN_FUNCS['image/png'] = "imagecreatefrompng";
+$_IMG_OPEN_FUNCS['image/jpeg'] = "imagecreatefromjpeg";
+$_IMG_OPEN_FUNCS['image/jpg'] = "imagecreatefromjpeg";
+$_IMG_OPEN_FUNCS['image/png'] = "imagecreatefrompng";
+$_IMG_OPEN_FUNCS['image/gif'] = "imagecreatefromgif";
+$_IMG_OPEN_FUNCS['image/bmp'] = "imagecreatefrombmp";
+$_IMG_OPEN_FUNCS['image/x-ms-bmp'] = "imagecreatefrombmp";
-$_IMG_WRITE_FUNCS['image/jpeg'] = "imagejpeg";
-$_IMG_WRITE_FUNCS['image/jpg'] = "imagejpeg";
-$_IMG_WRITE_FUNCS['image/png'] = "imagepng";
+$_IMG_WRITE_FUNCS['image/jpeg'] = "imagejpeg";
+$_IMG_WRITE_FUNCS['image/jpg'] = "imagejpeg";
+$_IMG_WRITE_FUNCS['image/png'] = "imagepng";
+$_IMG_WRITE_FUNCS['image/gif'] = "imagegif";
+$_IMG_WRITE_FUNCS['image/bmp'] = "imagebmp";
+$_IMG_WRITE_FUNCS['image/x-ms-bmp'] = "imagebmp";
/*
* Open the given image and crop it, such that the result is a square
diff --git a/app/class/obj.class.php b/app/class/obj.class.php
index 850184c..353d617 100644
--- a/app/class/obj.class.php
+++ b/app/class/obj.class.php
@@ -30,6 +30,9 @@ class obj extends table
"image/jpeg",
"image/jpg",
"image/png",
+ "image/gif",
+ "image/bmp",
+ "image/x-ms-bmp",
);
/*
diff --git a/app/class/pad.class.php b/app/class/pad.class.php
index dcf2b32..0c69385 100644
--- a/app/class/pad.class.php
+++ b/app/class/pad.class.php
@@ -108,6 +108,24 @@ class pad extends obj
$stage->saveObj();
$this->saveObj();
}
+
+ /*
+ * Get an array of all closed issues under this pad. Ordered by
+ * datetime closed.
+ */
+ public function getClosedIssues_ordByClosed() : array
+ {
+ $query = "SELECT o.guid FROM objects o JOIN issues i ON o.guid = i.guid " .
+ "WHERE o.parent = '" . database::esc($this->guid) . "' ORDER BY i.closed DESC";
+ $res = database::query($query);
+
+ $issues = array();
+
+ foreach ($res as $i)
+ $issues[] = new issue($i['guid']);
+
+ return $issues;
+ }
}
?>
diff --git a/app/class/user.class.php b/app/class/user.class.php
index 90aac44..231111d 100644
--- a/app/class/user.class.php
+++ b/app/class/user.class.php
@@ -294,6 +294,15 @@ class user extends agent
}
/*
+ * Get all contained users. This is just an array containing
+ * the user object.
+ */
+ public function getContainedUsers() : array
+ {
+ return array($this);
+ }
+
+ /*
* Send an email message to this user using stored configuration
* parameters. If config is not established, delivery is not
* attempted. Return status.
diff --git a/app/index.php b/app/index.php
index 21f3036..7f05479 100644
--- a/app/index.php
+++ b/app/index.php
@@ -111,7 +111,10 @@ function main(array $argv) : void
$obj = new pad($argv[0]);
setPageObj($obj);
setPageName($obj->name);
- require "view/pad.php";
+ if (isset($argv[1]) && $argv[1] == "closed")
+ require "view/pad_closed.php";
+ else
+ require "view/pad.php";
break;
}
}
@@ -119,6 +122,7 @@ function main(array $argv) : void
/* page not found */
else
{
+ header("HTTP/1.1 404 Not Found");
require "view/404.php";
}
}
diff --git a/app/model/issue.php b/app/model/issue.php
index 4300bbb..7159015 100644
--- a/app/model/issue.php
+++ b/app/model/issue.php
@@ -23,7 +23,8 @@ if (isAction("iss-mesg-add"))
{
$form = new form();
$form->text("issue");
- $form->text("mesg");
+ $form->text("mesg", false);
+ $form->text("assignee");
if (!$form->populate(input()))
return;
@@ -36,22 +37,69 @@ if (isAction("iss-mesg-add"))
return;
}
- if (!$user->canCreateSub($issue))
+ if (isset($form->mesg) && $form->mesg != "")
{
- logError(ERROR, "You do not have permission to post to this issue");
- return;
+ if (!$user->canCreateSub($issue))
+ {
+ logError(ERROR, "You do not have permission to post to this issue");
+ return;
+ }
+
+ $mesg = mesg::initNew($form->mesg, $user, $issue);
+
+ if ($mesg->setAttachment("attachment"))
+ logError(NOTICE, "Saved attachment " . $mesg->attachment);
+ }
+
+ if (isset(input()['advIssue']))
+ {
+ if (!$user->canModify($issue))
+ {
+ logError(ERROR, "You do not have permission to move this issue");
+ return;
+ }
+
+ $issue->advance($user);
+
+ if ($issue->isOpen())
+ {
+ $sgename = $issue->getParent()->name;
+ $log = mesg::initNewLog("%s advanced issue to '" . $sgename . "'", $user, $issue);
+ }
+ else
+ {
+ $log = mesg::initNewLog("%s closed issue", $user, $issue);
+ }
}
- $mesg = mesg::initNew($form->mesg, $user, $issue);
+ else if (isset(input()['assIssue']))
+ {
+ if (!$user->canModify($issue))
+ {
+ logError(ERROR, "You do not have permission to assign this issue");
+ return;
+ }
+
+ $assignee = new user($form->assignee);
+ $stat = $issue->addAssignee($assignee, $user);
- if ($mesg->setAttachment("attachment"))
- logError(NOTICE, "Saved attachment " . $mesg->attachment);
+ if (!$stat)
+ logError(ERROR, "Failed to assign issue");
+ else
+ $log = mesg::initNewLog("%s assigned " . $assignee->getDisplayName(), $user, $issue);
+ }
- if (isset(input()['closeIssue']))
+ else if (isset(input()['closeIssue']))
{
+ if (!$user->canModify($issue))
+ {
+ logError(ERROR, "You do not have permission to close this issue");
+ return;
+ }
+
$issue->close($user);
logError(NOTICE, "Issue #" . $issue->numb . " closed");
- $log = mesg::initNewLog("% closed issue", $user, $issue);
+ $log = mesg::initNewLog("%s closed issue", $user, $issue);
}
}
diff --git a/app/model/pad.php b/app/model/pad.php
index d7cfb23..0090c27 100644
--- a/app/model/pad.php
+++ b/app/model/pad.php
@@ -26,4 +26,6 @@ foreach ($stages as $s)
$issues = array_merge($issues, $i);
}
+$closed_issues = $pad->getClosedIssues_ordByClosed();
+
?>
diff --git a/app/view/datalsts.php b/app/view/datalsts.php
index 2bbb44e..ba9021b 100644
--- a/app/view/datalsts.php
+++ b/app/view/datalsts.php
@@ -118,6 +118,10 @@ require_once "class/issue.class.php";
<div class="panel panel-default">
<h2 class="text-center"><?=$s->name?></h2>
+ <?php if (count($s->getIssues_ordByDueByNumb()) == 0) { ?>
+ <h4 class="text-info text-center">No issues</h4>
+ <?php } ?>
+
<table class="table table-hover">
<?php foreach ($s->getIssues_ordByDueByNumb() as $i) { ?>
<?=issueListItem($i)?>
diff --git a/app/view/issue.php b/app/view/issue.php
index 2d781ba..01a9f5f 100644
--- a/app/view/issue.php
+++ b/app/view/issue.php
@@ -109,23 +109,57 @@ require_once "class/issue.class.php";
</div>
<?php } ?>
- <form method="post" action="<?=ap()?>" enctype="multipart/form-data">
- <?=\formctrl\formname( "iss-mesg-add" )?>
- <?=\formctrl\hidden( "issue", $i->guid )?>
- <?=\formctrl\textarea( "New message", "mesg", 5 )?>
-
- <div class="btn-group pull-right">
- <button type="submit" name="input[postMesg]" class="btn btn-primary">
- <span class="glyphicon glyphicon-envelope"></span> Post message
- </button>
-
- <button type="submit" name="input[closeIssue]" class="btn btn-success">
- <span class="glyphicon glyphicon-ok"></span> Close issue
- </button>
- </div>
+ <?php if ($i->isOpen()) { ?>
+ <form method="post" action="<?=ap()?>" enctype="multipart/form-data">
+ <?=\formctrl\formname( "iss-mesg-add" )?>
+ <?=\formctrl\hidden( "issue", $i->guid )?>
+ <?=\formctrl\textarea( "New message", "mesg", 5 )?>
+
+ <div class="btn-group pull-right">
+ <button type="submit" name="input[postMesg]" class="btn btn-primary" title="Post message">
+ <span class="glyphicon glyphicon-envelope"></span>
+ </button>
+
+ <button type="submit" name="input[closeIssue]" class="btn btn-default" title="Close issue">
+ <span class="glyphicon glyphicon-ok"></span>
+ </button>
+
+ <button type="submit" name="input[advIssue]" class="btn btn-default" title="Advance issue">
+ <span class="glyphicon glyphicon-chevron-up"></span>
+ </button>
+
+ <button type="button" class="btn btn-default" data-toggle="collapse" data-target="#assignCollapse-<?=$i->guid?>" title="Assign issue">
+ <span class="glyphicon glyphicon-share-alt"></span>
+ </button>
+ </div>
- <?=\formctrl\file( "Attachment", "attachment" )?>
- </form>
+ <div class="collapse" id="assignCollapse-<?=$i->guid?>">
+ <div class="form-group">
+ <label>Select assignee</label>
+
+ <select name="input[assignee]" class="form-control selectpicker">
+ <?php foreach ($i->getParent()->getParent()->getOwner()->getContainedUsers() as $memb) { ?>
+ <option data-icon="glyphicon-user" value="<?=$memb->guid?>">
+ <?=$memb->getDisplayName()?>
+ </option>
+ <?php } ?>
+
+ <?php foreach ($i->getParent()->getParent()->getMembers() as $memb) { ?>
+ <option data-icon="glyphicon-user" value="<?=$memb->guid?>">
+ <?=$memb->getDisplayName()?>
+ </option>
+ <?php } ?>
+ </select>
+ </div>
+
+ <button type="submit" name="input[assIssue]" class="btn btn-default">
+ <span class="glyphicon glyphicon-share-alt"></span> Post and assign issue
+ </button>
+ </div>
+
+ <?=\formctrl\file( "Attachment", "attachment" )?>
+ </form>
+ <?php } ?>
</div>
<div class="col-md-4">
diff --git a/app/view/pad.php b/app/view/pad.php
index a9c8508..bf626bf 100644
--- a/app/view/pad.php
+++ b/app/view/pad.php
@@ -42,9 +42,15 @@ require_once "view/issue.php";
<span class="glyphicon glyphicon-edit"></span>
<?=$pad->name?>
- <button type="button" class="btn btn-success" data-toggle="modal" data-target="#newIssueModal">
- <span class="glyphicon glyphicon-plus"></span> Open Issue
- </button>
+ <div class="btn-group">
+ <button type="button" class="btn btn-success" data-toggle="modal" data-target="#newIssueModal">
+ <span class="glyphicon glyphicon-plus"></span> Open Issue
+ </button>
+
+ <a href="<?=ap()?>/closed" class="btn btn-default">
+ View closed issues
+ </a>
+ </div>
</h1>
</div>
</div>
diff --git a/app/view/pad_closed.php b/app/view/pad_closed.php
new file mode 100644
index 0000000..e11ca84
--- /dev/null
+++ b/app/view/pad_closed.php
@@ -0,0 +1,68 @@
+<?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 "model/pad.php";
+require_once "view/stdpage.php";
+require_once "view/datalsts.php";
+require_once "view/issue.php";
+
+?>
+
+<!DOCTYPE html>
+
+<html lang="en">
+ <head>
+ <?=stdpage\head( getPageName() )?>
+ </head>
+
+ <body>
+ <?php foreach ($closed_issues as $i) { ?>
+ <?=issue_v\issue($i)?>
+ <?php } ?>
+
+ <?=stdpage\top()?>
+ <?=stdpage\nav()?>
+
+ <div class="container">
+ <div class="well well-lg">
+ <div class="row">
+ <div class="col-md-12">
+ <h1>
+ <span class="glyphicon glyphicon-edit"></span>
+ <?=$pad->name?>
+
+ <a href="<?=ar()?>/<?=$pad->guid?>" class="btn btn-default">
+ View open issues
+ </a>
+ </h1>
+ </div>
+ </div>
+ </div>
+
+ <div class="panel panel-default">
+ <?php if (count($closed_issues) == 0) { ?>
+ <h4 class="text-info text-center">No closed issues</h4>
+ <?php } ?>
+
+ <table class="table table-hover">
+ <?php foreach ($closed_issues as $i) { ?>
+ <?=datalsts\issueListItem($i)?>
+ <?php } ?>
+ </table>
+ </div>
+ </div>
+
+ <?=stdpage\foot()?>
+ </body>
+</html>