Add ErrorPopup mod
Jeremy D

Jeremy D commited on 2022-04-15 18:34:04
Showing 7 changed files, with 602 additions and 0 deletions.

... ...
@@ -0,0 +1,190 @@
1
+checks:
2
+    php:
3
+        variable_existence: true
4
+        use_statement_alias_conflict: true
5
+        unused_variables: true
6
+        unused_properties: true
7
+        unused_parameters: true
8
+        unused_methods: true
9
+        unreachable_code: true
10
+        switch_fallthrough_commented: true
11
+        simplify_boolean_return: true
12
+        return_doc_comments: true
13
+        return_doc_comment_if_not_inferrable: true
14
+        require_scope_for_methods: true
15
+        require_php_tag_first: true
16
+        remove_extra_empty_lines: true
17
+        property_assignments: true
18
+        precedence_mistakes: true
19
+        precedence_in_conditions: true
20
+        parse_doc_comments: true
21
+        parameter_non_unique: true
22
+        parameter_doc_comments: true
23
+        param_doc_comment_if_not_inferrable: true
24
+        overriding_private_members: true
25
+        no_trailing_whitespace: true
26
+        no_short_open_tag: true
27
+        no_property_on_interface: true
28
+        no_non_implemented_abstract_methods: true
29
+        no_short_method_names:
30
+            minimum: '3'
31
+        no_goto: true
32
+        no_error_suppression: true
33
+        no_debug_code: true
34
+        more_specific_types_in_doc_comments: true
35
+        missing_arguments: true
36
+        method_calls_on_non_object: true
37
+        instanceof_class_exists: true
38
+        foreach_traversable: true
39
+        fix_use_statements:
40
+            remove_unused: true
41
+            preserve_multiple: false
42
+            preserve_blanklines: false
43
+            order_alphabetically: false
44
+        fix_line_ending: true
45
+        fix_doc_comments: true
46
+        encourage_shallow_comparison: true
47
+        duplication: true
48
+        deprecated_code_usage: true
49
+        deadlock_detection_in_loops: true
50
+        code_rating: true
51
+        closure_use_not_conflicting: true
52
+        closure_use_modifiable: true
53
+        catch_class_exists: true
54
+        avoid_duplicate_types: true
55
+        avoid_closing_tag: false
56
+        assignment_of_null_return: true
57
+        argument_type_checks: true
58
+        no_long_variable_names:
59
+            maximum: '40'
60
+        no_short_variable_names:
61
+            minimum: '3'
62
+        phpunit_assertions: true
63
+        remove_php_closing_tag: false
64
+        no_mixed_inline_html: false
65
+        require_braces_around_control_structures: false
66
+        psr2_control_structure_declaration: false
67
+        avoid_superglobals: false
68
+        security_vulnerabilities: false
69
+        no_exit: false
70
+coding_style:
71
+    php:
72
+        indentation:
73
+            general:
74
+                use_tabs: true
75
+                size: 4
76
+            switch:
77
+                indent_case: true
78
+        spaces:
79
+            general:
80
+                linefeed_character: newline
81
+            before_parentheses:
82
+                function_declaration: false
83
+                closure_definition: false
84
+                function_call: false
85
+                if: true
86
+                for: true
87
+                while: true
88
+                switch: true
89
+                catch: true
90
+                array_initializer: false
91
+            around_operators:
92
+                assignment: true
93
+                logical: true
94
+                equality: true
95
+                relational: true
96
+                bitwise: true
97
+                additive: true
98
+                multiplicative: true
99
+                shift: true
100
+                unary_additive: false
101
+                concatenation: true
102
+                negation: false
103
+            before_left_brace:
104
+                class: true
105
+                function: true
106
+                if: true
107
+                else: true
108
+                for: true
109
+                while: true
110
+                do: true
111
+                switch: true
112
+                try: true
113
+                catch: true
114
+                finally: true
115
+            before_keywords:
116
+                else: true
117
+                while: true
118
+                catch: true
119
+                finally: true
120
+            within:
121
+                brackets: false
122
+                array_initializer: false
123
+                grouping: false
124
+                function_call: false
125
+                function_declaration: false
126
+                if: false
127
+                for: false
128
+                while: false
129
+                switch: false
130
+                catch: false
131
+                type_cast: false
132
+            ternary_operator:
133
+                before_condition: true
134
+                after_condition: true
135
+                before_alternative: true
136
+                after_alternative: true
137
+                in_short_version: false
138
+            other:
139
+                before_comma: false
140
+                after_comma: true
141
+                before_semicolon: false
142
+                after_semicolon: true
143
+                after_type_cast: true
144
+        braces:
145
+            classes_functions:
146
+                class: new-line
147
+                function: new-line
148
+                closure: new-line
149
+            if:
150
+                opening: new-line
151
+                always: false
152
+                else_on_new_line: true
153
+            for:
154
+                opening: new-line
155
+                always: false
156
+            while:
157
+                opening: new-line
158
+                always: false
159
+            do_while:
160
+                opening: undefined
161
+                always: true
162
+                while_on_new_line: true
163
+            switch:
164
+                opening: new-line
165
+            try:
166
+                opening: new-line
167
+                catch_on_new_line: true
168
+                finally_on_new_line: true
169
+        upper_lower_casing:
170
+            keywords:
171
+                general: lower
172
+            constants:
173
+                true_false_null: lower
174
+
175
+
176
+build:
177
+    nodes:
178
+        analysis:
179
+            tests:
180
+                override:
181
+                    - php-scrutinizer-run
182
+            dependencies:
183
+                after:
184
+                    - git clone https://github.com/SimpleMachines/SMF2.1 smf
185
+
186
+filter:
187
+    dependency_paths:
188
+        - smf/
189
+    excluded_paths:
190
+        - '*.min.js'
... ...
@@ -0,0 +1,25 @@
1
+Developer's Certificate of Origin 1.1
2
+
3
+      By making a contribution to this project, I certify that:
4
+
5
+      (a) The contribution was created in whole or in part by me and I
6
+          have the right to submit it under the open source license
7
+          indicated in the file; or
8
+
9
+      (b) The contribution is based upon previous work that, to the best
10
+          of my knowledge, is covered under an appropriate open source
11
+          license and I have the right under that license to submit that
12
+          work with modifications, whether created in whole or in part
13
+          by me, under the same open source license (unless I am
14
+          permitted to submit under a different license), as indicated
15
+          in the file; or
16
+
17
+      (c) The contribution was provided directly to me by some other
18
+          person who certified (a), (b) or (c) and I have not modified
19
+          it.
20
+
21
+      (d) I understand and agree that this project and the contribution
22
+          are public and that a record of the contribution (including all
23
+          personal information I submit with it, including my sign-off) is
24
+          maintained indefinitely and may be redistributed consistent with
25
+          this project or the open source license(s) involved.
0 26
\ No newline at end of file
... ...
@@ -0,0 +1,328 @@
1
+<?php
2
+
3
+/**
4
+ * The class for Error Popup.  We don't actually need a class, but it prevents clashes
5
+ * @package ErrorPopup
6
+ * @author SleePy <sleepy @ simplemachines (dot) org>
7
+ * @copyright 2022
8
+ * @license 3-Clause BSD https://opensource.org/licenses/BSD-3-Clause
9
+ * @version 1.0
10
+ */
11
+class ErrorPopup
12
+{
13
+	/*
14
+	 * Inject into the menu system current action.
15
+	 * Nothing is returned, but we do inject some javascript and css.
16
+	 * Thise will clone the Admin Menu > Error Log menu into the top menu (User menu)
17
+	 *
18
+	 * @CalledBy: setupMenuContext - integrate_current_action
19
+	*/
20
+	public static function hook_current_action(): void
21
+	{
22
+		global $context, $scripturl;
23
+		static $calledOnce = false;
24
+
25
+		if ($calledOnce)
26
+			return;
27
+		$calledOnce = true;
28
+
29
+		// Don't bother with non admins.
30
+		if (empty($context['user']['is_admin']))
31
+			return;
32
+
33
+		/* SMF calls this is in setupMenuContext
34
+			var user_menus = new smc_PopupMenu();
35
+
36
+			This is the pure Javasript logic.
37
+			addInlineJavaScript('
38
+		orgErrorLog = document.querySelector(\'li.button_admin ul a[href*="errorlog"]\');
39
+		if (typeof orgErrorLog !== "undefined" && orgErrorLog !== null)
40
+		{
41
+			errorLI = orgErrorLog.parentElement.outerHTML;
42
+			errorLI += \'<div id="error_menu" class="top_menu scrollable" style="width: 90vw; max-width: 1200px;"></div>\';
43
+
44
+			topInfo = document.querySelector(\'ul#top_info\');
45
+			topInfo.innerHTML += errorLI;
46
+
47
+			document.querySelector(\'ul#top_info a[href*="errorlog"]\').setAttribute("id", "error_menu_top");
48
+
49
+			user_menus.add("error", "' . $scripturl . '?action=admin;area=logs;sa=errorlog");
50
+		}', true);
51
+		*/
52
+		addInlineJavaScript('
53
+
54
+			var errorLI = $("li.button_admin ul").find(\'a[href*="errorlog"]\').parent().prop("outerHTML");
55
+			errorLI += \'<div id="error_menu" class="top_menu scrollable" style="width: 90vw; max-width: 1200px;"></div>\';
56
+			$("ul#top_info").append(errorLI);
57
+			$("ul#top_info").find(\'a[href*="errorlog"]\').attr("id", "error_menu_top");
58
+			user_menus.add("error", "' . $scripturl . '?action=admin;area=logs;sa=errorlog");
59
+
60
+			function tryUpdateErrorCounter(xhr) {
61
+				var newErrorCount = xhr.getResponseHeader("x-smf-errorlogcount");
62
+
63
+				if (parseInt(newErrorCount))
64
+				{
65
+					$("ul#top_info a#error_menu_top .amt").html(parseInt(newErrorCount));
66
+				}
67
+			}
68
+			', true);
69
+
70
+		// Fixes a minor bug where the content isn't sized right.
71
+		addInlineCss('
72
+			div#error_menu .half_content { width: 49%;}
73
+		');
74
+
75
+		// Fix the admin login prompt to work.
76
+		addInlineJavaScript('
77
+			$("div#error_menu").on("submit", "#frmLogin", function(e) {
78
+				e.preventDefault();
79
+				e.stopPropagation();
80
+
81
+				form = $("div#error_menu #frmLogin");
82
+				$.ajax({
83
+					url: form.prop("action") + ";ajax",
84
+					method: "POST",
85
+					headers: {
86
+						"X-SMF-AJAX": 1
87
+					},
88
+					xhrFields: {
89
+						withCredentials: typeof allow_xhjr_credentials !== "undefined" ? allow_xhjr_credentials : false
90
+					},
91
+					data: form.serialize(),
92
+					success: function(data, status, xhr) {
93
+						if (typeof xhr != "undefined")
94
+							tryUpdateErrorCounter(xhr);
95
+
96
+						if (data.indexOf("<bo" + "dy") > -1) {
97
+							document.open();
98
+							document.write(data);
99
+							document.close();
100
+						}
101
+						else if (data.indexOf("<form") > -1) {
102
+							form.html($(data).html());
103
+							$("div#error_menu").customScrollbar().resize()
104
+						}
105
+						else
106
+							form.parent().html($(data).find(".roundframe").html());
107
+					},
108
+					error: function(xhr) {
109
+						var data = xhr.responseText;
110
+						if (data.indexOf("<bo" + "dy") > -1) {
111
+							document.open();
112
+							document.write(data);
113
+							document.close();
114
+						}
115
+						else
116
+							form.parent().html($(data).filter("#fatal_error").html());
117
+
118
+						$("div#error_menu").customScrollbar().resize()
119
+					}
120
+				});
121
+
122
+				return false;
123
+			});', true);
124
+
125
+		// Add the logic to make hyperlinks work when we are in the popup.
126
+		addInlineJavaScript('
127
+			$("div#error_menu").on("click", "div.information a, div.pagelinks a.nav_page, div.half_content > a[href*=\'errorlog\']", function(e) {
128
+				e.preventDefault();
129
+				e.stopPropagation();
130
+
131
+				currentLink = e.currentTarget.href;
132
+				contentBox = $("div#error_menu .overview");
133
+
134
+				$.ajax({
135
+					url: currentLink + ";ajax",
136
+					method: "GET",
137
+					headers: {
138
+						"X-SMF-AJAX": 1
139
+					},
140
+					xhrFields: {
141
+						withCredentials: typeof allow_xhjr_credentials !== "undefined" ? allow_xhjr_credentials : false
142
+					},
143
+					success: function(data, status, xhr) {
144
+						if (typeof xhr != "undefined")
145
+							tryUpdateErrorCounter(xhr);
146
+
147
+						if (data.indexOf("<bo" + "dy") > -1) {
148
+							document.open();
149
+							document.write(data);
150
+							document.close();
151
+						}
152
+						else
153
+							contentBox.html(data);
154
+
155
+						$("div#error_menu").customScrollbar().resize()
156
+					},
157
+					error: function(xhr) {
158
+						var data = xhr.responseText;
159
+						if (data.indexOf("<bo" + "dy") > -1) {
160
+							document.open();
161
+							document.write(data);
162
+							document.close();
163
+						}
164
+						else
165
+							contentBox.html($(data).filter("#fatal_error").html());
166
+
167
+						$("div#error_menu").customScrollbar().resize()
168
+					}
169
+				});
170
+			});', true);
171
+
172
+		// Fix the admin login prompt to work.
173
+		addInlineJavaScript('
174
+			$("div#error_menu").on("click", "form[action*=\'errorlog\'] input:submit", function(e) {
175
+				e.preventDefault();
176
+				e.stopPropagation();
177
+
178
+				button = $(e.currentTarget);
179
+				form = $("div#error_menu form[action*=\'errorlog\']");
180
+				formData = form.serialize();
181
+				if (button.attr("name") !== undefined) {
182
+					formData += "&"+button.attr("name")+"=";
183
+				}
184
+
185
+				$.ajax({
186
+					url: form.prop("action") + ";ajax",
187
+					method: "POST",
188
+					headers: {
189
+						"X-SMF-AJAX": 1
190
+					},
191
+					xhrFields: {
192
+						withCredentials: typeof allow_xhjr_credentials !== "undefined" ? allow_xhjr_credentials : false
193
+					},
194
+					data: formData,
195
+					success: function(data, status, xhr) {
196
+						if (typeof xhr != "undefined")
197
+							tryUpdateErrorCounter(xhr);
198
+
199
+						if (data.indexOf("<bo" + "dy") > -1) {
200
+							document.open();
201
+							document.write(data);
202
+							document.close();
203
+						}
204
+						else if (data.indexOf("<form") > -1) {
205
+							form.html($(data).html());
206
+							$("div#error_menu").customScrollbar().resize()
207
+						}
208
+						else
209
+							form.parent().html($(data).html());
210
+
211
+						$("div#error_menu").customScrollbar().resize()
212
+					},
213
+					error: function(xhr) {
214
+						var data = xhr.responseText;
215
+						if (data.indexOf("<bo" + "dy") > -1) {
216
+							document.open();
217
+							document.write(data);
218
+							document.close();
219
+						}
220
+						else
221
+							form.parent().html($(data).filter("#fatal_error").html());
222
+
223
+						$("div#error_menu").customScrollbar().resize()
224
+					}
225
+				});
226
+
227
+				return false;
228
+			});', true);
229
+
230
+		// Fix up the select code links.
231
+		addInlineJavaScript('
232
+			$("div#error_menu").on("click", ".smf_select_text", function(e) {
233
+				e.preventDefault();
234
+
235
+				// Do you want to target yourself?
236
+				var actOnElement = $(this).attr("data-actonelement");
237
+
238
+				return typeof actOnElement !== "undefined" ? smfSelectText(actOnElement, true) : smfSelectText(this);
239
+			});', true);
240
+	}
241
+
242
+	/*
243
+	 * When we are on the logs sub action, we allow a ajax action to strip html.
244
+	 *
245
+	 * @CalledBy: AdminLogs - integrate_manage_logs
246
+	*/
247
+	public static function hook_manage_logs(&$log_functions): void
248
+	{
249
+		global $context, $db_show_debug, $smcFunc;
250
+
251
+		// Not a AJAX request.
252
+		if (!isset($_REQUEST['ajax']))
253
+			return;
254
+
255
+		// Strip away layers and remove debugger.
256
+		$context['template_layers'] = array();
257
+		$db_show_debug = false;
258
+
259
+		// Sneak a header in here that we can use to update the counter.
260
+		if (!headers_sent())
261
+		{
262
+			$result = $smcFunc['db_query']('', '
263
+				SELECT COUNT(*)
264
+				FROM {db_prefix}log_errors',
265
+				[]
266
+			);
267
+			list ($num_errors) = $smcFunc['db_fetch_row']($result);
268
+			$smcFunc['db_free_result']($result);
269
+		
270
+			header('x-smf-errorlogcount: ' . $num_errors);
271
+		}
272
+	}
273
+
274
+	/*
275
+	 * When we are on the logs sub action, we allow a ajax action to strip html.
276
+	 *
277
+	 * @CalledBy: AdminLogs - integrate_manage_logs
278
+	*/
279
+	public static function hook_validateSession(&$types): void
280
+	{
281
+		global $context, $db_show_debug;
282
+
283
+		// Not a AJAX request.
284
+		if (
285
+			!isset($_REQUEST['ajax'], $_REQUEST['action'], $_REQUEST['area'])
286
+			|| $_REQUEST['action'] != 'admin'
287
+			|| $_REQUEST['area'] != 'logs'
288
+		)
289
+			return;
290
+
291
+		// Strip away layers and remove debugger.
292
+		$context['template_layers'] = array();
293
+		$db_show_debug = false;
294
+	}
295
+
296
+	/*
297
+	 * When we are on the logs sub action, we allow a ajax action to strip html.
298
+	 *
299
+	 * @CalledBy: redirectexit - integrate_redirect
300
+	*/
301
+	public static function hook_redirect(&$setLocation, &$refresh, &$permanent): void
302
+	{
303
+	///https://dev.sleepycode.com/smf21mod/index.php?action=admin;area=logs;sa=errorlog;start=0;ajax
304
+		// We are on a error log action such as delete.
305
+		if (
306
+			isset($_REQUEST['action'], $_REQUEST['area'], $_REQUEST['sa'], $_REQUEST['ajax'])
307
+			&& $_REQUEST['action'] == 'admin'
308
+			&& $_REQUEST['area'] == 'logs'
309
+			&& (
310
+				isset($_POST['delall'])
311
+				|| isset($_POST['delete'])
312
+			) 
313
+		)
314
+			$setLocation .= ';ajax';
315
+	}
316
+}
317
+
318
+/*
319
+ * This is a special loader function designed to make it easy to have this function without hooks for mod testing.
320
+ * You need to call this file in something very early in SMF, before reloadSettings.php is called
321
+*/
322
+if (!defined('SMF_INTEGRATION_SETTINGS'))
323
+	define('SMF_INTEGRATION_SETTINGS', json_encode([
324
+		'integrate_current_action' => 'ErrorPopup::hook_current_action',
325
+		'integrate_manage_logs' => 'ErrorPopup::hook_manage_logs',
326
+		'integrate_validateSession' => 'ErrorPopup::hook_validateSession',
327
+		'integrate_redirect' => 'ErrorPopup::hook_redirect',
328
+	]));
0 329
\ No newline at end of file
... ...
@@ -0,0 +1,29 @@
1
+BSD 3-Clause License
2
+
3
+Copyright (c) 2022, SleePy
4
+All rights reserved.
5
+
6
+Redistribution and use in source and binary forms, with or without
7
+modification, are permitted provided that the following conditions are met:
8
+
9
+1. Redistributions of source code must retain the above copyright notice, this
10
+   list of conditions and the following disclaimer.
11
+
12
+2. Redistributions in binary form must reproduce the above copyright notice,
13
+   this list of conditions and the following disclaimer in the documentation
14
+   and/or other materials provided with the distribution.
15
+
16
+3. Neither the name of the copyright holder nor the names of its
17
+   contributors may be used to endorse or promote products derived from
18
+   this software without specific prior written permission.
19
+
20
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
23
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
24
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
26
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
27
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
28
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
... ...
@@ -0,0 +1,5 @@
1
+This customization adds a popup window for the error log.  This was developed to easily check the error log while doing other development work.
2
+
3
+This avoids heavily trying to modify too much aspects of the code and requires javascript and JQuery to be functional.
4
+
5
+There is a feature in which you can add this early in in your PHP stack to auto include it in SMF without needing the hooks to be installed.  I won't provide any configuration examples for this, but one way to do this is to configure the php.ini setting auto_prepend_file for your forum directory.
0 6
\ No newline at end of file
... ...
@@ -0,0 +1,25 @@
1
+<!DOCTYPE package-info SYSTEM "http://www.simplemachines.org/xml/package-info">
2
+<package-info xmlns="http://www.simplemachines.org/xml/package-info" xmlns:smf="http://www.simplemachines.org/">
3
+	<id>sleepy:errorpup</id>
4
+	<name>Error Log Popup</name>
5
+	<version>1.0</version>
6
+	<type>modification</type>
7
+
8
+	<install for="SMF 2.1.*">
9
+		<readme>README.txt</readme>
10
+		<require-file name="ErrorPopup.php" destination="$sourcedir" />
11
+		<hook hook="integrate_current_action" function="ErrorPopup::hook_current_action" file="$sourcedir/ErrorPopup.php" />
12
+		<hook hook="integrate_manage_logs" function="ErrorPopup::hook_manage_logs" file="$sourcedir/ErrorPopup.php" />
13
+		<hook hook="integrate_validateSession" function="ErrorPopup::hook_validateSession" file="$sourcedir/ErrorPopup.php" />
14
+		<hook hook="integrate_redirect" function="ErrorPopup::hook_redirect" file="$sourcedir/ErrorPopup.php" />
15
+	</install>
16
+	
17
+	<uninstall for="SMF 2.1.*">
18
+		<hook reverse="true" hook="integrate_current_action" function="ErrorPopup::hook_current_action" file="$sourcedir/ErrorPopup.php" />
19
+		<hook reverse="true" hook="integrate_manage_logs" function="ErrorPopup::hook_manage_logs" file="$sourcedir/ErrorPopup.php" />
20
+		<hook reverse="true" hook="integrate_validateSession" function="ErrorPopup::hook_validateSession" file="$sourcedir/ErrorPopup.php" />
21
+		<hook reverse="true" hook="integrate_redirect" function="ErrorPopup::hook_redirect" file="$sourcedir/ErrorPopup.php" />
22
+		<remove-file name="$sourcedir/ErrorPopup.php" />
23
+	</uninstall>
24
+
25
+</package-info>
0 26
\ No newline at end of file
1 27