Skip to content

Commit f253caa

Browse files
message system (#372)
Co-authored-by: Copilot <[email protected]>
1 parent 24a8df5 commit f253caa

File tree

10 files changed

+219
-53
lines changed

10 files changed

+219
-53
lines changed

resources/init.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,10 @@
3434
$WEBHOOK = new UnityWebhook();
3535
$GITHUB = new UnityGithub();
3636

37+
if (!array_key_exists("messages", $_SESSION)) {
38+
$_SESSION["messages"] = [];
39+
}
40+
3741
if (isset($_SERVER["REMOTE_USER"])) {
3842
// Check if SSO is enabled on this page
3943
$SSO = UnitySSO::getSSO();

resources/lib/UnityHTTPD.php

Lines changed: 75 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,16 @@
44

55
use UnityWebPortal\lib\exceptions\NoDieException;
66
use UnityWebPortal\lib\exceptions\ArrayKeyException;
7+
use RuntimeException;
8+
9+
enum UnityHTTPDMessageLevel: string
10+
{
11+
case DEBUG = "debug";
12+
case INFO = "info";
13+
case SUCCESS = "success";
14+
case WARNING = "warning";
15+
case ERROR = "error";
16+
}
717

818
class UnityHTTPD
919
{
@@ -24,8 +34,10 @@ public static function die(mixed $x = null, bool $show_user = false): never
2434
}
2535
}
2636

27-
public static function redirect($dest): never
37+
public static function redirect(?string $dest = null): never
2838
{
39+
$dest ??= pathJoin(CONFIG["site"]["prefix"], $_SERVER["REQUEST_URI"]);
40+
$dest = htmlspecialchars($dest);
2941
header("Location: $dest");
3042
self::errorToUser("Redirect failed, click <a href='$dest'>here</a> to continue.", 302);
3143
self::die();
@@ -196,4 +208,66 @@ public static function alert(string $message): void
196208
// jsonEncode escapes quotes
197209
echo "<script type='text/javascript'>alert(" . \jsonEncode($message) . ");</script>";
198210
}
211+
212+
private static function ensureSessionMessagesSanity()
213+
{
214+
if (!isset($_SESSION)) {
215+
throw new RuntimeException('$_SESSION is unset');
216+
}
217+
if (!array_key_exists("messages", $_SESSION)) {
218+
self::errorLog(
219+
"invalid session messages",
220+
'array key "messages" does not exist for $_SESSION',
221+
data: ['$_SESSION' => $_SESSION],
222+
);
223+
$_SESSION["messages"] = [];
224+
}
225+
if (!is_array($_SESSION["messages"])) {
226+
$type = gettype($_SESSION["messages"]);
227+
self::errorLog(
228+
"invalid session messages",
229+
"\$_SESSION['messages'] is type '$type', not an array",
230+
data: ['$_SESSION' => $_SESSION],
231+
);
232+
$_SESSION["messages"] = [];
233+
}
234+
}
235+
236+
public static function message(string $title, string $body, UnityHTTPDMessageLevel $level)
237+
{
238+
self::ensureSessionMessagesSanity();
239+
array_push($_SESSION["messages"], [$title, $body, $level]);
240+
}
241+
242+
public static function messageDebug(string $title, string $body)
243+
{
244+
return self::message($title, $body, UnityHTTPDMessageLevel::DEBUG);
245+
}
246+
public static function messageInfo(string $title, string $body)
247+
{
248+
return self::message($title, $body, UnityHTTPDMessageLevel::INFO);
249+
}
250+
public static function messageSuccess(string $title, string $body)
251+
{
252+
return self::message($title, $body, UnityHTTPDMessageLevel::SUCCESS);
253+
}
254+
public static function messageWarning(string $title, string $body)
255+
{
256+
return self::message($title, $body, UnityHTTPDMessageLevel::WARNING);
257+
}
258+
public static function messageError(string $title, string $body)
259+
{
260+
return self::message($title, $body, UnityHTTPDMessageLevel::ERROR);
261+
}
262+
263+
public static function getMessages()
264+
{
265+
self::ensureSessionMessagesSanity();
266+
return $_SESSION["messages"];
267+
}
268+
269+
public static function clearMessages()
270+
{
271+
$_SESSION["messages"] = [];
272+
}
199273
}

