PluginProbe
404 Solution / 4.2.0
404 Solution v4.2.0
4.3.5 4.3.4 4.3.3 4.3.2 4.3.1 4.3.0 4.2.0 4.1.19 4.1.18 4.1.17 4.1.16 4.1.15 4.1.13 4.1.12 4.1.11 4.1.10 4.1.9 4.1.8 4.1.7 4.1.6 4.1.5 4.1.4 4.1.3 trunk 2.30.0 All 109 releases
404-solution / includes / php / wordpress / WPUtils.php

WPUtils.php in 404 Solution 4.2.0, at includes/php/wordpress/WPUtils.php

238 lines 8.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3
4 if (!defined('ABSPATH')) {
5 exit;
6 }
7
8 class ABJ_404_Solution_WPUtils {
9
10 /** @var array<string, callable> */
11 static $actionsAlreadyAdded = array();
12
13 /** Wrapper for the add_action function that throws an exception if the action already exists.
14 *
15 * @global type $wp_filter
16 * @param string $tag The name of the action to which the $function_to_add is hooked.
17 * @param callable $function_to_add The name of the function you wish to be called.
18 * @param int $priority Optional. Used to specify the order in which the functions
19 * associated with a particular action are executed. Default 10.
20 * Lower numbers correspond with earlier execution,
21 * and functions with the same priority are executed
22 * in the order in which they were added to the action.
23 * @param int $accepted_args Optional. The number of arguments the function accepts. Default 1.
24 * @return mixed Whatever add_action() returns.
25 */
26 static function safeAddAction($tag, $function_to_add, $priority = 10, $accepted_args = 1) {
27 global $wp_filter;
28
29 // If we've already added the action then make sure it's the SAME action that we've already
30 // added and that we're not overwriting something.
31 // This isn't strictly necessary but it's cleaner to have
32 // one function instead of two.
33 if (array_key_exists($tag, self::$actionsAlreadyAdded)) {
34 // we already saw the action. check if they're the same.
35 $shouldError = true;
36 if (array_key_exists($tag, self::$actionsAlreadyAdded)) {
37 $functionAlreadyAdded = self::$actionsAlreadyAdded[$tag];
38 // Callables stored here are always arrays ([class/object, method])
39 $existingArr = is_array($functionAlreadyAdded) ? $functionAlreadyAdded : array($functionAlreadyAdded);
40 $newArr = is_array($function_to_add) ? $function_to_add : array($function_to_add);
41 $differences = array_udiff($existingArr, $newArr,
42 array(self::class, 'compareAjaxActionArrays'));
43
44 // any differences mean we accidentally registered the same action to do
45 // two different things. If the differences are 0 then we've accidentally registered
46 // the same action multiple times.
47 if (empty($differences)) {
48 $shouldError = false;
49 }
50 }
51
52 if ($shouldError) {
53 throw new \Exception("I can't add the action " . $tag .
54 " because someone has already registered that tag. Here's what the existing action looks like: " .
55 (string)json_encode($wp_filter[$tag], JSON_PRETTY_PRINT));
56 }
57 }
58
59 self::$actionsAlreadyAdded[$tag] = $function_to_add;
60 return add_action($tag, $function_to_add, $priority, $accepted_args);
61 }
62
63 /**
64 * @param mixed $a
65 * @param mixed $b
66 * @return int
67 */
68 private static function compareAjaxActionArrays($a, $b): int {
69 $str1 = self::getValueOrObjectClass($a);
70 $str2 = self::getValueOrObjectClass($b);
71
72 return strcmp($str1, $str2);
73 }
74
75 /**
76 * Return a string representation of the given WP_Error object.
77 *
78 * If the argument is not a WP_Error object, return a string indicating that.
79 *
80 * Otherwise, return a string that includes the following information:
81 *
82 * - code: the error code
83 * - message: the error message
84 * - data: the error data (using var_export)
85 * - all error codes: each code and its associated messages
86 * - backtrace: the backtrace at the time of calling (using print_r on debug_backtrace)
87 *
88 * If any of the above information cannot be retrieved, include an error message
89 * indicating that.
90 *
91 * @param WP_Error $error The object to stringify.
92 * @return string A string representation of the object.
93 */
94 static function stringify_wp_error($error) {
95 $output = "WP_Error object:\n";
96
97 try {
98 $output .= "Code: " . $error->get_error_code() . "\n";
99 } catch (Throwable $e) {
100 $output .= "Code: [error getting code: " . $e->getMessage() . "]\n";
101 }
102
103 try {
104 $output .= "Message: " . $error->get_error_message() . "\n";
105 } catch (Throwable $e) {
106 $output .= "Message: [error getting message: " . $e->getMessage() . "]\n";
107 }
108
109 try {
110 $data = $error->get_error_data();
111 if (is_array($data) || is_object($data)) {
112 $output .= "Data: " . print_r($data, true) . "\n";
113 } else {
114 $output .= "Data: " . var_export($data, true) . "\n";
115 }
116 } catch (Throwable $e) {
117 $output .= "Data: [error getting data: " . $e->getMessage() . "]\n";
118 }
119
120 try {
121 $codes = $error->get_error_codes();
122 $output .= "All error codes:\n";
123 foreach ($codes as $code) {
124 try {
125 $messages = $error->get_error_messages($code);
126 $output .= "- $code: " . implode("; ", $messages) . "\n";
127 } catch (Throwable $e) {
128 $output .= "- $code: [error getting messages: " . $e->getMessage() . "]\n";
129 }
130 }
131 } catch (Throwable $e) {
132 $output .= "All error codes: [error fetching codes: " . $e->getMessage() . "]\n";
133 }
134
135 try {
136 $output .= "Backtrace:\n" . print_r(debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS), true) . "\n";
137 } catch (Throwable $e) {
138 $output .= "Backtrace: [error generating backtrace: " . $e->getMessage() . "]\n";
139 }
140
141 return $output;
142 }
143
144 /**
145 * Gets a string representation of a callable for comparison.
146 *
147 * @param mixed $callable The callable (string function name, array [object/class, method], Closure).
148 * @return string A string representation.
149 */
150 private static function getValueOrObjectClass($callable) {
151 if (is_string($callable)) {
152 // Simple function name
153 return trim($callable);
154 } elseif (is_array($callable) && count($callable) === 2) {
155 // Array callable: [object/class, method]
156 $classOrObject = $callable[0];
157 $method = is_string($callable[1]) ? $callable[1] : '';
158 if (is_object($classOrObject)) {
159 // Instance method: [new ClassName(), 'methodName']
160 return get_class($classOrObject) . '::' . trim($method);
161 } elseif (is_string($classOrObject)) {
162 // Static method: ['ClassName', 'methodName']
163 return trim($classOrObject) . '::' . trim($method);
164 }
165 } elseif ($callable instanceof \Closure) {
166 // It's a Closure (anonymous function). Comparing these reliably is tricky.
167 // Returning a generic placeholder might be sufficient if you don't expect
168 // multiple different closures on the same hook tag.
169 // Alternatively, use spl_object_hash for a unique ID per instance,
170 // but note this hash can change between requests.
171 return 'Closure#' . spl_object_hash($callable);
172 }
173
174 // Fallback for unexpected types - you might want to log or throw an error here
175 return serialize($callable);
176 }
177
178 /** Set the version to the file date/time.
179 * @param string $handle
180 * @param string $src
181 * @param array<int, string> $deps
182 * @param string|bool $ver
183 * @param bool $in_footer
184 * @return void
185 */
186 static function my_wp_enq_scrpt(string $handle, string $src = '', array $deps = array(),
187 $ver = false, bool $in_footer = false): void {
188
189 $ver = ABJ_404_Solution_WPUtils::createUpdatedVersionNumber($src, $ver);
190
191 wp_enqueue_script($handle, $src, $deps, $ver, $in_footer);
192 }
193
194 /** Set the version to the file date/time.
195 * @param string $handle
196 * @param string $src
197 * @param array<int, string> $deps
198 * @param string|bool $ver
199 * @param string $media
200 * @return void
201 */
202 static function my_wp_enq_style(string $handle, string $src = '', array $deps = array(), $ver = false, string $media = 'all'): void {
203 $ver = ABJ_404_Solution_WPUtils::createUpdatedVersionNumber($src, $ver);
204
205 wp_enqueue_style($handle, $src, $deps, $ver, $media);
206 }
207
208 /** This forces the version number of a file to be the modified date of that
209 * file. It gets the local file location by changing the URL, gets the modified
210 * date, then returns that date as a string for the version number.
211 * @param string $src
212 * @param string|bool $ver
213 * @return string|false
214 */
215 static function createUpdatedVersionNumber($src = '', $ver = false) {
216 // if there's no version number and the file is for our plugin
217 if ($ver === false && ($src != null && $src != '' &&
218 strpos($src, ABJ404_URL) === 0)) {
219
220 // get the local file path by changing the URL.
221 $correctedFilePath = str_replace(ABJ404_URL, ABJ404_PATH, $src);
222 // get the modified date as the version (guard missing files in tests/odd installs).
223 if (is_string($correctedFilePath) && is_file($correctedFilePath)) {
224 $mtime = @filemtime($correctedFilePath);
225 if ($mtime !== false) {
226 $ver = date('Y-m-d_H:i:s', $mtime);
227 }
228 }
229 }
230
231 if (is_string($ver)) {
232 return $ver;
233 }
234 return false;
235 }
236
237 }
238