PluginProbe ʕ •ᴥ•ʔ
Advanced Ads – Ad Manager & AdSense / 2.0.25
Advanced Ads – Ad Manager & AdSense v2.0.25
2.0.25 2.0.24 2.0.23 2.0.22 2.0.21 1.38.0 1.39.0 1.39.1 1.39.2 1.39.3 1.39.4 1.4.0 1.4.1 1.4.2 1.4.3 1.4.4 1.4.5 1.4.6 1.4.7 1.4.8 1.4.9 1.40.0 1.40.1 1.40.2 1.41.0 1.42.0 1.42.1 1.42.2 1.43.0 1.44.0 1.44.1 1.45.0 1.45.1 1.46.0 1.47.0 1.47.1 1.47.2 1.47.3 1.47.4 1.47.5 1.48.0 1.48.1 1.49.0 1.5.0 1.5.0.1 1.5.1 1.5.2 1.5.2.1 1.5.4 1.5.4.1 1.5.5 1.50.0 1.51.0 1.51.1 1.51.2 1.51.3 1.52.0 1.52.1 1.52.2 1.52.3 1.52.4 1.53.0 1.53.1 1.53.2 1.54.0 1.54.1 1.55.0 1.56.0 1.56.1 1.56.2 1.56.3 1.56.4 1.6 1.6.1 1.6.10 1.6.10.1 1.6.10.2 1.6.11 1.6.11.1 1.6.12 1.6.13 1.6.14 1.6.15 1.6.16 1.6.17 1.6.17.1 1.6.17.2 1.6.2 1.6.2.1 1.6.3 1.6.4 1.6.4.1 1.6.5 1.6.6 1.6.6.1 1.6.7 1.6.7.1 1.6.8 1.6.8.1 1.6.8.2 1.6.8.3 1.6.9 1.6.9.1 1.6.9.2 1.6.9.3 1.6.9.4 1.7 1.7.0.1 1.7.0.2 1.7.0.3 1.7.1 1.7.1.1 1.7.1.2 1.7.1.3 1.7.1.4 1.7.1.5 1.7.10 trunk 1.7.11 1.0.1 1.7.12 1.0.2 1.7.13 1.0.3 1.7.14 1.1.0 1.7.15 1.1.1 1.7.16 1.1.2 1.7.17 1.1.3 1.7.18 1.10 1.7.19 1.10.1 1.7.2 1.10.10 1.7.2.1 1.10.11 1.7.20 1.10.12 1.7.21 1.10.2 1.7.22 1.10.3 1.7.23 1.10.4 1.7.24 1.10.5 1.7.25 1.10.6 1.7.3 1.10.7 1.7.4 1.10.8 1.7.4.1 1.10.9 1.7.4.2 1.11 1.7.4.3 1.11.1 1.7.4.4 1.11.2 1.7.4.5 1.12 1.7.5 1.13 1.7.5.1 1.13.1 1.7.6 1.13.2 1.7.7 1.13.3 1.7.8 1.13.4 1.7.9 1.13.5 1.7.9.1 1.13.6 1.7.9.2 1.13.7 1.7.9.3 1.13.8 1.8 1.14 1.8.1 1.14.1 1.8.10 1.14.10 1.8.11 1.14.11 1.8.12 1.14.2 1.8.13 1.14.3 1.8.14 1.14.4 1.8.15 1.14.5 1.8.16 1.14.6 1.8.17 1.14.7 1.8.18 1.14.8 1.8.19 1.14.9 1.8.2 1.15 1.8.20 1.16 1.8.21 1.16.1 1.8.22 1.17 1.8.23 1.17.1 1.8.24 1.17.10 1.8.25 1.17.10-rc.1 1.8.26 1.17.11 1.8.27 1.17.12 1.8.28 1.17.12-rc.1 1.8.29 1.17.2 1.8.3 1.17.3 1.8.30 1.17.4 1.8.4 1.17.5 1.8.5 1.17.6 1.8.6 1.17.7 1.8.7 1.17.8 1.8.8 1.17.9 1.8.9 1.17.9-beta.1 1.9 1.18.0 2.0.0 1.19.0 2.0.1 1.19.1 2.0.10 1.2 2.0.11 1.2.1 2.0.12 1.2.2 2.0.13 1.2.3 2.0.14 1.2.4 2.0.15 1.2.5 2.0.16 1.2.6 2.0.17 1.2.7 2.0.18 1.20.0 2.0.19 1.20.0-rc.1 2.0.2 1.20.0-rc.2 2.0.20 1.20.1 2.0.3 1.20.2 2.0.4 1.20.3 2.0.5 1.21.0 2.0.6 1.21.1 2.0.7 1.22.0 2.0.8 1.22.1 2.0.9 1.22.2 1.23.0 1.23.1 1.23.2 1.24.0 1.24.1 1.24.2 1.25.0 1.25.1 1.26.0 1.27.0 1.28.0 1.29.0 1.29.1 1.3 1.3.1 1.3.10 1.3.11 1.3.12 1.3.13 1.3.14 1.3.15 1.3.16 1.3.17 1.3.18 1.3.2 1.3.3 1.3.4 1.3.5 1.3.6 1.3.7 1.3.8 1.3.9 1.30.0 1.30.1 1.30.2 1.30.2-rc.1 1.30.3 1.30.4 1.30.4-rc.1 1.30.5 1.31.0 1.31.1 1.32.0 1.32.0-rc.1 1.33.0 1.33.1 1.33.2 1.34.0 1.35.0 1.35.1 1.36.0 1.36.1 1.36.2 1.36.3 1.37.0 1.37.1 1.37.2
advanced-ads / includes / license / utils.php
advanced-ads / includes / license Last commit date
class-license-exchange.php 1 day ago class-license-package-installer.php 1 day ago class-license-product-map.php 1 day ago class-license-shop-client.php 1 day ago class-license-site-activation.php 1 day ago class-license.php 1 day ago utils.php 1 day ago
utils.php
444 lines
1 <?php
2 /**
3 * License helper utilities.
4 *
5 * @package AdvancedAds
6 */
7
8 namespace AdvancedAds\License;
9
10 defined( 'ABSPATH' ) || exit;
11
12 /**
13 * Stateless license helpers.
14 */
15 final class License_Utils {
16
17 /**
18 * Unix timestamp of the last successful license sync.
19 */
20 public const OPTION_LAST_SYNC = 'advanced-ads-license-last-sync';
21
22 /**
23 * Option: licenseKey => expiry notice level (month|ten_days).
24 */
25 public const OPTION_EXPIRY_NOTICES = 'advanced-ads-license-expiry-notices';
26
27 /**
28 * License admin screen URL with optional query args.
29 *
30 * @param array<string, int|string> $query_args Query arguments.
31 * @return string
32 */
33 public static function admin_screen_url( array $query_args = [] ): string {
34 $base = add_query_arg(
35 [
36 'page' => 'advanced-ads-app',
37 'path' => '/license',
38 ],
39 admin_url( 'admin.php' )
40 );
41
42 return $query_args ? add_query_arg( $query_args, $base ) : $base;
43 }
44
45 /**
46 * Normalize legacy license map.
47 *
48 * @param array<string, mixed> $legacy Raw option value.
49 * @return array<string, string>
50 */
51 public static function normalize_legacy_map( array $legacy ): array {
52 $out = [];
53 foreach ( $legacy as $id => $val ) {
54 if ( is_string( $val ) && '' !== $val ) {
55 $out[ (string) $id ] = $val;
56 continue;
57 }
58 if ( is_array( $val ) && ! empty( $val['license'] ) ) {
59 $out[ (string) $id ] = (string) $val['license'];
60 }
61 }
62
63 return $out;
64 }
65
66 /**
67 * Get expiry timestamp.
68 *
69 * @param array<string, mixed> $row Rich license row.
70 * @return int Zero when missing, lifetime, or unparseable.
71 */
72 public static function license_expiry_timestamp( array $row ): int {
73 $raw = strtolower( trim( (string) ( $row['expiryDate'] ?? '' ) ) );
74 if ( '' === $raw || 'lifetime' === $raw ) {
75 return 0;
76 }
77
78 $candidates = [
79 [ 'd-m-Y', str_replace( '/', '-', $raw ) ],
80 [ 'd/m/Y', $raw ],
81 ];
82 foreach ( $candidates as $candidate ) {
83 $format = $candidate[0];
84 $value = $candidate[1];
85 $dt = \DateTimeImmutable::createFromFormat( '!' . $format, $value );
86 if ( $dt instanceof \DateTimeImmutable && $dt->format( $format ) === $value ) {
87 return $dt->getTimestamp();
88 }
89 }
90
91 $ts = strtotime( str_replace( '/', '-', $raw ) );
92
93 return false === $ts ? 0 : $ts;
94 }
95
96 /**
97 * Check if license valid.
98 *
99 * @param array<string, mixed> $row Rich license row.
100 * @return bool
101 */
102 public static function license_expiry_is_future( array $row ): bool {
103 return self::license_expiry_timestamp( $row ) > time();
104 }
105
106 /**
107 * Last sync time.
108 *
109 * @return int
110 */
111 public static function get_last_sync(): int {
112 return (int) get_option( self::OPTION_LAST_SYNC, 0 );
113 }
114
115 /**
116 * Apply local expiry status.
117 *
118 * @param array<string, mixed> $row Rich license row.
119 * @return array<string, mixed>
120 */
121 public static function apply_local_expiry_to_row( array $row ): array {
122 $expiry_ts = self::license_expiry_timestamp( $row );
123 if ( $expiry_ts > 0 && $expiry_ts <= time() ) {
124 $row['status'] = 'expired';
125 }
126
127 return $row;
128 }
129
130 /**
131 * Expiring soon check.
132 *
133 * @param array<string, mixed> $row Rich license row.
134 * @param int $days Days threshold.
135 * @return bool
136 */
137 public static function is_license_expiring_soon( array $row, int $days = 30 ): bool {
138 $ts = self::license_expiry_timestamp( $row );
139
140 return $ts > time() && ( $ts - time() ) <= $days * DAY_IN_SECONDS;
141 }
142
143 /**
144 * Expiry milestones.
145 *
146 * @param array<string, mixed> $row Rich license row.
147 * @return array{month:int, ten_days:int, grace:int}
148 */
149 public static function license_expiry_timestamps( array $row ): array {
150 $expiry_ts = self::license_expiry_timestamp( $row );
151 if ( $expiry_ts <= 0 ) {
152 return [ 'month' => 0, 'ten_days' => 0, 'grace' => 0 ];
153 }
154
155 return [
156 'month' => $expiry_ts - 30 * DAY_IN_SECONDS,
157 'ten_days' => $expiry_ts - 10 * DAY_IN_SECONDS,
158 'grace' => $expiry_ts + 2 * HOUR_IN_SECONDS,
159 ];
160 }
161
162 /**
163 * Addon option slug.
164 *
165 * @param string $addon_id Short id.
166 * @return string
167 */
168 public static function options_slug_for_addon_id( string $addon_id ): string {
169 return ( defined( 'ADVADS_SLUG' ) ? ADVADS_SLUG : 'advanced-ads' ) . '-' . $addon_id;
170 }
171
172 /**
173 * Get expiry flags.
174 *
175 * @return array<string, string>
176 */
177 public static function get_expiry_notice_flags(): array {
178 $flags = get_option( self::OPTION_EXPIRY_NOTICES, [] );
179
180 return is_array( $flags ) ? $flags : [];
181 }
182
183 /**
184 * Set expiry flag.
185 *
186 * @param string $license_key License key.
187 * @param string $level month|ten_days.
188 * @return void
189 */
190 public static function set_expiry_notice_flag( string $license_key, string $level ): void {
191 self::update_expiry_notices( $license_key, $level );
192 }
193
194 /**
195 * Update expiry notices.
196 *
197 * @param string $license_key License key; empty with null level clears all.
198 * @param string|null $level month|ten_days to set; null to clear one key.
199 * @return void
200 */
201 public static function update_expiry_notices( string $license_key, ?string $level ): void {
202 $license_key = trim( $license_key );
203
204 if ( '' === $license_key && null === $level ) {
205 delete_option( self::OPTION_EXPIRY_NOTICES );
206 return;
207 }
208
209 if ( '' === $license_key || ( null !== $level && ! in_array( $level, [ 'month', 'ten_days' ], true ) ) ) {
210 return;
211 }
212
213 if ( null === $level ) {
214 $flags = self::get_expiry_notice_flags();
215 if ( ! isset( $flags[ $license_key ] ) ) {
216 return;
217 }
218
219 unset( $flags[ $license_key ] );
220 update_option( self::OPTION_EXPIRY_NOTICES, $flags, false );
221 return;
222 }
223
224 $flags = self::get_expiry_notice_flags();
225 $flags[ $license_key ] = $level;
226 update_option( self::OPTION_EXPIRY_NOTICES, $flags, false );
227 }
228
229 /**
230 * Unique non-empty license keys from a legacy addon map.
231 *
232 * @param array<string, string> $map Normalized legacy map.
233 * @return string[]
234 */
235 public static function unique_legacy_keys( array $map ): array {
236 return array_values( array_unique( array_filter( array_map( 'strval', $map ) ) ) );
237 }
238
239 /**
240 * Whether every unique legacy key appears on at least one rich row.
241 *
242 * @param array<string, string> $map Normalized legacy map.
243 * @param array<int, array<string, mixed>> $rich Rich license list.
244 * @return bool
245 */
246 public static function rich_covers_legacy_keys( array $map, array $rich ): bool {
247 $needed = self::unique_legacy_keys( $map );
248 if ( [] === $needed ) {
249 return true;
250 }
251
252 $present = [];
253 foreach ( $rich as $row ) {
254 if ( ! is_array( $row ) ) {
255 continue;
256 }
257 $key = trim( (string) ( $row['licenseKey'] ?? '' ) );
258 if ( '' !== $key ) {
259 $present[ $key ] = true;
260 }
261 }
262
263 foreach ( $needed as $key ) {
264 if ( ! isset( $present[ $key ] ) ) {
265 return false;
266 }
267 }
268
269 return true;
270 }
271
272 /**
273 * Whether stored value is a plan-level site activation list (not legacy flat strings).
274 *
275 * @param array<string|int, mixed> $raw Raw option value.
276 * @return bool
277 */
278 public static function is_site_activation_list_storage( array $raw ): bool {
279 if ( [] === $raw ) {
280 return false;
281 }
282
283 foreach ( $raw as $key => $val ) {
284 if ( is_string( $val ) && '' !== trim( $val ) ) {
285 return false;
286 }
287 if ( ! is_array( $val ) || '' === trim( (string) ( $val['license'] ?? '' ) ) ) {
288 return false;
289 }
290 if ( is_string( $key ) && ! is_numeric( $key ) && self::looks_like_addon_id( (string) $key ) ) {
291 return false;
292 }
293 }
294
295 return true;
296 }
297
298 /**
299 * Whether raw activation data is per-addon shaped and should be compacted to unique keys.
300 *
301 * @param array<string|int, mixed> $raw Raw option value.
302 * @return bool
303 */
304 public static function needs_site_activation_compaction( array $raw ): bool {
305 foreach ( $raw as $key => $val ) {
306 if ( is_string( $key ) && ! is_numeric( $key ) && self::looks_like_addon_id( (string) $key ) && is_array( $val ) ) {
307 return true;
308 }
309 }
310
311 return false;
312 }
313
314 /**
315 * @param string $key Array key from option storage.
316 * @return bool
317 */
318 private static function looks_like_addon_id( string $key ): bool {
319 return in_array( $key, [ 'pro', 'gam', 'tracking', 'sticky', 'responsive', 'layer', 'selling', 'slider-ads' ], true );
320 }
321
322 /**
323 * Normalize plan-level site activation list: one row per unique license key.
324 *
325 * Accepts legacy flat strings, per-addon rows (compacted), or a numeric list.
326 *
327 * @param array<string|int, mixed> $raw Raw option value.
328 * @return array<int, array{license: string, status: string}>
329 */
330 public static function normalize_site_activation_list( array $raw ): array {
331 $by_key = [];
332
333 foreach ( $raw as $val ) {
334 if ( is_string( $val ) && '' !== trim( $val ) ) {
335 self::merge_site_activation_status( $by_key, trim( $val ), 'inactive' );
336 continue;
337 }
338 if ( ! is_array( $val ) ) {
339 continue;
340 }
341 $license = trim( (string) ( $val['license'] ?? '' ) );
342 if ( '' === $license ) {
343 continue;
344 }
345 $status = strtolower( trim( (string) ( $val['status'] ?? 'inactive' ) ) );
346 self::merge_site_activation_status( $by_key, $license, 'active' === $status ? 'active' : 'inactive' );
347 }
348
349 $out = [];
350 foreach ( $by_key as $license => $status ) {
351 $out[] = [
352 'license' => (string) $license,
353 'status' => $status,
354 ];
355 }
356
357 return $out;
358 }
359
360 /**
361 * @param array<string, string> $by_key License => status accumulator.
362 * @param string $license License key.
363 * @param string $status active|inactive.
364 * @return void
365 */
366 private static function merge_site_activation_status( array &$by_key, string $license, string $status ): void {
367 if ( ! isset( $by_key[ $license ] ) ) {
368 $by_key[ $license ] = $status;
369 return;
370 }
371 if ( 'active' === $status ) {
372 $by_key[ $license ] = 'active';
373 }
374 }
375
376 /**
377 * @deprecated Use {@see normalize_site_activation_list()}.
378 * @param array<string, mixed> $raw Raw option value.
379 * @return array<int, array{license: string, status: string}>
380 */
381 public static function normalize_activation_map( array $raw ): array {
382 return self::normalize_site_activation_list( $raw );
383 }
384
385 /**
386 * Map legacy EDD mirror status to site activation status.
387 *
388 * @param mixed $status Legacy mirror value.
389 * @return string active|inactive
390 */
391 public static function legacy_mirror_status_to_activation( $status ): string {
392 return 'valid' === $status ? 'active' : 'inactive';
393 }
394
395 /**
396 * Whether outbound HTTPS to a URL should verify SSL certificates.
397 *
398 * Local dev hosts (.test, .local, localhost) skip verification for self-signed certs.
399 *
400 * @param string $url Request URL.
401 * @return bool
402 */
403 public static function should_verify_ssl_for_url( string $url ): bool {
404 $host = wp_parse_url( $url, PHP_URL_HOST );
405 if ( ! is_string( $host ) || '' === $host ) {
406 return true;
407 }
408
409 if ( in_array( $host, [ 'localhost', '127.0.0.1' ], true ) ) {
410 return false;
411 }
412
413 foreach ( [ '.test', '.local', '.localhost' ] as $local_tld ) {
414 if ( strlen( $host ) > strlen( $local_tld ) && str_ends_with( $host, $local_tld ) ) {
415 return false;
416 }
417 }
418
419 return true;
420 }
421
422 /**
423 * Find license by key.
424 *
425 * @param array<int, array<string, mixed>> $rich Rich license list (already loaded).
426 * @param string $license_key License key.
427 * @return array<string, mixed>|null
428 */
429 public static function get_rich_license_row_by_key( array $rich, string $license_key ): ?array {
430 $license_key = trim( $license_key );
431 if ( '' === $license_key ) {
432 return null;
433 }
434
435 foreach ( $rich as $row ) {
436 if ( is_array( $row ) && (string) ( $row['licenseKey'] ?? '' ) === $license_key ) {
437 return $row;
438 }
439 }
440
441 return null;
442 }
443 }
444