PluginProbe
ActivityPub / 8.0.1
ActivityPub v8.0.1
9.3.1 9.3.0 9.2.2 9.2.1 9.2.0 9.1.0 9.0.2 9.0.1 9.0.0 8.3.0 8.2.1 8.2.0 8.1.1 1.0.5 1.0.6 1.0.7 1.0.8 1.0.9 1.1.0 1.2.0 1.3.0 2.0.0 2.0.1 2.1.0 2.1.1 All 160 releases
activitypub / includes / functions.php

functions.php in ActivityPub 8.0.1, at includes/functions.php

319 lines 8.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Functions file.
4 *
5 * General utility functions for the ActivityPub plugin.
6 *
7 * @package Activitypub
8 */
9
10 namespace Activitypub;
11
12 /**
13 * Convert a string from camelCase to snake_case.
14 *
15 * @param string $input The string to convert.
16 *
17 * @return string The converted string.
18 */
19 function camel_to_snake_case( $input ) {
20 return strtolower( preg_replace( '/(?<!^)[A-Z]/', '_$0', $input ) );
21 }
22
23 /**
24 * Convert a string from snake_case to camelCase.
25 *
26 * @param string $input The string to convert.
27 *
28 * @return string The converted string.
29 */
30 function snake_to_camel_case( $input ) {
31 return lcfirst( str_replace( '_', '', ucwords( $input, '_' ) ) );
32 }
33
34 /**
35 * Convert seconds to ISO 8601 duration format.
36 *
37 * @param int $seconds The duration in seconds.
38 *
39 * @return string The duration in ISO 8601 format (e.g., "PT1H23M45S").
40 */
41 function seconds_to_iso8601( $seconds ) {
42 $seconds = (int) $seconds;
43
44 if ( $seconds <= 0 ) {
45 return 'PT0S';
46 }
47
48 $hours = floor( $seconds / 3600 );
49 $minutes = floor( ( $seconds % 3600 ) / 60 );
50 $secs = $seconds % 60;
51
52 $duration = 'PT';
53
54 if ( $hours > 0 ) {
55 $duration .= $hours . 'H';
56 }
57
58 if ( $minutes > 0 ) {
59 $duration .= $minutes . 'M';
60 }
61
62 if ( $secs > 0 || ( 0 === $hours && 0 === $minutes ) ) {
63 $duration .= $secs . 'S';
64 }
65
66 return $duration;
67 }
68
69 /**
70 * Check if a site supports the block editor.
71 *
72 * @return boolean True if the site supports the block editor, false otherwise.
73 */
74 function site_supports_blocks() {
75 /**
76 * Allow plugins to disable block editor support,
77 * thus disabling blocks registered by the ActivityPub plugin.
78 *
79 * @param boolean $supports_blocks True if the site supports the block editor, false otherwise.
80 */
81 return apply_filters( 'activitypub_site_supports_blocks', true );
82 }
83
84 /**
85 * Check if data is valid JSON.
86 *
87 * @deprecated 7.1.0 Use {@see \json_decode}.
88 *
89 * @param string $data The data to check.
90 *
91 * @return boolean True if the data is JSON, false otherwise.
92 */
93 function is_json( $data ) {
94 \_deprecated_function( __FUNCTION__, '7.1.0', 'json_decode' );
95
96 return \is_array( \json_decode( $data, true ) );
97 }
98
99 /**
100 * Check whether a blog is public based on the `blog_public` option.
101 *
102 * @return bool True if public, false if not
103 */
104 function is_blog_public() {
105 /**
106 * Filter whether the blog is public.
107 *
108 * @param bool $public Whether the blog is public.
109 */
110 return (bool) apply_filters( 'activitypub_is_blog_public', \get_option( 'blog_public', 1 ) );
111 }
112
113 /**
114 * Get the masked WordPress version to only show the major and minor version.
115 *
116 * @return string The masked version.
117 */
118 function get_masked_wp_version() {
119 // Only show the major and minor version.
120 $version = get_bloginfo( 'version' );
121 // Strip the RC or beta part.
122 $version = preg_replace( '/-.*$/', '', $version );
123 $version = explode( '.', $version );
124 $version = array_slice( $version, 0, 2 );
125
126 return implode( '.', $version );
127 }
128
129 /**
130 * Check if a plugin is active, loading plugin.php if necessary.
131 *
132 * This is a wrapper around the core is_plugin_active() function that ensures
133 * the function is available by loading wp-admin/includes/plugin.php if needed.
134 * This is useful when checking plugin status outside of the admin context.
135 *
136 * @param string $plugin Plugin basename (e.g., 'plugin-folder/plugin-file.php').
137 *
138 * @return bool True if the plugin is active, false otherwise.
139 */
140 function is_plugin_active( $plugin ) {
141 // Include plugin.php if not already loaded (needed for core is_plugin_active).
142 if ( ! \function_exists( 'is_plugin_active' ) ) {
143 require_once ABSPATH . 'wp-admin/includes/plugin.php';
144 }
145
146 return \is_plugin_active( $plugin );
147 }
148
149 /**
150 * Returns the website hosts allowed to credit this blog.
151 *
152 * @return array|null The attribution domains or null if not found.
153 */
154 function get_attribution_domains() {
155 if ( '1' !== \get_option( 'activitypub_use_opengraph', '1' ) ) {
156 return null;
157 }
158
159 $domains = \get_option( 'activitypub_attribution_domains', home_host() );
160 $domains = explode( PHP_EOL, $domains );
161
162 if ( ! $domains ) {
163 $domains = null;
164 }
165
166 return $domains;
167 }
168
169 /**
170 * Change the display of large numbers on the site.
171 *
172 * @author Jeremy Herve
173 *
174 * @see https://wordpress.org/support/topic/abbreviate-numbers-with-k/
175 *
176 * @param string $formatted Converted number in string format.
177 * @param float $number The number to convert based on locale.
178 *
179 * @return string Converted number in string format.
180 */
181 function custom_large_numbers( $formatted, $number ) {
182 global $wp_locale;
183
184 $decimals = 0;
185 $decimal_point = '.';
186 $thousands_sep = ',';
187
188 if ( isset( $wp_locale ) ) {
189 $decimals = (int) $wp_locale->number_format['decimal_point'];
190 $decimal_point = $wp_locale->number_format['decimal_point'];
191 $thousands_sep = $wp_locale->number_format['thousands_sep'];
192 }
193
194 if ( $number < 1000 ) { // Any number less than a Thousand.
195 return \number_format( $number, $decimals, $decimal_point, $thousands_sep );
196 } elseif ( $number < 1000000 ) { // Any number less than a million.
197 return \number_format( $number / 1000, $decimals, $decimal_point, $thousands_sep ) . 'K';
198 } elseif ( $number < 1000000000 ) { // Any number less than a billion.
199 return \number_format( $number / 1000000, $decimals, $decimal_point, $thousands_sep ) . 'M';
200 } else { // At least a billion.
201 return \number_format( $number / 1000000000, $decimals, $decimal_point, $thousands_sep ) . 'B';
202 }
203 }
204
205 /**
206 * Escapes a Tag, to be used as a hashtag.
207 *
208 * @param string $input The string to escape.
209 *
210 * @return string The escaped hashtag.
211 */
212 function esc_hashtag( $input ) {
213 $hashtag = \wp_specialchars_decode( $input, ENT_QUOTES );
214 // Remove all characters that are not letters, numbers, or hyphens.
215 $hashtag = \preg_replace( '/[^\p{L}\p{Nd}-]+/u', '-', $hashtag );
216
217 // Capitalize every letter that is preceded by a hyphen.
218 $hashtag = preg_replace_callback(
219 '/-+(.)/',
220 static function ( $matches ) {
221 return strtoupper( $matches[1] );
222 },
223 $hashtag
224 );
225
226 // Add a hashtag to the beginning of the string.
227 $hashtag = ltrim( $hashtag, '#' );
228 $hashtag = trim( $hashtag, '-' );
229 $hashtag = '#' . $hashtag;
230
231 /**
232 * Allow defining your own custom hashtag generation rules.
233 *
234 * @param string $hashtag The hashtag to be returned.
235 * @param string $input The original string.
236 */
237 $hashtag = apply_filters( 'activitypub_esc_hashtag', $hashtag, $input );
238
239 return esc_html( $hashtag );
240 }
241
242 /**
243 * Replace content with links, mentions or hashtags by Regex callback and not affect protected tags.
244 *
245 * @param string $content The content that should be changed.
246 * @param string $regex The regex to use.
247 * @param callable $regex_callback Callback for replacement logic.
248 *
249 * @return string The content with links, mentions, hashtags, etc.
250 */
251 function enrich_content_data( $content, $regex, $regex_callback ) {
252 // Small protection against execution timeouts: limit to 1 MB.
253 if ( mb_strlen( $content ) > MB_IN_BYTES ) {
254 return $content;
255 }
256 $tag_stack = array();
257 $protected_tags = array(
258 'pre',
259 'code',
260 'textarea',
261 'style',
262 'a',
263 );
264 $content_with_links = '';
265 $in_protected_tag = false;
266 foreach ( wp_html_split( $content ) as $chunk ) {
267 if ( preg_match( '#^<!--[\s\S]*-->$#i', $chunk, $m ) ) {
268 $content_with_links .= $chunk;
269 continue;
270 }
271
272 if ( preg_match( '#^<(/)?([a-z-]+)\b[^>]*>$#i', $chunk, $m ) ) {
273 $tag = strtolower( $m[2] );
274 if ( '/' === $m[1] ) {
275 // Closing tag.
276 $i = array_search( $tag, $tag_stack, true );
277 // We can only remove the tag from the stack if it is in the stack.
278 if ( false !== $i ) {
279 $tag_stack = array_slice( $tag_stack, 0, $i );
280 }
281 } else {
282 // Opening tag, add it to the stack.
283 $tag_stack[] = $tag;
284 }
285
286 // If we're in a protected tag, the tag_stack contains at least one protected tag string.
287 // The protected tag state can only change when we encounter a start or end tag.
288 $in_protected_tag = array_intersect( $tag_stack, $protected_tags );
289
290 // Never inspect tags.
291 $content_with_links .= $chunk;
292 continue;
293 }
294
295 if ( $in_protected_tag ) {
296 // Don't inspect a chunk inside an inspected tag.
297 $content_with_links .= $chunk;
298 continue;
299 }
300
301 // Only reachable when there is no protected tag in the stack.
302 $content_with_links .= \preg_replace_callback( $regex, $regex_callback, $chunk );
303 }
304
305 return $content_with_links;
306 }
307
308 /**
309 * Get an ActivityPub embed HTML for a URL.
310 *
311 * @param string $url The URL to get the embed for.
312 * @param boolean $inline_css Whether to inline CSS. Default true.
313 *
314 * @return string|false The embed HTML or false if not found.
315 */
316 function get_embed_html( $url, $inline_css = true ) {
317 return Embed::get_html( $url, $inline_css );
318 }
319