PluginProbe ʕ •ᴥ•ʔ
Advanced Ads – Ad Manager & AdSense / 2.0.26
Advanced Ads – Ad Manager & AdSense v2.0.26
2.0.26 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 / class-license-utils.php
advanced-ads / includes / license Last commit date
class-license-product-map.php 3 weeks ago class-license-shop-client.php 3 weeks ago class-license-site-activation.php 3 weeks ago class-license-utils.php 3 weeks ago class-license.php 3 weeks ago
class-license-utils.php
451 lines
1 <?php
2 /**
3 * License helper utilities.
4 *
5 * @package AdvancedAds
6 */
7
8 namespace AdvancedAds\License;
9
10 use AdvancedAds\Utilities\Addons;
11
12 defined( 'ABSPATH' ) || exit;
13
14 /**
15 * Stateless license helpers.
16 */
17 final class License_Utils {
18
19 /**
20 * Unix timestamp of the last successful license sync.
21 */
22 public const OPTION_LAST_SYNC = 'advanced-ads-license-last-sync';
23
24 /**
25 * Option: licenseKey => expiry notice level (month|ten_days).
26 */
27 public const OPTION_EXPIRY_NOTICES = 'advanced-ads-license-expiry-notices';
28
29 /**
30 * License admin screen URL with optional query args.
31 *
32 * @param array<string, int|string> $query_args Query arguments.
33 * @return string
34 */
35 public static function admin_screen_url( array $query_args = [] ): string {
36 $base = add_query_arg(
37 [
38 'page' => 'advanced-ads-app',
39 'path' => '/license',
40 ],
41 admin_url( 'admin.php' )
42 );
43
44 return $query_args ? add_query_arg( $query_args, $base ) : $base;
45 }
46
47 /**
48 * Normalize legacy license map.
49 *
50 * @param array<string, mixed> $legacy Raw option value.
51 * @return array<string, string>
52 */
53 public static function normalize_legacy_map( array $legacy ): array {
54 $out = [];
55 foreach ( $legacy as $id => $val ) {
56 if ( is_string( $val ) && '' !== $val ) {
57 $out[ (string) $id ] = $val;
58 continue;
59 }
60 if ( is_array( $val ) && ! empty( $val['license'] ) ) {
61 $out[ (string) $id ] = (string) $val['license'];
62 }
63 }
64
65 return $out;
66 }
67
68 /**
69 * Get expiry timestamp.
70 *
71 * @param array<string, mixed> $row Rich license row.
72 * @return int Zero when missing, lifetime, or unparseable.
73 */
74 public static function license_expiry_timestamp( array $row ): int {
75 $raw = strtolower( trim( (string) ( $row['expiryDate'] ?? '' ) ) );
76 if ( '' === $raw || 'lifetime' === $raw ) {
77 return 0;
78 }
79
80 $candidates = [
81 [ 'd-m-Y', str_replace( '/', '-', $raw ) ],
82 [ 'd/m/Y', $raw ],
83 ];
84 foreach ( $candidates as $candidate ) {
85 $format = $candidate[0];
86 $value = $candidate[1];
87 $dt = \DateTimeImmutable::createFromFormat( '!' . $format, $value );
88 if ( $dt instanceof \DateTimeImmutable && $dt->format( $format ) === $value ) {
89 return $dt->getTimestamp();
90 }
91 }
92
93 $ts = strtotime( str_replace( '/', '-', $raw ) );
94
95 return false === $ts ? 0 : $ts;
96 }
97
98 /**
99 * Check if license valid.
100 *
101 * @param array<string, mixed> $row Rich license row.
102 * @return bool
103 */
104 public static function license_expiry_is_future( array $row ): bool {
105 return self::license_expiry_timestamp( $row ) > time();
106 }
107
108 /**
109 * Last sync time.
110 *
111 * @return int
112 */
113 public static function get_last_sync(): int {
114 return (int) get_option( self::OPTION_LAST_SYNC, 0 );
115 }
116
117 /**
118 * Apply local expiry status.
119 *
120 * @param array<string, mixed> $row Rich license row.
121 * @return array<string, mixed>
122 */
123 public static function apply_local_expiry_to_row( array $row ): array {
124 $expiry_ts = self::license_expiry_timestamp( $row );
125 if ( $expiry_ts > 0 && $expiry_ts <= time() ) {
126 $row['status'] = 'expired';
127 }
128
129 return $row;
130 }
131
132 /**
133 * Expiring soon check.
134 *
135 * @param array<string, mixed> $row Rich license row.
136 * @param int $days Days threshold.
137 * @return bool
138 */
139 public static function is_license_expiring_soon( array $row, int $days = 30 ): bool {
140 $ts = self::license_expiry_timestamp( $row );
141
142 return $ts > time() && ( $ts - time() ) <= $days * DAY_IN_SECONDS;
143 }
144
145 /**
146 * Expiry milestones.
147 *
148 * @param array<string, mixed> $row Rich license row.
149 * @return array{month:int, ten_days:int, grace:int}
150 */
151 public static function license_expiry_timestamps( array $row ): array {
152 $expiry_ts = self::license_expiry_timestamp( $row );
153 if ( $expiry_ts <= 0 ) {
154 return [ 'month' => 0, 'ten_days' => 0, 'grace' => 0 ];
155 }
156
157 return [
158 'month' => $expiry_ts - 30 * DAY_IN_SECONDS,
159 'ten_days' => $expiry_ts - 10 * DAY_IN_SECONDS,
160 'grace' => $expiry_ts + 2 * HOUR_IN_SECONDS,
161 ];
162 }
163
164 /**
165 * Addon option slug.
166 *
167 * @param string $addon_id Short id.
168 * @return string
169 */
170 public static function options_slug_for_addon_id( string $addon_id ): string {
171 return ( defined( 'ADVADS_SLUG' ) ? ADVADS_SLUG : 'advanced-ads' ) . '-' . $addon_id;
172 }
173
174 /**
175 * Resolve short add-on id from options slug (advanced-ads-pro → pro).
176 *
177 * @param string $options_slug Options slug.
178 * @return string
179 */
180 public static function addon_id_from_options_slug( string $options_slug ): string {
181 $options_slug = trim( $options_slug );
182 if ( str_starts_with( $options_slug, 'advanced-ads-' ) ) {
183 return substr( $options_slug, strlen( 'advanced-ads-' ) );
184 }
185
186 return $options_slug;
187 }
188
189 /**
190 * Get expiry flags.
191 *
192 * @return array<string, string>
193 */
194 public static function get_expiry_notice_flags(): array {
195 $flags = get_option( self::OPTION_EXPIRY_NOTICES, [] );
196
197 return is_array( $flags ) ? $flags : [];
198 }
199
200 /**
201 * Update expiry notices.
202 *
203 * @param string $license_key License key; empty with null level clears all.
204 * @param string|null $level month|ten_days to set; null to clear one key.
205 * @return void
206 */
207 public static function update_expiry_notices( string $license_key, ?string $level ): void {
208 $license_key = trim( $license_key );
209
210 if ( '' === $license_key && null === $level ) {
211 delete_option( self::OPTION_EXPIRY_NOTICES );
212 return;
213 }
214
215 if ( '' === $license_key || ( null !== $level && ! in_array( $level, [ 'month', 'ten_days' ], true ) ) ) {
216 return;
217 }
218
219 if ( null === $level ) {
220 $flags = self::get_expiry_notice_flags();
221 if ( ! isset( $flags[ $license_key ] ) ) {
222 return;
223 }
224
225 unset( $flags[ $license_key ] );
226 update_option( self::OPTION_EXPIRY_NOTICES, $flags, false );
227 return;
228 }
229
230 $flags = self::get_expiry_notice_flags();
231 $flags[ $license_key ] = $level;
232 update_option( self::OPTION_EXPIRY_NOTICES, $flags, false );
233 }
234
235 /**
236 * Unique non-empty license keys from a legacy addon map.
237 *
238 * @param array<string, string> $map Normalized legacy map.
239 * @return string[]
240 */
241 public static function unique_legacy_keys( array $map ): array {
242 return array_values( array_unique( array_filter( array_map( 'strval', $map ) ) ) );
243 }
244
245 /**
246 * Whether every unique legacy key appears on at least one rich row.
247 *
248 * @param array<string, string> $map Normalized legacy map.
249 * @param array<int, array<string, mixed>> $rich Rich license list.
250 * @return bool
251 */
252 public static function rich_covers_legacy_keys( array $map, array $rich ): bool {
253 $needed = self::unique_legacy_keys( $map );
254 if ( [] === $needed ) {
255 return true;
256 }
257
258 $present = [];
259 foreach ( $rich as $row ) {
260 if ( ! is_array( $row ) ) {
261 continue;
262 }
263 $key = trim( (string) ( $row['licenseKey'] ?? '' ) );
264 if ( '' !== $key ) {
265 $present[ $key ] = true;
266 }
267 }
268
269 foreach ( $needed as $key ) {
270 if ( ! isset( $present[ $key ] ) ) {
271 return false;
272 }
273 }
274
275 return true;
276 }
277
278 /**
279 * Classify raw advanced-ads-licenses storage shape.
280 *
281 * @param array<string|int, mixed> $raw Raw option value.
282 * @return string `empty` | `list` | `compact` | `legacy`
283 */
284 public static function site_activation_storage_shape( array $raw ): string {
285 if ( [] === $raw ) {
286 return 'empty';
287 }
288
289 $is_list = true;
290
291 foreach ( $raw as $key => $val ) {
292 $addon_key = is_string( $key ) && ! is_numeric( $key ) && Addons::is_known_addon( (string) $key );
293
294 if ( $addon_key && is_array( $val ) ) {
295 return 'compact';
296 }
297
298 if ( is_string( $val ) && '' !== trim( $val ) ) {
299 $is_list = false;
300 continue;
301 }
302
303 if ( ! is_array( $val ) || '' === trim( (string) ( $val['license'] ?? '' ) ) ) {
304 $is_list = false;
305 continue;
306 }
307
308 if ( $addon_key ) {
309 $is_list = false;
310 }
311 }
312
313 return $is_list ? 'list' : 'legacy';
314 }
315
316 /**
317 * Whether stored value is a plan-level site activation list (not legacy flat strings).
318 *
319 * @param array<string|int, mixed> $raw Raw option value.
320 * @return bool
321 */
322 public static function is_site_activation_list_storage( array $raw ): bool {
323 return 'list' === self::site_activation_storage_shape( $raw );
324 }
325
326 /**
327 * Whether raw activation data is per-addon shaped and should be compacted to unique keys.
328 *
329 * @param array<string|int, mixed> $raw Raw option value.
330 * @return bool
331 */
332 public static function needs_site_activation_compaction( array $raw ): bool {
333 return 'compact' === self::site_activation_storage_shape( $raw );
334 }
335
336 /**
337 * Normalize plan-level site activation list: one row per unique license key.
338 *
339 * Accepts legacy flat strings, per-addon rows (compacted), or a numeric list.
340 *
341 * @param array<string|int, mixed> $raw Raw option value.
342 * @return array<int, array{license: string, status: string}>
343 */
344 public static function normalize_site_activation_list( array $raw ): array {
345 $by_key = [];
346
347 foreach ( $raw as $val ) {
348 if ( is_string( $val ) && '' !== trim( $val ) ) {
349 self::merge_site_activation_status( $by_key, trim( $val ), 'inactive' );
350 continue;
351 }
352 if ( ! is_array( $val ) ) {
353 continue;
354 }
355 $license = trim( (string) ( $val['license'] ?? '' ) );
356 if ( '' === $license ) {
357 continue;
358 }
359 $status = strtolower( trim( (string) ( $val['status'] ?? 'inactive' ) ) );
360 self::merge_site_activation_status( $by_key, $license, 'active' === $status ? 'active' : 'inactive' );
361 }
362
363 $out = [];
364 foreach ( $by_key as $license => $status ) {
365 $out[] = [
366 'license' => (string) $license,
367 'status' => $status,
368 ];
369 }
370
371 return $out;
372 }
373
374 /**
375 * Site activation status
376 *
377 * @param array<string, string> $by_key License => status accumulator.
378 * @param string $license License key.
379 * @param string $status active|inactive.
380 * @return void
381 */
382 private static function merge_site_activation_status( array &$by_key, string $license, string $status ): void {
383 if ( ! isset( $by_key[ $license ] ) ) {
384 $by_key[ $license ] = $status;
385 return;
386 }
387 if ( 'active' === $status ) {
388 $by_key[ $license ] = 'active';
389 }
390 }
391
392 /**
393 * Map legacy EDD mirror status to site activation status.
394 *
395 * @param mixed $status Legacy mirror value.
396 * @return string active|inactive
397 */
398 public static function legacy_mirror_status_to_activation( $status ): string {
399 return 'valid' === $status ? 'active' : 'inactive';
400 }
401
402 /**
403 * Whether outbound HTTPS to a URL should verify SSL certificates.
404 *
405 * Local dev hosts (.test, .local, localhost) skip verification for self-signed certs.
406 *
407 * @param string $url Request URL.
408 * @return bool
409 */
410 public static function should_verify_ssl_for_url( string $url ): bool {
411 $host = wp_parse_url( $url, PHP_URL_HOST );
412 if ( ! is_string( $host ) || '' === $host ) {
413 return true;
414 }
415
416 if ( in_array( $host, [ 'localhost', '127.0.0.1' ], true ) ) {
417 return false;
418 }
419
420 foreach ( [ '.test', '.local', '.localhost' ] as $local_tld ) {
421 if ( strlen( $host ) > strlen( $local_tld ) && str_ends_with( $host, $local_tld ) ) {
422 return false;
423 }
424 }
425
426 return true;
427 }
428
429 /**
430 * Find license by key.
431 *
432 * @param array<int, array<string, mixed>> $rich Rich license list (already loaded).
433 * @param string $license_key License key.
434 * @return array<string, mixed>|null
435 */
436 public static function get_rich_license_row_by_key( array $rich, string $license_key ): ?array {
437 $license_key = trim( $license_key );
438 if ( '' === $license_key ) {
439 return null;
440 }
441
442 foreach ( $rich as $row ) {
443 if ( is_array( $row ) && (string) ( $row['licenseKey'] ?? '' ) === $license_key ) {
444 return $row;
445 }
446 }
447
448 return null;
449 }
450 }
451