@@ -34,15 +34,79 @@ public static function die(mixed $x = null, bool $show_user = false): never
3434 }
3535 }
3636
37+ /*
38+ send HTTP header, set HTTP response code,
39+ print a message just in case the browser fails to redirect if PHP is not being run from the CLI,
40+ and then die
41+ */
3742 public static function redirect (?string $ dest = null ): never
3843 {
3944 $ dest ??= pathJoin (CONFIG ["site " ]["prefix " ], $ _SERVER ["REQUEST_URI " ]);
4045 $ dest = htmlspecialchars ($ dest );
4146 header ("Location: $ dest " );
42- self ::errorToUser ("Redirect failed, click <a href=' $ dest'>here</a> to continue. " , 302 );
47+ http_response_code (302 );
48+ if (CONFIG ["site " ]["enable_redirect_message " ]) {
49+ echo "If you're reading this message, then your browser has failed to redirect you " .
50+ "to the proper destination. click <a href=' $ dest'>here</a> to continue. " ;
51+ }
4352 self ::die ();
4453 }
4554
55+ /*
56+ generates a unique error ID, writes to error log, and then:
57+ if "html_errors" is disabled in the PHP config file:
58+ prints a message to stdout and dies
59+ else, if the user is doing an HTTP POST:
60+ registers a message in the user's session and issues a redirect to display that message
61+ else:
62+ prints an HTML message to stdout, sets an HTTP response code, and dies
63+ we can't always do a redirect or else we could risk an infinite loop.
64+ */
65+ public static function gracefulDie (
66+ string $ log_title ,
67+ string $ log_message ,
68+ string $ user_message_title ,
69+ string $ user_message_body ,
70+ ?\Throwable $ error = null ,
71+ int $ http_response_code = 200 ,
72+ mixed $ data = null ,
73+ ): never {
74+ $ errorid = uniqid ();
75+ $ suffix = sprintf (
76+ "Please notify a Unity admin at %s. Error ID: %s. " ,
77+ CONFIG ["mail " ]["support " ],
78+ $ errorid ,
79+ );
80+ $ user_message_title = htmlspecialchars ($ user_message_title );
81+ $ user_message_body = htmlspecialchars ($ user_message_body );
82+ if (strlen ($ user_message_body ) === 0 ) {
83+ $ user_message_body = $ suffix ;
84+ } else {
85+ $ user_message_body .= " $ suffix " ;
86+ }
87+ self ::errorLog ($ log_title , $ log_message , data: $ data , error: $ error , errorid: $ errorid );
88+ if (ini_get ("html_errors " ) !== "1 " ) {
89+ self ::die ("$ user_message_title -- $ user_message_body " );
90+ } elseif (($ _SERVER ["REQUEST_METHOD " ] ?? "" ) == "POST " ) {
91+ self ::messageError ($ user_message_title , $ user_message_body );
92+ self ::redirect ();
93+ } else {
94+ if (!headers_sent ()) {
95+ http_response_code ($ http_response_code );
96+ }
97+ // text may not be shown in the webpage in an obvious way, so make a popup
98+ self ::alert ("$ user_message_title -- $ user_message_body " );
99+ echo "<h1> $ user_message_title</h1><p> $ user_message_body</p> " ;
100+ // display_errors should not be enabled in production
101+ if (!is_null ($ error ) && ini_get ("display_errors " ) === "1 " ) {
102+ echo "<table> " ;
103+ echo $ error ->xdebug_message ;
104+ echo "</table> " ;
105+ }
106+ self ::die ();
107+ }
108+ }
109+
46110 // $data must be JSON serializable
47111 public static function errorLog (
48112 string $ title ,
@@ -72,6 +136,7 @@ public static function errorLog(
72136 }
73137 $ output ["REMOTE_USER " ] = $ _SERVER ["REMOTE_USER " ] ?? null ;
74138 $ output ["REMOTE_ADDR " ] = $ _SERVER ["REMOTE_ADDR " ] ?? null ;
139+ $ output ["_REQUEST " ] = $ _REQUEST ;
75140 if (!is_null ($ errorid )) {
76141 $ output ["errorid " ] = $ errorid ;
77142 }
@@ -94,69 +159,61 @@ private static function throwableToArray(\Throwable $t): array
94159 return $ output ;
95160 }
96161
97- private static function errorToUser (
98- string $ msg ,
99- int $ http_response_code ,
100- ?string $ errorid = null ,
101- ): void {
102- if (!CONFIG ["site " ]["enable_error_to_user " ]) {
103- return ;
104- }
105- $ notes = "Please notify a Unity admin at " . CONFIG ["mail " ]["support " ] . ". " ;
106- if (!is_null ($ errorid )) {
107- $ notes = $ notes . " Error ID: $ errorid. " ;
108- }
109- if (!headers_sent ()) {
110- http_response_code ($ http_response_code );
111- }
112- // text may not be shown in the webpage in an obvious way, so make a popup
113- self ::alert ("$ msg $ notes " );
114- echo "<h1> $ msg</h1><p> $ notes</p> " ;
115- }
116-
117162 public static function badRequest (
118- string $ message ,
163+ string $ log_message ,
119164 ?\Throwable $ error = null ,
120165 ?array $ data = null ,
121166 ): never {
122- $ errorid = uniqid ();
123- self ::errorToUser ("Invalid requested action or submitted data. " , 400 , $ errorid );
124- self ::errorLog ("bad request " , $ message , $ errorid , $ error , $ data );
125- self ::die ($ message );
167+ self ::gracefulDie (
168+ "bad request " ,
169+ $ log_message ,
170+ "Invalid requested action or submitted data. " ,
171+ "" ,
172+ error: $ error ,
173+ http_response_code: 400 ,
174+ data: $ data ,
175+ );
126176 }
127177
128178 public static function forbidden (
129- string $ message ,
179+ string $ log_message ,
130180 ?\Throwable $ error = null ,
131181 ?array $ data = null ,
132182 ): never {
133- $ errorid = uniqid ();
134- self ::errorToUser ("Permission denied. " , 403 , $ errorid );
135- self ::errorLog ("forbidden " , $ message , $ errorid , $ error , $ data );
136- self ::die ($ message );
183+ self ::gracefulDie (
184+ "forbidden " ,
185+ $ log_message ,
186+ "Permission denied. " ,
187+ "" ,
188+ error: $ error ,
189+ http_response_code: 403 ,
190+ data: $ data ,
191+ );
137192 }
138193
139194 public static function internalServerError (
140- string $ message ,
195+ string $ log_message ,
141196 ?\Throwable $ error = null ,
142197 ?array $ data = null ,
143198 ): never {
144- $ errorid = uniqid ();
145- self :: errorToUser ( " An internal server error has occurred. " , 500 , $ errorid );
146- self :: errorLog ( " internal server error " , $ message , $ errorid , $ error , $ data );
147- if (! is_null ( $ error ) && ini_get ( " display_errors " ) && ini_get ( " html_errors " )) {
148- echo " <table> " ;
149- echo $ error-> xdebug_message ;
150- echo " </table> " ;
151- }
152- self :: die ( $ message );
199+ self :: gracefulDie (
200+ " internal server error " ,
201+ $ log_message ,
202+ " An internal server error has occurred. " ,
203+ "" ,
204+ error: $ error,
205+ http_response_code: 500 ,
206+ data: $ data ,
207+ );
153208 }
154209
155210 // https://www.php.net/manual/en/function.set-exception-handler.php
156211 public static function exceptionHandler (\Throwable $ e ): void
157212 {
158- ini_set ("log_errors " , true ); // in case something goes wrong and error is not logged
159- self ::internalServerError ("An internal server error has occurred. " , error: $ e );
213+ // we disable log_errors before we enable this exception handler to avoid duplicate logging
214+ // if this exception handler itself fails, information will be lost unless we re-enable it
215+ ini_set ("log_errors " , true );
216+ self ::internalServerError ("" , error: $ e );
160217 }
161218
162219 public static function errorHandler (int $ severity , string $ message , string $ file , int $ line )
0 commit comments