resources/lib/utils.php

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,3 +70,15 @@ function mbDetectEncoding(string $string, ?array $encodings = null, mixed $_ = n
7070
}
7171
return $output;
7272
}
73+
74+
/* https://stackoverflow.com/a/15575293/18696276 */
75+
function pathJoin()
76+
{
77+
$paths = [];
78+
foreach (func_get_args() as $arg) {
79+
if ($arg !== "") {
80+
$paths[] = $arg;
81+
}
82+
}
83+
return preg_replace("#/+#", "/", join("/", $paths));
84+
}

resources/templates/header.php

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
// header also needs to handle POST data. So this header does the PRG redirect
1717
// for all pages.
1818
unset($_POST); // unset ensures that header must not come before POST handling
19-
UnityHTTPD::redirect(CONFIG["site"]["prefix"] . $_SERVER['REQUEST_URI']);
19+
UnityHTTPD::redirect();
2020
}
2121

2222
if (isset($SSO)) {
@@ -56,6 +56,7 @@
5656
<link rel='stylesheet' type='text/css' href='$prefix/css/modal.css'>
5757
<link rel='stylesheet' type='text/css' href='$prefix/css/tables.css'>
5858
<link rel='stylesheet' type='text/css' href='$prefix/css/filters.css'>
59+
<link rel='stylesheet' type='text/css' href='$prefix/css/messages.css'>
5960
";
6061
?>
6162

@@ -133,23 +134,28 @@
133134
<button style="position: absolute; right: 10px; top: 10px;" class="btnClose"></button>
134135
</div>
135136
<div class="modalBody"></div>
136-
<div class="modalMessages"></div>
137-
<div class="modalButtons">
138-
<div class='buttonList messageButtons' style='display: none;'>
139-
<button class='btnOkay'>Okay</button>
140-
</div>
141-
<div class='buttonList yesnoButtons' style='display: none;'>
142-
<button class='btnYes'>Yes</button>
143-
<button class='btnNo'>No</button>
144-
</div>
145-
</div>
146137
</div>
147138
</div>
148139
<script src="<?php echo CONFIG["site"]["prefix"]; ?>/js/modal.js"></script>
149140

150141
<main>
151142

152143
<?php
144+
foreach (UnityHTTPD::getMessages() as [$title, $body, $level]) {
145+
echo sprintf(
146+
"
147+
<div class='message %s'>
148+
<h3>%s</h3>
149+
<p>%s</p>
150+
<button onclick=\"this.parentElement.style.display='none';\">×</button>
151+
</div>
152+
",
153+
htmlspecialchars($level->value),
154+
htmlspecialchars($title),
155+
htmlspecialchars($body)
156+
);
157+
}
158+
UnityHTTPD::clearMessages();
153159
if (
154160
isset($_SESSION["is_admin"])
155161
&& $_SESSION["is_admin"]

test/functional/PIMemberRequestTest.php

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
<?php
22

33
use PHPUnit\Framework\TestCase;
4+
use UnityWebPortal\lib\UnityHTTPDMessageLevel;
45
use UnityWebPortal\lib\UnitySQL;
6+
use UnityWebPortal\lib\UnityHTTPD;
57

68
class PIMemberRequestTest extends TestCase
79
{
@@ -43,8 +45,14 @@ public function testRequestMembership()
4345
$this->assertTrue($SQL->requestExists($uid, $gid));
4446
$this->cancelRequest($gid);
4547
$this->assertFalse($SQL->requestExists($uid, $gid));
48+
UnityHTTPD::clearMessages();
4649
$this->requestMembership("asdlkjasldkj");
47-
$this->assertContains("This PI doesn't exist", $_SESSION["MODAL_ERRORS"]);
50+
assertMessageExists(
51+
$this,
52+
UnityHTTPDMessageLevel::ERROR,
53+
"/.*/",
54+
"/^This PI doesn't exist$/",
55+
);
4856
$this->requestMembership($pi_group->getOwner()->getMail());
4957
$this->assertTrue($SQL->requestExists($uid, $gid));
5058
} finally {

test/phpunit-bootstrap.php

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,9 @@
2525
require_once __DIR__ . "/../resources/lib/exceptions/EncodingConversionException.php";
2626

2727
use UnityWebPortal\lib\UnityGroup;
28+
use UnityWebPortal\lib\UnityHTTPD;
29+
use UnityWebPortal\lib\UnityHTTPDMessageLevel;
30+
use PHPUnit\Framework\TestCase;
2831

2932
$_SERVER["HTTP_HOST"] = "phpunit"; // used for config override
3033
require_once __DIR__ . "/../resources/config.php";
@@ -323,3 +326,29 @@ function getAdminUser()
323326
{
324327
return ["[email protected]", "foo", "bar", "[email protected]"];
325328
}
329+
330+
function assertMessageExists(
331+
TestCase $test_case,
332+
UnityHTTPDMessageLevel $level,
333+
string $title_regex,
334+
string $body_regex,
335+
) {
336+
$messages = UnityHTTPD::getMessages();
337+
$error_msg = sprintf(
338+
"message(level='%s' title_regex='%s' body_regex='%s'), not found. found messages: %s",
339+
$level->value,
340+
$title_regex,
341+
$body_regex,
342+
jsonEncode($messages),
343+
);
344+
$messages_with_title = array_filter($messages, fn($x) => preg_match($title_regex, $x[0]));
345+
$messages_with_title_and_body = array_filter(
346+
$messages_with_title,
347+
fn($x) => preg_match($body_regex, $x[1]),
348+
);
349+
$messages_with_title_and_body_and_level = array_filter(
350+
$messages_with_title_and_body,
351+
fn($x) => $x[2] == $level,
352+
);
353+
$test_case->assertNotEmpty($messages_with_title_and_body_and_level, $error_msg);
354+
}

webroot/css/messages.css

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
.message {
2+
border-radius: 10px;
3+
padding: 10px 40px 10px 40px;
4+
/* needed for button position: absolute */
5+
position: relative;
6+
text-align: center;
7+
/* width: fit-content; */
8+
/* subtract padding from indented width */
9+
width: 90% - 80px;
10+
margin-left: auto;
11+
margin-right: auto;
12+
margin-bottom: 20px;
13+
}
14+
15+
.message h3 {
16+
margin: 0;
17+
}
18+
19+
.message.debug {
20+
color: #856404;
21+
background-color: #fff3cd;
22+
}
23+
24+
.message.success {
25+
color: #155724;
26+
background-color: #d4edda;
27+
}
28+
29+
.message.info {
30+
color: #0c5460;
31+
background-color: #d1ecf1;
32+
}
33+
34+
.message.warning {
35+
color: #856404;
36+
background-color: #fff3cd;
37+
}
38+
39+
.message.error {
40+
color: #721c24;
41+
background-color: #f8d7da;
42+
}
43+
44+
.message button {
45+
position: absolute;
46+
top: 0;
47+
right: 0;
48+
background-color: inherit;
49+
color: inherit;
50+
font-size: 2rem;
51+
border: none;
52+
}

webroot/css/modal.css

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -30,17 +30,7 @@ span.modalTitle {
3030
font-size: 13pt;
3131
}
3232

33-
div.modalMessages {
34-
color: var(--color-text-failure);
35-
font-size: 11pt;
36-
}
37-
38-
div.modalMessages > * {
39-
margin-top: 7px;
40-
display: block;
41-
}
42-
43-
div.modalBody > * {
33+
div.modalBody>* {
4434
margin: 0;
4535
}
4636

webroot/js/modal.js

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
1-
function openModal(title, link, message = "") {
1+
function openModal(title, link) {
22
$("span.modalTitle").html(title);
3-
$("div.modalMessages").html(message);
43
$.ajax({
54
url: link,
65
success: function (result) {

0 commit comments

Comments
 (0)